Communitygithub.com

원본 영상에서 쇼츠까지: 자막 넣은 9:16 클립 추출

긴 영상에서 하이라이트를 찾습니다(Whisper로 펀치라인·리액션·멈춤을, 오디오 트랜지언트 감지로 도끼질·타격·드럼 비트를). 각 순간이 실제로 화면에 잡혔는지 확인한 뒤 독립 클립으로 자르고, 얼굴 추적 패닝이나 분할 화면으로 16:9를 9:16으로 리프레이밍하며 단어 단위 자막을 번인합니다.

원본 영상에서 쇼츠까지: 자막 넣은 9:16 클립 추출란 무엇인가요?

‘원본 영상에서 쇼츠까지’ 레시피 2단계(세로 클립), 출처는 GitHub 스타 600개 이상의 Claude Code 스킬 louisedesadeleer/clipify이며 npx skills add louisedesadeleer/clipify 로 설치합니다. 먼저 모드를 고릅니다. 대화 모드는 Whisper로 전사(영어는 tiny.en 단어 타임스탬프, 다른 언어는 base)하고 펀치라인·반전·어색한 멈춤·음량 피크를 찾습니다. 액션 모드는 Whisper 없이 1~6kHz 대역의 스펙트럴 플럭스로 트랜지언트를 감지해 타격마다 컷합니다. 하이브리드는 둘을 섞습니다. 후보 3~5개를 제안하고, 각 시점의 프레임을 뽑아 동작이 보이는지 확인한 뒤 클립을 자르고, 9:16용 얼굴 추적 크롭 또는 분할 화면을 만들고, 패닝을 타임워프로 조이고, ffmpeg로 Opus 스타일의 단어 단위 ASS 자막을 번인합니다(마스터 libx264, CRF 20).

지원 대상✓Claude Code~Codex CLI~Cursor
npx skills add louisedesadeleer/clipify

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

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/ (시작 시 mkdir, 디버깅을 위해 아티팩트 유지).


워크플로우

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).
  • 여러 클립 소스(휴대폰 클립 폴더)의 경우 각 클립에서 감지기를 실행하고 다양한 각도에서 다양한 세트를 선택하세요. 약 0.9초/샷으로 45–60초 몽타주를 위해 50–65회 타격을 목표로 하세요.

각 "샷"은 타격 약 0.4초 전(윈드업 보여줌)에 시작하여 타격 약 0.5초 후(타격 + 짧은 후속 동작)에 종료되어야 합니다. 비트당 약 0.9초입니다.

1.5단계 — 각 후보가 프레임 내에 있는지 확인

렌더링 전에 항상 수행하세요. 두 모드 모두에서. 오디오 감지는 이벤트의 소리를 찾습니다. 카메라가 그것을 캡처하지 않았을 수도 있습니다. 이것을 건너뛰는 것은 "내가 보이지 않는 곳에서 자르기"와 같은 사용자 피드백을 받는 #1 원인입니다.

# 각 후보 타격 시간 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 (Insta 피드)?"

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(1080×960과 일치하도록 1.125:1)로 대상으로 해야 합니다.

4c단계 — 시간 왜곡 공개(선택 사항)

사용자가 느린 풍경/팬/공개 샷을 "절반으로 자르고" 싶지만 호를 잃지 않으려 할 때, 트리밍 대신 속도-램프를 사용하세요. 트리밍은 호의 일부를 버리도록 강제합니다. 속도-램핑은 더 빠른 템포로 모든 비트를 보존합니다.

# 2x 속도: 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.5x — "약간 빠르게" 느껴져야 하지만 빨라 보이지 않는 인간 움직임
  • 2x — 더 펀치감 있는 공개; 여전히 자연스럽게 읽힘
  • 3x–4x — 타임랩스 분위기(4x의 경우 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/에 저장(mkdir이 없는 경우 생성)
  • 클립당 한 줄씩 출력: 이름, 길이, 재미있었던 점, 출력 경로
  • open <path>로 첫 번째 출력을 열어 사용자가 확인할 수 있도록 함
  • 반복 제안(다른 스타일, 다른 ROI, 분할 화면으로 전환, 자막 재조정)

함정(이전 실행에서 얻은 교훈 — 반복하지 마세요)

  • 오디오 감지 ≠ 보이는 이벤트. 렌더링 전에 항상 1.5단계(각 후보 프레임 확인)를 실행하세요. 감지기는 자르는 소리를 찾는 것이지, 자르는 사람이 프레임 안에 있는지가 아닙니다. 대상이 몸을 숙이거나, 카메라 밖에 있거나, 렌즈에 엄지손가락이 있는 경우에도 오디오 감지기가 여전히 트리거됩니다. 렌더링 전에 잡아내세요.
  • 스펙트럼-플럭스 대역이 중요합니다. 기본 --band 1000:6000은 나무/금속/유리 충격을 포함합니다. 무거운 저음(킥 드럼, 바디샷)은 --band 200:2000이 필요합니다. 휘파람, 딸깍 소리, 스네어 크랙은 --band 4000:10000이 필요합니다. --min-flux 30이 아무것도 반환하지 않으면 먼저 15로 낮추십시오. 수천 개를 반환하면 임계값을 높이기 전에 다른 대역을 시도하십시오.
  • 속도-램프 오디오는 쌍으로. atempo는 단일 필터에서 0.5–2.0만 허용합니다. 4x의 경우 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를 실행하세요.
  • 계획을 한 줄로 말한 다음 실행하세요. 모든 반복을 설명하지 마세요.

관련 스킬