### Example: Writing an Mdat Box Source: https://github.com/abema/go-mp4/blob/master/_autodocs/writing.md Demonstrates how to start an 'mdat' box, write media data, and then update and finalize the box header with the correct size. This involves calling StartBox, writing the payload, calculating the total size, and then calling EndBox. ```go w := mp4.NewWriter(file) mdatBi := &mp4.BoxInfo{ Type: mp4.BoxTypeMdat(), Size: 0, // Placeholder; will update in EndBox HeaderSize: 8, } mdatBi2, err := w.StartBox(mdatBi) if err != nil { log.Fatal(err) } // Write payload n, _ := w.Write(mediaData) // Update and finalize box header mdatBi2.Size = mdatBi2.HeaderSize + uint64(n) _, err = w.EndBox() ``` -------------------------------- ### Install mp4tool Source: https://github.com/abema/go-mp4/blob/master/README.md Install the mp4tool command-line utility using go install. Ensure your Go environment is set up correctly. ```sh go install github.com/abema/go-mp4/cmd/mp4tool@latest ``` ```sh mp4tool -help ``` -------------------------------- ### Example: Print All Boxes Source: https://github.com/abema/go-mp4/blob/master/_autodocs/read-and-traverse.md This example demonstrates how to use ReadBoxStructure to iterate through all top-level boxes in an MP4 file and print their type and size. ```go file, _ := os.Open("media.mp4") defile.Close() _, err := mp4.ReadBoxStructure(file, func(h *mp4.ReadHandle) (interface{}, error) { fmt.Printf("Box: %s, Size: %d\n", h.BoxInfo.Type.String(), h.BoxInfo.Size) return nil, nil }) ``` -------------------------------- ### Example: Seek to Payload Source: https://github.com/abema/go-mp4/blob/master/_autodocs/box-info.md Demonstrates reading box information and then using `SeekToPayload` to position the reader at the start of the box's data payload. Logs a fatal error if seeking fails. ```go bi, _ := mp4.ReadBoxInfo(file) if _, err := bi.SeekToPayload(file); err != nil { log.Fatal(err) } // Now file is positioned at start of box payload ``` -------------------------------- ### Example: Finalizing Nested Boxes Source: https://github.com/abema/go-mp4/blob/master/_autodocs/writing.md Illustrates the process of finalizing nested MP4 boxes, such as 'mvhd' within 'moov'. It shows how to start a parent box, then start and finalize a child box after writing its payload, and finally finalize the parent box with its total calculated size. ```go moovBi, _ := w.StartBox(&mp4.BoxInfo{ Type: mp4.BoxTypeMoov(), Size: 0, HeaderSize: 8, }) // Write moov children... mvhd, _ := w.StartBox(&mp4.BoxInfo{ Type: mp4.BoxTypeMvhd(), Size: 0, HeaderSize: 8, }) // Write mvhd payload... mvhdPayloadSize := uint64(100) // Example mvhdBi.Size = mvhdBi.HeaderSize + mvhdPayloadSize w.EndBox() // More children... // Finalize moov moovBi.Size = uint64(2048) // Total size after writing children w.EndBox() ``` -------------------------------- ### Example Usage of SetFlags Source: https://github.com/abema/go-mp4/blob/master/_autodocs/box-interface.md Demonstrates how to set the flags for an Mvhd box to zero. ```go mvhd := box.(*mp4.Mvhd) mvhd.SetFlags(0x000000) ``` -------------------------------- ### Example: Expand All Boxes Recursively Source: https://github.com/abema/go-mp4/blob/master/_autodocs/read-and-traverse.md This example shows how to use ReadBoxStructure to recursively traverse and expand all supported boxes within an MP4 file, printing the depth and type of each box. ```go _, err := mp4.ReadBoxStructure(file, func(h *mp4.ReadHandle) (interface{}, error) { fmt.Printf("Depth: %d, Type: %s\n", len(h.Path), h.BoxInfo.Type.String()) if h.BoxInfo.IsSupportedType() { _, err := h.ReadPayload() if err != nil { return nil, err } return h.Expand() // Recurse into children } return nil, nil }) ``` -------------------------------- ### Example Box Implementation: Mdat Source: https://github.com/abema/go-mp4/blob/master/_autodocs/box-interface.md An example of implementing a custom MP4 box ('mdat') by embedding mp4.Box and overriding GetType and GetFieldLength methods. ```go type Mdat struct { mp4.Box Data []byte `mp4:"0,size=8,len=dynamic"` } func (mdat *Mdat) GetType() mp4.BoxType { return mp4.StrToBoxType("mdat") } func (mdat *Mdat) GetFieldLength(name string, ctx mp4.Context) uint { if name == "Data" { // Return size of box payload minus already-read headers return uint(len(mdat.Data)) } panic(fmt.Errorf("unknown field: %s", name)) } func init() { mp4.AddBoxDef(&Mdat{}) } ``` -------------------------------- ### Example BoxPath Usage Source: https://github.com/abema/go-mp4/blob/master/_autodocs/read-and-traverse.md Illustrates creating a BoxPath to target the 'tkhd' box within the 'moov' and 'trak' boxes. ```go // Path to tkhd box: moov → trak → tkhd path := mp4.BoxPath{ mp4.BoxTypeMoov(), mp4.BoxTypeTrak(), mp4.BoxTypeTkhd(), } ``` -------------------------------- ### Example: Read and Print Box Info Source: https://github.com/abema/go-mp4/blob/master/_autodocs/box-info.md Demonstrates opening an MP4 file, reading its first box header information using `ReadBoxInfo`, and printing the box type and size. Ensures the file is closed using `defer`. ```go file, _ := os.Open("media.mp4") defer file.Close() bi, err := mp4.ReadBoxInfo(file) if err != nil { log.Fatal(err) } fmt.Printf("Box type: %s, Size: %d\n", bi.Type.String(), bi.Size) ``` -------------------------------- ### Register Supported Box Versions Source: https://github.com/abema/go-mp4/blob/master/_autodocs/errors.md Example of registering multiple supported versions for a custom box type using AddBoxDef. ```go // Accept versions 0, 1, and 2 mp4.AddBoxDef(&MyBox{}, 0, 1, 2) ``` -------------------------------- ### MP4 File Probing Example Source: https://github.com/abema/go-mp4/blob/master/_autodocs/extract-and-probe.md Demonstrates how to open an MP4 file, probe it for metadata using mp4.Probe, and print key information like major brand, duration, and track details. Ensure the file is closed using defer. ```go file, _ := os.Open("media.mp4") defile.Close() info, err := mp4.Probe(file) if err != nil { log.Fatal(err) } fmt.Printf("Major brand: %s\n", string(info.MajorBrand[:])) fmt.Printf("Fast start: %v\n", info.FastStart) fmt.Printf("Timescale: %d, Duration: %d\n", info.Timescale, info.Duration) fmt.Printf("Tracks: %d\n", len(info.Tracks)) for i, track := range info.Tracks { fmt.Printf(" Track %d: ID=%d, Codec=%v, Encrypted=%v\n", i, track.TrackID, track.Codec, track.Encrypted) } ``` -------------------------------- ### Get Supported Versions for BoxType Source: https://github.com/abema/go-mp4/blob/master/_autodocs/box-type-and-registration.md Returns the list of FullBox versions supported for this box type. ```go func (boxType BoxType) GetSupportedVersions(ctx Context) ([]uint8, error) ``` ```go versions, err := mp4.StrToBoxType("mvhd").GetSupportedVersions(ctx) // Output: [0 1] ``` -------------------------------- ### Example: Conditional Box Copying Source: https://github.com/abema/go-mp4/blob/master/_autodocs/writing.md Shows how to iterate through boxes in a source file and conditionally copy them to a writer. It demonstrates handling a specific box type ('mdat') with custom logic while copying all other boxes directly using CopyBox. ```go // Copy all boxes except mdat, which we modify mp4.ReadBoxStructure(srcFile, func(h *mp4.ReadHandle) (interface{}, error) { if h.BoxInfo.Type == mp4.BoxTypeMdat() { // Custom mdat handling w.StartBox(&h.BoxInfo) // ... custom write logic ... h.BoxInfo.Size = ... w.EndBox() } else { // Copy unchanged w.CopyBox(srcFile, &h.BoxInfo) } return nil, nil }) ``` -------------------------------- ### Seek to Box Start Source: https://github.com/abema/go-mp4/blob/master/_autodocs/box-info.md Seeks the provided `io.Seeker` to the beginning of the box, which is the first byte of the size field. Returns the new position and any error encountered. ```go func (bi *BoxInfo) SeekToStart(s io.Seeker) (int64, error) ``` -------------------------------- ### Example Implementation of IsOptFieldEnabled Source: https://github.com/abema/go-mp4/blob/master/_autodocs/box-interface.md Shows how to implement IsOptFieldEnabled for a Colr box to conditionally include the ColourPrimaries field based on the ColourType. ```go type Colr struct { mp4.Box ColourType [4]byte `mp4:"0,size=8,string"` ColourPrimaries uint16 `mp4:"1,size=16,opt=dynamic" // ... } func (c *Colr) IsOptFieldEnabled(name string, ctx mp4.Context) bool { if name == "ColourPrimaries" { return bytes.Equal(c.ColourType[:], []byte{'n', 'c', 'l', 'x'}) } return false } ``` -------------------------------- ### Calculate Total Bitrate of MP4 File Source: https://github.com/abema/go-mp4/blob/master/_autodocs/examples.md This example shows how to probe an MP4 file, calculate the duration of the media in seconds, and then compute the average bitrate for each track and the total bitrate of the entire file. It requires opening the file and using the mp4.Probe function. ```go package main import ( "fmt" "log" "os" "github.com/abema/go-mp4" ) func main() { file, err := os.Open("media.mp4") if err != nil { log.Fatal(err) } defer file.Close() info, err := mp4.Probe(file) if err != nil { log.Fatal(err) } fmt.Printf("Duration: %d (timescale: %d)\n", info.Duration, info.Timescale) durationSecs := float64(info.Duration) / float64(info.Timescale) fmt.Printf("Duration: %.2f seconds\n\n", durationSecs) for _, track := range info.Tracks { totalSize := uint64(0) for _, sample := range track.Samples { totalSize += uint64(sample.Size) } trackDurationSecs := float64(track.Duration) / float64(track.Timescale) bitrate := (float64(totalSize) * 8) / trackDurationSecs fmt.Printf("Track %d:\n", track.TrackID) fmt.Printf(" Total size: %d bytes\n", totalSize) fmt.Printf(" Average bitrate: %.0f kbps\n", bitrate/1000) } totalSize := uint64(0) for _, track := range info.Tracks { for _, sample := range track.Samples { totalSize += uint64(sample.Size) } } totalBitrate := (float64(totalSize) * 8) / durationSecs fmt.Printf("\nTotal bitrate: %.0f kbps\n", totalBitrate/1000) } ``` -------------------------------- ### Seek to Box Payload Start Source: https://github.com/abema/go-mp4/blob/master/_autodocs/box-info.md Seeks the provided `io.Seeker` to the start of the box's payload, which is the first byte immediately following the header. Returns the new position and any error encountered. ```go func (bi *BoxInfo) SeekToPayload(s io.Seeker) (int64, error) ``` -------------------------------- ### Example: Conditional Payload Reading Source: https://github.com/abema/go-mp4/blob/master/_autodocs/box-info.md Shows how to use `IsSupportedType` to conditionally read a box's payload. If the box type is supported, it proceeds to read the payload; otherwise, it skips the unsupported box. ```go if bi.IsSupportedType() { box, _, _ := h.ReadPayload() // Safe to read } else { // Skip unsupported box type } ``` -------------------------------- ### Stringify Complex Box with Indentation Source: https://github.com/abema/go-mp4/blob/master/_autodocs/stringification.md This example demonstrates reading an MP4 file, unmarshalling a box, and then stringifying it with custom indentation for better readability. It requires opening the file, reading box info, seeking to the payload, and then unmarshalling and stringifying. ```go // Read a moov box file, _ := os.Open("media.mp4") defer file.Close() bi, _ := mp4.ReadBoxInfo(file) bi.SeekToPayload(file) box, _, _ := mp4.UnmarshalAny(file, bi.Type, bi.Size - bi.HeaderSize, bi.Context) // Stringify with indentation str, _ := mp4.StringifyWithIndent(box, " ", bi.Context) fmt.Println(str) ``` -------------------------------- ### Extract Video Codec Info (Go) Source: https://github.com/abema/go-mp4/blob/master/_autodocs/examples.md Get H.264 profile, level, and resolution from an MP4 file. Requires the 'video.mp4' file to exist. ```go package main import ( "fmt" "log" "os" "github.com/abema/go-mp4" ) func main() { file, err := os.Open("video.mp4") if err != nil { log.Fatal(err) } defer file.Close() info, err := mp4.Probe(file) if err != nil { log.Fatal(err) } for _, track := range info.Tracks { if track.Codec == mp4.CodecAVC1 && track.AVC != nil { avc := track.AVC // Determine H.264 profile name var profileName string switch avc.Profile { case 66: profileName = "Baseline" case 77: profileName = "Main" case 88: profileName = "Extended" case 100: profileName = "High" default: profileName = fmt.Sprintf("Unknown (%d)", avc.Profile) } // Determine H.264 level name levelNum := avc.Level / 10 levelDec := avc.Level % 10 fmt.Printf("Video Codec Information:\n") fmt.Printf(" Profile: %s\n", profileName) fmt.Printf(" Level: %d.%d\n", levelNum, levelDec) fmt.Printf(" Resolution: %dx%d\n", avc.Width, avc.Height) fmt.Printf(" NAL Unit Size: %d bytes\n", avc.LengthSize) } } } ``` -------------------------------- ### Start MP4 Box Writing Source: https://github.com/abema/go-mp4/blob/master/_autodocs/writing.md Initiates writing a box by writing its header and pushing it onto an internal stack. The caller must track the total payload size for the subsequent EndBox call. The Size field in the input BoxInfo is used as-is for the initial write and must be updated before calling EndBox. ```go func (w *Writer) StartBox(bi *BoxInfo) (*BoxInfo, error) ``` -------------------------------- ### Selective Stringification of Specific Boxes Source: https://github.com/abema/go-mp4/blob/master/_autodocs/stringification.md This example demonstrates how to selectively stringify only certain types of boxes during file traversal. It uses a switch statement within the `mp4.ReadBoxStructure` callback to identify and process desired box types. ```go // Only stringify specific boxes _, err := mp4.ReadBoxStructure(file, func(h *mp4.ReadHandle) (interface{}, error) { switch h.BoxInfo.Type { case mp4.BoxTypeMvhd(), mp4.BoxTypeTkhd(), mp4.BoxTypeMdhd(): box, _, _ := h.ReadPayload() str, _ := mp4.Stringify(box, h.BoxInfo.Context) fmt.Printf("%s: %s\n", h.BoxInfo.Type.String(), str) } return nil, nil }) ``` -------------------------------- ### Writing and Modifying MP4 Boxes with go-mp4 Source: https://github.com/abema/go-mp4/blob/master/README.md The Writer interface allows for modifying MP4 files. This example demonstrates how to update an 'emsg' box by modifying its MessageData and rewriting it, while copying other boxes. ```go r := bufseekio.NewReadSeeker(inputFile, 128*1024, 4) w := mp4.NewWriter(outputFile) _, err = mp4.ReadBoxStructure(r, func(h *mp4.ReadHandle) (interface{}, error) { switch h.BoxInfo.Type { case mp4.BoxTypeEmsg(): // write box size and box type _, err := w.StartBox(&h.BoxInfo) if err != nil { return nil, err } // read payload box, _, err := h.ReadPayload() if err != nil { return nil, err } // update MessageData emsg := box.(*mp4.Emsg) emsg.MessageData = []byte("hello world") // write box playload if _, err := mp4.Marshal(w, emsg, h.BoxInfo.Context); err != nil { return nil, err } // rewrite box size _, err = w.EndBox() return nil, err default: // copy all return nil, w.CopyBox(r, &h.BoxInfo) } }) ``` -------------------------------- ### Probing MP4 File Information with go-mp4 Source: https://github.com/abema/go-mp4/blob/master/README.md Use the Probe function to quickly get basic information about an MP4 file, such as the number of tracks. It requires an io.ReadSeeker with a specified buffer size. ```go // get basic informations info, err := mp4.Probe(bufseekio.NewReadSeeker(file, 1024, 4)) if err != nil { : } fmt.Println("track num:", len(info.Tracks)) ``` -------------------------------- ### MP4 Struct Tag: Version-Conditional Fields Example Source: https://github.com/abema/go-mp4/blob/master/_autodocs/marshalling.md Demonstrates how `ver` and `nver` components in `mp4` tags conditionally include fields based on the FullBox version. This is useful for handling different MP4 file versions. ```go type Mvhd struct { FullBox `mp4:"0,extend"` CreationTimeV0 uint32 `mp4:"2,size=32,ver=0"` // Only in v0 CreationTimeV1 uint64 `mp4:"3,size=64,nver=0"` // Only in v1+ } ``` -------------------------------- ### ProbeInfo Struct Definition Source: https://github.com/abema/go-mp4/blob/master/_autodocs/extract-and-probe.md Defines the high-level metadata for an MP4 file, including brand information, fast start capability, timing details, and parsed tracks and segments. Use this to get an overview of the MP4 file's properties. ```go type ProbeInfo struct { MajorBrand [4]byte MinorVersion uint32 CompatibleBrands [][4]byte FastStart bool Timescale uint32 Duration uint64 Tracks []*Track Segments []*Segment } ``` -------------------------------- ### FullBox Methods Source: https://github.com/abema/go-mp4/blob/master/_autodocs/box-interface.md Demonstrates the full implementations of IBox methods for managing version and flags within a FullBox. ```go func (box *FullBox) GetVersion() uint8 func (box *FullBox) SetVersion(version uint8) func (box *FullBox) GetFlags() uint32 func (box *FullBox) SetFlags(flags uint32) func (box *FullBox) AddFlag(flag uint32) func (box *FullBox) RemoveFlag(flag uint32) func (box *FullBox) CheckFlag(flag uint32) bool ``` -------------------------------- ### Read MP4 Box Structure Internally Source: https://github.com/abema/go-mp4/blob/master/_autodocs/read-and-traverse.md Use this function to traverse box structures starting from within a parent box. It reads child boxes until the payload is exhausted, invoking a handler for each child. Ensure the reader is positioned at the parent box's payload start. ```go func ReadBoxStructureFromInternal(r io.ReadSeeker, bi *BoxInfo, handler ReadHandler, params ...interface{}) (interface{}, error) ``` -------------------------------- ### Seek Source: https://github.com/abema/go-mp4/blob/master/_autodocs/writing.md Adjusts the offset of the underlying writer. It supports seeking relative to the start, current position, or end of the writer. ```APIDOC ## Seek ### Description Seeks in the underlying writer. Supports `io.SeekStart`, `io.SeekCurrent`, `io.SeekEnd`. ### Method `(*Writer) Seek` ### Parameters #### Path Parameters - **offset** (int64) - Required - Offset from whence - **whence** (int) - Required - io.SeekStart, io.SeekCurrent, or io.SeekEnd ### Returns - `int64` - new position - `error` ``` -------------------------------- ### Create New MP4 Writer Source: https://github.com/abema/go-mp4/blob/master/_autodocs/writing.md Initializes a new Writer instance. Ensure the provided io.WriteSeeker is properly managed (e.g., closed when done). ```go func NewWriter(w io.WriteSeeker) *Writer ``` ```go file, _ := os.Create("output.mp4") defer file.Close() w := mp4.NewWriter(file) ``` -------------------------------- ### Typical MP4 Box Reading Workflow Source: https://github.com/abema/go-mp4/blob/master/_autodocs/box-info.md Demonstrates the standard procedure for reading MP4 boxes. It involves reading the box header, verifying the box type, seeking to the payload, unmarshalling the box data, and then advancing to the end of the current box to prepare for the next. ```go import "github.com/abema/go-mp4" // ... inside a function that takes an io.Reader 'file' and mp4.Context 'ctx' // 1. Read box header bi, err := mp4.ReadBoxInfo(file) if err != nil { return err } // 2. Check if type is supported if !bi.IsSupportedType() { // Skip unknown type bi.SeekToEnd(file) continue } // 3. Seek to payload and read bi.SeekToPayload(file) box, n, err := mp4.UnmarshalAny(file, bi.Type, bi.Size - bi.HeaderSize, bi.Context) if err != nil { return err } // 4. Advance to next box bi.SeekToEnd(file) ``` -------------------------------- ### Check if FastStart (Streaming-Friendly) Source: https://github.com/abema/go-mp4/blob/master/_autodocs/quick-reference.md Determines if an MP4 file is optimized for streaming by checking the 'FastStart' property, which indicates if the 'moov' atom precedes the 'mdat' atom. ```go info, _ := mp4.Probe(file) if info.FastStart { fmt.Println("File is optimized for streaming (moov before mdat)") } else { fmt.Println("File requires full download (mdat before moov)") } ``` -------------------------------- ### GetFieldLength Method for ICustomFieldObject Source: https://github.com/abema/go-mp4/blob/master/_autodocs/box-interface.md Returns the element count of a dynamic-length array field during marshalling. For example, in a Stts box, this returns the EntryCount. ```go func (box ICustomFieldObject) GetFieldLength(name string, ctx Context) uint ``` -------------------------------- ### Get Wildcard BoxType Source: https://github.com/abema/go-mp4/blob/master/_autodocs/box-type-and-registration.md Returns the wildcard box type (all zero bytes). Used in box path matching to match any box type. ```go func BoxTypeAny() BoxType ``` ```go // Match any track: moov → trak → [any child] path := mp4.BoxPath{mp4.BoxTypeMoov(), mp4.BoxTypeTrak(), mp4.BoxTypeAny()} ``` -------------------------------- ### Samples Type Source: https://github.com/abema/go-mp4/blob/master/_autodocs/types.md Represents a slice of individual sample metadata. ```go type Samples []*Sample ``` -------------------------------- ### StartBox Source: https://github.com/abema/go-mp4/blob/master/_autodocs/writing.md Begins writing a box. Writes the box header and pushes it onto the box stack for later size update. It records the current position as the Offset, calls WriteBoxInfo to write header bytes, and pushes the box onto an internal stack. It returns the updated BoxInfo with the real Offset and HeaderSize. ```APIDOC ## StartBox ### Description Begins writing a box. Writes the box header and pushes it onto the box stack for later size update. ### Method `func (w *Writer) StartBox(bi *BoxInfo) (*BoxInfo, error)` ### Parameters #### Path Parameters - **bi** (*BoxInfo) - Required - BoxInfo with Type and (placeholder) Size ### Returns - `*BoxInfo` - updated BoxInfo with actual Offset/HeaderSize - `error` ### Behavior 1. Records current position as Offset 2. Calls WriteBoxInfo to write header bytes 3. Pushes box onto internal stack 4. Returns updated BoxInfo with real Offset and HeaderSize 5. Caller must track total payload size for later EndBox call ### Note The `Size` field in the input BoxInfo is used as-is for the initial write. It must be updated before `EndBox` is called (or placeholder if updating later). ### Example ```go w := mp4.NewWriter(file) mdatBi := &mp4.BoxInfo{ Type: mp4.BoxTypeMdat(), Size: 0, // Placeholder; will update in EndBox HeaderSize: 8, } mdatBi2, err := w.StartBox(mdatBi) if err != nil { log.Fatal(err) } // Write payload n, _ := w.Write(mediaData) // Update and finalize box header mdatBi2.Size = mdatBi2.HeaderSize + uint64(n) _, err = w.EndBox() ``` ``` -------------------------------- ### Modify EMSG Box and Rewrite MP4 (Go) Source: https://github.com/abema/go-mp4/blob/master/_autodocs/examples.md Change emsg box message data and rewrite the MP4 file. This example requires 'input.mp4' and creates 'output.mp4'. ```go package main import ( "fmt" "io" "log" "os" "github.com/abema/go-mp4" ) func main() { inFile, err := os.Open("input.mp4") if err != nil { log.Fatal(err) } defer inFile.Close() outFile, err := os.Create("output.mp4") if err != nil { log.Fatal(err) } defer outFile.Close() w := mp4.NewWriter(outFile) _, err = mp4.ReadBoxStructure(inFile, func(h *mp4.ReadHandle) (interface{}, error) { switch h.BoxInfo.Type { case mp4.BoxTypeEmsg(): // Modify emsg box box, _, err := h.ReadPayload() if err != nil { return nil, err } emsg := box.(*mp4.Emsg) // Update message emsg.MessageData = []byte("New event message") // Write modified box w.StartBox(&h.BoxInfo) // Write payload _, err = mp4.Marshal(w, emsg, h.BoxInfo.Context) if err != nil { return nil, err } // Get current position to calculate size endPos, _ := w.Seek(0, io.SeekCurrent) startPos := int64(h.BoxInfo.Offset) h.BoxInfo.Size = uint64(endPos - startPos) _, err = w.EndBox() return nil, err default: // Copy other boxes unchanged return nil, w.CopyBox(inFile, &h.BoxInfo) } }) if err != nil { log.Fatal(err) } } ``` -------------------------------- ### ReadBoxStructureFromInternal Source: https://github.com/abema/go-mp4/blob/master/_autodocs/read-and-traverse.md Traverses the box structure starting from within a parent box, reading child boxes until the payload is exhausted. It handles context propagation and QuickTime compatibility detection. ```APIDOC ## ReadBoxStructureFromInternal ### Description Traverses box structure starting from within a parent box. Reads child boxes until payload exhausted. ### Signature ```go func ReadBoxStructureFromInternal(r io.ReadSeeker, bi *BoxInfo, handler ReadHandler, params ...interface{}) (interface{}, error) ``` ### Parameters #### Path Parameters - **r** (io.ReadSeeker) - Required - Reader at parent box payload start - **bi** (*BoxInfo) - Required - Parent BoxInfo - **handler** (ReadHandler) - Required - Callback invoked for each child - **params** (...interface{}) - Optional - Parameters passed to callbacks ### Returns - **interface{}** - Accumulated callback values - **error** - Error encountered during traversal ### Behavior - Seeks to parent payload start - Reads child boxes until payload boundary - Sets Context based on parent box type and inherited context - Detects QuickTime compatibility from ftyp if at root level - Parses keys box to count ilst items ### Context Propagation During traversal, the Context struct is populated based on box nesting: - `UnderWave = true` when inside a wave box - `UnderIlst = true` when inside ilst (iTunes metadata) - `UnderIlstMeta = true` when inside a metadata container under ilst - `UnderUdta = true` when inside udta (user data) - `IsQuickTimeCompatible = true` if ftyp compatible_brands contains "qt" These flags inform box definition lookup. For example, a box type that appears under ilst may be parsed differently than the same type under different parent. ``` -------------------------------- ### Go MP4 Standard Boxes: Sample Tables Source: https://github.com/abema/go-mp4/blob/master/_autodocs/MANIFEST.txt Lists standard MP4 box types for sample tables. These are crucial for describing sample timing, composition, sizes, and chunk offsets. ```Go Sample tables: stbl, stsd, stts, ctts, stsc, stsz, stco, co64 ``` -------------------------------- ### Probe MP4 File for Track and Codec Info Source: https://github.com/abema/go-mp4/blob/master/_autodocs/00-START-HERE.md Use Probe to get information about all tracks and their codecs within an MP4 file. This is useful for quick metadata extraction. ```go // Get all tracks and their codecs info, _ := mp4.Probe(file) for _, track := range info.Tracks { fmt.Printf("Track %d: %v codec, %d samples\n", track.TrackID, track.Codec, len(track.Samples)) } ``` -------------------------------- ### Main Entry Points Source: https://github.com/abema/go-mp4/blob/master/_autodocs/INDEX.md This section lists the primary functions available to users for interacting with MP4 files, including reading, writing, and manipulating box structures. ```APIDOC ## ReadBoxStructure ### Description Performs a tree traversal of the MP4 structure, invoking a callback function for each box encountered. ### Purpose Tree traversal with callback. ### Docs [Read and Traverse](read-and-traverse.md) ``` ```APIDOC ## ExtractBox / ExtractBoxes ### Description Extracts specific boxes from an MP4 file based on their path. ### Purpose Extract boxes by path. ### Docs [Extraction and Probing](extract-and-probe.md) ``` ```APIDOC ## ExtractBoxWithPayload / ExtractBoxesWithPayload ### Description Extracts boxes from an MP4 file along with their parsed payloads. ### Purpose Extract with parsed payloads. ### Docs [Extraction and Probing](extract-and-probe.md) ``` ```APIDOC ## Probe ### Description Extracts high-level media information from an MP4 file. ### Purpose Extract high-level media info. ### Docs [Extraction and Probing](extract-and-probe.md) ``` ```APIDOC ## ReadBoxInfo ### Description Reads the header information for a specific box within an MP4 file. ### Purpose Read box header. ### Docs [Box Info](box-info.md) ``` ```APIDOC ## WriteBoxInfo / EncodeBoxInfo ### Description Writes or encodes the header information for a box into an MP4 file. ### Purpose Write box header. ### Docs [Box Info](box-info.md) ``` ```APIDOC ## Unmarshal / UnmarshalAny ### Description Deserializes the payload of a box, converting it into a Go struct. ### Purpose Deserialize box payload. ### Docs [Marshalling](marshalling.md) ``` ```APIDOC ## Marshal ### Description Serializes a box payload from a Go struct into its byte representation. ### Purpose Serialize box payload. ### Docs [Marshalling](marshalling.md) ``` ```APIDOC ## Stringify / StringifyWithIndent ### Description Formats the box structure into a human-readable string, with an option for indentation. ### Purpose Format box as string. ### Docs [Stringification](stringification.md) ``` ```APIDOC ## NewWriter ### Description Creates a new writer instance for constructing and writing MP4 files. ### Purpose Create box writer. ### Docs [Writing](writing.md) ``` ```APIDOC ## AddBoxDef / AddBoxDefEx ### Description Registers a custom box definition with the MP4 parser, allowing for handling of non-standard box types. ### Purpose Register custom box. ### Docs [Box Type and Registration](box-type-and-registration.md) ``` -------------------------------- ### Extract Track Duration and Timescale from MP4 Source: https://github.com/abema/go-mp4/blob/master/_autodocs/examples.md Extracts timing information, codec details, and other metadata for each track in an MP4 file. Uses `mp4.Probe` to get file-level information and then iterates through `info.Tracks`. ```go package main import ( "fmt" "log" "os" "github.com/abema/go-mp4" ) func main() { file, err := os.Open("media.mp4") if err != nil { log.Fatal(err) } defer file.Close() info, err := mp4.Probe(file) if err != nil { log.Fatal(err) } fmt.Printf("File: %s\n", "media.mp4") fmt.Printf("Major Brand: %s\n", string(info.MajorBrand[:])) fmt.Printf("Global Duration: %d (timescale %d)\n", info.Duration, info.Timescale) fmt.Printf("Fast Start: %v\n", info.FastStart) fmt.Printf("Tracks: %d\n\n", len(info.Tracks)) for i, track := range info.Tracks { fmt.Printf("Track %d:\n", i+1) fmt.Printf(" ID: %d\n", track.TrackID) fmt.Printf(" Timescale: %d\n", track.Timescale) fmt.Printf(" Duration: %d\n", track.Duration) // Calculate seconds if track.Timescale > 0 { seconds := float64(track.Duration) / float64(track.Timescale) fmt.Printf(" Duration: %f seconds\n", seconds) } switch track.Codec { case mp4.CodecAVC1: fmt.Printf(" Codec: H.264/AVC\n") if track.AVC != nil { fmt.Printf(" Resolution: %dx%d\n", track.AVC.Width, track.AVC.Height) fmt.Printf(" Profile: %d, Level: %d\n", track.AVC.Profile, track.AVC.Level) } case mp4.CodecMP4A: fmt.Printf(" Codec: AAC\n") if track.MP4A != nil { fmt.Printf(" Channels: %d\n", track.MP4A.ChannelCount) } default: fmt.Printf(" Codec: Unknown\n") } fmt.Printf(" Encrypted: %v\n", track.Encrypted) fmt.Printf(" Samples: %d\n", len(track.Samples)) fmt.Printf(" Chunks: %d\n\n", len(track.Chunks)) } } ``` -------------------------------- ### Get String Representation of BoxType Source: https://github.com/abema/go-mp4/blob/master/_autodocs/box-type-and-registration.md Returns a human-readable string representation of the box type. If all four bytes are printable ASCII, returns the string form; otherwise, returns hex format. ```go func (boxType BoxType) String() string ``` ```go fmt.Println(mp4.StrToBoxType("mdat").String()) // Output: "mdat" ``` -------------------------------- ### Edit and Rewrite MP4 File Source: https://github.com/abema/go-mp4/blob/master/_autodocs/quick-reference.md Modifies an MP4 file by reading it box by box and rewriting specific boxes with new data. This example shows how to update an 'emsg' box and copy others. ```go w := mp4.NewWriter(outFile) mp4.ReadBoxStructure(inFile, func(h *mp4.ReadHandle) (interface{}, error) { switch h.BoxInfo.Type { case mp4.BoxTypeEmsg(): box, _, _ := h.ReadPayload() emsg := box.(*mp4.Emsg) emsg.MessageData = []byte("new data") w.StartBox(&h.BoxInfo) mp4.Marshal(w, emsg, h.BoxInfo.Context) h.BoxInfo.Size = ... w.EndBox() default: w.CopyBox(inFile, &h.BoxInfo) } return nil, nil }) ``` -------------------------------- ### Extract All Samples from Track (Go) Source: https://github.com/abema/go-mp4/blob/master/_autodocs/examples.md Reads and prints sample size, timing, and location for each sample in the first track of an MP4 file. Limited to the first 10 samples for brevity. ```go package main import ( "fmt" "log" "os" "github.com/abema/go-mp4" ) func main() { file, err := os.Open("media.mp4") if err != nil { log.Fatal(err) } defer file.Close() info, err := mp4.Probe(file) if err != nil { log.Fatal(err) } if len(info.Tracks) == 0 { log.Fatal("No tracks found") } track := info.Tracks[0] // First track fmt.Printf("Track %d - %d samples:\n", track.TrackID, len(track.Samples)) dtsTime := uint64(0) for i, sample := range track.Samples { if i >= 10 { fmt.Printf("... (%d more samples)\n", len(track.Samples)-10) break } ptsTime := dtsTime + uint64(sample.CompositionTimeOffset) fmt.Printf("Sample %d:\n", i) fmt.Printf(" Size: %d bytes\n", sample.Size) fmt.Printf(" DTS: %d (delta from prev: %d)\n", dtsTime, sample.TimeDelta) fmt.Printf(" PTS: %d (offset: %d)\n", ptsTime, sample.CompositionTimeOffset) dtsTime += uint64(sample.TimeDelta) } // Find sample location if len(track.Chunks) > 0 { chunk := track.Chunks[0] fmt.Printf("\nFirst chunk:\n") fmt.Printf(" Data Offset: 0x%x\n", chunk.DataOffset) fmt.Printf(" Samples: %d\n", chunk.SamplesPerChunk) } } ``` -------------------------------- ### Print All Box Info During Traversal Source: https://github.com/abema/go-mp4/blob/master/_autodocs/stringification.md This snippet shows how to traverse an MP4 file and print detailed information for each box encountered, including its path, type, size, and stringified payload if supported. It uses a callback function with `mp4.ReadBoxStructure`. ```go _, err := mp4.ReadBoxStructure(file, func(h *mp4.ReadHandle) (interface{}, error) { fmt.Printf("Path: %v\n", h.Path) fmt.Printf("Box: %s\n", h.BoxInfo.Type.String()) fmt.Printf("Size: %d bytes\n", h.BoxInfo.Size) if h.BoxInfo.IsSupportedType() { box, _, _ := h.ReadPayload() str, _ := mp4.Stringify(box, h.BoxInfo.Context) fmt.Printf("Data: %s\n", str) } return nil, nil }) ``` -------------------------------- ### Verify MP4 box version checking in Go Source: https://github.com/abema/go-mp4/blob/master/_autodocs/errors.md Test the version checking mechanism by creating a box with an unsupported version and attempting to stringify it. This confirms that the library enforces version constraints, such as those defined by mp4.AddBoxDef. ```go // Create a box with unsupported version box := &mp4.Mvhd{FullBox: mp4.FullBox{Version: 5}} // Try to stringify (would fail if version 5 not registered) // mp4.AddBoxDef(&mp4.Mvhd{}, 0, 1) only allows 0, 1 ``` -------------------------------- ### MP4 Struct Tag: Order Field Example Source: https://github.com/abema/go-mp4/blob/master/_autodocs/marshalling.md Illustrates the use of the `order` component in `mp4` tags to control field serialization order. The `extend` keyword allows inheriting fields from embedded structs. ```go type Mvhd struct { FullBox `mp4:"0,extend"` // Version and Flags take orders 0-1 Timescale uint32 `mp4:"2,size=32"` // Timescale is order 2 } ``` -------------------------------- ### go-mp4 Project File Structure Source: https://github.com/abema/go-mp4/blob/master/_autodocs/INDEX.md Overview of the directory structure for the go-mp4 project, detailing the purpose of each markdown file. ```text /workspace/home/output/ ├── INDEX.md (this file) ├── README.md (overview) ├── quick-reference.md (code snippets) ├── examples.md (9 practical examples) │ ├── box-type-and-registration.md (BoxType, AddBoxDef) ├── box-interface.md (IBox, custom fields) ├── box-info.md (BoxInfo, Context) ├── read-and-traverse.md (ReadBoxStructure, ReadHandle) ├── extract-and-probe.md (ExtractBox, Probe) ├── marshalling.md (Marshal, Unmarshal) ├── writing.md (Writer) ├── stringification.md (Stringify) │ ├── types.md (type catalog) ├── errors.md (error reference) ├── common-boxes.md (ftyp, moov, trak, etc.) └── INDEX.md (navigation) ``` -------------------------------- ### GetVersion Method Source: https://github.com/abema/go-mp4/blob/master/_autodocs/box-interface.md Retrieves the version field from a FullBox. Returns 0 for non-FullBox types. ```go func (box IImmutableBox) GetVersion() uint8 ``` -------------------------------- ### Stringify Go Box Structs Source: https://github.com/abema/go-mp4/blob/master/_autodocs/stringification.md Use `Stringify` to get a human-readable string representation of a box struct. It reflects over fields, formats them, and calls custom hooks. Errors occur if box type definitions are not found. ```go func Stringify(src IImmutableBox, ctx Context) (string, error) ``` ```go mvhd := &mp4.Mvhd{ FullBox: mp4.FullBox{Version: 0, Flags: [3]byte{}}, Timescale: 24000, DurationV0: 3600000, } str, err := mp4.Stringify(mvhd, mp4.Context{}) if err != nil { log.Fatal(err) } fmt.Println(str) // Output: // {Version=0 Flags=0x000000 Timescale=24000 DurationV0=3600000} ``` -------------------------------- ### Handle ErrBoxInfoNotFound Source: https://github.com/abema/go-mp4/blob/master/_autodocs/errors.md Demonstrates how to check for and handle the ErrBoxInfoNotFound error when creating a box. ```go box, err := mp4.StrToBoxType("xxxx").New(ctx) if err == mp4.ErrBoxInfoNotFound { log.Println("Box type xxxx is not supported") // Either register a definition or skip parsing } ``` -------------------------------- ### Create New Box Instance Source: https://github.com/abema/go-mp4/blob/master/_autodocs/box-type-and-registration.md Creates a new instance of the box with this type. Returns `ErrBoxInfoNotFound` if the box type is not supported. ```go func (boxType BoxType) New(ctx Context) (IBox, error) ``` ```go ctx := mp4.Context{} box, err := mp4.StrToBoxType("mdat").New(ctx) if err != nil { log.Fatal(err) } Mdat := box.(*mp4.Mdat) ``` -------------------------------- ### List All Boxes in MP4 File Source: https://github.com/abema/go-mp4/blob/master/_autodocs/examples.md Recursively prints all boxes and their hierarchy within an MP4 file. Requires opening the file and using `mp4.ReadBoxStructure` with a custom handler to process each box. ```go package main import ( "fmt" "log" "os" "strings" "github.com/abema/go-mp4" ) func main() { file, err := os.Open("media.mp4") if err != nil { log.Fatal(err) } defer file.Close() _, err = mp4.ReadBoxStructure(file, func(h *mp4.ReadHandle) (interface{}, error) { indent := strings.Repeat(" ", len(h.Path)-1) fmt.Printf("%s[%s] Size=%d\n", indent, h.BoxInfo.Type.String(), h.BoxInfo.Size) if h.BoxInfo.IsSupportedType() { _, _, err := h.ReadPayload() if err != nil { return nil, err } return h.Expand() } return nil, nil }) if err != nil { log.Fatal(err) } } ``` -------------------------------- ### Create MP4 Box by Type String Source: https://github.com/abema/go-mp4/blob/master/_autodocs/quick-reference.md Creates a new MP4 box instance from its string representation. This is useful for dynamically creating boxes, such as 'mdat'. ```go box, _ := mp4.StrToBoxType("mdat").New(ctx) Mdat := box.(*mp4.Mdat) ``` -------------------------------- ### Common MP4 Box Constructors Source: https://github.com/abema/go-mp4/blob/master/_autodocs/quick-reference.md Constructors for various common MP4 boxes, including file type, media data, movie, track, and metadata boxes. ```go mp4.BoxTypeFtyp() // File Type mp4.BoxTypeMdat() // Media Data mp4.BoxTypeMoov() // Movie mp4.BoxTypeMvhd() // Movie Header mp4.BoxTypeTrak() // Track mp4.BoxTypeTkhd() // Track Header mp4.BoxTypeMdia() // Media mp4.BoxTypeMdhd() // Media Header mp4.BoxTypeMnif() // Media Information mp4.BoxTypeStbl() // Sample Table mp4.BoxTypeStsd() // Sample Description mp4.BoxTypeStts() // Time-to-Sample mp4.BoxTypeCtts() // Composition-to-DTS mp4.BoxTypeStsc() // Sample-to-Chunk mp4.BoxTypeStsz() // Sample Size mp4.BoxTypeStco() // Chunk Offset mp4.BoxTypeCo64() // Chunk Offset 64 mp4.BoxTypeMoof() // Movie Fragment mp4.BoxTypeMfhd() // Movie Fragment Header mp4.BoxTypeTraf() // Track Fragment mp4.BoxTypeTfhd() // Track Fragment Header mp4.BoxTypeTfdt() // Track Fragment Decode Time mp4.BoxTypeTrun() // Track Run mp4.BoxTypeMeta() // Metadata mp4.BoxTypeIlst() // iTunes Metadata mp4.BoxTypeAvcC() // AVC Decoder Configuration mp4.BoxTypeEsds() // Elementary Stream Descriptor mp4.BoxTypeHvcC() // HEVC Decoder Configuration ```