前言
最近自己有一个视频转 GIF
的需求,于是就在某度上搜了一下。
然后,搜出来的东西,要么要下载,要么转换需要会员之类的。然后我就忍不了了,我只想要一个很基础的视频转 GIF
功能。
无奈之下,只好自己写一个了。我想要的是一个 web
端的视频转 GIF
应用,可以支持一些简单的配置,比如说:
- 分辨率
- 帧率
- 常见的视频格式支持,对我来说就是
mp4
跟webm
这个时候就需要用到 FFMpeg
的 wasm
版本,文档地址在这里:ffmpeg.wasm文档
FFMpeg引入
按照惯例先简单介绍一下什么是 FFmpeg
: FFmpeg
是一个开源的跨平台音视频处理工具集,它提供了丰富的命令行选项和参数,可以满足各种多媒体处理需求,比如视频转码、音频提取、视频剪辑、视频合并等。
FFmpeg wasm
是 FFmpeg
的 WebAssembly(Wasm)
版本。 WebAssembly
是一种低级别的字节码格式,可以在 Web
浏览器中运行,它提供了一种跨平台、高性能的执行环境。 FFmpeg wasm
就是将 FFmpeg
工具集编译成 WebAssembly
格式,以便在浏览器中直接运行 FFmpeg
功能,而无需依赖服务器端的转码或处理。
也就是说,利用 FFmpeg wasm
,我们就可以在浏览器环境实现一些音视频处理的功能,而不需要依赖服务器。
安装一下需要的依赖:npm i @ffmpeg/ffmpeg @ffmpeg/util
然后把官网的代码拷下来试试看:
这里有几个问题需要注意:
- 如果你是使用
vite
打包的,在baseURL
中把umd
换成esm
- 同样,如果你是使用
vite
打包的,在vite
配置中加上如下配置:
不然会报下面的错:
然后发现,跑是能跑的
就是这个 wasm
文件加载需要一些时间,一个是因为比较大,另一个主要的原因是引的外部链接。如果我单纯使用开发模式,则可以把它下载下来放到本地。这样还在就会快很多。
下载一下这两个文件,放到我们自己的工程的 public
目录下:
然后把 baseUrl
改成空字符串,读取我们本地的文件,加载就会嘎嘎快
简版实现
前置工作做完之后,我们来开始实现一个简版。页面非常简单:
- 一个上传按钮,用来上传视频
- 上传好的视频预览一下
- 点击转换的按钮
先看一下 html
结构:
js
<div className={styles.container}>
<Row>
<Button type="primary" onClick={() => uploadRef.current.click()}>
上传视频
</Button>
<input
accept="video/mp4,video/webm"
onChange={handleFileChange}
ref={uploadRef}
type="file"
className={styles.upload}
/>
</Row>
<Divider />
{!isEmpty(currentUrl) && (
<>
<video style={{ maxHeight: 200 }} src={currentUrl} controls></video>
<Divider />
</>
)}
<Button type="primary" onClick={transform}>
转换并下载
</Button>
</div>
搞一个按钮,点击的时候触发文件上传,然后把上传的文件保存下来,同时对上传的文件生成 URL
用来预览
js
const handleFileChange = (e) => {
const file = e.target.files[0];
setCurrentFile(file);
if (currentUrl) {
URL.revokeObjectURL(currentUrl);
}
setCurrentUrl(URL.createObjectURL(file));
};
使用 ffmpeg
将视频转成 GIF
的时候,主要用到这个命令:
css
ffmpeg -i input.mp4 -c:v gif output.gif
先实现一个入口函数:
js
const transform = async () => {
const type = currentFile.type;
setLoading(true);
const videoType = type.split("/")[1]; //webm 或者 mp4
try {
await doTransform(videoType);
} catch (err) {
console.log("transform error", err);
} finally {
setLoading(false);
}
};
在 doTransform
中实现真正的转换:
js
const doTransform = async (fileType) => {
const ffmpeg = ffmpegRef.current;
await ffmpeg.writeFile(`input.${fileType}`, await fetchFile(currentFile));
await ffmpeg.exec(["-i", `input.${fileType}`, "-c:v", "gif", "output.gif"]);
const data = await ffmpeg.readFile("output.gif");
const url = URL.createObjectURL(
new Blob([data.buffer], { type: "image/gif" })
);
download(url);
};
然后实现一个简单的下载函数,把转换好的 GIF
下载下来:
js
const download = (url) => {
const link = document.createElement("a");
link.href = url;
link.download = "output.gif";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
可以看到转换是没问题的,就是体积有点太大了
下面就来做实现一些配置,压缩一下转换的 GIF
。
拓展配置
下面来支持一下转换 GIF
结果的帧率以及分辨率的配置,来让我们可以选择 GIF
的质量和大小。
分辨率这里可以先取到视频原始的分辨率,通过监听 onLoadedMetadata
就可以在视频加载完成后取到
js
const onLoadedMetadata = () => {
const video = videoRef.current;
setConfig({
width: video.videoWidth,
height: video.videoHeight,
});
};
然后根据自己的心情,大致给分辨率定几个选项:
js
const ratioOptions = useMemo(() => {
if (isEmpty(config)) {
return [];
}
const { width, height } = config;
return [1, 0.75, 0.5, 0.33, 0.25, 0.2, 0.1].map((item) => {
const newWidth = Math.round(width * item);
const newHeight = Math.round(height * item);
return {
label: `${newWidth}x${newHeight} (${item * 100}%)`,
value: `${newWidth}x${newHeight}`,
};
});
}, [config]);
帧率的话就是一个滑块组件,步长为 1
,最小为 5
, 最大为 30
。
然后修改一下 ffmpeg
转换的命令
js
const [width, height] = currentRatio.split("x");
await ffmpeg.exec([
"-i",
`input.${fileType}`,
"-vf",
`fps=${currentFPS},scale=${width}:${height}:flags=lanczos`,
"-c:v",
"gif",
"output.gif",
]);
大功告成:
最后
看了下市面上的视频转 GIF
工具,大多数还有一个选择时间区间的功能。比如说我有一个视频 20秒
,我可以只选择它 5-10秒
的部分来转换成一个 GIF
。
这个 ffmpeg
也是可以实现的,对应的命令是:
js
ffmpeg -ss 00:00:01 -t 00:00:05.5 -i input.mp4 -vf -c:v gif output.gif
在这个示例中,-ss -ss 00:00:01
表示从第1秒开始,-t 00:00:05.5
表示持续 5.5
秒钟。这样就可以实现转换指定时间区间的功能。
但是这个功能对我目前来说好像用的比较少🤔️,所以我就没做。
再一个就是我之前尝试使用 webcodecs
来做视频转 GIF
------纯前端也能实现视频转GIF,但是 webcodecs
在这里充当的只是一个解码器的角色,只是把视频解码成一帧帧图像,而将图像合成为 gif
的时候用到了其他库,这一整个过程下来的速率是远比使用 ffmpeg
去做要慢的。
而且 ffmpeg
还支持很多的配置,所以如果真的要做成一个工具自己平时用起来,那还是 ffmpeg
比较靠谱。但是 ffmpeg
包体积比较大,所以就看怎么取舍吧。
以上就是本文的全部内容,如果你觉得有意思的话,点点关注点点赞吧~