Toolmingo
Guides8 min read

How to Convert Video Files (MP4, MOV, WebM, MKV, GIF)

Learn how to convert between video formats like MP4, MOV, WebM, MKV, AVI, and GIF — with code examples in JavaScript, Python, Go, and PHP, plus ffmpeg CLI.

Video conversion shows up everywhere: a designer needs WebM for the web, an editor needs MP4 from iPhone MOV, a marketer needs a GIF from a clip. Each format makes different trade-offs between file size, quality, browser support, and codec compatibility — and simply renaming .mov to .mp4 does nothing.

Video format quick reference

Format Container Typical codec Best for Avg. size (1 min, 1080p)
MP4 MPEG-4 H.264 / H.265 Universal — web, mobile, social 60–150 MB
MOV QuickTime ProRes / H.264 Apple ecosystem, Premiere editing 60–500 MB
WebM WebM VP8 / VP9 / AV1 Web <video> tag, no royalties 30–80 MB
MKV Matroska Any Archiving, multi-track (subtitles/audio) 60–400 MB
AVI AVI DivX / Xvid Legacy Windows, DVD rips 100–400 MB
GIF GIF LZW Short looping clips, no audio 2–20 MB
WEBP (anim) WEBP WebP Animated images for web (better than GIF) 1–10 MB

Key rule: converting lossy → lossy (e.g., AVI → MP4) re-encodes and adds generation loss. Always start from the highest-quality source you have.

Converting with ffmpeg (CLI)

ffmpeg handles virtually every video conversion. Install it once and it works everywhere.

# MOV → MP4 (H.264, AAC audio)
ffmpeg -i input.mov -c:v libx264 -c:a aac -movflags +faststart output.mp4

# MP4 → WebM (VP9)
ffmpeg -i input.mp4 -c:v libvpx-vp9 -b:v 0 -crf 30 -c:a libopus output.webm

# MKV → MP4 (copy streams without re-encoding — fast)
ffmpeg -i input.mkv -c copy output.mp4

# MP4 → GIF (15 fps, 480px wide, palette optimized)
ffmpeg -i input.mp4 -vf "fps=15,scale=480:-1:flags=lanczos,palettegen" palette.png
ffmpeg -i input.mp4 -i palette.png -vf "fps=15,scale=480:-1:flags=lanczos,paletteuse" output.gif

# MP4 → extract audio only
ffmpeg -i input.mp4 -vn -c:a copy output.aac

ffmpeg flag cheat sheet:

Flag Meaning
-c:v libx264 Encode video with H.264
-c:v libvpx-vp9 Encode video with VP9
-c:a aac Encode audio with AAC
-c copy Copy streams without re-encoding
-crf 23 Quality factor (0=lossless, 51=worst; 18–28 typical)
-b:v 2M Target video bitrate (2 Mbps)
-movflags +faststart Put MP4 metadata first — enables progressive playback
-ss 00:00:10 -t 30 Start at 10 s, clip 30 s
-vf scale=1280:-1 Scale width to 1280, keep aspect ratio

JavaScript (Node.js with fluent-ffmpeg)

import ffmpeg from "fluent-ffmpeg";

/**
 * Convert a video file from one format to another.
 * @param {string} input  - Source path (e.g. "input.mov")
 * @param {string} output - Destination path (e.g. "output.mp4")
 * @returns {Promise<void>}
 */
export function convertVideo(input, output) {
  return new Promise((resolve, reject) => {
    ffmpeg(input)
      .outputOptions([
        "-c:v libx264",
        "-c:a aac",
        "-movflags +faststart",
        "-crf 23",
      ])
      .on("end", resolve)
      .on("error", reject)
      .save(output);
  });
}

// Usage
await convertVideo("input.mov", "output.mp4");
console.log("Done");

Install: npm install fluent-ffmpeg (requires ffmpeg in PATH).

For browser-side conversion use ffmpeg.wasm:

import { FFmpeg } from "@ffmpeg/ffmpeg";
import { fetchFile, toBlobURL } from "@ffmpeg/util";

const ffmpeg = new FFmpeg();

// Load ffmpeg.wasm (runs in a Web Worker — non-blocking)
const baseURL = "https://unpkg.com/@ffmpeg/core@0.12.6/dist/umd";
await ffmpeg.load({
  coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, "text/javascript"),
  wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, "application/wasm"),
});

