Read & write metadata and chapters¶
afmpeg reads and writes container-level tags, per-stream language/disposition/tags, and chapters (spec 0020). Writing tags and chapters works in any profile; it rides the mux, so it pairs naturally with a stream copy.
Read what's there¶
Probe surfaces the container tags, its chapters, and per-stream
metadata:
p, err := rt.Probe(ctx, fs, "in.mkv")
// p.Tags["title"], p.Tags["artist"] — container tags
// p.Chapters[i].Start / .End / .Title — chapters (seconds + title)
for _, s := range p.Streams {
// s.Language, s.Disposition (["default"], ["forced"], …), s.Tags
}
Write container tags¶
Set Output.Metadata — a copy is enough, no re-encode needed:
cmd := afmpeg.Command{
Inputs: []afmpeg.Input{{Path: "in.mp4"}},
Outputs: []afmpeg.Output{{
Path: "out.mp4",
Map: []string{"0:v", "0:a"},
VideoCodec: afmpeg.CodecCopy,
AudioCodec: afmpeg.CodecCopy,
Metadata: map[string]string{"title": "My Film", "artist": "Me"},
}},
}
res, err := rt.RunJob(ctx, fs, cmd)
Set per-stream language, disposition, and tags¶
Output.StreamMetadata is keyed by the stream's Map entry (a graph pad like "[vout]" or
a copied input stream like "0:a"):
Outputs: []afmpeg.Output{{
Path: "out.mkv",
Map: []string{"0:v", "0:a"},
VideoCodec: afmpeg.CodecCopy,
AudioCodec: afmpeg.CodecCopy,
StreamMetadata: map[string]afmpeg.StreamMeta{
"0:a": {
Language: "eng",
Disposition: []string{"default"},
Tags: map[string]string{"title": "Commentary"},
},
},
}},
Carry chapters across¶
Output.Chapters is a passthrough directive — "copy" carries the first input's chapters
onto the output, an input index ("1") picks another input, and "" / "none" drops them.
Authoring chapters inline is not supported.
Outputs: []afmpeg.Output{{
Path: "out.mp4",
Map: []string{"0:v", "0:a"},
VideoCodec: afmpeg.CodecCopy,
AudioCodec: afmpeg.CodecCopy,
Chapters: "copy",
}},
See also¶
- Compose a command with the builder — the
Command/Outputshape. - Remux without re-encoding — the copy path these examples build on.