😠受不了那些视频转GIF要收费的工具,我自己写了一个

前言

最近自己有一个视频转 GIF 的需求,于是就在某度上搜了一下。

然后,搜出来的东西,要么要下载,要么转换需要会员之类的。然后我就忍不了了,我只想要一个很基础的视频转 GIF 功能。

无奈之下,只好自己写一个了。我想要的是一个 web 端的视频转 GIF 应用,可以支持一些简单的配置,比如说:

  • 分辨率
  • 帧率
  • 常见的视频格式支持,对我来说就是 mp4webm

这个时候就需要用到 FFMpegwasm 版本,文档地址在这里:ffmpeg.wasm文档

FFMpeg引入

按照惯例先简单介绍一下什么是 FFmpegFFmpeg 是一个开源的跨平台音视频处理工具集,它提供了丰富的命令行选项和参数,可以满足各种多媒体处理需求,比如视频转码、音频提取、视频剪辑、视频合并等。

FFmpeg wasmFFmpegWebAssembly(Wasm) 版本。 WebAssembly 是一种低级别的字节码格式,可以在 Web 浏览器中运行,它提供了一种跨平台、高性能的执行环境。 FFmpeg wasm 就是将 FFmpeg 工具集编译成 WebAssembly 格式,以便在浏览器中直接运行 FFmpeg 功能,而无需依赖服务器端的转码或处理。

也就是说,利用 FFmpeg wasm ,我们就可以在浏览器环境实现一些音视频处理的功能,而不需要依赖服务器。

安装一下需要的依赖:npm i @ffmpeg/ffmpeg @ffmpeg/util

然后把官网的代码拷下来试试看:

这里有几个问题需要注意:

  1. 如果你是使用 vite 打包的,在 baseURL 中把 umd 换成 esm
  1. 同样,如果你是使用 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 包体积比较大,所以就看怎么取舍吧。

以上就是本文的全部内容,如果你觉得有意思的话,点点关注点点赞吧~

相关推荐
Simaoya19 分钟前
vue判断元素滚动到底部后加载更多
前端·javascript·vue.js
头顶一只喵喵20 分钟前
Vue基础知识:Vue3.3出现的defineOptions,如何使用,解决了什么问题?
前端·javascript·vue.js·vue3
黑色的糖果1 小时前
echarts横向立体3D柱状图
前端·javascript·echarts
茶卡盐佑星_1 小时前
vue3.0所采用的composition Api与vue2.x使用的Option Api有什么区别
前端·javascript·vue.js
乐安lan1 小时前
01前端导入
前端
lauo1 小时前
【WEB前端2024】3D智体编程:乔布斯3D纪念馆-第55课-芝麻开门(语音 识别 控制3D纪念馆开门 和 关门)
前端·javascript·人工智能·3d·机器人·开源·语音识别
2401_857622661 小时前
探索 WebKit 的动感世界:设备方向和运动支持全解析
前端·webkit
开源博客1 小时前
Vue3 如何接入 i18n 实现国际化多语言
前端·vue·i18n·vite
Ann_R1 小时前
el-date-picker 开始时间选定后,结束时间不可选择开始时间之前的日期
前端·vue.js·elementui
濮水大叔1 小时前
2024已过半,还没试过在vue3中使用ioc容器吗?
前端·typescript·vue3·ioc·tsx