导航

萌即是正义!时不时分享一些ACG活动记录与有趣代码的小站!

侧边栏
热门文章
1页面
程序员老黄历&求签
热度
338
2页面
关于
热度
91
3推文
突然公布了《少女与战车》新作《少女与战车 LoveLove大作战》的PV。 上映日期分别为: 第1幕 2025年12月26日 第2幕 2026年1月30日 第3幕 2026年3月6日 第4幕 2026年4月10日 有新作是好事……但是,这顺序不太对啊,这最终章什么时候出完啊? https://www.bilibili.com/video/BV1vnGGz6Ek5
热度
65
4博文
3DS模拟器简体中文字库 | Citra3ds字库 | 符文工房4乱码解决字库 | shared_font.bin
热度
65
5博文
《上伊那牡丹,酒醉身姿似百合花般》圣地巡礼之杂司谷旧传教士馆
热度
52
6博文
游览世界文化遗产 《寒蝉鸣泣之时》圣地巡礼——白川乡 下篇
热度
52
7博文
《夏目友人帐》圣地巡礼之人吉 风景篇
热度
52
8博文
大开眼界的《憧憬成为魔法少女非常热闹哟大感谢祭》S场
热度
52
9推文
作为凉宫春日等人就读的“北高”的原型,其校园风景在动画中得以重现的兵库县立西宫北高等学校,将在2027年3月末以“西宫北高校”的名义关闭,并由“西宫苦乐园高校”接管。 一直以来,许多粉丝都将西宫北高作为凉宫春日的“圣地”来访问。虽然该校此前从未对外开放校内,但这次将首次举办校内公开企划! 在这次活动中,大家可以欣赏动画中出现的场景,并在校园内进行拍照留念等活动。 ※ 截至2025年11月14日,入场门票已全部售罄,且不会追加销售。请注意,未持有入场门票者将无法进入会场。
热度
39
10博文
探访《蜡笔小新》老家——春日部游记
热度
39
最新评论
广树管理员
2026-08-25 08:21
@RQ_东流水:不知道活动会不会到国庆
广树管理员
2026-08-25 08:20
@Zrzzz:灵感源自AI
RQ_东流水
2026-08-25 00:13
@广树:行,那我国庆看能不能再去一趟日本看看
Zrzzz
2026-08-24 23:07
小羊的椅子坐垫十分可爱 缝之
广树管理员
2026-08-24 18:19
@RQ_东流水:应该是有遗漏
攻略中
空之轨迹 the 1st
暂无评分
Steam空之轨迹 the 1st
2026年8月02日 12时 ~ 攻略中
已累计游玩1个月2天
因父亲卡西乌斯失踪而踏上旅程的艾丝蒂尔与约书亚, 在巡游利贝尔王国的过程中, 经历了无数与未知同伴的相遇与别离, 而在旅途终点等待着两人的,究竟会是什么……?
节奏天国 奇迹之星
暂无评分
NS节奏天国 奇迹之星
2026年7月08日 20时 ~ 攻略中
已累计游玩1个月27天
《节奏天国:奇迹之星》是一款轻松又充满创意的音乐节奏游戏,玩家只需跟随音乐节拍按下按键,就能完成跳跃、敲击空罐、制作料理等各种有趣的小挑战。游戏延续了《节奏天国》系列简单易上手、节奏感强、画风幽默的特点,让玩家在一个个短小精巧的关卡中感受音乐与反应力结合的乐趣,适合喜欢节奏游戏和轻松休闲玩法的玩家体验。
告别回忆 -无垢少女- 致最爱的你
暂无评分
Steam告别回忆 -无垢少女- 致最爱的你
2026年6月24日 20时 ~ 攻略中
已累计游玩2个月11天
系列作发售至今经历了19年,做为其系列最终作的《告别回忆 ~无垢少女~》(以下略称为本篇)在2018年3月发售。本次的FanDisc和至今的系列作相同,带给玩家在各结局后的短篇故事。请充分享受和各个女主角们在欢乐结局后的幸福时光。
亚路塔:狐狸狐途的面包冒险
暂无评分
Steam亚路塔:狐狸狐途的面包冒险
2026年2月12日 19时 ~ 攻略中
已累计游玩6个月23天
一场席卷全世界的面包革命现在开始! 主人公查雅阴错阳差之下,开始了在荒废的岛屿上经营面包店的生活。 出外冒险搜集食材、研发崭新面包配方、遇见性格各异的伙伴们,建造更丰富的设施,用面包香气使无人问津的广场再次热闹起来!
fault - StP - LIGHTKRAVTE
暂无评分
Steamfault - StP - LIGHTKRAVTE
2025年5月29日 20时 ~ 攻略中
已累计游玩1年3个月
全球累计销量超过50万份的“fault”系列最新作!故事的舞台是一个融合了奇幻与科幻的超前世界——卢森海德王国。本作讲述了生活在此地的一个平凡又平庸的究极普通市民——名为果子的少年的故事。
PSN奖杯卡

PSN奖杯卡

归档
赞助商广告

如何使用ffmpeg.wasm在浏览器前端实现视频压缩和剪辑

2024-01-27 13:11:17
JavaScript

一直以来,视频网站的视频压缩和剪辑往往都是通过服务器后台进行操作的,但是对于像是个人博客这样微弱的服务器算力和流量,将视频交由服务器来处理实在是有点为难服务器了。

那么有没有什么办法可以通过浏览器前端对视频进行操作的办法呢?这里就来推荐一款好用的前端视频处理依赖包——ffmpeg.wasm

实现起来也相对比较容易,原理就不赘述了,直接上实操。

按照<文档>的描述,先引入两个包:

npm install @ffmpeg/ffmpeg @ffmpeg/util

然后按照描述引入包即可。

这里要注意的是通过以下方式load的时候可能会出现跨域的问题

const baseURL = 'https://unpkg.com/@ffmpeg/[email protected]/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/[email protected]/dist/esm/ffmpeg-core.js`
const wasmURL = `/@ffmpeg/[email protected]/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如图所示:

image.png

当前仅需要开始时间,结束时间,最长边,码率以及帧率的设置,所以相关调用的配置可以这么写:

// 压缩视频
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目前还算是个实验性的项目,还有非常多的不稳定性存在,包括但不限于:

  1. 无法导入过大的视频
  2. 视频处理速度非常慢,按照上面的配置1秒钟需要处理5秒钟。也许可以使用多线程的配置进行优化,但是因为浏览器的限制因素,配置多线程有点麻烦,此次并没有尝试。
  3. 一旦视频开始处理就无法停止,也就是说只能通过刷新页面达到强行停止的效果。
  4. 没有很好的内存释放机制,可能会有内存溢出的风险。

不过以上的问题目前都还能接受,相对于通过服务器后端处理视频的缺点来说。

所以总结下来如果想用ffmpeg.wasm进行高强度的前端视频处理目前仍然还有难度,但是对于处理发表在博客上这样几秒钟的短视频还是相当合适的!

收款.png


file_000000008460720687635c008459256b.png