장편 영상 → 한국어 큐레이션 요약 영상 파이프라인

개요 (한국어): 35분짜리 YouTube 키노트 같은 장편 영상을 4~5분짜리 한국어 큐레이션 요약 영상으로 만든 노하우. ReClip(yt-dlp)으로 받기 → 자막 추출 → 의미 단위로 mini-clip 분리 → edge-tts 한국어 내레이션 → ffmpeg 합성까지의 전 과정. 2026-05-20 Google I/O 2026 keynote 작업에서 도출.

1. 파이프라인 전체 그림

YouTube URL
  │
  ▼  [yt-dlp]
원본 mp4 + 자동 자막 VTT
  │
  ▼  [Claude 정독]
전체 흐름 파악 → 핵심 섹션 N개 선정
  │
  ▼  [Python: VTT cue 검색]
각 핵심 자막 줄 → 영문 앵커 → 원본 timestamp
  │
  ▼  [Claude 작성]
화면 자막(요약 헤드라인) + 내레이션 텍스트(친절 설명조) 분리 작성
  │
  ▼  [edge-tts]
mini-clip마다 한국어 narration mp3 생성 + duration 측정
  │
  ▼  [ffmpeg]
mini-clip cut (clip_dur = TTS_dur + 0.8s) + 무음 패딩 + TTS 합성
  │
  ▼  [ffmpeg concat + subtitles filter]
한 본으로 합치고 자막 burn-in (Malgun Gothic)
  │
  ▼
최종 mp4 (영상 + 한국어 내레이션 + 헤드라인 자막)

2. 단계별 핵심 노하우

2.1 영상·자막 받기 (yt-dlp)

ReClip 같은 Flask 래퍼를 Windows에서 직접 실행할 때 함정:

  • app.pysubprocess.run(["yt-dlp", ...]) 로 시스템 PATH에서 yt-dlp를 찾는다.
  • venv 안에만 yt-dlp가 있으면 [WinError 2] 지정된 파일을 찾을 수 없습니다 가 발생.
  • 해결: 실행 전 $env:PATH = "venv\Scripts;$env:PATH" 로 venv\Scripts 를 PATH 앞에 박는다. Linux/Mac의 source venv/bin/activate 가 PATH를 갱신해주는 것을 Windows에서는 수동으로 해야 한다.

자막만 추출하는 명령:

