作者:广树时间:2024-01-27 13:11:17分类:JavaScript/jQuery/Vue
一直以来,视频网站的视频压缩和剪辑往往都是通过服务器后台进行操作的,但是对于像是个人博客这样微弱的服务器算力和流量,将视频交由服务器来处理实在是有点为难服务器了。
那么有没有什么办法可以通过浏览器前端对视频进行操作的办法呢?这里就来推荐一款好用的前端视频处理依赖包——ffmpeg.wasm
实现起来也相对比较容易,原理就不赘述了,直接上实操。
按照<文档>的描述,先引入两个包:
npm install @ffmpeg/ffmpeg @ffmpeg/util
然后按照描述引入包即可。
这里要注意的是通过以下方式load的时候可能会出现跨域的问题
const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/umd'
await ffmpeg.load({
coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
});
但是因为ffmpeg-core.wasm体积过于庞大,不想放到自己的服务器上要怎么办呢?
这里我参考了<crop>的做法,将ffmpeg-core.js和ffmpeg-core.wasm存入indexedDB的做法实现将ffmpeg.wasm安装到浏览器端中。
这里和前面提到的crop一样,用了idb-keyval来管理indexedDB。
import { get, set, delMany } from 'idb-keyval'
// 安装ffmepg
const coreURL = `/@ffmpeg/core@0.12.6/dist/esm/ffmpeg-core.js`
const wasmURL = `/@ffmpeg/core@0.12.6/dist/esm/ffmpeg-core.wasm`
export const installBufferToIndexedDB = async (key, url) => {
const response = await fetch(url)
// if not ok, throw error
if (!response.ok) {
throw new Error(`Unable to fetch: ${url}`)
}
const reader = response.body?.getReader()
if (!reader) {
throw new Error(`Unable to fetch: ${url}`)
}
let receivedLength = 0
const chunks = []
// eslint-disable-next-line no-constant-condition
while (true) {
const { done, value } = await reader.read()
if (done) {
break
}
chunks.push(value)
receivedLength += value.length
}
const buffer = await new Blob(chunks).arrayBuffer()
try {
set(key, buffer)
console.log(`Saved to IndexedDB: ${url}`)
} catch {
console.warn(`Failed to save to IndexedDB: ${url}`)
}
}
export const installFFmpeg = async (baseUrl) => {
await installBufferToIndexedDB('ffmpeg-core.js', baseUrl + coreURL)
await installBufferToIndexedDB('ffmpeg-core.wasm', baseUrl + wasmURL)
}
这样就可以免去每次运行时需要长时间加载的烦恼了。
当然不用的时候也可以直接卸载:
export const uninstallFFmpeg = async () => {
await delMany(['ffmpeg-core.js', 'ffmpeg-core.wasm'])
}
安装完毕后就是读取:
const retrieveBlob = async (key, type) => {
const buffer = await get(key)
if (!buffer) {
ElMessage.error(`Failed to retrieve from IndexedDB: ${key}`)
throw new Error(`Failed to retrieve from IndexedDB: ${key}`)
}
const blob = new Blob([buffer], { type });
return URL.createObjectURL(blob);
}
以及初始化ffmpeg:
export const initFFmpeg = async () => {
const ffmpeg = new FFmpeg();
await ffmpeg.load({
coreURL: await retrieveBlob(
`ffmpeg-core.js`,
'text/javascript',
),
wasmURL: await retrieveBlob(
`ffmpeg-core.wasm`,
'application/wasm',
),
});
return ffmpeg
}
在获取到ffmpeg之后就可以对起执行各种操作了,具体API可以参阅<Class: FFmpeg>。
在维基萌的博客中的操作界面UI如图所示:
当前仅需要开始时间,结束时间,最长边,码率以及帧率的设置,所以相关调用的配置可以这么写:
// 压缩视频
arg.push('-i', 'input')
arg.push('-ss', formatTime(startTime))
arg.push('-t', formatTime(endTime - startTime))
arg.push('-s', `${width}x${height}`)
arg.push('-b:v', `${bitrate}k`)
arg.push('-r', `${fps}`)
arg.push('-c:v', 'libx264')
arg.push('-c:a', 'aac')
arg.push('-preset', 'veryfast')
arg.push('-f', 'mp4')
arg.push(outputFileName)
写完配置后丢进去执行然后等待执行结束即可
export const execFFmpeg = async (ffmpeg, file, args, outputFileName) => {
try {
await ffmpeg.writeFile(
'input',
new Uint8Array(await file.arrayBuffer()),
);
await ffmpeg.exec([...args]);
const data = (await ffmpeg.readFile(outputFileName));
return new File([data.buffer], outputFileName, { type: 'video/mp4' });
} finally {
try {
await ffmpeg.deleteFile('input');
} catch {
//
}
try {
await ffmpeg.deleteFile(outputFileName);
} catch {
//
}
}
}
以上代码大部分都是借鉴了<crop>,这个项目的代码,非常感谢原作者!
最终压缩出来效果可以参考以下视频:
视频参数为最长边480,码率500,帧率30
约9秒的视频,最终生成的大小是750KB,对于维基萌这样的小破站应该属于可以接受的范围。
当然,ffmpeg.wasm目前还算是个实验性的项目,还有非常多的不稳定性存在,包括但不限于:
不过以上的问题目前都还能接受,相对于通过服务器后端处理视频的缺点来说。
所以总结下来如果想用ffmpeg.wasm进行高强度的前端视频处理目前仍然还有难度,但是对于处理发表在博客上这样几秒钟的短视频还是相当合适的!