Pure Go MKV/WebM toolkit. Read, write, mux, split, edit MKV containers, and remux to/from MP4. Stdlib only, zero external dependencies.
CLI tool and importable Go library.
go install github.com/gravity-zero/mkvgo/cmd/mkvgo@latestOr as a library:
go get github.com/gravity-zero/mkvgomkvgo <command> [options]
Global flags: -json (structured output), --version
| Category | Command | Description |
|---|---|---|
| Inspection | info |
Show container info (title, duration, muxing app) — MKV or MP4 |
tracks |
List all tracks with codec, language, resolution, Dolby Vision — MKV or MP4 | |
chapters |
List chapters with timestamps — MKV or MP4 | |
attachments |
List attachments (fonts, images) | |
tags |
Show all tags | |
probe |
Full dump of all metadata (colour, Dolby Vision, keyframes, dropped tracks) — MKV or MP4 | |
keyframes |
List video keyframe timestamps (MKV Cues / MP4 sample table) | |
validate |
Check MKV structure for issues | |
compare |
Diff metadata of two MKV files | |
| Extraction | demux |
Extract tracks to raw streams |
extract-attachment |
Extract an attachment to file | |
extract-subtitle |
Extract subtitle track as SRT, ASS or WebVTT (MKV or MP4) | |
to-vtt |
Convert an external .srt/.ass/.vtt sidecar to WebVTT |
|
| Editing | edit |
Edit metadata from JSON (arg or stdin) |
edit-title |
Change the container title | |
edit-track |
Edit track properties (lang, name, default, forced) | |
edit-inplace |
Edit metadata without rewriting clusters (instant) | |
remove-track |
Remove tracks from an MKV | |
add-track |
Add a track from another MKV | |
| Assembly | mux |
Combine tracks into a single MKV |
merge |
Combine all tracks from multiple MKVs | |
merge-subtitle |
Inject an external SRT/ASS into an MKV | |
join |
Concatenate multiple MKVs sequentially | |
| Splitting | split |
Split MKV by time ranges or chapters |
| Indexing | reindex |
Rebuild the seek index (Cues) of a file |
| Remux | to-mp4 |
Remux MKV/WebM to MP4 (--faststart, --skip-unsupported) |
from-mp4 |
Remux MP4 to MKV | |
to-webm |
Remux MKV/WebM to WebM (WebM-subset codecs only) |
Full CLI reference: docs/cli.md
Probe a file:
mkvgo probe video.mkv
mkvgo probe -json video.mkv | jq '.tracks[]'Read metadata from stdin (pipe-friendly):
The inspection commands (info, tracks, chapters, attachments, tags, probe) accept - as the input path and read from stdin via the streaming reader.
cat video.mkv | mkvgo info -
cat video.mkv | mkvgo tracks -json -Remove a track:
# Remove track 3 (e.g. commentary audio)
mkvgo remove-track video.mkv -o clean.mkv -t 3Edit metadata with JSON:
# Via argument
mkvgo edit video.mkv -o edited.mkv '{"title":"New Title"}'
# Via stdin (pipe from file or another tool)
cat meta.json | mkvgo edit video.mkv -o edited.mkv -Split by time:
# Split into two parts: 0-5min and 5min-end
mkvgo split video.mkv -o parts/ -range 0-300000,300000-0Merge subtitles:
mkvgo merge-subtitle video.mkv -o output.mkv subs.srt -lang eng -name "English"
mkvgo merge-subtitle video.mkv -o output.mkv subs.ass -format ass -lang jpn -name "Japanese"Extract subtitles:
mkvgo extract-subtitle video.mkv -t 3 -o subs.srt
mkvgo extract-subtitle video.mkv -t 3 -o subs.ass -format assRemux to/from MP4 (no transcode):
# MKV/WebM → MP4 (H.264/HEVC/AV1 + AAC/Opus/AC-3/E-AC-3/FLAC/MP3/DTS,
# SRT/WebVTT→tx3g, chapters, colour/HDR preserved)
mkvgo to-mp4 video.mkv video.mp4
# Subtitles: ASS/SSA → tx3g (lossy), or WebVTT → lossless native wvtt (Apple/CMAF)
mkvgo to-mp4 --flatten-subs anime.mkv anime.mp4
mkvgo to-mp4 --webvtt-native web.mkv web.mp4
# moov before mdat (progressive HTTP), and drop tracks MP4 can't carry (e.g. TrueHD)
mkvgo to-mp4 --faststart --skip-unsupported video.mkv video.mp4
# MP4 → MKV
mkvgo from-mp4 video.mp4 video.mkv
# MKV/WebM → WebM (VP8/VP9/AV1 + Vorbis/Opus only; other codecs rejected)
mkvgo to-webm video.mkv video.webmFull library guide: docs/library.md
Import the facade package for convenience, or import sub-packages directly.
Read metadata:
package main
import (
"context"
"fmt"
"github.com/gravity-zero/mkvgo/matroska"
)
func main() {
c, err := matroska.Open(context.Background(), "video.mkv")
if err != nil { panic(err) }
fmt.Println(c.Info.Title, c.DurationMs, "ms")
for _, t := range c.Tracks {
fmt.Printf(" #%d %s %s (%s)\n", t.ID, t.Type, t.Codec, t.Language)
}
}For library indexing, prefer matroska.OpenMeta (or mp4.OpenMeta for MP4): it returns the same Info + Tracks but stops as soon as both are parsed — never walking Clusters/Cues — so it is orders of magnitude faster than the full Open. Use Open only when you also need Chapters/Attachments/Tags/Cues.
Mux tracks from multiple sources:
err := matroska.Mux(ctx, matroska.MuxOptions{
OutputPath: "output.mkv",
Tracks: []matroska.TrackInput{
{SourcePath: "video.mkv", TrackID: 1},
{SourcePath: "audio.mkv", TrackID: 1, Language: "eng", Name: "Stereo"},
},
})Rebuild the seek index of a file:
err := ops.Reindex(ctx, "input.mkv", "output.mkv")Read or write from a non-seekable stream (pipe, network):
reader.ReadStream parses metadata and returns a *BlockReader from an io.Reader without ever calling Seek. writer.NewStreamWriter writes a live MKV stream to an io.Writer using unknown-size Segment and Clusters. See docs/library.md for details.
Remux a file to WebM:
// Validates the codecs (VP8/VP9/AV1, Vorbis/Opus, WebVTT), copies the media
// verbatim into a webm-DocType container, rejects non-WebM codecs.
err := matroska.RemuxToWebM(ctx, "in.mkv", "out.webm")Remux to/from MP4 (no transcode):
// H.264/HEVC/AV1 video; AAC/Opus/AC-3/E-AC-3/FLAC/MP3/DTS audio; SRT/WebVTT→tx3g
// (Options{NativeWebVTT:true} keeps WebVTT lossless as wvtt; Options{FlattenStyledSubs:true}
// carries ASS/SSA as tx3g); chapters, colour/HDR and B-frame ordering preserved.
// Options{FastStart:true} writes moov first; Options{SkipUnsupported:true} drops tracks.
err := mp4.RemuxToMP4(ctx, "in.mkv", "out.mp4")
err = mp4.RemuxFromMP4(ctx, "in.mp4", "out.mkv")Probe an MP4's metadata without remuxing (fast path for indexing):
// Reads only the moov box — codecs, language, default flag, channels, colour,
// chapters, duration — never the sample data. Counterpart of matroska.OpenMeta.
// The second value lists non-carried tracks (cover art, hint/timecode).
c, dropped, err := mp4.OpenMeta(ctx, "video.mp4")
fmt.Println(c.DurationMs, len(c.Tracks), "tracks,", len(dropped), "dropped")Edit metadata with custom FS (S3, HTTP, etc.):
s3fs := &matroska.FS{
Open: func(p string) (mkv.ReadSeekCloser, error) { /* S3 GetObject */ },
Create: func(p string) (mkv.WriteSeekCloser, error) { /* S3 PutObject */ },
}
err := matroska.EditMetadata(ctx, "s3://bucket/video.mkv", "s3://bucket/out.mkv",
func(c *matroska.Container) {
c.Info.Title = "Updated"
},
matroska.Options{FS: s3fs},
)cmd/mkvgo/ CLI binary
commands/ one file per command group
matroska/ facade -- stable public API, re-exports everything
mkv/ core types, FS port, EBML IDs (experimental, may change)
reader/ parse MKV → Container
writer/ Container → MKV bytes
ops/ high-level operations (mux, split, merge, edit, remux-webm...)
subtitle/ SRT/ASS parsing
ebml/ low-level EBML encoding/decoding (no Matroska knowledge)
mp4/ MP4 (ISO-BMFF) remux to/from MKV (experimental, isolated from EBML core)
Import graph: cmd/mkvgo -> matroska -> mkv/* -> ebml; mp4 -> mkv/*
make build # build for current platform
make test # run tests with -race
make release # cross-compile all platformsmake release produces stripped binaries (~2.3 MB) in dist/:
| Platform | Output |
|---|---|
| Linux amd64 | dist/mkvgo-linux-amd64 |
| Linux arm64 | dist/mkvgo-linux-arm64 |
| Windows amd64 | dist/mkvgo-windows-amd64.exe |
| macOS amd64 | dist/mkvgo-darwin-amd64 |
| macOS arm64 | dist/mkvgo-darwin-arm64 |
Build for a specific platform manually:
GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o mkvgo ./cmd/mkvgo/Version is injected at build time via -ldflags:
go build -ldflags="-s -w -X main.version=1.0.0" -o mkvgo ./cmd/mkvgo/MIT