MKV(Matroska) 컨테이너 실전 활용 | EBML·다중 자막·FFmpeg 리먹스

이 글의 핵심

MKV는 트랙과 자막을 거의 제한 없이 담을 수 있어 아카이브에는 좋지만, 웹 브라우저와 일부 기기에서는 그대로 재생되지 않습니다. 블루레이 리핑, 다국어 강의, 감독 해설 트랙 같은 사례로 트랙을 관리하는 방법을 보여 주고, 배포용으로 변환할 때 불필요한 트랙 제거와 자막 형식 변환에서 주의할 점을 설명합니다.

들어가며

MKV(Matroska Video)는 EBML(Extensible Binary Meta Language)을 뼈대로 한 오픈 사양 컨테이너로, 여러 오디오·자막·챕터·첨부 파일을 한 파일에 의미 있게 묶는 것에 강합니다. 블루레이 리핑, 팟캐스트 아카이브, 다국어 강의 녹화, 자막 제작 워크플로처럼 “한 콘텐츠에 선택지가 많은 패키지”가 필요할 때 MP4보다 표현력에서 유리한 경우가 많습니다.


컨테이너 개요

역사 및 개발 배경

Matroska는 2002년 MCF(Multimedia Container Format) 프로젝트에서 갈라져 나와 시작된 오픈 컨테이너이며, EBML로 확장 가능한 요소 트리를 표현합니다. 주요 연혁은 다음과 같습니다.

  • 2002: Matroska 프로젝트 시작
  • 2010: WebM (Matroska 부분집합) 발표
  • 2020: EBML이 IETF RFC 8794로 표준화
  • 2024: Matroska 컨테이너 사양이 IETF RFC 9559로 발행

코덱 제한이 거의 없다는 점은 장점이자 단점입니다. MKV에는 H.264와 AAC뿐 아니라 FLAC, DTS, TrueHD, ASS 자막, 블루레이의 PGS 비트맵 자막까지 원본 그대로 담을 수 있어 “아무것도 버리지 않는” 보관 형식으로 좋습니다. 하지만 그 말은 곧 MKV 파일이 재생된다는 보장이 없다는 뜻이기도 합니다. 컨테이너는 읽을 수 있어도 안에 든 DTS 오디오나 PGS 자막을 디코딩하지 못하는 TV와 스마트폰이 많고, 사용자는 이를 “MKV가 안 된다”로 받아들입니다. 이 글의 명령 대부분이 트랙을 고르고 형식을 바꾸는 데 집중하는 이유가 여기에 있습니다.

기술적 특징

항목설명
기반EBML (Extensible Binary Meta Language)
트랙비디오, 오디오, 자막 독립 트랙
코덱H.264, HEVC, AV1, FLAC, AC3, ASS 등 광범위
메타데이터Tags, Chapters, Attachments
확장자.mkv (비디오), .mka (오디오), .mks (자막)

MKV vs MP4 vs WebM

특징MKVMP4WebM
코덱 지원거의 모든 코덱H.264/HEVC + AAC 중심VP8/VP9/AV1 + Opus
다중 트랙자유로움가능하나 플레이어 지원 편차 큼가능하나 코덱 제한
텍스트 자막SRT, ASS, WebVTT 등mov_text(tx3g) 위주WebVTT
챕터중첩·다국어 이름 지원단순 챕터 목록Matroska 챕터(지원 플레이어 적음)
브라우저 지원낮음(브라우저마다 다름)가장 넓음넓음(Safari는 비교적 늦게 지원)
용도아카이브범용 배포웹 배포

내부 구조

EBML과 Matroska 요소

EBML은 가변 길이 정수(VINT)로 요소 ID·크기를 인코딩해, 앞에서 순차 스캔하며 트리를 복원합니다.

구조:

