다음에 올 것을 만들어가는 기술에 대한 심층 기사.

FFmpeg 완벽 이해: 놓치고 있던 멘탈 모델

FFmpeg 명령어를 무작정 복사하지 마세요. 컨테이너, 코덱, 스트림, 필터의 원리와 실전에서 바로 쓰는 레시피를 정리했습니다.

필름, 오디오, 텍스트 스트림이 깔때기를 통과해 하나의 빛나는 컨테이너에 담기는 기계 일러스트.

우리 모두 겪어본 일이죠. 동영상을 변환해야 해서 구글에 검색하면, 누군가 키보드를 아무렇게나 두드린 것 같은 명령어가 적힌 Stack Overflow 답변이 나옵니다: ffmpeg -i input.mov -c:v libx264 -preset slow -crf 22 -c:a aac -b:a 128k -movflags +faststart output.mp4. 실행하면 돌아갑니다. 그런데 왜 되는지는 전혀 모르죠. 다음에 조금만 다른 작업이 필요해지면 또 Stack Overflow로 돌아갑니다. 저도 몇 년 동안 이렇게 지내다가, 결국 FFmpeg가 저에게 정확히 무엇을 원하는지 제대로 파악하려고 자리에 앉았습니다.

사실 FFmpeg는 그렇게 복잡하지 않습니다. 한번 이해하면 거의 모든 명령어가 읽히는 일관된 멘탈 모델이 있어요. 문제는 아무도 이 모델을 설명해 주지 않고 명령어만 던져준다는 점입니다. 그러니 이걸 제대로 짚어 보죠.

컨테이너 vs 코덱: 가장 먼저 이해해야 할 것

동영상 파일에서 가장 큰 혼란의 원인은 컨테이너와 코덱의 차이입니다. 컨테이너는 파일 형식입니다. .mp4, .mkv, .webm, .mov 같은 것들이죠. 내용물을 담는 상자라고 생각하면 됩니다. 코덱은 그 상자 안에 있는 실제 비디오와 오디오 데이터를 압축하는 방식입니다. H.264, H.265, VP9, AV1, AAC, Opus 같은 것들이 여기에 해당합니다.

MP4 파일에는 H.264 비디오와 AAC 오디오가 들어 있을 수도 있고, H.265 비디오와 Opus 오디오가 들어 있을 수도 있으며, 그 밖에도 수많은 조합이 가능합니다. MKV 파일에는 거의 무엇이든 담을 수 있고요. 누군가 'MP4로 변환하라'고 할 때는 보통 'H.264 비디오와 AAC 오디오를 MP4 컨테이너에 담으라'는 뜻입니다. 그런데 이건 사실 세 가지 별개의 결정이에요.

# See what's actually inside a file
$ ffprobe -hide_banner video.mkv
Input #0, matroska:
Stream #0:0: Video: hevc (Main), 1920x1080, 23.98 fps
Stream #0:1: Audio: opus, 48000 Hz, stereo
Stream #0:2: Subtitle: subrip
# This file is:
# Container: MKV (Matroska)
# Video codec: HEVC (H.265)
# Audio codec: Opus
# Plus a subtitle stream

무엇을 하기 전에 ffprobe부터 실행하세요. 지금 다루는 파일이 어떤 상태인지 정확히 알려줍니다. 절반의 경우 필요한 '변환'은 그냥 리먹싱, 즉 같은 스트림을 재인코딩 없이 다른 컨테이너로 옮기는 작업입니다. 그건 거의 순식간에 끝나요.

FFmpeg 명령어의 구조

모든 FFmpeg 명령어는 같은 패턴을 따르고, 한번 보고 나면 다시는 못 본 척할 수 없습니다:

