Remux without re-encoding (stream copy)¶
Most container changes don't need a transcode. Moving mp4 → mkv, or joining segments that
already share a codec, is a stream copy: read the demuxed packets, optionally fix their
bitstream framing, and mux them straight back out — no decode, no encode. It's fast, lossless,
and needs no codec at all, so it works for any codec in either licence variant, including
streams the engine can't itself decode (spec 0013).
The mechanism: name the input streams to copy in Map with unbracketed specifiers
("0:v", "0:a:0", "0:0" — an input index, a media type, an optional per-type index) and set
the codec to the CodecCopy sentinel. Bracketed labels ("[vout]") still mean graph pads
that get encoded, so copy and transcode mix freely in one job.
Change the container, keep the bytes¶
cmd := afmpeg.Command{
Inputs: []afmpeg.Input{{Path: "in.mp4"}},
Outputs: []afmpeg.Output{{
Path: "out.mkv",
Map: []string{"0:v", "0:a"},
VideoCodec: afmpeg.CodecCopy,
AudioCodec: afmpeg.CodecCopy,
}},
}
_, err := rt.RunJob(ctx, fs, cmd)
The video and audio are copied through byte-for-byte; only the wrapper changes. Any bitstream filter the target container requires (for example H.264's NAL framing differs between mp4 and MPEG-TS) is auto-inserted by the muxer — you don't have to reason about it for the common case.
Copy one stream, re-encode another¶
Because an unbracketed Map entry means copy and a bracketed one means a graph pad, a single
output can do both — here the video is remuxed untouched while the audio is normalised and
re-encoded:
cmd := afmpeg.Command{
Inputs: []afmpeg.Input{{Path: "in.mp4"}},
FilterComplex: "[0:a]loudnorm[aout]",
Outputs: []afmpeg.Output{{
Path: "out.mp4",
Map: []string{"0:v", "[aout]"},
VideoCodec: afmpeg.CodecCopy, // copied
AudioCodec: "aac", // encoded from the [aout] pad
}},
}
Join like-codec segments (concat)¶
WithConcatInput presents a playlist of like-codec files as one continuous input via the
concat demuxer — a stream-copy join, distinct from the concat filter (which decodes and
re-encodes):
cmd := afmpeg.NewCommand(
afmpeg.WithConcatInput("part0.ts", "part1.ts", "part2.ts"),
afmpeg.WithOutput("joined.ts",
afmpeg.Map("0:v"), afmpeg.Map("0:a"),
afmpeg.VideoCodec(afmpeg.CodecCopy), afmpeg.AudioCodec(afmpeg.CodecCopy)),
)
The segments must share codec and parameters — that's the demuxer's requirement, and a mismatch surfaces as a typed error. MPEG-TS is the natural concat-copy source (its packets carry continuous timestamps); joining mp4 segments with audio can hit mp4's priming-sample timestamp discontinuity at a boundary, so concat-copy is happiest on stream-friendly containers.
Override or disable the bitstream filter¶
The muxer's auto-insertion is right for the common case. When you need to force a specific
filter (or force none), set it per copied stream, keyed by the Map entry:
afmpeg.WithOutput("out.ts",
afmpeg.Map("0:v"), afmpeg.VideoCodec(afmpeg.CodecCopy),
afmpeg.BitstreamFilter("0:v", "h264_mp4toannexb"), // explicit; "none" disables
)
Notes¶
- Copy can only cut on keyframes. Trimming a copied stream to an exact frame needs a decode
the copy path skips; for a frame-accurate cut re-encode with
SeekAccurateTo(extract a clip, spec 0014). - Container reach. Copy lands on mp4/mkv/webm in the lean profile; MPEG-TS, HLS/DASH segmenting, and fragmented MP4 are available in the intermediate profile (spec 0015) — see package for streaming.
- Licensing. Copy touches no codec library, so it stays in the LGPL default and even reaches codecs the engine doesn't ship a decoder or encoder for.