MKV 파일
├─ EBML Header
│  ├─ DocType: "matroska"
│  └─ DocTypeVersion: 4
├─ Segment
│  ├─ SeekHead (인덱스)
│  ├─ Info
│  │  ├─ TimestampScale: 1000000
│  │  ├─ Duration: 300.0s
│  │  └─ Title: "영화 제목"
│  ├─ Tracks
│  │  ├─ Video Track
│  │  │  ├─ CodecID: V_MPEG4/ISO/AVC
│  │  │  └─ Width: 1920, Height: 1080
│  │  ├─ Audio Track 1 (한국어)
│  │  │  ├─ CodecID: A_AAC
│  │  │  └─ Language: kor
│  │  ├─ Audio Track 2 (영어)
│  │  │  └─ Language: eng
│  │  └─ Subtitle Track (한국어)
│  │     ├─ CodecID: S_TEXT/ASS
│  │     └─ Language: kor
│  ├─ Tags
│  │  ├─ TITLE: "영화 제목"
│  │  ├─ ARTIST: "감독"
│  │  └─ DATE: "2026"
│  ├─ Chapters
│  │  ├─ Chapter 1: 00:00:00 "오프닝"
│  │  └─ Chapter 2: 00:05:00 "본편"
│  ├─ Attachments
│  │  └─ Font: subtitle_font.ttf
│  ├─ Cluster 1 (0-2s)
│  ├─ Cluster 2 (2-4s)
│  └─ Cues (시크 인덱스)

이 구조에서 실제 재생에 영향을 주는 부분은 Cluster와 Cues입니다. 오디오·비디오·자막 프레임은 타임스탬프 순서로 섞여(인터리빙) 몇 초 단위의 Cluster에 들어가고, Cues는 “이 시각의 키프레임은 파일의 몇 바이트 위치에 있다”는 색인입니다. Cues가 없으면 플레이어는 시크할 때 파일을 앞에서부터 훑어야 해서 느려지고, 네트워크 스트리밍에서는 탐색 막대를 움직여도 한참 멈춰 있는 증상이 생깁니다. Cues가 파일 끝에 있는 것 자체는 정상이며, 플레이어는 앞쪽 SeekHead를 보고 그 위치로 건너뜁니다. 다만 HTTP로 재생할 때는 끝부분을 가져오는 추가 요청이 한 번 더 필요합니다. 녹화 도중 프로그램이 죽어 Segment 크기와 Cues가 기록되지 않은 MKV도 EBML의 순차 구조 덕분에 대부분 재생은 되지만 시크가 되지 않는데, ffmpeg -i broken.mkv -c copy fixed.mkv로 리먹스하면 Cues가 다시 만들어집니다. OBS 같은 녹화 도구가 오랫동안 MP4 대신 MKV를 기본 녹화 형식으로 써 온 이유도 이것입니다. MP4는 녹화가 비정상 종료되면 moov atom not found로 파일 전체를 읽을 수 없게 되기 쉽습니다.


실전 사용

기본 명령

1) 구조 확인

# 전체 정보
ffprobe -hide_banner -show_format -show_streams -show_chapters input.mkv
# 트랙 목록만
ffprobe -v error -show_entries stream=index,codec_type,codec_name,language input.mkv

2) 무손실 리먹스

# MKV → MKV (코덱 복사)
ffmpeg -i input.mkv -c copy output.mkv
# MP4 → MKV
ffmpeg -i input.mp4 -c copy output.mkv

다중 트랙 관리

여러 입력에서 트랙 조립

# 비디오 + 다중 오디오 + 자막
ffmpeg -i video.mp4 \
  -i audio_kor.m4a \
  -i audio_eng.m4a \
  -i subtitle_kor.srt \
  -i subtitle_eng.srt \
  -map 0:v:0 \
  -map 1:a:0 \
  -map 2:a:0 \
  -map 3:s:0 \
  -map 4:s:0 \
  -c copy \
  -metadata:s:a:0 language=kor -metadata:s:a:0 title="한국어" \
  -metadata:s:a:1 language=eng -metadata:s:a:1 title="English" \
  -metadata:s:s:0 language=kor -metadata:s:s:0 title="한국어 자막" \
  -metadata:s:s:1 language=eng -metadata:s:s:1 title="English Subtitle" \
  package.mkv

