# go-mp4: reading and writing MP4 boxes from Go

> go-mp4 is a Go library that gives you the low level I/O of an MP4 file: you walk the box tree, read the payloads you care about, and write boxes back out, including boxes you define yourself. It is deliberately not a media library, since it will not decode or convert anything, and the README says so in one line before showing the code.

**abema/go-mp4** — Go library for reading and writing MP4 file

- Repository: https://github.com/abema/go-mp4
- Website: https://dev.to/sunfishshogi/go-mp4-golang-library-and-cli-tool-for-mp4-52o1
- Stars: 548 · Forks: 39
- Language: Go
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/abema-go-mp4

## A box level library, and the sentence that limits it

go-mp4 is a Go library which provides low-level I/O interfaces of MP4, and its own summary is precise: it supports you to parse or build any MP4 boxes, atoms, directly.

MP4 is a tree of boxes. A file is a sequence of boxes, some of which contain other boxes, and each box has a type, a size and a payload whose format depends on the type. A library that stops at that level is useful for a specific set of jobs: inspecting a file, patching a box, extracting metadata, building a file from scratch, or working with the fragmented MP4 structures that streaming produces.

The README also draws the boundary in its second paragraph. The library provides very flexible interfaces for reading boxes, and if you want to read only specific parts of an MP4 file it extracts those boxes via the io.ReadSeeker interface. On the other hand, the library is not suitable for complex data conversions.

That is a useful and unusual thing to find in a README, because most media libraries blur the line between the container and the content. Here the container is the whole subject, and nothing inside a moov or a trak is interpreted as audio samples or video frames.

The project is MIT licensed, the primary language is Go, and the repository topics are fmp4, go, golang, mp4 and mp4box. The README links a Go reference on pkg.go.dev and carries a coverage badge from Coveralls, along with a test workflow badge, and the homepage is a long-form article on dev.to about the library and the command line tool. The releases are v1.7.3 in September 2026, v1.7.2 and v1.7.1, and the last push to the repository was on 2026-10-07.

## Walking the box tree, and the shortcuts for the boxes you want

The reading API has two halves, and the second half is where most callers start.

The first is ReadBoxStructure and ReadBoxStructureFromInternal, which let you scan the box tree by depth-first order. The callback receives a read handle, and from it you get the path and therefore the current depth, the box type such as mdhd, tfdt or mdat, the box size, and then the payload if the type is one the library supports.

```go
boxes, err := mp4.ExtractBoxWithPayload(file, nil, mp4.BoxPath{mp4.BoxTypeMoov(), mp4.BoxTypeTrak(), mp4.BoxTypeTkhd()})
if err != nil {
   :
}
for _, box := range boxes {
  tkhd := box.Payload.(*mp4.Tkhd)
  fmt.Println("track ID:", tkhd.TrackID)
}
```

That is the ExtractBoxWithPayload shortcut, and it is the form to reach for when you know the path you want. The path is written as a sequence of box types, here moov then trak then tkhd, and the payload comes back typed, so the track header is cast to the Tkhd structure and its TrackID field read directly rather than parsed from bytes by hand.

There is a family of these wrappers: ExtractBox, ExtractBoxes, ExtractBoxWithPayload, ExtractBoxesWithPayload and Probe. The plural versions return every match, which is what you want for a path that can appear more than once, such as several trak boxes inside one moov.

Probe is the other shortcut and it is the one to use when you do not know what you are looking for. It takes a reader and returns basic information about the file, and the example asks for the number of tracks. The demo wraps the file in bufseekio.NewReadSeeker with a 1024 byte buffer and a factor of 4, which is where the buffering question comes in.

The tree walk itself is what you use when you want everything, for instance to print a file structure. It calls back for every box, and a supported box can be expanded so that its children are visited too.

## Writing: start a box, marshal a payload, end the box

The writer mirrors the reader, and the API is three operations plus a copy.

You create a writer over the output, then for each box you either copy it unchanged from the input or write a new one. Writing a new one means starting the box, which writes the size and the type and gives you a writer positioned at the payload, marshalling the payload structure into it, and ending the box, which writes the final size back.

