大项目日记-FFmpeg实现封面截取 | 青训营笔记

174 阅读2分钟

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

0. 问题来源

在这次的青训营后端大项目中,我们需要为用户上传的稿件生成相应的的稿件封面,这就需要我们从视频中截取某一帧来作为稿件封面。

为此我们可以使用,ffmpeg获取指定视频某一帧来生成截图,并将截图储存在数据库,这篇笔记将从ffmpeg的安装配置、go环境配置、相关思路、实现代码等几个部分出发

1. 安装配置

sudo apt-get install ffmpeg

2. go环境配置

对于 ffmpeg 有十分完善的第三方库来供我们使用,我们只需要直接获取即可。

go get -u github.com/u2takey/ffmpeg-go 
go get -u github.com/disintegration/imaging

3. 实现思路

首先将PublishVideoHandler中提交的视频储存在本地,之后调用GetSnapShot()将对应地址的视频及图片进行提取

传入参数:

  1. videoPath 待截取视频路径
  2. snapShotPath 截图存储路径
  3. snapShotName 截图命名
  4. frameNum 指定的视频截取帧数

实现代码

func SnapShotFromVideo(videoPath, snapShotPath string, frameNum int) (err error) {
   buf := bytes.NewBuffer(nil)
   err = ffmpeg.Input(videoPath).
      Filter("select", ffmpeg.Args{fmt.Sprintf("gte(n,%d)", frameNum)}).
      Output("pipe:", ffmpeg.KwArgs{"vframes": 1, "format": "image2", "vcodec": "mjpeg"}).
      WithOutput(buf, os.Stdout).
      Run()
   if err != nil {
      log.Fatal("生成缩略图失败:", err)
      return err
   }

   img, err := imaging.Decode(buf)
   if err != nil {
      log.Fatal("生成缩略图失败:", err)
      return err
   }

   err = imaging.Save(img, snapShotPath)
   if err != nil {
      log.Fatal("生成缩略图失败:", err)
      return err
   }
   return nil
}

由于我们必须要确保生成的视频封面的截图唯一,即避免重复,为此,我们可以在视频封面的名字后加上一串后缀

由于之前我在User的结构体中将userid与username均设置为了主键 这也就意味着我们可以使用uid来特殊标识某一用户提交的视频

但是为了更好地区分每个用户所提交的视频,我们还可以使用用户提交的视频数量Cnt来进行区分

代码实现如下

func NewUnicFileName(userid int64) string {
   var count int64
   if err := models.NewVideoDao().QueryVideoCntByUserId(userid, &count); err != nil {
      log.Println(err)
   }
   return fmt.Sprintf("%d-%d", userid, count)
}

4. 补充思考

对于大项目的应用场景,抖音极简版,如果每次上传稿件都需要我们通过查询数据库来获得这份投稿是用户的第几份稿件,这无疑会对我们的数据可产生巨大的负担,为此我们不禁思考有没有直接生成一个唯一稿件 ID 的算法呢。
雪花算法便可以很好的满足我们的需求。

在本次大项目中的实现代码如下:

package util

import (
   "ByteDance_5th/pkg/errortype"
   "errors"
   "sync"
   "time"
)

const (
   workerBits  uint8 = 10
   numberBits  uint8 = 10
   workerMax   int64 = -1 ^ (-1 << workerBits)
   numberMax   int64 = -1 ^ (-1 << numberBits)
   timeShift   uint8 = workerBits + numberBits
   workerShift uint8 = numberBits
   startTime   int64 = 1525705533000 // 服务上线时的时间戳
)

type Worker struct {
   mu        sync.Mutex
   timestamp int64
   workerId  int64
   number    int64
}

func NewWorker(workerId int64) (*Worker, error) {
   if workerId < 0 || workerId > workerMax {
      return nil, errors.New(errortype.SnowFlakeErr)
   }
   // 生成一个新节点
   return &Worker{
      timestamp: 0,
      workerId:  workerId,
      number:    0,
   }, nil
}

func (w *Worker) GetId() int64 {
   w.mu.Lock()
   defer w.mu.Unlock()
   now := time.Now().UnixNano() / 1e6
   if w.timestamp == now {
      w.number++
      if w.number > numberMax {
         for now <= w.timestamp {
            now = time.Now().UnixNano() / 1e6
         }
      }
   } else {
      w.number = 0
      w.timestamp = now
   }
   ID := int64((now-startTime)<<timeShift | (w.workerId << workerShift) | (w.number))
   return ID
}