본문으로 건너뛰기
Previous
Next
FFmpeg 실전 가이드 | 동영상/오디오 처리

FFmpeg 실전 가이드 | 동영상/오디오 처리

FFmpeg 실전 가이드 | 동영상/오디오 처리

이 글의 핵심

FFmpeg 실전 가이드에 대해 정리한 개발 블로그 글입니다. > TL;DR: FFmpeg로 동영상/오디오를 자유자재로 다루는 방법을 배웁니다. 설치부터 변환, 인코딩, 스트리밍까지 실무에서 바로 쓸 수 있는 모든 것을 마스터합니다. 이 글을 읽으면: - ✅ FFmpeg 설치부터 기본… 개념과 예제 코드를 단계적으로 다루며, 실무·학습에 참고할 수 있도록 구성했습니다. 관련 키워드:…

🎯 이 글을 읽으면 (읽는 시간: 30분)

TL;DR: FFmpeg로 동영상/오디오를 자유자재로 다루는 방법을 배웁니다. 설치부터 변환, 인코딩, 스트리밍까지 실무에서 바로 쓸 수 있는 모든 것을 마스터합니다. 이 글을 읽으면:

  • ✅ FFmpeg 설치부터 기본 사용법까지 완벽 이해
  • ✅ 동영상 변환, 인코딩, 최적화 기법 실전 적용
  • ✅ HLS 스트리밍과 라이브 방송 구축 능력 습득
  • ✅ Python/Node.js에서 FFmpeg 프로그래밍 방식 활용 실무 활용:
  • 🔥 동영상 플랫폼 (YouTube, Netflix 스타일)
  • 🔥 라이브 스트리밍 서비스
  • 🔥 썸네일 자동 생성 시스템
  • 🔥 동영상 인코딩 파이프라인 난이도: 중급 | 실습 예제: 30개 | 프로덕션 레벨

이 글의 핵심

FFmpeg는 동영상과 오디오를 다루는 가장 강력한 오픈소스 도구입니다. 이 가이드는 설치부터 실전 활용까지, 실무에서 바로 써먹을 수 있는 패턴과 노하우를 담았습니다. 단순한 명령어 나열이 아닌, 왜 이렇게 쓰는지, 어떤 상황에서 쓰는지를 중심으로 정리했습니다.

사전 지식 (초보자를 위한 기초)

1. 동영상 파일 구조

동영상 파일을 이해하려면 컨테이너코덱의 차이를 알아야 합니다. 많은 사람들이 이 둘을 혼동하는데, 실제로는 완전히 다른 개념입니다.

동영상 파일 구조:
┌─────────────────────────────────┐
│   컨테이너 (Container)          │
│   예: MP4, MKV, AVI, MOV        │
│                                 │
│  ┌──────────────────────────┐  │
│  │  비디오 스트림           │  │
│  │  코덱: H.264, H.265      │  │
│  └──────────────────────────┘  │
│                                 │
│  ┌──────────────────────────┐  │
│  │  오디오 스트림           │  │
│  │  코덱: AAC, MP3          │  │
│  └──────────────────────────┘  │
│                                 │
│  ┌──────────────────────────┐  │
│  │  자막 스트림 (선택)      │  │
│  │  SRT, ASS                │  │
│  └──────────────────────────┘  │
└─────────────────────────────────┘

컨테이너 (Container):

  • 비디오, 오디오, 자막을 담는 “상자” 역할
  • 예: MP4, MKV, AVI, MOV, WebM
  • 같은 코덱이라도 컨테이너에 따라 호환성이 달라집니다
  • MP4는 가장 범용적이고, MKV는 기능이 많지만 일부 기기에서 재생 안 될 수 있습니다 코덱 (Codec):
  • 데이터를 압축/해제하는 “알고리즘”
  • 비디오: H.264 (가장 보편적), H.265 (고효율), VP9 (웹용), AV1 (차세대)
  • 오디오: AAC (범용), MP3 (레거시), Opus (고효율), FLAC (무손실)
  • 같은 화질이라도 코덱에 따라 파일 크기가 2배 이상 차이날 수 있습니다 비유로 이해하기:
  • 컨테이너 = 택배 상자
  • 코덱 = 물건을 압축하는 방법
  • 같은 물건(영상)이라도 어떻게 압축하고(코덱) 어떤 상자에 담느냐(컨테이너)에 따라 결과가 달라집니다

2. 비트레이트와 해상도

비트레이트 (Bitrate): 초당 전송되는 데이터 양입니다. 단위는 kbps(킬로비트), Mbps(메가비트)를 사용합니다.

  • 높은 비트레이트: 화질은 좋지만 파일 크기가 크고 스트리밍 시 버퍼링 발생 가능
  • 낮은 비트레이트: 파일은 작지만 화질 저하, 특히 빠른 움직임에서 블록 노이즈 발생
  • 적정 비트레이트: 해상도와 프레임레이트, 콘텐츠 특성에 따라 결정됩니다
비디오 비트레이트 가이드:
4K (3840×2160):  20-50 Mbps
1080p (1920×1080): 5-10 Mbps
720p (1280×720):   2.5-5 Mbps
480p (854×480):    1-2 Mbps
360p (640×360):    0.5-1 Mbps
오디오 비트레이트:
320 kbps: 고음질 (음악)
192 kbps: 표준 (음악)
128 kbps: 일반 (팟캐스트)
64 kbps: 저음질 (통화)

해상도 (Resolution): 가로×세로 픽셀 수입니다.

  • 4K (3840×2160): 영화관 수준, 파일 크기 매우 큼
  • 1080p (1920×1080): Full HD, 가장 보편적인 고화질
  • 720p (1280×720): HD, 모바일이나 웹 스트리밍에 적합
  • 480p (854×480): SD, 저사양 기기나 느린 네트워크용 실전 팁: 같은 비트레이트라도 해상도가 높으면 화질이 떨어집니다. 4K를 5Mbps로 인코딩하는 것보다 1080p를 5Mbps로 인코딩하는 게 훨씬 깨끗합니다.

3. 프레임레이트

프레임레이트 (Frame Rate, FPS): 초당 몇 장의 화면을 보여주는지를 나타냅니다. 단위는 fps(frames per second)입니다. 프레임레이트가 높을수록 움직임이 부드럽지만, 파일 크기도 비례해서 커집니다. 콘텐츠 특성에 맞는 선택이 중요합니다.

일반적인 프레임레이트:
24 fps: 영화 (시네마틱 느낌, 영화관 표준)
30 fps: TV, 유튜브 (일반 영상, 가장 보편적)
60 fps: 게임, 스포츠 (부드러운 움직임, 액션 장면)
120 fps: 슬로우 모션 촬영용 (편집 시 24fps나 30fps로 느리게 재생)
실전 선택 가이드:
- 브이로그, 강의: 30fps (파일 크기와 품질 균형)
- 게임 플레이, 스포츠: 60fps (빠른 움직임 표현)
- 영화 느낌: 24fps (시네마틱 무드)
- 슬로우 모션 필요: 120fps 이상 촬영 후 편집

4. 코덱 선택 가이드

코덱 선택은 호환성, 파일 크기, 인코딩 속도, 화질의 균형을 맞추는 작업입니다. 상황별로 최적의 선택이 다릅니다.

비디오 코덱 비교:
H.264 (AVC) - 가장 안전한 선택
  ✅ 장점: 거의 모든 기기 지원, 빠른 인코딩, 하드웨어 가속 지원
  ❌ 단점: H.265 대비 파일 크기 큼
  📌 추천: 일반 용도, 최대 호환성 필요, 빠른 처리 필요
  
H.265 (HEVC) - 용량 최적화
  ✅ 장점: H.264 대비 40-50% 작은 파일, 4K에 적합
  ❌ 단점: 느린 인코딩, 구형 기기 미지원, 라이선스 비용
  📌 추천: 4K 영상, 저장 공간 부족, 최신 기기 타겟
  
VP9 - 웹 스트리밍용
  ✅ 장점: 로열티 프리, YouTube 지원, 웹 브라우저 호환
  ❌ 단점: 매우 느린 인코딩, 하드웨어 지원 제한적
  📌 추천: YouTube 업로드, 웹 전용 콘텐츠
  
AV1 - 미래 대비
  ✅ 장점: 최고의 압축 효율, 로열티 프리
  ❌ 단점: 극도로 느린 인코딩, 제한적 재생 지원
  📌 추천: 장기 보관용, 실험적 프로젝트
오디오 코덱 비교:
AAC - 범용 표준
  ✅ 장점: 우수한 음질/용량 비율, 광범위한 지원
  ❌ 단점: MP3보다 약간 느린 인코딩
  📌 추천: 대부분의 경우 (기본 선택)
  
MP3 - 레거시 호환
  ✅ 장점: 100% 호환성, 빠른 인코딩
  ❌ 단점: AAC 대비 큰 파일 또는 낮은 음질
  📌 추천: 구형 기기 지원 필수, 팟캐스트
  
Opus - 고효율
  ✅ 장점: 최고의 음질/용량 비율, 로열티 프리
  ❌ 단점: 제한적 기기 지원
  📌 추천: 웹 스트리밍, 음성 통화, Discord
FLAC - 무손실
  ✅ 장점: 완벽한 음질 보존, 압축 지원
  ❌ 단점: 큰 파일 크기
  📌 추천: 음악 아카이빙, 마스터링
실전 의사결정 트리:
1. 최대 호환성 필요? → H.264 + AAC
2. 파일 크기 최소화? → H.265 + AAC (또는 Opus)
3. YouTube 업로드? → VP9 + Opus (또는 H.264 + AAC)
4. 4K 영상? → H.265 + AAC
5. 웹 전용? → VP9 + Opus
6. 음악/오디오 보관? → FLAC

1. FFmpeg이란?

FFmpeg는 동영상과 오디오를 다루는 가장 강력한 오픈소스 멀티미디어 프레임워크입니다. 1999년부터 개발되어 현재까지 업계 표준으로 자리잡았으며, YouTube, Netflix, VLC 등 수많은 서비스와 프로그램이 FFmpeg를 기반으로 동작합니다.

주요 기능

FFmpeg로 할 수 있는 작업은 거의 무한합니다: 기본 기능:

  • 동영상/오디오 포맷 변환 (MP4 ↔ AVI ↔ MKV 등)
  • 인코딩/디코딩 (압축 및 압축 해제)
  • 해상도 변경 및 품질 조정
  • 썸네일 및 스크린샷 추출 고급 기능:
  • 실시간 스트리밍 (RTMP, HLS, DASH)
  • 필터 적용 (자르기, 회전, 워터마크, 색보정)
  • 오디오 처리 (노이즈 제거, 볼륨 조절, 믹싱)
  • 화면 녹화 및 웹캠 캡처
  • 자막 추가 및 변환
  • 여러 파일 병합 및 분할 실무 활용 예시:
  • 유튜브 업로드용 영상 최적화
  • 웹사이트용 다중 해상도 영상 생성
  • 라이브 스트리밍 서버 구축
  • 동영상 자동 처리 파이프라인 구축
  • 모바일 앱용 영상 변환

FFmpeg 구성 요소

FFmpeg는 여러 도구와 라이브러리로 구성되어 있습니다. 각각의 역할을 이해하면 더 효과적으로 활용할 수 있습니다.

명령줄 도구:
ffmpeg
  - 가장 많이 쓰는 핵심 도구
  - 파일 변환, 인코딩, 필터 적용 등 모든 작업 수행
  - 예: ffmpeg -i input.mp4 output.avi
ffprobe
  - 미디어 파일 정보 분석 도구
  - 코덱, 해상도, 비트레이트, 길이 등 메타데이터 추출
  - 스크립트에서 파일 정보 확인 시 필수
  - 예: ffprobe -show_format input.mp4
ffplay
  - 간단한 미디어 재생기
  - 인코딩 결과 빠르게 확인할 때 유용
  - 테스트 및 디버깅용
  - 예: ffplay output.mp4
라이브러리 (프로그래밍 시 사용):
libavcodec
  - 코덱 인코딩/디코딩 라이브러리
  - H.264, H.265, AAC, MP3 등 수백 개 코덱 지원
  - 직접 코딩할 때 가장 핵심적인 라이브러리
libavformat
  - 컨테이너 포맷 처리 라이브러리
  - MP4, MKV, AVI 등 파일 읽기/쓰기
  - 스트리밍 프로토콜 지원 (RTMP, HLS 등)
libavfilter
  - 필터 그래프 처리 라이브러리
  - 영상/오디오 효과 적용
  - 크기 조정, 자르기, 워터마크, 색보정 등
libavutil
  - 공통 유틸리티 라이브러리
  - 수학 함수, 메모리 관리, 로깅 등
  - 다른 라이브러리들의 기반
libswscale
  - 이미지 스케일링 및 색공간 변환
  - 해상도 변경, RGB ↔ YUV 변환
  - 고품질 리사이징 알고리즘 제공
libswresample
  - 오디오 리샘플링 라이브러리
  - 샘플레이트 변환, 채널 믹싱
  - 스테레오 ↔ 모노 변환 등
실전 팁:
- 일반 사용자: ffmpeg 명령어만 알면 충분
- 개발자: Python/Node.js 바인딩 사용 (ffmpeg-python, fluent-ffmpeg)
- 고급 개발자: libav 라이브러리 직접 사용 (C/C++)

2. 설치 및 기본 사용법

설치

macOS:

brew install ffmpeg
# 모든 옵션 포함
brew install ffmpeg --with-fdk-aac --with-libvpx --with-libvorbis

Ubuntu/Debian:

sudo apt update
sudo apt install ffmpeg
# 최신 버전 (PPA)
sudo add-apt-repository ppa:savoury1/ffmpeg4
sudo apt update
sudo apt install ffmpeg

Windows:

# Chocolatey
choco install ffmpeg
# 또는 수동 설치
# 1. https://ffmpeg.org/download.html
# 2. 압축 해제
# 3. 환경 변수 PATH에 bin 폴더 추가

Docker:

docker pull linuxserver/ffmpeg
docker run --rm -v $(pwd):/data linuxserver/ffmpeg \
  -i /data/input.mp4 -c:v libx264 /data/output.mp4

버전 확인

ffmpeg -version
# 출력:
# ffmpeg version 6.1.1
# configuration: --enable-gpl --enable-libx264 --enable-libx265 ...
# libavcodec     60.31.102
# libavformat    60.16.100
# ...

기본 명령어 구조 - FFmpeg의 실행 원리

FFmpeg 명령어는 일정한 패턴을 따릅니다. 이 구조를 이해하면 복잡한 명령어도 쉽게 만들 수 있습니다.

ffmpeg [전역 옵션] [입력 옵션] -i [입력 파일] [출력 옵션] [출력 파일]

# 기본 예시
ffmpeg -i input.mp4 -c:v libx264 -crf 23 output.mp4
#      ↑           ↑              ↑          ↑
#      입력        비디오 코덱    품질       출력

# 더 복잡한 예시
ffmpeg -y -i input.mp4 -i audio.mp3 -c:v libx264 -crf 20 -c:a aac -b:a 192k -shortest output.mp4
#      ↑  ↑           ↑             ↑              ↑      ↑          ↑         ↑         ↑
#      덮어쓰기  비디오입력  오디오입력  비디오코덱  비디오품질  오디오코덱  오디오비트  짧은쪽맞춤  출력

FFmpeg의 내부 처리 파이프라인 (상세):

ffmpeg -i input.mp4 -c:v libx264 -crf 23 -vf scale=1280:720 output.mp4

1. Demuxing (디먹싱) - libavformat
   ┌────────────────────────────────┐
   │   input.mp4 Container          │
   │   ┌──────────────────────────┐ │
   │   │ Video Stream #0 (H.264)  │ │ → Packet Queue
   │   │ - Codec: H.264           │ │
   │   │ - Bitrate: 5 Mbps        │ │
   │   │ - Resolution: 1920x1080  │ │
   │   └──────────────────────────┘ │
   │   ┌──────────────────────────┐ │
   │   │ Audio Stream #1 (AAC)    │ │ → Packet Queue
   │   │ - Codec: AAC             │ │
   │   │ - Bitrate: 192 kbps      │ │
   │   │ - Sample Rate: 48000 Hz  │ │
   │   └──────────────────────────┘ │
   │   ┌──────────────────────────┐ │
   │   │ Metadata                 │ │
   │   │ - Duration: 00:05:30     │ │
   │   │ - Creation Time          │ │
   │   └──────────────────────────┘ │
   └────────────────────────────────┘

   Demuxer 동작:
   - MP4 atom 파싱 (moov, mdat, ftyp)
   - Stream 정보 추출
   - Packet 단위로 데이터 읽기
   - Seek 테이블 생성

2. Decoding (디코딩) - libavcodec
   Compressed Packet → Raw Frame
   
   Video Decoding:
   [H.264 Packet: 50KB] → libavcodec/h264 decoder
   
   내부 동작:
   1) NAL Unit 분리
   2) SPS/PPS 파싱 (해상도, 프로파일)
   3) Slice 디코딩
   4) Reference Frame 관리
   5) Deblocking Filter 적용
   
   결과: [YUV420p Frame: 3MB]
   - Y plane: 1920x1080 (luma)
   - U plane: 960x540 (chroma)
   - V plane: 960x540 (chroma)
   
   Audio Decoding:
   [AAC Packet: 5KB] → libavcodec/aac decoder
   
   내부 동작:
   1) ADTS Header 파싱
   2) MDCT (Modified DCT)
   3) Psychoacoustic 모델 적용
   
   결과: [PCM Samples: 96KB]
   - Format: Float32
   - Channels: 2 (Stereo)
   - Samples: 1024 per channel

3. Filtering (필터링) - libavfilter
   Raw Frame → Filter Graph → Processed Frame
   
   -vf scale=1280:720 실행:
   
   Filter Graph:
   [in] → scale → [out]
   
   scale 필터 내부:
   1) swscale (libswscale) 호출
   2) 리샘플링 알고리즘 선택:
      - Bilinear (빠름)
      - Bicubic (균형)
      - Lanczos (고품질)
   3) Y/U/V plane 개별 스케일링:
      Y: 1920x1080 → 1280x720
      U: 960x540 → 640x360
      V: 960x540 → 640x360
   
   결과: [YUV420p Frame: 1.3MB]
   
   복잡한 Filter Graph 예시:
   -vf "scale=1280:720,fps=30,eq=brightness=0.1"
   
   [in] → scale → fps → eq → [out]
          ↓       ↓     ↓
      swscale  frame  color
              drop   adjust

