2 Commits
Author SHA1 Message Date
tamakyi 6f3c8096b4 修分块渲染的弹幕轨迹:跨块续画 + 文字头像同源,并补失败推送与输出帧率设置
1. 密集弹幕时头像偏离轨迹、弹幕突然加速、之后一段时间屏幕空白

   分块渲染的固有问题,三个毛病叠在一起:

   - writeChunkAss 把 \move 的起止时间截断到块边界(End=min(t0+10, chunk.end)),
     但 x2 写死成 -Width —— 等于把 10 秒的路挤进可见的那零点几秒里,文字直接飞过去。
     而头像 overlay 的 x 一直是「固定 /10 秒 + 真实 t0」,于是同一时刻文字已经走到
     左边、头像还在半路,看起来就是「头像掉队(移动变慢)」。
   - 块尾还会被单块上限收紧成「下一条弹幕出现的时间」,最极端时窗口只剩 0.01 秒。
   - 每块只画「本块新出现」的弹幕,跨块的续画没有:块头屏幕上只剩刚出现的几条,
     要 10 秒才重新填满 —— 就是「弹幕突然加速,然后一段时间屏幕上什么都没有」。

   改法:文字层改成按绝对时间算轨迹(danmuTextX / danmuAvatarX / avatarXExpr 同源),
   任何一块里的文字速度都等于真实速度;分块器改成「续画(起播时间在 start-10 ~ start)
   + 本块新出现的」,块尾取「下一条弹幕的起播时间」,这样每条弹幕只在唯一一块里算
   新出现,显示期间跨过的后面几块把它接着画,一帧都不缺。

   代价是每块要多画一倍多的弹幕,所以顺手把头像一个「绝对路径 + 每人一行 scale」改成
   「预缩放 + 短相对路径」(prepareAvatarInputs / scaleAvatarFile),每条从实测 241
   字符降到约 130,单块上限才能从 120 抬到 200(200 条 ≈ 26600,仍低于自检阈值 30000
   和 Windows 的 32767)。

2. 合并失败没有任何通知

   notify.go 新增 mergeFail 事件与 notifyMergeFailure(),接在 autoMerge() 和手动合并的
   失败分支上。正常结束、文件里没有弹幕、以及用户自己点的停止都不算失败,不推 ——
   最后一种推「合并失败」会误导。配置项 notifyMergeFail 默认开,且和 notifyEnabled 一样
   在 Unmarshal 之前预置 true,免得老设置文件升级后被静默关掉。

3. 低帧率录播里的弹幕一格一格跳

   新增设置项 mergeFps,默认 0 = 跟随源片(什么都不做)。只在目标高于源片帧率时才加
   fps 滤镜,而且必须加在字幕/头像之前 —— 加在之后只是把已经画好的几帧复制一遍,
   看起来还是卡。上限 240。

4. 顺带

   - -danmuburn 命令行模式不走 initialize(),exeDir 从来没被赋值:头像缓存会落到
     「当前工作目录/avatar_cache」,旧版还会把这个相对路径写进滤镜,而 ffmpeg 的工作
     目录是弹幕目录,报「打不开 avatar_cache\xxx.png」整块失败。
   - cleanTempFiles 见到目录就 continue,预缩放头像那个临时文件夹永远清不掉。
   - command_len_test 不再用会过期的「每条 241 字符」估算,改成真跑一遍最坏情况;
     探针的头像路径也改成真实的短相对路径(原来量的是早已不存在的长绝对路径)。
   - 新增 TestWebuiSettingFieldsMatchInputs:把页面里 JS 的 settingFields/numberFields
     抠出来,跟所有 input/select/textarea 的 id 比对,漏登记的设置项会被抓住。

验证