ffmpeg [global options] [input options] -i input [output options] output
# The KEY insight: options apply to the NEXT file.
# Options before -i apply to the input.
# Options after -i (before the output filename) apply to the output.
# This matters! These two commands are different:
ffmpeg -ss 00:01:00 -i video.mp4 output.mp4   # Seeks BEFORE decoding (fast)
ffmpeg -i video.mp4 -ss 00:01:00 output.mp4   # Seeks AFTER decoding (slow, precise)

FFmpeg에서는 위치가 중요합니다. 모두가 걸려 넘어지는 부분이에요. 옵션의 의미는 -i 앞에 오느냐 뒤에 오느냐에 따라 달라질 수 있습니다. 위의 -ss 예시가 가장 흔한 경우입니다. -i 앞에 두면 FFmpeg는 입력 파일에서 바로 탐색합니다(빠르지만 정확하지 않을 수 있음). 뒤에 두면 그 지점까지 모두 디코딩한 뒤 출력을 시작합니다(느리지만 프레임 단위로 정확함).

스트림 선택: FFmpeg에게 원하는 것 알려주기

동영상 파일에는 여러 스트림이 들어 있는 경우가 많습니다. 비디오, 오디오, 자막, 때로는 언어별로 여러 오디오 트랙이 있기도 하죠. FFmpeg는 스트림 지정자 문법을 사용해 특정 스트림을 골라낼 수 있습니다:

  • -c:v — 비디오 스트림용 코덱 (v = video)
  • -c:a — 오디오 스트림용 코덱 (a = audio)
  • -c:s — 자막 스트림용 코덱 (s = subtitle)
  • -c copy — 재인코딩 없이 모든 스트림 복사
  • -c:v copy -c:a aac — 비디오는 그대로 복사하고 오디오는 AAC로 재인코딩
  • -map 0:a:1 — 첫 번째 입력의 두 번째 오디오 스트림 선택

-c copy 플래그는 가장 중요하게 알아둬야 할 옵션입니다. 재인코딩 없이 스트림 데이터를 그대로 복사하라는 뜻이에요. 거의 즉시 끝나고 무손실이며, 결국 데이터를 한 컨테이너에서 다른 컨테이너로 옮기는 것뿐입니다. 코덱을 바꿀 필요가 없다면 언제든 -c copy를 쓰세요.

# Remux MKV to MP4 without re-encoding (instant)
ffmpeg -i input.mkv -c copy output.mp4
# Copy video but re-encode audio (fast — only audio is processed)
ffmpeg -i input.mkv -c:v copy -c:a aac -b:a 192k output.mp4
# Extract just the audio stream
ffmpeg -i video.mp4 -vn -c:a copy audio.aac
# -vn means "no video" — drop the video stream entirely

실제로 쓰게 될 실전 레시피

제가 계속 쓰는 명령어들입니다. 각각 설명을 붙여 두었으니 필요에 맞게 고쳐 쓰시면 됩니다.

웹용 동영상 압축하기

ffmpeg -i raw_video.mov \
-c:v libx264 \
-preset slow \
-crf 23 \
-c:a aac -b:a 128k \
-movflags +faststart \
web_video.mp4
# -c:v libx264     → encode video with H.264
# -preset slow     → slower encoding = smaller file (options: ultrafast to veryslow)
# -crf 23          → quality level (18=near lossless, 23=default, 28=low quality)
# -c:a aac         → encode audio as AAC
# -b:a 128k        → audio bitrate 128 kbps
# -movflags +faststart → move metadata to start of file for streaming

CRF(Constant Rate Factor) 값은 가장 중요한 화질 조절 손잡이입니다. 숫자가 낮을수록 화질은 좋아지고 파일은 커집니다. 대부분의 웹 영상에는 CRF 20~25가 적당한 지점이에요. 저는 잘 보여야 하는 영상에는 보통 22를, 화질이 덜 중요한 배경용이나 보조 영상에는 26을 씁니다.

재인코딩 없이 동영상 자르기

