Help us improve
Share bugs, ideas, or general feedback.
From media-os
Author or edit metadata, chapters, and cover art in media files using ffmpeg. Sets container/stream tags, adds/ejects chapter markers, embeds cover art or fonts, marks subtitle dispositions, and strips metadata without re-encoding.
npx claudepluginhub damionrashford/media-os --plugin media-osHow this skill is triggered — by the user, by Claude, or both
Slash command
/media-os:ffmpeg-metadata [operation] [input][operation] [input]The summary Claude sees in its skill listing — used to decide when to auto-load this skill
**Context:** $ARGUMENTS
Guides technical evaluation of code review feedback: read fully, restate for understanding, verify against codebase, respond with reasoning or pushback before implementing.
Share bugs, ideas, or general feedback.
Context: $ARGUMENTS
default and/or forced.Decide which category your task falls in. Each maps to a different flag set.
| Operation | Core flags |
|---|---|
| Set container tags | -metadata KEY=VALUE ... |
| Set per-stream tags | -metadata:s:<type>:<index> KEY=VALUE (e.g. s:a:0, s:v:0, s:s:0) |
| Add chapters | ffmetadata file + -map_metadata 1 -map_chapters 1 |
| Extract chapters | -f ffmetadata chapters.txt |
| Embed cover art | Second input (image) + -map 0 -map 1:v -disposition:v:<i> attached_pic |
| MKV attachment | -attach FILE -metadata:s:t:<i> mimetype=... |
| Strip metadata | -map_metadata -1 -map_chapters -1 |
| Disposition flags | -disposition:<specifier> +default+forced (or bare value to replace) |
All of these should use -c copy — changing metadata never requires re-encoding.
ffmpeg -i in.mp4 -c copy \
-metadata title="My Video" \
-metadata artist="Alice" \
-metadata year=2026 \
-metadata comment="Rendered 2026-04-17" \
out.mp4
ffmpeg -i in.mkv -c copy \
-metadata:s:a:0 language=eng -metadata:s:a:0 title="English 5.1" \
-metadata:s:a:1 language=fra -metadata:s:a:1 title="Français Stereo" \
-metadata:s:s:0 language=eng -metadata:s:s:0 title="English SDH" \
out.mkv
Stream specifiers: s:v:0 (first video), s:a:1 (second audio), s:s:0 (first subtitle), s:t:0 (first attachment in MKV).
Create chapters.txt (FIRST line must be ;FFMETADATA1):
;FFMETADATA1
[CHAPTER]
TIMEBASE=1/1000
START=0
END=60000
title=Intro
[CHAPTER]
TIMEBASE=1/1000
START=60000
END=180000
title=Main
[CHAPTER]
TIMEBASE=1/1000
START=180000
END=210000
title=Outro
Apply:
ffmpeg -i in.mp4 -i chapters.txt -map_metadata 1 -map_chapters 1 -c copy out.mp4
ffmpeg -i in.mp4 -f ffmetadata chapters_out.txt
You can edit the resulting file (change titles, times) and re-apply with the command above.
MP3:
ffmpeg -i in.mp3 -i cover.jpg \
-map 0:a -map 1:v -c copy \
-id3v2_version 3 \
-metadata:s:v title="Album cover" -metadata:s:v comment="Cover (front)" \
-disposition:v:0 attached_pic \
out.mp3
MP4 / M4A:
ffmpeg -i in.m4a -i cover.jpg \
-map 0 -map 1 -c copy \
-disposition:v:1 attached_pic \
out.m4a
MKV (via attachment):
ffmpeg -i in.mkv -attach cover.jpg \
-metadata:s:t:0 mimetype=image/jpeg \
-metadata:s:t:0 filename=cover.jpg \
-c copy out.mkv
ffmpeg -i in.mkv -attach arial.ttf \
-metadata:s:t:0 mimetype=application/x-truetype-font \
-metadata:s:t:0 filename=arial.ttf \
-c copy out.mkv
# ADD flags (keeps any existing)
ffmpeg -i in.mkv -c copy -disposition:s:0 +default+forced out.mkv
# REPLACE flags (drops everything else)
ffmpeg -i in.mkv -c copy -disposition:s:0 default out.mkv
# CLEAR all flags on a stream
ffmpeg -i in.mkv -c copy -disposition:s:0 0 out.mkv
ffmpeg -i in.mp4 -map 0 -map_metadata -1 -map_chapters -1 \
-c copy -fflags +bitexact -flags:v +bitexact -flags:a +bitexact \
out.mp4
The +bitexact flags also suppress the automatic encoder=Lavf... tag ffmpeg adds.
After writing metadata, confirm it stuck:
# All format + stream tags, JSON
ffprobe -v error -show_format -show_streams -show_chapters -of json out.mp4
# Just the title
ffprobe -v error -show_entries format_tags=title -of default=nw=1:nk=1 out.mp4
# Per-stream language tags
ffprobe -v error -show_entries stream=index,codec_type:stream_tags=language,title \
-of compact out.mkv
# Disposition flags
ffprobe -v error -show_entries stream=index,codec_type:stream_disposition=default,forced,attached_pic \
-of compact out.mkv
# Chapters only
ffprobe -v error -show_chapters -of json out.mp4
If a tag is missing, check: (a) container supports that key, (b) you used -c copy (re-encoding sometimes drops unsupported tags), (c) -map_metadata wasn't set to -1.
-c copy is required for metadata-only edits, otherwise ffmpeg re-encodes (slow, quality loss).title, artist, album, album_artist, composer, date/year, comment, genre, track, disc, copyright, description, synopsis, show, episode_id, network, lyrics, performer. Arbitrary keys silently drop.-id3v2_version 3 for maximum player compatibility.-disposition:v:0 attached_pic or players ignore it.covr atom; ffmpeg handles this automatically when attached_pic disposition is set on the image stream.;FFMETADATA1 on line 1 — no leading whitespace, no BOM.TIMEBASE uses fraction form — 1/1000 = milliseconds, 1/1 = seconds, 1/90000 = MPEG-TS ticks. START and END are integers in timebase units.encoder=Lavf... sneaks back in even after -map_metadata -1. Add -fflags +bitexact -flags:v +bitexact -flags:a +bitexact to suppress.-map_chapters -1 is separate from -map_metadata -1 — chapters are tracked independently, strip both to fully clean.-disposition:s:0 +default+forced ADDS, -disposition:s:0 default REPLACES. 0 clears all flags.-map to control order.-c copy — that's by design. To remove them, see -map -0:t exclusion.-metadata:g KEY=VALUE also works for global tags (equivalent to bare -metadata).-metadata:s:a:0 applies to OUTPUT stream 0 of type a, after -map reordering. If you remap, recount.ffmpeg -i episode42.mp3 -i cover.jpg \
-map 0:a -map 1:v -c copy -id3v2_version 3 \
-metadata title="Episode 42: Time Travel" \
-metadata artist="The Pod" -metadata album="The Pod Season 3" \
-metadata date=2026 -metadata track=42 -metadata genre=Podcast \
-disposition:v:0 attached_pic \
out.mp3
chapters.txt (see Step 2).ffmpeg -i movie.mkv -i chapters.txt \
-map_metadata 1 -map_chapters 1 \
-metadata:s:a:0 language=eng -metadata:s:a:0 title="English 5.1" \
-metadata:s:a:1 language=fra -metadata:s:a:1 title="Français 5.1" \
-metadata:s:s:0 language=eng -disposition:s:0 +default \
-c copy tagged.mkv
ffmpeg -i vacation.mp4 -map 0 -map_metadata -1 -map_chapters -1 \
-c copy -fflags +bitexact -flags:v +bitexact -flags:a +bitexact \
clean.mp4
ffprobe -v error -show_format -show_streams clean.mp4 | grep -i tag
ffmpeg -i in.mkv -c copy -disposition:s:0 +default+forced out.mkv
ffprobe -show_entries stream=index:stream_disposition=default,forced -of compact out.mkv
At least one output file must be specifiedCause: -f ffmetadata extraction missing an output path.
Solution: ffmpeg -i in.mp4 -f ffmetadata chapters.txt — chapters.txt is the output.
Cause: Container doesn't support that tag name (common: arbitrary keys on MP4).
Solution: Use MKV for free-form tags, or pick a supported MP4 key from the list in references/tags.md.
Cause: Missing attached_pic disposition, OR cover is first video stream when player expects it second.
Solution: Set -disposition:v:N attached_pic on the image stream; reorder with -map so the primary video is first.
Cause: ffmetadata file missing ;FFMETADATA1 header, or wrong TIMEBASE.
Solution: First line must be exactly ;FFMETADATA1. TIMEBASE must be N/D (e.g. 1/1000).
Cause: Used two-letter code (en) instead of three-letter ISO 639-2 (eng).
Solution: Always use three-letter: eng, fra, deu, spa, ita, por, jpn, kor, zho, rus, ara, hin.
encoder=Lavf... keeps appearingCause: ffmpeg writes its own encoder tag.
Solution: Add -fflags +bitexact -flags:v +bitexact -flags:a +bitexact.
references/tags.md for the ffmetadata format spec, per-container tag catalogs (MP4/MKV/MP3/FLAC/OGG/WAV), ISO 639-2 language codes, disposition flag list, cover-art compatibility matrix, MKV edition entries, and ffprobe verification recipes.scripts/meta.py — argparse CLI for all metadata operations (set, chapters, extract-chapters, cover, attach, strip, disposition). Prints the ffmpeg command it runs; supports --dry-run and --verbose. Stdlib only.Usage examples:
# Set container + per-stream tags
uv run ${CLAUDE_SKILL_DIR}/scripts/meta.py set \
--input in.mp4 --output out.mp4 \
--tags title="My Video" artist="Alice" year=2026 \
--stream-tags a:0:language=eng a:0:title="English 5.1" s:0:language=eng
# Add chapters from inline timestamps
uv run ${CLAUDE_SKILL_DIR}/scripts/meta.py chapters \
--input in.mp4 --output out.mp4 \
--from-timestamps "0:00 Intro" "1:00 Main" "3:00 Outro"
# Add chapters from a pre-written ffmetadata file
uv run ${CLAUDE_SKILL_DIR}/scripts/meta.py chapters \
--input in.mp4 --output out.mp4 --chapters-file chapters.txt
# Extract chapters
uv run ${CLAUDE_SKILL_DIR}/scripts/meta.py extract-chapters \
--input in.mp4 --output chapters.txt
# Embed cover art
uv run ${CLAUDE_SKILL_DIR}/scripts/meta.py cover \
--input song.mp3 --output tagged.mp3 --image cover.jpg
# Attach font to MKV
uv run ${CLAUDE_SKILL_DIR}/scripts/meta.py attach \
--input in.mkv --output out.mkv --file arial.ttf \
--mimetype application/x-truetype-font
# Set disposition flags
uv run ${CLAUDE_SKILL_DIR}/scripts/meta.py disposition \
--input in.mkv --output out.mkv --stream s:0 --flags "+default+forced"
# Strip everything
uv run ${CLAUDE_SKILL_DIR}/scripts/meta.py strip \
--input in.mp4 --output clean.mp4