go-mp4

repository·master·Indexed 19 days ago

https://github.com/abema/go-mp4

A low-level Go library for parsing and building MP4 boxes (atoms). It provides flexible I/O interfaces via io.ReadSeeker to scan box trees, extract specific boxes, and modify MP4 structures using a Writer. The library supports custom box definitions, provides the mp4tool CLI for file inspection, and includes built-in support for various codecs and metadata types, including AV1, AC-3, E-AC-3, and FLAC.

Tokens
18.2K
Snippets
61
Records
99
Agent score
69%

What's inside go-mp4

  1. Define user-defined MP4 boxes

    master

    You can extend the library by defining custom box types. This requires:

    1. Defining a BoxType using mp4.StrToBoxType.
    2. Creating a struct that embeds mp4.FullBox (or similar) and uses struct tags to define the layout.
    3. Implementing the GetType() BoxType method on your struct.
    4. Registering the box with mp4.AddBoxDef(instance, offset) during initialization.

    Example struct tags:

    • mp4:"0,extend" for a FullBox.
    • mp4:"1,size=32" for a fixed-size field.
    • mp4:"2,size=8,len=dynamic" for a dynamic byte array.
    func BoxTypeXxxx() BoxType { return mp4.StrToBoxType("xxxx") }
    
    func init() {
    	mp4.AddBoxDef(&Xxxx{}, 0)
    }
    
    type Xxxx struct {
    	FullBox  `mp4:"0,extend"`
    	UI32      uint32 `mp4:"1,size=32"`
    	ByteArray []byte `mp4:"2,size=8,len=dynamic"`
    }
    
    func (*Xxxx) GetType() BoxType {
    	return BoxTypeXxxx()
    }
  2. Write and edit MP4 box trees using Writer

    master

    The mp4.NewWriter provides a way to build or modify MP4 box trees. When editing an existing file, you typically use ReadBoxStructure to traverse the source and a Writer to write to the destination.

    Common operations within the ReadBoxStructure callback:

    • w.StartBox(&h.BoxInfo): Starts writing a box with the specified header.
    • mp4.Marshal(w, box, context): Writes the payload of a specific box type.
    • w.EndBox(): Finalizes the current box (useful for updating size).
    • w.CopyBox(r, &h.BoxInfo): Copies an existing box from the reader to the writer without modification.

    Warning: When modifying boxes that change the file structure (like adding metadata), you must manually update dependent boxes (like stco chunk offsets) if the mdat box offset changes.

    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)
    	}
    })
  3. Analyze media tracks with Track

    master

    A Track object represents a single media stream (e.g., video or audio) within the MP4 file. It contains detailed information about the codec and the layout of the media data.

    Key fields:

    • TrackID (uint32): Unique identifier for the track.
    • Codec (Codec): The type of codec used (CodecAVC1 for H.264, CodecMP4A for AAC, or CodecUnknown).
    • Encrypted (bool): Indicates if the track is encrypted.
    • AVC (*AVCDecConfigInfo): Detailed configuration for AVC (H.264) tracks, including Profile, Level, Width, and Height.
    • MP4A (*MP4AInfo): Detailed configuration for AAC tracks, including ChannelCount and AAC profile indicators (OTI, AudOTI).
    • Samples (Samples): A list of Sample objects containing Size, TimeDelta, and CompositionTimeOffset.
    • Chunks (Chunks): A list of Chunk objects mapping DataOffset and SamplesPerChunk to the physical file location.
  4. Understand BoxInfo and Context for MP4 metadata

    master

    The mp4 package provides BoxInfo and Context types to manage metadata for MP4 boxes (atoms) as defined in ISO/IEC 14496-12.

    BoxInfo

    BoxInfo contains the structural metadata for a box, including its position and size in a file. Key fields include:

    • Offset: The byte offset of the box in the file.
    • Size: The total size of the box in bytes.
    • HeaderSize: The size of the box header (either SmallHeaderSize (8 bytes) or LargeHeaderSize (16 bytes)).
    • Type: The 4-character BoxType identifier.
    • ExtendToEOF: A boolean indicating if the box extends to the end of the file (when Size is 0).
    • Context: Structural metadata about where the box resides in the MP4 hierarchy.

    Context

    Context provides state about the box's location within the MP4 structure, which is used to determine if a box type is supported via IsSupportedType(). It tracks properties such as:

    • IsQuickTimeCompatible: Whether the file is QuickTime compatible.
    • UnderWave, UnderIlst, UnderUdta, etc.: Boolean flags indicating if the current box is nested under specific parent boxes like moov.movie.track.mdia.minf.stbl.stts (implied by hierarchy) or specific containers like udta or ilst.
    • QuickTimeKeysMetaEntryCount: Expected items under an ilst box.
  5. Use IImmutableBox and IBox interfaces for MP4 boxes

    master

    The library uses a hierarchy of interfaces to represent MP4 boxes:

    1. IImmutableBox: Represents a box that can be read but not modified. It provides access to the box's Version, Flags, and BoxType (via GetType()).
    2. IBox: Extends IImmutableBox to allow modification. It adds methods to SetVersion, SetFlags, AddFlag, and RemoveFlag.

    Use IImmutableBox when you only need to inspect box metadata, and IBox when you need to construct or modify boxes.

  6. Understand the ProbeInfo structure

    master

    ProbeInfo is the primary result returned by the Probe function. It provides a high-level overview of the MP4 container.

    Key fields:

    • MajorBrand ([4]byte): The primary brand of the MP4 file.
    • MinorVersion (uint32): The minor version of the major brand.
    • CompatibleBrands ([][4]byte): A list of brands compatible with the major brand.
    • FastStart (bool): True if the moov box appears before the mdat box, allowing for efficient streaming.
    • Timescale (uint32): The global timescale used for duration calculations.
    • Duration (uint64): The total duration of the file in timescale units.
    • Tracks (Tracks): A collection of Track objects describing individual media streams.
    • Segments (Segments): A collection of Segment objects, typically used in fragmented MP4 (fMP4) files.
  7. Extract specific MP4 boxes

    master

    If you only need to extract specific boxes rather than scanning the whole tree, use the following wrapper functions:

    • ExtractBox
    • ExtractBoxes
    • ExtractBoxWithPayload
    • ExtractBoxesWithPayload
    • Probe (to get basic information like track numbers)

    You can specify which boxes to extract by providing an mp4.BoxPath containing the desired box types.

    // extract specific boxes
    boxes, err := mp4.ExtractBoxWithPayload(file, nil, mp4.BoxPath{mp4.BoxTypeMoov(), mp4.BoxTypeTrak(), mp4.BoxTypeTkhd()})
    if err != nil {
       // handle error
    }
    for _, box := range boxes {
      tkhd := box.Payload.(*mp4.Tkhd)
      fmt.Println("track ID:", tkhd.TrackID)
    }
    
    // get basic informations
    info, err := mp4.Probe(bufseekio.NewReadSeeker(file, 1024, 4))  
    if err != nil {
       // handle error
    }
    fmt.Println("track num:", len(info.Tracks))
  8. Read MP4 box trees using ReadBoxStructure

    master

    To scan an MP4 box (atom) tree in depth-first order, use mp4.ReadBoxStructure or mp4.ReadBoxStructureFromInternal. You provide an io.ReadSeeker and a callback function that receives a *mp4.ReadHandle for each box.

    Inside the callback, you can:

    • Access box metadata via h.BoxInfo (Type, Size, etc.).
    • Read the payload using h.ReadPayload().
    • Convert the payload to a string using mp4.Stringify().
    • Recursively expand children by returning h.Expand() from the callback.

    Note: This library is designed for low-level I/O and is not intended for complex data conversions.

    // expand all boxes
    _, err := mp4.ReadBoxStructure(file, func(h *mp4.ReadHandle) (interface{}, error) {
    	fmt.Println("depth", len(h.Path))
    
    	// Box Type (e.g. "mdhd", "tfdt", "mdat")
    	fmt.Println("type", h.BoxInfo.Type.String())
    
    	// Box Size
    	fmt.Println("size", h.BoxInfo.Size)
    
    	if h.BoxInfo.IsSupportedType() {
    		// Payload
    		box, _, err := h.ReadPayload()
    		if err != nil {
    			return nil, err
    		}
    		str, err := mp4.Stringify(box, h.BoxInfo.Context)
    		if err != nil {
    			return nil, err
    		}
    		fmt.Println("payload", str)
    
    		// Expands children
    		return h.Expand()
    	}
    	return nil, nil
    })
  9. Convert an MP4 box to a string with indentation

    master

    To generate a pretty-printed, multi-line string representation of an MP4 box, use StringifyWithIndent. You must provide a non-empty indentation string (like spaces or tabs) to trigger the multi-line formatting. If an empty string is provided, the output will be compact.

    Note: Both functions require a Context object and an IImmutableBox implementation.

    // Assuming 'box' is an IImmutableBox and 'ctx' is a mp4.Context
    prettyStr, err := mp4.StringifyWithIndent(box, "    ", ctx)
    if err != nil {
        // handle error
    }
    fmt.Println(prettyStr)
  10. Install and use the mp4tool CLI

    master

    The mp4tool command-line utility allows you to inspect MP4 files.

    Installation:

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

    Usage:

    • mp4tool dump <FILE>: Prints the MP4 box tree.
    • mp4tool dump <FILE> -a: Shows all box details (equivalent to expanding all).
    • mp4tool dump <FILE> -mdat: Expands the mdat box content.
    go install github.com/abema/go-mp4/cmd/mp4tool@latest
    
    mp4tool -help
    
    # Example: dump box tree
    mp4tool dump MP4_FILE_NAME
  11. Use IAnyType and AnyTypeBox for generic MP4 box types

    master

    The mp4 package provides IAnyType and AnyTypeBox to handle MP4 boxes where the specific type is not fixed at compile time but can be set or retrieved dynamically.

    • IAnyType is an interface that embeds IBox and requires a SetType(BoxType) method. Use this when you need to interact with a box that has a variable type.
    • AnyTypeBox is a concrete implementation of IAnyType. It embeds a standard Box and includes a Type field of type BoxType.

    You can use AnyTypeBox to represent a box whose type is determined during parsing or runtime, allowing you to call GetType() to identify it or SetType(boxType) to modify it.

    // Example of using AnyTypeBox to manage a box with a dynamic type
    var box mp4.IAnyType = &mp4.AnyTypeBox{
        Box: mp4.Box{ /* ... */ },
        Type: mp4.BoxType{ /* ... */ },
    }
    
    // Set a new type
    box.SetType(mp4.BoxType{ /* ... */ })
    
    // Retrieve the type
    type := box.(*mp4.AnyTypeBox).GetType()
  12. Get VP8 and VP9 BoxTypes

    master

    The package provides helper functions to retrieve the BoxType identifiers for VP8 and VP9 codec-specific boxes. These types are used to identify VisualSampleEntry boxes containing VP8 or VP9 data.

    type8 := mp4.BoxTypeVp08()
    type9 := mp4.BoxTypeVp09()