- gofmt / go vet / go test(174 个)/ go test -race / staticcheck / GOOS=linux go build 全过。
- 反向验证 32 项全过:把每个修复临时退回,对应测试确实失败。
- 真实片段实测(2023AcFun 晚会 120 秒高峰段,689 条弹幕,10 秒窗口内最多同时 140 条):
  旧 6 块 / 绘制 689 次 / 54.1% 的弹幕文字被加速(中位 2.12x,最差 500x)/
  54.2% 有轨迹缺口(中位缺 5.29 秒)/ 78.3s;
  新 5 块 / 绘制 984 次 / 加速 0% / 缺口 0% / 98.8s(+26%)。
  对照帧里旧版在块尾前 0.1 秒屏幕已经全空,块头后 0.4 秒仍然全空;新版一直是满屏。
- 输出帧率:15fps 源配 mergeFps=60,成片 60fps,1 秒内 60 帧哈希互不相同(不是复制帧);
  默认设置下成片仍是 15fps。
2026-09-26 16:34:47 +08:00
tamakyi a2b7185a7b 头像链接落盘 + 并发获取 + 单块上限按命令行实测值收紧
一、头像链接的反查结果落盘(avatar_cache/uid_urls.jsonl,7 天有效期)
   老录播的 jsonl 里没有头像链接,合并时要按 uid 向 AcFun 反查;但结果原来只存在内存里,
   进程一重启就得把整场 uid 重新查一遍。实测一个 uid 约 1 秒,几百人的场次要几分钟,
   日志还跟着打「正在获取头像:已处理 x/y 个用户」,看起来就像在重新下载头像
   (实测图片缓存本身是好的:同进程第二轮、重启进程后第三轮都是「新增 0、重写 0」)。
   现在查到就落盘(追加写,加载时顺手压掉重复/过期行),重启后直接读记录不再查。
   有效期 7 天是为了主播换头像后能自己更新过来。

二、头像获取改成 6 路并发
   查链接和下图都要走网络,串行做几百人要好几分钟。每个任务开头仍过闸门,
   暂停/停止的响应不受影响(任务走带缓冲的 channel,worker 提前退出也不会卡住投递端)。

三、日志分清楚「复用」和「真下载」
   原来的 looked++ 只看「这一条没有链接」,内存记忆命中时也会打印「已按 uid 向 AcFun 反查」,
   误导成在重下。现在汇总成一句:链接复用 X / 向 AcFun 查 Y,图片复用缓存 Z / 新下载 W。

四、单块条数上限从 200 收紧到 120(线上事故)
   线上一次 4 万条弹幕的合并在第 73 块失败:
   "fork/exec ...: The filename or extension is too long"(就是 200 条上限那一版)。
   真机实测:一块里每条弹幕来自不同用户时,每条占「-i 头像路径 + scale 行 + overlay 行」
   约 241 字符 —— 120 条 28864 字符、200 条 48288 字符,而 Windows 的 CreateProcess 限 32767。
   现在上限 120(给中文路径和其它参数留 3KB 余量),并加了一道自检:真超了就报
   「ffmpeg 命令过长(N 字符):这一块有 X 条弹幕、Y 个头像」,而不是烂在 exec 里。

   顺带试过 -filter_complex_script(滤镜写文件、命令行只留文件名),
   实测这个 ffmpeg 构建没有该选项(Unrecognized option),所以走收紧条数这条路。

验证:
- gofmt / go vet / go build / GOOS=linux go build 通过;go test 全绿(11.0s);
  go test -race 全绿(15.7s);staticcheck -checks=all 零告警
- 新增 avatar_cache_test.go(11 个)与 command_len_test.go(3 个)用例;反向验证扩到 17 项全过
- 隔离实例:e2e.py 36 项、smoke_extra.py 33 项全过
- 新增 smoke_manyuid.py(300 条密集弹幕 / 300 个不同用户 / 全带头像,就是线上事故的复现场景):
  合并成功、分 3 块(120/120/60 条)、日志无「命令过长」;
  第二次合并同一场「图片复用缓存 300 / 新下载 0」
2026-09-26 11:08:26 +08:00