| name | ffmpeg-format-conversion |
| version | 1.3.1 |
| description | Convert media between containers and codecs with FFmpeg - remux with -c copy, transcode to H.264/HEVC/AV1/Opus, CRF vs two-pass bitrate, hardware encoders (NVENC/QSV/VideoToolbox). Use when changing file format (MKV to MP4, WAV to Opus), shrinking files, or fixing playback compatibility. Not for trimming/joining clips (use ffmpeg-video-editing), visual effects (ffmpeg-video-filters), loudness/mixing (ffmpeg-audio-processing), or inspecting streams (ffmpeg-media-info). |
| risk | safe |
| source | openrouter-deepsearch |
| date_added | 2026-06-16T00:00:00.000Z |
FFmpeg Format Conversion Skill
Convert media files between different formats and containers using FFmpeg. This skill covers modern codecs (AV1, HEVC), container switching, and optimization for 2026 hardware and software standards.
Mental model: container, codec, and re-encoding
Understanding this model is what lets you pick the fastest, least lossy command instead of blindly re-encoding everything.
- Container (the file extension:
.mp4, .mkv, .webm, .mov) is just a wrapper. It describes how streams are interleaved and indexed, but it does not encode the actual audio or video data.
- Codec (
libx264, libx265, aac, libopus) is the algorithm that compressed each stream. The codec — not the container — determines quality, file size, and playback compatibility.
- Re-encoding means decoding a stream back to raw samples and compressing it again. For lossy codecs this always discards quality and costs significant CPU time, so it should be a deliberate choice, not a default.
The single most important consequence: changing the container does not require re-encoding the streams. If an MKV holds H.264 video and AAC audio that an MP4 can also hold, you can copy the streams byte-for-byte into a new container (a "remux"). That is why -c copy appears so often below — it is near-instant and lossless. Reach for a real encoder only when the target container genuinely cannot carry the existing codec, or when you actually want different quality, size, or compatibility.
When to Use
- Convert video containers (MP4, MKV, AVI, WebM, etc.) — often a pure remux.
- Convert audio formats (MP3, AAC, WAV, Opus, FLAC, etc.).
- Transcode to a different codec for compatibility (old TV, browser, phone) or for better compression.
- Copy streams without re-encoding (stream copying / remuxing) for near-instant conversion.
- Optimize media for web delivery (smaller, faststart) or archival storage (lossless).
When NOT to use
| If the task is... | Use instead |
|---|
| Cutting, trimming, or concatenating clips | ffmpeg-video-editing |
| Scaling, cropping, watermarks, speed changes, visual effects | ffmpeg-video-filters |
| Loudness normalization, mixing, channel extraction | ffmpeg-audio-processing |
| Inspecting codecs, streams, or duration (often the right first step) | ffmpeg-media-info |
| Extracting keyframes or thumbnails | ffmpeg-keyframe-extraction |
Also out of scope:
- Media already in the desired container and codec — re-encoding again only adds generation loss and wastes time.
- High-end non-linear editing (color grading, multi-track timelines) where a dedicated NLE/DAW gives you a UI and non-destructive edits.
- DRM-encrypted files without the proper, lawfully obtained decryption keys.
Prerequisites
- FFmpeg installed and on
PATH. Verify with ffmpeg -version (Windows PowerShell) or which ffmpeg (macOS/Linux).
- ffprobe (ships with FFmpeg) for verification steps.
- For hardware encoders: appropriate GPU drivers (NVIDIA for NVENC, Intel for QSV, Apple for VideoToolbox).
- For batch scripts: Bash shell on macOS/Linux, or Git Bash / WSL on Windows. The TypeScript wrapper requires Node.js 18+.
Decision guide
| Situation | Approach | Why |
|---|
| Same codecs, different container (MKV to MP4) | -c copy (remux) | Lossless and seconds-fast; no quality decision to make. |
| Target container cannot hold the source codec | Re-encode only the offending stream | Copy what you can, transcode the minimum. |
| Need smaller files for the web | libx265/libsvtav1 with CRF | Modern codecs cut size ~30-50% at equal quality. |
| Need maximum playback compatibility | libx264 + yuv420p + AAC | Plays on essentially everything, including old hardware. |
| Need an exact target file size | Two-pass bitrate encoding | The first pass measures complexity so the budget lands accurately. |
| Encoding a huge batch or in real time | Hardware encoder (NVENC/QSV/VideoToolbox) | Order-of-magnitude faster, at a small efficiency cost. |
Procedure
Step 1: Probe the source before converting
Always inspect the source first so you know which streams exist and whether a remux is possible.
# Windows PowerShell — probe source streams
ffprobe -v error -show_entries stream=index,codec_type,codec_name -of default input.mkv
ffprobe -v error -show_entries stream=index,codec_type,codec_name -of default input.mkv
Record the codec names and stream count. This baseline is what you compare the output against in Verification.
Step 2: Stream copy (remux) — try this first
Because a remux is lossless and fast, it should be your first instinct whenever the source codecs are already compatible with the target container.
ffmpeg -i input.mkv -c copy output.mp4
ffmpeg -i input.mkv -c:v copy -c:a aac -b:a 192k output.mp4
ffmpeg -i input.mp4 -c copy -movflags +faststart output.mp4
Step 3: Re-encode when remux is not possible
Be explicit about both the video and audio codec. Relying on FFmpeg's container-default codec works, but stating -c:v and -c:a makes the command self-documenting and reproducible.
ffmpeg -i input.avi -c:v libx264 -c:a aac output.mp4
ffmpeg -i input.mp4 -c:v libx265 -crf 28 -c:a copy output.mp4
Step 4: Choose the video codec
Each codec is a trade between compatibility, file size, and encode speed. Pick based on where the file will play, not on which codec is newest.
ffmpeg -i input.mp4 -c:v libx264 -pix_fmt yuv420p -crf 23 -c:a aac output.mp4
ffmpeg -i input.mp4 -c:v libx265 -crf 28 -c:a aac output.mp4
ffmpeg -i input.mp4 -c:v libvpx-vp9 -crf 30 -b:v 0 -c:a libopus output.webm
ffmpeg -i input.mp4 -c:v libsvtav1 -preset 6 -crf 30 -c:a libopus output.mp4
Step 5: Choose the audio codec
The audio choice mirrors the video logic: lossless for archives, efficient lossy codecs for distribution, legacy codecs only when a target device demands them.
ffmpeg -i input.wav -c:a libmp3lame -q:a 2 output.mp3
ffmpeg -i input.wav -c:a aac -b:a 192k output.m4a
ffmpeg -i input.wav -c:a libopus -b:a 128k output.opus
ffmpeg -i input.wav -c:a flac output.flac
Step 6: Quality control — CRF vs. bitrate
CRF (Constant Rate Factor) targets a consistent visual quality and lets file size float, which is what you want for almost all on-demand video. Target-bitrate encoding does the opposite: it pins the size and lets quality float. Reach for bitrate only when a hard size limit matters.
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -c:a copy output.mp4
ffmpeg -i input.mp4 -c:v libx264 -b:v 2M -c:a aac -b:a 192k output.mp4
ffmpeg -y -i input.mp4 -c:v libx264 -b:v 2M -pass 1 -an -f null /dev/null
ffmpeg -y -i input.mp4 -c:v libx264 -b:v 2M -pass 2 -c:a aac -b:a 192k output.mp4
Windows PowerShell two-pass variant — use NUL as the null sink:
ffmpeg -y -i input.mp4 -c:v libx264 -b:v 2M -pass 1 -an -f null NUL
ffmpeg -y -i input.mp4 -c:v libx264 -b:v 2M -pass 2 -c:a aac -b:a 192k output.mp4
Step 7: Speed presets
A preset is a dial between encode time and compression efficiency. A slower preset spends more CPU finding savings, producing a smaller file at the same CRF — quality stays constant, size and time change. Use a slow preset for files you encode once and serve many times; use a fast preset for throwaway or real-time work.
ffmpeg -i input.mp4 -c:v libx264 -preset slow -crf 22 -c:a copy output.mp4
ffmpeg -i input.mp4 -c:v libsvtav1 -preset 8 -crf 30 -c:a libopus output.mp4
Step 8: Hardware acceleration (optional)
Hardware encoders run on the GPU's dedicated media block instead of the CPU. They are typically an order of magnitude faster, which makes them ideal for large batches or live streaming. The trade-off: at a given file size they are slightly less efficient than a slow software encode, so for archival masters where every bit counts, software (libx264/libx265/libsvtav1) still wins.
ffmpeg -i input.mp4 -c:v h264_nvenc -preset p5 -cq 23 -c:a copy output.mp4
ffmpeg -i input.mp4 -c:v hevc_nvenc -preset p5 -cq 28 -c:a copy output.mp4
ffmpeg -i input.mp4 -c:v h264_qsv -global_quality 23 -c:a copy output.mp4
ffmpeg -i input.mp4 -c:v h264_videotoolbox -b:v 4M -c:a copy output.mp4
ffmpeg -i input.mp4 -c:v av1_nvenc -preset p5 -cq 30 -c:a copy output.mp4
ffmpeg -i input.mp4 -c:v av1_qsv -global_quality 30 -c:a copy output.mp4
Step 9: Batch conversion (Bash)
A naive for f in *.mkv loop is a trap: it silently does nothing when there are no matches, breaks on filenames with spaces, clobbers existing outputs, and reports success even when individual encodes fail. The script below is the hardened version.
When to use: If you need to convert more than a handful of files, copy the batch script example below into your project. It handles nullglob, never clobbers, deletes half-written outputs on failure, and propagates a non-zero exit code to CI.
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat >&2 <<'EOF'
Usage: convert-batch.sh <source-ext> <target-ext> [ffmpeg-args...]
source-ext Extension to match in the current directory (e.g. mkv)
target-ext Extension for the produced files (e.g. mp4)
ffmpeg-args Optional encoder flags. Defaults to stream copy (-c copy).
Examples:
convert-batch.sh mkv mp4
convert-batch.sh avi mp4 -c:v libx264 -c:a aac
EOF
exit 64
}
[[ $# -ge 2 ]] || usage
src_ext="${1#.}"
dst_ext="${2#.}"
shift 2
ffmpeg_args=("$@")
[[ ${#ffmpeg_args[@]} -gt 0 ]] || ffmpeg_args=(-c copy)
if [[ "$src_ext" == "$dst_ext" ]]; then
echo "error: source and target extensions are identical ('$src_ext')" >&2
exit 64
fi
if ! command -v ffmpeg >/dev/null 2>&1;
>&2
69
-s nullglob
inputs=( *. )
-u nullglob
[[ -eq 0 ]];
>&2
66
converted=0
skipped=0
failed=0
input ;
output=
[[ -e ]];
>&2
skipped=$((skipped + ))
ffmpeg -nostdin -hide_banner -loglevel error \
-i ;
converted=$((converted + ))
>&2
-f --
failed=$((failed + ))
[[ -eq 0 ]]
Step 10: Programmatic conversion (TypeScript wrapper)
When to use: If conversion is part of a larger Node.js application, copy the TypeScript wrapper example below into your project. It uses spawn (an argument array, never a shell string) to prevent shell-injection from unsanitized paths, validates parameters before launching, and surfaces failures as typed errors. There are no any types — unknown plus type guards handle the genuinely dynamic edges.
import { spawn } from "node:child_process";
import { access, constants } from "node:fs/promises";
import { basename } from "node:path";
type VideoCodec = "libx264" | "libx265" | "libvpx-vp9" | "libsvtav1" | "copy";
type AudioCodec = "aac" | "libmp3lame" | "libopus" | "flac" | "copy";
type X26xPreset =
| "ultrafast"
| "superfast"
| "veryfast"
| "faster"
| "fast"
| "medium"
| "slow"
| "slower"
| "veryslow";
interface ConvertOptions {
readonly input: string;
readonly output: string;
: ;
: ;
?: ;
?: X26xPreset;
?: ;
?: ;
}
{
: ;
: [];
}
: <
<<, >, [: , : ]>
> = {
: [, ],
: [, ],
: [, ],
: [, ],
};
{
() {
(message);
. = ;
}
}
(): value is . {
value && value;
}
(): <> {
{
(path, constants.);
;
} {
;
}
}
(): <> {
{
(path, constants.);
} (: ) {
reason =
(cause) && cause. === ? : ;
();
}
}
(): {
(options..() === || options..() === ) {
();
}
(options. === options.) {
();
}
(options. !== ) {
(options. === ) {
();
}
(!.(options.)) {
();
}
[min, max] = [options.];
(options. < min || options. > max) {
(
,
);
}
}
(options. !== ) {
(options. === ) {
();
}
(!.(options.) || options. <= ) {
(
,
);
}
}
}
(): [] {
: [] = [, , , ];
args.(options. === ? : );
args.(, options.);
args.(, options.);
(options. !== ) {
(options. !== ) {
args.(, (options.));
}
(
options. !== &&
(options. === || options. === )
) {
args.(, options.);
}
(options. === ) {
args.(, );
}
(options. === && options. !== ) {
args.(, );
}
}
args.(, options.);
(
options. !== &&
options. !== &&
options. !==
) {
args.(, );
}
args.(options.);
args;
}
(): <> {
(options);
(options.);
(options. !== && ( (options.))) {
(
,
);
}
args = (options);
<>( {
child = (, args, { : [, , ] });
stderr = ;
child.?.();
child.?.(, {
stderr += chunk;
});
child.(, {
hint =
(cause) && cause. ===
?
: cause.;
( ());
});
child.(, {
(code === ) {
({ : options., args });
;
}
(
(
,
code,
stderr.(),
),
);
});
});
}
(): <> {
{
result = ({
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: ,
});
.();
} (: ) {
(error ) {
.();
process. = ;
} (error ) {
.();
process. = ;
} {
.(, error);
process. = ;
}
}
}
();
{ convert, };
{ , , , , X26xPreset };
Examples
Quick reference: common conversions
ffmpeg -i input.mkv -c copy output.mp4
ffmpeg -i input.mkv -c:v copy -c:a aac -b:a 192k output.mp4
ffmpeg -i input.wav -c:a libopus -b:a 128k output.opus
ffmpeg -i input.avi -c:v libx264 -pix_fmt yuv420p -crf 23 -c:a aac output.mp4
ffmpeg -i input.mp4 -c:v libvpx-vp9 -crf 30 -b:v 0 -c:a libopus output.webm
ffmpeg -i input.mp4 -c:v libx265 -crf 28 -c:a copy output.mp4
ffmpeg -i input.mp4 -c copy -movflags +faststart output.mp4
Common codecs reference
Video:
- H.264 (
libx264) — universal compatibility; the safe default when the target is unknown.
- H.265 (
libx265) — high efficiency for 4K/HDR; smaller files, but patchy browser support.
- VP9 (
libvpx-vp9) — royalty-free, native in browsers; remember -b:v 0 for CRF mode.
- AV1 (
libsvtav1 / libaom-av1) — best compression and royalty-free; prefer SVT-AV1 for usable encode speeds, or av1_nvenc/av1_qsv where the GPU supports it.
Audio:
- AAC (
aac) — the modern default for MP4/M4A; better than MP3 at equal bitrate.
- MP3 (
libmp3lame) — legacy only; choose it when a specific old device requires it.
- Opus (
libopus) — most efficient lossy codec, superb for both speech and music.
- FLAC (
flac) — lossless; for archival masters and lossless transcodes.
Pitfalls
- Remux before you re-encode. If you only need to change the container,
-c copy is lossless and seconds-fast. Re-encoding "just to be safe" throws away quality for no benefit.
-pix_fmt yuv420p for H.264. Many hardware decoders and QuickTime reject other chroma subsamplings (such as yuv444p). Adding it is the difference between "plays everywhere" and "black screen on a smart TV".
+faststart for web MP4s. Without it the index (moov atom) sits at the end of the file, so a browser must download the whole thing before playback can begin. It is a free remux flag, so include it for anything streamed.
- VP9 CRF needs
-b:v 0. Omitting it leaves libvpx in a constrained-bitrate mode and your -crf is effectively ignored.
- Hardware encoders are fast, not free. NVENC/QSV/VideoToolbox trade a little compression efficiency for huge speed. Great for batches and live; for size-critical archival, slow software encoding still wins per bit.
- Process untrusted media carefully. Malformed files have historically triggered decoder vulnerabilities. Keep FFmpeg current, and in automated pipelines prefer
spawn with an argument array over a shell string so filenames cannot inject commands.
- Use modern flag syntax. Prefer
-c:v / -c:a over the deprecated -vcodec / -acodec; the stream-specifier form is what current documentation and newer features assume.
- The null sink is platform-specific. Two-pass pass-one discards its muxed output to
/dev/null on macOS/Linux and NUL on Windows.
- Naive batch loops are dangerous. A bare
for f in *.mkv silently does nothing when there are no matches (the glob expands to a literal *.mkv), breaks on filenames with spaces, clobbers existing outputs, and reports success even when individual encodes fail. Always use nullglob, -nostdin, existence checks, and exit-code propagation.
- Never delete source files. Conversion outputs are derivatives; the source is the master. Automated pipelines must never delete or overwrite the original input as part of a conversion step.
Verification
These checks exist because FFmpeg can exit 0 while still producing a file that is subtly wrong (missing audio, wrong pixel format, unplayable on the target). Confirm the result, not just the exit code.
- Probe the source to record a baseline:
ffprobe -v error -show_entries stream=index,codec_type,codec_name -of default input.mkv
- Probe the output and confirm the codec and container match what you intended:
ffprobe -v error -show_entries stream=index,codec_type,codec_name -of default output.mp4
- Confirm stream counts match. A dropped audio or subtitle track is a common silent failure:
ffprobe -v error -show_entries stream=index,codec_type -of csv output.mp4
-
Play the output in a real player (VLC, mpv, or the actual target device) to catch A/V sync and seeking problems that ffprobe cannot see.
-
Check the file size against your goal — if you re-encoded to shrink a file and it grew, the CRF/bitrate is wrong.
-
For batch jobs: confirm the output count equals the input count, and that the script's exit code is 0.
Related skills
ffmpeg-media-info — probe codecs, streams, and duration first; the answer decides remux vs. re-encode.
ffmpeg-video-editing — cut, trim, and concatenate segments.
ffmpeg-video-filters — scale, crop, watermark, speed, and visual effects.
ffmpeg-audio-processing — loudness normalization, mixing, and channel work.
ffmpeg-keyframe-extraction — pull I-frames and thumbnails out of video.