视频编码实测档案 · Intel Arc A380 QSV

DanmakuRender 改造版

基于 SmallPeaches/DanmakuRender v5 的改造版本, 面向「直播录制 → 弹幕渲染 → 网盘归档」的无人值守流水线,做了 12 项改造与稳定性加固。 当前 v5.1.0,可长期无人值守运行。

GitHub · DanmakuRepo

流水线

监听开播 → 录制 → 叠加弹幕渲染 → rclone 上传网盘 → 清理本地源文件
                              ↑
                全程 SQLite 状态持久化,重启自动对账

两个栏目的关联:本项目的第 1 项改造,与本站编码栏目遇到的是同一个缺陷—— ffmpeg 的 -global_quality 缺少 :v 流说明符,参数落到音频编码器, libopus 不支持质量模式,直接报错并产出 0 字节文件。
详见踩坑记录中"音频报 Quality-based encoding not supported"一条。 两条独立工序先后撞上同一处,这也是把两者放在同一站点的原因。

12 项改造

#改动原因
1 编码参数缺失 :v 的自动检查 0 字节 -global_quality 32 会被套到音频编码器上,libopus 不支持质量模式,导致渲染失败、输出 0 字节;而清理规则仍会删除源视频,造成录播永久丢失。现于启动时检查 -global_quality / -crf / -cq / -qp 是否带 :v
2 GOP 按帧率自动计算 gop_multiplier: 8 → 60fps 得 -g 480、30fps 得 -g 240。帧率由 ffprobe 实时探测。22fps × 8 = 176,实测关键帧位置 [0, 176, 352, 528] 完全吻合
3 YouTube 上传改为 rclone 原版依赖 googleapiclient 但未列入 requirements,启用必然 ImportError。改为 rclone,并将功能重叠的 custom 段合并进 rclone
4 target 按 engine 自动推断 原版一个 key 展开多份上传时,各份均从 bilibili 段继承默认值,导致"B站 + 网盘"场景中网盘那份错误继承 realtime: True
5 _check_for_upload 空列表保护 同组全部分段短于 min_length 时,up_videos[0] 抛 IndexError,中断整个上传流程
6 GetStreamURL() 返回 None 时可读报错 抖音风控或限流时无 stream_url,原版继续执行并抛 NoneType is not iterable,无法判断是流地址缺失。历史日志中出现 26 次,每次该段录制作废
7 rclone 失败可读化 原版仅有 return code: 1。现改为子进程输出重定向到临时文件(避免管道阻塞),失败时附尾部 2 KB,命令不存在时明确提示
8 大文件跳过上传规则 横屏且(码率 ≥2500 或 帧率 ≥40)则跳过上传,节省带宽与网盘空间。跳过不影响本地清理
9 清理与上传完全解耦 详见下节事故复盘,改动最重的一处
10 移除 B站上传段 任务中仍写 target: bilibili 时跳过并记录 warning,不中断流程。biliup.exe(26.8 MB)保留——删除反而会触发自动安装弹窗并阻塞
11 rclone remote 名修正 注释写作 123pan,实际网盘 remote 为 cd2(WebDAV,12 TB 已用 7 TB)。两者为不同网盘
12 .gitignore 补充敏感信息 忽略 rclone.conf 与 .login_info/,避免网盘密码与 cookie 被推送至 GitHub

事故复盘:渲染未结束时源文件被删除

2026-10-05 真实事故:原视频在渲染尚未结束时被删除,渲染失败 (不存在视频文件 xxx.mkv,跳过渲染),原文件不可恢复。 实际损失:1 段录播(掉了颗兔牙-2026年10月05日01点33分),源文件被删且弹幕版未生成。

根因:将 _check_for_clean 放宽为「文件自身 ready 即清理」时, 忽略了一个事实——src_video 一录完即为 ready,但此刻它正被渲染进程读取。 日志显示渲染任务与清理任务在同一毫秒提交(02:33:17.573 / .574),清理先执行, 渲染启动时文件已不存在。

现有三道防线(修改代码时不可移除)

防线位置作用
1onLiveSegment不在渲染期间调用 _check_for_clean。源视频清理仅由 onRenderEnd 触发
2_check_for_clean文件仍有未完成的异步任务(wait 非空)时不清理
3_check_for_clean源视频的下游仍处于 rendering 状态时不清理

防线 3 只拦 rendering,故意不拦 uploading—— 上传读取的是 dm_video(另一文件),不影响源视频;且上传任务投递即返回、不等结果。 若拦 uploading,源视频须等上传完成后才能删除,违背"渲染完成即清理"的设计目标。

回归测试:tools\test_clean_order.py 按真实事件顺序断言。修改清理逻辑后必须执行。

