本文档旨在梳理我对 "Atomic Block"(原子视频块)生成流程的理解。请校验以下逻辑是否符合你的预期。
一个标准的 Block 对应一个用户选中的视频片段(Clip)。
Block 的总时长 = Head + Body + Tail。
| <--- Head ---> | <--- Body ---> | <--- Tail ---> |
| (Transition) | (Main Content) | (Fade Out) |
Head 的总时长 = 转场音频时长 (transitionDuration) + 淡入时长 (fadeInDuration)。
Head 内部又分为两段:
-
纯背景段 (Pure Background):
- 时长: 等于
transitionDuration。 - 画面: 仅显示背景视频(根据全局时间轴裁切)。正片画面不可见。
- 音频: 播放转场音效(如 "Whoosh")。
- 逻辑: 如果没有转场音频,此段时长为 0。
- 时长: 等于
-
淡入段 (Fade In):
- 时长: 等于
fadeInDuration。 - 画面: 正片从第 0 秒开始播放,透明度从 0 渐变到 1。底层显示背景视频。
- 音频: 正片音频从静音渐变到 100%。
- 消耗: 此段消耗正片的前
fadeInDuration秒内容。
- 时长: 等于
- 时长:
Clip原始时长-fadeInDuration-fadeOutDuration。 - 画面: 正片完全不透明(Alpha=1)。
- 音频: 正片原声。
- 消耗: 消耗正片中间段内容。
- 时长: 等于
fadeOutDuration。 - 画面: 正片透明度从 1 渐变到 0。底层显示背景视频(此背景是为下一个 Block 做铺垫的)。
- 音频: 正片音频从 100% 渐变到静音。
- 消耗: 消耗正片的最后
fadeOutDuration秒内容。
以下是我对现有代码逻辑的理解,请指出是否与你的需求有出入。
我的理解: 不会。 转场音频(如 2秒)会额外增加 Block 的总时长。 例如:正片 10秒,淡入 1秒,转场音频 2秒。
- Block 总长 = 2s (纯背景) + 10s (正片本身) = 12s。
- 前 2s 是纯背景 + 转场音。
- 第 3s 是正片开始淡入。
回答:你的理解正确
我的理解: 背景视频是连续的。
系统计算了全局的累积时间 (accumulatedTime)。
- Block N 的 Head 背景,取自背景视频的
accumulatedTime时刻。 - Block N 的 Tail 背景,取自背景视频的
accumulatedTime + BlockDuration时刻。 - 这样当 Block N 和 Block N+1 拼接时,背景画面是平滑连续的。
回答:你的理解正确
我的理解:这是边缘情况。
- 目前逻辑可能未做特殊保护,可能导致 Body 长度为负,进而报错或产生奇怪的视效。
- 预期行为: 应该自动缩短淡入/淡出时长,或者强制正片最短时长限制。
回答:淡入淡出退化为0秒,并且给出警告日志(我希望对日志信息做警告和报错分级的日志收集单独显示)
我的理解:
之前的实现使用了 concat -c copy(流复制),直接拼接了多个 Block 的原始数据包。
由于每个 Block 都是独立生成的,其内部的时间戳(PTS/DTS)在拼接处可能不连续,或者存在微小的重叠/空隙(由于帧率转换精度问题)。
现在的修复:
- 强制补帧 (
tpad): 确保视频流不会因为精度问题短缺。 - 完全重编码 (
libx264): 在拼接阶段重新生成所有帧和时间戳,强制保证连续性 (CFR)。
回答:所有的编码都应该在block内部完成,以避免重复编码造成性能浪费,后续的拼接只涉及
我的理解: 如果用户感觉到画面被跳过,除了播放器行为外,还可能是预处理时长判定错误。 如果预处理认为视频有 10秒,但生成的文件实际上只有 8秒。
- Map 阶段下达命令:"截取第 8-10 秒作为 Tail"。
- 但文件只有 8秒。FFmpeg 读不到数据,Tail 变成全黑或全背景(因 tpad 补帧可能是以最后一帧——即第8秒的帧——重复)。
- 修正: 我已添加了
ffprobe检测实际时长的逻辑,以消除这种"认知偏差"。
-
淡入/淡出曲线: 目前使用的是线性 (
alpha=1->alpha=0)。是否需要对数或其他曲线?- (当前假设: 线性即可) 回答:暂定线性
-
背景模糊处理: 目前 Body 部分如果画面比例不一致,使用了模糊背景填充 (
boxblur)。Tail 部分的背景使用的是即将在下一个Clip显示的全局背景,还是当前Clip的模糊背景?- 纠正: 之前的代码逻辑中,Tail 部分的底层背景使用的是 Global Background (为了衔接下一个)。而 Body 部分的背景是 Blurred Clip 本身。
- 视觉突变: 这意味着从 Body 到 Tail,背景会从 "模糊的当前视频" 突变/淡入到 "全局背景视频"。这是预期的吗?
- (当前代码逻辑: Tail 叠加在 Global Background 上)。 回答:因为是渐变的,所以不影响,而且只有填充部分,影响不大
请针对以上内容进行批注或确认。一旦对齐,我将编写独立的测试脚本来验证每一个 Block 是否完美符合上述定义。
为了统一共识并确保修复有效,我们将按照以下计划进行验证。不急于编码,先确保测试覆盖了所有关键场景。
验证 Map (Block生成) 和 Reduce (拼接) 两个阶段在不同输入源(B站/本地)和不同配置下的稳定性及正确性。
我们将创建一个测试脚本 scripts/verify_pipeline.js,模拟从 LocalStorage 读取的卡片数据。
需覆盖的典型卡片类型:
- B站源卡片 (Bilibili Source)
- 特点: 已下载并预处理(Preprocessed),通常在
temp目录中。 - 测试点: 预处理文件的路径处理、预处理元数据读取、不需要再次裁剪。
- 特点: 已下载并预处理(Preprocessed),通常在
- 本地源卡片 (Local Source)
- 特点: 原始文件路径,可能非标准帧率/分辨率。
- 测试点: 自动标准化 (Encoding)、裁剪 (Clipping)、帧率转换。 补充:本地源预处理后也会将标准化后的文件放在temp中,所以和b站源一样
| ID | 场景名称 | 输入源 | 淡入/淡出 | 转场 | 预期行为 |
|---|---|---|---|---|---|
| CASE_01 | 标准全功能 | B站/本地 | 2s / 2s | 有转场音效 | 生成完整 Head(含背景+淡入)+Body+Tail。Check: 时长 = Clip+转场。 |
| CASE_02 | 无转场纯淡入 | B站/本地 | 1s / 1s | 无 | Head 仅包含淡入部分。Head 背景时长 = 0。Check: 无空黑段。 |
| CASE_03 | 超短视频回退 | 任意 | 2s / 2s | 任意 | Clip时长 < 4s。触发回退逻辑。输出 SimpleClip(无淡入淡出)。Check: 有警告日志。 |
| CASE_04 | 异构源视频 | 本地 | 1s / 1s | 无 | 源视频为 60fps/24fps 或 VFR。Check: 输出 Block 严格为 30fps CFR。 |
补充:淡入和淡出以及转场音频都是用户可选开启的,情况要考虑全面
测试脚本将对生成的每一个 Block 和最终 Output 执行 ffprobe 校验:
-
Block 级校验:
vcodec:h264(libx264/nvenc)acodec:aacsample_rate:48000fps:30(必须恒定)- 关键:
start_time≈ 0.000000
-
Pipeline 级校验:
- 执行
ffmpeg -f concat -c copy ...。 - 必须无错误日志 (如
Invalid DTS,Non-monotonous DTS)。 - 最终文件时长 = Sum(Block Durations)。
- 执行
- 确认上述测试用例是否覆盖了您的疑虑。
- 编写
scripts/verify_pipeline.js。 - 执行测试并生成报告。
补充:block级别的校验还要确定是否完成了时间统一编码