Navigation That Actually Works
Watching a 3-hour video essay without chapter markers means either watching the whole thing or manually scrubbing to find the section you want. For content creators, chapters make long-form videos significantly more useful — viewers can jump to relevant sections, return to specific parts, and the chapter titles show up in search results on YouTube.
For personal media libraries, chapter markers in movies and TV episodes let you skip intros, jump to a specific scene, or pick up where you left off without a lot of timeline dragging.
This guide covers the technical mechanics of adding chapters to both MP4 and MKV files, setting them up for YouTube, and building an automated pipeline for batch-adding chapters to a video library.
How Chapter Markers Work in Video Files
Chapters are metadata — they don't affect the video or audio streams at all. They're essentially a list of (timestamp, title) pairs stored in the container alongside the media streams. The player reads these pairs and renders them as navigation points.
The structure is simple:
00:00:00 — Introduction
00:04:30 — Background
00:12:15 — Main Topic
00:28:00 — Case Studies
00:45:00 — Conclusion
Different container formats store chapters differently:
| Container | Chapter Format | Tools to Edit |
|---|---|---|
| MP4/M4V | MP4 Timed Metadata (ChapterList) | FFmpeg, mp4chaps, AtomicParsley |
| MKV | Matroska Chapters XML | FFmpeg, mkvpropedit, MKVToolNix |
| M4B (audiobooks) | MP4 Chapters | mp4chaps, iTunes |
| WebM | Matroska Chapters XML | FFmpeg |
| OGG | Ogg Chapter Extension | Vorbis tools |
MP4 and MKV are the formats you'll encounter most often for video.
Adding Chapters to MP4 Files
Method 1: FFmpeg Metadata File
FFmpeg uses a special metadata format for chapters. Create a file called chapters.txt:
;FFMETADATA1
title=My Video Title
artist=Author Name
[CHAPTER]
TIMEBASE=1/1000
START=0
END=270000
title=Introduction
[CHAPTER]
TIMEBASE=1/1000
START=270000
END=735000
title=Background and Context
[CHAPTER]
TIMEBASE=1/1000
START=735000
END=1680000
title=Main Topic
[CHAPTER]
TIMEBASE=1/1000
START=1680000
END=2700000
title=Case Studies
[CHAPTER]
TIMEBASE=1/1000
START=2700000
END=3200000
title=Conclusion
The TIMEBASE=1/1000 means timestamps are in milliseconds. START=270000 means 270 seconds = 4 minutes 30 seconds. END is where the chapter ends (the START of the next chapter, or the video duration for the last chapter).
Apply the chapters to an MP4:
ffmpeg -i input.mp4 -i chapters.txt \
-map_metadata 1 -map 0 \
-c copy output_with_chapters.mp4
The -map_metadata 1 applies metadata from the second input (chapters.txt). The -c copy copies all streams without re-encoding.
Method 2: Convert Timecodes to Milliseconds
Manually calculating milliseconds is tedious. A helper script:
def timecode_to_ms(timecode):
"""Convert HH:MM:SS or MM:SS to milliseconds."""
parts = timecode.split(':')
if len(parts) == 3:
h, m, s = parts
elif len(parts) == 2:
h, m, s = 0, parts[0], parts[1]
else:
h, m, s = 0, 0, parts[0]
return int(h) * 3600000 + int(m) * 60000 + int(float(s) * 1000)
def generate_ffmetadata(chapters, output_file='chapters.txt'):
"""
chapters: list of (timecode, title) tuples
Example: [('00:00:00', 'Intro'), ('00:04:30', 'Part 1'), ...]
"""
with open(output_file, 'w') as f:
f.write(';FFMETADATA1\n\n')
for i, (timecode, title) in enumerate(chapters):
start = timecode_to_ms(timecode)
if i + 1 < len(chapters):
end = timecode_to_ms(chapters[i + 1][0])
else:
end = start + 10000 # 10 seconds for last chapter; adjust as needed
f.write('[CHAPTER]\n')
f.write('TIMEBASE=1/1000\n')
f.write(f'START={start}\n')
f.write(f'END={end}\n')
f.write(f'title={title}\n\n')
print(f"Written {len(chapters)} chapters to {output_file}")
# Example usage
chapters = [
('00:00:00', 'Introduction'),
('00:04:30', 'Background'),
('00:12:15', 'Main Topic'),
('00:28:00', 'Case Studies'),
('00:45:00', 'Conclusion'),
]
generate_ffmetadata(chapters)
Method 3: mp4chaps (Simple Text Format)
The mp4chaps tool (part of the mp4v2 package) uses a simpler chapter format:
00:00:00.000 Introduction
00:04:30.000 Background and Context
00:12:15.000 Main Topic
00:28:00.000 Case Studies
00:45:00.000 Conclusion
Apply to MP4:
# Install mp4v2-utils (Ubuntu/Debian)
apt-get install mp4v2-utils
# Write chapters to file
mp4chaps --import chapters.txt input.mp4
# List existing chapters
mp4chaps --list input.mp4
# Remove all chapters
mp4chaps --remove input.mp4
mp4chaps modifies files in-place. Make a backup before using it if the original file is important.
Adding Chapters to MKV Files
MKV chapters use XML format, which is more verbose but also more powerful (supports nested chapters, chapter UUIDs, and language-specific titles).
Basic MKV Chapter XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Chapters SYSTEM "matroskachapters.dtd">
<Chapters>
<EditionEntry>
<EditionFlagHidden>0</EditionFlagHidden>
<EditionFlagDefault>1</EditionFlagDefault>
<ChapterAtom>
<ChapterTimeStart>00:00:00.000</ChapterTimeStart>
<ChapterDisplay>
<ChapterString>Introduction</ChapterString>
<ChapterLanguage>eng</ChapterLanguage>
</ChapterDisplay>
</ChapterAtom>
<ChapterAtom>
<ChapterTimeStart>00:04:30.000</ChapterTimeStart>
<ChapterDisplay>
<ChapterString>Background and Context</ChapterString>
<ChapterLanguage>eng</ChapterLanguage>
</ChapterDisplay>
</ChapterAtom>
<ChapterAtom>
<ChapterTimeStart>00:12:15.000</ChapterTimeStart>
<ChapterDisplay>
<ChapterString>Main Topic</ChapterString>
<ChapterLanguage>eng</ChapterLanguage>
</ChapterDisplay>
</ChapterAtom>
<ChapterAtom>
<ChapterTimeStart>00:45:00.000</ChapterTimeStart>
<ChapterDisplay>
<ChapterString>Conclusion</ChapterString>
<ChapterLanguage>eng</ChapterLanguage>
</ChapterDisplay>
</ChapterAtom>
</EditionEntry>
</Chapters>
Apply to MKV using mkvpropedit:
# Install mkvtoolnix
apt-get install mkvtoolnix
# Add chapters
mkvpropedit input.mkv --chapters chapters.xml
# Verify chapters were added
mkvinfo input.mkv | grep -A 2 "Chapter"
Using MKVToolNix GUI
For a graphical approach, MKVToolNix GUI includes a chapter editor that lets you type timecodes and titles without writing XML. Available on Windows, macOS, and Linux. This is the most user-friendly option for occasional manual chapter editing.
FFmpeg for MKV Chapters
FFmpeg can also add chapters to MKV files using the same metadata format as MP4:
ffmpeg -i input.mkv -i chapters.txt \
-map_metadata 1 -map 0 \
-c copy output_with_chapters.mkv
The same chapters.txt format works for both MP4 and MKV.
YouTube Chapters
YouTube auto-generates chapters from video description timestamps. No file modification needed — chapters come from the description text.
YouTube Chapter Format
In the video description, include a list of timestamps:
0:00 Introduction
4:30 Background
12:15 Main Topic
28:00 Case Studies
45:00 Conclusion
Rules:
- The first timestamp must be
0:00 - Minimum of three timestamps required
- Timestamps must be in ascending order
- Minimum chapter length of 10 seconds
- No special formatting required — any line that starts with a valid timestamp is treated as a chapter
YouTube automatically converts these into a chapter list displayed on the video player and in the video's timeline.
Pro Tip: YouTube also reads chapters from embedded MP4 chapter markers if you upload a file that has them. The description-based method takes precedence if both are present.
Extracting YouTube Chapter Timestamps
If you've already written YouTube description chapters and want to embed them in the file:
import re
def parse_youtube_chapters(description):
"""Extract chapter timestamps from a YouTube video description."""
pattern = r'^(\d{1,2}:\d{2}(?::\d{2})?)\s+(.+)$'
chapters = []
for line in description.strip().split('\n'):
match = re.match(pattern, line.strip())
if match:
chapters.append((match.group(1), match.group(2)))
return chapters
description = """
0:00 Introduction
4:30 Background and Context
12:15 The Main Problem
28:00 Three Real-World Examples
45:00 Summary and Next Steps
"""
chapters = parse_youtube_chapters(description)
generate_ffmetadata(chapters) # Use the function from earlier
Checking and Removing Chapters
Reading Existing Chapters
# List chapters in any file
ffprobe -v quiet -print_format json -show_chapters input.mp4 | \
python3 -c "
import sys, json
chapters = json.load(sys.stdin)['chapters']
for ch in chapters:
start = float(ch['start_time'])
h, remainder = divmod(start, 3600)
m, s = divmod(remainder, 60)
print(f'{int(h):02d}:{int(m):02d}:{s:06.3f} {ch[\"tags\"].get(\"title\", \"(no title)\")}')
"
# Simpler output using ffprobe directly
ffprobe -v error -print_format flat -show_chapters input.mp4
Removing All Chapters
# Remove chapters from MP4
ffmpeg -i input.mp4 -map_chapters -1 -c copy output_no_chapters.mp4
# Remove chapters from MKV using mkvpropedit
mkvpropedit input.mkv --chapters ''
Chapters in Media Players and Plex
| Player/Platform | MP4 Chapters | MKV Chapters | Notes |
|---|---|---|---|
| VLC | Yes | Yes | Shows in Playback > Chapters menu |
| Plex | Yes | Yes | Shows as chapter thumbnails |
| Jellyfin | Yes | Yes | Chapter thumbnails + navigation |
| Emby | Yes | Yes | |
| Kodi | Yes | Yes | |
| Apple TV (native) | Yes | Limited | MKV requires Infuse |
| QuickTime | Yes | No | MP4/M4V only |
| Windows Media Player | Yes | No | MP4/M4V only |
| YouTube | Yes (embedded) | N/A | Also reads from description |
Plex and Jellyfin generate chapter thumbnails by extracting video frames at each chapter timestamp. For this to work well, ensure your chapter metadata timestamps actually correspond to the beginning of the relevant content section.
Batch Processing: Adding Chapters to a Video Library
For a large collection of files (TV episodes with standard intro lengths, for example), automate chapter creation:
import subprocess
import json
import os
def get_video_duration(filepath):
"""Get video duration in seconds using ffprobe."""
result = subprocess.run(
['ffprobe', '-v', 'quiet', '-print_format', 'json', '-show_format', filepath],
capture_output=True, text=True
)
data = json.loads(result.stdout)
return float(data['format']['duration'])
def add_tv_chapters(filepath, intro_duration=90, outro_duration=60):
"""Add standard TV episode chapters: Intro, Episode, Credits."""
duration = get_video_duration(filepath)
chapters = [
(0, 'Intro'),
(intro_duration, 'Episode'),
(duration - outro_duration, 'Credits'),
]
# Generate chapters.txt
with open('temp_chapters.txt', 'w') as f:
f.write(';FFMETADATA1\n\n')
for i, (start, title) in enumerate(chapters):
end = chapters[i+1][0] if i+1 < len(chapters) else duration
f.write('[CHAPTER]\n')
f.write('TIMEBASE=1/1000\n')
f.write(f'START={int(start * 1000)}\n')
f.write(f'END={int(end * 1000)}\n')
f.write(f'title={title}\n\n')
output_path = filepath.replace('.mp4', '_chaptered.mp4').replace('.mkv', '_chaptered.mkv')
subprocess.run([
'ffmpeg', '-i', filepath, '-i', 'temp_chapters.txt',
'-map_metadata', '1', '-map', '0', '-c', 'copy', output_path
], check=True)
os.remove('temp_chapters.txt')
print(f"Created: {output_path}")
# Process all MP4 files in a folder
import glob
for video_file in glob.glob('*.mp4'):
add_tv_chapters(video_file)
Frequently Asked Questions
Do chapters affect video playback quality?
No. Chapters are purely metadata — timestamps and text labels in the container. They don't touch the video or audio streams. Adding or removing chapters with -c copy doesn't re-encode anything.
Why don't my MP4 chapters show up in QuickTime?
QuickTime uses Apple's chapter format (QTTEXT) rather than the standard FFmpeg chapter format. For QuickTime-compatible chapters, use mp4chaps with the Apple chapter format or AtomicParsley --chapters. Most other players (VLC, MPC-HC, media servers) read the standard FFmpeg chapter format correctly.
Can I add chapter artwork (thumbnails) to chapter markers?
Yes, in MKV format. Each Matroska ChapterAtom can include a ChapterSegmentEditionUID linking to a specific frame, and Plex/Jellyfin extract thumbnail frames automatically. You can also use mkvmerge with --chapters to attach pre-generated thumbnail images to chapter entries.
How do I add chapters without changing the file?
You can't add chapters without modifying the file — chapters must be stored in the container. However, -c copy ensures no re-encoding, which is the fastest possible modification. For MKV files, mkvpropedit --chapters modifies only the chapter metadata, leaving all streams completely untouched (no re-mux required).
What happens if chapter timestamps are slightly off?
Nothing breaks — the video still plays fine. The chapter markers just appear at the specified timestamps, which may not align perfectly with the content if the timestamps are inaccurate. For precision, use a player with frame-accurate timecode display (VLC shows HH:MM:SS.mmm) to find the exact timestamp for each chapter start.
Conclusion
Chapter markers are low-effort metadata additions that significantly improve the usability of long-form video content. The FFmpeg approach works for both MP4 and MKV without re-encoding, making batch processing a library of files fast and lossless.
For converting between MP4 and MKV containers while preserving chapters (use -map_chapters 0 to ensure chapters are included), the MKV converter and MP4 converter handle these conversions. The MKV vs MP4 comparison covers the broader trade-offs between these two container formats if you're deciding which to standardize on for your library. For video file repair scenarios that often accompany chapter metadata work, the how to fix corrupted video files guide has additional diagnostic steps.