await ffmpeg.writeFile("input.mov", await fetchFile(file)); // file = File from <input>
await ffmpeg.exec(["-i", "input.mov", "-c:v", "libx264", "-c:a", "aac", "output.mp4"]);

const data = await ffmpeg.readFile("output.mp4");
const url = URL.createObjectURL(new Blob([data.buffer], { type: "video/mp4" }));

Note: ffmpeg.wasm runs entirely client-side. Files never leave the browser, but processing is slower than native ffmpeg.

Python (subprocess + ffmpeg)

import subprocess
import shlex
from pathlib import Path

def convert_video(
    input_path: str,
    output_path: str,
    crf: int = 23,
    video_codec: str = "libx264",
    audio_codec: str = "aac",
) -> None:
    """
    Convert a video file using ffmpeg.
    Raises subprocess.CalledProcessError on failure.
    """
    cmd = [
        "ffmpeg",
        "-i", input_path,
        "-c:v", video_codec,
        "-c:a", audio_codec,
        "-crf", str(crf),
        "-movflags", "+faststart",
        "-y",           # overwrite output without asking
        output_path,
    ]
    subprocess.run(cmd, check=True, capture_output=True)


def video_to_gif(input_path: str, output_path: str, fps: int = 15, width: int = 480) -> None:
    """Convert a video clip to an optimised GIF using a two-pass palette approach."""
    palette = Path(output_path).with_suffix(".palette.png")
    scale = f"scale={width}:-1:flags=lanczos"

    # Pass 1 — generate colour palette
    subprocess.run(
        ["ffmpeg", "-i", input_path, "-vf", f"fps={fps},{scale},palettegen",
         "-y", str(palette)],
        check=True, capture_output=True,
    )
    # Pass 2 — encode GIF with palette
    subprocess.run(
        ["ffmpeg", "-i", input_path, "-i", str(palette),
         "-vf", f"fps={fps},{scale},paletteuse",
         "-y", output_path],
        check=True, capture_output=True,
    )
    palette.unlink(missing_ok=True)


# Usage
convert_video("input.mov", "output.mp4")
video_to_gif("clip.mp4", "animation.gif", fps=12, width=360)

Go (exec wrapper)

package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

// VideoCodec returns the ffmpeg video codec flag for a given output extension.
func videoCodec(ext string) string {
	switch strings.ToLower(ext) {
	case ".webm":
		return "libvpx-vp9"
	case ".ogv":
		return "libtheora"
	default:
		return "libx264"
	}
}

