Communitygithub.com

Istok-Media-Tool/zoom

與使用者一同在計劃編輯器中規劃縮放(點選文字,請參閱 plan-editor 技能),然後在已剪輯好的談話性頭部影片中,於內容驅動的時刻動態加入推近裁切,並正確地以主體為中心並保留適當的頭部空間——讓不同縮放級別之間的剪接讀起來像是有意為之的電影運鏡,而非主體在畫面中明顯跳動。

zoom 是什麼?

此技能旨在為已剪輯好的談話性頭部影片添加「動態」縮放效果,避免常見的業餘錯誤。其核心流程分為五個步驟:首先,必須與使用者一同在「plan-editor」工具中逐字規劃縮放時機與級別,確保每個縮放變換都有內容上的理由。其次,針對每一個片段(即使來自同一段連續錄影)獨立測量主體的人臉中心與頭頂位置,因為長時間錄影中主體的姿勢可能改變。接著,透過指令碼計算裁切參數,特別注意「廣角」鏡頭也需要裁切以達到與特寫鏡頭相同的光學中心與頭部空間(預設為畫面高度的9%),避免剪接時主體跳動。然後,必須先對每個片段進行抽格檢驗,確保動態畫面中頭部不會被裁切。最後,將確認的計劃與時間戳記結合,生成最終的裁切序列並渲染輸出。若畫面中有關鍵物件(如平板),必須確保所有縮放級別都完整包含該物件。

相容平台✓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. 使用者希望一起決定縮放計劃,逐字討論,使用 plan-editor 工具(步驟 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:在 plan-editor 工具中一起規劃縮放

每支影片都必須執行:載入並遵循 plan-editor 技能($SKILLS/plan-editor/SKILL.md)。這是使用者標準的「點選文字」工具,他們在此與您一起決定縮放,並透過「Izmeni tekst」在同一個句子中修正字幕文字。切勿根據使用者尚未在該工具中塑造過的計劃來渲染縮放。

您在該步驟中的任務是提出初步建議:根據內容的能量曲線來建構——平穩/冷靜的部分用廣角,轉折或笑點在其關鍵字上進行推近,解說性的段落拉遠以提供喘息空間,高潮部分使用最緊湊的畫面;決定性的級別差異(不要有微妙的漸進變化);在開場之後至少有一次真正的回到廣角——並說明每次變換的原因。使用者的回覆(PLAN_JSON、SENTENCES_JSON)即為確認的計劃;在步驟 5 中完全按照此計劃渲染。

應用計劃需要文字時間戳記(步驟 5)和每個片段的物體位置(步驟 2)。

examples/sample/ 包含一個實際確認的資料集(句子、使用者的計劃、位置、剪接),適用於以 transpose=1 旋轉的橫向拍攝影片——可作為檔案格式及此使用者偏好的計劃類型的參考。

步驟 2:測量主體的位置——每個片段,每次都做

切勿將一個片段測量的頭部位置重複用於另一個片段的裁切計算,即使在同一段連續錄影中——當你進入長時間錄影的幾分鐘後,某人可能在座位上移動了位置,而根據早期片段畫面計算的裁切可能會導致後續片段中的主體偏離中心,或在較緊湊的縮放下實際裁切到主體。

對於你要裁切的每個片段(包括「廣角」片段——請參閱步驟 4):

ffmpeg -nostdin -y -ss <mid_timestamp_of_that_segment> -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 <measured_x> --head-top-y <measured_y> --zoom 1.7
python3 scripts/compute_crop.py --cx <measured_x> --head-top-y <measured_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 = 來自步驟 2 的 [[cx, head_top_y], ...] 每片段(如果在旋轉後的畫面上測量,且 --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 "<source_video>" selections.txt final_output.mp4 "$PWD/build"

將工作目錄傳遞為絕對路徑——使用相對路徑時,最終的拼接步驟會找不到 concat_list.txt。

渲染出的片段時長可能與先前渲染的時長相差百分之幾秒,因此如果字幕(字幕技能)是根據較早的渲染進行對時的,請在燒錄字幕前,根據每個片段的新開始時間偏移每個字幕。

在改寫 cut_and_concat.sh 之前,請閱讀其頂部的註解區塊——它記錄了 ffmpeg/bash 的陷阱(迴圈內 stdin 被竊取、淡出效果吞掉剪接點的最後一個音節、為什麼片段在拼接前需要個別重新編碼),否則很容易在不知不覺中再次引入這些問題。

混合了基礎(廣角)片段和兩次推近的範例選擇檔案:

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

演講者展示的物體必須在所有縮放級別中保持完整

在實際的影片中,演講者展示了旁邊平板上的照片;以臉部為中心的裁切會切掉其左邊緣(「ne sme tablet da bude isečen ovako nigde」)。在渲染之前,列出畫面中影片所關於的所有物體(平板、產品、舉起的頁面),並在網格上測量其範圍。每個縮放裁切都必須包含該範圍,並留有約 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.

相關技能