video.go 13 KB

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