# Extract from 1:30 to 3:45 — no re-encoding, nearly instant
ffmpeg -ss 00:01:30 -to 00:03:45 -i input.mp4 -c copy trimmed.mp4
# If you need frame-accurate cuts (slower):
ffmpeg -i input.mp4 -ss 00:01:30 -to 00:03:45 \
-c:v libx264 -crf 18 -c:a copy trimmed.mp4

썸네일 생성하기

# Single thumbnail at the 10-second mark
ffmpeg -ss 00:00:10 -i video.mp4 -frames:v 1 thumb.jpg
# One thumbnail every 30 seconds
ffmpeg -i video.mp4 -vf "fps=1/30" thumbs/thumb_%04d.jpg
# Resize to 320px wide, maintain aspect ratio
ffmpeg -ss 00:00:10 -i video.mp4 -frames:v 1 \
-vf "scale=320:-1" thumb_small.jpg

동영상 클립에서 GIF 만들기

# Good quality GIF with palette generation (two-pass)
ffmpeg -ss 00:00:05 -t 3 -i video.mp4 \
-vf "fps=15,scale=480:-1:flags=lanczos,split[s0][s1]; \
[s0]palettegen[p];[s1][p]paletteuse" \
output.gif
# -t 3              → duration: 3 seconds
# fps=15            → 15 frames per second (keep GIFs small)
# scale=480:-1      → resize to 480px wide
# palettegen/use    → generate optimal color palette (much better quality)

필터 이해하기: 진짜 강력한 기능

필터는 FFmpeg가 '유용한 도구'에서 '말도 안 되게 강력한 도구'로 바뀌는 지점입니다. -vf 플래그(비디오 필터)와 -af 플래그(오디오 필터)를 사용하면 처리 작업을 연결할 수 있습니다. 순차 처리는 쉼표로, 복합 필터 그래프는 세미콜론으로 구분합니다.

# Chain multiple filters: resize, then add padding for exact dimensions
ffmpeg -i input.mp4 -vf "scale=1280:720:force_original_aspect_ratio=decrease,\
pad=1280:720:(ow-iw)/2:(oh-ih)/2:black" output.mp4
# Normalize audio volume
ffmpeg -i input.mp4 -af "loudnorm" -c:v copy output.mp4
# Speed up a video 2x (with audio pitch correction)
ffmpeg -i input.mp4 -vf "setpts=0.5*PTS" -af "atempo=2.0" fast.mp4
# Add text overlay
ffmpeg -i input.mp4 \
-vf "drawtext=text='DRAFT':fontsize=72:fontcolor=red:x=10:y=10" \
watermarked.mp4

필터 시스템은 그 자체로 글 한 편을 채울 만큼 깊지만, 핵심은 이겁니다. 필터는 디코딩된 프레임을 처리합니다. 따라서 필터를 쓸 때는 -c copy를 사용할 수 없어요. 영상을 디코딩하고, 필터를 적용하고, 다시 인코딩해야 합니다. 명령어에 -vf가 들어간다면 전체 재인코딩에 걸릴 시간을 미리 감안하세요.

하드웨어 가속: GPU 활용하기

영상을 자주 인코딩한다면 하드웨어 가속으로 5~10배까지 빨라질 수 있습니다. 최신 GPU에는 FFmpeg가 활용할 수 있는 전용 인코딩 하드웨어가 들어 있어요.

# NVIDIA GPU (NVENC)
ffmpeg -i input.mp4 -c:v h264_nvenc -preset p4 -crf 23 output.mp4
# Apple Silicon (VideoToolbox)
ffmpeg -i input.mp4 -c:v h264_videotoolbox -b:v 5M output.mp4
# Intel Quick Sync (QSV)
ffmpeg -i input.mp4 -c:v h264_qsv -preset medium output.mp4
# Check what's available on your system:
ffmpeg -encoders 2>/dev/null | grep -E '(nvenc|videotoolbox|qsv|vaapi)'