-map을 하나라도 쓰면 FFmpeg의 자동 스트림 선택이 꺼지고, 명시한 스트림만 출력에 들어갑니다. -map을 빼면 FFmpeg는 입력 전체에서 비디오 하나, 오디오 하나(채널이 가장 많은 것), 자막 하나만 골라 넣기 때문에 다섯 개 입력을 넣어도 트랙이 대부분 사라집니다. -metadata:s:a:0의 s:a:0은 출력 파일 기준 첫 번째 오디오 스트림이라는 뜻이어서, 입력 번호(1:a:0)와 헷갈리지 않아야 합니다. 언어 코드는 ISO 639-2 세 글자(kor, eng, jpn)를 쓰는 것이 호환성이 가장 좋습니다. 또 -c copy는 오디오를 재인코딩하지 않으므로 .m4a의 AAC가 그대로 들어가지만, 두 오디오 파일의 시작 시점이 영상과 다르면 싱크가 어긋난 채로 묶입니다. 싱크를 맞춰야 한다면 해당 입력 앞에 -itsoffset 0.5 같은 옵션으로 오프셋을 줍니다.

기본 트랙 설정

# 첫 번째 오디오를 기본으로
ffmpeg -i input.mkv \
  -c copy \
  -disposition:a:0 default \
  -disposition:a:1 0 \
  output.mkv

자막 관리

자막 추출

# 첫 번째 자막 트랙 (원본이 SRT일 때)
ffmpeg -i input.mkv -map 0:s:0 -c:s copy subtitle.srt
# 여러 자막 트랙: 출력 파일을 트랙마다 따로 지정
ffmpeg -i input.mkv \
  -map 0:s:0 -c:s copy sub_0.srt \
  -map 0:s:1 -c:s copy sub_1.srt
# mkvtoolnix로 트랙 ID 기준 추출 (형식 그대로)
mkvextract input.mkv tracks 2:sub_kor.ass 3:sub_eng.srt

자막을 뽑을 때 가장 흔한 실패는 원본 형식과 확장자가 맞지 않는 경우입니다. 원본이 ASS인데 .srt로 -c:s copy하면 FFmpeg가 에러를 내거나 형식이 깨진 파일이 나옵니다. 블루레이에서 온 PGS(hdmv_pgs_subtitle)나 DVD의 VobSub는 글자가 아니라 이미지라서, -c:s srt로 바꾸려 하면 Subtitle encoding currently only possible from text to text or bitmap to bitmap 에러가 납니다. 이미지 자막을 텍스트로 바꾸려면 Subtitle Edit 같은 OCR 도구가 필요합니다. 추출 전에 ffprobe로 codec_name을 먼저 확인하는 습관을 들이면 이런 시행착오가 줄어듭니다. FFmpeg는 출력 파일 하나에 여러 자막 스트림을 %d 패턴으로 나눠 쓰는 기능이 없으므로, 트랙이 많다면 위처럼 출력마다 -map을 지정하거나 mkvextract를 쓰는 편이 간단합니다.

자막 추가

# 외부 SRT 추가
ffmpeg -i video.mkv -i subtitle.srt \
  -map 0 -map 1 \
  -c copy \
  -metadata:s:s:0 language=kor \
  output.mkv

여기서 -metadata:s:s:0이 새로 추가한 자막을 가리키는 것은 video.mkv에 자막이 하나도 없을 때뿐입니다. -map 0이 기존 자막 트랙을 먼저 출력에 넣으므로, 원본에 자막이 두 개 있었다면 새 SRT는 s:s:2가 됩니다. 결과 파일을 ffprobe로 확인하지 않으면 기존 영어 자막에 한국어 태그를 붙이는 실수를 하기 쉽습니다. SRT 파일이 UTF-8이 아닌 CP949(EUC-KR)로 저장되어 있으면 한글이 깨지므로, 입력 앞에 -sub_charenc CP949를 주거나 파일을 UTF-8로 변환해 두어야 합니다.

ASS 자막 + 폰트 임베드

# ASS 자막 + 폰트 첨부
ffmpeg -i video.mkv \
  -i subtitle.ass \
  -attach font.ttf -metadata:s:t mimetype=application/x-truetype-font \
  -map 0 -map 1 \
  -c copy \
  -metadata:s:s:0 language=kor \
  output.mkv

챕터 관리

챕터 추가

