HandBrake Web 0.8.1 配置参数详解

逐项拆解 HandBrake Web 0.8.1 实际读取的环境变量与 config.yaml 字段,给出默认值、作用域、是否必填与 12 项实测结论,并点名源码中并不存在却被广泛误用的变量。

HandBrake WebHandBrake视频转码Node.js运维自动化2026/10/27

HandBrake Web 0.8.1 配置参数详解

目标:把 HandBrake Web 0.8.1 真正会读的环境变量与 config.yaml 字段全部列清,逐项给出默认值、作用域、是否必填与验证方法。文中有 12 项独立实测,命令与输出均来自 2026-10-27 的实机验证;源码里没有读取的变量也被点名,避免照着过时文章配置半天不生效。

环境信息

项目取值
应用版本HandBrake Web 0.8.1
实测系统Ubuntu 26.04.1 LTS,内核 7.0.0-38-generic
Node.jsv24.22.1
pnpm10.16.1
HandBrakeCLI1.11.0(系统包版本)
server 进程server/build/server.js
worker 进程worker/build/worker.js
配置来源进程环境变量(含工作目录下的 .env)+ $DATA_PATH/config.yaml

两个进程都通过 dotenv/config 加载工作目录下的 .env,所以"写进 .env"和"写进 service 的环境变量段"效果一致;同名的真实环境变量优先于 .env 文件(dotenv 默认不覆盖已存在的值)。

一、参数体系总览

HandBrake Web 0.8.1 的配置分成三层,优先级从高到低:

层内容生效时机谁读取
进程环境变量DATA_PATH、VIDEO_PATH 等进程启动时server / worker
数据目录下的 config.yaml路径默认值、预设开关、队列行为进程启动时加载,改写后立即广播给已连接页面仅 server
代码内硬编码端口 9999、临时文件路径、日志轮转编译进产物server / worker

记住一句话:环境变量决定"程序把文件放哪、跟谁通信";config.yaml 决定"界面默认打开哪个目录、队列怎么起跑";硬编码决定"改不了的那些"。

1.1 server 环境变量

变量默认值必填作用域实测结论
DATA_PATHcwd()/../data否SQLite、日志、预设、config.yaml 的根未设置时指向工作目录上一级的 data,目录不存在会在权限检查阶段报错并终止
VIDEO_PATHcwd()/../video否目录浏览、媒体根路径同上,未设置且不存在会终止
DEFAULT_PRESETS_PATH/var/lib/handbrake/preset_builtin.json否内置预设文件文件缺失时 server 启动直接失败(退出码 1)
NODE_ENV未设置生产必填是否挂载前端静态目录、是否检查更新未设置 = development:不挂载静态目录、跳过更新检查
SERVER_URLhttp://localhost否仅用于日志展示改它不会改变监听行为

1.2 worker 环境变量

变量默认值必填作用域实测结论
WORKER_ID无是注册名、日志文件名、任务归属缺失时打印错误并 exit(0),退出码 0
SERVER_URL无是目标地址主机部分缺失时打印错误,进程随后自行退出
SERVER_PORT无是目标地址端口部分同上;必须是目标进程实际监听的端口
DATA_PATH/tmp/handbrake-web否任务日志只有位于 /tmp 下才自动创建,否则目录不存在直接崩
VIDEO_PATHcwd()/../video否媒体根路径不存在时在权限检查阶段崩溃

1.3 config.yaml 字段

字段默认值作用域实测结论
config.version2配置结构版本低于 2 会跑迁移,迁移结果写回文件
paths.media-path/video界面目录浏览器的根只影响前端选择器;任务里存的是绝对路径
paths.input-path/video"输入目录"默认值必须位于 media-path 之下,否则界面报 Invalid
paths.output-path""(空)"输出目录"默认值留空表示跟随所选输入目录
presets.show-default-presetstrue是否展示内置预设前端展示开关,不改服务端加载逻辑
presets.allow-preset-creatorfalse是否允许自建预设前端入口开关
application.queue-startup-behavior0启动时队列状态0 沿用上次、1 置为 Idle、2 置为 Stopped
application.update-check-interval12更新时间检查间隔(小时)0 关闭检查