한 가지 주의할 점이 있습니다. 하드웨어 인코더는 빠르지만, 같은 화질 기준으로 보면 소프트웨어 인코딩(libx264)보다 보통 파일이 조금 더 큽니다. 화질이 중요한 일회성 트랜스코딩이라면 여전히 소프트웨어 인코딩이 낫습니다. 일괄 처리나 실시간 스트리밍이라면 하드웨어 가속이 확실한 승자죠. 저는 최종 배포용 인코딩에는 libx264를, 나머지에는 NVENC를 씁니다.

흔한 실수와 피하는 방법

  • 필요 없는데 재인코딩하기. 단순히 자르기, 오디오 추출, 컨테이너 변경만 한다면 -c copy를 쓰세요. 30초를 잘라내려고 20분씩 재인코딩할 이유는 없습니다.
  • CRF 값을 너무 낮게 잡기. CRF 18은 대부분의 사람이 체감하지 못하는 화질 향상을 위해 파일을 엄청나게 키웁니다. 23에서 시작하고, 아티팩트가 보일 때만 더 낮추세요. 대부분의 웹 콘텐츠에는 23~25면 충분합니다.
  • -movflags +faststart를 빼먹기. 이 옵션이 없으면 MP4 메타데이터가 파일 끝에 놓여서, 브라우저가 전체 파일을 다 받아야 재생을 시작할 수 있습니다. 웹 영상이라면 항상 붙이세요.
  • 입력 파일을 확인하지 않기. 명령어를 쓰기 전에 ffprobe를 실행하세요. 입력의 코덱, 해상도, 스트림 구성을 알면 모든 것이 훨씬 쉬워집니다.
  • 멈춘 것처럼 보일 때 확인하지 않기. FFmpeg가 멈춘 것 같다면 기존 파일을 덮어쓸지 묻는 질문에 답을 기다리고 있을 수 있습니다. -y를 붙이면 자동으로 덮어쓰고, -n을 붙이면 절대 덮어쓰지 않습니다.

치트 시트

제가 데스크톱의 텍스트 파일에 보관해 두는 내용입니다. 일상에서 필요한 작업의 약 90%를 커버해요:

# Probe a file
ffprobe -hide_banner input.mp4
# Remux (change container, no re-encode)
ffmpeg -i input.mkv -c copy output.mp4
# Compress for web
ffmpeg -i input.mov -c:v libx264 -crf 23 -preset medium \
-c:a aac -b:a 128k -movflags +faststart output.mp4
# Extract audio only
ffmpeg -i video.mp4 -vn -c:a copy audio.m4a
# Trim without re-encoding
ffmpeg -ss 00:01:00 -to 00:02:30 -i input.mp4 -c copy clip.mp4
# Resize to 720p
ffmpeg -i input.mp4 -vf "scale=-1:720" -c:a copy output.mp4
# Convert to GIF
ffmpeg -ss 5 -t 3 -i input.mp4 \
-vf "fps=12,scale=400:-1" output.gif
# Concatenate files (same codec)
echo "file 'part1.mp4'" > list.txt
echo "file 'part2.mp4'" >> list.txt
ffmpeg -f concat -safe 0 -i list.txt -c copy combined.mp4

FFmpeg는 하루 만에 마스터할 수 있는 도구가 아닙니다. 하지만 멘탈 모델을 이해하고 나면, 즉 컨테이너는 스트림을 담고, 스트림에는 코덱이 있고, 옵션은 다음 파일에 적용되며, 필터는 재인코딩을 요구한다는 것을 알고 나면 문서가 이해되기 시작합니다. 명령어를 무작정 복사하는 대신 의도를 가지고 조합하게 되죠. 솔직히 '이건 마법 같아'에서 '아, 이게 이렇게 동작하는 거구나'로 넘어가는 순간은 개발자로서 누릴 수 있는 가장 뿌듯한 순간 중 하나입니다.