비디오 합성 — 조립, 오디오, 인코딩, 검증
FFmpeg는 전달 계층이지 설계 도구가 아닙니다. 이미 설계된 장면을 받아 깨끗한 마스터와 플랫폼 인코딩을 생성해야 합니다.
references/FFMPEG-COOKBOOK.md에서 바로 사용 가능한 명령어를, scripts/validate.sh에서 전달 전 검증 스크립트를 참조하세요.
제로 규칙: 먼저 프로브하세요. 가정된 스트림 속성에 기반한 필터 그래프 구성은 깨진 출력의 가장 흔한 원인입니다.
ffprobe -v error -show_streams -show_format -of json input.mp4
1. 결합 전 정규화
호환되지 않는 입력은 끊김, 무음, 드리프트 및 헤일로를 유발합니다. 다중 소스 조립 전에 모든 입력이 다음 속성에 일치하도록 만드세요:
| 속성 | 목표값 |
|---|---|
| 해상도 | 프로젝트 마스터 (예: 1920×1080) |
| 픽셀 종횡비 | 1:1 |
| 프레임 속도 | 프로젝트 FPS, 상수 |
| 타임베이스 | 일관성 유지 |
| 픽셀 형식 | 전달용 yuv420p, 알파용 yuva444p10le/PNG |
| 색 공간 / 전송 함수 | 하나의 표준; 태그 지정 |
| 오디오 | 48kHz, 스테레오, 일관된 코덱 |
ffmpeg -i in.mp4 -vf "scale=1920:1080:flags=lanczos,fps=30,setsar=1" \
-c:v libx264 -crf 14 -preset fast -pix_fmt yuv420p \
-c:a aac -ar 48000 -ac 2 -b:a 256k normalized.mp4
중간 결과물은 고품질로 유지하세요(CRF 12–16 또는 ProRes). 전달 버전은 한 번만 인코딩합니다.
2. 연결
동일한 코덱, 크기, FPS, 픽셀 형식 및 오디오 레이아웃 → 스트림 복사, 빠르고 무손실:
printf "file 'shot_01.mp4'\nfile 'shot_02.mp4'\n" > list.txt
ffmpeg -f concat -safe 0 -i list.txt -c copy final.mp4
그 외의 경우 → 필터 그래프:
ffmpeg -i a.mp4 -i b.mp4 -filter_complex \
"[0:v]scale=1920:1080,fps=30,setsar=1[v0]; \
[1:v]scale=1920:1080,fps=30,setsar=1[v1]; \
[v0][0:a][v1][1:a]concat=n=2:v=1:a=1[v][a]" \
-map "[v]" -map "[a]" -c:v libx264 -crf 16 -pix_fmt yuv420p -c:a aac final.mp4
일치하지 않는 입력에 스트림 복사 연결을 절대 하지 마세요. "성공"하고 깨진 재생을 생성합니다.
3. 오버레이, 알파 및 PIP
# 배경 위에 투명 요소
ffmpeg -i bg.mp4 -i logo.mov -filter_complex \
"[0:v][1:v]overlay=W-w-48:48:format=auto" -c:v libx264 -crf 16 out.mp4
# PiP, 크기 조정 및 둥근 모서리 소스
ffmpeg -i main.mp4 -i insert.mp4 -filter_complex \
"[1:v]scale=640:-2[pip];[0:v][pip]overlay=W-w-40:H-h-40" out.mp4
알파 규칙:
- H.264/MP4는 알파를 지원할 수 없습니다. PNG 시퀀스, ProRes 4444 또는 WebM/VP9(알파 포함)을 사용하세요.
- 각 단계 후 대비되는 배경 위에서 알파를 확인하세요.
- 어두운 가장자리에서 사전 곱셈 vs. 직선 알파 헤일로를 주의하고, 소스 렌더러의 규칙과 일치시켜야 합니다.
4. 오디오
# 페이드 인/아웃과 음성 아래 덕킹이 있는 배경 음악
ffmpeg -i video.mp4 -i music.mp3 -i voice.wav -filter_complex \
"[1:a]volume=0.35,afade=t=in:st=0:d=1,afade=t=out:st=28:d=2[bed]; \
[2:a]volume=1.0[vo]; \
[bed][vo]sidechaincompress=threshold=0.08:ratio=6:attack=8:release=250[duck]; \
[duck][vo]amix=inputs=2:duration=first:dropout_transition=0[a]" \
-map 0:v -map "[a]" -c:v copy -c:a aac -b:a 192k out.mp4
# 음량 측정 후 정규화
ffmpeg -i in.mp4 -af loudnorm=I=-14:TP=-1:LRA=11:print_format=json -f null -
ffmpeg -i in.mp4 -af loudnorm=I=-14:TP=-1:LRA=11 -c:v copy out.mp4
목표는 대상에 따라 다릅니다(-14 LUFS는 많은 웹 플랫폼, -23/-24는 방송용). 측정 후 선택하세요. 이미 목표에 도달한 파일은 정규화하지 마세요.
5. 자막
# 번인 (하드 자막)
ffmpeg -i in.mp4 -vf "subtitles=captions.srt:force_style='FontName=Inter,FontSize=22,PrimaryColour=&H00FFFFFF,OutlineColour=&H80000000,BorderStyle=3,Outline=1,Shadow=0'" \
-c:v libx264 -crf 18 -c:a copy out.mp4
# 소프트 자막 (이동 가능한 트랙)
ffmpeg -i in.mp4 -i captions.srt -c copy -c:s mov_text -metadata:s:s:0 language=eng out.mp4
RTL 확인: shaping 가능한 글꼴, 올바른 Alignment, 그리고 렌더링된 프레임 검사가 포함된 force_style을 사용하세요. 캡션을 안전 영역 내에, 플랫폼 UI 위에 유지하세요.
6. 플랫폼 인코딩
# 가로 YouTube, 1080p30
ffmpeg -i master.mov -c:v libx264 -preset slow -crf 18 -pix_fmt yuv420p \
-profile:v high -level 4.1 -movflags +faststart \
-c:a aac -b:a 192k -ar 48000 out_1080p.mp4
# 세로 소셜, 1080x1920
ffmpeg -i master.mov -vf "scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920" \
-c:v libx264 -preset slow -crf 18 -pix_fmt yuv420p -movflags +faststart \
-c:a aac -b:a 192k out_vertical.mp4
# 추가 합성을 위한 고품질 중간 포맷
ffmpeg -i master.mov -c:v prores_ks -profile:v 3 -pix_fmt yuv422p10le -c:a pcm_s24le master.mov
인코더 존재 여부 확인: ffmpeg -encoders | grep -E "libx264|prores|nvenc". 하드웨어 인코더는 빠르지만 비트당 품질이 낮은 경우가 많습니다. 배송 전에 비교하세요.
7. 프레임, 연속 사진, GIF
# 특정 시간의 스틸 이미지
ffmpeg -ss 00:00:03.5 -i in.mp4 -frames:v 1 frame.png
# 연속 사진
ffmpeg -i in.mp4 -vf "fps=1,scale=320:-1,tile=4x4" -frames:v 1 sheet.jpg
# 고품질 GIF (palettegen/paletteuse)
ffmpeg -i in.mp4 -vf "fps=15,scale=640:-1:flags=lanczos,palettegen" palette.png
ffmpeg -i in.mp4 -i palette.png -lavfi "fps=15,scale=640:-1:flags=lanczos[x];[x][1:v]paletteuse" out.gif
8. 검증 (건너뛰지 마세요)
ffprobe -v error \
-show_entries format=duration,size,bit_rate,format_name \
-show_entries stream=index,codec_type,codec_name,width,height,r_frame_rate,avg_frame_rate,pix_fmt,channels,sample_rate,nb_frames \
-of json final.mp4
자동화된 검사:
# 검은색 구간 (실수로 인한 검은 프레임)
ffmpeg -i final.mp4 -vf "blackdetect=d=0.1:pix_th=0.05" -an -f null - 2>&1 | grep blackdetect || true
# 프리즈 구간 (정지 화면이 너무 긴 경우)
ffmpeg -i final.mp4 -vf "freezedetect=n=0.003:d=2" -an -f null - 2>&1 | grep freeze || true
# 오디오 존재 여부 및 피크 레벨
ffmpeg -i final.mp4 -af volumedetect -f null - 2>&1 | grep -E "mean_volume|max_volume"
그런 다음 첫 번째 프레임, 마지막 프레임, 각 전환 및 텍스트가 많은 프레임을 검사하세요. 숫자는 파일이 잘 구성되었음을 증명하지만, 올바르게 보인다는 것을 증명하지는 않습니다.
9. 프레임 정확도 함정
- VFR 소스 (화면 녹화, 휴대폰)는 CFR 타임라인과 드리프트합니다.
fps=30으로 정규화하고 지속 시간을 확인하세요. - 1 프레임 오차: 30fps에서 N 프레임은 정확히 N/30초입니다. 300프레임 렌더링은 10.0초입니다. 지속 시간이 9.97초라면 프레임이 드롭된 것입니다.
- 오디오가 비디오보다 짧음 → 끝에 무음; 의도적으로 패딩하거나 자르세요.
- 회전된 휴대폰 비디오는 디스플레이 매트릭스를 갖습니다. 정말 의도한 경우에만
-noautorotate를 사용하세요. - 인코딩 후 색상 변화:
color_primaries/transfer태그를 확인하세요. 브라우저 미리보기는 신뢰할 수 있는 색상 참조가 아닙니다. - 반복 인코딩은 품질을 저하시킵니다. 고품질 마스터를 유지하고 그로부터 인코딩하세요.
10. 전달 패키지
output/
final_1080p.mp4 # 기본
final_vertical.mp4 # 플랫폼 변형 (요청 시)
final_master.mov # ProRes/H.264 CRF 14, 향후 인코딩을 위해 보관
thumbnail.png # 1280x720 또는 1080x1920
contact-sheet.jpg
captions.srt
project-report.md # 도구 버전, 설정, 수행된 검사
ffprobe의 정확한 속성을 보고하세요. 절대 추측하지 마세요.