用handbrake-js打造批量视频转码服务:从单文件编码到任务队列的进阶实战
用handbrake-js打造批量视频转码服务:从单文件编码到任务队列的进阶实战
handbrake-js 是面向 Node.js 的免费开源视频转码库,它把功能强大的 HandBrake 命令行工具封装成了简洁的 JavaScript API,让你用几行代码就能完成 MP4、MKV、AVI 等常见格式之间的视频编码与转码。本文将带你从单文件编码入手,逐步搭建一个带进度监听、并发控制和错误重试的批量视频转码服务。
一、handbrake-js 是什么?为什么适合做转码服务?
handbrake-js 的底层是 HandBrake(HandBrakeCLI v1.11.1),这是一个成熟的视频转码引擎,支持:
| 类别 | 支持内容 |
|---|---|
| 输出容器 | MP4 / M4V、MKV |
| 视频编码 | H.264 (x264)、H.265 (x265)、MPEG-4、MPEG-2、VP8、Theora |
| 音频编码 | AAC、MP3、Flac、AC3、Vorbis |
| 运行环境 | Mac、Windows、Ubuntu(Node.js ≥ 14) |
它的设计目标就是"为在 Node.js 中构建视频转码软件提供轻量、稳定的基础",这也是本文批量转码服务的基石。
一键安装步骤
在你的项目目录中执行:
npm install handbrake-js --save
- Mac / Windows:只需 Node.js,安装脚本会自动下载并解压 HandBrakeCLI 二进制文件到本地,全程无需手动配置。
- Linux(Ubuntu):需要手动安装一次命令行工具:
sudo apt install handbrake-cli
项目自带 scripts/install-ubuntu.sh 脚本封装了这条命令;而 scripts/install.js 则负责在 Mac/Windows 上自动完成二进制下载与校验,版本不匹配时会重新拉取最新版。
二、单文件转码:spawn / exec / run 三种 API 怎么选?
handbrake-js 对外只暴露三个核心函数(定义在 index.js 中),选型建议如下:
| 方法 | 返回 | 适用场景 |
|---|---|---|
hbjs.spawn(options) | 事件流对象 | 长任务,需要实时进度(推荐批量服务使用) |
hbjs.run(options) | Promise | 异步写法,不关心进度 |
hbjs.exec(options, cb) | 回调 | 短任务、回调风格代码 |
用 spawn 转码第一个视频
以下示例把演示视频 demo.mkv 转码为 MP4,并实时输出编码日志:
import hbjs from 'handbrake-js'
const options = {
input: 'test/video/demo.mkv',
output: 'output.mp4',
preset: 'Very Fast 480p30'
}
hbjs.spawn(options)
.on('error', console.error)
.on('output', process.stdout.write.bind(process.stdout))
完整可运行示例见 examples/spawn.js 和 examples/run.js。
事件模型:转码过程的"仪表盘"
spawn() 返回的 Handbrake 实例是一个 EventEmitter(实现位于 lib/handbrake.js),转码全过程会依次触发这些事件:
start → begin → progress(多次) → end → complete,异常时触发 error,手动中止时触发 cancelled。
其中 progress 事件携带的字段是构建批量服务的关键:
| 字段 | 含义 |
|---|---|
percentComplete | 完成百分比 |
fps / avgFps | 当前/平均帧率 |
eta | 预计剩余时间 |
task | 当前阶段(Encoding 编码 / Muxing 封装) |
taskNumber / taskCount | 当前任务序号 / 总任务数 |
这些字段的解析逻辑由 lib/progress.js 从 HandBrakeCLI 的输出流中正则提取,你无需自己解析文本。
三、进阶实战:构建批量视频转码任务队列
单文件转码只是起点。批量服务的核心是任务队列 + 并发控制 + 失败重试。下面的骨架代码展示了完整思路:
import hbjs from 'handbrake-js'
import fs from 'fs'
const CONCURRENCY = 2 // 同时最多 2 个编码任务
const queue = [] // 待转码文件列表
let active = 0
function encode (file) {
active++
const job = hbjs.spawn({
input: file,
output: file.replace(/\.\w+$/, '.mp4'),
preset: 'Fast 720p30'
})
job.on('progress', p => {
console.log(`${file} ${p.percentComplete}% ETA ${p.eta}`)
})
job.on('end', () => done(null, file))
job.on('error', err => done(err, file))
}
function done (err, file) {
if (err) {
// 简单重试策略:重新入队,最多 3 次
if ((file._retries || 0) < 3) {
file._retries++
queue.push(file)
} else {
console.error('放弃任务:', file, err.message)
}
}
active--
next()
}
function next () {
while (active < CONCURRENCY && queue.length) {
encode(queue.shift())
}
}
// 初始化:扫描目录,全部入队
for (const f of fs.readdirSync('videos')) {
if (/\.(mkv|avi|mov)$/i.test(f)) queue.push({ file: f, _retries: 0 })
}
next()
这个队列有几个值得注意的设计点:
- 并发上限:编码是 CPU 密集型任务,
CONCURRENCY建议设为 CPU 核心数附近,避免过度争抢。 - 重试幂等:失败任务重新入队而非直接抛出,保证一批视频"整体可完成"。
- 进度可聚合:每个任务的
progress事件可以向上汇总,轻松做出"整批 62%、剩余约 1 小时 20 分"这样的总进度条。
如果用户需要中止某个任务,可直接调用实例的 cancel() 方法——它会向 HandBrakeCLI 进程发送 SIGINT 并触发 cancelled 事件,无需硬杀进程。
四、生产环境避坑指南
1. 自定义 HandBrakeCLI 路径(Docker / 特殊 Linux 环境)
默认路径逻辑在 lib/config.js 中:Mac/Windows 指向包内 bin/ 目录,Linux 则依赖系统命令。如果 HandBrakeCLI 装在非标准位置(容器内很常见),有两条路:
- 设置环境变量:
HANDBRAKECLI_PATH=/path/to/HandBrakeCLI - 或在 options 中直接传
HandbrakeCLIPath字段,三个 API 均支持
更多用法可参考 examples/handbrakecli-path-env.js 等示例文件。
2. 错误分类,对号入座
所有运行时错误都通过 error 事件抛出,error.name 是稳定的错误标识符(枚举定义在 lib/handbrake.js 底部):
| 错误标识 | 触发条件 | 服务侧建议 |
|---|---|---|
ValidationError | 输入输出路径相同、缺少输出路径等 | 任务入队前做参数校验 |
InvalidInput | 输入文件不是视频 | 扫描目录时过滤非视频文件 |
InvalidPreset | 预设名称写错 | 用 run({ presetList: true }) 先拉取有效预设 |
Other | HandBrake 崩溃 / 未知错误 | 记录 error.output 便于排查 |
HandbrakeCLINotFound | 二进制文件缺失 | 检查 HandbrakeCLIPath 配置 |
每个错误对象还附带 err.output(完整 CLI 输出)和 err.options(本次参数),是批量服务日志落盘的最佳素材。
3. 三个高频坑
- 输出路径与输入路径相同会被前置校验拦截,批量命名时务必生成新文件名;
- 编码开始前如果没有触发
begin事件,基本可以判定输入有问题; - 项目附带完整测试(test/spawn.js、test/run.js 等),其断言方式就是各事件的推荐用法,值得借鉴。
五、总结:从 1 个文件到 1 条流水线
回顾整条进阶路径:
npm install装好 handbrake-js,Linux 补装一次 handbrake-cli;- 用
spawn()完成单文件转码,监听progress拿到百分比与 ETA; - 把任务组织成队列,加并发上限与失败重试;
- 按
eError枚举分类处理异常,用HandbrakeCLIPath适配部署环境。
至此,一个可投入生产的批量视频转码服务骨架就搭好了。想继续深挖源码,建议按 index.js → lib/handbrake.js → lib/progress.js 的顺序阅读,不到一千行代码就能吃透整个转码流程。
更多推荐
所有评论(0)