Communitygithub.com

Istok-Media-Tool/zoom

与用户一起在计划编辑器中规划变焦,然后对已剪辑的讲述者视频在内容驱动的时刻添加动态推拉变焦裁剪,确保主体居中且头部空间适当——使不同变焦级别之间的切换看起来像是有意的电影级镜头选择,而非主体在画面中明显跳动。

zoom 是什么?

该技能旨在为已剪辑的讲述者视频添加专业的动态变焦效果,避免常见的错误,如主体跳动或头部空间不当。它要求用户首先通过计划编辑器工具(Plan-Editor)与AI共同决定变焦计划,基于内容能量曲线(如平静场景用广角,关键点推近)逐词确定变焦触发点。然后,针对每个视频片段(包括广角镜头)分别测量主体(头部)位置,并计算裁剪参数,确保所有镜头共享相同的视觉中心和头部空间(默认9%),以消除切换时的跳跃感。最后,通过实际帧验证确保主体在动态中不被裁剪,再输出最终视频。该技能支持多个AI编码工具(如Claude Code),并强调避免复用测量数据、必须对广角镜头进行裁剪以及保护画面中的重要对象(如平板电脑)等关键要点。

兼容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Istok-Media-Tool/video-edit-skills/tree/HEAD/video-edit/skills/zoom

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

$SKILLS 是该技能文件夹的父目录:即技能加载时显示的基础目录的父目录(插件的 skills/ 文件夹)。

动态变焦

为什么需要这个技能

为已剪辑的讲述者视频添加“动态”变焦,看似简单,但很容易出错,而且只有在观看最终结果时才会显现,仅凭数值无法察觉。在实际案例中,同一个视频经过多次修正反馈后才算正确——每次修正都教会了我们一些值得记住的经验:

  1. 在每个剪辑点都进行变焦的做法显得廉价和花哨——真正的剪辑判断应该更稀疏、更有意为之。

  2. 以眼平线为中心对称裁剪,而非以实际头顶为准,导致在缩放后头部上方留下过多空白空间(头部空间约 17%,而理想的约为 9%)。

  3. 将某个片段测得的头部位置复用于同一录制中其他片段的裁剪计算,会导致主体在录制过程中(即使是在单个连续录制中)因几分钟前后位置偏移而明显偏离画面中心。

  4. 如果只对变焦镜头进行裁剪,而对“广角”/非变焦镜头完全不裁剪,那么广角与变焦镜头之间的每次切换仍会导致主体明显横向跳动——广角镜头需要进行相同的重新定心和头部空间处理,只是缩放级别较低,才能使它们之间的切换感觉像连贯的拍摄。

  5. 每个剪辑只用一个变焦级别,再在每个句子的中点机械地放置一个推近,用户会觉得非常“随意”。变焦必须跟随脚本:每次变化都应落在一个合理的关键词或短语上(揭示、关键术语、个人问题、笑点),而拉远则应落在关于“规模”的词语上(如“宇宙”、“数十亿年”)。变化发生在剪辑内部,有时一个剪辑内会有多次变化。

  6. 用户希望与您共同逐词决定变焦计划,使用计划编辑器工具(步骤 1)。对于任何脚本,都要始终使用它。

  7. 用户选择了变焦级别:1.0x、1.5x、1.7x、2.0x、2.2x(计划级别 0-4)。“1.0x”(等级 0)并非原始帧:渲染为未裁剪的原始帧会导致主体偏离中心且头部上方空间过大,用户要求它与其他镜头一样居中并具有相同的头部空间。因此,等级 0 = --baseline 裁剪(满足 9% 头部空间的最宽松裁剪,并重新居中;实际约 1.2x)——build_zoom_selections.py 会自动处理。在工具中保留“1,0x”标签;它表示“最广角,居中”。

步骤 1:在计划编辑器工具中共同规划变焦

每段视频都必须执行:加载并遵循计划编辑器技能($SKILLS/plan-editor/SKILL.md)。它是用户的标准点击单词工具,用于与您一起决定变焦,并通过“Izmeni tekst”修改同一句子中的字幕文本。切勿渲染未经用户在该工具中确认的计划。