1.4 硬编码常量(改不了,只能绕)

项值出处绕法
监听端口9999server-startup.ts 里 const port = 9999;前面加反向代理
前端静态目录/handbrake-web/clientroutes/client.ts 里 path.join('/handbrake-web/client')由反向代理托管同一份构建产物
预设临时文件/tmp/preset.jsonworker 转码前写入无法改;多任务同机并行时互相覆盖
任务日志名<WORKER_ID>-job-<N>.logworker 日志模块无法改
服务端日志轮转每天、保留 5 份CreateRotatingFileLogger无法改
SQLite 文件名handbrake.dbdatabase.ts无法改
ffprobe内置 ffprobe-staticserver 依赖不需要系统装 ffprobe
HandBrakeCLI 命令名HandBrakeCLIworker spawn('HandBrakeCLI')只能保证它在 PATH 里

二、服务端环境变量逐项验证

2.1 DATA_PATH 与 VIDEO_PATH 的默认值

不设置任何路径变量,从 server/build 的上一级目录启动:

cd server
env -u DATA_PATH -u VIDEO_PATH node build/server.js

权限检查阶段的原始输出:

[server] [permissions] Checking necessary directory permissions...
[server] [permissions] Checking permissions for the path '/opt/data/quzuo/.trae/tmp/handbrake-web/src/data'.
[server] [permissions] [error] The directory mapped to '/opt/data/quzuo/.trae/tmp/handbrake-web/src/data' does not have adequate read permissions. This will cause issues.
[server] [permissions] [error] The directory mapped to '/opt/data/quzuo/.trae/tmp/handbrake-web/src/data' does not have adequate write permissions. This will cause issues.
[server] [permissions] [error] Cannot recurse the directory '/opt/data/quzuo/.trae/tmp/handbrake-web/src/data'.
[server] [permissions] Checking permissions for the path '/opt/data/quzuo/.trae/tmp/handbrake-web/src/video'.
...
[server] [permissions] Finished checking permissions across 2 paths with 4 errors and 0 warnings.

结论:

  • 默认 DATA_PATH 是 工作目录上一级的 data(cwd()/../data),不是可执行文件所在目录;
  • 默认 VIDEO_PATH 是 工作目录上一级的 video;
  • 目录不存在时权限检查会报 4 条 error,随后数据库打开失败,进程以 ENOENT 退出(实测 exit=1)。

这也是"源码跑起来必须显式给路径"的原因:默认值只对仓库自带的开发目录结构成立。

2.2 显式指定路径后启动正常

DATA_PATH=/srv/handbrake-web/data \
VIDEO_PATH=/srv/handbrake-web/video \
DEFAULT_PRESETS_PATH=/opt/handbrake-web/preset_builtin.json \
node build/server.js
[server] [permissions] Checking permissions for the path '/opt/data/quzuo/.trae/tmp/handbrake-web/c1/data'.
[server] [permissions] Finished checking permissions for the path '/opt/data/quzuo/.trae/tmp/handbrake-web/c1/data'.
[server] [permissions] Checking permissions for the path '/opt/data/quzuo/.trae/tmp/handbrake-web/c1/video'.
[server] [permissions] Finished checking permissions for the path '/opt/data/quzuo/.trae/tmp/handbrake-web/c1/video'.
[server] [permissions] Finished checking permissions across 2 paths with 0 errors and 0 warnings.
[server] [config] Created the config file at '/opt/data/quzuo/.trae/tmp/handbrake-web/c1/data/config.yaml' with recommended defaults.
[server] [config] The config file at '/opt/data/quzuo/.trae/tmp/handbrake-web/c1/data/config.yaml' has been loaded.
[server] [presets] Default presets have been loaded from '/opt/data/quzuo/.trae/tmp/handbrake-web/preset_builtin.json'.
[server] [presets] The directory for preset storage at the path '/opt/data/quzuo/.trae/tmp/handbrake-web/c1/data/presets' does not exist, making the directory.
[server] [presets] 2 presets have been loaded from '/opt/data/quzuo/.trae/tmp/handbrake-web/c1/data/presets'.
[server] [queue] The queue status does not exist in the database, initializing to the state 'stopped'.
[server] [database] Sucessfully updated the status of 'queue' to 'Stopped'.

