Communitygithub.com

原片變短影音:切出有字幕的 9:16 直式片段

在長影片裡找出精彩時刻(用 Whisper 找笑點、反應與停頓,或用音訊瞬態偵測找劈砍、擊打與鼓點),確認每個時刻確實在畫面內,切成獨立片段,用人臉跟隨平移或分割畫面把 16:9 改成 9:16,並燒錄逐字字幕。

原片變短影音:切出有字幕的 9:16 直式片段 是什麼?

「原片變短影音」配方第 2 步(切成直式),來自 louisedesadeleer/clipify,一個 GitHub 600+ 星的 Claude Code 技能,以 npx skills add louisedesadeleer/clipify 安裝。先選模式:對白模式用 Whisper 轉寫(英文用 tiny.en 詞級時間戳,其他語言用 base),掃描笑點、反轉、尷尬停頓與音量峰值;動作模式跳過 Whisper,在 1–6 kHz 頻段做頻譜通量瞬態偵測,每一擊都切;混合模式兩者穿插。它提出 3–5 個候選,在每個候選時間點抽格確認動作確實入鏡,切出片段,為 9:16 產生人臉跟隨裁切或分割畫面,以時間重映射讓平移揭示更緊湊,最後用 ffmpeg 燒錄 opus 風格的逐字 ASS 字幕(母版 libx264、CRF 20)。

相容平台✓Claude Code~Codex CLI~Cursor
npx skills add louisedesadeleer/clipify

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

Clipify

在影片中找到精彩時刻,將其剪輯成獨立的片段,選擇性地將 16:9 格式轉換為 9:16(人臉追蹤或分割畫面),並對平移畫面進行時間扭曲以實現更緊湊的揭示效果,最後加上 opus 風格的逐字字幕。

模式

在步驟 1 之前選擇一種模式。選擇會影響您尋找精彩時刻的方式以及字幕的適用性。

  • 對話模式(播客、訪談、談話性人物預設使用):使用 Whisper 進行轉錄,您掃描尋找妙語、反應或尷尬的停頓。→ 步驟 1A。字幕在步驟 5。
  • 動作模式(劈柴、運動、打擊樂、鼓樂、或任何重複撞擊聲):完全跳過 Whisper,執行 detect_transients.py 來找出每個撞擊點,並在每個撞擊點進行剪輯。→ 步驟 1B。跳過步驟 5;撞擊聲本身就是節奏。
  • 混合模式:對談話部分使用對話模式,對其餘部分使用動作模式,並進行交叉剪輯。同時使用步驟 1A 和 1B。

如果使用者說「我沒有在講話」,就是動作模式。如果他們說「在每個 X 處剪輯」(砍、擊、打、鼓點、揮舞、腳步聲),就是動作模式。否則預設為對話模式。

輸入

  • 影片檔案路徑(使用者會提供;否則詢問)
  • 可選:要求的格式(9:16、16:9、1:1)——如果未提供,在選定候選片段後詢問
  • 可選:字幕樣式偏好——如果未提供,在加字幕前詢問

工具(僅使用最快路徑)

  • Whisper: whisper --model tiny.en --word_timestamps True --output_format json(比 small.en 快約 10 倍;英文品質足夠)。非英文:--model base(省略 --language)。
  • ffmpeg: 解碼時加上 -hwaccel videotoolbox,渲染時使用 -preset ultrafast。最終輸出使用 -c:v libx264 -crf 20。
  • Numpy 用於音訊對齊(FFT 互相關)。不需要 scipy/cv2。
  • 腳本: <skill-dir>/scripts/(<skill-dir> 是包含此 SKILL.md 的目錄——通常是 ~/.claude/skills/clipify/)
    • analyze.py —— 從兩個 ROI 運動檔案建立說話者時間軸
    • build_pan.py —— 帶有硬剪輯的 ffmpeg 裁切 x 表達式
    • build_ass.py —— 從 Whisper JSON 建立 opus 風格的 ASS 字幕
    • audio_align.py —— 找出子剪輯在較長來源中的偏移量
    • detect_transients.py —— 找出尖銳的撞擊聲(動作模式);請參閱 --help

工作目錄:/tmp/clipify/(開始時建立,保留工件以便除錯)。


工作流程

步驟 1A —— 尋找對話時刻(對話模式)

mkdir -p /tmp/clipify
ffmpeg -y -hwaccel videotoolbox -i "$VIDEO" -vn -ac 1 -ar 16000 /tmp/clipify/audio.wav
whisper /tmp/clipify/audio.wav --model tiny.en --word_timestamps True --output_format json --output_dir /tmp/clipify --language en

