/dynamic-edit
一个从未移动的说话人头部视频就是死水一潭;但一个毫无理由移动的视频更糟。本管道决定哪种效果值得用在哪个单词上,将其写成一个人类可以否决的计划,然后才投入算力。
仓库根目录:
THP_HOME=${THP_HOME:-$(cat ~/.config/talking-head-pipeline/root)}
规划前请阅读:$THP_HOME/docs/STYLE_RULES.md(工艺规则)、$THP_HOME/docs/LAYOUTS.md(几何与字幕安全区)、references/plan-format.md(架构)、references/qa.md(关卡)。3D相关:$THP_HOME/docs/3D.md。
输入约定:素材已预剪辑。 无需无声去除、无填充词处理。原始素材需先通过 /autocut 处理;带回其 clean.mp4 和工作目录(用于逐词文本)。
切勿跳过计划步骤。 旋转描摹成本约为每分钟素材12分钟,完整渲染则需几十分钟。在文本计划中捕获的错误猜测则不费分毫。
工作流程
1. 搭建项目
P=<project directory>/<slug> # 询问人类视频项目存放目录;切勿使用桌面
cp -R "$THP_HOME/template" "$P"
cd "$P"
uv venv --python 3.11 .venv # mediapipe 在 3.12 之后没有 wheel
uv pip install --python .venv/bin/python "mediapipe==0.10.21" opencv-python backgroundremover numpy pillow
scripts/download_models.sh # MediaPipe 人脸检测器(未包含在仓库中)
(cd remotion && pnpm install) # 或 npm install
cp "<autocut workdir>/clean.mp4" remotion/public/raw.mp4
python3 "$THP_HOME/scripts/autocut/verbatim_cut_words.py" "<autocut workdir>" -o remotion/public/words.json
从16:9素材制作竖版Reels:用 scripts/prep_vertical.sh "<autocut workdir>" "$P" 替代最后两行(对人脸中位x值进行静态9:16裁剪,绝不使用跟随裁剪)。
2. 清查、剪辑、追踪
ffprobe -v error -select_streams v:0 -show_entries stream=width,height,r_frame_rate,nb_frames \
-show_entries format=duration -of default=nw=1 remotion/public/raw.mp4
.venv/bin/python scripts/detect_cuts.py remotion/public/raw.mp4 --out remotion/public/cuts.json
.venv/bin/python scripts/track_head.py remotion/public/raw.mp4 --out remotion/public/track.json --cuts remotion/public/cuts.json
- 缩放余量 = 源宽度 / 时间线宽度(2560 到 1920 = 1.33倍免费)。超出此范围则需要放大。
- 宽高比根据计划宽度/高度自动检测(
lib/layouts.ts配置文件)。单一引擎,绝不分叉。 - 读取 track.json 中的
detectionRate。若低于约0.9,基于人脸的移动不可靠,请说明情况。 - 剪辑检测会遗漏渐变变化(例如切换到屏幕分享的动画)。对每个片段采样一帧,手动将遗漏的边界添加到 cuts.json。屏幕分享片段不添加推近镜头、不绘制字幕、不添加卡片(设置
captions.silence覆盖这些范围)。
3. 校对单词
在 words.json 中搜索所有专有名词、品牌、股票代码和脚本中提及的人物。Whisper系列模型会误拼这些词,但句子仍然读起来流畅,所以人工审核常常漏过。修正单词文件(合并两个token为一个是可以的),绝不修正渲染结果。数字使用阿拉伯数字。
4. 编写计划,然后暂停
- 先通读完整转录稿。找到叙述中的关键节奏:钩子、主张、数字、转折、揭示、收尾、更适合展示而非叙述的时刻。
- 根据下方的“效果选择”为每个节奏选择一个工具。许多节奏可以不做任何事。
- 复制
scripts/build_plan.example.py到scripts/build_plan.py,使用单词锚点("phrase@seconds")编写那里的节奏。它会生成remotion/plan.json和字幕。它会拒绝那些没有原因、没有入口和出口单词锚点、或者推近镜头间隔太紧的事件。 - 向人类展示一个表格,每个事件一行:时间、效果、“落在X上,在Y之后离开,因为Z”。另外:旋转描摹秒数(及由此推断的遮罩时间)、任何超出缩放余量的重新构图、任何超过约12秒且无事件的拉伸、以及所需3D渲染的数量。
- 等待审批。 然后从磁盘重新读取
plan.json;人类可能已对其进行了编辑。
如果人类在特定时刻指定了特定效果,那就是那个节奏的计划。
5. 只构建计划所需的内容,不多不少
- 旋转窗口:
scripts/render_and_qa.sh为其生成遮罩;或每个窗口使用scripts/matte_frames.py。 - 3D英雄文字 / 3D资产:
tools/render3d(详见 docs/3D.md)。先生成静态帧(--stills),查看它们,然后--clip --dir=remotion/public/<name>。将镜头中相同的入/出帧设置为计划事件中的at/until。 - 侧边卡片: 种类位于
remotion/src/layouts/cards.tsx;如有需要可添加新种类。 - 屏幕场景页面:
remotion/src/screen/pages.tsx。对于任何要放大的内容,代码中重建UI;截图在2倍放大会变模糊。 - 全屏场景:
scenes/fullscreen/registry.ts(depth、clip、stage)。 - 音效: 计划确定后,使用
scripts/add_sfx.py "$P" sfx_map.json --library <dir>。
6. 用静态帧检查,然后渲染
静态帧成本低;一次渲染则要花掉整个下午:
cd remotion && npx remotion still src/index.ts Edit ../qa/landing.png --frame=<N>
检查每个英雄落地(及落地后25帧)、每个布局入口、每个过渡、每个3D到达。然后:
scripts/render_and_qa.sh "$P" <name> # 生成旋转窗口遮罩、渲染、编写QA表单
渲染参数:--concurrency 4-6,使用 nice 降低进程优先级,并保持 Config.setChromiumOpenGlRenderer('angle')(已在 remotion.config.ts 中),否则WebGL场景会在无头模式下失败。交付 yuv420p bt709;yuvj420p 文件在 QuickTime 中播放为空白。
7. QA(references/qa.md),然后同步修剪
在任何人查看之前,运行 references/qa.md 中的每个关卡。然后测量并修剪渲染结果相对于源素材的音频延迟(Remotion 渲染通常会有几十毫秒的延迟;需测量,不可假定):
"$THP_HOME/scripts/master/sync_trim.sh" renders/<name>.mp4 remotion/public/raw.mp4 renders/<name>_final.mp4 [--loudnorm -14]
目标:最终同步误差低于5毫秒。仅当人类要求时进行响度标准化(社交平台默认 -14 LUFS)。为人类打开最终文件。
8. 交付后修复:修补范围,绝不重新渲染整个视频
scripts/patch_render.sh --base renders/prev.mp4 --replace 0-1049 --render 0-887 --out renders/next.mp4
--replace 是要丢弃的旧渲染范围,--render 是当前计划中替换它的范围(当源文件被重新剪辑时,两者不同)。只编码补丁部分;头部和尾部在关键帧上流复制;帧计数会被验证。当计划存在后需要从源文件中移除某个卡顿点时,从锚点(build_plan.py)重新构建计划,使每个效果随其关联单词移动。
9. 清理,仅在人类表示视频已完成时进行
先询问。scripts/cleanup.sh 会删除遮罩PNG、帧转储、QA表单、草稿、venv和node_modules;它保留计划、源文件、追踪数据、字幕和最终渲染。重新生成遮罩每英尺素材耗时约12分钟,所以绝不要未经提示运行。
选择效果(工具箱,非播放顺序)
没有固定公式。没有任何英雄文字、旋转描摹或3D的视频也是有效计划。纯粹的直面镜头作品可能只需要推近镜头和字幕。每种效果都由脚本中的某些内容赢得,并且每个事件都需命名原因(其叙述工作)、入点(效果落定的单词;移动在此前开始,以便效果到达该单词)和出点(效果离开后的单词:该想法的结束、下一个真正的停顿、软化词前的单词)。绝无固定保持,绝无“安静了一段时间”。
- 人脸优先。 前2-3秒是说话人的脸部,第0帧已经是可读的钩子图像(非黑场淡入)。约2.5秒前不放全屏图形:自动播放是静音的,以统计数据开场看起来像信息图,会被划走。
- 推近镜头(主力):持续一句左右,少量,有目的。它在关键句子构建时落地,持续到句子结束,在最后一个单词上释放。绝无单词级推近;绝无定时器间隔。长片中大约每30-45秒说话时间一个;短片中仅当脚本值得时才更紧凑。锚定在追踪到的人脸上。两个相关短语:对承载故事的那一半进行推近。嵌套强调(持续+关键短语上的小强调)代替一个句子中的三次推近。
- 侧边布局(16:9):当数字、列表或对比需要在解释时保持在屏幕上时使用。主体滑到一侧,内容大小的卡片在另一侧,卡片在其关联单词上切换,并在说出数字时强调。大多数长片中的信息节奏需要这个。
- 屏幕场景(16:9):用于“看这里如何操作”的演练。每个视频使用一次或两次,不是每次提到都用。如果再现产品,则与实际UI一一对应;只有数据更改,且数据是真实的。
- 带卡片的偏移重构图: 侧边布局的轻量版,在缩放余量内。
- 旋转描摹(主体背后): 仅用于开场和英雄时刻。默认预算每个视频15秒;超过30秒需警告;超过60秒需询问。任何从背后穿过的物体都必须值得遮挡。
- 主体后的英雄单词: 用于钩子、硬转折、命名概念。放在头顶上方,每个字形可读(在落地+25帧处检查静态帧)。每视频一个,除非两个单词相距很远。
- 3D英雄文字: 一个大的主张或数字,放在头部旁边或胸前,与每个画面边缘等距(1080宽时至少约90像素),绝不可覆盖面部。
- 3D资产: 说话者提到的标志或物体,在面部旁边的空白空间中旋转,如有重叠则使用旋转描摹在背后。
- 全屏场景: 仅当展示清晰优于叙述时使用,最多两个,使用淡出+模糊进出。
- 字幕: 固定底部三分之一,所有地方(说话人、侧边、屏幕)保持相同位置和样式。强调=强调字体 AND 1.22倍大小用于整个短语,绝不跨两个卡片分割。默认关闭头部追踪字幕,除非特别要求。
- 音效: 每个可见事件(过渡、英雄落地、硬推近、卡片、点击)一个,音量微妙。
硬性规则
- 面部上不可放任何元素。 在任何帧的窗口内,无字幕、卡片、芯片、标志或3D物体覆盖面部框。将其移动、缩小或通过旋转描摹放在背后。从 track.json 测量。
- 说话人上方无带边框的文字标签。 任何卡片上不单边强调线(上、左或其他边缘):使用填充色调或完整均匀边框。
- 仅全帧淡入淡出。 标题保留时绝不可淡出容器。
- 过渡: 淡出+模糊(默认)、普通淡出或液体转场。绝不用锯齿/齿状快门。
- 文字不被缩放或画面边缘切割。 放大的UI在字幕条上方淡出(
World边缘淡出)。字幕绝不被缩放。 - 重新构图与为其腾出空间的渲染一同交付。 没有朝向空墙的相机移动。
- 验证屏幕上的每个数字 与所说内容或已保存、有来源的抓取一致。绝不可编造。
- 物体和图标 是真实标志或生成/来源的图像。代码绘制运动、文字、UI、图表。箭头是资产,非SVG。
- 屏幕上无长破折号。除非人类要求,否则无“评论下方”/“私信我”行动号召。
- 堆叠在视频上的每一层都是其自己的
AbsoluteFill(静态视频绘制在任何绝对定位的兄弟元素之下,这会静默地将图形放在遮罩前面)。 - 对于重新构图,使用
translate + scale,绝不移动transform-origin。
效果参数和测量数字:references/effects.md。