# 챕터 파일 (chapters.txt) — OGM 형식, mkvmerge가 읽는 형식
cat > chapters.txt << EOF
CHAPTER01=00:00:00.000
CHAPTER01NAME=오프닝
CHAPTER02=00:05:00.000
CHAPTER02NAME=본편
CHAPTER03=00:50:00.000
CHAPTER03NAME=엔딩
EOF
# 챕터 추가 (mkvtoolnix)
mkvmerge -o output.mkv --chapters chapters.txt input.mkv
# FFmpeg로 넣으려면 FFMETADATA 형식이 필요 (시각은 TIMEBASE 단위 정수)
cat > chapters.ffmeta << EOF
;FFMETADATA1
[CHAPTER]
TIMEBASE=1/1000
START=0
END=300000
title=오프닝
[CHAPTER]
TIMEBASE=1/1000
START=300000
END=3000000
title=본편
EOF
ffmpeg -i input.mkv -i chapters.ffmeta -map 0 -map_chapters 1 -c copy output.mkv

CHAPTER01=... 형태의 OGM 챕터 파일은 mkvmerge가 이해하는 형식이고, FFmpeg는 이 파일을 입력으로 넣어도 챕터로 인식하지 못합니다. 흔히 보는 예제처럼 ffmpeg -i chapters.txt로 넣으면 Invalid data found when processing input이 나거나 챕터 없이 파일이 만들어집니다. FFmpeg에서는 첫 줄이 ;FFMETADATA1인 메타데이터 파일에 각 챕터의 시작·끝을 TIMEBASE 단위 정수로 적어야 합니다. 위 명령은 -map_chapters 1로 챕터만 메타데이터 파일에서 가져옵니다. 여기에 -map_metadata 1까지 붙이면 파일 전체 태그(제목 등)도 메타데이터 파일 내용으로 바뀌므로 원본 제목이 사라질 수 있습니다.

MKV → MP4 변환

# 코덱 호환 시 (H.264 + AAC)
ffmpeg -i input.mkv \
  -c:v copy \
  -c:a copy \
  -map 0:v:0 \
  -map 0:a:0 \
  -movflags +faststart \
  output.mp4
# 코덱 변환 필요 시
ffmpeg -i input.mkv \
  -c:v libx264 \
  -preset medium \
  -crf 23 \
  -c:a aac \
  -b:a 192k \
  -movflags +faststart \
  output.mp4

첫 번째 명령이 -map 0:v:0 -map 0:a:0으로 트랙을 골라내는 데는 이유가 있습니다. -map 0으로 모든 트랙을 넣고 -c copy하면, MKV에 흔한 SRT·ASS 자막 때문에 Could not find tag for codec subrip in stream #2, codec not currently supported in container 에러로 변환이 멈춥니다. MP4가 담을 수 있는 텍스트 자막은 mov_text뿐이므로, 자막을 유지하려면 -map 0:s:0 -c:s mov_text를 추가해 변환해야 하고, 이때 ASS의 글꼴·위치 정보는 사라집니다. PGS 같은 이미지 자막은 MP4에 넣을 수 없습니다. 오디오도 같은 문제가 있어서 DTS나 TrueHD 트랙을 그대로 복사하면 MP4로는 만들어지더라도 대부분의 기기에서 소리가 나지 않으므로, 이 경우에는 오디오만 -c:a aac로 재인코딩하고 비디오는 copy로 두는 조합이 시간을 가장 아낍니다. -movflags +faststart는 MP4의 색인(moov)을 파일 앞으로 옮겨 웹에서 다운로드가 끝나기 전에 재생을 시작할 수 있게 해 주는 옵션입니다.


성능 비교

컨테이너 오버헤드

MKV, MP4, WebM 모두 컨테이너 자체가 차지하는 용량(헤더, 블록 헤더, 색인)은 오디오·비디오 비트스트림에 비해 매우 작습니다. 같은 스트림을 -c copy로 리먹스했을 때 파일 크기가 크게 달라졌다면 컨테이너 차이보다는 트랙 구성이 바뀌었을 가능성이 높습니다.

트랙이 파일 크기에 주는 영향