两个附带结论:

  1. config.yaml 在首次启动时按默认值自动生成,路径是 $DATA_PATH/config.yaml;
  2. presets/ 目录不存在会自动建,用户预设从这里加载(本次实测只有 2 个,因为目录是新的)。

2.3 DEFAULT_PRESETS_PATH 缺失会让 server 起不来

只给 DATA_PATH/VIDEO_PATH、不给 DEFAULT_PRESETS_PATH:

DATA_PATH=/srv/handbrake-web/data VIDEO_PATH=/srv/handbrake-web/video node build/server.js
[server] [presets] [error] Could not load the default presets from '/var/lib/handbrake/preset_builtin.json'.
Error: ENOENT: no such file or directory, open '/var/lib/handbrake/preset_builtin.json'

实测进程退出码 1,没有任何降级逻辑。生产部署时这个文件必须存在,路径可以是:

  • 官方预编译发行包自带的 /var/lib/handbrake/preset_builtin.json;
  • 从 HandBrake 源码仓库取 preset/preset_builtin.json 放到任意目录,再用 DEFAULT_PRESETS_PATH 指过去。

2.4 PORT 根本不存在

给进程设置 PORT=8129 后,实际监听地址仍是 9999:

DATA_PATH=/srv/handbrake-web/data VIDEO_PATH=/srv/handbrake-web/video \
DEFAULT_PRESETS_PATH=/opt/handbrake-web/preset_builtin.json \
PORT=8129 node build/server.js

运行中采集监听表:

*:9999

日志里也只有:

[server] Available at 'http://localhost:9999'.

源码中对应的是 server-startup.ts:

const url = process.env.SERVER_URL || 'http://localhost';
const port = 9999;

SERVER_URL 只参与上面这条日志的拼接,port 是字面量。所以 PORT、LISTEN_PORT、HTTP_PORT 之类变量一律无效,想让页面跑在 80/443 只能靠反向代理。

2.5 NODE_ENV 决定两件事

源码里 NODE_ENV 出现在两处,作用完全不同:

// routes/client.ts
const isProduction = process.env.NODE_ENV == 'production';
if (isProduction) app.use(express.static(clientBuildPath));
// scripts/version.ts
if (process.env.NODE_ENV == undefined || process.env.NODE_ENV == 'development') {
	// 跳过更新检查
}

实测对照:

设置启动日志中是否有 [version] 行
不设 NODE_ENV完全没有
NODE_ENV=production有 Checking if there is an application update...
[server] [version] Checking if there is an application update...
[server] [version] The application is up to date.

两条实践结论:

  1. 生产环境必须设 NODE_ENV=production,否则内置页面不会被挂载(访问根路径只会落进兜底路由,取不到文件);
  2. 内网离线环境如果不希望它每 12 小时访问一次代码托管站点,别靠"不设 NODE_ENV"来规避——那会连带关掉静态目录;正确做法是把 update-check-interval 设为 0。

2.6 SERVER_URL 只影响一行日志

在 server 上设置 SERVER_URL=https://hb.example.com,启动日志变成:

[server] Available at 'https://hb.example.com:9999'.

监听地址、路由、CORS 都不受影响(cors 配置是通配 origin: '*')。如果反向代理做了 TLS 终止,把它设成外部地址纯粹是为了让日志可读。

三、worker 环境变量逐项验证

3.1 WORKER_ID 缺失:打印错误,退出码 0

env -u WORKER_ID VIDEO_PATH=/srv/handbrake-web/video \
SERVER_URL=127.0.0.1 SERVER_PORT=9999 node build/worker.js
[worker] [permissions] Finished checking permissions across 2 paths with 0 errors and 0 warnings.
[undefined] No 'WORKER_ID' envrionment variable is set - this worker will not be set up.

原文在句尾还有一句提示,指向部署侧编排文件里的环境变量段,与本文讨论的变量本身无关,这里按句号截断。

