前端使用ffmpeg工具 视频转出的gif画质好糊?怎么办?

496 阅读2分钟

在使用ffmpeg时发现ffmpeg虽然可以满足基本的视频转gif的需求,但转出的画质无法满足要求,通过多种方法进行优化,效果仍然不是很理想,本篇文章讲述作者通过什么样的方法最终实现了想要的gif效果

一、react中如何使用ffmpeg

首先先将@ffmpeg/ffmpeg 和@ffmpeg/util 通过yarn或者npm的方式导入到项目

yarn add @ffmpeg/ffmpeg @ffmpeg/util

在使用ffmpeg之前需要确保其进行加载

  const ffmpegRef = useRef(new FFmpeg());
  const load = async () => {
    const ffmpeg = ffmpegRef.current;
    await ffmpeg.load({
      coreURL: await toBlobURL(`/ffmpeg-core.js`, 'text/javascript'),
      wasmURL: await toBlobURL(`/ffmpeg-core.wasm`, 'application/wasm')
    });
    setLoaded(true);
  };
  useEffect(() => {
    load();
  }, []);

二、使用ffmpeg 如何处理视频转gif


    await ffmpeg.exec([
      '-ss',
      startTime,
      '-t',
      endTime,
      '-i',
      `input.${fileType}`,
      '-vf',
      `fps=${currentFPS},scale=${currentRatioWidth}:${currentRatioHeight}:flags=lanczos`,
      '-c:v',
      'gif',
      'output.gif'
    ]);

三、ffmpeg 如何优化清晰度

原理:GIF受限于256色调色板,默认情况下ffmpeg只使用一个通用的调色板去尝试覆盖所有的颜色区域,以此来支持含有大量内容的文件。 所以提高GIF图片质量的首先要定义一个好的调色板,用以下代码来实现,输出一个output.png即为该视频对应的调色板,这里的-b表示设置输出文件的视频比特率

 await ffmpeg.exec([
      '-i',
      `input.${fileType}`,
      '-b',
      '2048k',
      '-vf',
      `fps=${currentFPS},scale=${currentRatioWidth}:${currentRatioHeight}:flags=lanczos,palettegen`,
      '-y',
      'output.png'
    ]);
    const transitionBoard = (await ffmpeg.readFile('output.png')) as any;

再使用输出的调色板作为新调色板参与gif的制作, 生成全局调色板后,第二步就是将颜色效果映射到颜色输出流中,这个是通过 paletteuse 滤波器完成的。它将会使用这个调色板来生成最终的量化颜色流,它的任务是在生成的调色板中找出最合适的颜色来表示输入的颜色。

    await ffmpeg.exec([
      '-ss',
      startTime,
      '-t',
      endTime,
      '-i',
      `input.${fileType}`,
      '-i',
      'output.png',
      '-lavfi',
      `fps=${currentFPS},scale=${currentRatioWidth}:${currentRatioHeight}:flags=lanczos[x];[x][1:v]paletteuse`,
      '-lossless',
      '-y',
      'output.gif'
    ]);

四、其他办法(也是我目前采用的办法)

使用ffmpeg将视频按照需要的帧率转化为帧图片,因为发现转出的帧图片画质相对较高,再用gif.js 将图片再次合成为gif ,gif合成是用的图片直接进行处理,画质相较ffmpeg直接生成的效果更好 先使用ffmpeg生成帧图片

  await ffmpeg.exec([
        '-ss',
        `${startTime.h}:${startTime.m}:${startTime.s}`,
        '-t',
        '00:00:08',
        '-i',
        `input.${fileType}`,
        '-vf',
        `fps=1/(1/${currentFPS}),scale=${currentRatioWidth}:${currentRatioHeight}`,
        `${dirPath}/output_%04d.png`
      ]);

然后再用gif.js将帧图片生成gif

  const makeGIF = async () => {
    setLoading(true);
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');
    canvas.width = width;
    canvas.height = imgRef?.current?.clientHeight ?? width;
    const gif = new GIF({
      workers: 10,
      quality: 1,
      workerScript: '/gif.worker.js',
      width: canvas.width,
      height: canvas.height
    });
    const promises = ImgList.map((imgItem: string) => {
      return new Promise((resolve) => {
        const img = new Image();
        img.src = imgItem;
        img.onload = () => {
          context?.drawImage(img, 0, 0, canvas.width, canvas.height);
          const screenshotDataURL = canvas.toDataURL();
          resolve(screenshotDataURL);
        };
      });
    });
    const imagesList = await Promise.all(promises);
    for (let i = 0; i < imagesList.length; i++) {
      let img = await getImageFromBase64(imagesList[i]);
      gif.addFrame(img, { delay: 1000 / currentFPS });
    }

    gif.on('finished', function (blob: Blob | MediaSource) {
      let url = URL.createObjectURL(blob);
      setGifUrl(url);
      let link = document.createElement('a');
      link.download = Math.random().toString().replace('0.', '') + '.gif';
      link.href = url;
      link.click();
      URL.revokeObjectURL(link.href); //释放URL对象
      setLoading(false);
    });
    gif.render();
  };

可以看下效果对比

gif生成的gif.gif

该图片为此方案生成的gif图

output.gif

该图片为直接用ffmpeg生成的gif图

画面效果对比还是挺明显的,而且gif大小相差不大