抖音大项目视频模块介绍(2)| 青训营笔记

131 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 7 天

视频投稿

需求

登录用户可以投稿发布视频,自定义视频标题,系统自动生成视频封面。

  • 基本流程
    1. 用户点击“+”进入视频发布页面,填写描述信息,选择上传视频,点击发布。后端接收请求,开始业务逻辑。
    2. rpc调用VideoService,利用对象存储将视频、视频封面上传到minIO服务器,利用ffmpeg截取视频的第一帧作为封面,同时将user_id、视频url、封面url写入mysql数据库。
    3. 后端回复前端上传成功。
业务流程图

image.png

核心代码

采用单例模式作为设计模式,在rpcService中的handler进行实例化。

/*
    /cmd/video/service/publish_video.go
*/
type PublishVideoService struct {
   ctx context.Context
}

func NewPublishVideoService(ctx context.Context) *PublishVideoService {
   return &PublishVideoService{ctx: ctx}
}

func (p *PublishVideoService) PublishVideo(req *douyinvideo.DouyinPublishActionRequest) error {
   /*
       1.首先将传过来的数据写入到minio
       2.将minio中视频的url写入到数据库
       3.制作视频封面并同样上传至minio、写入url至数据库
   */
   client, err := minio.New(
      consts.Endpoint,
      consts.AccessKeyID,
      consts.SecretAccessKey,
      consts.UseSSL,
   )
   if err != nil {
      log.Print(err)
      return err
   }
   //视频的命名方式为: user_id + title
   n, err := client.PutObjectWithContext(
      p.ctx,
      consts.BucketName,
      req.Token+"_"+req.Title+".mp4",
      bytes.NewBuffer(req.Data),
      int64(len(req.Data)),
      minio.PutObjectOptions{ContentType: "video/mp4"},
   )
   if err != nil {
      log.Print("上传失败")
      log.Print(err)
      return err
   }
   
   //记录上传到服务器的时间
   t := time.Now().Unix()
   log.Printf("upload to minio %s of size %d", req.Token+req.Title, n)
   fmt.Println("object name:", req.Token+"_"+req.Title+".mp4")
   err = client.FGetObjectWithContext(
      p.ctx,
      consts.BucketName,
      req.Token+"_"+req.Title+".mp4",
      consts.TempVideoFilePath+req.Token+"_"+req.Title+".mp4",
      minio.GetObjectOptions{},
   )
   if err != nil {
      log.Print("下载失败:", err)
      return err
   }
   
   //利用ffmpeg制作视频封面
   imgPath, err := ffmpegPicture(consts.TempVideoFilePath + req.Token + "_" + req.Title)
   if err != nil {
      log.Print(err)
      return err
   }
   fmt.Println("截图成功: ", imgPath)

   picObject, err := client.FPutObjectWithContext(
      p.ctx,
      consts.BucketName,
      req.Token+"_"+req.Title+".png",
      imgPath,
      minio.PutObjectOptions{ContentType: "image/png"},
   )
   if err != nil {
      log.Print("上传截图失败:", err)
      return err
   }
   fmt.Printf("Successfully uploaded %s of size %d\n", req.Token+req.Title+".png", picObject)

   err = os.Remove(consts.TempVideoFilePath + req.Token + "_" + req.Title + ".mp4")
   if err != nil {
      log.Print("删除本地视频文件失败:", err)
      return err
   }
   err = os.Remove(consts.TempVideoFilePath + req.Token + "_" + req.Title + ".png")
   if err != nil {
      log.Print("删除本地视频截图失败:", err)
      return err
   }

   //将相关信息写入数据库
   var video = douyinvideo.Video{
      Id:            0,
      Author:        nil,
      PlayUrl:       consts.HttpTemplate + consts.LocalIp + consts.MinioPort + "/" + consts.BucketName + "/" + req.Token + "_" + req.Title + ".mp4",
      CoverUrl:      consts.HttpTemplate + consts.LocalIp + consts.MinioPort + "/" + consts.BucketName + "/" + req.Token + "_" + req.Title + ".png",
      FavoriteCount: 0,
      CommentCount:  0,
      IsFavorite:    false,
      Title:         req.Title,
   }
   token, _ := strconv.Atoi(req.Token)

   err = db.InsertVideo(p.ctx, &video, token, t)
   if err != nil {
      log.Print("insert video failed: ", err)
      return err
   }

   return nil
}

这一部分采用了minio分布式对象存储,并且运用ffmpeg对视频帧进行截取,制作视频封面。