在使用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图
该图片为直接用ffmpeg生成的gif图
画面效果对比还是挺明显的,而且gif大小相差不大