MKV (Matroska) is a container that holds basically anything — H.264, H.265, AV1, AAC, FLAC, AC3, embedded subtitles. MP4 is more restrictive. The conversion is mostly about identifying which streams need re-encoding (usually audio and subtitles) and which can be copied as-is (usually video).
Method 1: ffmpeg-python with smart stream handling
The right pattern: copy the video stream (almost always H.264-compatible), re-encode audio if it's not AAC/MP3, drop or burn-in subtitles.
pip install ffmpeg-python
apt install ffmpeg
import ffmpeg
def get_audio_codec(in_path: str) -> str:
probe = ffmpeg.probe(in_path)
audio = next((s for s in probe["streams"] if s["codec_type"] == "audio"), None)
return audio["codec_name"] if audio else ""
def mkv_to_mp4(in_path: str, out_path: str) -> None:
audio_codec = get_audio_codec(in_path)
args = {
"c:v": "copy", # video: always copy (H.264 → MP4 is fine)
"movflags": "+faststart", # web-ready
"sn": None, # drop subtitle streams
}
if audio_codec in ("aac", "mp3"):
args["c:a"] = "copy"
else:
# FLAC, AC3, DTS, etc. → re-encode to AAC
args["c:a"] = "aac"
args["b:a"] = "192k"
(
ffmpeg
.input(in_path)
.output(out_path, **args)
.overwrite_output()
.run(quiet=True)
)
mkv_to_mp4("movie.mkv", "movie.mp4")
Three things to know:
- Video is almost always copyable. If video is H.264 or H.265, MP4 supports it. If it's VP9 or AV1, you need to re-encode to libx264.
- Audio often needs re-encoding. MKV commonly carries FLAC, AC3, or DTS — none of which MP4 supports natively. Re-encode to AAC at 192k.
- Subtitles drop by default. MKV's MKS subtitles aren't compatible with MP4's mov_text. Either drop (sn) or burn into video (filter_complex with -vf subtitles=).
Method 2: With burned-in subtitles
If the MKV has subtitles you want to preserve, burn them into the video stream (renders them as part of the picture).
import ffmpeg
def mkv_to_mp4_with_subs(in_path: str, out_path: str, subtitle_idx: int = 0) -> None:
# Burning subtitles requires re-encoding video — always slower than stream-copy
(
ffmpeg
.input(in_path)
.output(
out_path,
**{
"vf": f"subtitles={in_path}:si={subtitle_idx}",
"c:v": "libx264",
"crf": 23,
"preset": "medium",
"c:a": "aac",
"b:a": "192k",
"movflags": "+faststart",
},
)
.overwrite_output()
.run(quiet=True)
)
mkv_to_mp4_with_subs("movie.mkv", "movie.mp4", subtitle_idx=0)
Burning subtitles requires re-encoding the video, which takes 5-10x as long as stream-copy. For large movies this can be 1+ hour. If subtitles are optional, dropping them keeps the conversion fast.
Method 3: ChangeThisFile API (no FFmpeg)
If you don't want FFmpeg in your container, the API runs it server-side. Free tier covers 1,000 conversions/month.
import requests
API_KEY = "ctf_sk_your_key_here"
def mkv_to_mp4(in_path: str, out_path: str) -> None:
with open(in_path, "rb") as f:
response = requests.post(
"https://changethisfile.com/v1/convert",
headers={"Authorization": f"Bearer {API_KEY}"},
files={"file": f},
data={"source": "mkv", "target": "mp4"},
timeout=600, # movies can take a while
)
response.raise_for_status()
with open(out_path, "wb") as out:
out.write(response.content)
mkv_to_mp4("movie.mkv", "movie.mp4")
The API auto-detects video/audio codecs and uses stream-copy where possible. Subtitles are dropped by default; pass burn_subtitles=true in form data to render them into video.
When to use each
| Approach | Best for | Tradeoff |
|---|---|---|
| ffmpeg-python (stream-copy) | Most MKVs — instant for H.264 + AAC content | Need to detect audio codec first |
| ffmpeg-python (burn subs) | MKV with subtitles you must keep | Always re-encodes video — slow |
| ChangeThisFile API | No FFmpeg in your environment | Network call, file size limit (25MB free) |
Production tips
- Always probe audio codec first. If audio is AAC/MP3, full stream-copy works (lossless, instant). If it's FLAC/AC3/DTS, audio re-encoding is needed but video can still copy.
- Drop subtitles for fast conversion. MKV embedded subtitles (SRT, ASS, PGS) don't carry into MP4 cleanly. Either drop, burn-in (slow), or extract to a separate .srt sidecar.
- Watch for video codecs MP4 doesn't support. VP9, AV1, Theora — these require full video re-encoding to libx264. Check ffprobe output before estimating conversion time.
- Bound concurrency strictly. Movie-length re-encoding can take hours. For batch jobs, run one at a time per host or you'll OOM.
- Use timeout=600s+ for movies. Re-encoding a 2-hour HD movie takes 30+ minutes on typical hardware. Default 60s timeouts will fail.
For most MKV files, stream-copying video + re-encoding audio gives near-instant conversion at original quality. For environments without FFmpeg, the API. Free tier covers 1,000 conversions/month.