// 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 childBoxDefinitions map[string]map[string]BoxDefinition IgnoreUnknownBoxes bool Content []any } // NewParser creates a new parser with byte data func NewParser(reader io.Reader) *Parser { // prepare valid box type combinations types := make(map[string]map[string]BoxDefinition, 0) for _, definition := range BoxDefinitions { for _, parent := range definition.ParentTypes { if _, exists := types[parent]; !exists { types[parent] = make(map[string]BoxDefinition, 0) } types[parent][definition.Type] = definition } } return &Parser{reader: reader, childBoxDefinitions: types} } // Parse starts the file parsing func (parser *Parser) Parse() error { var filePosition uint64 = 0 for { box, endPosition, endOfFile, err := parseNextBox(parser, parser.reader, boxTypeParentFile, 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(parser *Parser, reader io.Reader, currentBoxType string, filePosition uint64) (box any, 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 if boxTypeDefinition, found := parser.childBoxDefinitions[currentBoxType][boxType]; found { box, err = boxTypeDefinition.Parser(parser, filePosition, boxHeaderSize, boxContentBytes) } else { logger.Println("unknown box type", boxType, "at", filePosition, "in", currentBoxType) if !parser.IgnoreUnknownBoxes { return nil, filePosition, false, errors.New("unknown box type " + boxType + " in " + currentBoxType) } } // check for box errors if err != nil { return } // calculate end position endPosition = filePosition + uint64(boxSize) return } func (box *Box) parseChildBoxes(parser *Parser, currentBoxType string, filePosition uint64, content []byte) (subBoxes []any, 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(parser, byteReader, currentBoxType, 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 } } }