讀取產生的 JSON(或 .txt)並選取 3–5 個候選片段。要掃描的搞笑訊號:

  • 妙語和反應: 像「什麼」、「等等」、「不會吧」、笑聲、「哈哈」、髒話
  • 反轉時刻: 鋪陳問題 → 意想不到的答案
  • 尷尬的停頓: Whisper 片段中有較長間隔,或填充詞(「呃」、「嗯」)
  • 自嘲 / 值得引用的金句: 簡短、可獨立存在的陳述句
  • 音訊峰值: 透過 ffmpeg -af volumedetect 檢測,或尋找快速來回對話(交替的短 Whisper 片段)

針對每個候選片段,提出:[開始時間, 結束時間, 為什麼有趣, 建議標題]。目標長度 10–25 秒。顯示清單並讓使用者確認/挑選。

步驟 1B —— 找出每個撞擊點(動作模式)

用音訊瞬態檢測取代 Whisper。檢測器透過計算 1–6 kHz 頻段的頻譜通量來找出尖銳的撞擊聲(斧頭砍木頭、拳頭擊打護墊、鼓面、球棒擊球、木材落在堆上)——這是撞擊所產生的、而環境音/風聲所沒有的寬頻脈衝。

ffmpeg -y -hwaccel videotoolbox -i "$VIDEO" -vn -ac 1 -ar 16000 /tmp/clipify/audio.wav
python3 <skill-dir>/scripts/detect_transients.py /tmp/clipify/audio.wav \
  --min-flux 30 --min-gap 0.6 > /tmp/clipify/strikes.json

調整:

  • --min-flux 30 是合理的預設值。真實的撞擊會達到 50–300;環境/風聲在 5–15。太多誤報 → 提高到 40–50。太少命中 → 降低到 20。
  • --min-gap 0.6(秒)是兩個撞擊之間的最小間隔。快速鼓點可能需要 0.2;劈柴使用 0.6–1.0 即可。
  • --band 1000:6000 涵蓋木材/金屬/玻璃撞擊。沉重的悶響(大鼓、身體撞擊)位於更低頻段(--band 200:2000);口哨/叮噹聲/小鼓爆裂位於更高頻段(--band 4000:10000)。
  • 對於多片段來源(一組手機影片),對每個片段執行檢測器,並在不同角度中選取多樣化的組合。目標是 50–65 個撞擊點,以製作約 0.9 秒/鏡頭的 45–60 秒蒙太奇。

每個「鏡頭」應在撞擊前約 0.4 秒開始(顯示蓄力動作),並在撞擊後約 0.5 秒結束(撞擊 + 短暫的跟隨動作)。即每個節拍約 0.9 秒。

步驟 1.5 —— 驗證每個候選片段是否在畫面中

無論哪種模式,在渲染前務必執行此步驟。 音訊檢測找到的是事件的聲音;攝影機可能沒有捕捉到它。跳過此步驟是使用者回饋「你在砍柴時剪輯,但我根本不在畫面中」的首要原因。

# 在每個候選撞擊時間 T 取一個畫面:
for T in $STRIKE_TIMES; do
  ffmpeg -y -ss "$T" -i "$VIDEO" -frames:v 1 -vf scale=320:-1 \
    /tmp/clipify/verify_${T}.jpg
done

閱讀每個驗證圖片,並在以下情況下放棄該候選片段:

  • 對象彎腰、在畫面外、或被障礙物遮擋
  • 拇指擋住鏡頭(是的,這會發生——而且只有視覺檢查才能發現)
  • 明顯是另一種聲源(關門聲、掉落的工具)觸發了檢測器

對於約 30 個候選片段,這只需一次快速的 ffmpeg 調用加上一次批次圖片讀取。成本低廉的保險。

步驟 2 —— 修剪每個選定的片段

ffmpeg -y -ss "$START" -t "$DURATION" -i "$VIDEO" -c copy /tmp/clipify/clip_$N.mp4

(使用 -c copy 進行即時修剪。僅在需要幀級精確剪輯時才重新編碼。)

步驟 3 —— 決定輸出格式

詢問使用者(如果他們已指定則跳過):「9:16(TikTok / Reels)、16:9(YouTube)、還是 1:1(Instagram 動態)?」

步驟 4 —— 如果是 16:9 → 9:16:人臉之間的平移 vs. 分割畫面

使用 ffprobe 檢測來源長寬比。如果來源是 16:9 且目標是 9:16,詢問:

「兩個選項:(a) 硬剪輯平移,追蹤正在說話的人(每次螢幕上只有一個臉),或 (b) 分割畫面堆疊,兩個臉都可見。您想要哪個?」

如果只有一個臉(單一說話者片段),則跳過此問題。對於單一說話者,只需置中裁切。

