tamakyi
|
3e16e78003
|
.ass 时间基准和视频对不上时按第一条弹幕自动对齐,并去掉重复的还原日志
1. 整场弹幕都被判「超出视频时长」,合并直接取消
上游原版录出来的 .ass 时间不是相对录制起点,而是相对弹幕客户端连上房间的时刻。
程序先开着、直播中途才开始录的场次,这份 .ass 的零点会比视频起点早一大截
(用户实测那份早了 7849.92 秒:955 条弹幕、跨度 5436.7 秒,视频只有 5456.9 秒)。
一律按「视频时间 = 弹幕时间 + 偏移」算的话,这 955 条全都落在 5456.9 秒之外,
于是「有 955 条弹幕超出了录播视频的时长,已跳过」→「弹幕文件里没有弹幕,取消手动合并」,
一分半的录播一条弹幕都合不出来。
这类偏移是能救的:跨度(末 - 首)5436.7 秒装得进 5456.9 秒,整体前移 7849.92 秒之后
覆盖 0 ~ 5436.7 秒、尾部正好空 20.2 秒 —— 和正常录播的尾部空隙(实测 6~20 秒)吻合,
是时间基准偏移,不是文件配错。
新增 fitDanmuToVideo,在探到视频时长之后、裁剪之前介入,触发条件收得很紧:
只有「按现偏移一条弹幕都用不上」**且**「弹幕跨度 < 视频时长」才整体平移,把第一条
对齐到视频起点,日志写明平移量、对齐后的覆盖范围和尾部空隙,并提示整体偏早或偏晚
可以在「偏移」里微调。其余情况完全保持原行为:只是一部分超出 → 照旧只丢尾部;
跨度本身就装不下 → 一条不留。这样不可能影响任何现在能正常合并的场次。
2. 「已从 xxx.ass 还原出 N 条弹幕」会打印两遍
页面的「检查」和真正合并会各解析一遍 .ass,而这条日志写在被复用的 parseAssFile 里,
于是同一句话出现两次,第二遍后面还跟着「已把 xxx.ass 还原成 xxx.jsonl」,看不出谁是谁。
把这两条日志从 parseAssFile 里摘出去(它改成返回坏行数 bad,由调用方决定要不要告警),
交给真正落盘那次的调用方 prepareDanmuSource(path, true) 打印,现在还会写明写成了
哪个文件;坏行警告统一走新增的 logAssBadLines,不再重复。
顺手给「最后一条弹幕超出视频总长」那条警告补了指引,说明合成时会自动对齐。
3. 测试里后台任务漏到用例结束之后
safeGo 原来直接把 goroutine 扔出去,用例结束它还在读全局配置,下一个用例开头一改配置,
-race 就判成数据竞争(而且报在后面的用例上,很难看出真凶)。把「怎么起后台任务」抽成
var runBackground,生产行为不变,测试里换成可等待的版本,清理时等它收工。
注意:这个竞争单独 -run 一个用例复现不出来(-race -count=3 也过),必须整包跑
go test -race . 才会在后续用例上报 race detected。
验证
- gofmt 无输出 / go vet 通过 / go test(182 个,18.5s)全过 / go test -race 整包跑 20 轮
(13 轮顺序 + 6 轮 -shuffle=on)全过 / staticcheck 无输出 / GOOS=linux go build 通过。
另:有一轮 -race 曾报过 FAIL、随后 20 轮都没能复现,怀疑是 TestDownloadAvatarCapsResponseBody
自带的抖动(它的断言依赖「服务端写不完 10MB」,受内核缓冲大小影响),与本批改动无关,
该文件本批也没动过;待单独确认。
- 反向验证从 32 项扩到 37 项全过(新增:自动对齐 / 只丢尾部不平移 / 跨度装不进不强平移 /
还原日志只打一次 / 后台任务可等待);脚本新增单用例附加参数 "args" 与不带 -run 的整包用例。
- 用用户那份真实素材(955 条,7849.92 ~ 13286.59 秒,视频 5456.883 秒)端到端跑通:
不填偏移 + 5460 秒视频 → 日志「弹幕时间基准和视频对不上:955 条弹幕全部落在视频时长
(5460.0 秒)之外,最早那条在 7849.9 秒;已按「第一条弹幕对齐视频起点」整体前移
7849.9 秒(偏移 0.0 → -7849.9)」「对齐后弹幕覆盖 0.0 ~ 5436.7 秒,视频总长 5460.0 秒
(尾部空 23.3 秒)」,分 5 块渲染,exit=0,成片 5462.31 秒 / 23.3 MB。
再按 jsonl + 偏移反推「哪些时刻该有弹幕」,随机取 40 个应有时刻 + 20 个任意弹幕窗口都
不覆盖的空屏时刻,抽帧数白像素(白字黑底):60/60 全对,跨块边界(1116/2435/3393/4408)
与最尾部 t=5435.7 都对得上。
- 60 秒小片新旧对照(真实 9 条弹幕、原始时间戳 7849.92 ~ 7908.7):旧版「有 9 条弹幕超出了
录播视频的时长(60.1 秒),已跳过」+「jsonl 里没有弹幕」、无成片;新版自动前移 7849.9 秒、
9 条全画上、成片 34 MB。抽帧看轨迹仍是匀速 10 秒穿屏(danmuShowSeconds),
头像跟着文字同步走,没有回归。
|
2026-09-26 17:51:43 +08:00 |
|
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
|
9684346067
|
清掉 5 个确认没人调用的遗留函数
staticcheck 的 U1000 一直报这 5 个,逐个核对过引用(含同名方法、测试、字符串调用、反射):
- runFFmpeg:runFFmpegWith 的薄包装,没有任何调用点
- isLiveOn(uid int):包级函数没人用,实际在跑的始终是 streamer.isLiveOn() 方法
- quitRecord:需要收尾的地方都直接 updateLiveInfo(..., isRecording = false)
- setLiveInfo:整体覆盖 liveInfo,早已被 updateLiveInfo(原地改部分字段)取代
- isRecording(liveID):字段 info.isRecording 到处都在直接用
上一轮那个 jobActive 虽然也是 U1000,但它是「本该接上却漏接」的护栏(接回后
P0 的并发问题才解决),所以这次删之前特意确认这 5 个不是同类情况:它们都有
等价且更合适的现役替代实现。
验证:gofmt -l 空;go vet ./... 空;go build ./... 通过;go test ./... 全绿(11.3s);
staticcheck -checks=all 的 U1000 归零。
|
2026-09-26 09:37:11 +08:00 |
|
tamakyi
|
8e82aa54c6
|
修第二批 P1:合并收尾、分块上限、确定性失败与 ffmpeg 路径归一
一、合并收尾只删真正进了成片的分段
prepareForMerge 中途有 4 处会回退成「只合并第一个分段」,而收尾仍按原分段列表
全删源视频 —— 后半场没进成片却照样被删掉,等于永久丢数据。现在 prepareForMerge
把「实际参与合并的源文件」一并返回(新增 used),收尾只删这些。
二、拼接中断不再在录播目录留几 GB 中间文件
concat 失败分支原来只有「被停止」那条路径删了 acfunlive_chunk_joined.*,
普通失败会把它留在录播目录,而且带着 chunkTempPrefix,界面文件列表里根本看不见。
三、把「被停止」和「拼接失败」分开
停止任务会杀掉 ffmpeg 让它非 0 退出,原来一律报成「拼接失败」,
卡片上显示的是失败而不是已停止;prepareForMerge 那里也统一改用 errors.Is 判断。
四、单块弹幕条数加硬上限(maxDanmuPerChunkHard = 200)
块尾会把「显示时间跨过块尾」的弹幕一并并入,弹幕密集时单块能膨胀到几百条。
一条弹幕在 -filter_complex 里约占 110 字节,几百条足以顶破 Windows 的
32767 字符命令行上限,表现同样是「合并失败且看不出原因」。到上限就把块尾
收到下一条弹幕出现之前,保证被挤到下一块的弹幕不会因为「整条早于块头」被丢弃。
五、确定性失败不再白重试三次
头像超出体积/尺寸上限这类失败重试多少次结果都一样,原来会白白把十几 MB 重新下三遍。
新增 noRetryError(runThrice 用 errors.As 识别)只标记这两个上限,网络类错误照旧重试。
六、ffmpeg 路径统一归一成绝对路径
PATH 里带「.」这类相对条目、或程序目录探测失败时,getFFmpeg 会返回相对路径。
合并时 ffmpeg 工作目录被改成输出目录,相对路径到那边就找不着了;而且 Windows 上
exec 会直接拒绝执行「相对当前目录」找到的可执行文件,报错还跟路径完全看不出关系。
验证:
- gofmt -l 空;go vet ./... 空;go build ./... 与 GOOS=linux go build 通过
- go test ./... 全绿(10.8s);go test -race ./... 全绿(14.7s)
- 新增 chunks_test.go 10 个用例;6 项修复逐条反向验证(临时退回修复 → 对应测试确实失败)
- 51897 隔离实例上新产物实测:e2e.py 24 项全过;smoke_extra.py 34 项全过
(含并发合并返回 409 并报出在跑的任务名、口令失败后正确口令仍立刻可用、
超大头像被拒不误伤正常头像且只尝试 1 次、停止后无临时文件残留)
|
2026-09-26 09:35:51 +08:00 |
|
tamakyi
|
9b9b3fa2cd
|
弹幕写盘出错不再被静默吞掉
审查发现:录制时每写一条弹幕都调用 os.File.Write / fmt.Fprintf,
但返回值一个都没接。磁盘写满、外接盘或网络盘掉线时,写入会失败而程序
毫无察觉 —— 照样打印「录制完成」、照样自动合并,最后拿到的成片缺了一大截
弹幕,界面上看不出是「缺了」还是「本来就没弹幕」。
1. 写入器记住写盘错误
danmuWriter 增加一个 err 字段:每条弹幕的 ass / nick.ass / jsonl 写入,
以及文件头的写入,都检查返回值。只保留第一个错误 —— 磁盘满了之后
每写一条都会失败,全记下来只会把日志刷爆,而第一个就足以说明原因。
2. close() 负责把错误交出来
三个文件的 Close 错误也要记:os.File 在部分系统上(NFS、某些网络盘)
是到 Close 才报出「写不进去」,只盯每次 Write 的返回值会漏掉这种情况。
getDanmu 里拿到错误就明确告警,并把这条记录标记成「弹幕文件写坏了」。
3. 写坏的本场跳过自动合并
光打一条日志不够 —— 照样会合成一个缺弹幕的成片。liveInfo 增加
danmuWriteFailed,finishPart 据此跳过合并,录播和弹幕文件都留着,
用户检查磁盘后还能手动合并。
已验证:
- 新增 4 个用例:
TestDanmuWriteErrorSurfacedOnClose(写入失败后 failed() 为真、
close() 必须把错误交出来、错误里能看出是哪个文件)、
TestDanmuWriteKeepsOnlyFirstError(只记第一个)、
TestDanmuWriteNoFalseAlarm(正常录制不能误判,这是加检查最容易踩的反向坑)、
TestFinishPartSkipsMergeWhenDanmuBroken(先跑一个「条件都满足应当合并」
的对照组确认测试本身有效,再验证标了写坏之后不合并)。
- 反向验证:把 jsonl 的两处 Write 错误改回丢弃,
TestDanmuWriteErrorSurfacedOnClose 立刻报「failed() 应为 true」。
- 测试踩到的两个坑(都记进注释):替换 w.jsonl 前要先关掉原句柄否则
Windows 上临时目录删不掉;测试里没跑 initialize(),lInfoMap.info 是 nil,
直接赋值会 panic。
- gofmt / go vet / go build / GOOS=linux go build / go test / go test -race 全过,
全量测试连跑两次稳定。
|
2026-09-26 03:14:07 +08:00 |
|
tamakyi
|
7dd2487cb8
|
合并任务的可视化进度、预计剩余时间与暂停/停止
用户反馈:合并的进度只体现在日志里(ffmpeg 的 frame=/speed= 行),看不出
百分比、当前环节和还要多久,也没有办法中途暂停或放弃。
1. 新增全局任务状态(progress.go)
一份状态同时服务自动合并和手动合并,记录阶段、总进度、速度、帧率、
已处理时长、输入/预计输出/已写出体积、ETA 和速度折线采样点。
总进度按阶段加权:准备 0.02 / 分段拼接 0.08 / 获取头像 0.10 /
画面渲染 0.75 / 拼接与落盘 0.05,渲染阶段按「已渲染 ÷ 总时长」折算。
ETA 在渲染阶段用「剩余时长 ÷ 倍速」,其它阶段按已用时间线性外推。
2. 用 ffmpeg 的 -progress pipe:1 取结构化进度
runFFmpeg 拆出带回调的版本:解析 out_time_us / speed / fps / total_size,
分块渲染时把块内时间折算到整片时间轴,并把已完成块的字节数累计进来。
ffmpeg 自己的统计行仍在 stderr,单独用命令行时不会少东西。
预计成片体积先按「(视频码率 + 音频 192k)× 时长 ÷ 8」估,渲染过 5%
之后换成「已写出字节 ÷ 已完成比例」外推(estimateAudioKbps)。
3. 暂停 / 停止
Windows 用 ntdll 的 NtSuspendProcess / NtResumeProcess(照任务管理器
的「挂起」语义),Linux 用 SIGSTOP / SIGCONT,放在带 build tag 的
jobctl_windows.go / jobctl_unix.go 里。
非 ffmpeg 环节(下载头像、逐块渲染之间)用 jobGate 闸门生效。
停止 = 杀掉当前 ffmpeg 并置位,各层返回 errJobStopped:不再回退其它
编码器、不落成片、清掉分块临时文件,任务以「已停止」结束(而不是失败)。
4. 接口
GET /api/job 给页面轮询任务快照;POST /api/job/control 接受
pause / resume / stop,没有任务时 409、未知动作 400。
5. 界面
监控分区顶部新增「转换任务」卡片:阶段步骤条、总进度条、实时指标、
预计剩余时间、源片与预计成片的体积对比条、渲染速度折线(每秒一个点、
最多两分钟),以及暂停/继续/停止按钮;页面最顶上再加一条全局细进度条,
切到哪个分区都看得到。有任务时每秒轮询,空闲时 4 秒。任务结束后卡片
再停留 20 秒显示结果。
已验证:
- 新增 progress_test.go 12 个用例:阶段权重和为 1、进度按阶段折算、阶段
不回退、ETA、体积预估与实测外推的切换、折线采样、暂停/停止的闸门语义、
控制接口在无任务时报错、speed 字段与 -progress 文本的解析。
测试里把 errJobStopped 和真失败区分开(jobErrText)。
- 端到端(独立实例 51897 + 独立 config,全程没碰仓库里的实例和
acfunlive.exe):跑通完整合并,进度到 100%、预计体积与成片实际大小
相差 0.4%;暂停后进度冻结且 ffmpeg 的 CPU 时间 5 秒内 0 增长;
继续后进度恢复;停止后任务以「已停止」结束、没有半成品成片、
没有残留临时文件、没有残留 ffmpeg 进程。
- 界面(playwright 驱动系统 Edge):步骤条 5 个阶段且只有一个处于进行中、
进度条与顶部细条同步、5 项指标(速度/帧率/已处理时长/已用时间/已写出)、
ETA 文案、体积对比条两段宽度按比例、折线有采样点、界面按钮能暂停/继续/
停止、结束后按钮隐藏且文案是「已停止」、深色主题与 390 宽移动端无横向溢出。
- 过程中修掉三个真缺陷:停止时残留分块临时文件(part 登记晚于渲染)、
界面「继续」按钮点不动(onclick 写死了 pause)、停止被当成失败报红
(现在按 message 区分 已完成/已停止/失败)。
- gofmt / go vet / go build / GOOS=linux go build / go test 全过。
|
2026-09-26 02:05:47 +08:00 |
|
tamakyi
|
e35a4f7cef
|
合并先在本地做完再落到成片目录;头像支持 gif 和静态 webp
起因是核查合并落盘和头像处理时发现的两处问题:
1) 成片落盘:ffmpeg 原来是直接写目标目录(automerge.go 里那句注释「不再事后再
移动一次」)。成片目录可能是网络盘或外接盘,写到一半失败会在那里留下一个
看起来正常的半成品。现在改成先本地合成、完成后再落盘:
- 输出先写到弹幕文件所在目录(本地)的 acfunlive_chunk_merging_*
- 写完后由 placeOutput 落盘:同盘直接 os.Rename(瞬时),跨盘退回拷贝;
拷贝中途失败留下的是 *.acfunlive.part,一眼看得出不是成品
- 成功或失败都会清掉本地临时文件,失败时两个目录都不留东西
- 临时文件沿用 chunkTempPrefix,不再出现在文件管理的列表里(listFiles 过滤)
- 启动时清理上次残留;删不掉时提示一句 —— 强杀程序不会连带杀掉 ffmpeg
子进程,它可能还在写这个文件,这种情况下次启动会再清一次
2) 头像:downloadAvatar 只注册了 jpeg 和 png 解码器,AcFun 上的 gif 头像必然
解码失败,静态 webp 同样。加上 _ "image/gif"(多帧取第一帧)和
_ "golang.org/x/image/webp"(x/image 本来就是直接依赖,零新增模块)。
动画 webp 两条路都解不开(x/image 报 invalid format,ffmpeg 5.0 也不行),
保持只烧文字。解码失败时错误信息带上 Content-Type,方便排查是哪张图。
在隔离实例上用真 ffmpeg 跑了真合并验证:gif 头像和 webp 头像都进了缓存并烧进
画面(抽帧确认圆形头像);日志里 ffmpeg 的输出是 record 下的临时文件,完成后
才出现在 merged;坏视频合成失败后成片目录没有半成品、录播目录零残留;合并
中途强杀留下的临时文件在下次启动时被清掉。
新增 output_test.go(7 个)和 avatar_test.go(3 个);go test ./...、gofmt、
go vet 干净。
|
2026-09-25 17:34:31 +08:00 |
|
tamakyi
|
ed4e47e305
|
init
|
2026-09-25 13:16:54 +08:00 |
|