// Copyright 2024 Martin Riedl // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package gomp4 import ( "bytes" "encoding/binary" "errors" "fmt" "io" ) // Parser struct for mp4 container parsing type Parser struct { reader io.Reader Content []interface{} } // NewParser creates a new parser with byte data func NewParser(reader io.Reader) *Parser { return &Parser{reader: reader} } // Parse starts the file parsing func (parser *Parser) Parse() error { var filePosition uint64 = 0 for { box, endPosition, endOfFile, err := parseNextBox(parser.reader, filePosition) if box != nil { parser.Content = append(parser.Content, box) } if endOfFile { return nil } else if err != nil { logger.Println("error at", endPosition) return err } filePosition = endPosition } } func parseNextBox(reader io.Reader, filePosition uint64) (box interface{}, endPosition uint64, endOfFile bool, err error) { // first 4 bytes are the size of the box sizeBytes := make([]byte, boxSizeLength) sizeBytesCount, err := reader.Read(sizeBytes) if err != nil { if err == io.EOF { return nil, filePosition, true, nil } return nil, filePosition, false, err } // check read bytes count if sizeBytesCount != len(sizeBytes) { return nil, filePosition, false, fmt.Errorf("unable to parse next box size") } // parse box size boxSize := binary.BigEndian.Uint32(sizeBytes) logger.Println("new box size", boxSize, "at", filePosition) // TODO: check large size box type // parse box type boxTypeBytes := make([]byte, boxTypeLength) boxTypeBytesCount, err := reader.Read(boxTypeBytes) if err != nil { return nil, filePosition, false, err } // check read bytes count if boxTypeBytesCount != len(boxTypeBytes) { return nil, filePosition, false, fmt.Errorf("unable to parse box type at %d", filePosition) } boxType := string(boxTypeBytes) logger.Println("new box type", boxType, "at", filePosition) // parse box data boxHeaderSize := boxSizeLength + boxTypeLength boxContentSize := boxSize - boxHeaderSize boxContentBytes := make([]byte, boxContentSize) boxContentBytesCount, err := reader.Read(boxContentBytes) if err != nil { return nil, filePosition, false, err } // check read bytes count if boxContentBytesCount != len(boxContentBytes) { return nil, filePosition, false, fmt.Errorf("unable to parse box content at %d", filePosition) } // parse struct of box type switch boxType { case BoxTypeFileType: box = ParseFileTypeBox(filePosition, boxHeaderSize, boxContentBytes) case BoxTypeMediaData: box = ParseMediaDataBox(filePosition, boxHeaderSize, boxContentBytes) case BoxTypeMovie: box, err = ParseMovieBox(filePosition, boxHeaderSize, boxContentBytes) case BoxTypeMovieHeader: box = ParseMovieHeaderBox(filePosition, boxHeaderSize, boxContentBytes) case BoxTypeTrack: box, err = ParseTrackBox(filePosition, boxHeaderSize, boxContentBytes) case BoxTypeTrackHeader: box = ParseTrackHeaderBox(filePosition, boxHeaderSize, boxContentBytes) case BoxTypeMedia: box, err = ParseMediaBox(filePosition, boxHeaderSize, boxContentBytes) case BoxTypeMediaHeader: box = ParseMediaHeaderBox(filePosition, boxHeaderSize, boxContentBytes) case BoxTypeHandlerReference: box = ParseHandlerReferenceBox(filePosition, boxHeaderSize, boxContentBytes) case BoxTypeMediaInformation: box, err = ParseMediaInformationBox(filePosition, boxHeaderSize, boxContentBytes) case BoxTypeSampleTable: box, err = ParseSampleTableBox(filePosition, boxHeaderSize, boxContentBytes) case BoxTypeDataInformation: box, err = ParseDataInformationBox(filePosition, boxHeaderSize, boxContentBytes) case BoxTypeDataReferenceBox: box, err = ParseDataReferenceBox(filePosition, boxHeaderSize, boxContentBytes) case BoxTypeDataEntryUrlBox: box = ParseDataEntryUrlBox(filePosition, boxHeaderSize, boxContentBytes) case BoxTypeDataEntryUrnBox: box = ParseDataEntryUrnBox(filePosition, boxHeaderSize, boxContentBytes) case BoxTypeMovieFragment: box, err = ParseMovieFragmentBox(filePosition, boxHeaderSize, boxContentBytes) case BoxTypeMovieFragmentHeader: box = ParseMovieFragmentHeaderBox(filePosition, boxHeaderSize, boxContentBytes) case BoxTypeTrackFragment: box, err = ParseTrackFragmentBox(filePosition, boxHeaderSize, boxContentBytes) case BoxTypeTrackFragmentHeader: box = ParseTrackFragmentHeaderBox(filePosition, boxHeaderSize, boxContentBytes) case BoxTypeTrackFragmentRun: box, err = ParseTrackFragmentRunBox(filePosition, boxHeaderSize, boxContentBytes) case BoxTypeVideoMediaHeader: box = ParseVideoMediaHeaderBox(filePosition, boxHeaderSize, boxContentBytes) default: logger.Println("unknown box type", boxType, "at", filePosition) return nil, filePosition, false, errors.New("unknown box type " + boxType) } // check for box errors if err != nil { return } // calculate end position endPosition = filePosition + uint64(boxSize) return } func (box *Box) parseChildBoxes(filePosition uint64, content []byte) (subBoxes []interface{}, e error) { byteReader := bytes.NewReader(content) endPosition := filePosition + uint64(box.HeaderSize) + uint64(len(content)) var currentFilePosition = filePosition + uint64(box.HeaderSize) for { // parse next packet currentBox, currentEndPosition, _, err := parseNextBox(byteReader, currentFilePosition) if err != nil { e = err logger.Println("error at", currentEndPosition, err) return } currentFilePosition = currentEndPosition // store packet subBoxes = append(subBoxes, currentBox) // check for end of content if endPosition == currentEndPosition { return } } }