步驟 4a —— 人臉之間的平移(建議用於快節奏談話對話)

  1. 定位兩個臉部 ROI。 取一個畫面:ffmpeg -ss <中間時間> -i <片段> -frames:v 1 /tmp/clipify/probe.jpg。讀取它。目測每個臉的嘴巴+下巴區域,在來源像素空間中表示為 x,y,w,h。(不需要 cv2——片段內攝影機是靜止的;一個畫面就夠了。)透過繪製方塊進行驗證:

    ffmpeg -i probe.jpg -vf "drawbox=x=$LX:y=$LY:w=$LW:h=$LH:[email protected]:t=4,drawbox=x=$RX:y=$RY:w=$RW:h=$RH:[email protected]:t=4" verify.jpg
    

    最多疊代兩次。方塊應覆蓋嘴巴 + 下巴,並避免手和麥克風。不要過度調整——幀差分是有容錯的。

  2. 提取每個 ROI 中的逐帧運動能量:

    ffmpeg -y -i clip.mp4 -filter_complex "
    [0:v]split=2[a][b];
    [a]crop=$LW:$LH:$LX:$LY,format=gray,tblend=all_mode=difference,signalstats,metadata=mode=print:key=lavfi.signalstats.YAVG:file=/tmp/clipify/L.txt[la];
    [b]crop=$RW:$RH:$RX:$RY,format=gray,tblend=all_mode=difference,signalstats,metadata=mode=print:key=lavfi.signalstats.YAVG:file=/tmp/clipify/R.txt[ra]
    " -map "[la]" -f null - -map "[ra]" -f null -
    
  3. 建立說話者時間軸(最小停留時間 1.0 秒——短暫的插話合併到前一個說話者):

    python3 <skill-dir>/scripts/analyze.py /tmp/clipify/L.txt /tmp/clipify/R.txt 1.0 > /tmp/clipify/segments.json
    
  4. 為來源中 9:16 的垂直條帶選擇平移 x 座標。 假設來源寬度 W=1920 且目標寬度 W=1080,裁切條帶寬度為 608。

    • LEFT_X = face_left_center_x - 304(限制 ≥ 0)
    • RIGHT_X = face_right_center_x - 304(限制 ≤ source_W - 608)
  5. 產生硬剪輯 x 表達式並渲染:

    EXPR=$(python3 <skill-dir>/scripts/build_pan.py /tmp/clipify/segments.json $LEFT_X $RIGHT_X)
    ffmpeg -y -hwaccel videotoolbox -i clip.mp4 -filter_complex \
      "[0:v]crop=608:1080:x='$EXPR':y=0,scale=1080:1920:flags=lanczos[v]" \
      -map "[v]" -map 0:a -c:v libx264 -preset fast -crf 20 -pix_fmt yuv420p \
      -c:a aac -b:a 192k /tmp/clipify/clip_panned.mp4
    

    假設來源為 1920×1080;對於 4K 來源,先縮小或將所有座標加倍。

步驟 4b —— 分割畫面(兩個臉始終可見)

兩個堆疊的圖塊,每個 1080×960。當前說話者的圖塊位於上方——在說話者變化時進行覆蓋翻轉。

[0:v]split=2[a0][a1];
[a0]crop=Wcrop:Hcrop:LX_tile:LY_tile,scale=1080:960,split=2[lt0][lt1];
[a1]crop=Wcrop:Hcrop:RX_tile:RY_tile,scale=1080:960,split=2[rt0][rt1];
[lt0][rt0]vstack[layoutL];
[rt1][lt1]vstack[layoutR];
[layoutL][layoutR]overlay=0:0:enable='<RIGHT_SPEAKER_ENABLE>'[v]

從 segments.json 將 <RIGHT_SPEAKER_ENABLE> 建立為 between(t,a,b)+between(t,a,b)+...,對應於右側說話者的片段。圖塊裁切目標是每個臉周圍約 720×640(1.125:1 以匹配 1080×960)。

步驟 4c —— 時間扭曲揭示效果(可選)

當使用者想要將緩慢的風景/平移/揭示鏡頭收緊(「將其長度減半」)但又不失其弧線時,請使用速度曲線而不是修剪。修剪迫使您放棄弧線的一部分;速度曲線則以更高的節奏保留所有節拍。

# 2 倍速度:12 秒弧線 → 6 秒輸出,透過 atempo 保留音調
ffmpeg -y -ss "$START" -i "$CLIP" -t "$ORIG_DUR" \
  -vf "setpts=PTS/2,scale=1080:1920:flags=lanczos,fps=30" \
  -af "atempo=2.0" \
  -c:v libx264 -preset fast -crf 21 -pix_fmt yuv420p \
  -c:a aac -b:a 128k /tmp/clipify/clip_2x.mp4

