video.go 15 KB

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