解耦后的逻辑

弹幕版渲染完成 ─┬─ 源视频 .mkv  → 立即按 clean_args 删除(不等上传)
                ├─ 弹幕版 .mp4  → 立即提交上传(rclone → 网盘)
                └─ 弹幕版 .mp4  → 本地保留(作为备份)

上传成功或失败均为独立结果,不影响上述清理行为

另一处细节:upload_skipped 必须被视为可清理状态,否则命中跳过规则的文件永远无法清理,源视频持续堆积。

跳过上传规则的判定

当前生效规则 = 横屏必选 且(码率 ≥2500 或 帧率 ≥40):

情况判定
横屏 1080p 6000kbps 30fps跳过上传 横屏 + 码率
横屏 1080p 1000kbps 60fps跳过上传 横屏 + 帧率
横屏 1080p 6000kbps 60fps跳过上传 三项全部满足
横屏 1080p 2500kbps 40fps跳过上传 恰好达到阈值
横屏 1080p 1500kbps 30fps正常上传 两项均未达到
横屏 1080p 2499kbps 39fps正常上传 略低于阈值
竖屏 1080×1920 8000kbps 60fps正常上传 横屏为必选项,竖屏不跳过
竖屏 1080×1920 1000kbps 30fps正常上传

已修正的问题:若横屏参与命中计数,skip_min_matches: 1 会退化为 "只要横屏即跳过",码率与帧率阈值形同虚设。因此在必选模式下,横屏被明确排除在计数之外。

另:数据缺失时 fail-open——任一项探测不到均不触发跳过,宁可多传,不可误跳。

前提:该规则仅在 auto_upload: True 时生效。未开启上传的任务不会触发。

历史日志分析:127 个文件、39.5 万行

基于原版 12.8 万条记录分析,结论是真正的问题仅三类,其余为噪音。

问题次数性质
Stream-gears 异常退出 1,520 次真实事件
(50 个主播)
真实问题 主播仍在线时 biliup 子进程退出,该分段作废。根因为 biliup Rust 层缺陷: 抖音流 Non-monotonous DTS 触发 overflow when subtracting durations。 上游未修复,依靠重连退避自动续录
抖音 API KeyError('data') 6,430 条 真实问题 风控或限流时接口不返回 data,连锁导致第 6 项所修的 NoneType 崩溃
磁盘写满 [Errno 28] 9 条 真实问题 直接中断录制,需增加水位告警
弹幕链路 error 814 条 噪音 全部自愈(自动取消并重建 task),仅造成弹幕数秒至数十秒空白,视频不受影响
download.log WARN 102 万条 噪音 FLV 解析器常规抖动(Non-monotonous DTS 96 万条),零 ERROR。 但文件已达 140 MB / 105 万行,需增加轮转

平台分布:全部错误均来自抖音,B站为 0 条——所有任务都是抖音直播间。

遗留问题(原版既有)

  1. dm_filter.dm_type 留空即只收纯文字弹幕,礼物弹幕会在进入模板前被丢弃——本版已填(设为 [danmaku, gift],实测:纯弹幕 ✓ / 礼物 ✓ / 进场 ✗ / 超级弹幕 ✗)。注意原版为 all,两侧有意不同
  2. B站 cookie 过期不提示,表现为上传持续失败(cookie 位于 ./login_info/bilibili.json)
  3. .temp/failed_uploads.json 中的失败上传不会自动重试,需在 WebUI 手动触发
  4. 失败任务列表无时间戳,重启后状态仍显示 rendering,看似卡住实为未运行
  5. 运行中修改配置会导致任务静默失败(Task xxx not exists.,历史 56 次)。修改 configs/DMR-*.yml 后建议重启程序
  6. DNS 故障导致日志暴涨:2026-08-18~19 两天 42,718 条 Failed to resolve(约 1600 条/小时),重试循环无退避与熔断,日均写入 20 MB
  7. download.log 无限增长,需定期清理或增加轮转

与上游的差异

方面上游本版
上传目标B站 / YouTube / 自定义rclone → 网盘
弹幕过滤全部接收仅弹幕与礼物
跳过上传无横屏 / 码率 / 帧率规则
清理与上传耦合解耦
通知无开播通知 + 启动报告
启动自检无配置完整性 + 关键命名 + 0 字节保险闸
工具集基础20 余个运维与审计工具
测试437 个单元测试沿用,未新增运行时依赖

关键设计:0 字节产物保险闸

即使 ffmpeg 报成功,也会校验产物非空——避免上传空文件并误删源视频。 这是第 1 项改造(:v 问题)之后的第二道保险:参数错误不会静默产出空文件。

渲染失败不触发清理;产物为空判定为失败并删除残留,源视频保留。