The README's example edits an emsg box and writes to another file. It opens the input through a buffered reader with a 128 KiB buffer and a factor of 4, creates the writer, and then in the callback switches on the box type. For the emsg case it starts the box, reads the payload, changes the MessageData field to hello world, marshals it and ends the box. For everything else it copies the box across with w.CopyBox.

That copy-everything-else pattern is the idiomatic way to edit a file, and it is why the reader and the writer are designed to be used together in one pass.

Then there is the warning, and it is the most important sentence in the README for anybody writing files. The above sample code does not work for some MP4 files. If your MP4 files include specific box types, for example stco and mfra, you should update those when offsets of the mdat box changed.

The reason is not mysterious. A chunk offset box such as stco contains absolute byte positions of the media data inside the file, and a fragment index such as mfra points at fragments in the same way. If your edit changes the size or position of the mdat box, every one of those positions is now wrong, and the file will play up to the point of the change and then break.

The README points at a sample gist, Insert Metadata and Update stco box, which adds a metadata box and updates the chunk offsets in the stco boxes to match. So the fix is known and documented, and it is work you have to do.

## Defining a box the library does not know

The library covers a lot of the specification, but a format that keeps growing will always have boxes it has not met, and go-mp4 lets you add them.

The pattern is four parts. A function that returns the box type, built from a four character string with StrToBoxType. A registration call in an init function, AddBoxDef, that registers your struct along with the version it applies to. The struct itself, which embeds FullBox and describes its fields with the mp4 struct tags. And a GetType method that returns the type function's result.

The struct tags are the part that takes a moment to read, and they are the library's own little schema language. The tag on FullBox is 0,extend, which says the fields continue from the base. The tag on the 32 bit unsigned integer is 1,size=32, which gives the field number and the size in bits. And the byte array is tagged 2,size=8,len=dynamic, which says eight bits per element and that the length is not fixed.

So the field numbers are what the format calls them, the sizes are in bits rather than bytes, and dynamic length tells the marshaller to take the length from the slice itself. That is enough to describe a box that the library has never seen.

This is a real capability rather than a workaround, because a box you define is then handled by all the same machinery: it can be read by the tree walk, it can be printed by the stringifier, and it can be written by the marshaller. The README's box type files show how much of the specification is covered already, with separate files for the ISO base media formats, the 3GPP boxes, AV1, FLAC, Opus, VP, the ETSI TS 102 366 boxes, the ISO 23001 boxes and metadata, so an unusual box is an exception rather than the rule.

## No buffering, and a command line tool for looking at files

Two practical details round out the library, and one of them is a deliberate omission.

The omission is buffering. go-mp4 has no buffering feature for I/O, and if you should reduce Read function calls, you can wrap the io.ReadSeeker with bufseekio. The reasoning is the usual one about keeping a library small and about not imposing a buffer size on a caller who knows the access pattern, and it also means that the reader you pass in can be anything that satisfies io.ReadSeeker, including a plain file, a pipe wrapper, or the in-memory reader used by the tests.

The command line tool is mp4tool, installed with the Go toolchain:

```bash
go install github.com/abema/go-mp4/cmd/mp4tool@latest

mp4tool -help
```

The dump command prints the box tree of a file, and the README shows the output for a fragmented MP4: a moof of 504 bytes containing an mfhd, the sequence number, then a traf with a tfhd carrying the track ID and the default sample duration, size and flags, a tfdt with the base media decode time, and a trun with the sample table, followed by an mdat with the media data.

Two flags in that output are worth noting. The trun line is truncated with a note to use the -a option to show all, and the mdat is shown as Data=[...] with a note to use the -mdat option to expand it. Those two defaults are sensible for a terminal: a sample table can run to thousands of entries and the media payload can be megabytes, and neither is useful to print by default.

So the tool is a debugging instrument rather than a media utility, and the README links a dev.to article about the library and the CLI tool together, which is where a fuller treatment of the command lives.

## The file layout, and what the dependency list says

The repository layout shows the shape of the specification coverage, because the box types are grouped by the part of the standard they come from.

