video.go 15 KB

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