트랙별 용량은 비트레이트 × 재생 시간으로 바로 계산할 수 있습니다. 1시간(3600초) 분량에서 128kbps AAC 스테레오 오디오 트랙은 128,000 ÷ 8 × 3600 ≈ 57.6MB이고, 640kbps AC3 5.1 트랙은 약 288MB입니다. 블루레이의 TrueHD나 DTS-HD MA 같은 무손실 오디오는 수 Mbps라 한 트랙이 12GB에 이르기도 합니다. 반면 SRT·ASS 텍스트 자막은 1시간에 수십수백 KB 수준이라 몇 개를 더해도 거의 차이가 없고, PGS 이미지 자막은 트랙당 수십 MB까지 커질 수 있습니다. 그래서 리핑한 MKV의 용량을 줄일 때는 자막보다 쓰지 않는 무손실 오디오 트랙을 먼저 빼는 것이 효과가 큽니다.


실무 활용 사례

사례 1: 블루레이 리핑 - 다중 오디오·자막

원본 품질을 유지하면서 다국어 오디오와 자막을 남기고 챕터도 보존하는 것이 목표입니다. MakeMKV는 디스크의 모든 트랙을 넣으므로 필요한 것만 골라냅니다.

MakeMKV 출력 → FFmpeg 정리

# MakeMKV 출력 (모든 트랙 포함)
# input.mkv: 비디오 1개, 오디오 5개, 자막 10개
# 필요한 트랙만 선택
ffmpeg -i input.mkv \
  -map 0:v:0 \
  -map 0:a:0 -map 0:a:1 \
  -map 0:s:0 -map 0:s:1 \
  -c copy \
  -metadata:s:a:0 language=kor -metadata:s:a:0 title="한국어" \
  -metadata:s:a:1 language=eng -metadata:s:a:1 title="English" \
  -metadata:s:s:0 language=kor \
  -metadata:s:s:1 language=eng \
  cleaned.mkv

Python 자동화

import subprocess
from pathlib import Path
def clean_bluray_rip(input_file, output_file, keep_tracks):
    """
    블루레이 리핑 정리
    keep_tracks: {'audio': [0, 1], 'subtitle': [0, 1]}
    """
    cmd = ['ffmpeg', '-i', str(input_file)]
    
    cmd.extend(['-map', '0:v:0'])
    
    for audio_idx in keep_tracks.get('audio', []):
        cmd.extend(['-map', f'0:a:{audio_idx}'])
    
    for sub_idx in keep_tracks.get('subtitle', []):
        cmd.extend(['-map', f'0:s:{sub_idx}'])
    
    cmd.extend(['-c', 'copy', str(output_file)])
    
    subprocess.run(cmd, check=True)
# 사용
keep_tracks = {
    'audio': [0, 1],  # 한국어, 영어
    'subtitle': [0, 1, 2]  # 한국어, 영어, 일본어
}
clean_bluray_rip('rip.mkv', 'cleaned.mkv', keep_tracks)

사례 2: 다국어 강의 - 오디오 트랙 전환

강의 영상 하나에 한국어·영어 음성과 자막을 함께 넣어, 시청자가 플레이어에서 언어를 전환하게 하는 구성입니다.

생성

# 비디오 + 다국어 오디오 + 자막
ffmpeg -i lecture.mp4 \
  -i audio_kor.wav \
  -i audio_eng.wav \
  -i subtitle_kor.srt \
  -i subtitle_eng.srt \
  -map 0:v:0 \
  -map 1:a:0 \
  -map 2:a:0 \
  -map 3:s:0 \
  -map 4:s:0 \
  -c:v copy \
  -c:a aac -b:a 128k \
  -c:s copy \
  -metadata:s:a:0 language=kor -metadata:s:a:0 title="한국어" \
  -metadata:s:a:1 language=eng -metadata:s:a:1 title="English" \
  -metadata:s:s:0 language=kor \
  -metadata:s:s:1 language=eng \
  -disposition:a:0 default \
  lecture_multi.mkv

사례 3: 팟캐스트 아카이브 - 챕터 마커

2시간 분량의 팟캐스트에 주제별 챕터와 제목·진행자 태그를 넣어 오디오 전용 Matroska(.mka)로 보관합니다.

