Every video file carries a set of tags alongside the picture and sound. Most people never look at them, which is precisely why they are worth looking at before publishing anything.
See what is there
ffprobe -v error -show_format -show_streams input.mp4 | grep -i -A20 TAG
Typical contents: the encoding software and its version, the camera or phone model, a creation timestamp, and occasionally location coordinates or the full path of the project on someone's computer.
None of it is needed for playback. All of it is published with the file.
Strip everything, then add back what you want
ffmpeg -i input.mp4 -map_metadata -1 -c copy clean.mp4
-map_metadata -1 removes the lot. -c copy means no frame is touched, so this completes in about a second regardless of file length.
Then set only what belongs there:
ffmpeg -i clean.mp4 -metadata title="Product Tour" \ -metadata comment="example.com" -c copy final.mp4
Strip-then-add is more reliable than trying to remove specific fields, because you do not have to know in advance what a particular camera or editor decided to write.
Common fields
title, artist, album, date, comment, description. Containers differ in what they keep: MKV accepts almost anything, MP4 is stricter, and a tag silently dropped is normal instead of an error.
Streams can be tagged individually, which is how you label multiple audio or subtitle tracks so a player can present them properly:
ffmpeg -i input.mkv -map 0 -c copy \ -metadata:s:a:0 language=eng -metadata:s:a:0 title="English" \ -metadata:s:a:1 language=tur -metadata:s:a:1 title="Türkçe" out.mkv
Language tags are the ones that matter in practice: without them a player shows "Track 1" and "Track 2" and the viewer guesses. Working with audio tracks explains the rest.
Chapters
Chapters let a viewer jump within a long video, and they are stored as metadata rather than as anything in the picture.
Export what the file has:
ffmpeg -i input.mp4 -f ffmetadata chapters.txt
The format is plain text:
;FFMETADATA1 [CHAPTER] TIMEBASE=1/1000 START=0 END=125000 title=Introduction [CHAPTER] TIMEBASE=1/1000 START=125000 END=302000 title=Setting up
TIMEBASE=1/1000 means the numbers are milliseconds, so 125000 is two minutes and five seconds. Getting this line wrong is the usual reason chapters land in absurd places.
Write them in:
ffmpeg -i input.mp4 -i chapters.txt -map_metadata 1 -c copy out.mp4
Finding the boundaries by hand is the tedious part, and it can be automated, detecting scenes and silence produces a usable first draft of the timestamps.
Support is uneven
MKV handles chapters well. MP4 support is real but inconsistent across players, and browsers largely ignore embedded chapters entirely.
If chapters are for a website, the reliable route is to keep the list beside the video and have the player seek to a timestamp, rather than relying on the container. Embedded chapters are for files people download.
Make stripping part of the pipeline
Any site accepting video uploads is accepting whatever metadata those files carry, and republishing it.
Adding -map_metadata -1 to the processing step costs nothing and removes an entire category of accidental disclosure, device models, timestamps, home locations. That nobody involved intended to publish.
Handling video uploads from a web application goes over where in the pipeline it belongs.
Container support is not uniform
Metadata is written differently by each container, and a field that survives in one is silently dropped in another.
MKV accepts almost any tag you invent. MP4 has a defined set and discards the rest without complaint. WebM is stricter still.
ffmpeg -i in.mkv -map_metadata 0 -c copy out.mp4 ffprobe -v error -show_format out.mp4 | grep -i TAG
Comparing before and after is the only reliable way to know what survived. A tag that disappears during a container change is normal in place of an error, and it is why titles set on an MKV vanish when it becomes an MP4.
Where a field matters and the container will not hold it, keep it in your own database alongside the file rather than relying on the file to carry it.
Creation time is the field that misleads
Players and file browsers display a creation timestamp, and it comes from the container rather than from the filesystem.
A converted file therefore shows the date of the conversion, not of the original recording, which reorders a library sorted by date and is frequently reported as files having been altered.
ffmpeg -i in.mp4 -map_metadata 0 -c copy -metadata creation_time="2026-03-14T10:00:00" out.mp4
Preserve it deliberately when converting an archive, or set it from a known value. And note that stripping metadata removes it entirely, which is usually what you want when publishing and rarely what you want when archiving: the two purposes need different commands.
Reading chapters back reliably
Confirming that chapters were written is worth doing, because a malformed metadata file produces no error and no chapters.
ffprobe -v error -show_chapters -of default=noprint_wrappers=1 out.mp4 | head -20
If nothing is listed, the usual cause is the time base: a file declaring milliseconds while the numbers are in seconds produces chapters at absurd positions or none at all.
Check the first chapter's start and the last chapter's end against the file's duration. Those two numbers catch every scaling mistake in one glance. Using ffprobe deals with reading the duration.
Batch stripping without touching anything else
for f in *.mp4; do ffmpeg -nostdin -v error -i "$f" -map_metadata -1 -c copy "clean/$f" && echo "ok $f" done
Because it is a copy operation, a directory of files processes in seconds rather than hours, and nothing about the picture or sound changes.
The -nostdin matters in a loop: without it FFmpeg consumes the shell's input and only the first file is processed; a failure that looks like the loop being broken. Building a batch pipeline deals with the rest of that pattern.
Metadata leaves the file even when you strip it
Removing the fields from one file does nothing about the copies that already exist.
ffprobe -v error -show_entries format_tags -of json input.mp4 ffprobe -v error -show_entries stream_tags -of json input.mp4 | head -20
Read both. Fields exist at the container level and again per stream, and a command that clears one leaves the other in place.
The stream level fields are the ones people miss, because the usual check shows the container and reports nothing. A file that appears clean can still carry an encoder name, a language tag or a handler string identifying the software and occasionally the device.
Location data is the field worth checking
Video from a phone frequently carries where it was recorded, and publishing it is a decision people make without knowing they made it.
ffprobe -v error -show_entries format_tags=location,location-eng,com.apple.quicktime.location.ISO6709 \ -of default=nw=1 input.mp4
If anything is returned, the coordinates are in the file and travel with it to whoever downloads it.
Strip it deliberately rather than relying on a general clear, and confirm afterwards rather than assuming. This is the one field where the consequence of missing it is about a person rather than about tidiness, which makes it worth its own check in any pipeline handling material from customers.
Verify the result rather than trusting the command
A strip that ran without error can still have left fields behind, and comparing before and after takes one command.
for f in input.mp4 output.mp4; do printf '%-14s ' "$f" ffprobe -v error -show_entries format_tags -of json "$f" | tr -d '\n ' | head -c 120 echo done
The output for the cleaned file should contain no fields beyond what you added deliberately. Anything remaining means the strip applied to the container and not the streams, or the container format kept a field it defines itself.
Some formats always record something. Where that matters, changing container is the answer rather than more aggressive stripping, since a field the format requires cannot be removed and still produce a valid file. Remuxing and stream copy covers changing it without re-encoding.