.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), 头像跟着文字同步走,没有回归。
This commit is contained in:
+21
-3
@@ -278,7 +278,7 @@ func prepareDanmuSource(path string, write bool) (danmuSource, error) {
|
||||
}
|
||||
}
|
||||
|
||||
meta, records, err := parseAssFile(path)
|
||||
meta, records, bad, err := parseAssFile(path)
|
||||
if err != nil {
|
||||
return danmuSource{}, err
|
||||
}
|
||||
@@ -289,6 +289,12 @@ func prepareDanmuSource(path string, write bool) (danmuSource, error) {
|
||||
return src, nil
|
||||
}
|
||||
|
||||
// 还原日志只在真正落盘这次打:页面的「检查」也会解析一遍同一个 .ass,
|
||||
// 两处都打日志的话同一次还原会在日志里出现两遍
|
||||
summary := fmt.Sprintf("已从 %s 还原出 %d 条弹幕(%dx%d,字号 %d,弹幕到 %s)",
|
||||
filepath.Base(path), len(records), meta.PlayResX, meta.PlayResY, meta.FontSize,
|
||||
formatSeconds(records[len(records)-1].Time))
|
||||
|
||||
if err := writeJsonlFile(jsonlPath, meta, records); err != nil {
|
||||
// 目录只读等情况退到临时目录,不影响这次合并
|
||||
tmp := filepath.Join(os.TempDir(), chunkTempPrefix+"converted.jsonl")
|
||||
@@ -298,12 +304,22 @@ func prepareDanmuSource(path string, write bool) (danmuSource, error) {
|
||||
lPrintWarnf("无法把还原结果写入 %s(%v),改用临时文件 %s", jsonlPath, err, tmp)
|
||||
src.jsonlPath = tmp
|
||||
src.tempJsonl = true
|
||||
lPrintf("%s,写成 %s", summary, filepath.Base(tmp))
|
||||
logAssBadLines(path, bad)
|
||||
return src, nil
|
||||
}
|
||||
lPrintf("已把 %s 还原成 %s", filepath.Base(path), filepath.Base(jsonlPath))
|
||||
lPrintf("%s,写成 %s", summary, filepath.Base(jsonlPath))
|
||||
logAssBadLines(path, bad)
|
||||
return src, nil
|
||||
}
|
||||
|
||||
// .ass 里有行没能解析出来时提醒一次(只在真正落盘那次调,避免重复)
|
||||
func logAssBadLines(path string, bad int) {
|
||||
if bad > 0 {
|
||||
lPrintWarnf("%s 里有 %d 行弹幕没能解析出来,已跳过", filepath.Base(path), bad)
|
||||
}
|
||||
}
|
||||
|
||||
// .ass 还原结果退到系统临时目录时,用完就删。只删标记过的临时文件:
|
||||
// 正常情况 jsonlPath 是用户录播目录里的同名 jsonl,删了就是丢数据
|
||||
func dropTempDanmu(src danmuSource) {
|
||||
@@ -460,7 +476,9 @@ func buildMergePlan(req mergeRequest) (mergePlan, error) {
|
||||
if info.Duration > 0 && src.last+info.Offset >= info.Duration {
|
||||
info.Warnings = append(info.Warnings, fmt.Sprintf(
|
||||
"最后一条弹幕在 %.1f 秒,加上偏移后已超出视频总长 %.1f 秒,超出的弹幕会被丢掉;"+
|
||||
"如果这个视频只是整场的一段,请把同场的其它分段一起加进来",
|
||||
"如果这个视频只是整场的一段,请把同场的其它分段一起加进来;"+
|
||||
"如果弹幕整体都落在时长之外,通常是这个弹幕文件的时间基准和视频不一致"+
|
||||
"(合成时会按第一条弹幕自动对齐,日志里有平移量),也可以自己填「偏移」",
|
||||
src.last+info.Offset, info.Duration))
|
||||
}
|
||||
if info.Duration == 0 {
|
||||
|
||||
Reference in New Issue
Block a user