您在该步骤中的任务是提出初始方案:根据内容的能量曲线构建——介绍/平静部分使用广角,转折或笑点在其关键词处需要进行推近,解释性节拍需要拉远以呼吸,高潮部分使用最紧的构图;决定性的级别差异(不微妙递增);开场后至少有一次真正的返回广角——并说明每次变化的原因。用户的回复(PLAN_JSON、SENTENCES_JSON)是确认的计划;在步骤 5 中完全照此渲染。

应用计划需要单词时间戳(步骤 5)和每个剪辑的主体位置(步骤 2)。

examples/sample/ 目录下保存了一组真实的已确认数据(句子、用户的计划、位置、剪辑),来自一个使用 transpose=1 旋转的侧面拍摄视频——可作为文件格式以及此类用户选择计划的参考。

步骤 2:测量主体的位置——每个片段单独测量,每次都测

切勿将一个片段测得的头部位置复用于另一个片段的裁剪计算,即使是在同一个连续录制中——在长时间录制中,几分钟后用户可能已经移动了位置,而基于早期片段帧计算的裁剪可能会导致后期看似相似但构图已不同的片段中主体偏离中心,甚至在更紧的变焦下剪辑到主体。

对于您将要裁剪的每个片段(包括“广角”片段——参见步骤 4):

ffmpeg -nostdin -y -ss <该片段的中段时间戳> -i source.mp4 -frames:v 1 ref.png
ffmpeg -nostdin -y -i ref.png -vf "drawgrid=w=54:h=96:t=1:[email protected]" grid.png

(54x96 在 1080x1920 帧上形成 20x20 网格 - 按比例缩放以适应其他分辨率。)查看 grid.png 并读出:

  • cx - 脸部/鼻子中心的 x 坐标
  • head_top_y - 头顶最高点的 y 坐标(不是眼睛——头部空间是从头顶开始测量的)

步骤 3:计算裁剪参数

python3 scripts/compute_crop.py --cx <测量的x坐标> --head-top-y <测量的y坐标> --zoom 1.7
python3 scripts/compute_crop.py --cx <测量的x坐标> --head-top-y <测量的y坐标> --baseline
  • --zoom Z 用于明确的推近级别(例如,1.7 表示 170% 镜头)。
  • --baseline 用于“广角”镜头:它会找到满足头部空间目标并水平居中的最宽松裁剪——这样广角镜头只被轻度裁剪(并非字面意义上的 100%/未处理),但与变焦镜头共享完全相同的视觉中心和头部空间,这才是真正防止切换时主体看起来跳动的关键。不要跳过裁剪广角镜头——仅修复变焦镜头而保留原始广角镜头,是测试中导致“看起来不对劲”的最主要原因,其影响甚至超过单个变焦级别设置错误。

默认头部空间为裁剪后画面高度的 9%,经测试看起来正确——低于此值会显得局促,而且这不仅是美学问题:垂直平台(Instagram/TikTok/Shorts)会在画面上方覆盖用户名/字幕/淡出 UI,因此这个边距是平台会占用的功能空间,而不是空闲区域。如果特定项目有特殊要求,可以使用 --headroom 覆盖,但对于发布到这些平台的视频,不要大幅低于约 9%。

以上所有内容的完整理由都记录在 scripts/compute_crop.py 的文档字符串中——内容简短,如果对特定案例有任何不清楚的地方,请阅读它。

步骤 4:在完整渲染前验证实际渲染输出

在单个静止帧上看起来正确的裁剪,在检查运动时仍可能剪辑到主体或使其偏离中心。对于每个片段(尤其是当您选择参考帧时肢体运动最多——例如前倾、头部倾斜的那个),请执行以下操作:

  1. 仅渲染该片段并应用其裁剪。
  2. 在其整个时长内(而不仅仅是您测量的那一个帧)采样 3 个或更多帧,例如 ffmpeg -i clip.mp4 -vf fps=3 check_%02d.png。
  3. 检查它们。如果头部被切断或过于靠近边缘,请在继续之前放松该片段的变焦或扩大边距。