// ConvertVideo converts src to dst using ffmpeg.
// dst extension determines output format.
func ConvertVideo(src, dst string) error {
	vcodec := videoCodec(filepath.Ext(dst))

	args := []string{
		"-i", src,
		"-c:v", vcodec,
		"-c:a", "aac",
		"-movflags", "+faststart",
		"-crf", "23",
		"-y",
		dst,
	}
	cmd := exec.Command("ffmpeg", args...)
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

func main() {
	if err := ConvertVideo("input.mov", "output.mp4"); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	fmt.Println("Done")
}

PHP (exec + escapeshellarg)

<?php

/**
 * Convert a video file using ffmpeg.
 *
 * @param string $input       Source path
 * @param string $output      Destination path
 * @param int    $crf         Quality (0–51; lower = better; 23 is default)
 * @param string $videoCodec  ffmpeg video codec name
 * @param string $audioCodec  ffmpeg audio codec name
 * @throws RuntimeException   on ffmpeg failure
 */
function convertVideo(
    string $input,
    string $output,
    int $crf = 23,
    string $videoCodec = 'libx264',
    string $audioCodec = 'aac'
): void {
    // ALWAYS escapeshellarg() on user-supplied paths to prevent injection
    $cmd = sprintf(
        'ffmpeg -i %s -c:v %s -c:a %s -crf %d -movflags +faststart -y %s 2>&1',
        escapeshellarg($input),
        escapeshellarg($videoCodec),
        escapeshellarg($audioCodec),
        $crf,
        escapeshellarg($output)
    );

    exec($cmd, $outputLines, $exitCode);

    if ($exitCode !== 0) {
        throw new \RuntimeException(
            "ffmpeg failed (exit $exitCode): " . implode("\n", $outputLines)
        );
    }
}

// Usage
convertVideo('input.mov', 'output.mp4');

// WebM (VP9)
convertVideo('input.mp4', 'output.webm', crf: 30, videoCodec: 'libvpx-vp9', audioCodec: 'libopus');

Quick reference

Task ffmpeg one-liner
MOV → MP4 ffmpeg -i in.mov -c:v libx264 -c:a aac -movflags +faststart out.mp4
MP4 → WebM (VP9) ffmpeg -i in.mp4 -c:v libvpx-vp9 -b:v 0 -crf 30 -c:a libopus out.webm
MKV → MP4 (no re-encode) ffmpeg -i in.mkv -c copy out.mp4
MP4 → GIF two-pass palettegen → paletteuse (see above)
Trim clip ffmpeg -ss 00:00:05 -to 00:00:15 -i in.mp4 -c copy clip.mp4
Mute video ffmpeg -i in.mp4 -an -c:v copy muted.mp4
Scale to 720p ffmpeg -i in.mp4 -vf scale=-1:720 -c:a copy out.mp4
Extract thumbnail ffmpeg -i in.mp4 -ss 00:00:03 -frames:v 1 thumb.jpg

6 common mistakes

1. Renaming the file instead of converting it

Changing video.mov to video.mp4 does not re-encode it. The container changes but the codec stays the same. Some players can handle this, many cannot.

2. Forgetting -movflags +faststart for web streaming

Without it the MP4 metadata block goes at the end. The browser must download the entire file before starting playback. Always add -movflags +faststart for any video served over HTTP.

3. Using -c copy when the codec is incompatible

-c copy skips re-encoding. This is fast and lossless but only works when the source codec is supported in the destination container. Copying H.264 into WebM will fail.

4. Two-pass GIF skipping the palette step

A single-pass GIF conversion (ffmpeg -i input.mp4 output.gif) uses a generic 256-colour palette that looks terrible. Always use the two-pass palettegen / paletteuse pipeline for sharp-looking GIFs.

5. Converting lossy to lossy repeatedly

Every lossy re-encode (AVI → MP4 → WebM) degrades quality. Keep the original source. If you need multiple output formats, always transcode from the original.

6. Not checking the output codec with -c copy and MKV

MKV containers can hold almost any codec, but MP4 cannot hold some (e.g., FLAC audio, HEVC in some cases). Run ffprobe input.mkv first to see what's inside, then decide whether -c copy is safe.

Frequently asked questions

Q: What's the best format for web video? MP4 (H.264) has the widest device support. WebM (VP9) gives 20–40% smaller files but requires a fallback. Use <video> with both sources:

<video>
  <source src="video.webm" type="video/webm">
  <source src="video.mp4" type="video/mp4">
</video>

Q: Can I convert video without installing ffmpeg? Browser-side conversion is possible with ffmpeg.wasm — no server required. It's slower but works entirely in the browser. For online conversion without any code, use the Media Converter tool.

Q: How do I reduce file size without visible quality loss? Lower the CRF value slightly (-crf 28 instead of 23), scale down to a smaller resolution, or drop to 24 fps if the source is 60 fps. For web, VP9 or H.265 give noticeably smaller files than H.264 at the same quality.

Q: Why is my GIF huge — larger than the original MP4? GIF is uncompressed (only LZW, no inter-frame compression). A 10-second 480px GIF can easily hit 10 MB while the MP4 is 1 MB. Consider using <video autoplay loop muted playsinline> instead — it looks identical but uses proper video compression.

Q: How do I convert only a portion of a video? Use -ss (start time) and -t (duration) or -to (end time):

ffmpeg -ss 00:00:30 -to 00:01:00 -i input.mp4 -c copy clip.mp4

Put -ss before -i for fast seeking (keyframe accuracy); after -i for frame-accurate seeking.

Q: Is there a lossless video format? Yes: ffmpeg -i input.mp4 -c:v libx264 -crf 0 lossless.mp4 (lossless H.264) or use MKV with FFV1 for archiving. Lossless files are very large — typically 10× the size of a good-quality lossy encode.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools