tamakyi
|
a409c5b4dd
|
新增「转码」分区与手动合并逐任务配置,修掉 PC 端图表文字被拉扁
一、PC 端图表文字被拉伸(bug)
.spark 折线图把 viewBox 写死成 "0 0 300 48" 又带 preserveAspectRatio="none":
移动端元素宽≈300px 时缩放≈1 看不出问题,PC 上卡片宽约 1030px,横轴被放大 3.4 倍
而纵轴仍是 48px,图里的 <text> 被整体拉扁。现在按元素实际宽度算 viewBox 宽度
(取不到时回落 300),并监听 resize 重画;preserveAspectRatio 保留不影响 1:1 显示。
二、新增「转码」分区(transcode.go,第七个 tab)
原来只有「录播 + 弹幕 → 成片」一条路,已有成片想压小、换 HEVC、降分辨率只能手搓命令行。
现在从文件列表多选加入,每个文件一行独立配置:编码器、码率控制(跟随源片 / 目标码率 /
质量 CRF·ICQ / 源片百分比)、编码速度、输出帧率、分辨率(只列不高于源片的档)、
音频(拷贝 / 128k / 192k / 去掉),并实时预估产物大小与整批合计。
- POST /api/transcode/probe | estimate | transcode,都在既有的口令鉴权之后
- 目标码率模式按「码率 × 时长」出准数;质量模式用单调系数表插值并标注是粗估
- 落盘沿用合并那套:先在源文件所在目录生成,完成后再 placeOutput 到输出目录,
失败或中断删临时文件,不留半成品
- 与合并共用同一张进度卡片和任务位(mergeSlot),串行排队,可暂停 / 停止
三、手动合并改成「一次排多个任务」
原来一次只能合一场。现在多选视频后按「文件名里的录制时间 + 主体名」自动归组
(相邻两段间隔 2 小时内算同一场),每组一个任务卡,各自配弹幕、偏移、输出名和
合成参数(码率 / 帧率 / 字号 / 编码器),可拆组、可移除;服务端串行跑,逐个回传结果。
请求体从 {videos,danmu,...} 扩成 {jobs:[{...}]},老的单任务写法继续可用。
四、审阅中一并修掉 / 加固的点
- 容器总码率含音频,直接当视频码率用会错两处:预估体积时又加一遍音轨(算成双份),
「跟随源片」时把音轨码率也编进视频里。现在分开记「视频流码率」与「容器总码率」,
转码侧按「视频流码率 → 容器码率 − 音轨 → 体积 ÷ 时长 − 音轨」三级取值;
合并的自动码率仍按老口径(容器含音频),单独用 streamOrContainerKbps 锁住
- 源片本来没有音轨时,预估不再凭空加 128 kbps 音频
- 目标码率加上界(200 Mbps),单位写错的数在开跑前就被挡住(合并的逐任务码率同理)
- 探测结果加缓存(ffmpeg + 路径 + 体积 + 修改时间),体积估算跟着输入框重发时不再
每次都起一个 ffmpeg;文件一变(录制中追加、换了文件)缓存自然失效
- 只有音轨 / 只有封面图的文件、以及正在写入的文件,排队时就被挡住并说清原因
- probeMedia / parseMediaInfo 合并了原先重复的两套解析(probeDuration 与
probeVideoStream 各起一次 ffmpeg 解析同一段输出),burnDanmu 现在一次探测拿全
- 前端:估算参数非法时把服务端给的原因显示在合计行(原来静默不动,像是没改成功);
估算请求加序号,慢的旧回包不再覆盖新结果;转码下拉刷新时保留已勾选项;
跑完的行标「完成 / 失败」并淡化;删掉没有对应元素、从未使用的 CSS 规则
验证
- gofmt / go vet 干净;go build(windows)与 GOOS=linux go build 均通过
- go test ./... 20.9s 全过;go test -race ./... 26.1s 全过(182 → 206 个用例,
transcode_test.go 新增 20 个)
- 反向验证 C:\OtherFiles\acf-test\reverse_check.py 从 37 项加到 43 项,全过
- 51899 隔离实例 + 真 ffmpeg 冒烟 29 项全过:401 鉴权、probe 的 hasVideo/hasAudio、
ts 容器(不带每流码率)探测到的视频码率 = 容器 429 − 音轨 127、
HEVC 按 0.65 折算、参数非法 400 带原因、只有音轨的文件排队即被拒、
输出目录不存在被拒、两任务队列真转成功并落到指定目录、缩放成片高度确实是 240、
日志里带「第 n/N 个」和参数摘要
- 页面 jsdom 冒烟 37 项全过、无 JS 报错(分组 / 拆组 / 逐任务参数 / 转码行渲染 /
跳过无视频轨文件 / 估算失败提示 / 结果徽标)
|
2026-09-27 10:36:29 +08:00 |
|
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
|
c26eb4c648
|
合并任务排队串行,不再互相顶掉进度和杀错 ffmpeg
审查发现:全局只有一份任务进度状态,但没有任何东西拦住两个合并同时跑。
1. 为什么必须串行
jobStart() 原来是无条件重置全部字段的。多个主播同时下播(很常见),
或者自动合并跑着的时候在页面上点一次手动合并,就会有两个任务同时跑:
后启动的把前一个的输出名、进度、已渲染时长全部清零;更糟的是 jobStop()
只认最后注册的那个 ffmpeg,点「停止」杀的是别人家任务的进程,
而自己那个继续跑;两个任务还会互相读到 stopped 标记而提前退出。
界面上看只是一个进度条乱跳,背后是合并结果可能张冠李戴。
2. 怎么串起来的
加一把 mergeSlot 锁占任务位:
- 手动合并用 TryLock,抢不到直接 409 并告诉用户现在谁在跑 —— 它是一次
HTTP 请求,不能让人干等几十分钟。
- 自动合并用 Lock 排队等待,它本来就是后台任务,丢掉白录一场。
jobStart 改成返回 bool:已经有任务在跑时返回 false 且一个字段都不动。
这样即使以后有调用方忘了先拿任务位,也只是失败而不会互相覆盖。
3. 顺带修「panic 之后卡片一直转」
原来 jobFinish() 写在 autoMerge / mergeHandler 的正常返回路径上,任务内部
一旦 panic(外部 safeGo 的 recover 兜在外层)它就永远不会执行,任务会一直
停在 active,界面上的进度条再也不动,ffmpeg 也失联了。
现在统一走 runMergeExclusive():任务位用 defer 释放,panic 翻译成 error,
保证 jobFinish 一定能跑到。
4. 之前 ban 掉的 jobActive() 接回来了
它本来就该是这道护栏,写了却没人调用,现在用于判断要不要提示「在排队」
以及拒绝新任务时报出正在忙的任务名。jobState 的锁改成 RWMutex 供它读。
已验证:
- 新增 4 个用例:TestJobStartRejectsWhenBusy(被拒时 Must 一个字段都不改,
包括进度必须还是原任务的)、TestMergeSlotTryLockExclusive、
TestMergeSlotSerializes(4 个并发任务必须全跑完且不重不漏)、
TestMergeExclusiveReleasesOnPanic(panic 后必须拿到任务位 + 翻出错误)。
- 反向验证:把 jobStart 的占用判断临时改成 `if false && job.active`,
TestJobStartRejectsWhenBusy 立刻报「应当返回 false」。
- 现有测试跟着走真实链路(progress_test 加了 startJobForTest 统一先收尾、
manualmerge_test 走 execManualMerge 独占执行)—— 改之前这些用例单独跑过、
连着跑会失败,正是因为前一个用例把任务状态留在了 active。
- gofmt / go vet / go build / GOOS=linux go build / go test / go test -race 全过。
|
2026-09-26 02:41:29 +08:00 |
|
tamakyi
|
a891094f29
|
合并默认改用 HEVC、码率按「源片 + 500」自动算;手动合并自动匹配同名弹幕
两件事:
1) 合成码率与编码器
- `mergeBitrate` 填 0(默认)时不再是 ICQ 质量模式,改成「源片视频码率 + 500 Kbps」:
烧进去的文字和头像都是高频细节,和源片同码率会糊,所以留一点余量;探测不到源片
码率时才退回质量模式(QSV 用 ICQ 22、软编用 CRF 18),下限 800 Kbps。
- 编码器默认换成硬件 HEVC:候选顺序 hevc_qsv → h264_qsv → libx264,QSV 可用性按
编码器分别探测(原来只探 h264_qsv)。HEVC 在 mp4 里打 `hvc1` tag,Apple 系
(QuickTime / Safari / iPhone)才认 —— ffmpeg 默认写的是 hev1。
- 日志现在会打印编码器优先顺序和实际用的码率。
实测(90 秒真实素材,源片 3217 kbps,密集弹幕):日志「按源片 3217 kbps + 弹幕
余量 500 kbps 取 3717 kbps」,成片 codec=hevc、tag=hvc1、实际 3.71 Mbps。
之前同样是这份素材:h264 + ICQ22 会飙到 7386 kbps(79 MB),改成自动码率后
43.7 MB,体积小了 45% 画质还更稳。
2) 手动合并自动匹配弹幕
- 新增 `GET /api/merge/danmu?video=…`:按视频名找同目录下的同名弹幕,依次试
`.jsonl`、`.ass`、`.nick.ass`;同目录没有就拿文件名去录播目录再找一次。
- 页面在「手填/粘贴视频后失焦」「多选弹窗选完」「从文件列表多选后」「点预览或
开始合并时弹幕框还空着」这几种情况下都会自动匹配并提示;弹幕框里已经有内容时
绝不动它,找不到也保持为空让用户自己选。
- 填回框里的写法与浏览弹窗一致:录播/成片目录下的只写文件名,别处的写绝对路径。
测试:新增 encode_test.go(编码器顺序、参数与 hvc1、自动码率、ffmpeg 输出解析)
和 TestFindDanmuForVideo(7 个子用例:优先级、录播目录回退、绝对路径、找不到返回空);
仓库外的 verify_danmu_match.py 4 组断言全过,verify_pick.py / verify_settings.py 重跑
无回归;go test ./...、gofmt、go vet 干净。
|
2026-09-25 19:23:44 +08:00 |
|
tamakyi
|
ed4e47e305
|
init
|
2026-09-25 13:16:54 +08:00 |
|