实测退出码 0,不是 1 也不是 2。这个细节很关键:用 systemd 托管时 Restart=on-failure 不会把它拉起来,用 Restart=always 则会陷入无休止的重启循环(每次都是秒退)。同理,任何"退出码非 0 才算失败"的监控都会漏掉这一条。

日志前缀 [undefined] 也是线索——日志模块用 WORKER_ID 作为标签,变量为空时标签就变成字符串 undefined。

3.2 缺 SERVER_URL / SERVER_PORT:报错后进程自行退出

WORKER_ID=w2 VIDEO_PATH=/srv/handbrake-web/video node build/worker.js
[w2] [properties] HandBrake Version = 1.11.0
[w2] [properties] Capability 'cpu' support = true
[w2] [properties] Capability 'qsv' support = false
[w2] [properties] Capability 'nvenc' support = false
[w2] [properties] Capability 'vcn' support = false
[w2] The SERVER_URL or SERVER_PORT environment variables are not set, no valid server to connect to.

注意顺序:能力探测先做完,连接判断在最后。因此即使连不上 server,HandBrakeCLI --version 也会被执行一次,日志里能直接看到版本号,可用来确认 PATH 是否配置正确。

进程随后因为没有待处理的句柄而自然退出,实测退出码同样是 0。

3.3 SERVER_URL 不要带端口

目标地址是拼出来的:

serverAddress = `${serverURLPrefix ? serverURL : 'http://' + serverURL}:${serverPort}/worker`;

所以:

SERVER_URLSERVER_PORT拼出的地址
127.0.0.19999http://127.0.0.1:9999/worker
handbrake-server9999http://handbrake-server:9999/worker
https://hb.example.com443https://hb.example.com:443/worker
http://127.0.0.1:99999999http://127.0.0.1:9999:9999/worker ← 非法

最后一行就是实测踩到的坑:worker 起来后日志只有断连与重连提示,服务端从头到尾看不到注册记录。SERVER_URL 只写主机名(需要 TLS 时写协议 + 主机名),端口一律交给 SERVER_PORT。

3.4 DATA_PATH 的两套规则

export const getDataPath = () => process.env.DATA_PATH || '/tmp/handbrake-web';

worker 默认就落在 /tmp/handbrake-web,启动时会打印:

[worker] [permissions] Checking permissions for the path '/tmp/handbrake-web'.
[worker] [permissions] Finished checking permissions for the path '/tmp/handbrake-web'.

自定义时有一条隐藏规则:只有路径位于 /tmp 之下才会被自动创建,否则要求目录已存在。给一个不存在的非 /tmp 路径:

WORKER_ID=w4 DATA_PATH=/var/tmp/c1-missing-data \
VIDEO_PATH=/srv/handbrake-web/video SERVER_URL=127.0.0.1 SERVER_PORT=9999 node build/worker.js
Error: ENOENT: no such file or directory, access '/var/tmp/c1-missing-data'
  errno: -2,
  code: 'ENOENT',

实测退出码 1,且是在权限检查阶段就崩,连能力探测都没走到。生产上要么提前建好目录并给足权限,要么干脆不挂载这个卷、让日志只留在本地临时目录。

3.5 VIDEO_PATH 与权限检查

worker 启动第一步是检查两个路径(VIDEO_PATH 与 DATA_PATH)的读/写权限:

[worker] [permissions] Checking permissions for the path '/opt/data/quzuo/.trae/tmp/handbrake-web/c1/video'.
[worker] [permissions] Finished checking permissions for the path '/opt/data/quzuo/.trae/tmp/handbrake-web/c1/video'.
[worker] [permissions] Finished checking permissions across 2 paths with 0 errors and 0 warnings.

如果 VIDEO_PATH 不设置,默认是 cwd()/../video;在构建产物的工作目录下启动时这个目录通常不存在,于是报 ENOENT 直接退出(实测退出码 1)。worker 的 VIDEO_PATH 必须和 server 指向同一批文件——任务里存的是绝对路径字符串,两边看到的路径不一致就会在打开输入文件时失败。

四、config.yaml 逐项验证

4.1 首次启动生成的内容