챕터 파일 생성

# chapters.txt
cat > chapters.txt << EOF
CHAPTER01=00:00:00.000
CHAPTER01NAME=인트로
CHAPTER02=00:05:30.000
CHAPTER02NAME=주제 1: 기술 트렌드
CHAPTER03=00:35:00.000
CHAPTER03NAME=주제 2: 커리어
CHAPTER04=01:05:00.000
CHAPTER04NAME=Q&A
CHAPTER05=01:55:00.000
CHAPTER05NAME=아웃트로
EOF

MKV 생성

# 1) FFmpeg로 인코딩 + 태그
ffmpeg -i podcast.wav \
  -c:a aac -b:a 128k \
  -metadata title="팟캐스트 에피소드 1" \
  -metadata artist="호스트 이름" \
  -metadata date="2026-03-31" \
  podcast_tmp.mka
# 2) mkvmerge로 OGM 형식 챕터 추가
mkvmerge -o podcast_ep01.mka --chapters chapters.txt podcast_tmp.mka

OGM 챕터 파일의 시각은 시:분:초.밀리초 형식이라 분 자리가 60을 넘으면 안 됩니다. 00:65:00.000처럼 쓰면 mkvmerge가 형식 오류로 거부하므로 1시간 5분은 01:05:00.000으로 적어야 합니다. 챕터 파일은 UTF-8로 저장해야 한글 챕터 이름이 깨지지 않습니다.

사례 4: 영화 컬렉션 - 감독 해설 트랙

원본 오디오와 감독 해설 오디오를 별도 트랙으로 두고, 한국어·영어 자막을 함께 넣습니다. 원본 오디오가 기본 트랙이 되도록 disposition을 지정합니다.

생성

ffmpeg -i movie.mp4 \
  -i commentary.wav \
  -i subtitle_kor.srt \
  -i subtitle_eng.srt \
  -map 0:v:0 \
  -map 0:a:0 \
  -map 1:a:0 \
  -map 2:s:0 \
  -map 3:s:0 \
  -c:v copy \
  -c:a aac -b:a 192k \
  -c:s copy \
  -metadata:s:a:0 title="원본 오디오" \
  -metadata:s:a:1 title="감독 해설" \
  -metadata:s:s:0 language=kor \
  -metadata:s:s:1 language=eng \
  -disposition:a:0 default \
  movie_deluxe.mkv

최적화 팁

불필요한 트랙 제거

# 비디오 + 첫 번째 오디오만
ffmpeg -i input.mkv \
  -map 0:v:0 \
  -map 0:a:0 \
  -c copy \
  slim.mkv

자막 형식 변환

# ASS → SRT
ffmpeg -i input.mkv \
  -map 0:s:0 \
  -c:s srt \
  subtitle.srt
# SRT → ASS
ffmpeg -i subtitle.srt subtitle.ass

ASS → SRT 변환은 손실 변환입니다. ASS는 글꼴, 색, 화면 위치, 카라오케 효과, 여러 줄의 동시 표시까지 표현하지만 SRT는 시간과 텍스트(일부 플레이어가 해석하는 <i>, <b> 정도)만 담습니다. 변환하면 노래 가사가 화면 위쪽에 따로 뜨던 장면이 일반 자막과 겹쳐 나오는 식으로 깨질 수 있으므로, 원본 ASS는 MKV 안에 남겨 두고 호환용 SRT를 추가 트랙으로 넣는 편이 안전합니다.

오디오 정규화

# 라우드니스 정규화
ffmpeg -i input.mkv \
  -c:v copy \
  -af "loudnorm=I=-16:TP=-1.5:LRA=11" \
  -c:a aac -b:a 192k \
  normalized.mkv

