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

ApproachBest forTradeoff
ffmpeg-python (stream-copy)Most MKVs — instant for H.264 + AAC contentNeed to detect audio codec first
ffmpeg-python (burn subs)MKV with subtitles you must keepAlways re-encodes video — slow
ChangeThisFile APINo FFmpeg in your environmentNetwork 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.