cat "$DATA_PATH/config.yaml"
config:
  version: 2
paths:
  media-path: /video
  input-path: /video
  output-path: ""
presets:
  show-default-presets: true
  allow-preset-creator: false
application:
  queue-startup-behavior: 0
  update-check-interval: 12

4.2 配置确实下发到了页面

页面拿到的配置来自 config-update 事件。用脚本连一次 /client 即可看到完整 JSON:

const { io } = require('socket.io-client');
const s = io('http://127.0.0.1:9999/client');
s.on('config-update', (c) => { console.log('config:', JSON.stringify(c)); process.exit(0); });

实测输出:

config: {"config":{"version":2},"paths":{"media-path":"/video","input-path":"/video","output-path":""},"presets":{"show-default-presets":true,"allow-preset-creator":false},"application":{"queue-startup-behavior":0,"update-check-interval":12}}

同一连接还能收到:

presets: ["uncategorized"]
default categories: [ 'General', 'Web', 'Devices', 'Matroska', 'Hardware', 'Professional' ]

这解释了 show-default-presets 的真实作用:服务端总是加载内置预设并通过 default-presets-update 下发,show-default-presets=false 只是让页面不去展示它们。

4.3 paths 三项只影响界面

paths 在服务端的用途仅限于保存与回传,真正的取数在页面侧:

  • media-path 是目录浏览器的根,页面会校验 input-path、output-path 必须以它开头:
const mediaPathRegex = new RegExp(`^${paths['media-path']}`);
const inputPathValid = paths['input-path'].match(mediaPathRegex);
  • input-path 是"添加任务"时浏览器的起始目录;
  • output-path 为空时取所选输入目录,非空则固定成该目录。

它们不参与任务执行:任务入库时存的是完整绝对路径(input_path、output_path),与这三个字段无关。把 media-path 改成 /mnt/media 而实际文件不在那里,只会让界面上选不到文件。

4.4 queue-startup-behavior 三档实测

枚举值(服务端与页面共用):0 = Previous、1 = Active、2 = Stopped。

0(默认,沿用上次状态):

[server] [queue] Existing queue status 'Stopped' retreived from the database.

1:

[server] [queue] Initializating the queue state to 'Active'.
[server] [database] Sucessfully updated the status of 'queue' to 'Idle'.

2:

[server] [queue] Initializating the queue state to 'Stopped'.
[server] [database] Sucessfully updated the status of 'queue' to 'Stopped'.

要点:Active 并不等于"立刻开跑",它把队列状态置为 Idle(等待 worker 与任务);Previous 依赖数据库里的历史状态,首次启动时数据库没有这条记录,会走错误分支:

[server] [queue] The queue status does not exist in the database, initializing to the state 'stopped'.

这条 [error] 级日志在全新部署时必然出现,属正常现象。

4.5 update-check-interval 设为 0 关掉外联

NODE_ENV=production、间隔 12 时:

[server] [version] Checking if there is an application update...
[server] [version] The application is up to date.

改成 0 后同一套环境:

[server] [version] The check-interval is set to 0, update checking is disabled.

实测确认两点:

  1. 检查发生在启动时,之后按小时周期重复;
  2. 0 是"关闭"而不是"每小时",内网环境可以放心用。

4.6 配置写坏了不会让进程退出

LoadConfig 的错误分支里 process.exit() 被注释掉了:

} catch (error) {
	logger.error(`[server] [config] [error] Could not load the config file from '${configFilePath}'.`);
	logger.error(error);
	// process.exit();
}

后果是文件损坏(YAML 语法错、字段类型错)时进程照常启动,但内部用的是编译期默认值,页面显示与磁盘文件不一致。发现"改了配置没生效"时,先看日志里有没有 Could not load the config file。

4.7 改配置的两种正确姿势

方式做法生效范围
改文件编辑 $DATA_PATH/config.yaml 后重启进程全部
页面改在设置页保存,服务端写回文件并广播 config-update已连接页面立即生效

页面保存走的是 WriteConfig,会先写文件成功再更新内存——写失败会打印 Could not write new config to file.,此时页面上的修改不会保留。

