video-framing 是做什么的?
在 video-tighten 之后运行(它会隐藏tighten产生的跳切),在 video-color 之前运行。需要 resolve-bridge 技能安装在其旁边(它会在 ../resolve-bridge 查找,或设置 RVS_BRIDGE 环境变量),以及 ffmpeg、ffprobe 和 swift(macOS自带;Apple Vision用于测量面部和手部)。转录需要 uv(本地Whisper)或 RVS_TRANSCRIBE_CMD 中的转录命令;如果转录失败,prepare 会继续但发出警告,转录内容将不完整。
以 python3 <此技能文件夹>/scripts/framing.py <命令> --workdir <文件夹> 运行脚本。命令包括:prepare、solve、apply、pushes。
脚本负责测量和计算数学。由你来做出判断(什么是杂物、哪些机位是平淡的、哪些台词需要推入)。**切勿停下来请求批准。**用户稍后审核。如果你不确定某个判断,仍然给出最佳答案并继续构建;将其添加到 decisions.json 的 issues 列表中(参见如何决定),apply 会将其转换为检查清单问题。reason 字段仅用于报告。
工作流程
-
我们身在何处。 运行
python3 <resolve-bridge>/scripts/rvs.py --workdir <文件夹> status。如果尚未跟踪任何内容,请先采用时间线(参见resolve-bridge)。 -
准备。 不更改Resolve中的任何内容:
python3 scripts/framing.py prepare --workdir <文件夹>如果Resolve已打开且被跟踪的时间线包含尚未记录的手动编辑,
prepare首先会通过resolve-bridge将其记录为新版本(manual,“帧化前的手动编辑”),并告知用户。如果Resolve已打开且其缩放模式会导致apply拒绝操作(参见它对时间线的影响),prepare会在开始时打印警告,以便用户在进行长时间运行前更改。查找镜头内元素,将其分组为机位,测量面部、眼睛、身体和手部(大约每秒4个样本),并写入:
generated/framing/setups/<id>.jpg:每个机位一张静止图像,带有标记的10%网格(两个轴均为0.0到1.0);.edit/framing/transcript.md:MM:SS.ss-MM:SS.ss <机位> <文本>,每行对应一个在镜头内听到的句子,按时间线时间排列。每个镜头内片段只包含其自身源范围内的单词(来自Whisper的单词时间),因此不同片段之间不会重复,时间为第一个和最后一个单词的时间。直接从此类行中选择推入范围;.edit/framing/decisions.json:一个模板,每个机位为空(重新运行会保留其元素未改变的机位的决策),context.json包含预算和静止图像。
第一次
prepare很慢: 测量大约需要每秒钟源素材1.5秒,Whisper对每个源文件运行一次。在后台运行,对于5分钟的视频预计需要数十分钟。它会为每个元素和每个源打印进度行(已刷新,因此重定向的日志会显示)。后续运行会缓存并快速;技能更新如果改变了测量方式,会重新测量一次。它会为每个机位打印一行(元素、缩放预算、静止图像),以及已包含手动帧化设置的元素数量:这些元素保持原样,列表在
context.json和REPORT.md中。 -
决定。 查看每个机位的静止图像,填写
decisions.json(参见如何决定)。然后选择推入行。 -
求解。 一个不更改Resolve中任何内容且从不连接Resolve的试运行。它始终基于当前版本在已跟踪状态下的记录计划(通过resolve-bridge采用的时间线)工作,因此自上次记录版本以来在Resolve中进行的任何手动编辑都不在其中:
prepare和apply会先记录它们。python3 scripts/framing.py solve --workdir <文件夹>写入
generated/framing/REPORT.md、每个机位的setups/<id>_before_after.jpg、每个标记元素的flags/<n>.jpg,以及.edit/framing/solution.json。无效的decisions.json(缺少标签、框在0..1之外)会停止并给出原因和退出代码2。 -
自行检查预览。 打开每个前后对比图像和标记图像。每个预览显示一个帧:该机位的第一个元素,或标记元素的中间帧。手势在其他元素或其他时刻不在此预览中,因此不仅要检查机位预览,还要检查每个标记元素的预览,并阅读标记文本。如果手在画面中位置较低(伸向镜头、在底部边缘附近做手势),即使显示的单个帧看起来没问题,也要考虑设置
"bottom_hands": "gesture"。布局:左半部分是整个源素材,绿色显示输出帧,红色显示排除框,蓝色显示保留框(推入的结束帧为青色;在关于跳过的推近或放弃的推入的标记上,被拒绝的尝试为橙色);右半部分是结果,如同观众所见,带有三分线。查找规则禁止的内容:与帧边缘相切、被裁切的手(如果手在底部边缘被裁切,同时伸向镜头或在做手势,而不是放在膝盖或桌子上,则在该机位上设置"bottom_hands": "gesture"并重新求解)、头部太靠近顶部、边缘仍有杂物、选中的植物被裁切。如果画面看起来错误,请修正你自己的决定(移动框、更改标签、放弃推入)并重新求解。重复直到预览正确,或者剩余问题只有人才能判断。同时阅读报告的标记和放弃的推入:报告在每个标记下提供其原始详情(框坐标、元素键);检查清单问题则得到简明的文字说明。 -
应用。 需要打开Resolve并加载项目。
python3 scripts/framing.py apply --workdir <文件夹>再次求解,然后通过resolve-bridge分叉
<base>_vNN_framing(保留调色、效果和手动编辑),设置缩放、平移和倾斜(推入剪辑获得其起始帧),存档上一个版本并保存项目。记录该步骤后,每个标记和你issues列表中的每个条目都会成为EDIT_CHECKLIST.md中的一个问题,由剪辑名称和时间码定位。标记会先合并:一个机位所有跳过的推近合并为一个问题,计数其理由;同一机位的相同标记合并为一个。告知用户新的时间线名称、问题ID和报告位置。退出代码2表示预期的失败并附带消息(没有跟踪的时间线、缩放模式未经帧化校准等)。 -
在检查器中为推入设置关键帧。 Resolve的脚本API无法创建关键帧,因此
apply将每个推入剪辑保留在其起始帧,写入.edit/framing/keyframes_todo.json,并为每个推入添加一个检查清单问题,包含精确的值。如果你有计算机使用权限,请立即执行;否则告知用户这些问题由他们处理(每个剪辑大约一分钟),并且在用户下一次手动编辑同步后,由pushes check进行检查。python3 scripts/framing.py pushes begin --workdir <文件夹> # 分叉 <base>_vNN_pushins,列出推入 python3 scripts/framing.py pushes goto 1 start --workdir <文件夹> python3 scripts/framing.py pushes finish --workdir <文件夹> # 检查关键帧,保存,关闭问题对于每个推入,在Resolve编辑页面的检查器中(视频 > 变换)。后台应用控制无法输入到Resolve中,因此这需要全屏控制:
pushes goto N start。脚本移动播放头,检查器跟随。点击剪辑可能会将播放头移动一帧,因此之后不要点击时间线。检查检查器是否命名了该剪辑。- 点击缩放和位置行右侧的关键帧菱形。它们会变为红色。值已经是起始帧。
pushes goto N end。双击缩放X,然后全选,输入结束缩放值并按回车(缩放Y在关联时会跟随)。对位置X和Y执行相同操作。新的关键帧会自动出现。- 缓动:右键点击每个红色菱形。在结束帧处,选择缓入。在起始帧处,一旦结束帧存在,选择缓入和缓出。关键帧的菜单只提供其相邻帧允许的缓动选项,并且勾选标记并不总是更新,因此检查结果:在半程处,缩放应接近两个值的平均值,在四分之一程处应低于直线值。
- 切勿在已设置关键帧的剪辑上通过脚本设置属性:Resolve会删除其所有关键帧。
pushes finish读取关键帧(通过FCPXML导出)并比较帧和值。如果缺少或错误,它会指出并保持步骤未完成。--partial会无论如何保存,并保持这些问题未关闭。pushes check执行相同的检查但不保存。apply --pushes fusion会使用Fusion变换创建推入(旧方法,完全由脚本控制)。
如何决定
decisions.json:
{
"setups": {
"A": {
"items": ["..."],
"label": "plain",
"reason": "坐在桌旁,正面,静态。右侧边缘有带磁铁的微波炉,远处左侧有空椅子;植物保留。",
"keep_out": [[0.00, 0.45, 0.12, 1.00], [0.86, 0.30, 1.00, 0.80]],
"keep_in": [[0.60, 0.20, 0.74, 0.62]]
},
"B": {"items": ["..."], "label": "dynamic", "reason": "坐在柜台上,高角度", "keep_out": [], "keep_in": []}
},
"reclassify": {"V1@1234": "broll"},
"push_ins": [
{"start_s": 41.2, "end_s": 47.0, "text": "写作就是思考。",
"reason": "核心论点,第一次直白地说出"}
],
"issues": [
{"text": "右侧边缘的植物与茶几一起被裁切;不确定它是否是杂物",
"where": "S04_IMG_1234.MOV 在 00:14.00"}
]
}
框为 [左, 上, 右, 下],归一化到0..1范围内,基于机位静止图像(从其网格读取)。start_s 和 end_s 是来自 transcript.md 的时间线秒数。items 由 prepare 填充;请保持原样。
- 排除框:无人选择展示的、靠近边缘的一切。电器、磁铁、空椅子、灯具、线缆、恒温器、门框、沙发的一角。求解器会尽可能将其裁切掉,但不会剪裁到手势。
- 保留框:演讲者选择的布景装饰。植物、花卉、花瓶。求解器会保留其完整,或明确裁切掉,不留一丝。框出重要的部分(花束、植物的叶子),而不是延伸到源边缘:触及源边缘的框永远无法在裁切内完整保留。
- 为每个机位标记为
plain或dynamic。plain是指正面、眼睛水平、静态(坐在桌旁)。dynamic是指高或低角度、不寻常的位置(坐在柜台上)或强烈的对角线。动态机位不获得推入。 - 原因:每个机位一行,始终填写。它会进入报告。
- 底部手部(可选):在机位上设置
"bottom_hands": "gesture"。默认情况下,触及源底部(从其高度的93%向下)的手被视为休息状态:画面可以停在它们上方。伸向镜头的手也会触及底部边缘,该规则会裁切它们。如果前后对比或标记预览显示手在底部边缘被裁切,同时手在伸展或做手势,请在该机位上设置此项并重新求解:这些手随后会被完全计入,画面会变得更加宽松。 - 问题:每个你不确定的判断(杂物还是选择、两样东西保留哪个、你几乎选中的台词),同时应用你的最佳答案。
text说明你决定的内容以及需要人工检查的内容;where是剪辑名称和时间码(来自context.json),例如S04_IMG_1234.MOV 在 00:14.00。两者都不能为空。当apply完成时,它们会成为检查清单问题。 - 重新分类仅用于纠正分组:脚本视为镜头内但实际上是B-roll的元素(
broll),或相反情况(on_camera)。键为V<轨道>@<起始帧>(例如V1@1234):即.edit/framing/context.json中items的键,其中也给出了每个元素的剪辑名称和时间码。与任何元素都不匹配的键将被忽略,因此请对照context.json检查。所有元素都被重新分类为broll的机位不需要标签。 - 推入行来自脚本和
transcript.md:核心论点、论证的转折点、笑点、行动号召。而不是填充内容,也不是铺垫句。保持长度在3到8秒之间,且至少相隔30秒,放置在没有人声B-roll覆盖说话者的平淡机位上,并为每个编写原因。少而精优于弱行。求解器会放弃任何违反规则的行,并在报告中说明哪条规则(并在其标记预览中以橙色显示被拒绝的推入)。推入以时间线秒数记录,因此solve会检查每个推入的text是否在当前时间线的转录中:如果该台词在其范围内不再出现(时间线在你选择后发生了变化),则放弃该推入并创建一个问题以便重新选择。从transcript.md复制text,以便检查能够找到它。如果没有缓存的转录,则推入会被保留,并且报告会说明未检查。
帧化规则
这些是脚本强制执行的规则。原因在此列出,以便你能够判断规则未涵盖的镜头。
基础帧,每个机位一个
- 眼睛位于或刚好在上三分之一线上方,每个剪辑的头部空间小且相等。*眼睛靠近上三分之一线看起来自然且投入;上方墙面太多显得业余。剪到额头或触及头发的画面看起来像个错误。*当手在画面较低位置时,会将画面拉低,使眼睛移至上三分之一线上方;此时画面会向后倾斜,在相同缩放倍数下,在头部上方保留大约10%的画面高度(根据面部尺寸估计);在手势、杂物或接合处没有空间的情况下,仅由眼线决定。
- 对于直接面对镜头说话,面部居中;为了排除杂物,最多偏移画面宽度的5%。直对镜头意味着对称;面部偏离中心看起来像个错误,除非画面明显为此构图。
- 底部边缘永远不落在接合处(下巴、脖子、肘部、手腕、腰部...)