video.go 15 KB

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