五、实测矩阵

下表 12 项均在 2026-10-27 实机执行,命令与结论一一对应,便于复现。

#测什么命令要点结果
1DATA_PATH 缺省不设变量启动 server指向 cwd()/../data,目录缺失 → 4 条权限 error + 退出码 1
2VIDEO_PATH 缺省不设变量启动 server指向 cwd()/../video,同上
3显式路径设 DATA_PATH/VIDEO_PATH/DEFAULT_PRESETS_PATH权限 0 错,自动建 config.yaml 与 presets/
4内置预设缺失去掉 DEFAULT_PRESETS_PATHENOENT ... /var/lib/handbrake/preset_builtin.json,退出码 1
5PORT 是否生效PORT=8129仍监听 *:9999
6NODE_ENV 未设不设变量日志无任何 [version] 行(更新检查被跳过)
7NODE_ENV=production + 间隔 12改配置后启动Checking if there is an application update... / The application is up to date.
8update-check-interval=0改配置后启动The check-interval is set to 0, update checking is disabled.
9queue-startup-behavior=1改配置后启动Initializating the queue state to 'Active'. → queue 置为 Idle
10queue-startup-behavior=2改配置后启动Initializating the queue state to 'Stopped'.
11WORKER_ID 缺失不设变量启动 workerNo 'WORKER_ID' ... + 退出码 0
12worker 缺 SERVER_PORT设 WORKER_ID、VIDEO_PATHThe SERVER_URL or SERVER_PORT environment variables are not set, no valid server to connect to. + 退出码 0
13worker DATA_PATH 非 /tmp 且不存在DATA_PATH=/var/tmp/c1-missing-dataENOENT ... access '/var/tmp/c1-missing-data' + 退出码 1
14配置下发连 /client 收 config-update完整 JSON 与文件一致

六、常见误区与建议

6.1 这些变量不存在

误传的变量真实情况替代
PORT源码没有读取反向代理换端口
LOG_DIR / LOG_PATH日志固定落在 $DATA_PATH/log只能改 DATA_PATH
WORKER_DATA_DIRworker 只认 DATA_PATH设 DATA_PATH
HANDBRAKE_PATH / HANDBRAKE_BIN命令名写死 HandBrakeCLI保证 PATH 里有它,或改包装脚本
DATABASE_PATH / DB_PATH固定 $DATA_PATH/handbrake.db改 DATA_PATH
CLIENT_BUILD_PATH固定 /handbrake-web/client用反向代理托管前端

6.2 推荐的生产配置

server:

DATA_PATH=/srv/handbrake-web/data
VIDEO_PATH=/srv/handbrake-web/video
DEFAULT_PRESETS_PATH=/srv/handbrake-web/preset_builtin.json
NODE_ENV=production

worker:

WORKER_ID=worker-01
SERVER_URL=handbrake-server
SERVER_PORT=9999
VIDEO_PATH=/srv/handbrake-web/video
DATA_PATH=/srv/handbrake-web/worker-data/worker-01

配套的 config.yaml(内网、禁止外联):

config:
  version: 2
paths:
  media-path: /video
  input-path: /video
  output-path: /video/output
presets:
  show-default-presets: true
  allow-preset-creator: false
application:
  queue-startup-behavior: 2
  update-check-interval: 0

6.3 排查顺序

  1. 进程起不来 → 先看权限检查日志里的路径对不对(DATA_PATH/VIDEO_PATH 缺省是相对工作目录的);
  2. 起得来但打不开页面 → 检查 NODE_ENV=production 与前端产物是否落在 /handbrake-web/client;
  3. worker 连不上 → 按 SERVER_URL 不带端口、SERVER_PORT 为目标监听端口、DNS 解析三步走;
  4. 改配置没生效 → 看 Could not load the config file,或确认重启过;
  5. 任务失败但日志只有 [error] 级提示 → 那些是 HandBrake 写到 stderr 的探测信息,真正的成败看队列状态。

延伸阅读:《Ubuntu 22.04 安装 HandBrake Web 0.8.1 指南》、《HandBrake Web 0.8.1 介绍与选型指南》。