ffmpeg 파이프라인
도우미 스크립트는 이 파일과 같은 디렉토리의 scripts/에 있습니다. python <skill_dir>/scripts/<name>.py로 실행하세요.
probe.py IN...— 파일당 한 줄 요약 (크기, fps, 회전, HDR, 길이, 오디오).export.py IN OUT --preset instagram|tiktok|shorts|youtube|master|preview— 올바른 설정으로 최종 인코딩.frames.py IN OUTDIR --times 0.5,3,7.2또는--every 1— 체크 프레임 캡처.
1. 먼저 프로빙 및 정규화
전화기 영상의 함정:
- 회전: iPhone은 가로 픽셀을 저장하고 회전 플래그를 추가합니다. ffmpeg은 디코딩 시 자동 회전합니다(
-noautorotate로 비활성화). OpenCVVideoCapture는 회전하지 않을 수 있습니다 —frame.shape를 확인하고 직접 회전하세요. - VFR: 전화기는 가변 프레임 레이트를 기록합니다. 추출 시 CFR로 변환:
-vf fps=30(또는-r 30출력)하여 프레임 계산이 정확하도록 합니다. - HDR:
color_transfer=arib-std-b67(HLG) 또는smpte2084(PQ). 그대로 두면 SDR에서 흐릿하게/회색으로 보입니다. 톤 매핑:
# HDR (HLG 또는 PQ) -> SDR Rec.709, CPU zscale 경로
-vf "zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0,zscale=t=bt709:m=bt709:r=tv,format=yuv420p"
hable = 필름릭; mobius는 채도를 더 유지합니다. zscale이 없으면 libplacebo를 사용하세요 (-vf libplacebo=tonemapping=bt.2390:colorspace=bt709:color_primaries=bt709:color_trc=bt709).
- Apple Log / 카메라 로그: 먼저 제조사의 Log → Rec.709 LUT를 적용하고 (
lut3d=AppleLog_to_Rec709.cube), 그레이딩하세요. - 10비트 소스: 많이 그레이딩할 때는 중간 파일을 10비트로 유지하고 (
-pix_fmt yuv422p10leProRes 또는yuv420p10lex265), 소셜용 최종 내보내기 시에만 8비트yuv420p로 변환하세요.
2. 빌드 스크립트의 아키텍처
sources/ (절대 수정하지 않음)
edit/
build.py # 소스 + 데이터 파일에서 모든 것을 재생성
edl.json # [{src, in, out, speed, grade, transition}]
segments/ # EDL 항목당 하나의 그레이딩되고 정규화된 파일 (무손실에 가깝게)
overlays/ # PNG / PNG 시퀀스 / ProRes 4444 / WebM 알파
preview.mp4, final.mp4
규칙:
- 구간별 추출 모든 클립별 작업(트림, 속도, 스케일/크롭, 그레이드, fps)을 한 번에 수행 →
-crf 10(x264) 또는 ProRes 422 HQ 중간 파일. - 전환이 필요 없을 때 연결 concat 디먹서와
-c copy사용 (모든 세그먼트는 코덱, 해상도, fps, pix_fmt, 타임베이스가 동일해야 함). - 오버레이 및 텍스트 연결된 비디오에 대해 한 번의 최종 패스에서 처리한 다음, 전달 사양에 따라 한 번 인코딩합니다.
- 오디오는 별도로 구성(음악 + SFX + 대화 → 하나의 WAV)한 다음 최종 패스에서 먹싱합니다.
정확한 트림
# -ss를 -i 앞에 두면 빠르고 프레임 정확함 (ffmpeg >= 2.1, 재인코딩 시)
ffmpeg -ss 12.40 -i IN.mov -t 2.333 -vf "fps=30,scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920" \
-c:v libx264 -crf 10 -preset medium -pix_fmt yuv420p -an seg_003.mp4
프레임 정확해야 하는 트림에는 -c copy를 절대 사용하지 마세요 (키프레임에 스냅됩니다).
16:9를 9:16으로 채우기
- 크롭 (커버):
scale=-2:1920,crop=1080:1920:(iw-1080)*X:0(X는 0..1로 피사체 위치 선택, 피사체가 움직이면 추적). - 흐린 배경 (컨테인):
split[a][b];[a]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,boxblur=40:2[bg];[b]scale=1080:-2[fg];[bg][fg]overlay=(W-w)/2:(H-h)/2
연결
# list.txt: file 'segments/seg_001.mp4' (한 줄에 하나)
ffmpeg -f concat -safe 0 -i list.txt -c copy joined.mp4
xfade로 전환
ffmpeg -i a.mp4 -i b.mp4 -filter_complex \
"[0:v][1:v]xfade=transition=fade:duration=0.5:offset=4.5,format=yuv420p[v]; \
[0:a][1:a]acrossfade=d=0.5[a]" -map "[v]" -map "[a]" out.mp4
offset = 첫 번째 클립 길이 − 전환 길이. 유형: fade, fadeblack, fadewhite, wipeleft, slideup, smoothleft, circleopen, radial, zoomin, hblur, pixelize, dissolve… 여러 클립에 대해 누적 오프셋 체인 (각 xfade는 길이만큼 총 길이를 줄입니다).
오버레이
# 전체 비디오에 대한 정적 PNG (전체 프레임, 투명) — -loop 불필요
[0:v][1:v]overlay=0:0:format=auto
# 시간 제한
overlay=0:0:enable='between(t,2.0,5.5)'
# 출력에서 T 시간에 시작하는 애니메이션 오버레이 클립
[1:v]setpts=PTS-STARTPTS+T/TB[ov];[0:v][ov]overlay=0:0:eof_action=pass
알파 오버레이: PNG 시퀀스 (-framerate 30 -i ov/%04d.png), ProRes 4444 (-c:v prores_ks -profile:v 4444 -pix_fmt yuva444p10le), 또는 VP9 WebM (-c:v libvpx-vp9 -pix_fmt yuva420p).
긴 비디오의 경우 4K RGBA PNG를 -loop 1로 사용하지 마세요. RAM을 많이 소모합니다. 단일 이미지 입력은 overlay에서 자동으로 반복됩니다.
Python(PIL/OpenCV 합성)에서 ffmpeg로 원시 프레임 파이핑:
p = subprocess.Popen(["ffmpeg","-y","-f","rawvideo","-pix_fmt","rgb24","-s",f"{W}x{H}","-r","30","-i","-",
"-c:v","libx264","-crf","12","-pix_fmt","yuv420p","out.mp4"], stdin=subprocess.PIPE)
p.stdin.write(frame.tobytes()) # numpy uint8 HxWx3, 프레임당
같은 방식으로 소스 프레임 읽기 (-f rawvideo -pix_fmt rgb24 -), OpenCV 회전 및 코덱 문제 방지.
속도
- 일정:
setpts=PTS/2(2배) +atempo=2.0(atempo 범위 0.5-100; 더 체인). - 30fps에서 부드러운 슬로모:
minterpolate=fps=60:mi_mode=mci:mc_mode=aobmc:vsbmc=1, 그런 다음setpts=2*PTS. 가장자리/손에서 왜곡 확인. - 가변 속도 램프: 시간 맵에서 프레임별로 렌더링 (
beat-sync참조), 여러setpts표현식 쌓지 말 것.
자막 (마지막)
-vf "subtitles=captions.ass:fontsdir=fonts" # ASS는 완전한 스타일 제어 제공
3. 내보내기 프리셋
| 대상 | 비디오 | 오디오 | 비고 |
|---|---|---|---|
| 인스타그램 릴스 / 틱톡 / 쇼츠 | 1080x1920 (또는 2160x3840) H.264 High, 30 fps, CRF 16-18, -maxrate 25M -bufsize 50M, yuv420p | AAC 48 kHz 256-320k | -movflags +faststart; 플랫폼이 재인코딩하므로 고품질 파일 제공 |
| YouTube 16:9 | 3840x2160 또는 1920x1080, H.264 CRF 16 또는 HEVC CRF 18, 소스 fps | AAC 48 kHz 320k | 1080p 콘텐츠라도 4K로 업로드하여 더 나은 VP9/AV1 인코딩 획득 |
| 마스터 | ProRes 422 HQ 또는 x264 CRF 10 / x265 CRF 14 -tag:v hvc1 | PCM 24-bit 48 kHz | 보관용 |
| 미리보기 | 720p, CRF 28, -preset veryfast | AAC 128k | 빠른 반복 |
소셜용으로 항상 -pix_fmt yuv420p 추가 (10비트 또는 4:2:2는 업로드 후 실패하거나 잘못 보일 수 있음) 및 SDR을 올바르게 태그하기 위해 -color_primaries bt709 -color_trc bt709 -colorspace bt709 추가.
4. 확인
ffprobe -v error -show_entries stream=codec_name,width,height,r_frame_rate,pix_fmt -show_entries format=duration -of compact out.mp4
ffmpeg -i out.mp4 -af ebur128=peak=true -f null - 2>&1 | tail -12
ffmpeg -i out.mp4 -vf "select='gt(scene,0.3)',showinfo" -f null - 2>&1 | grep pts_time # 실제 컷 위치