4. Encoding (인코딩) - libavcodec
   Raw Frame → Compressed Packet
   
   libx264 인코딩 과정:
   
   1) Macroblock 분할 (16x16):
      1280x720 → 80x45 macroblocks
   
   2) Motion Estimation:
      현재 프레임과 Reference Frame 비교
      Motion Vector 계산
      → P-frame, B-frame 생성
   
   3) Transform (DCT):
      Spatial domain → Frequency domain
      고주파 성분 제거 (손실 압축)
   
   4) Quantization:
      CRF 23 적용
      → Quantization Parameter (QP) 계산
      → 세부 정보 손실 (압축률 조정)
   
   5) Entropy Coding (CABAC):
      비트스트림 최종 압축
      Huffman coding 기반
   
   6) Deblocking Filter:
      블록 노이즈 제거
      
   결과: [H.264 Packet: 25KB]
   - I-frame: 200KB (1초당 1개)
   - P-frame: 30KB (대부분)
   - B-frame: 10KB (optional)
   
   Audio Encoding (AAC):
   [PCM Samples: 96KB] → libavcodec/aac
   
   1) MDCT (1024 samples)
   2) Psychoacoustic 모델
      → 사람이 못 듣는 주파수 제거
   3) Quantization
   4) Huffman Coding
   
   결과: [AAC Packet: 3KB]
   - 압축률: 32:1 (96KB → 3KB)

5. Muxing (먹싱) - libavformat
   Compressed Packets → Container
   
   MP4 Muxer 동작:
   
   1) Container 헤더 작성:
      ftyp: 파일 타입
      moov: 메타데이터
      - mvhd: 영상 정보
      - trak: 트랙 정보
        - tkhd: 트랙 헤더
        - mdia: 미디어 정보
   
   2) 데이터 인터리빙:
      Video Packet + Audio Packet 교차 배치
      
      Timeline:
      [V0] [A0] [V1] [A1] [V2] [A2] ...
      
      이유: 순차 재생 최적화
   
   3) mdat Atom 작성:
      실제 압축 데이터 저장
   
   4) Index 생성 (stco, stsc, stsz):
      Seek 테이블
      → 빠른 탐색 가능
   
   5) -movflags +faststart 옵션:
      moov를 파일 앞으로 이동
      → 웹 스트리밍 즉시 시작 가능
   
   최종 output.mp4 구조:
   ┌───────────────┐
   │ ftyp (16B)    │
   ├───────────────┤
   │ moov (5KB)    │ ← faststart: 앞으로
   ├───────────────┤
   │ mdat (50MB)   │ ← 실제 데이터
   └───────────────┘

전체 파이프라인 메모리 사용:
- Demuxer Buffer: ~15MB
- Decoded Frames: ~10MB (2-3 프레임)
- Filter Buffer: ~5MB
- Encoder Buffer: ~20MB (Reference Frames)
- Muxer Buffer: ~5MB
총: ~55MB (1080p 기준)

병렬 처리:
-threads 4 사용 시:
Thread 1: Frame 1 → Decode → Filter → Encode
Thread 2: Frame 2 → Decode → Filter → Encode
Thread 3: Frame 3 → Decode → Filter → Encode
Thread 4: Frame 4 → Decode → Filter → Encode

→ 처리 속도: ~3-4배 향상

명령어 옵션의 위치가 중요한 이유:

# ❌ 잘못된 예: 입력 옵션을 -i 뒤에
ffmpeg -i input.mp4 -ss 00:01:00 -t 10 output.mp4
# 동작:
# 1. 전체 파일을 디코딩 (1분까지)
# 2. 1분부터 10초만 인코딩
# 문제: 불필요한 디코딩 (느림)

# ✅ 올바른 예: 입력 옵션을 -i 앞에
ffmpeg -ss 00:01:00 -t 10 -i input.mp4 output.mp4
# 동작:
# 1. 파일을 1분 위치로 seek (키프레임까지)
# 2. 10초만 디코딩
# 3. 인코딩
# 장점: 매우 빠름 (seek은 디코딩 불필요)

# 성능 차이:
# -i 뒤: 60초 소요 (1분까지 디코딩)
# -i 앞: 2초 소요 (seek + 10초 디코딩)

-c:v와 -c:a의 의미:

# -c:v libx264
# -c: codec (코덱 지정)
# :v: video stream (비디오 스트림)
# libx264: H.264 인코더 라이브러리

# -c:a aac
# :a: audio stream (오디오 스트림)

# 모든 스트림에 적용
-c copy  # 비디오 + 오디오 모두 복사

# 개별 지정
-c:v copy -c:a aac  # 비디오는 복사, 오디오만 재인코딩

# 여러 출력 파일
ffmpeg -i input.mp4 \
  -c:v libx264 -crf 23 1080p.mp4 \
  -c:v libx264 -crf 28 -vf scale=1280:720 720p.mp4
# 한 번의 디코딩으로 여러 해상도 생성 (효율적!)

-map의 강력한 기능:

# input.mp4: 비디오 2개 + 오디오 3개 (다국어)
ffprobe input.mp4
# Stream #0:0: Video (비디오 - 주 화면)
# Stream #0:1: Video (비디오 - 주석 화면)
# Stream #0:2: Audio (한국어)
# Stream #0:3: Audio (영어)
# Stream #0:4: Audio (일본어)

# 기본 동작 (자동 선택): 첫 번째 비디오 + 첫 번째 오디오
ffmpeg -i input.mp4 output.mp4
# Stream #0:0 (비디오) + Stream #0:2 (한국어)

# 수동 선택: 주 화면 + 영어 오디오
ffmpeg -i input.mp4 -map 0:0 -map 0:3 -c copy english.mp4
# -map 0:0: 입력 0번의 스트림 0번 (비디오)
# -map 0:3: 입력 0번의 스트림 3번 (영어 오디오)

# 여러 오디오 트랙 포함
ffmpeg -i input.mp4 -map 0:0 -map 0:2 -map 0:3 -c copy multi_audio.mp4
# 비디오 1개 + 오디오 2개 (한국어, 영어)

# 실전 예제: 비디오와 외부 오디오 결합
ffmpeg -i video.mp4 -i audio.mp3 -map 0:v -map 1:a -c copy output.mp4
# -map 0:v: 첫 번째 입력의 모든 비디오 스트림
# -map 1:a: 두 번째 입력의 모든 오디오 스트림

주요 옵션 설명:

# 전역 옵션 (입력 파일 앞에 위치)
-y              # 출력 파일 덮어쓰기 (확인 없이)
-n              # 출력 파일 덮어쓰기 금지
-v quiet        # 로그 출력 최소화
-stats          # 진행 상황 표시
# 입력 옵션 (-i 앞에 위치)
-ss 00:01:30    # 시작 시간 (1분 30초부터)
-t 10           # 지속 시간 (10초만)
-to 00:02:00    # 종료 시간 (2분까지)
# 비디오 출력 옵션
-c:v libx264    # 비디오 코덱 (H.264)
-c:v copy       # 비디오 재인코딩 없이 복사
-crf 23         # 품질 (0=최고, 51=최저, 23=권장)
-preset slow    # 인코딩 속도 (slow=고품질)
-b:v 5M         # 비디오 비트레이트 (5Mbps)
-r 30           # 프레임레이트 (30fps)
-vf scale=1280:720  # 비디오 필터 (해상도 변경)
# 오디오 출력 옵션
-c:a aac        # 오디오 코덱 (AAC)
-c:a copy       # 오디오 재인코딩 없이 복사
-b:a 192k       # 오디오 비트레이트 (192kbps)
-ar 48000       # 샘플레이트 (48kHz)
-ac 2           # 채널 수 (2=스테레오)
# 기타 유용한 옵션
-an             # 오디오 제거
-vn             # 비디오 제거
-shortest       # 가장 짧은 스트림에 맞춤
-map 0:v:0      # 첫 번째 비디오 스트림 선택
-map 0:a:1      # 두 번째 오디오 스트림 선택

실전 팁:

  1. 옵션 순서가 중요합니다
    • 입력 옵션은 -i 앞에
    • 출력 옵션은 -i 뒤, 출력 파일 앞에
  2. -c:v와 -vcodec은 같습니다
    • -c:v libx264 = -vcodec libx264
    • -c:a aac = -acodec aac
  3. copy는 재인코딩을 건너뜁니다
    • 매우 빠르지만 품질 조정 불가
    • 컨테이너만 바꿀 때 유용
  4. 여러 입력 파일 사용 가능
    ffmpeg -i video.mp4 -i audio.mp3 -c copy output.mp4
  5. 진행 상황 확인
    ffmpeg -i input.mp4 -c:v libx264 output.mp4
    # 실시간으로 frame=, fps=, time= 등이 표시됩니다

3. 동영상 변환 및 인코딩

동영상 변환은 FFmpeg의 가장 기본적이면서도 강력한 기능입니다. 단순 포맷 변환부터 고급 인코딩까지 다룹니다.

기본 변환

가장 간단한 형태의 변환입니다. FFmpeg가 자동으로 적절한 코덱을 선택합니다.

# MP4 → AVI (자동 코덱 선택)
ffmpeg -i input.mp4 output.avi
# MOV → MP4 (자동 코덱 선택)
ffmpeg -i input.mov output.mp4
# MKV → MP4 (재인코딩 없이 복사 - 가장 빠름)
ffmpeg -i input.mkv -c copy output.mp4
# -c copy: 비디오와 오디오를 재인코딩 없이 그대로 복사
# 장점: 매우 빠름 (1분 영상을 1초 만에 변환), 품질 손실 없음
# 단점: 해상도/품질 조정 불가, 코덱이 컨테이너와 호환되어야 함
# 비디오만 복사, 오디오는 재인코딩
ffmpeg -i input.mkv -c:v copy -c:a aac output.mp4
# MKV의 비디오는 그대로 두고 오디오만 AAC로 변환
# 실전 예시: iPhone 촬영 영상(MOV)을 웹용 MP4로
ffmpeg -i iphone_video.mov -c:v libx264 -crf 23 -c:a aac -b:a 128k web_video.mp4

언제 -c copy를 쓸까?

  • 컨테이너만 바꾸고 싶을 때 (MKV → MP4)
  • 영상 자르기만 할 때 (품질 유지)
  • 빠른 처리가 필요할 때 언제 재인코딩이 필요할까?
  • 해상도를 바꿀 때
  • 파일 크기를 줄일 때
  • 호환성 문제가 있을 때 (예: HEVC → H.264)
  • 필터를 적용할 때 (워터마크, 색보정 등)

H.264 인코딩

H.264는 가장 보편적인 비디오 코덱입니다. 거의 모든 기기에서 재생되며, 품질과 파일 크기의 균형이 우수합니다. CRF (Constant Rate Factor) 방식 - 권장 CRF는 일정한 품질을 유지하면서 인코딩하는 방식입니다. 파일 크기는 영상의 복잡도에 따라 자동으로 조절됩니다.

# 기본 CRF 인코딩
ffmpeg -i input.mp4 -c:v libx264 -crf 23 output.mp4
# CRF 값 가이드:
# 0  = 무손실 (파일 크기 매우 큼, 거의 사용 안 함)
# 17-18 = 시각적으로 무손실 (전문가용, 편집 소스)
# 19-23 = 고품질 (일반 용도, YouTube 권장)
# 24-28 = 중간 품질 (웹 스트리밍, 모바일)
# 29-35 = 낮은 품질 (저사양 기기, 프리뷰용)
# 36-51 = 매우 낮은 품질 (거의 사용 안 함)
# 실전 예시들:
# YouTube 업로드용 (고품질)
ffmpeg -i input.mp4 -c:v libx264 -crf 18 -preset slow -c:a aac -b:a 192k youtube.mp4
# 웹사이트 배경 영상 (중간 품질, 작은 파일)
ffmpeg -i input.mp4 -c:v libx264 -crf 28 -preset fast -c:a aac -b:a 128k web_bg.mp4
# 모바일 앱용 (균형잡힌 품질)
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -preset medium -vf scale=1280:720 -c:a aac -b:a 128k mobile.mp4
# 아카이브용 (최고 품질)
ffmpeg -i input.mp4 -c:v libx264 -crf 17 -preset veryslow -c:a aac -b:a 320k archive.mp4

CRF 선택 가이드:

용도CRF 값설명
편집 소스17-18재편집 시 품질 손실 최소화
YouTube/Vimeo18-20플랫폼 재인코딩 대비
일반 공유21-23대부분의 경우 적합
웹 스트리밍24-26빠른 로딩과 품질 균형
모바일26-28작은 화면, 데이터 절약
프리뷰/테스트28-30빠른 확인용
주의사항:
  • CRF 값이 낮을수록 품질은 좋지만 파일 크기가 커집니다
  • 같은 CRF라도 영상의 복잡도에 따라 파일 크기가 다릅니다
    • 정적인 장면: 작은 파일
    • 빠른 움직임, 복잡한 장면: 큰 파일 프리셋 (Preset) - 속도 vs 압축률 프리셋은 인코딩 속도와 압축 효율의 균형을 조절합니다. 느린 프리셋일수록 같은 품질에서 파일 크기가 작아지지만, 인코딩 시간이 오래 걸립니다.
# 프리셋 기본 사용법
ffmpeg -i input.mp4 -c:v libx264 -preset slow -crf 23 output.mp4
# 프리셋 종류 (빠름 → 느림):
ultrafast   # 인코딩: 초고속 (실시간 가능), 파일: 매우 큼
superfast   # 인코딩: 매우 빠름, 파일: 큼
veryfast    # 인코딩: 빠름, 파일: 약간 큼
faster      # 인코딩: 빠른 편, 파일: 보통
fast        # 인코딩: 보통, 파일: 보통
medium      # 인코딩: 보통 (기본값), 파일: 보통
slow        # 인코딩: 느림, 파일: 작음 (★ 권장)
slower      # 인코딩: 매우 느림, 파일: 매우 작음
veryslow    # 인코딩: 극도로 느림, 파일: 최소
# 실전 예시:
# 빠른 테스트용 (품질 확인)
ffmpeg -i input.mp4 -c:v libx264 -preset ultrafast -crf 23 test.mp4
# 1080p 1분 영상: 약 5초 소요
# 일반 용도 (균형)
ffmpeg -i input.mp4 -c:v libx264 -preset medium -crf 23 output.mp4
# 1080p 1분 영상: 약 30초 소요
# 최종 배포용 (최적 품질)
ffmpeg -i input.mp4 -c:v libx264 -preset slow -crf 20 final.mp4
# 1080p 1분 영상: 약 1-2분 소요
# 아카이브용 (최대 압축)
ffmpeg -i input.mp4 -c:v libx264 -preset veryslow -crf 18 archive.mp4
# 1080p 1분 영상: 약 5-10분 소요

프리셋 선택 가이드:

상황추천 프리셋이유
실시간 스트리밍ultrafast, veryfastCPU 부하 최소화
빠른 프리뷰veryfast, fast빠른 확인
일반 작업medium기본 균형
최종 배포slow최적 품질/크기 비율
아카이브slower, veryslow장기 보관용 최소 크기
배치 처리fast, medium대량 파일 처리
성능 비교 (1080p 1분 영상 기준):
프리셋      인코딩 시간    파일 크기    품질 (CRF 23)
ultrafast   5초           50MB        보통
veryfast    10초          40MB        좋음
medium      30초          30MB        매우 좋음
slow        1-2분         25MB        최고
veryslow    5-10분        23MB        최고+
실제 시간은 CPU 성능에 따라 다릅니다.

실전 팁:

  1. 개발/테스트 단계: veryfast 사용
    • 빠른 피드백으로 효율적인 작업
  2. 최종 배포: slow 사용
    • 시간을 들여도 최적 결과 확보
  3. 대량 처리: medium 사용
    • 시간과 품질의 균형
  4. 프리셋과 CRF 조합:
    # 빠르게 + 높은 품질 = 큰 파일
    ffmpeg -i input.mp4 -preset veryfast -crf 18 output.mp4
    
    # 느리게 + 낮은 품질 = 작은 파일
    ffmpeg -i input.mp4 -preset slow -crf 28 output.mp4
    
    # 권장 조합 (일반 용도)
    ffmpeg -i input.mp4 -preset slow -crf 22 output.mp4

Preset이 내부적으로 바꾸는 파라미터:

Preset은 libx264의 수십 개 파라미터를 한 번에 조정합니다.

주요 파라미터 비교:

파라미터                ultrafast    medium      slow        veryslow
────────────────────────────────────────────────────────────────────
ref (참조 프레임)       1            2           5           16
me (움직임 탐색)        dia          hex         umh         tesa
subme (서브픽셀)        1            6           8           11
me_range               16           16          16          24
b-adapt (B프레임)       0            1           2           2
rc_lookahead           10           40          50          60
trellis (최적화)        0            1           2           2
partitions             none         some        most        all
8x8dct                 no           yes         yes         yes
direct               spatial      spatial     auto        auto
deblock              yes(α=0)     yes         yes         yes
weightp                0            2           2           2
weightb                no           no          yes         yes
mixed_refs             no           yes         yes         yes
──────────────────────────────────────────────────────────────────

각 파라미터 의미:

1. ref (참조 프레임 수):
   - 이전 프레임을 몇 개까지 참조할지
   - 많을수록 압축률 향상, 메모리 증가
   - ultrafast: 1개 (직전 프레임만)
   - slow: 5개
   - veryslow: 16개 (최대)
   
   실제 동작:
   현재 프레임의 16x16 블록을:
   - ref=1: 직전 1개 프레임에서 탐색
   - ref=5: 최근 5개 프레임에서 최적 매칭 찾기
   - ref=16: 최근 16개 프레임에서 최적 매칭 찾기
   
   효과:
   - 카메라 팬/틸트: 높은 ref에서 큰 효과
   - 정적 장면: ref 증가 효과 미미
   - 메모리: ref × 해상도 × 1.5 (YUV420p)

2. me (Motion Estimation - 움직임 추정):
   알고리즘 순서 (빠름 → 느림 → 정확):
   
   dia (Diamond):
     ┌───┬───┬───┐
     │   │ ● │   │  4방향만 탐색
     ├───┼───┼───┤
     │ ● │ X │ ● │  X = 현재 위치
     ├───┼───┼───┤
     │   │ ● │   │
     └───┴───┴───┘
     
   hex (Hexagon):
     ┌───┬───┬───┐
     │ ● │   │ ● │  6방향 탐색
     ├───┼───┼───┤
     │ ● │ X │ ● │  더 넓은 범위
     ├───┼───┼───┤
     │ ● │   │ ● │
     └───┴───┴───┘
     
   umh (Uneven Multi-Hexagon):
     나선형 + 다각형 + 전역 탐색
     시작점 주변 정밀 탐색 후
     점점 범위 확대
     
   tesa (Transformed Exhaustive Search Algorithm):
     모든 가능한 위치 철저히 탐색
     가장 느리지만 가장 정확
   
   탐색 범위:
   - me_range: 탐색할 최대 픽셀 거리
   - ultrafast: 16px (빠른 움직임 놓칠 수 있음)
   - slow/veryslow: 24px (빠른 움직임도 캐치)

