这是我参与「第五届青训营 」伴学笔记创作活动的第 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()将对应地址的视频及图片进行提取
传入参数:
- videoPath 待截取视频路径
- snapShotPath 截图存储路径
- snapShotName 截图命名
- 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
}