export-final
组装最终文件。参数:$project slug(项目标识符),$platform 覆盖 output.platform(如果用户需要第二个交付物)。完整请求在 $ARGUMENTS 中。空的 $ 占位符表示该技能是自动触发而非斜杠调用:此时从会话上下文中的 PIPELINE_STATE 获取项目,并说明选择了哪个。动词名称遵循构建约定;python engine/ve.py --help 具有最高权威。
渲染架构(已确定)
- 视频:每个片段一个缓存的 MP4/MKV,编码参数相同,每个片段从 IDR 帧开始,然后使用
-f concat -safe 0 -c copy拼接。经验证无损且近乎即时。 - 音频:对整个时间线进行一次连续通读,编码为
pcm_s24le。永不按片段缓存。 - 单次混流,AAC 编码只进行一次。
不要在导出时重新设计此架构。尤其不要试图将其“简化”为跨所有片段的单个 filter_complex:一个 150 片段的图大约有 19k 个字符,而约 260 个片段就会导致 Windows 命令行溢出,产生一个无法启动的进程而非有用的错误信息。它还会丢弃逐片段缓存,而缓存正是第二次导出只需几秒钟的原因。
滤波器图始终写入文件,并通过 -/filter_complex fc.txt 传递。无条件执行,不仅仅是在图很长的时候。
第 1 步 - 预检
在任何编码之前:
- 运行 QC 检查。在未通过 QC 的项目上执行导出会浪费流水线中最长的操作。
- 确认
output.platform。它决定了响度目标、分辨率和字幕安全区域。如果$platform与output.platform不同,不要静默修改项目——而是渲染第二个输出路径,并说明哪个是哪个。 - 报告计划:片段数、预期总帧数、预期时长、编码器,以及本次通过是否烧录字幕和图形。
第 2 步 - 渲染片段
python engine/ve.py render $project
按片段缓存在 .cache/seg/ 下,键值基于源强键、入/出帧、滤镜、编码器和工具版本。仅重新渲染发生变化的片段。--only seg_0012 只渲染一个片段用于检查。
编码器默认值:h264_nvenc,cq 20,preset p6。在这张 Ada 显卡上,NVENC 与 libx264 在粗略的 VMAF 上相当——在 2 Mbps 时落后 0.19 VMAF,在 8 Mbps 时领先 0.06——同时速度是其 2-4 倍。对于预览来说是合理的默认值,对于最终成品也是可辩护的选择。仅当用户要求每比特最高质量且愿意等待时才切换到 libx264。
在此机器上,两个编码器每次运行时都是字节可重现的,这正是内容寻址片段缓存安全可靠的原因。
第 3 步 - 拼接,但先保护它
concat_compatible() 在每次 -c copy 拼接之前无条件运行。 参数不匹配时,拼接 demuxer 会以 exit 0 返回一个损坏的文件——无警告,无错误,一个播放十秒钟然后崩溃的文件。QC 检查 (c) 正是此检查。
它比较每个片段的:分辨率、像素格式、编码器、profile、level、时间基准、采样率和声道布局。不匹配意味着某个片段使用了不同的参数渲染——通常是在编码器设置更改后留下的陈旧缓存条目。重新渲染有问题的片段,而不是强制拼接。
第 4 步 - 音频和响度
python engine/ve.py render $project
然后对平台目标进行两遍 loudnorm,目标来自 output.loudnorm(YouTube:I -14,TP -1.5,LRA 11;其他平台不同,位于 presets/formats/ 中)。
第 2 遍必须携带显式的 -ar 48000。 缺少它将导致输出静默变为 192 kHz——loudnorm 的内部采样率泄露到输出中,文件比预期大四倍,并且一些播放器会错误地重采样它。这是整个链中最常被遗漏的标志。
使用两遍,而非单遍:单遍 loudnorm 是一个动态估计,在静默开场(如每个带背景音的头像视频)中会偏离目标一个分贝或更多。
第 5 步 - 单次混流
第 2 至 5 步全部通过 ve render $project 完成:它渲染或重用缓存的片段,拼接它们,一次性渲染音频,进行 loudnorm 标准化,并仅将 AAC 320k 混流一次到 projects/$project/out/final.mp4(按 output.path)。
不要在混流时重新编码视频。如果画面需要更改,则在片段级别更改,缓存会处理其余部分。
混流之后,ve render 合成渲染的叠加层,然后如果 captions.burn_in 则烧录 ASS 字幕——按此顺序,以便字幕位于图形之上。两者都会重新编码母版,这就是为什么 ve gfx 和 ve captions 应在 ve render 之前 运行,而非之后。之后运行会导致母版落后一个版本,而且没有警告。
ve export 是一个独立的后续步骤:它运行完整的 QC,除非传递 --force,否则在失败时拒绝,然后加盖日期戳的交付副本并同时写入 .manifest.json。
第 6 步 - 验证交付物,然后移交
对实际输出文件运行强制性检查,而非仅依据计划:
- 帧数等于启用片段的
(out_frame - in_frame)之和。QC (a)。 - 音频采样数等于
frames / fps * 48000。QC (b)。 ebur128集成响度在平台目标的 0.5 LU 范围内,真峰值低于上限。QC (f)。- 容器、分辨率、帧率和时长与
output匹配。 - 抽查三帧:第一帧、中间某片段边界处的帧、最后一帧。边界处的黑帧意味着片段渲染不足。
然后报告:输出路径、文件大小、时长、测量的 LUFS 和真峰值、帧数、片段数以及来自缓存的片段数。将 stage 设置为 "export"。
第二交付物
竖版裁剪不是对已完成文件的重新裁剪。它是一个不同的 output.platform,这意味着不同的响度目标、不同的字幕预设和不同的安全区域,并且通常需要不同的故事处理。说明这一点,而不是缩放 16:9 母版。
失败模式
| 症状 | 原因 | 修复 |
|---|---|---|
| 输出播放后中断,ffmpeg 退出码 0 | 拼接中参数不匹配 | 每次拼接前运行 concat_compatible();重新渲染有问题的片段 |
| 母版为 192 kHz | loudnorm 第 2 遍缺少 -ar 48000 | 添加并重新运行第 2 遍 |
| 编码进程无法启动 | 滤波器图在命令行上 | 始终写入 fc.txt 并传递 -/filter_complex |
| 响度偏离目标一个分贝 | 单遍 loudnorm | 使用两遍 |
| 导出每次变慢 | 编码器设置更改导致缓存失效 | 预期行为;确认设置更改是意图的 |
| 剪辑处出现黑帧 | 片段渲染不足 | 重新渲染该片段;检查 QC (a) |
| 最终成品中人声比预览中更安静 | amix 缺少 normalize=0 | 在音频图中修复,重新渲染音频 |