3. subme (Subpixel Motion Estimation):
   레벨 0-11:
   
   subme=1 (ultrafast):
     정수 픽셀만 탐색
     16x16 블록 단위
     
   subme=6 (medium):
     1/2 픽셀 정밀도
     8x8, 4x4 블록도 분석
     
   subme=8 (slow):
     1/4 픽셀 정밀도
     RD (Rate-Distortion) 최적화
     
   subme=11 (veryslow):
     1/8 픽셀 정밀도
     완전 RD 최적화
     모든 블록 크기 (16x16, 8x8, 4x4, 8x4, 4x8)
   
   효과:
   - 낮음: 블록 경계 눈에 띔 (blocky)
   - 높음: 부드러운 움직임, 작은 파일

4. b-adapt (B프레임 적응 모드):
   B프레임: 양방향 예측 프레임
   (과거 + 미래 프레임 참조)
   
   b-adapt=0 (ultrafast):
     B프레임 사용 안 함
     I-P-P-P-P... 구조만
     
   b-adapt=1 (medium):
     빠른 휴리스틱 알고리즘
     장면 변화 감지 후 B프레임 배치
     
   b-adapt=2 (slow/veryslow):
     최적 B프레임 위치 계산
     lookahead buffer 사용
     
   예시:
   b-adapt=0: I P P P P P (25% 압축)
   b-adapt=2: I B B P B B P (40% 압축)
   
   I = Intra (독립 프레임)
   P = Predictive (이전 참조)
   B = Bi-directional (양방향 참조)

5. rc_lookahead (Rate Control Lookahead):
   미래 프레임을 몇 개 미리 볼지
   
   lookahead=10 (ultrafast):
     10프레임 미리 분석
     장면 전환 감지 제한적
     
   lookahead=60 (veryslow):
     60프레임 (2초 @ 30fps) 미리 분석
     장면 전환 완벽 감지
     최적 비트 할당
   
   효과:
   - 높음: 장면 전환 전 비트 절약
   - 높음: 복잡한 장면에 비트 집중
   - 메모리: lookahead × 해상도 버퍼

6. trellis (Trellis 양자화):
   0 = 비활성화
   1 = 최종 인코딩 시만
   2 = 전체 과정 (가장 느림)
   
   동작:
   - DCT 계수를 최적화
   - Rate-Distortion 트레이드오프
   - 비트 절약 vs 품질 유지
   
   효과:
   - trellis=0: 빠름, 큰 파일
   - trellis=2: 5-10% 파일 크기 감소

7. partitions (매크로블록 분할):
   H.264는 16x16 매크로블록을 더 작게 나눌 수 있음
   
   none (ultrafast):
     16x16만 사용
     
   some (medium):
     16x16, 8x8
     
   most (slow):
     16x16, 8x8, 4x4
     
   all (veryslow):
     16x16, 8x16, 16x8, 8x8, 8x4, 4x8, 4x4
     
   효과:
   - 작은 블록: 디테일 보존, 느린 인코딩
   - 큰 블록: 빠른 인코딩, 블록 아티팩트

실제 명령어로 보는 차이:

# ultrafast (수동 재현)
ffmpeg -i input.mp4 -c:v libx264 \
  -x264-params \
    ref=1:\
    me=dia:\
    subme=1:\
    me_range=16:\
    b-adapt=0:\
    rc_lookahead=10:\
    trellis=0 \
  output.mp4

# slow (수동 재현)
ffmpeg -i input.mp4 -c:v libx264 \
  -x264-params \
    ref=5:\
    me=umh:\
    subme=8:\
    me_range=16:\
    b-adapt=2:\
    rc_lookahead=50:\
    trellis=2:\
    8x8dct=1:\
    mixed_refs=1:\
    weightb=1 \
  output.mp4

인코딩 시간 vs 압축률 (1080p 1분):

ultrafast:
  시간: 5초
  크기: 50MB
  CPU: 20%
  메모리: 200MB

medium:
  시간: 30초
  크기: 30MB (-40%)
  CPU: 80%
  메모리: 400MB

slow:
  시간: 90초
  크기: 25MB (-50%)
  CPU: 100%
  메모리: 800MB

veryslow:
  시간: 300초
  크기: 23MB (-54%)
  CPU: 100%
  메모리: 1.5GB

실전 권장:
- 실시간 스트리밍: ultrafast/veryfast
- 빠른 변환: fast/medium
- 최종 배포: slow
- 아카이브: veryslow (시간 여유 시)

2-Pass 인코딩 - 목표 비트레이트 정확도 2-Pass 인코딩은 영상을 두 번 처리합니다. 첫 번째는 분석, 두 번째는 실제 인코딩입니다. 목표 비트레이트를 정확히 맞춰야 할 때 사용합니다.

# 1단계: 분석 (영상 전체를 스캔하여 통계 수집)
ffmpeg -i input.mp4 -c:v libx264 -b:v 5M -pass 1 -f null /dev/null
# Windows에서는: -f null NUL
# 2단계: 실제 인코딩 (1단계 통계를 바탕으로 최적화)
ffmpeg -i input.mp4 -c:v libx264 -b:v 5M -pass 2 output.mp4
# 완전한 예시 (오디오 포함)
# Pass 1
ffmpeg -i input.mp4 -c:v libx264 -b:v 5M -pass 1 -an -f null /dev/null
# Pass 2
ffmpeg -i input.mp4 -c:v libx264 -b:v 5M -pass 2 -c:a aac -b:a 192k output.mp4

2-Pass vs 1-Pass (CRF) 비교:

특성1-Pass (CRF)2-Pass (Bitrate)
인코딩 시간1배2배
품질 일관성일정한 품질일정한 비트레이트
파일 크기가변적정확히 목표치
사용 시기일반 용도스트리밍, 방송
복잡한 장면비트레이트 증가품질 약간 저하
단순한 장면비트레이트 감소품질 약간 향상
언제 2-Pass를 사용할까?
사용해야 할 때:
  • 정확한 파일 크기가 필요할 때 (예: DVD, Blu-ray)
  • 스트리밍 비트레이트 상한이 정해져 있을 때
  • 방송 송출 규격을 맞춰야 할 때
  • 여러 영상의 비트레이트를 동일하게 맞춰야 할 때 ❌ 사용하지 않아도 될 때:
  • 일반 파일 변환 (CRF가 더 간단하고 효율적)
  • 빠른 처리가 필요할 때
  • 파일 크기가 유동적이어도 될 때
  • YouTube 등 업로드 (플랫폼이 다시 인코딩함) 실전 예시:
# 웹 스트리밍용 (정확한 비트레이트 필요)
# Pass 1
ffmpeg -i input.mp4 -c:v libx264 -b:v 2M -maxrate 2M -bufsize 4M \
  -pass 1 -an -f null /dev/null
# Pass 2
ffmpeg -i input.mp4 -c:v libx264 -b:v 2M -maxrate 2M -bufsize 4M \
  -pass 2 -c:a aac -b:a 128k streaming.mp4
# DVD 제작용 (MPEG-2, 정확한 크기)
# Pass 1
ffmpeg -i input.mp4 -c:v mpeg2video -b:v 8M -pass 1 -an -f null /dev/null
# Pass 2
ffmpeg -i input.mp4 -c:v mpeg2video -b:v 8M -pass 2 -c:a mp2 -b:a 192k dvd.mpg
# 모바일 스트리밍 (낮은 비트레이트, 정확한 제어)
# Pass 1
ffmpeg -i input.mp4 -c:v libx264 -b:v 500k -maxrate 500k -bufsize 1M \
  -pass 1 -an -f null /dev/null
# Pass 2
ffmpeg -i input.mp4 -c:v libx264 -b:v 500k -maxrate 500k -bufsize 1M \
  -pass 2 -c:a aac -b:a 64k mobile.mp4

2-Pass 최적화 팁:

  1. 첫 번째 Pass는 빠르게:
    # preset을 빠르게 설정 (분석만 하므로)
    ffmpeg -i input.mp4 -c:v libx264 -b:v 5M -preset veryfast -pass 1 -f null /dev/null
    ffmpeg -i input.mp4 -c:v libx264 -b:v 5M -preset slow -pass 2 output.mp4
  2. 로그 파일 정리:
    # 2-Pass는 ffmpeg2pass-0.log 파일을 생성합니다
    # 작업 후 삭제:
    rm ffmpeg2pass-0.log
  3. maxrate와 bufsize 함께 사용:
    # 비트레이트 스파이크 방지
    -b:v 5M -maxrate 5M -bufsize 10M

성능 비교:

1080p 10분 영상 기준:
1-Pass CRF:
- 시간: 5분
- 크기: 500MB (가변)
- 품질: 일정
2-Pass 5Mbps:
- 시간: 10분 (2배)
- 크기: 375MB (정확)
- 품질: 약간 가변
결론: 일반 용도는 1-Pass CRF 추천

H.265 (HEVC) 인코딩

# H.265 기본
ffmpeg -i input.mp4 -c:v libx265 -crf 28 output.mp4
# H.265는 H.264보다 CRF 값을 4-6 높여도 비슷한 품질
# H.264 CRF 23 ≈ H.265 CRF 28
# 프리셋 포함
ffmpeg -i input.mp4 -c:v libx265 -preset medium -crf 28 output.mp4
# 태그 추가 (호환성)
ffmpeg -i input.mp4 -c:v libx265 -crf 28 -tag:v hvc1 output.mp4

해상도 변경

해상도 변경은 모바일 최적화, 웹 스트리밍, 파일 크기 절감 등 다양한 목적으로 사용됩니다.

# 기본 해상도 변경 (1080p → 720p)
ffmpeg -i input.mp4 -vf scale=1280:720 output.mp4
# 비율 유지 (너비 고정, 높이 자동 계산)
ffmpeg -i input.mp4 -vf scale=1280:-1 output.mp4
# -1: 원본 비율 유지하며 자동 계산
# 비율 유지 (높이 고정, 너비 자동 계산)
ffmpeg -i input.mp4 -vf scale=-1:720 output.mp4
# 짝수 픽셀 보장 (H.264 등 일부 코덱 요구사항)
ffmpeg -i input.mp4 -vf scale=1280:-2 output.mp4
# -2: 2의 배수로 자동 조정 (홀수 픽셀 방지)
# 4K → 1080p (고품질 다운스케일)
ffmpeg -i input_4k.mp4 -vf scale=1920:1080 -c:v libx264 -preset slow -crf 18 output_1080p.mp4

실전 예시:

# 모바일 최적화 (720p, 작은 파일)
ffmpeg -i input.mp4 -vf scale=1280:-2 -c:v libx264 -crf 26 -preset fast -c:a aac -b:a 96k mobile.mp4
# 웹 스트리밍용 다중 해상도 생성
# 1080p
ffmpeg -i input.mp4 -vf scale=1920:-2 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 192k output_1080p.mp4
# 720p
ffmpeg -i input.mp4 -vf scale=1280:-2 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k output_720p.mp4
# 480p
ffmpeg -i input.mp4 -vf scale=854:-2 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 96k output_480p.mp4
# Instagram 정사각형 (1:1 비율, 크롭)
ffmpeg -i input.mp4 -vf "scale=1080:1080:force_original_aspect_ratio=increase,crop=1080:1080" \
  -c:v libx264 -crf 23 instagram.mp4
# YouTube Shorts / TikTok (9:16 세로 비율)
ffmpeg -i input.mp4 -vf "scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920" \
  -c:v libx264 -crf 23 shorts.mp4
# 4K 다운스케일 (고품질 유지)
ffmpeg -i input_4k.mp4 -vf scale=1920:-2 -c:v libx264 -preset slow -crf 18 \
  -c:a copy output_1080p.mp4

고급 스케일링 옵션:

# 고품질 스케일링 알고리즘 (lanczos)
ffmpeg -i input.mp4 -vf scale=1280:-2:flags=lanczos output.mp4
# 스케일링 알고리즘 비교:
# bilinear: 빠름, 품질 보통
# bicubic: 중간 (기본값)
# lanczos: 느림, 품질 최고
# spline: 부드러움
# 예시: 각 알고리즘 비교
ffmpeg -i input.mp4 -vf scale=1280:-2:flags=bilinear bilinear.mp4
ffmpeg -i input.mp4 -vf scale=1280:-2:flags=bicubic bicubic.mp4
ffmpeg -i input.mp4 -vf scale=1280:-2:flags=lanczos lanczos.mp4
# 업스케일링 (권장하지 않음, 품질 향상 없음)
ffmpeg -i 720p.mp4 -vf scale=1920:-2:flags=lanczos 1080p.mp4
# 주의: 해상도만 커지고 실제 화질은 개선되지 않음

비율 맞추기:

# 16:9 비율 강제 (패딩 추가)
ffmpeg -i input.mp4 -vf "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2" output.mp4
# 4:3 → 16:9 (좌우 검은 바 추가)
ffmpeg -i input_4_3.mp4 -vf "scale=1920:-2,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:black" output_16_9.mp4
# 16:9 → 4:3 (상하 검은 바 추가)
ffmpeg -i input_16_9.mp4 -vf "scale=-2:1080,pad=1440:1080:(ow-iw)/2:(oh-ih)/2:black" output_4_3.mp4

일괄 변환 스크립트:

# 폴더 내 모든 영상을 720p로 변환
for file in *.mp4; do
  ffmpeg -i "$file" -vf scale=1280:-2 -c:v libx264 -crf 23 "720p_$file"
done
# 다중 해상도 자동 생성
for file in *.mp4; do
  base="${file%.mp4}"
  ffmpeg -i "$file" -vf scale=1920:-2 -c:v libx264 -crf 23 "${base}_1080p.mp4"
  ffmpeg -i "$file" -vf scale=1280:-2 -c:v libx264 -crf 23 "${base}_720p.mp4"
  ffmpeg -i "$file" -vf scale=854:-2 -c:v libx264 -crf 23 "${base}_480p.mp4"
done

주의사항:

  1. 업스케일링은 피하세요
    • 720p → 1080p로 키워도 화질은 개선되지 않습니다
    • 오히려 파일 크기만 커집니다
  2. 홀수 픽셀 문제
    • H.264 등은 짝수 픽셀을 요구합니다
    • -2 사용으로 자동 조정
  3. 비율 왜곡 주의
    • scale=1280:720는 강제로 늘리거나 줄입니다
    • 비율 유지는 -1 또는 -2 사용
  4. 성능 고려
    • 다운스케일은 빠름
    • 업스케일은 느리고 의미 없음

프레임레이트 변경

# 60fps → 30fps
ffmpeg -i input.mp4 -r 30 output.mp4
# 24fps → 60fps (보간)
ffmpeg -i input.mp4 -filter:v "minterpolate=fps=60" output.mp4

비트레이트 설정

# 비디오 비트레이트
ffmpeg -i input.mp4 -b:v 5M output.mp4
# 오디오 비트레이트
ffmpeg -i input.mp4 -b:a 192k output.mp4
# 둘 다
ffmpeg -i input.mp4 -b:v 5M -b:a 192k output.mp4
# 최대 비트레이트 제한
ffmpeg -i input.mp4 -b:v 5M -maxrate 5M -bufsize 10M output.mp4

4. 오디오 처리

오디오 추출

# 동영상에서 오디오만 추출
ffmpeg -i video.mp4 -vn -c:a copy audio.aac
# -vn: 비디오 제외
# MP3로 변환
ffmpeg -i video.mp4 -vn -c:a libmp3lame -b:a 192k audio.mp3
# WAV로 변환 (무손실)
ffmpeg -i video.mp4 -vn audio.wav

오디오 변환

# MP3 → AAC
ffmpeg -i input.mp3 -c:a aac -b:a 192k output.aac
# WAV → MP3
ffmpeg -i input.wav -c:a libmp3lame -b:a 320k output.mp3
# FLAC → MP3 (무손실 → 손실)
ffmpeg -i input.flac -c:a libmp3lame -q:a 0 output.mp3
# -q:a 0: 최고 품질 (VBR)

오디오 편집

# 볼륨 조절 (2배)
ffmpeg -i input.mp3 -af "volume=2.0" output.mp3
# 볼륨 조절 (dB)
ffmpeg -i input.mp3 -af "volume=10dB" output.mp3
# 페이드 인/아웃
ffmpeg -i input.mp3 -af "afade=t=in:st=0:d=3,afade=t=out:st=57:d=3" output.mp3
# t=in: 페이드 인, t=out: 페이드 아웃
# st: 시작 시간(초), d: 지속 시간(초)
# 노이즈 제거
ffmpeg -i input.mp3 -af "highpass=f=200,lowpass=f=3000" output.mp3
# 스테레오 → 모노
ffmpeg -i input.mp3 -ac 1 output.mp3
# 샘플레이트 변경
ffmpeg -i input.mp3 -ar 44100 output.mp3

5. 썸네일 및 이미지 추출

썸네일 추출

# 첫 프레임 추출
ffmpeg -i video.mp4 -vframes 1 thumbnail.jpg
# 특정 시간 (5초)
ffmpeg -i video.mp4 -ss 00:00:05 -vframes 1 thumbnail.jpg
# 고품질
ffmpeg -i video.mp4 -ss 00:00:05 -vframes 1 -q:v 2 thumbnail.jpg
# -q:v 2: 품질 (1=최고, 31=최저)
# 특정 크기
ffmpeg -i video.mp4 -ss 00:00:05 -vf scale=1280:720 -vframes 1 thumbnail.jpg

여러 썸네일 추출

# 1초마다 추출
ffmpeg -i video.mp4 -vf fps=1 thumb_%04d.jpg
# 10초마다 추출
ffmpeg -i video.mp4 -vf fps=1/10 thumb_%04d.jpg
# 특정 구간 (10초~20초, 1초마다)
ffmpeg -i video.mp4 -ss 00:00:10 -to 00:00:20 -vf fps=1 thumb_%04d.jpg

GIF 생성

# 동영상 → GIF
ffmpeg -i video.mp4 -vf "fps=10,scale=480:-1:flags=lanczos" output.gif
# 고품질 GIF (팔레트 생성)
# 1단계: 팔레트 생성
ffmpeg -i video.mp4 -vf "fps=10,scale=480:-1:flags=lanczos,palettegen" palette.png
# 2단계: GIF 생성
ffmpeg -i video.mp4 -i palette.png -filter_complex "fps=10,scale=480:-1:flags=lanczos[x];[x][1:v]paletteuse" output.gif
# 특정 구간 (0~5초)
ffmpeg -ss 00:00:00 -t 00:00:05 -i video.mp4 -vf "fps=10,scale=480:-1:flags=lanczos,palettegen" palette.png
ffmpeg -ss 00:00:00 -t 00:00:05 -i video.mp4 -i palette.png -filter_complex "fps=10,scale=480:-1:flags=lanczos[x];[x][1:v]paletteuse" output.gif

6. 동영상 편집

자르기 (Trim)

