大项目作业 FFmpeg截取视频封面并转存MinIO实例 | 青训营笔记

1,065 阅读3分钟

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

运行准备

1、FFmpeg

FFmpeg 是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。本实例使用 FFmpeg 第三方软件来完成截取视频第一帧转换为图片格式作为封面,因此首先需要进行 FFmpeg 的安装与配置。这里给出windows系统下的安装配置方法:

官网安装链接:FFmpeg-5.1.2

  • 下载完文件之后解压,将文件目录下bin文件夹的路径添加到"环境变量->系统变量->Path"当中。 SharedScreenshot.jpg
  • 打开cmd使用ffmpeg命令查看使用安装和环境配置是否成功,成功打印如下即完成安装。 屏幕截图 2023-02-09 101518.jpg

Go中需要添加依赖

"github.com/u2takey/ffmpeg-go"

2、MinIO

MinIO 是一款高性能、分布式的对象存储系统。本实例使用 MinIO 作为 FFmpeg 完成图片截取后的文件输出对象,搭建 Go 与 MinIO 的交互框架。首先需要进行 MinIO 的安装与配置。这里给出windows系统下的安装配置方法:

官网安装链接:MinIO

屏幕截图 2023-02-09 102914.jpg

下载到本地后,指定存储目录,使用如下命令即成功启动MinIO

minio.exe server d:/MinIO/Data

屏幕截图 2023-02-09 103342.jpg

Go中需要添加依赖

"github.com/minio/minio-go/v7"

"github.com/minio/minio-go/v7/pkg/credentials"

实例演示

1、FFmpeg截图

参考其它青训营笔记,发现很多队伍使用 FFmpeg 进行截图采取的方式为:将前端发送来的 mp4 视频保存为在本地,作为 FFmpeg 的输入,再输出jpeg保存在本地。之后才将这两个文件上传到云存储系统中,并对本地文件进行删除。这样的操作效率较低,浪费了很多不必要的时间与资源。

我们队伍则希望实现将前端发送过来的视频流直接转送到云存储 MinIO 中,对该存储对象直接操作截取视频并将输出视频封面同样转存到云存储 MinIO 中。

参考ffmpeg Documentation,事实上 FFmpeg 可以将 url 作为输入完成与将本地 mp4 文件作为输入等同的操作。因此,将视频流直接转存到 MinIO 中后,可以通过存储 url 直接对其进行截取帧为视频封面的操作,再将输出图片流通过管道放到缓冲器直接回传至 MinIO ,省略中途本地存储的步骤。

本部分使用的 FFmpeg API 各参数含义参考:

ffmpeg_go package - github.com/u2takey/ffmpeg-go - Go Packages

        // 设置 FFmpeg 参数及运行
        inputFile := "https://www.w3schools.com/html/movie.mp4" //字节demo给出的一个视频url
	buf := bytes.NewBuffer(nil)
	saveName := "test_pic.jpeg"
	err := ffmpeg.Input(inputFile).
		Filter("select", ffmpeg.Args{fmt.Sprintf("gte(n,%d)", 5)}).
		Output("pipe:", ffmpeg.KwArgs{"vframes": 1, "format": "image2", "vcodec": "mjpeg"}).
		WithOutput(buf, os.Stdout).
		Run()

	// 结果显示
	if err != nil {
            log.Fatalln("截取图片失败", err)
            return
	}
	log.Printf("截取图片成功")

2、Go 与 MinIO 交互

本部分使用的 MinIO API 各参数含义参考:

MinIO Go Client API Reference — MinIO Object Storage for Linux

首先需要创建 MinIO 客户端对象并进行初始化,查找/创建目标桶。这一步由如下代码中的initMinio()完成。

第二步是将 ffmpeg 的输出流推送至 MinIO 中,这一步由如下代码中的putPicture(buf *bytes.Buffer, saveName string)完成。

var (
	client *minio.Client
)

// MinIO 服务的配置参数
const (
	endpoint        string = "127.0.0.1:9000/"
	accessKeyID     string = "minioadmin"
	secretAccessKey string = "minioadmin"
	useSSL          bool   = false
	bucketName      string = "video"
	picType         string = "image/jpeg"
)

//创建 MinIO 客户端对象, 并查找目标bucket(不存在则新建一个bucket)
func initMinio() {	
	minioClient, err := minio.New(endpoint, &minio.Options{
		Creds:  credentials.NewStaticV4(accessKeyID, secretAccessKey, ""),
		Secure: useSSL,
	})
	if err != nil {
		log.Fatalln("创建 MinIO 客户端失败", err)
		return
	}
	client = minioClient
	log.Printf("创建 MinIO 客户端成功")

	exist, err := client.BucketExists(context.Background(), bucketName)

	if err != nil {
		log.Fatalln("查找桶失败", err)
	}

	if !exist {
		err = client.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{})
		if err != nil {
			log.Fatalln("创建桶失败", err)
		}
	}
	log.Printf("桶已创建")
}

func putPicture(buf *bytes.Buffer, saveName string) {
	_, err := client.PutObject(context.Background(),
		bucketName,
		saveName,
		buf,
		int64(buf.Len()),
		minio.PutObjectOptions{
			ContentType: picType,
		})

	if err != nil {
		log.Fatalln("图片上传失败", err)
		return
	}
	log.Printf("图片上传成功")
}

3、结果

总体代码运行完成后,发现 MinIO 目标桶中新增了目标图片,即为运行成功。

总结

实现了一个小功能,后续要进一步结合大项目mvc框架做实现,需要重点关注与数据库和前端的交互。