video.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. package controllers
  2. import (
  3. "bytes"
  4. "encoding/binary"
  5. "encoding/json"
  6. "eta/eta_mini_crm_ht/models"
  7. "eta/eta_mini_crm_ht/models/request"
  8. "eta/eta_mini_crm_ht/models/response"
  9. "eta/eta_mini_crm_ht/services"
  10. "eta/eta_mini_crm_ht/services/elastic"
  11. "eta/eta_mini_crm_ht/utils"
  12. "fmt"
  13. "github.com/rdlucklib/rdluck_tools/paging"
  14. "io"
  15. "os"
  16. "path"
  17. "strconv"
  18. "strings"
  19. "time"
  20. )
  21. var (
  22. videoType = map[string]int{
  23. ".mp4": 1,
  24. }
  25. )
  26. type VideoController struct {
  27. BaseAuthController
  28. }
  29. // UploadVideo @Title 上传视频
  30. // @Description 上传视频
  31. // @Param File query file true "文件"
  32. // @Success 200 {object} models.ReportAuthorResp
  33. // @router /uploadVideo [post]
  34. func (this *VideoController) UploadVideo() {
  35. br := new(models.BaseResponse).Init()
  36. defer func() {
  37. this.Data["json"] = br
  38. this.ServeJSON()
  39. }()
  40. f, h, err := this.GetFile("File")
  41. if err != nil {
  42. br.Msg = "获取资源信息失败"
  43. br.ErrMsg = "获取资源信息失败,Err:" + err.Error()
  44. return
  45. }
  46. defer f.Close()
  47. size, err := strconv.Atoi(utils.UPLOAD_VIDEO_SIZE)
  48. if err != nil {
  49. size = 1024
  50. }
  51. if h.Size > 1024*1024*int64(size) {
  52. br.Msg = fmt.Sprintf("视频大小不能超过%dM", size)
  53. br.ErrMsg = "视频上传失败,Err:" + err.Error()
  54. return
  55. }
  56. ext := path.Ext(h.Filename)
  57. lowCaseExt := strings.ToLower(ext)
  58. if _, ok := videoType[lowCaseExt]; !ok {
  59. br.Msg = "视频格式不正确"
  60. br.ErrMsg = "视频上传失败,Err:" + err.Error()
  61. return
  62. }
  63. dateDir := time.Now().Format("20060102")
  64. uploadDir := utils.STATIC_DIR + "ht/audio" + dateDir
  65. err = os.MkdirAll(uploadDir, utils.DIR_MOD)
  66. if err != nil {
  67. br.Msg = "存储目录创建失败"
  68. br.ErrMsg = "存储目录创建失败,Err:" + err.Error()
  69. return
  70. }
  71. randStr := utils.GetRandStringNoSpecialChar(28)
  72. fileName := randStr + ext
  73. fpath := uploadDir + "/" + fileName
  74. err = this.SaveToFile("File", fpath)
  75. if err != nil {
  76. br.Msg = "视频上传失败"
  77. br.ErrMsg = "视频上传失败,Err:" + err.Error()
  78. return
  79. }
  80. duration, err := GetMP4Duration(f)
  81. if err != nil {
  82. br.Msg = "视频上传失败"
  83. br.ErrMsg = "获取视频时长失败,Err:" + err.Error()
  84. return
  85. }
  86. audioUploadDir := utils.RESOURCE_DIR + "video/"
  87. savePdfToOssPath := audioUploadDir + time.Now().Format("200601/20060102/")
  88. audioName := utils.GetRandStringNoSpecialChar(28)
  89. savePdfToOssPath += audioName + ext
  90. defer func() {
  91. err = os.Remove(fpath)
  92. fmt.Sprintf("删除文件失败:%v", err)
  93. }()
  94. ossClient := services.NewOssClient()
  95. if ossClient == nil {
  96. br.Msg = "视频上传失败"
  97. br.ErrMsg = "初始化OSS服务失败"
  98. return
  99. }
  100. mp3Url, err := ossClient.UploadFile("", fpath, savePdfToOssPath)
  101. if err != nil {
  102. br.Msg = "视频上传失败"
  103. br.ErrMsg = "视频上传失败,Err:" + err.Error()
  104. return
  105. }
  106. base := path.Base(h.Filename)
  107. resp := new(response.MediaUploadResp)
  108. resp.Url = mp3Url
  109. resp.FileName = base
  110. resp.DurationMillisecond = duration * 1000
  111. br.Data = resp
  112. br.Msg = "上传成功"
  113. br.Ret = 200
  114. br.Success = true
  115. }
  116. // AddVideo @Title 新增音频
  117. // @Description 新增音频
  118. // @Param File query file true "文件"
  119. // @Success 200 {object} models.ReportAuthorResp
  120. // @router /addVideo [post]
  121. func (this *VideoController) AddVideo() {
  122. br := new(models.BaseResponse).Init()
  123. defer func() {
  124. this.Data["json"] = br
  125. this.ServeJSON()
  126. }()
  127. var req request.VideoReq
  128. if err := json.Unmarshal(this.Ctx.Input.RequestBody, &req); err != nil {
  129. br.Msg = "参数解析失败"
  130. br.ErrMsg = "参数解析失败,Err:" + err.Error()
  131. return
  132. }
  133. if req.VideoName == "" {
  134. br.Msg = "视频名称为空"
  135. return
  136. }
  137. if req.AnalystId == 0 {
  138. br.Msg = "研究员ID为空"
  139. return
  140. }
  141. if req.AnalystName == "" {
  142. br.Msg = "研究员名称为空"
  143. return
  144. }
  145. if req.SrcUrl == "" {
  146. br.Msg = "视频地址为空"
  147. return
  148. }
  149. if req.DurationMillisecond == 0 {
  150. br.Msg = "视频时长为空"
  151. return
  152. }
  153. if req.CoverSrc == "" {
  154. br.Msg = "封面为空"
  155. return
  156. }
  157. var err error
  158. audioInsert := &models.Media{
  159. AuthorId: req.AnalystId,
  160. AuthorName: req.AnalystName,
  161. MediaType: models.Video,
  162. Src: req.SrcUrl,
  163. CoverSrc: req.CoverSrc,
  164. MediaName: req.VideoName,
  165. SourceType: "mp4",
  166. MediaPlayMilliseconds: req.DurationMillisecond,
  167. PermissionIds: req.PermissionIds,
  168. PublishedTime: time.Now(),
  169. SendStatus: models.UNSEND,
  170. Deleted: 0,
  171. CreatedTime: time.Now(),
  172. }
  173. err = models.InsertMedia(audioInsert)
  174. if err != nil {
  175. br.Msg = "添加失败"
  176. br.ErrMsg = "音频新增失败,Err:" + err.Error()
  177. return
  178. }
  179. // 添加es
  180. go func(audio *models.ESMedia) {
  181. docId := strconv.Itoa(audio.MediaId)
  182. err = elastic.EsAddOrEditMedia(utils.MEDIA_INDEX, docId, audio)
  183. if err != nil {
  184. utils.FileLog.Info("音频记录es新增失败,Err:" + err.Error())
  185. return
  186. }
  187. utils.FileLog.Info("音频记录es新增成功, pdfId:" + docId)
  188. }(audioInsert.ToView())
  189. // 创建消息
  190. _, _ = services.CreateMeta(audioInsert.AuthorName, audioInsert.AuthorId, audioInsert.Id, audioInsert.PublishedTime.Format(time.DateTime), models.VideoSourceType)
  191. br.Msg = "添加成功"
  192. br.Ret = 200
  193. br.Success = true
  194. }
  195. // EditVideo @Title 编辑音频
  196. //
  197. // @Description 编辑音频
  198. // @Success 200 {object} models.ReportAuthorResp
  199. // @router /editVideo [post]
  200. func (this *VideoController) EditVideo() {
  201. br := new(models.BaseResponse).Init()
  202. defer func() {
  203. this.Data["json"] = br
  204. this.ServeJSON()
  205. }()
  206. var req request.VideoReq
  207. if err := json.Unmarshal(this.Ctx.Input.RequestBody, &req); err != nil {
  208. br.Msg = "参数解析失败"
  209. br.ErrMsg = "参数解析失败,Err:" + err.Error()
  210. return
  211. }
  212. if req.VideoId <= 0 {
  213. br.Msg = "视频ID为空"
  214. return
  215. }
  216. if req.VideoName == "" {
  217. br.Msg = "视频名称为空"
  218. return
  219. }
  220. if req.AnalystId == 0 {
  221. br.Msg = "研究员ID为空"
  222. return
  223. }
  224. if req.AnalystName == "" {
  225. br.Msg = "研究员名称为空"
  226. return
  227. }
  228. if req.SrcUrl == "" {
  229. br.Msg = "视频地址为空"
  230. return
  231. }
  232. if req.DurationMillisecond == 0 {
  233. br.Msg = "视频时长为空"
  234. return
  235. }
  236. if req.CoverSrc == "" {
  237. br.Msg = "封面为空"
  238. return
  239. }
  240. var err error
  241. audioEdit := &models.Media{
  242. Id: req.VideoId,
  243. AuthorId: req.AnalystId,
  244. AuthorName: req.AnalystName,
  245. MediaType: models.Video,
  246. CoverSrc: req.CoverSrc,
  247. Src: req.SrcUrl,
  248. SourceType: "mp4",
  249. MediaName: req.VideoName,
  250. MediaPlayMilliseconds: req.DurationMillisecond,
  251. PermissionIds: req.PermissionIds,
  252. }
  253. //coverSrc
  254. _, err = models.UpdateMedia(audioEdit)
  255. if err != nil {
  256. br.Msg = "更新失败"
  257. br.ErrMsg = "视频更新失败,Err:" + err.Error()
  258. return
  259. }
  260. go func(audio *models.ESMedia) {
  261. docId := strconv.Itoa(audio.MediaId)
  262. err = elastic.EsAddOrEditMedia(utils.MEDIA_INDEX, docId, audio)
  263. if err != nil {
  264. utils.FileLog.Info("视频记录es更新失败,Err:" + err.Error())
  265. return
  266. }
  267. utils.FileLog.Info("视频记录es更新成功, pdfId:" + docId)
  268. }(audioEdit.ToView())
  269. br.Msg = "编辑成功"
  270. br.Ret = 200
  271. br.Success = true
  272. }
  273. // DeleteVideo
  274. // @Title 删除音频
  275. //
  276. // @Description 删除音频
  277. // @Success 200 {object} models.ReportAuthorResp
  278. // @router /deleteVideo [post]
  279. func (this *VideoController) DeleteVideo() {
  280. br := new(models.BaseResponse).Init()
  281. defer func() {
  282. this.Data["json"] = br
  283. this.ServeJSON()
  284. }()
  285. var req request.VideoReq
  286. if err := json.Unmarshal(this.Ctx.Input.RequestBody, &req); err != nil {
  287. br.Msg = "参数解析失败"
  288. br.ErrMsg = "参数解析失败,Err:" + err.Error()
  289. return
  290. }
  291. if req.VideoId <= 0 {
  292. br.Msg = "视频ID为空"
  293. return
  294. }
  295. var err error
  296. audioDelete := &models.Media{
  297. Id: req.VideoId,
  298. Deleted: 1,
  299. }
  300. err = models.DeleteMedia(audioDelete)
  301. if err != nil {
  302. br.Msg = "删除失败"
  303. br.ErrMsg = "视频删除失败,Err:" + err.Error()
  304. return
  305. }
  306. // 添加es
  307. go func(audio *models.ESMedia) {
  308. docId := strconv.Itoa(audio.MediaId)
  309. err = elastic.EsDeleteData(utils.MEDIA_INDEX, docId)
  310. if err != nil {
  311. utils.FileLog.Info("视频记录es删除失败,Err:" + err.Error())
  312. return
  313. }
  314. utils.FileLog.Info("视频记录es删除成功, pdfId:" + docId)
  315. }(audioDelete.ToView())
  316. br.Msg = "删除成功"
  317. br.Ret = 200
  318. br.Success = true
  319. }
  320. // VideoList
  321. // @Title 研报列表
  322. // @Description pdf研报列表
  323. // @Param PageSize query int true "每页数据条数"
  324. // @Param CurrentIndex query int true "当前页页码,从1开始"
  325. // @Param ClassifyIds query string true "二级分类id,可多选用英文,隔开"
  326. // @Param KeyWord query string true "报告标题/创建人"
  327. // @Param SortType query string true "排序方式"
  328. // @Success 200 {object} models.ReportAuthorResp
  329. // @router /videoList [get]
  330. func (this *VideoController) VideoList() {
  331. br := new(models.BaseResponse).Init()
  332. defer func() {
  333. this.Data["json"] = br
  334. this.ServeJSON()
  335. }()
  336. pageSize, _ := this.GetInt("PageSize")
  337. currentIndex, _ := this.GetInt("CurrentIndex")
  338. sortType := this.GetString("SortType")
  339. KeyWord := this.GetString("KeyWord")
  340. var condition string
  341. var pars []interface{}
  342. if pageSize <= 0 {
  343. pageSize = utils.PageSize20
  344. }
  345. if currentIndex <= 0 {
  346. currentIndex = 1
  347. }
  348. if KeyWord != "" {
  349. condition += " AND media_name like '%" + KeyWord + "%'"
  350. }
  351. sortCondition := " ORDER BY published_time "
  352. if sortType == "" {
  353. sortType = "DESC"
  354. }
  355. sortCondition = sortCondition + sortType
  356. total, err := models.GetMediaCountByCondition(models.Video, condition, pars)
  357. if err != nil {
  358. br.Msg = "获取视频列表失败"
  359. br.ErrMsg = "获取视频列表统计失败,Err:" + err.Error()
  360. return
  361. }
  362. startSize := utils.StartIndex(currentIndex, pageSize)
  363. List, err := models.GetMediaByCondition(models.Video, condition, sortCondition, pars, startSize, pageSize)
  364. if err != nil {
  365. br.Msg = "获取视频列表失败"
  366. br.ErrMsg = "获取视频列表失败,Err:" + err.Error()
  367. return
  368. }
  369. var reportViewList []*models.MediaView
  370. for _, report := range List {
  371. reportView := report.ToMediaView()
  372. reportViewList = append(reportViewList, reportView)
  373. }
  374. page := paging.GetPaging(currentIndex, pageSize, total)
  375. resp := new(response.MediaListResp)
  376. resp.List = reportViewList
  377. resp.Paging = page
  378. br.Ret = 200
  379. br.Success = true
  380. br.Data = resp
  381. br.Msg = "获取成功"
  382. }
  383. type BoxHeader struct {
  384. Size uint32
  385. FourccType [4]byte
  386. Size64 uint64
  387. }
  388. // GetMP4Duration 获取视频时长,以秒计
  389. func GetMP4Duration(reader io.ReaderAt) (lengthOfTime int, err error) {
  390. var info = make([]byte, 0x10)
  391. var boxHeader BoxHeader
  392. var offset int64 = 0
  393. // 获取moov结构偏移
  394. for {
  395. _, err = reader.ReadAt(info, offset)
  396. if err == io.EOF {
  397. fmt.Println("Reached EOF without finding moov box.")
  398. return 0, fmt.Errorf("moov box not found")
  399. }
  400. if err != nil {
  401. return 0, fmt.Errorf("error reading at offset %d: %w", offset, err)
  402. }
  403. boxHeader = getHeaderBoxInfo(info)
  404. fourccType := getFourccType(boxHeader)
  405. if fourccType == "moov" {
  406. fmt.Println("Found moov box at offset", offset)
  407. break
  408. }
  409. nextOffset := int64(boxHeader.Size)
  410. if fourccType == "mdat" && boxHeader.Size == 1 {
  411. nextOffset = int64(boxHeader.Size64)
  412. }
  413. if nextOffset == 0 {
  414. return 0, fmt.Errorf("box size is zero, which is invalid and likely means a parsing error")
  415. }
  416. offset += nextOffset
  417. }
  418. // 获取moov结构开头一部分
  419. moovStartBytes := make([]byte, 0x100)
  420. _, err = reader.ReadAt(moovStartBytes, offset)
  421. if err != nil {
  422. return 0, fmt.Errorf("error reading moov box at offset %d: %w", offset, err)
  423. }
  424. // 定义timeScale与Duration偏移
  425. timeScaleOffset := 0x1C
  426. durationOffset := 0x20
  427. timeScale := binary.BigEndian.Uint32(moovStartBytes[timeScaleOffset : timeScaleOffset+4])
  428. duration := binary.BigEndian.Uint32(moovStartBytes[durationOffset : durationOffset+4])
  429. fmt.Printf("timeScale: %d, duration: %d\n", timeScale, duration)
  430. if timeScale == 0 {
  431. return 0, fmt.Errorf("timeScale is zero, division by zero is not possible")
  432. }
  433. lengthOfTime = int(duration / timeScale)
  434. return
  435. }
  436. // getHeaderBoxInfo 获取头信息
  437. func getHeaderBoxInfo(data []byte) (boxHeader BoxHeader) {
  438. buf := bytes.NewBuffer(data)
  439. binary.Read(buf, binary.BigEndian, &boxHeader)
  440. if boxHeader.Size == 1 { // Large Size
  441. binary.Read(buf, binary.BigEndian, &boxHeader.Size64)
  442. }
  443. return
  444. }
  445. // UploadFile @Title 上传图片
  446. // @Description 上传视频
  447. // @Param File query file true "文件"
  448. // @Success 200 {object} models.ReportAuthorResp
  449. // @router /uploadFile [post]
  450. func (this *VideoController) UploadFile() {
  451. br := new(models.BaseResponse).Init()
  452. defer func() {
  453. this.Data["json"] = br
  454. this.ServeJSON()
  455. }()
  456. f, h, err := this.GetFile("File")
  457. if err != nil {
  458. br.Msg = "获取资源信息失败"
  459. br.ErrMsg = "获取资源信息失败,Err:" + err.Error()
  460. return
  461. }
  462. defer f.Close()
  463. size, err := strconv.Atoi(utils.UPLOAD_IMG_SIZE)
  464. if err != nil {
  465. size = 100
  466. }
  467. if h.Size > 1024*1024*int64(size) {
  468. br.Msg = fmt.Sprintf("图片大小不能超过%dK", size)
  469. br.ErrMsg = "图片上传失败,Err:" + err.Error()
  470. return
  471. }
  472. ext := path.Ext(h.Filename)
  473. dateDir := time.Now().Format("20060102")
  474. uploadDir := utils.STATIC_DIR + "ht/audio" + dateDir
  475. err = os.MkdirAll(uploadDir, utils.DIR_MOD)
  476. if err != nil {
  477. br.Msg = "存储目录创建失败"
  478. br.ErrMsg = "存储目录创建失败,Err:" + err.Error()
  479. return
  480. }
  481. randStr := utils.GetRandStringNoSpecialChar(28)
  482. fileName := randStr + ext
  483. fpath := uploadDir + "/" + fileName
  484. err = this.SaveToFile("File", fpath)
  485. if err != nil {
  486. br.Msg = "图片上传失败"
  487. br.ErrMsg = "图片上传失败,Err:" + err.Error()
  488. return
  489. }
  490. audioUploadDir := utils.RESOURCE_DIR + "img/"
  491. savePdfToOssPath := audioUploadDir + time.Now().Format("200601/20060102/")
  492. audioName := utils.GetRandStringNoSpecialChar(28)
  493. savePdfToOssPath += audioName + ext
  494. defer func() {
  495. err = os.Remove(fpath)
  496. fmt.Sprintf("删除文件失败:%v", err)
  497. }()
  498. ossClient := services.NewOssClient()
  499. if ossClient == nil {
  500. br.Msg = "图片上传失败"
  501. br.ErrMsg = "初始化OSS服务失败"
  502. return
  503. }
  504. mp3Url, err := ossClient.UploadFile("", fpath, savePdfToOssPath)
  505. if err != nil {
  506. br.Msg = "图片上传失败"
  507. br.ErrMsg = "图片上传失败,Err:" + err.Error()
  508. return
  509. }
  510. base := path.Base(h.Filename)
  511. resp := new(response.MediaUploadResp)
  512. resp.Url = mp3Url
  513. resp.FileName = base
  514. br.Data = resp
  515. br.Msg = "上传成功"
  516. br.Ret = 200
  517. br.Success = true
  518. }
  519. type Chunk struct {
  520. Filename string
  521. UniqueCode string
  522. ChunkNumber int
  523. TotalChunks int
  524. Data []byte
  525. }
  526. var chunksMap = make(map[string][]Chunk)
  527. // getFourccType 获取信息头类型
  528. func getFourccType(boxHeader BoxHeader) (fourccType string) {
  529. return string(boxHeader.FourccType[:])
  530. }