# 시간 기준 자르기
ffmpeg -i input.mp4 -ss 00:00:10 -to 00:00:30 output.mp4
# -ss: 시작 시간
# -to: 종료 시간
# 또는
ffmpeg -i input.mp4 -ss 00:00:10 -t 00:00:20 output.mp4
# -t: 지속 시간 (20초)
# 재인코딩 없이 (빠름, 정확도 낮음)
ffmpeg -ss 00:00:10 -i input.mp4 -to 00:00:30 -c copy output.mp4
# -ss를 -i 앞에 두면 더 빠름 (키프레임 탐색)

크롭 (Crop)

# 중앙 1280×720 크롭
ffmpeg -i input.mp4 -vf "crop=1280:720" output.mp4
# 특정 위치 크롭
ffmpeg -i input.mp4 -vf "crop=1280:720:0:0" output.mp4
# crop=width:height:x:y
# 16:9 → 1:1 (정사각형, 중앙)
ffmpeg -i input.mp4 -vf "crop=ih:ih" output.mp4
# ih: 입력 높이

회전

# 90도 시계방향
ffmpeg -i input.mp4 -vf "transpose=1" output.mp4
# 90도 반시계방향
ffmpeg -i input.mp4 -vf "transpose=2" output.mp4
# 180도
ffmpeg -i input.mp4 -vf "transpose=1,transpose=1" output.mp4
# 메타데이터로 회전 (재인코딩 없음)
ffmpeg -i input.mp4 -c copy -metadata:s:v:0 rotate=90 output.mp4

병합

동영상 이어붙이기:

# 1. 파일 목록 생성
cat > filelist.txt << EOF
file 'video1.mp4'
file 'video2.mp4'
file 'video3.mp4'
EOF
# 2. 병합
ffmpeg -f concat -safe 0 -i filelist.txt -c copy output.mp4
# 재인코딩 포함
ffmpeg -f concat -safe 0 -i filelist.txt -c:v libx264 -crf 23 output.mp4

오디오 추가:

# 동영상에 오디오 추가
ffmpeg -i video.mp4 -i audio.mp3 -c:v copy -c:a aac output.mp4
# 기존 오디오 교체
ffmpeg -i video.mp4 -i audio.mp3 -c:v copy -c:a aac -map 0:v:0 -map 1:a:0 output.mp4

7. 스트리밍 및 HLS

HLS 생성

HLS (HTTP Live Streaming)는 Apple이 개발한 스트리밍 프로토콜이다.

# 기본 HLS
ffmpeg -i input.mp4 \
  -c:v libx264 -c:a aac \
  -hls_time 10 \
  -hls_playlist_type vod \
  -hls_segment_filename "segment_%03d.ts" \
  playlist.m3u8
# 옵션:
# -hls_time 10: 세그먼트 길이 (10초)
# -hls_playlist_type vod: VOD (주문형)
# -hls_segment_filename: 세그먼트 파일명

다중 해상도 (Adaptive Bitrate):

# 1080p
ffmpeg -i input.mp4 \
  -vf scale=1920:1080 -c:v libx264 -b:v 5M -c:a aac -b:a 192k \
  -hls_time 10 -hls_playlist_type vod \
  -hls_segment_filename "1080p_%03d.ts" \
  1080p.m3u8
# 720p
ffmpeg -i input.mp4 \
  -vf scale=1280:720 -c:v libx264 -b:v 3M -c:a aac -b:a 128k \
  -hls_time 10 -hls_playlist_type vod \
  -hls_segment_filename "720p_%03d.ts" \
  720p.m3u8
# 480p
ffmpeg -i input.mp4 \
  -vf scale=854:480 -c:v libx264 -b:v 1.5M -c:a aac -b:a 128k \
  -hls_time 10 -hls_playlist_type vod \
  -hls_segment_filename "480p_%03d.ts" \
  480p.m3u8
# Master Playlist 생성
cat > master.m3u8 << EOF
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080
1080p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=3000000,RESOLUTION=1280x720
720p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1500000,RESOLUTION=854x480
480p.m3u8
EOF

RTMP 스트리밍

# YouTube 라이브
ffmpeg -re -i input.mp4 \
  -c:v libx264 -preset veryfast -b:v 3M -maxrate 3M -bufsize 6M \
  -pix_fmt yuv420p -g 60 -c:a aac -b:a 128k -ar 44100 \
  -f flv rtmp://a.rtmp.youtube.com/live2/YOUR_STREAM_KEY
# Twitch 라이브
ffmpeg -re -i input.mp4 \
  -c:v libx264 -preset veryfast -b:v 3M \
  -c:a aac -b:a 128k \
  -f flv rtmp://live.twitch.tv/app/YOUR_STREAM_KEY
# -re: 실시간 속도로 읽기
# -g 60: GOP 크기 (2초, 30fps 기준)

웹캠 스트리밍

# macOS (웹캠)
ffmpeg -f avfoundation -i "0:0" \
  -c:v libx264 -preset veryfast -b:v 2M \
  -c:a aac -b:a 128k \
  -f flv rtmp://localhost/live/stream
# Linux (웹캠)
ffmpeg -f v4l2 -i /dev/video0 \
  -c:v libx264 -preset veryfast -b:v 2M \
  -f flv rtmp://localhost/live/stream
# Windows (웹캠)
ffmpeg -f dshow -i video="USB Video Device" \
  -c:v libx264 -preset veryfast -b:v 2M \
  -f flv rtmp://localhost/live/stream

8. 필터 및 효과

워터마크

# 이미지 워터마크 (우측 상단)
ffmpeg -i input.mp4 -i logo.png \
  -filter_complex "overlay=W-w-10:10" \
  output.mp4
# 좌측 하단
ffmpeg -i input.mp4 -i logo.png \
  -filter_complex "overlay=10:H-h-10" \
  output.mp4
# 중앙
ffmpeg -i input.mp4 -i logo.png \
  -filter_complex "overlay=(W-w)/2:(H-h)/2" \
  output.mp4
# 투명도 조절
ffmpeg -i input.mp4 -i logo.png \
  -filter_complex "[1:v]format=rgba,colorchannelmixer=aa=0.5[logo];[0:v][logo]overlay=W-w-10:10" \
  output.mp4

텍스트 추가

# 간단한 텍스트
ffmpeg -i input.mp4 \
  -vf "drawtext=text='Hello World':fontsize=48:fontcolor=white:x=10:y=10" \
  output.mp4
# 시간 표시
ffmpeg -i input.mp4 \
  -vf "drawtext=text='%{pts\:hms}':fontsize=32:fontcolor=white:x=10:y=10" \
  output.mp4
# 한글 텍스트 (폰트 지정 필수)
ffmpeg -i input.mp4 \
  -vf "drawtext=text='안녕하세요':fontfile=/System/Library/Fonts/AppleSDGothicNeo.ttc:fontsize=48:fontcolor=white:x=10:y=10" \
  output.mp4
# 배경 추가
ffmpeg -i input.mp4 \
  -vf "drawtext=text='Hello':fontsize=48:fontcolor=white:x=10:y=10:box=1:[email protected]:boxborderw=5" \
  output.mp4

색상 조정

# 밝기 조절
ffmpeg -i input.mp4 -vf "eq=brightness=0.1" output.mp4
# 대비 조절
ffmpeg -i input.mp4 -vf "eq=contrast=1.5" output.mp4
# 채도 조절
ffmpeg -i input.mp4 -vf "eq=saturation=1.5" output.mp4
# 흑백
ffmpeg -i input.mp4 -vf "hue=s=0" output.mp4
# 세피아
ffmpeg -i input.mp4 -vf "colorchannelmixer=.393:.769:.189:0:.349:.686:.168:0:.272:.534:.131" output.mp4

블러 및 샤프닝

# 블러
ffmpeg -i input.mp4 -vf "boxblur=5:1" output.mp4
# 샤프닝
ffmpeg -i input.mp4 -vf "unsharp=5:5:1.0:5:5:0.0" output.mp4
# 특정 영역만 블러 (얼굴 가리기)
ffmpeg -i input.mp4 \
  -vf "boxblur=10:enable='between(t,5,10)'" \
  output.mp4

9. 미디어 정보 확인

ffprobe 사용

# 기본 정보
ffprobe input.mp4
# JSON 형식
ffprobe -v quiet -print_format json -show_format -show_streams input.mp4
# 비디오 정보만
ffprobe -v error -select_streams v:0 -show_entries stream=width,height,r_frame_rate,bit_rate -of default=noprint_wrappers=1 input.mp4
# 오디오 정보만
ffprobe -v error -select_streams a:0 -show_entries stream=codec_name,sample_rate,channels,bit_rate -of default=noprint_wrappers=1 input.mp4
# 길이 확인
ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 input.mp4

실용적인 정보 추출

# 해상도
ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 input.mp4
# 출력: 1920x1080
# 프레임레이트
ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate -of default=noprint_wrappers=1:nokey=1 input.mp4
# 출력: 30/1
# 비트레이트
ffprobe -v error -show_entries format=bit_rate -of default=noprint_wrappers=1:nokey=1 input.mp4
# 출력: 5000000 (5 Mbps)
# 코덱
ffprobe -v error -select_streams v:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1 input.mp4
# 출력: h264

10. FFmpeg 라이브러리 (libav)

Python (ffmpeg-python)

설치:

pip install ffmpeg-python

기본 사용:

import ffmpeg
# 동영상 변환
(
    ffmpeg
    .input('input.mp4')
    .output('output.mp4', vcodec='libx264', crf=23)
    .run()
)
# 해상도 변경
(
    ffmpeg
    .input('input.mp4')
    .filter('scale', 1280, 720)
    .output('output.mp4')
    .run()
)
# 썸네일 추출
(
    ffmpeg
    .input('input.mp4', ss=5)
    .output('thumbnail.jpg', vframes=1)
    .run()
)
# 오디오 추출
(
    ffmpeg
    .input('input.mp4')
    .output('audio.mp3', vn=None, acodec='libmp3lame', audio_bitrate='192k')
    .run()
)

고급 예제:

import ffmpeg
import sys
def convert_video(input_file, output_file, resolution='1280:720', crf=23):
    """동영상 변환"""
    try:
        (
            ffmpeg
            .input(input_file)
            .filter('scale', *resolution.split(':'))
            .output(
                output_file,
                vcodec='libx264',
                preset='slow',
                crf=crf,
                acodec='aac',
                audio_bitrate='192k'
            )
            .global_args('-loglevel', 'error')
            .run(capture_stdout=True, capture_stderr=True)
        )
        print(f"✅ Conversion complete: {output_file}")
    except ffmpeg.Error as e:
        print(f"❌ Error: {e.stderr.decode()}", file=sys.stderr)
        raise
def get_video_info(input_file):
    """동영상 정보 추출"""
    try:
        probe = ffmpeg.probe(input_file)
        video_stream = next((s for s in probe['streams'] if s['codec_type'] == 'video'), None)
        audio_stream = next((s for s in probe['streams'] if s['codec_type'] == 'audio'), None)
        
        return {
            'duration': float(probe['format']['duration']),
            'size': int(probe['format']['size']),
            'bitrate': int(probe['format']['bit_rate']),
            'video': {
                'codec': video_stream['codec_name'],
                'width': video_stream['width'],
                'height': video_stream['height'],
                'fps': eval(video_stream['r_frame_rate'])
            } if video_stream else None,
            'audio': {
                'codec': audio_stream['codec_name'],
                'sample_rate': audio_stream['sample_rate'],
                'channels': audio_stream['channels']
            } if audio_stream else None
        }
    except ffmpeg.Error as e:
        print(f"❌ Error: {e.stderr.decode()}", file=sys.stderr)
        raise
# 사용
info = get_video_info('input.mp4')
print(f"Duration: {info['duration']}s")
print(f"Resolution: {info['video']['width']}x{info['video']['height']}")
print(f"FPS: {info['video']['fps']}")

진행률 표시:

import ffmpeg
import sys
def convert_with_progress(input_file, output_file):
    """진행률 표시"""
    # 동영상 길이 확인
    probe = ffmpeg.probe(input_file)
    duration = float(probe['format']['duration'])
    
    # 변환 시작
    process = (
        ffmpeg
        .input(input_file)
        .output(output_file, vcodec='libx264', crf=23)
        .global_args('-progress', 'pipe:1')
        .overwrite_output()
        .run_async(pipe_stdout=True, pipe_stderr=True)
    )
    
    # 진행률 파싱
    for line in process.stdout:
        line = line.decode()
        if line.startswith('out_time_ms='):
            time_ms = int(line.split('=')[1])
            time_s = time_ms / 1000000.0
            progress = (time_s / duration) * 100
            
            sys.stdout.write(f"\rProgress: {progress:.1f}%")
            sys.stdout.flush()
    
    process.wait()
    print("\n✅ Complete!")
convert_with_progress('input.mp4', 'output.mp4')

Node.js (fluent-ffmpeg)

설치:

npm install fluent-ffmpeg

기본 사용:

const ffmpeg = require('fluent-ffmpeg');
// 동영상 변환
ffmpeg('input.mp4')
  .output('output.mp4')
  .videoCodec('libx264')
  .audioCodec('aac')
  .on('end', () => console.log('✅ Complete!'))
  .on('error', (err) => console.error('❌ Error:', err))
  .run();
// 해상도 변경
ffmpeg('input.mp4')
  .size('1280x720')
  .output('output.mp4')
  .run();
// 썸네일 추출
ffmpeg('input.mp4')
  .screenshots({
    timestamps: ['00:00:05'],
    filename: 'thumbnail.jpg',
    folder: './thumbnails'
  });

고급 예제:

const ffmpeg = require('fluent-ffmpeg');
const path = require('path');
// 동영상 정보 추출
function getVideoInfo(inputPath) {
  return new Promise((resolve, reject) => {
    ffmpeg.ffprobe(inputPath, (err, metadata) => {
      if (err) return reject(err);
      
      const videoStream = metadata.streams.find(s => s.codec_type === 'video');
      const audioStream = metadata.streams.find(s => s.codec_type === 'audio');
      
      resolve({
        duration: metadata.format.duration,
        size: metadata.format.size,
        bitrate: metadata.format.bit_rate,
        video: videoStream ? {
          codec: videoStream.codec_name,
          width: videoStream.width,
          height: videoStream.height,
          fps: eval(videoStream.r_frame_rate)
        } : null,
        audio: audioStream ? {
          codec: audioStream.codec_name,
          sampleRate: audioStream.sample_rate,
          channels: audioStream.channels
        } : null
      });
    });
  });
}
// 진행률 표시
function convertWithProgress(inputPath, outputPath) {
  return new Promise((resolve, reject) => {
    ffmpeg(inputPath)
      .output(outputPath)
      .videoCodec('libx264')
      .videoBitrate('5M')
      .audioCodec('aac')
      .audioBitrate('192k')
      .on('start', (cmd) => {
        console.log('Starting:', cmd);
      })
      .on('progress', (progress) => {
        console.log(`Progress: ${progress.percent?.toFixed(1)}%`);
      })
      .on('end', () => {
        console.log('✅ Complete!');
        resolve();
      })
      .on('error', (err) => {
        console.error('❌ Error:', err);
        reject(err);
      })
      .run();
  });
}
// HLS 생성
function createHLS(inputPath, outputDir) {
  return new Promise((resolve, reject) => {
    ffmpeg(inputPath)
      .outputOptions([
        '-c:v libx264',
        '-c:a aac',
        '-hls_time 10',
        '-hls_playlist_type vod',
        '-hls_segment_filename', path.join(outputDir, 'segment_%03d.ts')
      ])
      .output(path.join(outputDir, 'playlist.m3u8'))
      .on('end', resolve)
      .on('error', reject)
      .run();
  });
}
// 사용
(async () => {
  const info = await getVideoInfo('input.mp4');
  console.log('Video Info:', info);
  
  await convertWithProgress('input.mp4', 'output.mp4');
  await createHLS('input.mp4', './hls');
})();

Go (ffmpeg-go)

설치:

go get github.com/u2takey/ffmpeg-go

기본 사용:

package main
import (
    "fmt"
    ffmpeg "github.com/u2takey/ffmpeg-go"
)
func main() {
    // 동영상 변환
    err := ffmpeg.Input("input.mp4").
        Output("output.mp4", ffmpeg.KwArgs{
            "c:v": "libx264",
            "crf": 23,
        }).
        Run()
    
    if err != nil {
        panic(err)
    }
    
    fmt.Println("✅ Complete!")
}
// 해상도 변경
func resizeVideo(input, output string) error {
    return ffmpeg.Input(input).
        Filter("scale", ffmpeg.Args{"1280:720"}).
        Output(output).
        Run()
}
// 썸네일 추출
func extractThumbnail(input, output string, timestamp float64) error {
    return ffmpeg.Input(input, ffmpeg.KwArgs{"ss": timestamp}).
        Output(output, ffmpeg.KwArgs{"vframes": 1}).
        Run()
}
// 오디오 추출
func extractAudio(input, output string) error {
    return ffmpeg.Input(input).
        Output(output, ffmpeg.KwArgs{
            "vn":   "",
            "c:a":  "libmp3lame",
            "b:a":  "192k",
        }).
        Run()
}

11. 실전 프로젝트

프로젝트 1: 동영상 업로드 서버

Node.js + Express:

const express = require('express');
const multer = require('multer');
const ffmpeg = require('fluent-ffmpeg');
const path = require('path');
const fs = require('fs').promises;
const app = express();
const upload = multer({ dest: 'uploads/' });
// 동영상 업로드 및 처리
app.post('/upload', upload.single('video'), async (req, res) => {
  const inputPath = req.file.path;
  const outputDir = path.join('processed', req.file.filename);
  
  try {
    await fs.mkdir(outputDir, { recursive: true });
    
    // 1. 썸네일 생성
    await new Promise((resolve, reject) => {
      ffmpeg(inputPath)
        .screenshots({
          timestamps: ['00:00:01'],
          filename: 'thumbnail.jpg',
          folder: outputDir
        })
        .on('end', resolve)
        .on('error', reject);
    });
    
    // 2. 다중 해상도 변환
    const resolutions = [
      { name: '1080p', size: '1920x1080', bitrate: '5M' },
      { name: '720p', size: '1280x720', bitrate: '3M' },
      { name: '480p', size: '854x480', bitrate: '1.5M' }
    ];
    
    await Promise.all(resolutions.map(res => {
      return new Promise((resolve, reject) => {
        ffmpeg(inputPath)
          .size(res.size)
          .videoBitrate(res.bitrate)
          .output(path.join(outputDir, `${res.name}.mp4`))
          .on('end', resolve)
          .on('error', reject)
          .run();
      });
    }));
    
    // 3. HLS 생성
    await new Promise((resolve, reject) => {
      ffmpeg(inputPath)
        .outputOptions([
          '-c:v libx264',
          '-c:a aac',
          '-hls_time 10',
          '-hls_playlist_type vod',
          '-hls_segment_filename', path.join(outputDir, 'segment_%03d.ts')
        ])
        .output(path.join(outputDir, 'playlist.m3u8'))
        .on('end', resolve)
        .on('error', reject)
        .run();
    });
    
    // 4. 원본 파일 삭제
    await fs.unlink(inputPath);
    
    res.json({
      success: true,
      thumbnail: `/processed/${req.file.filename}/thumbnail.jpg`,
      videos: resolutions.map(r => ({
        quality: r.name,
        url: `/processed/${req.file.filename}/${r.name}.mp4`
      })),
      hls: `/processed/${req.file.filename}/playlist.m3u8`
    });
    
  } catch (error) {
    console.error('Processing error:', error);
    res.status(500).json({ error: 'Processing failed' });
  }
});
app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

프로젝트 2: 동영상 스트리밍 서버

Python + Flask:

from flask import Flask, request, send_file, jsonify
import ffmpeg
import os
import uuid
app = Flask(__name__)
UPLOAD_FOLDER = 'uploads'
PROCESSED_FOLDER = 'processed'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
os.makedirs(PROCESSED_FOLDER, exist_ok=True)
@app.route('/upload', methods=['POST'])
def upload_video():
    if 'video' not in request.files:
        return jsonify({'error': 'No video file'}), 400
    
    file = request.files['video']
    video_id = str(uuid.uuid4())
    input_path = os.path.join(UPLOAD_FOLDER, f"{video_id}.mp4")
    output_dir = os.path.join(PROCESSED_FOLDER, video_id)
    
    os.makedirs(output_dir, exist_ok=True)
    file.save(input_path)
    
    try:
        # 1. 동영상 정보 추출
        probe = ffmpeg.probe(input_path)
        duration = float(probe['format']['duration'])
        
        # 2. 썸네일 생성 (3개)
        timestamps = [duration * 0.25, duration * 0.5, duration * 0.75]
        for i, ts in enumerate(timestamps):
            (
                ffmpeg
                .input(input_path, ss=ts)
                .output(os.path.join(output_dir, f'thumb_{i}.jpg'), vframes=1)
                .overwrite_output()
                .run(capture_stdout=True, capture_stderr=True)
            )
        
        # 3. HLS 생성 (다중 해상도)
        resolutions = [
            {'name': '1080p', 'width': 1920, 'height': 1080, 'bitrate': '5M'},
            {'name': '720p', 'width': 1280, 'height': 720, 'bitrate': '3M'},
            {'name': '480p', 'width': 854, 'height': 480, 'bitrate': '1.5M'}
        ]
        
        for res in resolutions:
            res_dir = os.path.join(output_dir, res['name'])
            os.makedirs(res_dir, exist_ok=True)
            
            (
                ffmpeg
                .input(input_path)
                .output(
                    os.path.join(res_dir, 'playlist.m3u8'),
                    **{
                        'c:v': 'libx264',
                        'b:v': res['bitrate'],
                        'c:a': 'aac',
                        'b:a': '128k',
                        'vf': f"scale={res['width']}:{res['height']}",
                        'hls_time': 10,
                        'hls_playlist_type': 'vod',
                        'hls_segment_filename': os.path.join(res_dir, 'segment_%03d.ts')
                    }
                )
                .overwrite_output()
                .run(capture_stdout=True, capture_stderr=True)
            )
        
        # 4. Master Playlist 생성
        master_playlist = "#EXTM3U\n"
        for res in resolutions:
            bandwidth = int(res['bitrate'].replace('M', ')) * 1000000
            master_playlist += f"#EXT-X-STREAM-INF:BANDWIDTH={bandwidth},RESOLUTION={res['width']}x{res['height']}\n"
            master_playlist += f"{res['name']}/playlist.m3u8\n"
        
        with open(os.path.join(output_dir, 'master.m3u8'), 'w') as f:
            f.write(master_playlist)
        
        # 5. 원본 삭제
        os.remove(input_path)
        
        return jsonify({
            'video_id': video_id,
            'duration': duration,
            'thumbnails': [f'/processed/{video_id}/thumb_{i}.jpg' for i in range(3)],
            'hls': f'/processed/{video_id}/master.m3u8'
        })
        
    except ffmpeg.Error as e:
        return jsonify({'error': e.stderr.decode()}), 500
@app.route('/processed/<path:filename>')
def serve_processed(filename):
    return send_file(os.path.join(PROCESSED_FOLDER, filename))
if __name__ == '__main__':
    app.run(debug=True, port=5000)

프로젝트 3: 일괄 변환 도구

Python CLI:

#!/usr/bin/env python3
import ffmpeg
import argparse
import os
import sys
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed
def convert_video(input_path, output_dir, resolution, crf, preset):
    """단일 동영상 변환"""
    input_name = Path(input_path).stem
    output_path = os.path.join(output_dir, f"{input_name}_{resolution}.mp4")
    
    try:
        width, height = resolution.split('x')
        
        (
            ffmpeg
            .input(input_path)
            .filter('scale', width, height)
            .output(
                output_path,
                vcodec='libx264',
                preset=preset,
                crf=crf,
                acodec='aac',
                audio_bitrate='192k'
            )
            .global_args('-loglevel', 'error')
            .overwrite_output()
            .run(capture_stdout=True, capture_stderr=True)
        )
        
        return f"✅ {input_name}{output_path}"
    
    except ffmpeg.Error as e:
        return f"❌ {input_name}: {e.stderr.decode()}"
def batch_convert(input_dir, output_dir, resolution='1280x720', crf=23, preset='medium', workers=4):
    """일괄 변환"""
    os.makedirs(output_dir, exist_ok=True)
    
    # 동영상 파일 찾기
    video_extensions = ['.mp4', '.mov', '.avi', '.mkv', '.flv', '.wmv']
    video_files = []
    
    for ext in video_extensions:
        video_files.extend(Path(input_dir).glob(f'*{ext}'))
        video_files.extend(Path(input_dir).glob(f'*{ext.upper()}'))
    
    if not video_files:
        print(f"No video files found in {input_dir}")
        return
    
    print(f"Found {len(video_files)} videos")
    print(f"Resolution: {resolution}, CRF: {crf}, Preset: {preset}")
    print(f"Workers: {workers}\n")
    
    # 병렬 처리
    with ThreadPoolExecutor(max_workers=workers) as executor:
        futures = {
            executor.submit(convert_video, str(f), output_dir, resolution, crf, preset): f
            for f in video_files
        }
        
        for future in as_completed(futures):
            result = future.result()
            print(result)
    
    print(f"\n✅ All conversions complete!")
if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Batch video converter')
    parser.add_argument('input_dir', help='Input directory')
    parser.add_argument('output_dir', help='Output directory')
    parser.add_argument('--resolution', default='1280x720', help='Output resolution (default: 1280x720)')
    parser.add_argument('--crf', type=int, default=23, help='CRF value (default: 23)')
    parser.add_argument('--preset', default='medium', help='Encoding preset (default: medium)')
    parser.add_argument('--workers', type=int, default=4, help='Number of parallel workers (default: 4)')
    
    args = parser.parse_args()
    
    batch_convert(
        args.input_dir,
        args.output_dir,
        resolution=args.resolution,
        crf=args.crf,
        preset=args.preset,
        workers=args.workers
    )

사용:

# 기본
python batch_convert.py ./videos ./output
# 720p, CRF 20, slow 프리셋
python batch_convert.py ./videos ./output --resolution 1280x720 --crf 20 --preset slow
# 8개 병렬 처리
python batch_convert.py ./videos ./output --workers 8

12. 고급 필터

복잡한 필터 체인

# 여러 필터 적용
ffmpeg -i input.mp4 \
  -vf "scale=1280:720,eq=brightness=0.1:contrast=1.2,unsharp=5:5:1.0" \
  output.mp4
# 필터 그래프
ffmpeg -i input.mp4 -i logo.png \
  -filter_complex "\
    [0:v]scale=1280:720[scaled];\
    [scaled][1:v]overlay=W-w-10:10[watermarked];\
    [watermarked]drawtext=text='Hello':fontsize=48:x=10:y=10\
  " \
  output.mp4

화면 분할

# 2개 동영상 나란히 (Side by Side)
ffmpeg -i left.mp4 -i right.mp4 \
  -filter_complex "\
    [0:v]scale=960:1080[left];\
    [1:v]scale=960:1080[right];\
    [left][right]hstack\
  " \
  output.mp4
# 4개 동영상 (2×2 그리드)
ffmpeg -i top_left.mp4 -i top_right.mp4 -i bottom_left.mp4 -i bottom_right.mp4 \
  -filter_complex "\
    [0:v]scale=960:540[tl];\
    [1:v]scale=960:540[tr];\
    [2:v]scale=960:540[bl];\
    [3:v]scale=960:540[br];\
    [tl][tr]hstack[top];\
    [bl][br]hstack[bottom];\
    [top][bottom]vstack\
  " \
  output.mp4

슬로우 모션 / 타임랩스

# 슬로우 모션 (0.5배 속도)
ffmpeg -i input.mp4 -filter:v "setpts=2.0*PTS" output.mp4
# 타임랩스 (2배 속도)
ffmpeg -i input.mp4 -filter:v "setpts=0.5*PTS" output.mp4
# 오디오 포함 (속도 동기화)
ffmpeg -i input.mp4 \
  -filter_complex "[0:v]setpts=0.5*PTS[v];[0:a]atempo=2.0[a]" \
  -map "[v]" -map "[a]" \
  output.mp4

트랜지션 효과

# 페이드 인/아웃
ffmpeg -i input.mp4 \
  -vf "fade=t=in:st=0:d=3,fade=t=out:st=57:d=3" \
  output.mp4
# 크로스페이드 (2개 동영상 전환)
ffmpeg -i video1.mp4 -i video2.mp4 \
  -filter_complex "\
    [0:v]fade=t=out:st=7:d=1[v0];\
    [1:v]fade=t=in:st=0:d=1[v1];\
    [v0][v1]concat=n=2:v=1:a=0\
  " \
  output.mp4

FFmpeg 심화 — libav·필터 그래프·HW·매핑·프로덕션

이 절에서는 명령줄 너머의 내부 데이터 흐름과, 서비스 환경에서 자주 맞닥뜨리는 필터 그래프·하드웨어 파이프라인·스트림 선택을 엔지니어링 관점에서 정리합니다. 앞선 «기본 명령 구조» 절의 파이프라인 설명을 보강하는 목적입니다.

Libav 계열 아키텍처: 디먹서 → 디코더 → 필터 → 인코더 → 먹서

FFmpeg 실행 파일은 libavformat, libavcodec, libavfilter, libavutil, 필요 시 libswscale·libswresample을 조합해 동작합니다. 한 줄의 ffmpeg 명령은 내부적으로 다음과 같은 책임 분리를 따릅니다.

디먹서(Demuxer, libavformat)는 컨테이너를 읽어 스트림 단위 메타데이터압축된 패킷(AVPacket)을 생성합니다. MP4의 moov/mdat, MPEG-TS의 PID·PAT/PMT처럼 포맷별 파서가 다르지만, 상위 파이프라인은 동일하게 «스트림 인덱스 → 패킷 큐»로 추상화됩니다.

디코더(Decoder, libavcodec)는 패킷을 디코딩된 프레임(AVFrame) 또는 오디오 샘플 버퍼로 바꿉니다. 여기서 픽셀 포맷(yuv420p, nv12 등)·색역·타임스탬프(PTS/DTS)가 확정되며, 이후 필터·인코더는 이 표현에 맞춰 동작합니다.

필터(Filter graph, libavfilter)는 디코더 출력을 그대로 인코더에 넣지 않고 가공할 때 개입합니다. -vf/-af는 단일 입력·단일 출력 선형 체인이고, -filter_complex여러 입력 패드분기·합성을 표현합니다. 필터는 프레임 단위로 연결되므로, 해상도·픽셀 포맷·샘플레이트가 어긋나면 FFmpeg가 자동으로 삽입하는 변환 필터(format, fps, aformat 등)가 끼어들 수 있습니다. 예상과 다른 그래프가 만들어졌을 때는 -loglevel debug로 «자동 삽입» 로그를 확인하는 것이 좋습니다.

인코더(Encoder)는 필터 출력 프레임을 다시 압축 패킷으로 만듭니다. 소프트웨어 인코더(libx264 등)와 하드웨어 인코더(NVENC/VAAPI 등)는 입력 버퍼의 메모리 종류(시스템 메모리 vs GPU 서페이스)에서부터 차이가 납니다.

먹서(Muxer)는 패킷을 타임스탬프 순으로 인터리브하고, 컨테이너별 인덱스·헤더를 기록합니다. 웹 배포에서 자주 쓰는 -movflags +faststart먹서 단계에서 moov 위치를 조정하는 전형적인 예입니다.

비트스트림 필터(Bitstream filter)는 «디코딩 없이」 바이트열을 변형할 때 씁니다. 예를 들어 MP4에 들어 있는 H.264/HEVC를 Annex B 형태로 꺼내 TS에 실을 때 h264_mp4toannexb, hevc_mp4toannexb가 등장합니다. 패킷 경계와 NAL 구조를 이해하고 있을 때만 안전하게 적용할 수 있습니다.

필터 그래프 처리: 패드, 스케줄링, 병렬

-filter_complex 문자열은 내부적으로 방향 그래프(DAG)로 컴파일됩니다. [0:v]scale=1280:720[v1]처럼 레이블된 패드는 노드의 입·출력 핀에 해당합니다. split, overlay, xfade, concat처럼 다입력·다출력 필터를 쓸 때는 패드 연결을 빠뜨리지 않았는지가 오류의 대부분입니다.

오디오·비디오 동시 처리에서는 [0:v]…[v], [0:a]…[a]를 만든 뒤 -map "[v]" -map "[a]"로 출력 스트림에 묶는 패턴이 표준적입니다. 단순 -vf만 쓰면 비디오 체인만 바뀌고 오디오는 입력 그대로라 싱크가 어긋난 것처럼 보이는 경우가 있으므로, 속도 변경·트림을 할 때는 오디오 atempo·asetpts까지 한 그래프에서 설계하는 것이 안전합니다.

-filter_threads: 일부 필터는 내부적으로 스레드를 쓰며, 입력 해상도가 크면 CPU 병목이 됩니다. 반대로 과도한 스레드는 캐시 미스를 유발할 수 있어 실측으로 조정하는 편이 좋습니다.

하드웨어와 필터의 접점: CPU 메모리에 있는 프레임을 GPU 인코더로 보내려면 hwupload/hwupload_cuda 등으로 업로드해야 하고, GPU 디코더가 만든 서페이스를 CPU 필터(scale 등)로 내리려면 hwdownload가 필요합니다. «전 구간 GPU»를 기대하고도 중간에 CPU scale이 끼면 다운로드·재업로드 비용으로 이득이 사라지는 경우가 많습니다.

# 예: CUDA 디코더 출력을 유지한 채 GPU 쪽에서 크기 조정 후 NVENC로 인코딩
# (빌드·드라이버에 따라 사용 가능한 필터 이름이 다를 수 있음)
ffmpeg -hwaccel cuda -hwaccel_output_format cuda -i input.mp4 \
  -vf "scale_cuda=1280:720" \
  -c:v h264_nvenc -preset p4 -cq 23 \
  -c:a copy output.mp4

하드웨어 가속 API: VAAPI(리눅스)와 NVENC(NVIDIA)

공통 원칙: 하드웨어 가속의 이득은 «디코드·처리·인코드가 같은 메모리 공간에서 이어질 때» 극대화됩니다. 디코드만 GPU, 필터는 CPU, 인코드만 GPU처럼 섞이면 PCIe 대역·포맷 변환이 병목이 됩니다.

VAAPI (Video Acceleration API, 주로 Linux·Intel/AMD)

Intel·AMD GPU가 있는 리눅스 서버에서 흔히 쓰입니다. 디바이스 노드는 보통 /dev/dri/renderD128이며, 컨테이너·클라우드 VM에서는 render 노드 노출 여부가 먼저 확인되어야 합니다.

# 사용 가능한 VAAPI 디바이스 확인 (환경에 따라 패키지 필요)
vainfo

# VAAPI로 H.264 인코딩 (예시 — 드라이버·FFmpeg 빌드에 따라 옵션명이 다를 수 있음)
ffmpeg -init_hw_device vaapi=va:/dev/dri/renderD128 -filter_hw_device va \
  -hwaccel vaapi -hwaccel_output_format vaapi -i input.mp4 \
  -vf 'format=nv12|vaapi,hwupload' -c:v h264_vaapi -qp 23 \
  -c:a copy output.mp4

실무 체크포인트: Wayland/X11과 무관하게 헤드리스 서버에서 VAAPI를 쓰려면 권한(video 그룹)·i965/iris/mesa 드라이버 버전·FFmpeg의 --enable-vaapi 빌드가 맞아야 합니다. 실패 시 -loglevel verbose에서 vaapi 초기화 오류 메시지를 우선 확인합니다.

NVENC (NVIDIA)

NVIDIA GPU의 전용 인코더입니다. 디코딩-hwaccel cuda 또는 구형 파이프라인의 cuvid(빌드에 포함된 경우)를 쓰고, 인코딩h264_nvenc/hevc_nvenc/av1_nvenc 등으로 지정합니다. 품질 모드는 세대별로 -cq, -rc vbr, -preset p1~p7 등 표기가 나뉘어 와서 드라이버 릴리스 노트와 ffmpeg -h encoder=h264_nvenc 출력을 기준으로 잡는 것이 안전합니다.

# NVENC VBR + 품질 상한 예시 (환경에 따라 인식되지 않는 옵션은 제거)
ffmpeg -hwaccel cuda -i input.mp4 \
  -c:v h264_nvenc -preset p4 -rc vbr -cq 23 -b:v 5M -maxrate 8M -bufsize 16M \
  -c:a aac -b:a 192k output.mp4

NVENC와 VAAPI 선택: 데이터센터 GPU가 있으면 NVENC가 처리량에서 유리한 경우가 많고, CPU 내장 GPU·오픈소스 스택 위주면 VAAPI가 자연스럽습니다. 동시 세션 수는 NVENC 세대별 인코더 세션 제한(제품·드라이버에 따라 상이)이 병목이 되므로, 트랜스코딩 팜 설계 시 세션 수·지연·전력을 함께 적습니다.

스트림 매핑·메타데이터·챕터

-map은 «어떤 입력의 어떤 스트림이 출력의 몇 번째 트랙이 될지」를 명시합니다. 다국어 오디오·자막·여러 각도 영상이 있는 방송·블루레이 계열 소스에서 필수입니다. -map 0:v:0 -map 0:a:2처럼 인덱스는 0부터이며, 단순히 -c copy만 쓰면 «첫 번째 비디오+첫 오디오»가 기본이라 의도와 다르게 떨어질 수 있습니다.

# 모든 스트림을 최대한 보존해 복사 (포맷이 허용할 때)
ffmpeg -i input.mkv -map 0 -c copy output.mkv

# 메타데이터는 컨테이너·스트림 단위로 복사 또는 재작성
ffmpeg -i input.mp4 -map_metadata 0 -map_chapters 0 -c copy with_meta.mp4
ffmpeg -i input.mp4 -c copy \
  -metadata title="제목" -metadata author="작성자" \
  -metadata:s:a:0 language=kor \
  tagged.mp4

-disposition은 트랙의 기본 역할(강제 자막·청각 장애인용 보조 등)을 지정할 때 씁니다. 플레이어·스트리밍 규격이 이 값을 해석하므로, 배포 전 타깃 플랫폼 요구사항과 맞추는 것이 좋습니다.

챕터: ffprobe -show_chapters로 확인한 뒤, -map_chapters로 복사하거나 외부 파일에서 주입할 수 있습니다. 전자는 빠르고, 후자는 편집 도구와의 워크플로에 맞춰 선택합니다.

ffprobe -v error -show_chapters -show_entries format_tags=title -of json input.mp4

프로덕션에서 통하는 FFmpeg 패턴

실패를 줄이는 습관: 배치·큐 워커에서는 -nostdin으로 표준 입력이 우연히 소비되는 상황을 막고, -hide_banner -loglevel error(필요 시 -stats)로 로그 노이즈를 제어합니다. 재현 가능한 로그를 남기려면 FFmpeg 버전 문자열·전체 명령줄·입력 파일 해시를 함께 저장합니다.

진행률·모니터링: -progress url 또는 파이프로 키·값 형태의 진행 정보를 뽑아 워커가 파싱하기 쉽게 합니다. 대규모 클러스터에서는 이 값을 메트릭으로 넘겨 스톨·큐 적체를 감시합니다.

리소스: HLS·DASH 세그먼트를 만드는 워커는 동시 오픈 파일 수디스크 fsync 정책에 걸리기 쉽습니다. 로컬 SSD·tmpfs·세그먼트별 디렉터리 샤딩이 자주 쓰입니다.

입력 검증: 인코딩 전 ffprobe로 코덱·해상도·HDR 메타·가변 프레임레이트 여부를 확인하고, 이상이 있으면 전처리 필터(fps 고정, 색공간 태그 보정)를 명시적으로 넣습니다. «한 번에 긴 명령»보다 프로브 → 조건부 인코더 선택이 운영에 유리합니다.

하드웨어 파이프라인 검증: GPU 인코딩은 동일 설정이라도 드라이버 버전에 따라 화질·파일 크기가 달라질 수 있습니다. 릴리스마다 소수의 골든 샘플에 대해 VMAF·SSIM 또는 육안 검사를 자동화해 두면 회귀를 빨리 잡을 수 있습니다.

보안·견고성: 사용자가 업로드한 임의 파일명·임의 경로를 그대로 쉘에 넣지 말고, 인자 배열으로 FFmpeg를 호출합니다. 서버에서는 입력 길이·해상도 상한, 무한 필터 체인 같은 DoS성 입력에 대한 제한을 두는 것이 좋습니다.

# 배치 워커에서 흔한 «조용한» 래퍼 예시
ffmpeg -nostdin -hide_banner -loglevel error -stats \
  -i "$INPUT" -c:v libx264 -preset medium -crf 23 -c:a aac -b:a 192k \
  -movflags +faststart "$OUTPUT"

이 절의 내용은 아래 «성능 최적화» 절의 NVENC·QSV·AMF 실전 예제와 겹치지 않게 API·파이프라인 관점을 보강한 것입니다. 운영 환경(드라이버, FFmpeg 빌드 플래그)마다 최적 옵션이 달라지므로, 배포 전 ffmpeg -version-h encoder=… 출력을 기준선으로 삼으면 됩니다.


13. 성능 최적화

하드웨어 가속 - GPU의 힘 활용하기

왜 하드웨어 가속이 필요한가?

CPU 인코딩 (libx264):
- 범용 프로세서로 소프트웨어 연산
- 높은 품질, 최적 압축률
- 속도: 1080p 1분 영상 → 30-60초 소요

GPU 인코딩 (h264_nvenc):
- 전용 하드웨어 인코더 칩 사용
- 병렬 처리 (수천 개 코어)
- 속도: 1080p 1분 영상 → 5-10초 소요
- 품질: CPU 대비 약간 떨어짐 (CRF +2~3 수준)
- 파일 크기: 20-30% 더 큼

언제 GPU를 쓸까?
✅ 실시간 스트리밍 (Twitch, YouTube Live)
✅ 대량 파일 변환 (시간이 중요)
✅ 웹캠 녹화 (CPU 부하 최소화)
❌ 최종 아카이브 (최고 품질 필요)
❌ 저사양 서버 (GPU 없음)

NVIDIA GPU (NVENC) - 가장 널리 사용:

# GPU 사용 가능 여부 확인
ffmpeg -hwaccels
# 출력에 cuda가 있으면 사용 가능

ffmpeg -encoders | grep nvenc
# h264_nvenc: H.264 NVENC 인코더
# hevc_nvenc: H.265 NVENC 인코더

# H.264 GPU 인코딩
ffmpeg -hwaccel cuda -i input.mp4 \
  -c:v h264_nvenc -preset fast -b:v 5M \
  output.mp4

# 옵션 상세:
# -hwaccel cuda: CUDA 디코딩 가속 (디코딩도 GPU 사용)
# -c:v h264_nvenc: NVIDIA H.264 인코더
# -preset fast: GPU 프리셋
#   - default: 균형 (기본)
#   - fast: 빠름
#   - slow: 느림, 품질 향상
#   - lossless: 무손실 (파일 매우 큼)

# H.265 GPU 인코딩 (4K에 적합)
ffmpeg -hwaccel cuda -i input.mp4 \
  -c:v hevc_nvenc -preset fast -b:v 5M \
  output.mp4

# 품질 설정 (CQP - Constant Quantization Parameter)
ffmpeg -hwaccel cuda -i input.mp4 \
  -c:v h264_nvenc -preset slow -cq 23 \
  output.mp4
# -cq: GPU의 CRF 상당 (값이 낮을수록 고품질)
# 범위: 0-51 (CPU의 -crf와 유사)

# 2-Pass GPU 인코딩 (최고 품질)
ffmpeg -hwaccel cuda -i input.mp4 \
  -c:v h264_nvenc -preset slow -b:v 5M -multipass fullres \
  output.mp4
# -multipass fullres: GPU 2-pass (품질 향상)

Intel GPU (QSV - Quick Sync Video):

# QSV 지원 확인
ffmpeg -encoders | grep qsv
# h264_qsv: Intel H.264 인코더
# hevc_qsv: Intel H.265 인코더

# H.264 QSV 인코딩
ffmpeg -hwaccel qsv -i input.mp4 \
  -c:v h264_qsv -preset fast -global_quality 23 \
  output.mp4

# -global_quality: QSV의 품질 파라미터
#   값이 낮을수록 고품질 (CRF와 유사)
#   범위: 1-51

# H.265 QSV 인코딩
ffmpeg -hwaccel qsv -i input.mp4 \
  -c:v hevc_qsv -preset veryslow -global_quality 23 \
  output.mp4

# 해상도 변경 (GPU에서 스케일링)
ffmpeg -hwaccel qsv -i input.mp4 \
  -vf scale_qsv=1280:720 -c:v h264_qsv -global_quality 23 \
  output.mp4
# scale_qsv: GPU 스케일링 (CPU 스케일보다 빠름)

AMD GPU (AMF - Advanced Media Framework):

# AMF 지원 확인
ffmpeg -encoders | grep amf
# h264_amf: AMD H.264 인코더
# hevc_amf: AMD H.265 인코더

# H.264 AMF 인코딩
ffmpeg -i input.mp4 \
  -c:v h264_amf -quality balanced -rc cqp -qp 23 \
  output.mp4

# -quality: 인코딩 품질 모드
#   - speed: 빠름, 낮은 품질
#   - balanced: 균형 (권장)
#   - quality: 느림, 높은 품질

# -rc: 레이트 컨트롤 모드
#   - cqp: Constant QP (CRF와 유사)
#   - vbr: Variable Bitrate
#   - cbr: Constant Bitrate (스트리밍용)

# -qp: Quantization Parameter (0-51)

# H.265 AMF 인코딩
ffmpeg -i input.mp4 \
  -c:v hevc_amf -quality balanced -rc cqp -qp 23 \
  output.mp4

macOS (VideoToolbox) - Apple Silicon/Intel Mac:

# VideoToolbox 지원 확인
ffmpeg -encoders | grep videotoolbox
# h264_videotoolbox: Apple H.264 인코더
# hevc_videotoolbox: Apple H.265 인코더

# H.264 VideoToolbox 인코딩
ffmpeg -i input.mp4 \
  -c:v h264_videotoolbox -b:v 5M \
  output.mp4

# H.265 VideoToolbox 인코딩 (Apple Silicon 권장)
ffmpeg -i input.mp4 \
  -c:v hevc_videotoolbox -b:v 5M \
  output.mp4

# 품질 설정 (q:v)
ffmpeg -i input.mp4 \
  -c:v h264_videotoolbox -q:v 65 \
  output.mp4
# q:v 범위: 0-100 (높을수록 고품질)
# 권장: 60-75

하드웨어 가속 성능 비교:

테스트 환경: 1080p 60fps 1분 영상

libx264 (CPU):
  preset ultrafast: 20초 (품질 낮음)
  preset medium:    45초 (균형)
  preset slow:      90초 (고품질)
  
h264_nvenc (NVIDIA GPU):
  preset fast:      8초  (품질 중)
  preset slow:      12초 (품질 상)
  
h264_qsv (Intel GPU):
  preset fast:      15초 (품질 중)
  preset slow:      20초 (품질 상)
  
h264_amf (AMD GPU):
  quality speed:    10초 (품질 하)
  quality balanced: 15초 (품질 중)

결론:
- GPU는 CPU 대비 3-10배 빠름
- 품질은 CPU가 약간 우수
- 실시간 용도는 GPU 필수

하드웨어 가속 주의사항:

# ❌ 잘못된 사용: 디코딩만 GPU, 인코딩은 CPU
ffmpeg -hwaccel cuda -i input.mp4 -c:v libx264 output.mp4
# 효과: 거의 없음 (디코딩은 빠른데 인코딩이 느림)

# ✅ 올바른 사용: 디코딩 + 인코딩 모두 GPU
ffmpeg -hwaccel cuda -i input.mp4 -c:v h264_nvenc output.mp4
# 효과: 최대 성능 (전체 파이프라인 GPU 사용)

# GPU 메모리 부족 에러
ffmpeg -hwaccel cuda -i 4k_video.mp4 -c:v h264_nvenc output.mp4
# Error: out of memory
# 해결: 해상도 낮추기 또는 CPU 사용

# GPU 드라이버 오래됨
# 해결: 최신 드라이버 업데이트
# NVIDIA: https://www.nvidia.com/drivers
# Intel: https://www.intel.com/content/www/us/en/download-center/home.html

하드웨어 인코더 선택 가이드:

어떤 GPU 인코더를 쓸까?

NVIDIA (h264_nvenc):
✅ 가장 빠름 (Turing, Ampere 아키텍처)
✅ 최고 품질 (GPU 인코더 중)
✅ 다양한 옵션
❌ NVIDIA GPU 필요 (GTX 1000 시리즈 이상)

Intel (h264_qsv):
✅ 대부분의 Intel CPU에 내장 (7세대 이상)
✅ 추가 비용 없음
✅ 낮은 전력 소비
❌ NVIDIA보다 품질 약간 떨어짐

AMD (h264_amf):
✅ AMD GPU에서 작동
✅ 괜찮은 성능
❌ 옵션이 제한적
❌ 드라이버 호환성 문제 가능

Apple (h264_videotoolbox):
✅ 모든 Mac에서 작동
✅ Apple Silicon에서 매우 빠름
✅ 전력 효율 최고
❌ macOS 전용
❌ 옵션이 제한적

권장:
- Windows/Linux + NVIDIA → h264_nvenc
- Windows/Linux + Intel → h264_qsv
- Windows + AMD → h264_amf
- macOS → h264_videotoolbox
# NVENC VBR 예시 (같은 절 상단 NVENC 예제와 옵션 계열이 같음)
ffmpeg -hwaccel cuda -i input.mp4 \
  -c:v h264_nvenc -preset slow -rc vbr -cq 23 \
  output.mp4

멀티스레딩

# 스레드 수 지정
ffmpeg -i input.mp4 -threads 8 -c:v libx264 -crf 23 output.mp4
# 자동 (CPU 코어 수)
ffmpeg -i input.mp4 -threads 0 -c:v libx264 -crf 23 output.mp4
# 타일 인코딩 (AV1)
ffmpeg -i input.mp4 -c:v libaom-av1 -tiles 4x4 -threads 16 output.mp4

메모리 최적화

# 버퍼 크기 조절
ffmpeg -i input.mp4 \
  -bufsize 10M -maxrate 5M \
  -c:v libx264 -crf 23 \
  output.mp4
# 스트리밍 최적화 (faststart)
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -movflags +faststart output.mp4
# moov atom을 파일 앞으로 이동 (웹 스트리밍 필수)

14. 실전 명령어 모음

유튜브 최적화

# 유튜브 권장 설정
ffmpeg -i input.mp4 \
  -c:v libx264 -preset slow -crf 18 \
  -c:a aac -b:a 192k -ar 48000 \
  -pix_fmt yuv420p \
  -movflags +faststart \
  youtube.mp4
# 4K 유튜브
ffmpeg -i input.mp4 \
  -c:v libx264 -preset slow -crf 18 \
  -vf scale=3840:2160 \
  -c:a aac -b:a 192k \
  -pix_fmt yuv420p \
  -movflags +faststart \
  youtube_4k.mp4

Instagram 최적화

# Instagram 피드 (1:1)
ffmpeg -i input.mp4 \
  -vf "scale=1080:1080:force_original_aspect_ratio=decrease,pad=1080:1080:(ow-iw)/2:(oh-ih)/2" \
  -c:v libx264 -preset medium -crf 23 \
  -c:a aac -b:a 128k \
  -t 60 \
  instagram_feed.mp4
# Instagram 스토리 (9:16)
ffmpeg -i input.mp4 \
  -vf "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2" \
  -c:v libx264 -preset medium -crf 23 \
  -c:a aac -b:a 128k \
  -t 15 \
  instagram_story.mp4

웹 최적화

# 웹용 MP4 (빠른 시작)
ffmpeg -i input.mp4 \
  -c:v libx264 -preset medium -crf 23 \
  -c:a aac -b:a 128k \
  -movflags +faststart \
  web.mp4
# WebM (VP9)
ffmpeg -i input.mp4 \
  -c:v libvpx-vp9 -b:v 2M \
  -c:a libopus -b:a 128k \
  web.webm
# 다중 포맷 (호환성)
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -movflags +faststart web.mp4
ffmpeg -i input.mp4 -c:v libvpx-vp9 -b:v 2M web.webm

15. 라이브 스트리밍

RTMP 서버 설정

Nginx-RTMP:

# nginx.conf
rtmp {
    server {
        listen 1935;
        
        application live {
            live on;
            record off;
            
            # HLS 생성
            hls on;
            hls_path /tmp/hls;
            hls_fragment 3;
            hls_playlist_length 60;
        }
    }
}
http {
    server {
        listen 8080;
        
        location /hls {
            types {
                application/vnd.apple.mpegurl m3u8;
                video/mp2t ts;
            }
            root /tmp;
            add_header Cache-Control no-cache;
            add_header Access-Control-Allow-Origin *;
        }
    }
}

스트리밍 시작:

# 파일 스트리밍
ffmpeg -re -i video.mp4 \
  -c:v libx264 -preset veryfast -b:v 3M -maxrate 3M -bufsize 6M \
  -pix_fmt yuv420p -g 60 \
  -c:a aac -b:a 128k -ar 44100 \
  -f flv rtmp://localhost/live/stream
# 웹캠 스트리밍
ffmpeg -f avfoundation -i "0:0" \
  -c:v libx264 -preset veryfast -b:v 2M \
  -c:a aac -b:a 128k \
  -f flv rtmp://localhost/live/webcam
# 화면 녹화 + 스트리밍 (macOS)
ffmpeg -f avfoundation -capture_cursor 1 -i "1:0" \
  -c:v libx264 -preset veryfast -b:v 3M \
  -c:a aac -b:a 128k \
  -f flv rtmp://localhost/live/screen

재스트리밍 (Restreaming)

# RTMP → HLS
ffmpeg -i rtmp://source/live/stream \
  -c:v copy -c:a copy \
  -hls_time 3 -hls_list_size 10 \
  -hls_flags delete_segments \
  output.m3u8
# 다중 플랫폼 동시 스트리밍
ffmpeg -re -i input.mp4 \
  -c:v libx264 -preset veryfast -b:v 3M -c:a aac -b:a 128k \
  -f flv rtmp://a.rtmp.youtube.com/live2/YOUTUBE_KEY \
  -c:v libx264 -preset veryfast -b:v 3M -c:a aac -b:a 128k \
  -f flv rtmp://live.twitch.tv/app/TWITCH_KEY

16. 트러블슈팅

일반적인 문제

1) “Unknown encoder ‘libx264’”

# 원인: FFmpeg이 libx264 없이 컴파일됨
# 해결: 전체 버전 재설치
# macOS
brew reinstall ffmpeg
# Ubuntu (full 버전)
sudo apt install ffmpeg

2) “Conversion failed”

# 상세 로그 확인
ffmpeg -i input.mp4 -loglevel debug output.mp4
# 코덱 지원 확인
ffmpeg -codecs | grep h264
ffmpeg -encoders | grep h264

3) “Invalid data found when processing input”

# 원인: 손상된 파일
# 해결: 에러 무시 옵션
ffmpeg -err_detect ignore_err -i input.mp4 -c copy output.mp4

4) 오디오/비디오 동기화 문제

# 오디오 지연 (0.5초)
ffmpeg -i input.mp4 -itsoffset 0.5 -i input.mp4 -map 0:v -map 1:a -c copy output.mp4
# 오디오 앞당기기
ffmpeg -i input.mp4 -itsoffset -0.5 -i input.mp4 -map 0:v -map 1:a -c copy output.mp4

성능 문제

# 1. 하드웨어 가속 사용
ffmpeg -hwaccel cuda -i input.mp4 -c:v h264_nvenc output.mp4
# 2. 빠른 프리셋
ffmpeg -i input.mp4 -c:v libx264 -preset ultrafast output.mp4
# 3. 재인코딩 없이 복사
ffmpeg -i input.mp4 -c copy output.mp4
# 4. 낮은 해상도
ffmpeg -i input.mp4 -vf scale=640:360 output.mp4

17. 실전 스크립트

자동 인코딩 워커

Python (Celery):

from celery import Celery
import ffmpeg
import os
app = Celery('video_worker', broker='redis://localhost:6379/0')
@app.task
def encode_video(input_path, output_path, resolution='1280x720', crf=23):
    """비디오 인코딩 작업"""
    try:
        width, height = resolution.split('x')
        
        (
            ffmpeg
            .input(input_path)
            .filter('scale', width, height)
            .output(
                output_path,
                vcodec='libx264',
                preset='slow',
                crf=crf,
                acodec='aac',
                audio_bitrate='192k',
                movflags='+faststart'
            )
            .overwrite_output()
            .run(capture_stdout=True, capture_stderr=True)
        )
        
        return {'status': 'success', 'output': output_path}
    
    except ffmpeg.Error as e:
        return {'status': 'error', 'message': e.stderr.decode()}
@app.task
def create_thumbnails(input_path, output_dir, count=3):
    """썸네일 생성 작업"""
    try:
        probe = ffmpeg.probe(input_path)
        duration = float(probe['format']['duration'])
        
        os.makedirs(output_dir, exist_ok=True)
        
        thumbnails = []
        for i in range(count):
            timestamp = duration * (i + 1) / (count + 1)
            output_path = os.path.join(output_dir, f'thumb_{i}.jpg')
            
            (
                ffmpeg
                .input(input_path, ss=timestamp)
                .output(output_path, vframes=1, q=2)
                .overwrite_output()
                .run(capture_stdout=True, capture_stderr=True)
            )
            
            thumbnails.append(output_path)
        
        return {'status': 'success', 'thumbnails': thumbnails}
    
    except ffmpeg.Error as e:
        return {'status': 'error', 'message': e.stderr.decode()}
# 사용
if __name__ == '__main__':
    # 작업 큐에 추가
    encode_video.delay('input.mp4', 'output.mp4', resolution='1920x1080', crf=20)
    create_thumbnails.delay('input.mp4', './thumbnails', count=5)

진행률 웹소켓

Node.js + Socket.io:

const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const ffmpeg = require('fluent-ffmpeg');
const multer = require('multer');
const app = express();
const server = http.createServer(app);
const io = socketIo(server);
const upload = multer({ dest: 'uploads/' });
app.post('/convert', upload.single('video'), (req, res) => {
  const inputPath = req.file.path;
  const outputPath = `processed/${req.file.filename}.mp4`;
  const socketId = req.body.socketId;
  
  ffmpeg(inputPath)
    .output(outputPath)
    .videoCodec('libx264')
    .audioCodec('aac')
    .on('start', (cmd) => {
      console.log('Started:', cmd);
    })
    .on('progress', (progress) => {
      // 클라이언트에게 진행률 전송
      io.to(socketId).emit('progress', {
        percent: progress.percent,
        currentTime: progress.timemark,
        fps: progress.currentFps
      });
    })
    .on('end', () => {
      io.to(socketId).emit('complete', {
        url: `/processed/${req.file.filename}.mp4`
      });
    })
    .on('error', (err) => {
      io.to(socketId).emit('error', {
        message: err.message
      });
    })
    .run();
  
  res.json({ message: 'Processing started' });
});
io.on('connection', (socket) => {
  console.log('Client connected:', socket.id);
  
  socket.on('disconnect', () => {
    console.log('Client disconnected:', socket.id);
  });
});
server.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

클라이언트 (HTML):

<!DOCTYPE html>
<html>
<head>
    <title>Video Converter</title>
    <script src="/socket.io/socket.io.js"></script>
</head>
<body>
    <h1>Video Converter</h1>
    
    <input type="file" id="videoFile" accept="video/*">
    <button onclick="uploadVideo()">Upload & Convert</button>
    
    <div id="progress" style="display: none;">
        <p>Progress: <span id="percent">0</span>%</p>
        <progress id="progressBar" value="0" max="100"></progress>
    </div>
    
    <div id="result" style="display: none;">
        <p>✅ Conversion complete!</p>
        <video id="outputVideo" controls width="640"></video>
    </div>
    
    <script>
        const socket = io();
        
        function uploadVideo() {
            const fileInput = document.getElementById('videoFile');
            const file = fileInput.files[0];
            
            if (!file) {
                alert('Please select a video file');
                return;
            }
            
            const formData = new FormData();
            formData.append('video', file);
            formData.append('socketId', socket.id);
            
            document.getElementById('progress').style.display = 'block';
            
            fetch('/convert', {
                method: 'POST',
                body: formData
            });
        }
        
        socket.on('progress', (data) => {
            document.getElementById('percent').textContent = data.percent.toFixed(1);
            document.getElementById('progressBar').value = data.percent;
        });
        
        socket.on('complete', (data) => {
            document.getElementById('progress').style.display = 'none';
            document.getElementById('result').style.display = 'block';
            document.getElementById('outputVideo').src = data.url;
        });
        
        socket.on('error', (data) => {
            alert('Error: ' + data.message);
        });
    </script>
</body>
</html>

18. 고급 활용

자막 처리

# 자막 추가 (하드서브)
ffmpeg -i video.mp4 -vf subtitles=subtitle.srt output.mp4
# 자막 추가 (소프트서브)
ffmpeg -i video.mp4 -i subtitle.srt -c copy -c:s mov_text output.mp4
# 자막 추출
ffmpeg -i video.mp4 -map 0:s:0 subtitle.srt
# 자막 스타일 변경
ffmpeg -i video.mp4 \
  -vf "subtitles=subtitle.srt:force_style='FontName=Arial,FontSize=24,PrimaryColour=&H00FFFF'" \
  output.mp4

메타데이터 편집

# 메타데이터 확인
ffprobe -show_format input.mp4
# 메타데이터 추가
ffmpeg -i input.mp4 -c copy \
  -metadata title="My Video" \
  -metadata author="Alice" \
  -metadata comment="Created with FFmpeg" \
  output.mp4
# 메타데이터 제거
ffmpeg -i input.mp4 -c copy -map_metadata -1 output.mp4

화면 녹화

# macOS (화면 + 오디오)
ffmpeg -f avfoundation -capture_cursor 1 -i "1:0" \
  -c:v libx264 -preset ultrafast -crf 23 \
  -c:a aac -b:a 128k \
  screen_recording.mp4
# Linux (X11)
ffmpeg -f x11grab -s 1920x1080 -i :0.0 \
  -c:v libx264 -preset ultrafast -crf 23 \
  screen_recording.mp4
# Windows (GDI)
ffmpeg -f gdigrab -i desktop \
  -c:v libx264 -preset ultrafast -crf 23 \
  screen_recording.mp4

19. 프로덕션 팁

에러 처리

import ffmpeg
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def safe_convert(input_path, output_path, max_retries=3):
    """재시도 로직 포함 변환"""
    for attempt in range(max_retries):
        try:
            logger.info(f"Attempt {attempt + 1}/{max_retries}")
            
            (
                ffmpeg
                .input(input_path)
                .output(output_path, vcodec='libx264', crf=23)
                .global_args('-loglevel', 'error')
                .overwrite_output()
                .run(capture_stdout=True, capture_stderr=True)
            )
            
            logger.info("✅ Success!")
            return True
            
        except ffmpeg.Error as e:
            logger.error(f"❌ Attempt {attempt + 1} failed: {e.stderr.decode()}")
            
            if attempt == max_retries - 1:
                logger.error("All attempts failed")
                raise
    
    return False

큐 시스템

import ffmpeg
import redis
import json
import time
from threading import Thread
class VideoQueue:
    def __init__(self):
        self.redis = redis.Redis(host='localhost', port=6379, db=0)
        self.queue_key = 'video:queue'
        self.processing_key = 'video:processing'
    
    def add_job(self, input_path, output_path, options=None):
        """작업 추가"""
        job = {
            'input': input_path,
            'output': output_path,
            'options': options or {},
            'status': 'queued',
            'created_at': time.time()
        }
        
        job_id = f"job:{time.time()}"
        self.redis.lpush(self.queue_key, json.dumps(job))
        
        return job_id
    
    def process_job(self, job_data):
        """작업 처리"""
        job = json.loads(job_data)
        
        try:
            # FFmpeg 실행
            stream = ffmpeg.input(job['input'])
            
            # 옵션 적용
            if 'scale' in job['options']:
                stream = stream.filter('scale', *job['options']['scale'].split('x'))
            
            stream.output(
                job['output'],
                vcodec='libx264',
                crf=job['options'].get('crf', 23),
                preset=job['options'].get('preset', 'medium')
            ).overwrite_output().run()
            
            print(f"✅ Job complete: {job['output']}")
            
        except ffmpeg.Error as e:
            print(f"❌ Job failed: {e.stderr.decode()}")
    
    def worker(self):
        """워커 스레드"""
        print("Worker started")
        
        while True:
            # 큐에서 작업 가져오기
            job_data = self.redis.brpop(self.queue_key, timeout=5)
            
            if job_data:
                _, job_json = job_data
                self.redis.lpush(self.processing_key, job_json)
                
                self.process_job(job_json)
                
                self.redis.lrem(self.processing_key, 1, job_json)
    
    def start_workers(self, num_workers=4):
        """워커 시작"""
        threads = []
        for i in range(num_workers):
            t = Thread(target=self.worker, daemon=True)
            t.start()
            threads.append(t)
        
        return threads
# 사용
queue = VideoQueue()
# 작업 추가
queue.add_job('input1.mp4', 'output1.mp4', {'scale': '1280x720', 'crf': 20})
queue.add_job('input2.mp4', 'output2.mp4', {'scale': '1920x1080', 'crf': 18})
# 워커 시작 (4개)
workers = queue.start_workers(num_workers=4)
# 메인 스레드 유지
for worker in workers:
    worker.join()

20. 성능 벤치마크

인코딩 속도 비교

# 테스트 스크립트
#!/bin/bash
INPUT="test_4k.mp4"
echo "=== FFmpeg Encoding Benchmark ==="
echo
# H.264 프리셋 비교
for preset in ultrafast superfast veryfast faster fast medium slow slower veryslow; do
    echo "Testing preset: $preset"
    
    start=$(date +%s)
    ffmpeg -i "$INPUT" -c:v libx264 -preset "$preset" -crf 23 -an -f null - 2>&1 | grep "frame="
    end=$(date +%s)
    
    duration=$((end - start))
    echo "Time: ${duration}s"
    echo
done
# 코덱 비교
for codec in libx264 libx265 libvpx-vp9; do
    echo "Testing codec: $codec"
    
    start=$(date +%s)
    ffmpeg -i "$INPUT" -c:v "$codec" -b:v 5M -an -f null - 2>&1 | grep "frame="
    end=$(date +%s)
    
    duration=$((end - start))
    echo "Time: ${duration}s"
    echo
done

결과 예시 (4K 1분 영상):

프리셋 비교 (H.264):
ultrafast:  15초  (파일: 50MB)
veryfast:   25초  (파일: 40MB)
medium:     45초  (파일: 30MB)
slow:       90초  (파일: 25MB)
veryslow:   180초 (파일: 23MB)
코덱 비교 (medium 프리셋):
H.264:      45초  (파일: 30MB)
H.265:      120초 (파일: 15MB)
VP9:        180초 (파일: 18MB)

21. 유용한 원라이너

# 모든 MP4를 720p로 일괄 변환
for f in *.mp4; do ffmpeg -i "$f" -vf scale=1280:720 -c:v libx264 -crf 23 "720p_$f"; done
# 모든 동영상에서 오디오 추출
for f in *.mp4; do ffmpeg -i "$f" -vn -c:a libmp3lame -b:a 192k "${f%.mp4}.mp3"; done
# 모든 동영상의 첫 프레임 추출
for f in *.mp4; do ffmpeg -i "$f" -vframes 1 "${f%.mp4}.jpg"; done
# 모든 이미지를 동영상으로 (슬라이드쇼)
ffmpeg -framerate 1 -pattern_type glob -i '*.jpg' -c:v libx264 -pix_fmt yuv420p slideshow.mp4
# 오디오 파일들을 하나로 병합
ffmpeg -i "concat:audio1.mp3|audio2.mp3|audio3.mp3" -c copy output.mp3
# 동영상 정보 CSV로 저장
for f in *.mp4; do echo "$f,$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$f")"; done > videos.csv

22. Docker로 FFmpeg 사용

Dockerfile

