video.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  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. var err error
  255. audioEdit := &models.Media{
  256. Id: req.VideoId,
  257. AuthorId: req.AnalystId,
  258. AuthorName: req.AnalystName,
  259. Src: req.SrcUrl,
  260. MediaName: req.VideoName,
  261. MediaPlayMilliseconds: req.DurationMillisecond,
  262. PermissionIds: req.PermissionIds,
  263. }
  264. //coverSrc
  265. _, err = models.UpdateMedia(audioEdit)
  266. if err != nil {
  267. br.Msg = "更新失败"
  268. br.ErrMsg = "视频更新失败,Err:" + err.Error()
  269. return
  270. }
  271. // 添加es
  272. go func(audio *models.ESMedia) {
  273. docId := strconv.Itoa(audio.MediaId)
  274. err = elastic.EsAddOrEditMedia(utils.MEDIA_INDEX, docId, audio)
  275. if err != nil {
  276. utils.FileLog.Info("视频记录es更新失败,Err:" + err.Error())
  277. return
  278. }
  279. utils.FileLog.Info("视频记录es更新成功, pdfId:" + docId)
  280. }(audioEdit.ToView())
  281. br.Msg = "编辑成功"
  282. br.Ret = 200
  283. br.Success = true
  284. }
  285. // DeleteVideo
  286. // @Title 删除音频
  287. //
  288. // @Description 删除音频
  289. // @Success 200 {object} models.ReportAuthorResp
  290. // @router /deleteVideo [post]
  291. func (this *VideoController) DeleteVideo() {
  292. br := new(models.BaseResponse).Init()
  293. defer func() {
  294. this.Data["json"] = br
  295. this.ServeJSON()
  296. }()
  297. var req request.VideoReq
  298. if err := json.Unmarshal(this.Ctx.Input.RequestBody, &req); err != nil {
  299. br.Msg = "参数解析失败"
  300. br.ErrMsg = "参数解析失败,Err:" + err.Error()
  301. return
  302. }
  303. if req.VideoId <= 0 {
  304. br.Msg = "视频ID为空"
  305. return
  306. }
  307. var err error
  308. audioDelete := &models.Media{
  309. Id: req.VideoId,
  310. Deleted: 1,
  311. }
  312. err = models.DeleteMedia(audioDelete)
  313. if err != nil {
  314. br.Msg = "删除失败"
  315. br.ErrMsg = "视频删除失败,Err:" + err.Error()
  316. return
  317. }
  318. // 添加es
  319. go func(audio *models.ESMedia) {
  320. docId := strconv.Itoa(audio.MediaId)
  321. err = elastic.EsDeleteData(utils.MEDIA_INDEX, docId)
  322. if err != nil {
  323. utils.FileLog.Info("视频记录es删除失败,Err:" + err.Error())
  324. return
  325. }
  326. utils.FileLog.Info("视频记录es删除成功, pdfId:" + docId)
  327. }(audioDelete.ToView())
  328. br.Msg = "删除成功"
  329. br.Ret = 200
  330. br.Success = true
  331. }
  332. // VideoList
  333. // @Title 研报列表
  334. // @Description pdf研报列表
  335. // @Param PageSize query int true "每页数据条数"
  336. // @Param CurrentIndex query int true "当前页页码,从1开始"
  337. // @Param ClassifyIds query string true "二级分类id,可多选用英文,隔开"
  338. // @Param KeyWord query string true "报告标题/创建人"
  339. // @Param SortType query string true "排序方式"
  340. // @Success 200 {object} models.ReportAuthorResp
  341. // @router /videoList [get]
  342. func (this *VideoController) VideoList() {
  343. br := new(models.BaseResponse).Init()
  344. defer func() {
  345. this.Data["json"] = br
  346. this.ServeJSON()
  347. }()
  348. pageSize, _ := this.GetInt("PageSize")
  349. currentIndex, _ := this.GetInt("CurrentIndex")
  350. sortType := this.GetString("SortType")
  351. var condition string
  352. var pars []interface{}
  353. if pageSize <= 0 {
  354. pageSize = utils.PageSize20
  355. }
  356. if currentIndex <= 0 {
  357. currentIndex = 1
  358. }
  359. sortCondition := " ORDER BY published_time "
  360. if sortType == "" {
  361. sortType = "DESC"
  362. }
  363. sortCondition = sortCondition + sortType
  364. total, err := models.GetMediaCountByCondition(models.Video, condition, pars)
  365. if err != nil {
  366. br.Msg = "获取视频列表失败"
  367. br.ErrMsg = "获取视频列表统计失败,Err:" + err.Error()
  368. return
  369. }
  370. startSize := utils.StartIndex(currentIndex, pageSize)
  371. List, err := models.GetMediaByCondition(models.Video, condition, sortCondition, pars, startSize, pageSize)
  372. if err != nil {
  373. br.Msg = "获取视频列表失败"
  374. br.ErrMsg = "获取视频列表失败,Err:" + err.Error()
  375. return
  376. }
  377. var reportViewList []*models.MediaView
  378. for _, report := range List {
  379. reportView := report.ToMediaView()
  380. reportViewList = append(reportViewList, reportView)
  381. }
  382. page := paging.GetPaging(currentIndex, pageSize, total)
  383. resp := new(response.MediaListResp)
  384. resp.List = reportViewList
  385. resp.Paging = page
  386. br.Ret = 200
  387. br.Success = true
  388. br.Data = resp
  389. br.Msg = "获取成功"
  390. }
  391. type BoxHeader struct {
  392. Size uint32
  393. FourccType [4]byte
  394. Size64 uint64
  395. }
  396. // GetMP4Duration 获取视频时长,以秒计
  397. func GetMP4Duration(reader io.ReaderAt) (lengthOfTime int, err error) {
  398. var info = make([]byte, 0x10)
  399. var boxHeader BoxHeader
  400. var offset int64 = 0
  401. // 获取moov结构偏移
  402. for {
  403. _, err = reader.ReadAt(info, offset)
  404. if err == io.EOF {
  405. fmt.Println("Reached EOF without finding moov box.")
  406. return 0, fmt.Errorf("moov box not found")
  407. }
  408. if err != nil {
  409. return 0, fmt.Errorf("error reading at offset %d: %w", offset, err)
  410. }
  411. boxHeader = getHeaderBoxInfo(info)
  412. fourccType := getFourccType(boxHeader)
  413. if fourccType == "moov" {
  414. fmt.Println("Found moov box at offset", offset)
  415. break
  416. }
  417. nextOffset := int64(boxHeader.Size)
  418. if fourccType == "mdat" && boxHeader.Size == 1 {
  419. nextOffset = int64(boxHeader.Size64)
  420. }
  421. if nextOffset == 0 {
  422. return 0, fmt.Errorf("box size is zero, which is invalid and likely means a parsing error")
  423. }
  424. offset += nextOffset
  425. }
  426. // 获取moov结构开头一部分
  427. moovStartBytes := make([]byte, 0x100)
  428. _, err = reader.ReadAt(moovStartBytes, offset)
  429. if err != nil {
  430. return 0, fmt.Errorf("error reading moov box at offset %d: %w", offset, err)
  431. }
  432. // 定义timeScale与Duration偏移
  433. timeScaleOffset := 0x1C
  434. durationOffset := 0x20
  435. timeScale := binary.BigEndian.Uint32(moovStartBytes[timeScaleOffset : timeScaleOffset+4])
  436. duration := binary.BigEndian.Uint32(moovStartBytes[durationOffset : durationOffset+4])
  437. fmt.Printf("timeScale: %d, duration: %d\n", timeScale, duration)
  438. if timeScale == 0 {
  439. return 0, fmt.Errorf("timeScale is zero, division by zero is not possible")
  440. }
  441. lengthOfTime = int(duration / timeScale)
  442. return
  443. }
  444. // getHeaderBoxInfo 获取头信息
  445. func getHeaderBoxInfo(data []byte) (boxHeader BoxHeader) {
  446. buf := bytes.NewBuffer(data)
  447. binary.Read(buf, binary.BigEndian, &boxHeader)
  448. if boxHeader.Size == 1 { // Large Size
  449. binary.Read(buf, binary.BigEndian, &boxHeader.Size64)
  450. }
  451. return
  452. }
  453. // getFourccType 获取信息头类型
  454. func getFourccType(boxHeader BoxHeader) (fourccType string) {
  455. return string(boxHeader.FourccType[:])
  456. }