video.go 15 KB

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