FROM ubuntu:22.04
# FFmpeg 설치
RUN apt-get update && \
    apt-get install -y ffmpeg && \
    rm -rf /var/lib/apt/lists/*
WORKDIR /data
ENTRYPOINT [ffmpeg]

빌드 및 사용:

# 빌드
docker build -t my-ffmpeg .
# 사용
docker run --rm -v $(pwd):/data my-ffmpeg \
  -i /data/input.mp4 \
  -c:v libx264 -crf 23 \
  /data/output.mp4
# 별칭 생성
alias dffmpeg='docker run --rm -v $(pwd):/data my-ffmpeg'
# 사용
dffmpeg -i input.mp4 -c:v libx264 -crf 23 output.mp4

트러블슈팅

실무에서 자주 마주치는 문제들과 해결 방법입니다.

문제 1: “height not divisible by 2” 에러

증상:

[libx264 @ 0x...] height not divisible by 2 (1281x721)

원인: H.264 코덱은 짝수 픽셀을 요구합니다. 해결:

# ❌ 잘못된 예
ffmpeg -i input.mp4 -vf scale=1281:721 output.mp4
# ✅ 올바른 예
ffmpeg -i input.mp4 -vf scale=1280:-2 output.mp4
# -2는 2의 배수로 자동 조정

문제 2: 오디오/비디오 싱크 안 맞음

증상: 영상과 음성이 어긋남 원인: 프레임레이트 변환, 잘못된 타임스탬프 해결:

# 방법 1: 오디오 타임스탬프 재계산
ffmpeg -i input.mp4 -c:v copy -c:a copy -async 1 output.mp4
# 방법 2: 오디오/비디오 재동기화
ffmpeg -i input.mp4 -c:v libx264 -c:a aac -vsync 1 -async 1 output.mp4
# 방법 3: 오디오 지연 수정 (0.5초 지연)
ffmpeg -i input.mp4 -itsoffset 0.5 -i input.mp4 -map 0:v -map 1:a -c copy output.mp4

문제 3: “moov atom not found” 에러

증상: 웹 브라우저에서 재생 안 됨, 다운로드 완료 후에만 재생 원인: MP4의 메타데이터(moov atom)가 파일 끝에 위치 해결:

# 인코딩 시 faststart 옵션 추가
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -movflags +faststart output.mp4
# 이미 인코딩된 파일 수정 (빠름)
ffmpeg -i input.mp4 -c copy -movflags +faststart output.mp4

문제 4: 인코딩이 너무 느림

해결 방법:

# 1. 프리셋을 빠르게
ffmpeg -i input.mp4 -c:v libx264 -preset veryfast -crf 23 output.mp4
# 2. 해상도 낮추기
ffmpeg -i input.mp4 -vf scale=1280:-2 -c:v libx264 -crf 23 output.mp4
# 3. GPU 인코더 사용 (NVIDIA)
ffmpeg -i input.mp4 -c:v h264_nvenc -preset fast -crf 23 output.mp4
# 4. GPU 인코더 사용 (Intel QSV)
ffmpeg -i input.mp4 -c:v h264_qsv -preset fast -global_quality 23 output.mp4
# 5. GPU 인코더 사용 (AMD)
ffmpeg -i input.mp4 -c:v h264_amf -quality balanced -rc cqp -qp 23 output.mp4
# 6. 병렬 처리 (여러 파일)
parallel -j 4 ffmpeg -i {} -c:v libx264 -crf 23 {.}_output.mp4 ::: *.mp4

문제 5: 파일 크기가 너무 큼

해결:

# 1. CRF 값 높이기 (품질 낮추기)
ffmpeg -i input.mp4 -c:v libx264 -crf 28 output.mp4
# 2. 해상도 낮추기
ffmpeg -i input.mp4 -vf scale=1280:-2 -c:v libx264 -crf 23 output.mp4
# 3. 프레임레이트 낮추기
ffmpeg -i input.mp4 -r 24 -c:v libx264 -crf 23 output.mp4
# 4. H.265 사용 (더 작은 파일)
ffmpeg -i input.mp4 -c:v libx265 -crf 28 output.mp4
# 5. 2-Pass 인코딩으로 목표 크기 맞추기
# 목표: 100MB, 10분 영상 = 100MB / 600초 = 1.33Mbps
ffmpeg -i input.mp4 -c:v libx264 -b:v 1M -pass 1 -f null /dev/null
ffmpeg -i input.mp4 -c:v libx264 -b:v 1M -pass 2 -c:a aac -b:a 128k output.mp4

문제 6: “Conversion failed” 또는 “Invalid data found”

원인: 손상된 파일, 지원하지 않는 코덱 해결:

# 1. 파일 정보 확인
ffprobe input.mp4
# 2. 에러 무시하고 강제 변환
ffmpeg -err_detect ignore_err -i input.mp4 -c:v libx264 -crf 23 output.mp4
# 3. 손상된 부분 건너뛰기
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -max_muxing_queue_size 9999 output.mp4
# 4. 재인코딩 없이 복사 시도
ffmpeg -i input.mp4 -c copy output.mp4

문제 7: 메모리 부족 에러

해결:

# 1. 버퍼 크기 조정
ffmpeg -i input.mp4 -max_muxing_queue_size 9999 -c:v libx264 output.mp4
# 2. 파일을 나눠서 처리
ffmpeg -i input.mp4 -ss 0 -t 600 -c:v libx264 part1.mp4
ffmpeg -i input.mp4 -ss 600 -t 600 -c:v libx264 part2.mp4
# 나중에 병합
# 3. 스레드 수 제한
ffmpeg -i input.mp4 -threads 2 -c:v libx264 output.mp4

문제 8: 색상이 이상함 (회색빛, 채도 낮음)

원인: 색공간 변환 문제 해결:

# YUV 색공간 명시
ffmpeg -i input.mp4 -vf scale=1280:-2 -pix_fmt yuv420p -c:v libx264 output.mp4
# 색공간 정보 보존
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -color_primaries bt709 \
  -color_trc bt709 -colorspace bt709 output.mp4

문제 9: 자막이 안 나옴

해결:

# 1. 자막 스트림 확인
ffprobe -show_streams input.mkv
# 2. 자막 포함하여 변환
ffmpeg -i input.mkv -c:v libx264 -c:a aac -c:s mov_text output.mp4
# 3. 자막 하드코딩 (영상에 구워넣기)
ffmpeg -i video.mp4 -vf subtitles=subtitle.srt output.mp4
# 4. 외부 자막 파일 추가
ffmpeg -i video.mp4 -i subtitle.srt -c:v copy -c:a copy -c:s mov_text output.mp4

문제 10: FFmpeg 설치 확인

# 버전 확인
ffmpeg -version
# 지원 코덱 확인
ffmpeg -codecs | grep h264
ffmpeg -codecs | grep hevc
# 지원 포맷 확인
ffmpeg -formats | grep mp4
# 하드웨어 인코더 확인
ffmpeg -encoders | grep nvenc  # NVIDIA
ffmpeg -encoders | grep qsv    # Intel
ffmpeg -encoders | grep amf    # AMD

FAQ

Q1. FFmpeg와 HandBrake는 어떻게 나누면 되나? A:

  • FFmpeg: 명령줄 기반, 자동화·스크립트·서버에 적합, 세밀한 제어 가능
  • HandBrake: GUI 기반, 직관적, 빠른 설정, 개인 사용에 편리 선택 가이드:
  • 한두 개 파일 변환: HandBrake
  • 배치 처리, 자동화: FFmpeg
  • 서버/CI/CD 파이프라인: FFmpeg
  • 복잡한 필터, 스트리밍: FFmpeg
  • 처음 시작하는 초보자: HandBrake Q2. 코덱은 무엇을 쓰면 되나? A: 상황에 따라 다릅니다: | 상황 | 추천 코덱 | 이유 | |------|----------|------| | 최대 호환성 | H.264 | 모든 기기 지원 | | 파일 크기 최소화 | H.265 | 40-50% 작음 | | 웹 스트리밍 | H.264 또는 VP9 | 브라우저 지원 | | YouTube 업로드 | H.264 | 플랫폼 권장 | | 4K 영상 | H.265 | 효율적 압축 | | 미래 대비 | AV1 | 차세대 표준 | 실전 팁:
  • 일반 용도: H.264 (libx264)
  • 저장 공간 부족: H.265 (libx265)
  • 웹 전용: VP9 (libvpx-vp9) Q3. CRF와 비트레이트 중 무엇을 쓰나? A: 대부분의 경우 CRF를 권장합니다. CRF 사용 (권장):
  • ✅ 일정한 품질 유지
  • ✅ 간단한 사용법
  • ✅ 대부분의 용도에 적합
  • ❌ 파일 크기 예측 어려움
ffmpeg -i input.mp4 -c:v libx264 -crf 23 output.mp4

비트레이트 사용:

  • ✅ 정확한 파일 크기
  • ✅ 스트리밍 대역폭 제어
  • ❌ 복잡한 장면에서 품질 저하
  • ❌ 2-Pass 필요 시 시간 2배
ffmpeg -i input.mp4 -c:v libx264 -b:v 5M output.mp4

선택 가이드:

  • 일반 파일 변환: CRF 23
  • YouTube 업로드: CRF 18-20
  • 웹 스트리밍: 비트레이트 2-5M
  • 방송 송출: 비트레이트 (2-Pass)
  • 모바일: CRF 26-28 Q4. 인코딩이 너무 느리다 A: 여러 최적화 방법이 있습니다: 1. 프리셋 조정 (가장 쉬움)
# 느림 (현재)
ffmpeg -i input.mp4 -preset slow -crf 23 output.mp4
# 빠름 (권장)
ffmpeg -i input.mp4 -preset veryfast -crf 23 output.mp4
# 속도: 3-5배 향상, 파일 크기: 10-20% 증가

2. GPU 인코더 사용 (가장 효과적)

# NVIDIA GPU
ffmpeg -i input.mp4 -c:v h264_nvenc -preset fast -crf 23 output.mp4
# 속도: 5-10배 향상
# Intel GPU
ffmpeg -i input.mp4 -c:v h264_qsv -preset fast output.mp4
# AMD GPU
ffmpeg -i input.mp4 -c:v h264_amf -quality balanced output.mp4

3. 해상도 낮추기

ffmpeg -i input_4k.mp4 -vf scale=1920:-2 -crf 23 output_1080p.mp4
# 4K → 1080p: 4배 빠름

4. 병렬 처리 (여러 파일)

# GNU Parallel 사용
parallel -j 4 ffmpeg -i {} -crf 23 {.}_output.mp4 ::: *.mp4
# 4개 파일 동시 처리

5. 프레임레이트 낮추기

ffmpeg -i input.mp4 -r 24 -crf 23 output.mp4
# 60fps → 24fps: 2.5배 빠름

속도 vs 품질 비교:

방법속도 향상품질/크기 영향
preset veryfast3-5배크기 +10-20%
GPU 인코더5-10배크기 +20-30%
해상도 감소4배 (4K→1080p)해상도 감소
프레임레이트 감소2배 (60→30fps)부드러움 감소
Q5. 화질 손실을 최소화하려면?
A: 상황별 최적 전략:
1. 재인코딩 없이 변환 (무손실)
# 컨테이너만 변경
ffmpeg -i input.mkv -c copy output.mp4
# 장점: 초고속, 무손실
# 단점: 해상도/품질 조정 불가

2. 고품질 재인코딩 (거의 무손실)

# CRF 17-18 사용
ffmpeg -i input.mp4 -c:v libx264 -preset slow -crf 17 output.mp4
# 시각적으로 무손실, 파일 크기 큼

3. 편집용 중간 코덱

# ProRes (macOS)
ffmpeg -i input.mp4 -c:v prores_ks -profile:v 3 output.mov
# DNxHD (크로스 플랫폼)
ffmpeg -i input.mp4 -c:v dnxhd -b:v 185M output.mov
# 무손실 H.264
ffmpeg -i input.mp4 -c:v libx264 -preset slow -crf 0 output.mp4

4. 다단계 인코딩 최소화

# ❌ 나쁜 예: 3번 인코딩
원본 편집 효과 최종
(품질 손실 누적)
# ✅ 좋은 예: 1번 인코딩
원본 [편집+효과 동시] 최종

5. 적절한 코덱 선택

# 보관용: 무손실 또는 고품질
ffmpeg -i input.mp4 -c:v libx264 -crf 17 -preset slow archive.mp4
# 배포용: 균형잡힌 품질
ffmpeg -i input.mp4 -c:v libx264 -crf 20 -preset slow final.mp4
# 웹용: 적정 품질
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -preset medium web.mp4

품질 우선순위:

  1. 재인코딩 피하기 (-c copy)
  2. 낮은 CRF 값 (17-20)
  3. 느린 프리셋 (slow, slower)
  4. 적절한 해상도 유지
  5. 2-Pass 인코딩 (필요시) Q6. 웹 스트리밍용 최적 설정은? A:
# 웹 최적화 (progressive download)
ffmpeg -i input.mp4 \
  -c:v libx264 -preset medium -crf 23 \
  -c:a aac -b:a 128k \
  -movflags +faststart \
  -vf scale=1920:-2 \
  web_optimized.mp4
# 핵심 옵션:
# -movflags +faststart: 메타데이터를 앞으로 (즉시 재생)
# -crf 23: 웹용 적정 품질
# -b:a 128k: 오디오 대역폭 절약

Q7. 여러 파일을 하나로 합치려면? A:

# 방법 1: concat 프로토콜 (같은 코덱)
ffmpeg -i "concat:file1.mp4|file2.mp4|file3.mp4" -c copy output.mp4
# 방법 2: concat demuxer (권장)
# 1. 파일 목록 생성
echo "file 'file1.mp4'" > list.txt
echo "file 'file2.mp4'" >> list.txt
echo "file 'file3.mp4'" >> list.txt
# 2. 병합
ffmpeg -f concat -safe 0 -i list.txt -c copy output.mp4
# 방법 3: filter_complex (다른 코덱/해상도)
ffmpeg -i file1.mp4 -i file2.mp4 -i file3.mp4 \
  -filter_complex "[0:v][0:a][1:v][1:a][2:v][2:a]concat=n=3:v=1:a=1[v][a]" \
  -map "[v]" -map "[a]" output.mp4

Q8. 특정 구간만 자르려면? A:

# 빠른 방법 (재인코딩 없음, 키프레임에서만 정확)
ffmpeg -i input.mp4 -ss 00:01:30 -to 00:02:45 -c copy output.mp4
# 정확한 방법 (재인코딩, 느림)
ffmpeg -i input.mp4 -ss 00:01:30 -to 00:02:45 -c:v libx264 -crf 23 output.mp4
# 시작 시간과 길이로 지정
ffmpeg -i input.mp4 -ss 00:01:30 -t 00:01:15 -c copy output.mp4
# -ss: 시작 시간
# -t: 길이
# -to: 종료 시간

내부 동작과 핵심 메커니즘

이 글의 주제는 「FFmpeg 실전 가이드 | 동영상/오디오 처리」입니다. 앞선 튜토리얼을 구현·런타임 관점에서 다시 압축합니다. 구성 요소 간 책임 분리와 관측 가능한 지점을 기준으로 “입력이 어디서 검증되고, 핵심 연산이 어디서 일어나며, 부작용(I/O·네트워크·디스크)·동시성이 어디서 터지는가”를 한 장면으로 그리면 장애 분석이 빨라집니다.

처리 파이프라인(개념도)

flowchart TD
  A[입력·요청·이벤트] --> B[파싱·검증·디코딩]
  B --> C[핵심 연산·상태 전이]
  C --> D[부작용: I/O·네트워크·동시성]
  D --> E[결과·관측·저장]

경계에서의 지연·실패(시퀀스 관점)

sequenceDiagram
  participant C as 클라이언트/호출자
  participant B as 경계(프로세스·런타임·게이트웨이)
  participant D as 의존성(외부 API·DB·큐)
  C->>B: 요청/이벤트
  B->>D: 조회·쓰기·RPC
  D-->>B: 지연·부분 실패·재시도 가능
  B-->>C: 응답 또는 오류(코드·상관 ID)

알고리즘·프로토콜·리소스 관점 체크포인트

  • 불변 조건(Invariant): 각 단계가 만족해야 하는 조건(버퍼 경계, 프로토콜 상태, 트랜잭션 격리, 파일 디스크립터 상한)을 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
  • 결정성: 동일 입력에 동일 출력이 보장되는 순수 층과, 시간·네트워크·스레드 스케줄에 의해 달라질 수 있는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
  • 경계 비용: 직렬화/역직렬화, 문자 인코딩, syscall 횟수, 락 경합, GC·할당, 캐시 미스처럼 누적 비용을 의심 목록에 넣습니다.
  • 백프레셔: 생산자가 소비자보다 빠를 때(소켓 버퍼, 큐 깊이, 스트림) 어디서 어떤 신호로 속도를 줄일지 정의합니다.

프로덕션 운영 패턴

실서비스에서는 기능과 함께 관측·배포·보안·비용·규제가 동시에 요구됩니다.

영역운영 관점 질문
관측성요청 단위 상관 ID, 에러율/지연 분위수(p95/p99), 의존성 타임아웃·재시도가 대시보드에 보이는가
안전성입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가
신뢰성재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가
성능캐시 계층·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가
배포롤백 룬북, 카나리/블루그린, 마이그레이션 호환성·플래그가 문서화되어 있는가
용량피크 트래픽·디스크·파일 디스크립터·스레드 풀 상한을 주기적으로 검증하는가

스테이징은 데이터 양·네트워크 RTT·동시성을 가능한 한 프로덕션에 가깝게 맞추는 것이 재현율을 높입니다.


확장 예시: 엔드투엔드 미니 시나리오

「FFmpeg 실전 가이드 | 동영상/오디오 처리」을 실제 배포·운영 흐름으로 옮긴 체크리스트형 시나리오입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.

  1. 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드 표를 API 또는 이벤트 경계에 둔다.
  2. 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 한 화면(로그+메트릭+트레이스)에서 추적한다.
  3. 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
  4. 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지(또는 피처 플래그) 확인한다.
  5. 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값이 기대 범위인지 본다.

의사코드 스케치(프레임워크 무관)

handle(request):
  ctx = newCorrelationId()
  validated = validateSchema(request)        // 경계에서 거절
  authorize(validated, ctx)                  // 권한·테넌트
  result = domainCore(validated)             // 순수에 가까운 규칙
  persistOrEmit(result, idempotentKey)       // I/O: 멱등·재시도 정책
  recordMetrics(ctx, latency, outcome)
  return result

문제 해결(Troubleshooting)

증상가능 원인조치
간헐적 실패레이스, 타임아웃, 외부 의존성 불안정, DNS최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검
성능 저하N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거
메모리 증가캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납상한·TTL·힙/FD 스냅샷 비교
빌드·배포만 실패환경 변수, 권한, 플랫폼 차이, lockfileCI 로그와 로컬 diff, 런타임·이미지 버전 핀
설정이 로컬과 다름프로필·시크릿·기본값, 지역 리전단일 소스(예: 스키마 검증된 설정)와 배포 매트릭스 표준화
데이터 불일치비멱등 재시도, 부분 쓰기, 캐시 무효화 누락멱등 키·아웃박스·트랜잭션 경계 재검토

권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.

요약

핵심 정리

FFmpeg란?

  • 가장 강력한 오픈소스 멀티미디어 프레임워크
  • 명령줄 도구 (ffmpeg, ffprobe, ffplay)
  • 프로그래밍 라이브러리 (libav 계열)
  • 거의 모든 비디오/오디오 포맷 지원
  • YouTube, Netflix 등 산업 표준 핵심 명령어 치트시트:
# 기본 변환 (자동 코덱)
ffmpeg -i input.mp4 output.avi
# 고품질 H.264 인코딩 (권장)
ffmpeg -i input.mp4 -c:v libx264 -preset slow -crf 20 -c:a aac -b:a 192k output.mp4
# 빠른 변환 (재인코딩 없음)
ffmpeg -i input.mkv -c copy output.mp4
# 해상도 변경
ffmpeg -i input.mp4 -vf scale=1280:-2 output.mp4
# 특정 구간 자르기
ffmpeg -i input.mp4 -ss 00:01:30 -to 00:02:45 -c copy output.mp4
# 썸네일 추출
ffmpeg -i input.mp4 -ss 00:00:05 -vframes 1 -q:v 2 thumbnail.jpg
# 오디오 추출
ffmpeg -i input.mp4 -vn -c:a libmp3lame -b:a 192k audio.mp3
# HLS 스트리밍 생성
ffmpeg -i input.mp4 -hls_time 10 -hls_playlist_type vod playlist.m3u8
# 파일 정보 확인
ffprobe -show_format -show_streams input.mp4
# 여러 파일 병합
ffmpeg -f concat -safe 0 -i list.txt -c copy output.mp4

상황별 최적 설정:

# YouTube 업로드 (고품질)
ffmpeg -i input.mp4 \
  -c:v libx264 -preset slow -crf 18 \
  -c:a aac -b:a 192k \
  -pix_fmt yuv420p \
  youtube.mp4
# 웹 스트리밍 (즉시 재생)
ffmpeg -i input.mp4 \
  -c:v libx264 -preset medium -crf 23 \
  -c:a aac -b:a 128k \
  -movflags +faststart \
  -vf scale=1920:-2 \
  web.mp4
# 모바일 최적화 (작은 파일)
ffmpeg -i input.mp4 \
  -c:v libx264 -preset fast -crf 26 \
  -c:a aac -b:a 96k \
  -vf scale=1280:-2 \
  mobile.mp4
# 빠른 변환 (테스트용)
ffmpeg -i input.mp4 \
  -c:v libx264 -preset veryfast -crf 23 \
  -c:a aac -b:a 128k \
  quick.mp4
# 최소 파일 크기 (보관용)
ffmpeg -i input.mp4 \
  -c:v libx265 -preset slow -crf 28 \
  -c:a aac -b:a 128k \
  compressed.mp4
# 최고 품질 (아카이브)
ffmpeg -i input.mp4 \
  -c:v libx264 -preset veryslow -crf 17 \
  -c:a aac -b:a 320k \
  archive.mp4

CRF 값 선택 가이드:

용도CRF설명
편집 소스17-18시각적 무손실
YouTube18-20플랫폼 재인코딩 대비
일반 공유21-23균형잡힌 품질
웹 스트리밍23-26빠른 로딩
모바일26-28데이터 절약
프리셋 선택 가이드:
상황프리셋이유
--------------------
테스트/프리뷰veryfast빠른 확인
일반 작업medium기본 균형
최종 배포slow최적 품질
아카이브veryslow최소 크기
실시간 스트리밍ultrafastCPU 부하 최소

학습 로드맵

1주차: 기초 다지기

  • FFmpeg 설치 및 버전 확인
  • 기본 명령어 구조 이해
  • 간단한 포맷 변환 (-c copy)
  • CRF와 프리셋 개념 파악
  • 해상도 변경 실습 2주차: 실전 활용
  • H.264 인코딩 마스터
  • 오디오 처리 (추출, 변환, 편집)
  • 썸네일 및 GIF 생성
  • 영상 자르기 및 병합
  • 필터 적용 (워터마크, 자막) 3주차: 고급 기능
  • HLS 스트리밍 구축
  • 2-Pass 인코딩
  • GPU 가속 활용
  • Python/Node.js 연동
  • 배치 처리 스크립트 4주차: 최적화 및 트러블슈팅
  • 성능 최적화 기법
  • 일반적인 에러 해결
  • 프로덕션 파이프라인 구축
  • 모니터링 및 로깅
  • 실전 프로젝트 적용 실무 적용 팁:
  1. 작게 시작하세요
    • 짧은 테스트 영상으로 실험
    • 다양한 설정 비교
    • 결과를 눈으로 확인
  2. 자주 쓰는 명령어는 스크립트로
    # convert_to_web.sh
    #!/bin/bash
    ffmpeg -i "$1" -c:v libx264 -preset medium -crf 23 \
      -c:a aac -b:a 128k -movflags +faststart "web_$1"
  3. GPU 가속 활용
    • NVIDIA: h264_nvenc
    • Intel: h264_qsv
    • AMD: h264_amf
  4. 병렬 처리로 시간 절약
    parallel -j 4 ffmpeg -i {} -crf 23 {.}_output.mp4 ::: *.mp4
  5. 품질 vs 속도 균형
    • 개발: veryfast + crf 23
    • 배포: slow + crf 20
    • 테스트: ultrafast + crf 28

다음 단계

관련 심화 주제:

마치며

FFmpeg는 처음에는 복잡해 보이지만, 기본 패턴만 익히면 매우 강력한 도구입니다. 이 가이드의 예제들을 직접 실행해보면서 익숙해지세요. 기억해야 할 핵심:

  1. 대부분의 경우 -c:v libx264 -crf 23이면 충분합니다
  2. 재인코딩이 필요 없으면 -c copy를 사용하세요
  3. 웹용은 -movflags +faststart를 잊지 마세요
  4. 테스트는 짧은 영상으로, 최종본은 느린 프리셋으로
  5. 문제가 생기면 ffprobe로 먼저 확인하세요 질문이나 피드백은 댓글로 남겨주세요!

관련 글 (내부 링크)

FFmpeg와 함께 보면 좋은 비디오·오디오 관련 가이드입니다:


키워드: FFmpeg, Video, Audio, 동영상, 오디오, 인코딩, 스트리밍, HLS, RTMP, libav, Multimedia, 변환, H.264, H.265, VP9, AV1, AAC, MP3, 썸네일, GIF, 필터, 워터마크


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

FFmpeg, Video, Audio, 동영상, 오디오, 인코딩, 스트리밍, libav, Multimedia 등으로 검색하시면 이 글이 도움이 됩니다.