只有通过此验证后,才能渲染所有片段并进行拼接。

步骤 5:将确认的计划转换为基于单词的剪辑,然后组装

  1. 从相同剪辑的渲染(普通剪辑或之前的变焦渲染——填充必须匹配)中获取单词时间戳,并记下该渲染的每个剪辑的时长(在工作目录中使用 ffprobe 处理每个 clip_NNN.mp4):
    python3 scripts/word_times.py cut_render.mp4 words.json --language sr
    
  2. 构建选择文件。cuts.txt = 基本技能的未填充 start<TAB>end 每剪辑;pos.json = 每剪辑的 [[cx, head_top_y], ...](来自步骤 2,如果 --pre-filter 进行了旋转,则在旋转后的帧上测量):
    python3 scripts/build_zoom_selections.py --cuts cuts.txt --positions pos.json \
      --plan plan.json --sentences sentences.txt --words words.json \
      --durations 6.28,6.08,... [--pre-filter transpose=1] > selections.txt
    
    阅读它打印的匹配报告:每个计划的单词都应显示 exact(或合理的 fuzzy~ 拼写变体——Whisper 可能以不同的方式或西里尔字母拼写名称)。任何 proportional (no match!) 行必须在渲染前手动修复。变化发生在单词前 0.08 秒(--lead)。包含多个级别的剪辑将作为一个连续剪辑渲染(分割 + 定时叠加),因此句子中间的变焦永远不会增加剪辑点或音频接缝。
  3. 在每次变焦变化前后 0.2 秒处抽检帧(步骤 4 逻辑:头部不被剪辑,变化清晰可见),然后渲染:
scripts/cut_and_concat.sh "<源视频>" selections.txt final_output.mp4 "$PWD/build"

将工作目录作为绝对路径传递——如果使用相对路径,最终的 concat 步骤将找不到 concat_list.txt。

渲染的片段持续时间可能与之前渲染的相差百分之几秒,因此,如果字幕(字幕技能)是根据较早的渲染定时的,则在烧录之前,将每个字幕移动其片段的新开始位置。

在调整 cut_and_concat.sh 之前,阅读其顶部的注释块——它记录了 ffmpeg/bash 的陷阱(循环内的 stdin 窃取、淡出吃掉剪辑的最后一个音节、为什么剪辑在 concat 前需要单独重新编码),这些陷阱很容易在不知情的情况下重新引入。

混合了基线(广角)片段和两个推近的示例选择文件:

63.82	69.57	crop=908:1614:152:304,scale=1080:1920
80.37	85.90	crop=636:1130:288:348,scale=1080:1920
97.25	100.81	crop=908:1614:152:304,scale=1080:1920

如果源素材还需要旋转/方向修复,则单独处理(不属于此技能,因为并非总是需要)——如果适用,请自行在每个行的滤镜链前添加商定的旋转滤镜。

交付

如果主输出超出工具的文件大小限制,请制作辅助压缩副本,而不是压缩主文件:

ffmpeg -nostdin -y -i final_output.mp4 -c:v libx264 -preset slow -crf 24 -pix_fmt yuv420p -c:a aac -b:a 128k final_output_share.mp4 -loglevel error

讲话者展示的对象在所有变焦中必须保持完整

在实际的样片中,讲话者展示了旁边的平板电脑上的照片;以脸部为中心的裁剪切掉了其左边缘(“平板电脑在任何地方都不能被这样切掉”)。在渲染之前,列出视频中关于的所有内容(平板电脑、产品、举起的页面),并在网格上测量其边框。每个变焦裁剪都必须包含该边框,并留有约 30 像素的边距:将裁剪的 x/y 向它移动(脸部可以偏离中心,最多约 70%),如果脸部因此会碰到边缘,则对该变化使用较宽松的级别(在实际样片中从 1.7x 改为 1.6x),而不是裁剪任何一个。查看每个变焦范围的一个帧,以确认两者都完整。

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

相关技能