經驗法則:

  • 1.5 倍 —— 人類動作應感覺「稍微急促」,但看起來不像快轉
  • 2 倍 —— 更具衝擊力的揭示;仍然看起來自然
  • 3–4 倍 —— 縮時攝影氛圍(對於 4 倍鏈接 atempo=2.0,atempo=2.0;atempo 每個篩選器實例僅接受 0.5–2.0)
  • 如果音訊只是環境/風聲且花栗鼠音效會分散注意力,則省略 -af atempo 並使該片段靜音

當剪輯的其餘部分具有節奏感(砍柴蒙太奇、聊天來回)而揭示效果感覺像一個死點時,這很有用。

步驟 5 —— 加入字幕

在動作模式中跳過此步驟——撞擊聲就是節奏,字幕只會使視覺雜亂。

詢問一次(僅在使用者尚未指定樣式時):

「三種字幕樣式:opus(大而粗的白色字體,黃色活動詞高亮)、karaoke(4 字組,綠色高亮)、minimal(簡潔的 Helvetica 字體,無高亮)。或者貼上您喜歡的範例。」

如果他們貼上參考圖片/範例:盡可能比對字體、大小、粗細、顏色、位置和動畫——手動編寫自訂 ASS 或擴充 build_ass.py。

否則使用預設值:

# 在修剪後的片段上重新執行 Whisper,以獲得相對於片段開始的準確時間戳
whisper /tmp/clipify/clip_panned.mp4 --model tiny.en --word_timestamps True --output_format json --output_dir /tmp/clipify --language en
python3 <skill-dir>/scripts/build_ass.py /tmp/clipify/clip_panned.json /tmp/clipify/captions.ass opus

燒錄字幕:

ffmpeg -y -i /tmp/clipify/clip_panned.mp4 -vf "subtitles=/tmp/clipify/captions.ass" \
  -c:v libx264 -preset fast -crf 20 -c:a copy "$OUTPUT.mp4"

步驟 6 —— 交付

  • 將每個輸出儲存到 <source_dir>/clipify_out/(若不存在則建立目錄)
  • 每個片段輸出一行:名稱、持續時間、有趣之處、輸出路徑
  • 使用 open <path> 開啟第一個輸出,方便使用者檢查
  • 提供迭代選項(不同樣式、不同 ROI、切換為分割畫面、重新調整字幕時間)

陷阱(先前執行的教訓——請勿重複)

  • 音訊檢測 ≠ 可見事件。 在渲染前務必執行步驟 1.5(驗證每個候選片段畫面)。檢測器找到的是砍劈的聲音,而不是砍劈者是否在畫面中。對象彎腰、在畫面外、或拇指擋住鏡頭的命中點仍然會觸發音訊檢測器。在渲染前捕捉它們。
  • 頻譜通量頻段很重要。 預設值 --band 1000:6000 涵蓋木材/金屬/玻璃撞擊。沉重的低頻悶響(大鼓、身體撞擊)需要 --band 200:2000。口哨、叮噹聲、小鼓爆裂需要 --band 4000:10000。如果 --min-flux 30 未返回任何結果,請先嘗試降低至 15;如果返回數千個結果,請在提高閾值之前嘗試不同的頻段。
  • 速度曲線音訊應成對處理。 atempo 在單個篩選器中僅接受 0.5–2.0;對於 4 倍鏈接 atempo=2.0,atempo=2.0。沒有 atempo 的情況下,僅使用 setpts 會產生花栗鼠音效。
  • 不要過度調整 ROI。 最多兩次疊代。運動差異是有容錯的——覆蓋嘴巴+下巴的較寬 ROI 即使沒有完美地對準嘴巴中心也能很好地運作。
  • 注意片段內的場景切換。 執行 ffmpeg -filter:v "select='gt(scene,0.3)',showinfo" -f null - 來計算切換次數。如果 16:9→9:16 片段有許多切換,固定的臉部 ROI 僅對主要場景有效;警告使用者,並提供選擇單一鏡頭片段或接受切換期間非置中畫面的選項。
  • 來源解析度很重要。 如果來源是 4K,先縮小到 1920×1080(更快,適合 9:16 輸出)或將所有 ROI/平移座標乘以 2。
  • 來源中已燒錄的字幕。 某些「原始」片段仍然有字幕。如果是這樣,透過音訊互相關(audio_align.py)找到無字幕的母版,並從那裡開始修剪。
  • 如果短片段足夠,不要在完整的長片來源上執行 Whisper。 在步驟 2 之後對修剪後的片段執行 Whisper;僅在步驟 1 中需要逐字稿來尋找有趣時刻時,才對完整來源執行 Whisper。
  • 用一行說明計劃,然後行動。 不要敘述每個疊代。

相關技能