video.go 15 KB

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