yt-dlp --skip-download --write-auto-subs --sub-langs "en,ko" `
       --sub-format vtt -o "subs\%(id)s.%(ext)s" "<URL>"
  • YouTube 자동자막은 다국어가 다 옵션에 보이지만 Rate Limit(HTTP 429)에 자주 걸린다. 한국어는 1차 실패하면 그냥 영어만 받고 자체 번역(또는 친절 설명조 한국어 재작성)으로 우회.
  • vtt에는 단어 단위 타이밍 <00:00:01.234> 가 섞여 있다. <[^>]+> 정규식으로 제거.

2.2 VTT 파싱 (timestamp 보존)

VTT cue를 (start, end, text) 튜플 리스트로 만들어두면, 이후 모든 단계에서 timestamp 검색이 쉬워진다. YouTube auto-VTT는 연속 cue가 같은 텍스트로 누적되는 특성이 있어, 연속 중복은 한 cue로 머지하는 게 좋다.

cues.json 같은 중간 산출물로 저장해두면 디버깅·실험·재실행 비용이 확 준다.

2.3 의미 단위 분할 — “자막 1줄 = 1컷” 원칙

처음에는 섹션 단위(30~40초 1개 cut)로 자르고 자막을 그 위에 흘렸는데, 그 30초 안에서 화면이 여러 슬라이드로 바뀌어 자막이 말하는 내용과 화면이 안 맞는다.

정답: 자막을 5~8초 짜리 줄 단위로 잘게 쪼개고, 각 자막 줄에 해당하는 원본의 그 발화 순간을 검색해서 그 시점에서 짧은 mini-clip(=clip_dur)을 잘라낸다. 그러면 자막이 말하는 그 화면만 정확히 나온다.

검색 키는 영문 앵커 문구:

  • 큐레이션 자막을 한국어로 작성
  • 각 자막에 대해 transcript에서 그 내용을 가장 잘 표현하는 영문 어구 1개를 골라 앵커로 지정
  • 앵커 문구를 VTT cue에서 lower-case substring 매칭으로 검색 → start time
  • prev 변수로 직전 매칭 이후만 검색해서 시간 단조 증가 보장

시간 역행 사고: 동일 키워드가 영상 후반에도 등장하면 (예: “coming this fall”) 의도와 다른 시점이 잡힌다. 검증 단계에서 clips[i].src >= clips[i-1].src 를 확인하고, 깨지면 더 고유한 앵커(예: “two designs of a bigger collection”)로 교체하거나 큐레이션 순서를 transcript 시간 순으로 재정렬.

2.4 자막 ≠ 내레이션 (분리 설계)

자막을 그대로 TTS로 읽히면 짧고 딱딱하다. 처음엔 그렇게 했지만 결과물이 어색했다.

분리 원칙:

  • 자막 (화면 표시): 짧은 헤드라인 — “Maps, 10년 만의 최대 업그레이드”
  • 내레이션 (음성): 친절한 설명조 — “구글 맵스는 십년 만에 가장 큰 업그레이드를 단행했습니다.”

자막은 “한 눈에 보이는 요약”이고 내레이션은 “친절한 설명”이라 역할이 다르다.

2.5 한국어 TTS — edge-tts

선택 이유:

  • 무료, API 키 불필요, 인터넷만 있으면 됨
  • 한국어 신경망 음성 3종 (ko-KR-HyunsuMultilingualNeural 남성·다국어, ko-KR-InJoonNeural 남성, ko-KR-SunHiNeural 여성)
  • 합성 품질이 클라우드 TTS급
  • asyncio.gather 로 46개 batch 동시 호출 → 1초 미만

설치:

pip install edge-tts

호출 예:

import edge_tts, asyncio
async def gen():
    c = edge_tts.Communicate("안녕하세요", "ko-KR-HyunsuMultilingualNeural", rate="+6%")
    await c.save("out.mp3")
asyncio.run(gen())

2.6 영어·숫자 → 한글 표기 (TTS 정확 발음의 결정타)

TTS가 영어 단어를 영어 발음으로 읽으면 한국어 흐름에서 어색하다. 내레이션 텍스트에서 영어와 숫자를 모두 한글로 풀어 쓰면 TTS가 한국어 음운으로 자연스럽게 읽는다.

원어/숫자내레이션 표기
Gemini제미나이
YouTube유튜브
Anti-Gravity 2.0안티 그래비티 이점영
Doom
Door Dash도어 대시
Cloud VM클라우드 가상머신
MCP엠씨피
iOS아이오에스
Halo헤일로
AGI에이지아이
Isomorphic Labs아이소모픽 랩스
Pro·Ultra프로, 울트라
10년십년
30분삼십분
24시간이십사시간
2.0이점영

표기 규칙은 붙여쓰기가 자연스럽다 (“십 년” 보다 “십년”). 띄어쓰기를 넣으면 TTS가 약간 끊어 읽는다.

자막은 원어/숫자 그대로 유지 — 시각적 가독성이 더 중요하고, 내레이션이 이를 풀어 발음해주므로 충돌 없음.

2.7 ffmpeg — clip cut + TTS 합성

mini-clip마다 영상은 원본 timestamp에서 자르고, 오디오는 원본을 버리고 TTS를 입힌다. 각 clip의 길이는 그 자막의 TTS 길이에 동적으로 맞춘다:

clip_dur = max(MIN_DUR, tts_dur + PAD_LEAD + PAD_TAIL)
# PAD_LEAD=0.30  → TTS가 시작되기 전 살짝 화면 노출
# PAD_TAIL=0.50  → TTS 끝나고 화면이 살짝 더 머무름

ffmpeg 한 줄로 비디오는 무음 cut + TTS 오디오를 adelay 로 lead 만큼 미루고 apad 로 clip 끝까지 padding:

ffmpeg -ss SRC_START -t CLIP_DUR -i src.mp4 \
       -i tts.mp3 \
       -filter_complex "[0:v]scale=1280:-2,fps=30,format=yuv420p[v];\
                        [1:a]adelay=300|300,apad=whole_dur=CLIP_DUR[a]" \
       -map "[v]" -map "[a]" \
       -c:v libx264 -preset veryfast -crf 22 \
       -c:a aac -b:a 128k -shortest out.mp4

각 clip을 동일 코덱·해상도·fps로 통일해놓으면 다음 단계 concat 이 -c copy 로 가능해 빠르다.

2.8 concat + 자막 burn-in

# concat
ffmpeg -f concat -safe 0 -i list.txt -c:v copy -c:a aac -b:a 128k concat.mp4
 
# burn-in (Windows: cwd 이동해서 상대경로 — subtitles 필터가 콜론을 옵션 구분자로 오인)
cd outdir
ffmpeg -i concat.mp4 \
       -vf "subtitles=subs.srt:force_style='FontName=Malgun Gothic,Fontsize=20,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,BorderStyle=1,Outline=2,Shadow=0,Alignment=2,MarginV=48'" \
       -c:v libx264 -preset veryfast -crf 22 -c:a copy final.mp4

Windows ffmpeg subtitles 필터의 함정: D:/... 경로의 : 을 ffmpeg가 옵션 구분자로 오해. cwd를 outdir로 옮기고 subtitles=subs.srt 처럼 상대 경로로 호출하면 안전.

자막 SRT 텍스트 인코딩은 UTF-8, 폰트는 Malgun Gothic (한국어 가독성). 흰 글씨 + 검은 외곽선이 어떤 화면 위에서도 잘 읽힌다.

2.9 1.2배속 후처리

원본 영상 + 오디오를 같이 가속:

ffmpeg -i in.mp4 \
       -filter_complex "[0:v]setpts=PTS/1.2[v];[0:a]atempo=1.2[a]" \
       -map "[v]" -map "[a]" \
       -c:v libx264 -preset veryfast -crf 22 -c:a aac -b:a 128k out.mp4

atempo 는 0.5~2.0 범위. 그 이상은 atempo=2.0,atempo=1.2 식으로 체인.

3. Python stdout 인코딩 (Windows-only 함정)

Windows cp949 콘솔에서 한국어 + emdash() 같은 유니코드 문자를 print 하면 UnicodeEncodeError 가 자주 난다. 스크립트 맨 위에 박는 한 줄:

import sys, io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

4. 산출물 디렉토리 컨벤션 (반복 작업용)

D:\claude\reclip\
├── downloads\<job_id>.mp4       # 원본 다운로드
├── subs\
│   ├── <video_id>.en.vtt        # 원본 자막
│   ├── <video_id>.en.txt        # 깨끗한 transcript (Claude가 읽기용)
│   └── cues.json                # 파싱된 (start, end, text) 큐
├── highlights<N>\               # 반복 시도마다 N 증가
│   ├── tts\tts_NN.mp3          # mini-clip별 TTS
│   ├── m00~mNN.mp4             # mini-clip
│   ├── concat.mp4               # 자막 없는 본
│   ├── subs.srt                 # 사이드카 자막
│   └── highlight_vN.mp4         # 최종본

highlights<N> 으로 폴더를 분리해두면 v1→v2→v3 변형을 비교하기 쉽다.

5. 빌드 스크립트 템플릿 위치

C:\Users\USER\.claude\jobs\<job-id>\build_vN.py 에 빌드 스크립트를 둔다 (PLAN 리스트 = 자막 + 내레이션 + 앵커).

한 PLAN 리스트가 모든 정보 보유 — 자막 텍스트, 내레이션 텍스트, 영문 앵커. 코드는 거의 변하지 않고 텍스트만 바뀐다. v1~v5는 코드 동일, PLAN만 진화.

6. 검증 절차 (각 빌드 후)

  1. 순서 검증: clips[i].src >= clips[i-1].src 단조 증가 확인 → 깨지면 앵커 교체
  2. 샘플 프레임 추출: 서로 다른 섹션 3~4 지점에서 frame PNG 1장씩 뽑아 자막↔화면 매칭 시각 확인
    ffmpeg -ss <t> -i final.mp4 -frames:v 1 sample.png
  3. ffprobe duration: 총 길이가 의도와 일치 확인
  4. 첫 30초 청취: 내레이션이 자연스럽게 들리는지

7. 향후 확장 가능성

  • Qwen3-TTS voice clone: ComfyUI-Qwen3-TTS의 Qwen3VoiceClone 노드 + Base 모델로 reference audio 기반 클로닝 가능. 시도 시 ComfyUI 서버 띄워야 함 (GPU 필요). 한국어 reference audio 30초 정도가 적절.
  • VoiceDesign: 자연어 description으로 음성 디자인. CustomVoice 모델 다운로드 시 9종 preset 사용 가능 (Vivian, Ryan 등).
  • LLM 기반 자동 큐레이션: 이번엔 Claude(나)가 transcript를 직접 정독해서 큐레이션을 손으로 짰지만, 외부 LLM API에 transcript와 길이 목표를 던져 큐레이션 JSON을 받아오게 자동화 가능. 단 영어→한글 표기 변환은 보정 룰을 별도로 박아야 안정적.

8. 이번 작업의 최종 산출물 (2026-05-20)

버전특징길이경로
v3자막=TTS, 영어/숫자 원문3:57D:\claude\reclip\highlights4\highlight_v3.mp4
v4자막/내레이션 분리, 영어→한글5:13D:\claude\reclip\highlights5\highlight_v4.mp4
v5+ 숫자→한글5:13D:\claude\reclip\highlights6\highlight_v5.mp4
v5 fast 1.2xv5 가속4:21D:\claude\reclip\highlights6\highlight_v5_fast12.mp4
v6 (여성)SunHi voice5:09D:\claude\reclip\highlights7\highlight_v6_female.mp4
v6 fast 1.2xv6 가속4:18D:\claude\reclip\highlights7\highlight_v6_female_fast12.mp4

원본 영상: Google I/O 2026 keynote 발췌 35분 (The Verge, OMhKgQmeMhI).