loudnorm을 이렇게 한 번만 돌리면 필터가 오디오를 실시간으로 조금씩 측정하며 조정하는 동적 모드로 동작해, 목표 라우드니스에 정확히 맞지 않거나 조용한 구간이 부자연스럽게 커질 수 있습니다. 정확하게 맞추려면 먼저 -af loudnorm=print_format=json -f null -로 측정만 한 뒤, 출력된 measured_I, measured_TP 등의 값을 두 번째 실행의 필터 인자로 넣는 2패스 방식을 씁니다. -16 LUFS는 팟캐스트·모바일 청취에 흔히 쓰는 목표이고, 방송은 보통 -23(EBU R128)이나 -24를 씁니다. 필터를 쓰면 오디오는 반드시 재인코딩되므로 -c copy와 함께 쓸 수 없습니다. 또 이 명령에는 -map이 없어서 FFmpeg의 기본 스트림 선택에 따라 오디오는 채널이 가장 많은 트랙 하나만 출력에 들어갑니다. 여러 오디오 트랙을 모두 유지하려면 -map 0을 주고 트랙마다 필터를 지정해야 합니다.


트러블슈팅

문제 1: 브라우저에서 재생 안 됨

<video> 태그에 MKV를 직접 넣으면 대부분의 브라우저에서 재생되지 않거나 브라우저마다 결과가 다릅니다.

<video src="video.mkv" controls></video>
<!-- 대부분 브라우저: 재생 불가 -->

배포용으로는 MP4나 WebM으로 변환합니다. 원본이 이미 H.264·AAC라면 앞에서 본 것처럼 -c copy로 컨테이너만 바꾸면 되고, 아래는 재인코딩이 필요한 경우입니다. MP4가 담지 못하는 자막은 WebVTT로 따로 추출해 <track> 태그로 붙입니다.

# MP4로 변환
ffmpeg -i input.mkv \
  -c:v libx264 \
  -c:a aac \
  -movflags +faststart \
  output.mp4

문제 2: 자막이 안 보임

자막 트랙은 있는데 플레이어에서 자막이 나오지 않는 경우입니다. 가장 흔한 원인은 자막 트랙에 기본(default) 플래그가 없어 플레이어가 자막을 꺼진 상태로 시작하는 것입니다. 언어 태그가 없으면 “선호 언어 자막 자동 선택” 설정도 동작하지 않으므로 함께 넣어 둡니다.

# 첫 자막을 기본 트랙으로 지정하고 언어 태그 추가
ffmpeg -i input.mkv \
  -map 0 -c copy \
  -disposition:s:0 default \
  -metadata:s:s:0 language=kor \
  output.mkv

두 번째 원인은 기기가 자막 형식을 지원하지 않는 경우입니다. TV나 셋톱박스는 ASS나 PGS를 지원하지 않는 경우가 많아, 텍스트 자막이라면 SRT로 바꾸면 해결되기도 합니다.

# ASS → SRT 변환
ffmpeg -i input.mkv \
  -map 0:v -map 0:a \
  -map 0:s:0 \
  -c:v copy -c:a copy \
  -c:s srt \
  output.mkv

문제 3: 오디오 트랙 선택 안 됨

플레이어가 항상 첫 번째 오디오만 재생한다면 default 플래그를 원하는 트랙으로 옮깁니다.

# 두 번째 오디오를 기본으로
ffmpeg -i input.mkv \
  -c copy \
  -disposition:a:0 0 \
  -disposition:a:1 default \
  output.mkv

문제 4: 파일 크기 너무 큼

쓰지 않는 오디오 트랙(특히 무손실 오디오)이 용량 대부분을 차지하는 경우가 많으므로, 필요한 트랙만 남깁니다.

# 필요한 트랙만 유지
ffmpeg -i input.mkv \
  -map 0:v:0 \
  -map 0:a:0 \
  -map 0:s:0 \
  -c copy \
  slim.mkv

문제 5: 챕터 손실

FFmpeg는 MKV를 MP4로 리먹스할 때 챕터를 기본으로 복사해 QuickTime 챕터 트랙과 Nero 챕터(chpl) 형식으로 기록합니다. 그런데도 챕터가 안 보인다면 대개 플레이어가 MP4 챕터를 표시하지 않는 경우입니다. 이때는 MKV를 유지하거나, 챕터 정보를 별도 파일로 뽑아 두면 다른 형식으로 다시 넣을 수 있습니다.

# 챕터 추출
ffprobe -v error -show_chapters -of json input.mkv > chapters.json

다음 단계

참고 자료


같이 보면 좋은 글