There is box_types_iso14496_12.go, which is the base media file format, the ISO base media file format that MP4 is built on, and box_types_iso14496_14.go for the MP4 file format proper, plus box_types_iso14496_30.go. Then the 3GPP boxes in box_types_3gpp.go, AV1, FLAC, Opus and the VP family in their own files, the ETSI TS 102 366 boxes, the ISO 23001 boxes split into part 5 and part 7, and box_types_metadata.go.

Around them are the mechanics of the library: box.go and box_info.go for the box and its metadata, read.go, write.go, marshaller.go, field.go and string.go for the respective directions, extract.go for the extraction helpers, probe.go for the file summary, anytype.go, mp4.go, and the internal/ directory. Then cmd/ for the mp4tool binary, testdata/ for the test fixtures, and a test file beside each source file.

The dependency list in go.mod is short and instructive. There is github.com/orcaman/writerseeker, which is the writer that can write to an io.WriteSeeker, and github.com/sunfish-shogi/bufseekio, the buffered reader wrapper the README keeps recommending, written by the same author who wrote the dev.to article about this library. github.com/google/uuid is there for the boxes that carry identifiers. golang.org/x/term handles terminal size, which is what the CLI uses to decide how wide to print. The rest are test dependencies: testify, go-spew, go-billy for the in-memory filesystem used by tests, and yaml.v2 for the test fixture format.

The module declares go 1.14, which is a conservative floor, and the repository carries a .golangci.yml for the linting. There are no GitHub release notes beyond the tags, and the version numbers have moved in patch increments through the 1.7 line, which is what you would expect from a library whose public API has to stay stable.

## Conclusion

go-mp4 suits somebody who has to look inside an MP4 file or change it in a precise way, such as a packaging tool that adds metadata, a monitoring agent that reads track information, or a test harness that needs to inspect a fragmented stream box by box. It does not suit somebody who wants to play or transcode media, because the library works at the box level and does no decoding at all, and it does not suit somebody who wants to insert data into a file without thinking about offsets. Check three things before you adopt it: whether the boxes you need are among the types it defines, whether you can live with the offset bookkeeping when the mdat layout changes, since the README is explicit that some box types such as stco and mfra must be updated when it does, and which Go version you are on, since the module still declares go 1.14 and the library has been stable across its 1.7 line. The last push to the repository was on 2026-10-07 and the newest release is v1.7.3.

## FAQ

### What does go-mp4 do?

It is a Go library providing low-level I/O interfaces of MP4, so you can parse or build any MP4 boxes, atoms, directly. It gives flexible interfaces for reading boxes and for extracting only the parts of a file you need through the io.ReadSeeker interface, and it is not suitable for complex data conversions.

### How do I read a specific box from an MP4 file in go-mp4?

Use the extraction helpers such as ExtractBox or ExtractBoxWithPayload with a BoxPath describing the path you want, for example moov, trak and tkhd. The payload comes back typed, so you can cast it to the matching structure and read its fields, and Probe returns basic information such as the number of tracks.

### Why does editing an MP4 file with go-mp4 need extra work?

The README notes that the sample editing code does not work for some MP4 files, because box types such as stco and mfra hold byte offsets into the mdat box. If the mdat layout changes, those offsets have to be updated, and the README links a sample that adds a metadata box and updates the chunk offsets in the stco boxes.

### Can I add a box type that go-mp4 does not know?

Yes. You define a struct with the mp4 field tags, register it with mp4.AddBoxDef in an init function, return its type from a function built with mp4.StrToBoxType, and implement a GetType method. The example shows a box with an extended full box header, a 32 bit field and a dynamic length byte array.

### How do I inspect an MP4 file from the command line with go-mp4?

Install the bundled tool with go install github.com/abema/go-mp4/cmd/mp4tool@latest, then run mp4tool dump on the file to print the box tree. The trun sample table is truncated by default and can be shown in full with the -a option, and the mdat payload is expanded with the -mdat option.

## Sources

- [abema/go-mp4 on GitHub](https://github.com/abema/go-mp4)
- [License: MIT](https://github.com/abema/go-mp4/blob/master/LICENSE)
- [Project website](https://dev.to/sunfishshogi/go-mp4-golang-library-and-cli-tool-for-mp4-52o1)
- [README](https://github.com/abema/go-mp4/blob/master/README.md)
- [Releases](https://github.com/abema/go-mp4/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/abema-go-mp4
