diff options
Diffstat (limited to 'vendor')
21 files changed, 5887 insertions, 0 deletions
diff --git a/vendor/github.com/abema/go-mp4/.gitignore b/vendor/github.com/abema/go-mp4/.gitignore new file mode 100644 index 000000000..22d0d82f8 --- /dev/null +++ b/vendor/github.com/abema/go-mp4/.gitignore @@ -0,0 +1 @@ +vendor diff --git a/vendor/github.com/abema/go-mp4/LICENSE b/vendor/github.com/abema/go-mp4/LICENSE new file mode 100644 index 000000000..c06ca63d3 --- /dev/null +++ b/vendor/github.com/abema/go-mp4/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 AbemaTV + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/abema/go-mp4/README.md b/vendor/github.com/abema/go-mp4/README.md new file mode 100644 index 000000000..2c6acfdd3 --- /dev/null +++ b/vendor/github.com/abema/go-mp4/README.md @@ -0,0 +1,153 @@ +go-mp4 +------ + +[](https://pkg.go.dev/github.com/abema/go-mp4) + +[](https://coveralls.io/github/abema/go-mp4) +[](https://goreportcard.com/report/github.com/abema/go-mp4) + +go-mp4 is Go library for reading and writing MP4. + +## Integration with your Go application + +### Reading + +You can parse MP4 file as follows: + +```go +// 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 +}) +``` + +```go +// extract specific boxes +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) +} +``` + +```go +// get basic informations +info, err := mp4.Probe(bufseekio.NewReadSeeker(file, 1024, 4))   +if err != nil { +   : +} +fmt.Println("track num:", len(info.Tracks)) +``` + +### Writing + +Writer helps you to write box tree. +The following sample code edits emsg box and writes to another file. + +```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) +	} +}) +``` + +### User-defined Boxes + +You can create additional box definition as follows: + +```go +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() +} +``` + +### Buffering + +go-mp4 has no buffering feature for I/O. +If you should reduce Read function calls, you can wrap the io.ReadSeeker by [bufseekio](https://github.com/sunfish-shogi/bufseekio). + +## Command Line Tool + +Install mp4tool as follows: + +```sh +go install github.com/abema/go-mp4/mp4tool@latest + +mp4tool -help +``` + +For example, `mp4tool dump MP4_FILE_NAME` command prints MP4 box tree as follows: + +``` +[moof] Size=504 +  [mfhd] Size=16 Version=0 Flags=0x000000 SequenceNumber=1 +  [traf] Size=480 +    [tfhd] Size=28 Version=0 Flags=0x020038 TrackID=1 DefaultSampleDuration=9000 DefaultSampleSize=33550 DefaultSampleFlags=0x1010000 +    [tfdt] Size=20 Version=1 Flags=0x000000 BaseMediaDecodeTimeV1=0 +    [trun] Size=424 ... (use -a option to show all) +[mdat] Size=44569 Data=[...] (use -mdat option to expand) +``` diff --git a/vendor/github.com/abema/go-mp4/anytype.go b/vendor/github.com/abema/go-mp4/anytype.go new file mode 100644 index 000000000..d995f59b6 --- /dev/null +++ b/vendor/github.com/abema/go-mp4/anytype.go @@ -0,0 +1,19 @@ +package mp4 + +type IAnyType interface { +	IBox +	SetType(BoxType) +} + +type AnyTypeBox struct { +	Box +	Type BoxType +} + +func (e *AnyTypeBox) GetType() BoxType { +	return e.Type +} + +func (e *AnyTypeBox) SetType(boxType BoxType) { +	e.Type = boxType +} diff --git a/vendor/github.com/abema/go-mp4/bitio/bitio.go b/vendor/github.com/abema/go-mp4/bitio/bitio.go new file mode 100644 index 000000000..404fd1b82 --- /dev/null +++ b/vendor/github.com/abema/go-mp4/bitio/bitio.go @@ -0,0 +1,8 @@ +package bitio + +import "errors" + +var ( +	ErrInvalidAlignment  = errors.New("invalid alignment") +	ErrDiscouragedReader = errors.New("discouraged reader implementation") +) diff --git a/vendor/github.com/abema/go-mp4/bitio/read.go b/vendor/github.com/abema/go-mp4/bitio/read.go new file mode 100644 index 000000000..4da76eae6 --- /dev/null +++ b/vendor/github.com/abema/go-mp4/bitio/read.go @@ -0,0 +1,97 @@ +package bitio + +import "io" + +type Reader interface { +	io.Reader + +	// alignment: +	//  |-1-byte-block-|--------------|--------------|--------------| +	//  |<-offset->|<-------------------width---------------------->| +	ReadBits(width uint) (data []byte, err error) + +	ReadBit() (bit bool, err error) +} + +type ReadSeeker interface { +	Reader +	io.Seeker +} + +type reader struct { +	reader io.Reader +	octet  byte +	width  uint +} + +func NewReader(r io.Reader) Reader { +	return &reader{reader: r} +} + +func (r *reader) Read(p []byte) (n int, err error) { +	if r.width != 0 { +		return 0, ErrInvalidAlignment +	} +	return r.reader.Read(p) +} + +func (r *reader) ReadBits(size uint) ([]byte, error) { +	bytes := (size + 7) / 8 +	data := make([]byte, bytes) +	offset := (bytes * 8) - (size) + +	for i := uint(0); i < size; i++ { +		bit, err := r.ReadBit() +		if err != nil { +			return nil, err +		} + +		byteIdx := (offset + i) / 8 +		bitIdx := 7 - (offset+i)%8 +		if bit { +			data[byteIdx] |= 0x1 << bitIdx +		} +	} + +	return data, nil +} + +func (r *reader) ReadBit() (bool, error) { +	if r.width == 0 { +		buf := make([]byte, 1) +		if n, err := r.reader.Read(buf); err != nil { +			return false, err +		} else if n != 1 { +			return false, ErrDiscouragedReader +		} +		r.octet = buf[0] +		r.width = 8 +	} + +	r.width-- +	return (r.octet>>r.width)&0x01 != 0, nil +} + +type readSeeker struct { +	reader +	seeker io.Seeker +} + +func NewReadSeeker(r io.ReadSeeker) ReadSeeker { +	return &readSeeker{ +		reader: reader{reader: r}, +		seeker: r, +	} +} + +func (r *readSeeker) Seek(offset int64, whence int) (int64, error) { +	if whence == io.SeekCurrent && r.reader.width != 0 { +		return 0, ErrInvalidAlignment +	} +	n, err := r.seeker.Seek(offset, whence) +	if err != nil { +		return n, err +	} +	r.reader.width = 0 +	return n, nil +} diff --git a/vendor/github.com/abema/go-mp4/bitio/write.go b/vendor/github.com/abema/go-mp4/bitio/write.go new file mode 100644 index 000000000..5f63dd2d2 --- /dev/null +++ b/vendor/github.com/abema/go-mp4/bitio/write.go @@ -0,0 +1,61 @@ +package bitio + +import ( +	"io" +) + +type Writer interface { +	io.Writer + +	// alignment: +	//  |-1-byte-block-|--------------|--------------|--------------| +	//  |<-offset->|<-------------------width---------------------->| +	WriteBits(data []byte, width uint) error + +	WriteBit(bit bool) error +} + +type writer struct { +	writer io.Writer +	octet  byte +	width  uint +} + +func NewWriter(w io.Writer) Writer { +	return &writer{writer: w} +} + +func (w *writer) Write(p []byte) (n int, err error) { +	if w.width != 0 { +		return 0, ErrInvalidAlignment +	} +	return w.writer.Write(p) +} + +func (w *writer) WriteBits(data []byte, width uint) error { +	length := uint(len(data)) * 8 +	offset := length - width +	for i := offset; i < length; i++ { +		oi := i / 8 +		if err := w.WriteBit((data[oi]>>(7-i%8))&0x01 != 0); err != nil { +			return err +		} +	} +	return nil +} + +func (w *writer) WriteBit(bit bool) error { +	if bit { +		w.octet |= 0x1 << (7 - w.width) +	} +	w.width++ + +	if w.width == 8 { +		if _, err := w.writer.Write([]byte{w.octet}); err != nil { +			return err +		} +		w.octet = 0x00 +		w.width = 0 +	} +	return nil +} diff --git a/vendor/github.com/abema/go-mp4/box.go b/vendor/github.com/abema/go-mp4/box.go new file mode 100644 index 000000000..72a137951 --- /dev/null +++ b/vendor/github.com/abema/go-mp4/box.go @@ -0,0 +1,188 @@ +package mp4 + +import ( +	"errors" +	"io" +	"math" + +	"github.com/abema/go-mp4/bitio" +) + +const LengthUnlimited = math.MaxUint32 + +type ICustomFieldObject interface { +	// GetFieldSize returns size of dynamic field +	GetFieldSize(name string, ctx Context) uint + +	// GetFieldLength returns length of dynamic field +	GetFieldLength(name string, ctx Context) uint + +	// IsOptFieldEnabled check whether if the optional field is enabled +	IsOptFieldEnabled(name string, ctx Context) bool + +	// StringifyField returns field value as string +	StringifyField(name string, indent string, depth int, ctx Context) (string, bool) + +	IsPString(name string, bytes []byte, remainingSize uint64, ctx Context) bool + +	BeforeUnmarshal(r io.ReadSeeker, size uint64, ctx Context) (n uint64, override bool, err error) + +	OnReadField(name string, r bitio.ReadSeeker, leftBits uint64, ctx Context) (rbits uint64, override bool, err error) + +	OnWriteField(name string, w bitio.Writer, ctx Context) (wbits uint64, override bool, err error) +} + +type BaseCustomFieldObject struct { +} + +// GetFieldSize returns size of dynamic field +func (box *BaseCustomFieldObject) GetFieldSize(string, Context) uint { +	panic(errors.New("GetFieldSize not implemented")) +} + +// GetFieldLength returns length of dynamic field +func (box *BaseCustomFieldObject) GetFieldLength(string, Context) uint { +	panic(errors.New("GetFieldLength not implemented")) +} + +// IsOptFieldEnabled check whether if the optional field is enabled +func (box *BaseCustomFieldObject) IsOptFieldEnabled(string, Context) bool { +	return false +} + +// StringifyField returns field value as string +func (box *BaseCustomFieldObject) StringifyField(string, string, int, Context) (string, bool) { +	return "", false +} + +func (*BaseCustomFieldObject) IsPString(name string, bytes []byte, remainingSize uint64, ctx Context) bool { +	return true +} + +func (*BaseCustomFieldObject) BeforeUnmarshal(io.ReadSeeker, uint64, Context) (uint64, bool, error) { +	return 0, false, nil +} + +func (*BaseCustomFieldObject) OnReadField(string, bitio.ReadSeeker, uint64, Context) (uint64, bool, error) { +	return 0, false, nil +} + +func (*BaseCustomFieldObject) OnWriteField(string, bitio.Writer, Context) (uint64, bool, error) { +	return 0, false, nil +} + +// IImmutableBox is common interface of box +type IImmutableBox interface { +	ICustomFieldObject + +	// GetVersion returns the box version +	GetVersion() uint8 + +	// GetFlags returns the flags +	GetFlags() uint32 + +	// CheckFlag checks the flag status +	CheckFlag(uint32) bool + +	// GetType returns the BoxType +	GetType() BoxType +} + +// IBox is common interface of box +type IBox interface { +	IImmutableBox + +	// SetVersion sets the box version +	SetVersion(uint8) + +	// SetFlags sets the flags +	SetFlags(uint32) + +	// AddFlag adds the flag +	AddFlag(uint32) + +	// RemoveFlag removes the flag +	RemoveFlag(uint32) +} + +type Box struct { +	BaseCustomFieldObject +} + +// GetVersion returns the box version +func (box *Box) GetVersion() uint8 { +	return 0 +} + +// SetVersion sets the box version +func (box *Box) SetVersion(uint8) { +} + +// GetFlags returns the flags +func (box *Box) GetFlags() uint32 { +	return 0x000000 +} + +// CheckFlag checks the flag status +func (box *Box) CheckFlag(flag uint32) bool { +	return true +} + +// SetFlags sets the flags +func (box *Box) SetFlags(uint32) { +} + +// AddFlag adds the flag +func (box *Box) AddFlag(flag uint32) { +} + +// RemoveFlag removes the flag +func (box *Box) RemoveFlag(flag uint32) { +} + +// FullBox is ISOBMFF FullBox +type FullBox struct { +	BaseCustomFieldObject +	Version uint8   `mp4:"0,size=8"` +	Flags   [3]byte `mp4:"1,size=8"` +} + +// GetVersion returns the box version +func (box *FullBox) GetVersion() uint8 { +	return box.Version +} + +// SetVersion sets the box version +func (box *FullBox) SetVersion(version uint8) { +	box.Version = version +} + +// GetFlags returns the flags +func (box *FullBox) GetFlags() uint32 { +	flag := uint32(box.Flags[0]) << 16 +	flag ^= uint32(box.Flags[1]) << 8 +	flag ^= uint32(box.Flags[2]) +	return flag +} + +// CheckFlag checks the flag status +func (box *FullBox) CheckFlag(flag uint32) bool { +	return box.GetFlags()&flag != 0 +} + +// SetFlags sets the flags +func (box *FullBox) SetFlags(flags uint32) { +	box.Flags[0] = byte(flags >> 16) +	box.Flags[1] = byte(flags >> 8) +	box.Flags[2] = byte(flags) +} + +// AddFlag adds the flag +func (box *FullBox) AddFlag(flag uint32) { +	box.SetFlags(box.GetFlags() | flag) +} + +// RemoveFlag removes the flag +func (box *FullBox) RemoveFlag(flag uint32) { +	box.SetFlags(box.GetFlags() & (^flag)) +} diff --git a/vendor/github.com/abema/go-mp4/box_info.go b/vendor/github.com/abema/go-mp4/box_info.go new file mode 100644 index 000000000..b5c587fd9 --- /dev/null +++ b/vendor/github.com/abema/go-mp4/box_info.go @@ -0,0 +1,155 @@ +package mp4 + +import ( +	"bytes" +	"encoding/binary" +	"io" +	"math" +) + +type Context struct { +	// IsQuickTimeCompatible represents whether ftyp.compatible_brands contains "qt  ". +	IsQuickTimeCompatible bool + +	// UnderWave represents whether current box is under the wave box. +	UnderWave bool + +	// UnderIlst represents whether current box is under the ilst box. +	UnderIlst bool + +	// UnderIlstMeta represents whether current box is under the metadata box under the ilst box. +	UnderIlstMeta bool + +	// UnderIlstFreeMeta represents whether current box is under "----" box. +	UnderIlstFreeMeta bool + +	// UnderUdta represents whether current box is under the udta box. +	UnderUdta bool +} + +// BoxInfo has common infomations of box +type BoxInfo struct { +	// Offset specifies an offset of the box in a file. +	Offset uint64 + +	// Size specifies size(bytes) of box. +	Size uint64 + +	// HeaderSize specifies size(bytes) of common fields which are defined as "Box" class member at ISO/IEC 14496-12. +	HeaderSize uint64 + +	// Type specifies box type which is represented by 4 characters. +	Type BoxType + +	// ExtendToEOF is set true when Box.size is zero. It means that end of box equals to end of file. +	ExtendToEOF bool + +	// Context would be set by ReadBoxStructure, not ReadBoxInfo. +	Context +} + +func (bi *BoxInfo) IsSupportedType() bool { +	return bi.Type.IsSupported(bi.Context) +} + +const ( +	SmallHeaderSize = 8 +	LargeHeaderSize = 16 +) + +// WriteBoxInfo writes common fields which are defined as "Box" class member at ISO/IEC 14496-12. +// This function ignores bi.Offset and returns BoxInfo which contains real Offset and recalculated Size/HeaderSize. +func WriteBoxInfo(w io.WriteSeeker, bi *BoxInfo) (*BoxInfo, error) { +	offset, err := w.Seek(0, io.SeekCurrent) +	if err != nil { +		return nil, err +	} + +	var data []byte +	if bi.ExtendToEOF { +		data = make([]byte, SmallHeaderSize) +	} else if bi.Size <= math.MaxUint32 && bi.HeaderSize != LargeHeaderSize { +		data = make([]byte, SmallHeaderSize) +		binary.BigEndian.PutUint32(data, uint32(bi.Size)) +	} else { +		data = make([]byte, LargeHeaderSize) +		binary.BigEndian.PutUint32(data, 1) +		binary.BigEndian.PutUint64(data[SmallHeaderSize:], bi.Size) +	} +	data[4] = bi.Type[0] +	data[5] = bi.Type[1] +	data[6] = bi.Type[2] +	data[7] = bi.Type[3] + +	if _, err := w.Write(data); err != nil { +		return nil, err +	} + +	return &BoxInfo{ +		Offset:      uint64(offset), +		Size:        bi.Size - bi.HeaderSize + uint64(len(data)), +		HeaderSize:  uint64(len(data)), +		Type:        bi.Type, +		ExtendToEOF: bi.ExtendToEOF, +	}, nil +} + +// ReadBoxInfo reads common fields which are defined as "Box" class member at ISO/IEC 14496-12. +func ReadBoxInfo(r io.ReadSeeker) (*BoxInfo, error) { +	offset, err := r.Seek(0, io.SeekCurrent) +	if err != nil { +		return nil, err +	} + +	bi := &BoxInfo{ +		Offset: uint64(offset), +	} + +	// read 8 bytes +	buf := bytes.NewBuffer(make([]byte, 0, SmallHeaderSize)) +	if _, err := io.CopyN(buf, r, SmallHeaderSize); err != nil { +		return nil, err +	} +	bi.HeaderSize += SmallHeaderSize + +	// pick size and type +	data := buf.Bytes() +	bi.Size = uint64(binary.BigEndian.Uint32(data)) +	bi.Type = BoxType{data[4], data[5], data[6], data[7]} + +	if bi.Size == 0 { +		// box extends to end of file +		offsetEOF, err := r.Seek(0, io.SeekEnd) +		if err != nil { +			return nil, err +		} +		bi.Size = uint64(offsetEOF) - bi.Offset +		bi.ExtendToEOF = true +		if _, err := bi.SeekToPayload(r); err != nil { +			return nil, err +		} + +	} else if bi.Size == 1 { +		// read more 8 bytes +		buf.Reset() +		if _, err := io.CopyN(buf, r, LargeHeaderSize-SmallHeaderSize); err != nil { +			return nil, err +		} +		bi.HeaderSize += LargeHeaderSize - SmallHeaderSize +		bi.Size = binary.BigEndian.Uint64(buf.Bytes()) +	} + +	return bi, nil +} + +func (bi *BoxInfo) SeekToStart(s io.Seeker) (int64, error) { +	return s.Seek(int64(bi.Offset), io.SeekStart) +} + +func (bi *BoxInfo) SeekToPayload(s io.Seeker) (int64, error) { +	return s.Seek(int64(bi.Offset+bi.HeaderSize), io.SeekStart) +} + +func (bi *BoxInfo) SeekToEnd(s io.Seeker) (int64, error) { +	return s.Seek(int64(bi.Offset+bi.Size), io.SeekStart) +} diff --git a/vendor/github.com/abema/go-mp4/box_types.go b/vendor/github.com/abema/go-mp4/box_types.go new file mode 100644 index 000000000..d662dddbe --- /dev/null +++ b/vendor/github.com/abema/go-mp4/box_types.go @@ -0,0 +1,2745 @@ +package mp4 + +import ( +	"bytes" +	"errors" +	"fmt" +	"io" + +	"github.com/abema/go-mp4/bitio" +	"github.com/abema/go-mp4/util" +	"github.com/google/uuid" +) + +/*************************** btrt ****************************/ + +func BoxTypeBtrt() BoxType { return StrToBoxType("btrt") } + +func init() { +	AddBoxDef(&Btrt{}, 0) +} + +type Btrt struct { +	Box +	BufferSizeDB uint32 `mp4:"0,size=32"` +	MaxBitrate   uint32 `mp4:"1,size=32"` +	AvgBitrate   uint32 `mp4:"2,size=32"` +} + +// GetType returns the BoxType +func (*Btrt) GetType() BoxType { +	return BoxTypeBtrt() +} + +/*************************** co64 ****************************/ + +func BoxTypeCo64() BoxType { return StrToBoxType("co64") } + +func init() { +	AddBoxDef(&Co64{}, 0) +} + +type Co64 struct { +	FullBox     `mp4:"0,extend"` +	EntryCount  uint32   `mp4:"1,size=32"` +	ChunkOffset []uint64 `mp4:"2,size=64,len=dynamic"` +} + +// GetType returns the BoxType +func (*Co64) GetType() BoxType { +	return BoxTypeCo64() +} + +// GetFieldLength returns length of dynamic field +func (co64 *Co64) GetFieldLength(name string, ctx Context) uint { +	switch name { +	case "ChunkOffset": +		return uint(co64.EntryCount) +	} +	panic(fmt.Errorf("invalid name of dynamic-length field: boxType=co64 fieldName=%s", name)) +} + +/*************************** colr ****************************/ + +func BoxTypeColr() BoxType { return StrToBoxType("colr") } + +func init() { +	AddBoxDef(&Colr{}) +} + +type Colr struct { +	Box +	ColourType              [4]byte `mp4:"0,size=8,string"` +	ColourPrimaries         uint16  `mp4:"1,size=16,opt=dynamic"` +	TransferCharacteristics uint16  `mp4:"2,size=16,opt=dynamic"` +	MatrixCoefficients      uint16  `mp4:"3,size=16,opt=dynamic"` +	FullRangeFlag           bool    `mp4:"4,size=1,opt=dynamic"` +	Reserved                uint8   `mp4:"5,size=7,opt=dynamic"` +	Profile                 []byte  `mp4:"6,size=8,opt=dynamic"` +	Unknown                 []byte  `mp4:"7,size=8,opt=dynamic"` +} + +func (colr *Colr) IsOptFieldEnabled(name string, ctx Context) bool { +	switch colr.ColourType { +	case [4]byte{'n', 'c', 'l', 'x'}: +		switch name { +		case "ColourType", +			"ColourPrimaries", +			"TransferCharacteristics", +			"MatrixCoefficients", +			"FullRangeFlag", +			"Reserved": +			return true +		default: +			return false +		} +	case [4]byte{'r', 'I', 'C', 'C'}, [4]byte{'p', 'r', 'o', 'f'}: +		return name == "Profile" +	default: +		return name == "Unknown" +	} +} + +// GetType returns the BoxType +func (*Colr) GetType() BoxType { +	return BoxTypeColr() +} + +/*************************** cslg ****************************/ + +func BoxTypeCslg() BoxType { return StrToBoxType("cslg") } + +func init() { +	AddBoxDef(&Cslg{}, 0, 1) +} + +type Cslg struct { +	FullBox                        `mp4:"0,extend"` +	CompositionToDTSShiftV0        int32 `mp4:"1,size=32,ver=0"` +	LeastDecodeToDisplayDeltaV0    int32 `mp4:"2,size=32,ver=0"` +	GreatestDecodeToDisplayDeltaV0 int32 `mp4:"3,size=32,ver=0"` +	CompositionStartTimeV0         int32 `mp4:"4,size=32,ver=0"` +	CompositionEndTimeV0           int32 `mp4:"5,size=32,ver=0"` +	CompositionToDTSShiftV1        int64 `mp4:"6,size=64,nver=0"` +	LeastDecodeToDisplayDeltaV1    int64 `mp4:"7,size=64,nver=0"` +	GreatestDecodeToDisplayDeltaV1 int64 `mp4:"8,size=64,nver=0"` +	CompositionStartTimeV1         int64 `mp4:"9,size=64,nver=0"` +	CompositionEndTimeV1           int64 `mp4:"10,size=64,nver=0"` +} + +// GetType returns the BoxType +func (*Cslg) GetType() BoxType { +	return BoxTypeCslg() +} + +func (cslg *Cslg) GetCompositionToDTSShift() int64 { +	switch cslg.GetVersion() { +	case 0: +		return int64(cslg.CompositionToDTSShiftV0) +	case 1: +		return cslg.CompositionToDTSShiftV1 +	default: +		return 0 +	} +} + +func (cslg *Cslg) GetLeastDecodeToDisplayDelta() int64 { +	switch cslg.GetVersion() { +	case 0: +		return int64(cslg.LeastDecodeToDisplayDeltaV0) +	case 1: +		return cslg.LeastDecodeToDisplayDeltaV1 +	default: +		return 0 +	} +} + +func (cslg *Cslg) GetGreatestDecodeToDisplayDelta() int64 { +	switch cslg.GetVersion() { +	case 0: +		return int64(cslg.GreatestDecodeToDisplayDeltaV0) +	case 1: +		return cslg.GreatestDecodeToDisplayDeltaV1 +	default: +		return 0 +	} +} + +func (cslg *Cslg) GetCompositionStartTime() int64 { +	switch cslg.GetVersion() { +	case 0: +		return int64(cslg.CompositionStartTimeV0) +	case 1: +		return cslg.CompositionStartTimeV1 +	default: +		return 0 +	} +} + +func (cslg *Cslg) GetCompositionEndTime() int64 { +	switch cslg.GetVersion() { +	case 0: +		return int64(cslg.CompositionEndTimeV0) +	case 1: +		return cslg.CompositionEndTimeV1 +	default: +		return 0 +	} +} + +/*************************** ctts ****************************/ + +func BoxTypeCtts() BoxType { return StrToBoxType("ctts") } + +func init() { +	AddBoxDef(&Ctts{}, 0, 1) +} + +type Ctts struct { +	FullBox    `mp4:"0,extend"` +	EntryCount uint32      `mp4:"1,size=32"` +	Entries    []CttsEntry `mp4:"2,len=dynamic,size=64"` +} + +type CttsEntry struct { +	SampleCount    uint32 `mp4:"0,size=32"` +	SampleOffsetV0 uint32 `mp4:"1,size=32,ver=0"` +	SampleOffsetV1 int32  `mp4:"2,size=32,ver=1"` +} + +// GetType returns the BoxType +func (*Ctts) GetType() BoxType { +	return BoxTypeCtts() +} + +// GetFieldLength returns length of dynamic field +func (ctts *Ctts) GetFieldLength(name string, ctx Context) uint { +	switch name { +	case "Entries": +		return uint(ctts.EntryCount) +	} +	panic(fmt.Errorf("invalid name of dynamic-length field: boxType=ctts fieldName=%s", name)) +} + +func (ctts *Ctts) GetSampleOffset(index int) int64 { +	switch ctts.GetVersion() { +	case 0: +		return int64(ctts.Entries[index].SampleOffsetV0) +	case 1: +		return int64(ctts.Entries[index].SampleOffsetV1) +	default: +		return 0 +	} +} + +/*************************** dinf ****************************/ + +func BoxTypeDinf() BoxType { return StrToBoxType("dinf") } + +func init() { +	AddBoxDef(&Dinf{}) +} + +// Dinf is ISOBMFF dinf box type +type Dinf struct { +	Box +} + +// GetType returns the BoxType +func (*Dinf) GetType() BoxType { +	return BoxTypeDinf() +} + +/*************************** dref ****************************/ + +func BoxTypeDref() BoxType { return StrToBoxType("dref") } +func BoxTypeUrl() BoxType  { return StrToBoxType("url ") } +func BoxTypeUrn() BoxType  { return StrToBoxType("urn ") } + +func init() { +	AddBoxDef(&Dref{}, 0) +	AddBoxDef(&Url{}, 0) +	AddBoxDef(&Urn{}, 0) +} + +// Dref is ISOBMFF dref box type +type Dref struct { +	FullBox    `mp4:"0,extend"` +	EntryCount uint32 `mp4:"1,size=32"` +} + +// GetType returns the BoxType +func (*Dref) GetType() BoxType { +	return BoxTypeDref() +} + +type Url struct { +	FullBox  `mp4:"0,extend"` +	Location string `mp4:"1,string,nopt=0x000001"` +} + +func (*Url) GetType() BoxType { +	return BoxTypeUrl() +} + +const UrlSelfContained = 0x000001 + +type Urn struct { +	FullBox  `mp4:"0,extend"` +	Name     string `mp4:"1,string,nopt=0x000001"` +	Location string `mp4:"2,string,nopt=0x000001"` +} + +func (*Urn) GetType() BoxType { +	return BoxTypeUrn() +} + +const UrnSelfContained = 0x000001 + +/*************************** edts ****************************/ + +func BoxTypeEdts() BoxType { return StrToBoxType("edts") } + +func init() { +	AddBoxDef(&Edts{}) +} + +// Edts is ISOBMFF edts box type +type Edts struct { +	Box +} + +// GetType returns the BoxType +func (*Edts) GetType() BoxType { +	return BoxTypeEdts() +} + +/*************************** elst ****************************/ + +func BoxTypeElst() BoxType { return StrToBoxType("elst") } + +func init() { +	AddBoxDef(&Elst{}, 0, 1) +} + +// Elst is ISOBMFF elst box type +type Elst struct { +	FullBox    `mp4:"0,extend"` +	EntryCount uint32      `mp4:"1,size=32"` +	Entries    []ElstEntry `mp4:"2,len=dynamic,size=dynamic"` +} + +type ElstEntry struct { +	SegmentDurationV0 uint32 `mp4:"0,size=32,ver=0"` +	MediaTimeV0       int32  `mp4:"1,size=32,ver=0"` +	SegmentDurationV1 uint64 `mp4:"2,size=64,ver=1"` +	MediaTimeV1       int64  `mp4:"3,size=64,ver=1"` +	MediaRateInteger  int16  `mp4:"4,size=16"` +	MediaRateFraction int16  `mp4:"5,size=16,const=0"` +} + +// GetType returns the BoxType +func (*Elst) GetType() BoxType { +	return BoxTypeElst() +} + +// GetFieldSize returns size of dynamic field +func (elst *Elst) GetFieldSize(name string, ctx Context) uint { +	switch name { +	case "Entries": +		switch elst.GetVersion() { +		case 0: +			return 0 + +				/* segmentDurationV0 */ 32 + +				/* mediaTimeV0       */ 32 + +				/* mediaRateInteger  */ 16 + +				/* mediaRateFraction */ 16 +		case 1: +			return 0 + +				/* segmentDurationV1 */ 64 + +				/* mediaTimeV1       */ 64 + +				/* mediaRateInteger  */ 16 + +				/* mediaRateFraction */ 16 +		} +	} +	panic(fmt.Errorf("invalid name of dynamic-size field: boxType=elst fieldName=%s", name)) +} + +// GetFieldLength returns length of dynamic field +func (elst *Elst) GetFieldLength(name string, ctx Context) uint { +	switch name { +	case "Entries": +		return uint(elst.EntryCount) +	} +	panic(fmt.Errorf("invalid name of dynamic-length field: boxType=elst fieldName=%s", name)) +} + +func (elst *Elst) GetSegmentDuration(index int) uint64 { +	switch elst.GetVersion() { +	case 0: +		return uint64(elst.Entries[index].SegmentDurationV0) +	case 1: +		return elst.Entries[index].SegmentDurationV1 +	default: +		return 0 +	} +} + +func (elst *Elst) GetMediaTime(index int) int64 { +	switch elst.GetVersion() { +	case 0: +		return int64(elst.Entries[index].MediaTimeV0) +	case 1: +		return elst.Entries[index].MediaTimeV1 +	default: +		return 0 +	} +} + +/*************************** emsg ****************************/ + +func BoxTypeEmsg() BoxType { return StrToBoxType("emsg") } + +func init() { +	AddBoxDef(&Emsg{}, 0, 1) +} + +// Emsg is ISOBMFF emsg box type +type Emsg struct { +	FullBox               `mp4:"0,extend"` +	SchemeIdUri           string `mp4:"1,string"` +	Value                 string `mp4:"2,string"` +	Timescale             uint32 `mp4:"3,size=32"` +	PresentationTimeDelta uint32 `mp4:"4,size=32,ver=0"` +	PresentationTime      uint64 `mp4:"5,size=64,ver=1"` +	EventDuration         uint32 `mp4:"6,size=32"` +	Id                    uint32 `mp4:"7,size=32"` +	MessageData           []byte `mp4:"8,size=8,string"` +} + +func (emsg *Emsg) OnReadField(name string, r bitio.ReadSeeker, leftBits uint64, ctx Context) (rbits uint64, override bool, err error) { +	if emsg.GetVersion() == 0 { +		return +	} +	switch name { +	case "SchemeIdUri", "Value": +		override = true +		return +	case "MessageData": +		emsg.SchemeIdUri, err = util.ReadString(r) +		if err != nil { +			return +		} +		emsg.Value, err = util.ReadString(r) +		if err != nil { +			return +		} +		rbits += uint64(len(emsg.SchemeIdUri)+len(emsg.Value)+2) * 8 +		return +	default: +		return +	} +} + +func (emsg *Emsg) OnWriteField(name string, w bitio.Writer, ctx Context) (wbits uint64, override bool, err error) { +	if emsg.GetVersion() == 0 { +		return +	} +	switch name { +	case "SchemeIdUri", "Value": +		override = true +		return +	case "MessageData": +		if err = util.WriteString(w, emsg.SchemeIdUri); err != nil { +			return +		} +		if err = util.WriteString(w, emsg.Value); err != nil { +			return +		} +		wbits += uint64(len(emsg.SchemeIdUri)+len(emsg.Value)+2) * 8 +		return +	default: +		return +	} +} + +// GetType returns the BoxType +func (*Emsg) GetType() BoxType { +	return BoxTypeEmsg() +} + +/*************************** esds ****************************/ + +// https://developer.apple.com/library/content/documentation/QuickTime/QTFF/QTFFChap3/qtff3.html + +func BoxTypeEsds() BoxType { return StrToBoxType("esds") } + +func init() { +	AddBoxDef(&Esds{}, 0) +} + +const ( +	ESDescrTag            = 0x03 +	DecoderConfigDescrTag = 0x04 +	DecSpecificInfoTag    = 0x05 +	SLConfigDescrTag      = 0x06 +) + +// Esds is ES descripter box +type Esds struct { +	FullBox     `mp4:"0,extend"` +	Descriptors []Descriptor `mp4:"1,array"` +} + +// GetType returns the BoxType +func (*Esds) GetType() BoxType { +	return BoxTypeEsds() +} + +type Descriptor struct { +	BaseCustomFieldObject +	Tag                     int8                     `mp4:"0,size=8"` // must be 0x03 +	Size                    uint32                   `mp4:"1,varint"` +	ESDescriptor            *ESDescriptor            `mp4:"2,extend,opt=dynamic"` +	DecoderConfigDescriptor *DecoderConfigDescriptor `mp4:"3,extend,opt=dynamic"` +	Data                    []byte                   `mp4:"4,size=8,opt=dynamic,len=dynamic"` +} + +// GetFieldLength returns length of dynamic field +func (ds *Descriptor) GetFieldLength(name string, ctx Context) uint { +	switch name { +	case "Data": +		return uint(ds.Size) +	} +	panic(fmt.Errorf("invalid name of dynamic-length field: boxType=esds fieldName=%s", name)) +} + +func (ds *Descriptor) IsOptFieldEnabled(name string, ctx Context) bool { +	switch ds.Tag { +	case ESDescrTag: +		return name == "ESDescriptor" +	case DecoderConfigDescrTag: +		return name == "DecoderConfigDescriptor" +	default: +		return name == "Data" +	} +} + +// StringifyField returns field value as string +func (ds *Descriptor) StringifyField(name string, indent string, depth int, ctx Context) (string, bool) { +	switch name { +	case "Tag": +		switch ds.Tag { +		case ESDescrTag: +			return "ESDescr", true +		case DecoderConfigDescrTag: +			return "DecoderConfigDescr", true +		case DecSpecificInfoTag: +			return "DecSpecificInfo", true +		case SLConfigDescrTag: +			return "SLConfigDescr", true +		default: +			return "", false +		} +	default: +		return "", false +	} +} + +type ESDescriptor struct { +	BaseCustomFieldObject +	ESID                 uint16 `mp4:"0,size=16"` +	StreamDependenceFlag bool   `mp4:"1,size=1"` +	UrlFlag              bool   `mp4:"2,size=1"` +	OcrStreamFlag        bool   `mp4:"3,size=1"` +	StreamPriority       int8   `mp4:"4,size=5"` +	DependsOnESID        uint16 `mp4:"5,size=16,opt=dynamic"` +	URLLength            uint8  `mp4:"6,size=8,opt=dynamic"` +	URLString            []byte `mp4:"7,size=8,len=dynamic,opt=dynamic,string"` +	OCRESID              uint16 `mp4:"8,size=16,opt=dynamic"` +} + +func (esds *ESDescriptor) GetFieldLength(name string, ctx Context) uint { +	switch name { +	case "URLString": +		return uint(esds.URLLength) +	} +	panic(fmt.Errorf("invalid name of dynamic-length field: boxType=ESDescriptor fieldName=%s", name)) +} + +func (esds *ESDescriptor) IsOptFieldEnabled(name string, ctx Context) bool { +	switch name { +	case "DependsOnESID": +		return esds.StreamDependenceFlag +	case "URLLength", "URLString": +		return esds.UrlFlag +	case "OCRESID": +		return esds.OcrStreamFlag +	default: +		return false +	} +} + +type DecoderConfigDescriptor struct { +	BaseCustomFieldObject +	ObjectTypeIndication byte   `mp4:"0,size=8"` +	StreamType           int8   `mp4:"1,size=6"` +	UpStream             bool   `mp4:"2,size=1"` +	Reserved             bool   `mp4:"3,size=1"` +	BufferSizeDB         uint32 `mp4:"4,size=24"` +	MaxBitrate           uint32 `mp4:"5,size=32"` +	AvgBitrate           uint32 `mp4:"6,size=32"` +} + +/************************ free, skip *************************/ + +func BoxTypeFree() BoxType { return StrToBoxType("free") } +func BoxTypeSkip() BoxType { return StrToBoxType("skip") } + +func init() { +	AddBoxDef(&Free{}) +	AddBoxDef(&Skip{}) +} + +type FreeSpace struct { +	Box +	Data []uint8 `mp4:"0,size=8"` +} + +type Free FreeSpace + +func (*Free) GetType() BoxType { +	return BoxTypeFree() +} + +type Skip FreeSpace + +func (*Skip) GetType() BoxType { +	return BoxTypeSkip() +} + +/*************************** frma ****************************/ + +func BoxTypeFrma() BoxType { return StrToBoxType("frma") } + +func init() { +	AddBoxDef(&Frma{}) +} + +// Frma is ISOBMFF frma box type +type Frma struct { +	Box +	DataFormat [4]byte `mp4:"0,size=8,string"` +} + +// GetType returns the BoxType +func (*Frma) GetType() BoxType { +	return BoxTypeFrma() +} + +/*************************** ftyp ****************************/ + +func BoxTypeFtyp() BoxType { return StrToBoxType("ftyp") } + +func init() { +	AddBoxDef(&Ftyp{}) +} + +func BrandQT() [4]byte   { return [4]byte{'q', 't', ' ', ' '} } +func BrandISOM() [4]byte { return [4]byte{'i', 's', 'o', 'm'} } +func BrandISO2() [4]byte { return [4]byte{'i', 's', 'o', '2'} } +func BrandISO3() [4]byte { return [4]byte{'i', 's', 'o', '3'} } +func BrandISO4() [4]byte { return [4]byte{'i', 's', 'o', '4'} } +func BrandISO5() [4]byte { return [4]byte{'i', 's', 'o', '5'} } +func BrandISO6() [4]byte { return [4]byte{'i', 's', 'o', '6'} } +func BrandISO7() [4]byte { return [4]byte{'i', 's', 'o', '7'} } +func BrandISO8() [4]byte { return [4]byte{'i', 's', 'o', '8'} } +func BrandISO9() [4]byte { return [4]byte{'i', 's', 'o', '9'} } +func BrandAVC1() [4]byte { return [4]byte{'a', 'v', 'c', '1'} } +func BrandMP41() [4]byte { return [4]byte{'m', 'p', '4', '1'} } +func BrandMP71() [4]byte { return [4]byte{'m', 'p', '7', '1'} } + +// Ftyp is ISOBMFF ftyp box type +type Ftyp struct { +	Box +	MajorBrand       [4]byte               `mp4:"0,size=8,string"` +	MinorVersion     uint32                `mp4:"1,size=32"` +	CompatibleBrands []CompatibleBrandElem `mp4:"2,size=32"` // reach to end of the box +} + +type CompatibleBrandElem struct { +	CompatibleBrand [4]byte `mp4:"0,size=8,string"` +} + +func (ftyp *Ftyp) AddCompatibleBrand(cb [4]byte) { +	if !ftyp.HasCompatibleBrand(cb) { +		ftyp.CompatibleBrands = append(ftyp.CompatibleBrands, CompatibleBrandElem{ +			CompatibleBrand: cb, +		}) +	} +} + +func (ftyp *Ftyp) RemoveCompatibleBrand(cb [4]byte) { +	for i := 0; i < len(ftyp.CompatibleBrands); { +		if ftyp.CompatibleBrands[i].CompatibleBrand != cb { +			i++ +			continue +		} +		ftyp.CompatibleBrands[i] = ftyp.CompatibleBrands[len(ftyp.CompatibleBrands)-1] +		ftyp.CompatibleBrands = ftyp.CompatibleBrands[:len(ftyp.CompatibleBrands)-1] +	} +} + +func (ftyp *Ftyp) HasCompatibleBrand(cb [4]byte) bool { +	for i := range ftyp.CompatibleBrands { +		if ftyp.CompatibleBrands[i].CompatibleBrand == cb { +			return true +		} +	} +	return false +} + +// GetType returns the BoxType +func (*Ftyp) GetType() BoxType { +	return BoxTypeFtyp() +} + +/*************************** hdlr ****************************/ + +func BoxTypeHdlr() BoxType { return StrToBoxType("hdlr") } + +func init() { +	AddBoxDef(&Hdlr{}, 0) +} + +// Hdlr is ISOBMFF hdlr box type +type Hdlr struct { +	FullBox `mp4:"0,extend"` +	// Predefined corresponds to component_type of QuickTime. +	// pre_defined of ISO-14496 has always zero, +	// however component_type has "mhlr" or "dhlr". +	PreDefined  uint32    `mp4:"1,size=32"` +	HandlerType [4]byte   `mp4:"2,size=8,string"` +	Reserved    [3]uint32 `mp4:"3,size=32,const=0"` +	Name        string    `mp4:"4,string"` +} + +// GetType returns the BoxType +func (*Hdlr) GetType() BoxType { +	return BoxTypeHdlr() +} + +func (hdlr *Hdlr) OnReadField(name string, r bitio.ReadSeeker, leftBits uint64, ctx Context) (rbits uint64, override bool, err error) { +	switch name { +	case "Name": +		return hdlr.OnReadName(r, leftBits, ctx) +	default: +		return 0, false, nil +	} +} + +func (hdlr *Hdlr) OnReadName(r bitio.ReadSeeker, leftBits uint64, ctx Context) (rbits uint64, override bool, err error) { +	size := leftBits / 8 +	if size == 0 { +		hdlr.Name = "" +		return 0, true, nil +	} + +	buf := make([]byte, size) +	if _, err := io.ReadFull(r, buf); err != nil { +		return 0, false, err +	} + +	plen := buf[0] +	if hdlr.PreDefined != 0 && size >= 2 && size == uint64(plen+1) { +		// Pascal-style String +		hdlr.Name = string(buf[1 : plen+1]) +	} else { +		// C-style String +		clen := 0 +		for _, c := range buf { +			if c == 0x00 { +				break +			} +			clen++ +		} +		hdlr.Name = string(buf[:clen]) +	} +	return leftBits, true, nil +} + +/*************************** ilst ****************************/ + +func BoxTypeIlst() BoxType { return StrToBoxType("ilst") } +func BoxTypeData() BoxType { return StrToBoxType("data") } + +var ilstMetaBoxTypes = []BoxType{ +	StrToBoxType("----"), +	StrToBoxType("aART"), +	StrToBoxType("akID"), +	StrToBoxType("apID"), +	StrToBoxType("atID"), +	StrToBoxType("cmID"), +	StrToBoxType("cnID"), +	StrToBoxType("covr"), +	StrToBoxType("cpil"), +	StrToBoxType("cprt"), +	StrToBoxType("desc"), +	StrToBoxType("disk"), +	StrToBoxType("egid"), +	StrToBoxType("geID"), +	StrToBoxType("gnre"), +	StrToBoxType("pcst"), +	StrToBoxType("pgap"), +	StrToBoxType("plID"), +	StrToBoxType("purd"), +	StrToBoxType("purl"), +	StrToBoxType("rtng"), +	StrToBoxType("sfID"), +	StrToBoxType("soaa"), +	StrToBoxType("soal"), +	StrToBoxType("soar"), +	StrToBoxType("soco"), +	StrToBoxType("sonm"), +	StrToBoxType("sosn"), +	StrToBoxType("stik"), +	StrToBoxType("tmpo"), +	StrToBoxType("trkn"), +	StrToBoxType("tven"), +	StrToBoxType("tves"), +	StrToBoxType("tvnn"), +	StrToBoxType("tvsh"), +	StrToBoxType("tvsn"), +	{0xA9, 'A', 'R', 'T'}, +	{0xA9, 'a', 'l', 'b'}, +	{0xA9, 'c', 'm', 't'}, +	{0xA9, 'c', 'o', 'm'}, +	{0xA9, 'd', 'a', 'y'}, +	{0xA9, 'g', 'e', 'n'}, +	{0xA9, 'g', 'r', 'p'}, +	{0xA9, 'n', 'a', 'm'}, +	{0xA9, 't', 'o', 'o'}, +	{0xA9, 'w', 'r', 't'}, +} + +func IsIlstMetaBoxType(boxType BoxType) bool { +	for _, bt := range ilstMetaBoxTypes { +		if boxType == bt { +			return true +		} +	} +	return false +} + +func init() { +	AddBoxDef(&Ilst{}) +	AddBoxDefEx(&Data{}, isUnderIlstMeta) +	for _, bt := range ilstMetaBoxTypes { +		AddAnyTypeBoxDefEx(&IlstMetaContainer{}, bt, isIlstMetaContainer) +	} +	AddAnyTypeBoxDefEx(&StringData{}, StrToBoxType("mean"), isUnderIlstFreeFormat) +	AddAnyTypeBoxDefEx(&StringData{}, StrToBoxType("name"), isUnderIlstFreeFormat) +} + +type Ilst struct { +	Box +} + +// GetType returns the BoxType +func (*Ilst) GetType() BoxType { +	return BoxTypeIlst() +} + +type IlstMetaContainer struct { +	AnyTypeBox +} + +func isIlstMetaContainer(ctx Context) bool { +	return ctx.UnderIlst && !ctx.UnderIlstMeta +} + +const ( +	DataTypeBinary             = 0 +	DataTypeStringUTF8         = 1 +	DataTypeStringUTF16        = 2 +	DataTypeStringMac          = 3 +	DataTypeStringJPEG         = 14 +	DataTypeSignedIntBigEndian = 21 +	DataTypeFloat32BigEndian   = 22 +	DataTypeFloat64BigEndian   = 23 +) + +type Data struct { +	Box +	DataType uint32 `mp4:"0,size=32"` +	DataLang uint32 `mp4:"1,size=32"` +	Data     []byte `mp4:"2,size=8"` +} + +// GetType returns the BoxType +func (*Data) GetType() BoxType { +	return BoxTypeData() +} + +func isUnderIlstMeta(ctx Context) bool { +	return ctx.UnderIlstMeta +} + +// StringifyField returns field value as string +func (data *Data) StringifyField(name string, indent string, depth int, ctx Context) (string, bool) { +	switch name { +	case "DataType": +		switch data.DataType { +		case DataTypeBinary: +			return "BINARY", true +		case DataTypeStringUTF8: +			return "UTF8", true +		case DataTypeStringUTF16: +			return "UTF16", true +		case DataTypeStringMac: +			return "MAC_STR", true +		case DataTypeStringJPEG: +			return "JPEG", true +		case DataTypeSignedIntBigEndian: +			return "INT", true +		case DataTypeFloat32BigEndian: +			return "FLOAT32", true +		case DataTypeFloat64BigEndian: +			return "FLOAT64", true +		} +	case "Data": +		switch data.DataType { +		case DataTypeStringUTF8: +			return fmt.Sprintf("\"%s\"", util.EscapeUnprintables(string(data.Data))), true +		} +	} +	return "", false +} + +type StringData struct { +	AnyTypeBox +	Data []byte `mp4:"0,size=8"` +} + +// StringifyField returns field value as string +func (sd *StringData) StringifyField(name string, indent string, depth int, ctx Context) (string, bool) { +	if name == "Data" { +		return fmt.Sprintf("\"%s\"", util.EscapeUnprintables(string(sd.Data))), true +	} +	return "", false +} + +func isUnderIlstFreeFormat(ctx Context) bool { +	return ctx.UnderIlstFreeMeta +} + +/*************************** mdat ****************************/ + +func BoxTypeMdat() BoxType { return StrToBoxType("mdat") } + +func init() { +	AddBoxDef(&Mdat{}) +} + +// Mdat is ISOBMFF mdat box type +type Mdat struct { +	Box +	Data []byte `mp4:"0,size=8"` +} + +// GetType returns the BoxType +func (*Mdat) GetType() BoxType { +	return BoxTypeMdat() +} + +/*************************** mdhd ****************************/ + +func BoxTypeMdhd() BoxType { return StrToBoxType("mdhd") } + +func init() { +	AddBoxDef(&Mdhd{}, 0, 1) +} + +// Mdhd is ISOBMFF mdhd box type +type Mdhd struct { +	FullBox            `mp4:"0,extend"` +	CreationTimeV0     uint32 `mp4:"1,size=32,ver=0"` +	ModificationTimeV0 uint32 `mp4:"2,size=32,ver=0"` +	CreationTimeV1     uint64 `mp4:"3,size=64,ver=1"` +	ModificationTimeV1 uint64 `mp4:"4,size=64,ver=1"` +	Timescale          uint32 `mp4:"5,size=32"` +	DurationV0         uint32 `mp4:"6,size=32,ver=0"` +	DurationV1         uint64 `mp4:"7,size=64,ver=1"` +	// +	Pad        bool    `mp4:"8,size=1,hidden"` +	Language   [3]byte `mp4:"9,size=5,iso639-2"` // ISO-639-2/T language code +	PreDefined uint16  `mp4:"10,size=16"` +} + +// GetType returns the BoxType +func (*Mdhd) GetType() BoxType { +	return BoxTypeMdhd() +} + +func (mdhd *Mdhd) GetCreationTime() uint64 { +	switch mdhd.GetVersion() { +	case 0: +		return uint64(mdhd.CreationTimeV0) +	case 1: +		return mdhd.CreationTimeV1 +	default: +		return 0 +	} +} + +func (mdhd *Mdhd) GetModificationTime() uint64 { +	switch mdhd.GetVersion() { +	case 0: +		return uint64(mdhd.ModificationTimeV0) +	case 1: +		return mdhd.ModificationTimeV1 +	default: +		return 0 +	} +} + +func (mdhd *Mdhd) GetDuration() uint64 { +	switch mdhd.GetVersion() { +	case 0: +		return uint64(mdhd.DurationV0) +	case 1: +		return mdhd.DurationV1 +	default: +		return 0 +	} +} + +/*************************** mdia ****************************/ + +func BoxTypeMdia() BoxType { return StrToBoxType("mdia") } + +func init() { +	AddBoxDef(&Mdia{}) +} + +// Mdia is ISOBMFF mdia box type +type Mdia struct { +	Box +} + +// GetType returns the BoxType +func (*Mdia) GetType() BoxType { +	return BoxTypeMdia() +} + +/*************************** mehd ****************************/ + +func BoxTypeMehd() BoxType { return StrToBoxType("mehd") } + +func init() { +	AddBoxDef(&Mehd{}, 0, 1) +} + +// Mehd is ISOBMFF mehd box type +type Mehd struct { +	FullBox            `mp4:"0,extend"` +	FragmentDurationV0 uint32 `mp4:"1,size=32,ver=0"` +	FragmentDurationV1 uint64 `mp4:"2,size=64,ver=1"` +} + +// GetType returns the BoxType +func (*Mehd) GetType() BoxType { +	return BoxTypeMehd() +} + +func (mdhd *Mehd) GetFragmentDuration() uint64 { +	switch mdhd.GetVersion() { +	case 0: +		return uint64(mdhd.FragmentDurationV0) +	case 1: +		return mdhd.FragmentDurationV1 +	default: +		return 0 +	} +} + +/*************************** meta ****************************/ + +func BoxTypeMeta() BoxType { return StrToBoxType("meta") } + +func init() { +	AddBoxDef(&Meta{}, 0) +} + +// Meta is ISOBMFF meta box type +type Meta struct { +	FullBox `mp4:"0,extend"` +} + +// GetType returns the BoxType +func (*Meta) GetType() BoxType { +	return BoxTypeMeta() +} + +func (meta *Meta) BeforeUnmarshal(r io.ReadSeeker, size uint64, ctx Context) (n uint64, override bool, err error) { +	// for Apple Quick Time +	buf := make([]byte, 4) +	if _, err := io.ReadFull(r, buf); err != nil { +		return 0, false, err +	} +	if _, err := r.Seek(-int64(len(buf)), io.SeekCurrent); err != nil { +		return 0, false, err +	} +	if buf[0]|buf[1]|buf[2]|buf[3] != 0x00 { +		meta.Version = 0 +		meta.Flags = [3]byte{0, 0, 0} +		return 0, true, nil +	} +	return 0, false, nil +} + +/*************************** mfhd ****************************/ + +func BoxTypeMfhd() BoxType { return StrToBoxType("mfhd") } + +func init() { +	AddBoxDef(&Mfhd{}, 0) +} + +// Mfhd is ISOBMFF mfhd box type +type Mfhd struct { +	FullBox        `mp4:"0,extend"` +	SequenceNumber uint32 `mp4:"1,size=32"` +} + +// GetType returns the BoxType +func (*Mfhd) GetType() BoxType { +	return BoxTypeMfhd() +} + +/*************************** mfra ****************************/ + +func BoxTypeMfra() BoxType { return StrToBoxType("mfra") } + +func init() { +	AddBoxDef(&Mfra{}) +} + +// Mfra is ISOBMFF mfra box type +type Mfra struct { +	Box +} + +// GetType returns the BoxType +func (*Mfra) GetType() BoxType { +	return BoxTypeMfra() +} + +/*************************** mfro ****************************/ + +func BoxTypeMfro() BoxType { return StrToBoxType("mfro") } + +func init() { +	AddBoxDef(&Mfro{}, 0) +} + +// Mfro is ISOBMFF mfro box type +type Mfro struct { +	FullBox `mp4:"0,extend"` +	Size    uint32 `mp4:"1,size=32"` +} + +// GetType returns the BoxType +func (*Mfro) GetType() BoxType { +	return BoxTypeMfro() +} + +/*************************** minf ****************************/ + +func BoxTypeMinf() BoxType { return StrToBoxType("minf") } + +func init() { +	AddBoxDef(&Minf{}) +} + +// Minf is ISOBMFF minf box type +type Minf struct { +	Box +} + +// GetType returns the BoxType +func (*Minf) GetType() BoxType { +	return BoxTypeMinf() +} + +/*************************** moof ****************************/ + +func BoxTypeMoof() BoxType { return StrToBoxType("moof") } + +func init() { +	AddBoxDef(&Moof{}) +} + +// Moof is ISOBMFF moof box type +type Moof struct { +	Box +} + +// GetType returns the BoxType +func (*Moof) GetType() BoxType { +	return BoxTypeMoof() +} + +/*************************** moov ****************************/ + +func BoxTypeMoov() BoxType { return StrToBoxType("moov") } + +func init() { +	AddBoxDef(&Moov{}) +} + +// Moov is ISOBMFF moov box type +type Moov struct { +	Box +} + +// GetType returns the BoxType +func (*Moov) GetType() BoxType { +	return BoxTypeMoov() +} + +/*************************** mvex ****************************/ + +func BoxTypeMvex() BoxType { return StrToBoxType("mvex") } + +func init() { +	AddBoxDef(&Mvex{}) +} + +// Mvex is ISOBMFF mvex box type +type Mvex struct { +	Box +} + +// GetType returns the BoxType +func (*Mvex) GetType() BoxType { +	return BoxTypeMvex() +} + +/*************************** mvhd ****************************/ + +func BoxTypeMvhd() BoxType { return StrToBoxType("mvhd") } + +func init() { +	AddBoxDef(&Mvhd{}, 0, 1) +} + +// Mvhd is ISOBMFF mvhd box type +type Mvhd struct { +	FullBox            `mp4:"0,extend"` +	CreationTimeV0     uint32    `mp4:"1,size=32,ver=0"` +	ModificationTimeV0 uint32    `mp4:"2,size=32,ver=0"` +	CreationTimeV1     uint64    `mp4:"3,size=64,ver=1"` +	ModificationTimeV1 uint64    `mp4:"4,size=64,ver=1"` +	Timescale          uint32    `mp4:"5,size=32"` +	DurationV0         uint32    `mp4:"6,size=32,ver=0"` +	DurationV1         uint64    `mp4:"7,size=64,ver=1"` +	Rate               int32     `mp4:"8,size=32"` // fixed-point 16.16 - template=0x00010000 +	Volume             int16     `mp4:"9,size=16"` // template=0x0100 +	Reserved           int16     `mp4:"10,size=16,const=0"` +	Reserved2          [2]uint32 `mp4:"11,size=32,const=0"` +	Matrix             [9]int32  `mp4:"12,size=32,hex"` // template={ 0x00010000,0,0,0,0x00010000,0,0,0,0x40000000 } +	PreDefined         [6]int32  `mp4:"13,size=32"` +	NextTrackID        uint32    `mp4:"14,size=32"` +} + +// GetType returns the BoxType +func (*Mvhd) GetType() BoxType { +	return BoxTypeMvhd() +} + +// StringifyField returns field value as string +func (mvhd *Mvhd) StringifyField(name string, indent string, depth int, ctx Context) (string, bool) { +	switch name { +	case "Rate": +		return util.FormatSignedFixedFloat1616(mvhd.Rate), true +	default: +		return "", false +	} +} + +func (mvhd *Mvhd) GetCreationTime() uint64 { +	switch mvhd.GetVersion() { +	case 0: +		return uint64(mvhd.CreationTimeV0) +	case 1: +		return mvhd.CreationTimeV1 +	default: +		return 0 +	} +} + +func (mvhd *Mvhd) GetModificationTime() uint64 { +	switch mvhd.GetVersion() { +	case 0: +		return uint64(mvhd.ModificationTimeV0) +	case 1: +		return mvhd.ModificationTimeV1 +	default: +		return 0 +	} +} + +func (mvhd *Mvhd) GetDuration() uint64 { +	switch mvhd.GetVersion() { +	case 0: +		return uint64(mvhd.DurationV0) +	case 1: +		return mvhd.DurationV1 +	default: +		return 0 +	} +} + +// GetRate returns value of rate as float64 +func (mvhd *Mvhd) GetRate() float64 { +	return float64(mvhd.Rate) / (1 << 16) +} + +// GetRateInt returns value of rate as int16 +func (mvhd *Mvhd) GetRateInt() int16 { +	return int16(mvhd.Rate >> 16) +} + +/*************************** pssh ****************************/ + +func BoxTypePssh() BoxType { return StrToBoxType("pssh") } + +func init() { +	AddBoxDef(&Pssh{}, 0, 1) +} + +// Pssh is ISOBMFF pssh box type +type Pssh struct { +	FullBox  `mp4:"0,extend"` +	SystemID [16]byte  `mp4:"1,size=8,uuid"` +	KIDCount uint32    `mp4:"2,size=32,nver=0"` +	KIDs     []PsshKID `mp4:"3,nver=0,len=dynamic,size=128"` +	DataSize int32     `mp4:"4,size=32"` +	Data     []byte    `mp4:"5,size=8,len=dynamic"` +} + +type PsshKID struct { +	KID [16]byte `mp4:"0,size=8,uuid"` +} + +// GetFieldLength returns length of dynamic field +func (pssh *Pssh) GetFieldLength(name string, ctx Context) uint { +	switch name { +	case "KIDs": +		return uint(pssh.KIDCount) +	case "Data": +		return uint(pssh.DataSize) +	} +	panic(fmt.Errorf("invalid name of dynamic-length field: boxType=pssh fieldName=%s", name)) +} + +// StringifyField returns field value as string +func (pssh *Pssh) StringifyField(name string, indent string, depth int, ctx Context) (string, bool) { +	switch name { +	case "KIDs": +		buf := bytes.NewBuffer(nil) +		buf.WriteString("[") +		for i, e := range pssh.KIDs { +			if i != 0 { +				buf.WriteString(", ") +			} +			buf.WriteString(uuid.UUID(e.KID).String()) +		} +		buf.WriteString("]") +		return buf.String(), true + +	default: +		return "", false +	} +} + +// GetType returns the BoxType +func (*Pssh) GetType() BoxType { +	return BoxTypePssh() +} + +/*************************** saio ****************************/ + +func BoxTypeSaio() BoxType { return StrToBoxType("saio") } + +func init() { +	AddBoxDef(&Saio{}, 0, 1) +} + +type Saio struct { +	FullBox              `mp4:"0,extend"` +	AuxInfoType          [4]byte  `mp4:"1,size=8,opt=0x000001,string"` +	AuxInfoTypeParameter uint32   `mp4:"2,size=32,opt=0x000001,hex"` +	EntryCount           uint32   `mp4:"3,size=32"` +	OffsetV0             []uint32 `mp4:"4,size=32,ver=0,len=dynamic"` +	OffsetV1             []uint64 `mp4:"5,size=64,nver=0,len=dynamic"` +} + +func (saio *Saio) GetFieldLength(name string, ctx Context) uint { +	switch name { +	case "OffsetV0", "OffsetV1": +		return uint(saio.EntryCount) +	} +	panic(fmt.Errorf("invalid name of dynamic-length field: boxType=saio fieldName=%s", name)) +} + +func (*Saio) GetType() BoxType { +	return BoxTypeSaio() +} + +func (saio *Saio) GetOffset(index int) uint64 { +	switch saio.GetVersion() { +	case 0: +		return uint64(saio.OffsetV0[index]) +	case 1: +		return saio.OffsetV1[index] +	default: +		return 0 +	} +} + +/*************************** saiz ****************************/ + +func BoxTypeSaiz() BoxType { return StrToBoxType("saiz") } + +func init() { +	AddBoxDef(&Saiz{}, 0) +} + +type Saiz struct { +	FullBox               `mp4:"0,extend"` +	AuxInfoType           [4]byte `mp4:"1,size=8,opt=0x000001,string"` +	AuxInfoTypeParameter  uint32  `mp4:"2,size=32,opt=0x000001,hex"` +	DefaultSampleInfoSize uint8   `mp4:"3,size=8,dec"` +	SampleCount           uint32  `mp4:"4,size=32"` +	SampleInfoSize        []uint8 `mp4:"5,size=8,opt=dynamic,len=dynamic,dec"` +} + +func (saiz *Saiz) IsOptFieldEnabled(name string, ctx Context) bool { +	switch name { +	case "SampleInfoSize": +		return saiz.DefaultSampleInfoSize == 0 +	} +	return false +} + +func (saiz *Saiz) GetFieldLength(name string, ctx Context) uint { +	switch name { +	case "SampleInfoSize": +		return uint(saiz.SampleCount) +	} +	panic(fmt.Errorf("invalid name of dynamic-length field: boxType=saiz fieldName=%s", name)) +} + +func (*Saiz) GetType() BoxType { +	return BoxTypeSaiz() +} + +/*********************** SampleEntry *************************/ + +func BoxTypeAvc1() BoxType { return StrToBoxType("avc1") } +func BoxTypeEncv() BoxType { return StrToBoxType("encv") } +func BoxTypeMp4a() BoxType { return StrToBoxType("mp4a") } +func BoxTypeEnca() BoxType { return StrToBoxType("enca") } +func BoxTypeAvcC() BoxType { return StrToBoxType("avcC") } +func BoxTypePasp() BoxType { return StrToBoxType("pasp") } + +func init() { +	AddAnyTypeBoxDef(&VisualSampleEntry{}, BoxTypeAvc1()) +	AddAnyTypeBoxDef(&VisualSampleEntry{}, BoxTypeEncv()) +	AddAnyTypeBoxDef(&AudioSampleEntry{}, BoxTypeMp4a()) +	AddAnyTypeBoxDef(&AudioSampleEntry{}, BoxTypeEnca()) +	AddAnyTypeBoxDef(&AVCDecoderConfiguration{}, BoxTypeAvcC()) +	AddAnyTypeBoxDef(&PixelAspectRatioBox{}, BoxTypePasp()) +} + +type SampleEntry struct { +	AnyTypeBox +	Reserved           [6]uint8 `mp4:"0,size=8,const=0"` +	DataReferenceIndex uint16   `mp4:"1,size=16"` +} + +type VisualSampleEntry struct { +	SampleEntry     `mp4:"0,extend"` +	PreDefined      uint16    `mp4:"1,size=16"` +	Reserved        uint16    `mp4:"2,size=16,const=0"` +	PreDefined2     [3]uint32 `mp4:"3,size=32"` +	Width           uint16    `mp4:"4,size=16"` +	Height          uint16    `mp4:"5,size=16"` +	Horizresolution uint32    `mp4:"6,size=32"` +	Vertresolution  uint32    `mp4:"7,size=32"` +	Reserved2       uint32    `mp4:"8,size=32,const=0"` +	FrameCount      uint16    `mp4:"9,size=16"` +	Compressorname  [32]byte  `mp4:"10,size=8"` +	Depth           uint16    `mp4:"11,size=16"` +	PreDefined3     int16     `mp4:"12,size=16"` +} + +// StringifyField returns field value as string +func (vse *VisualSampleEntry) StringifyField(name string, indent string, depth int, ctx Context) (string, bool) { +	switch name { +	case "Compressorname": +		if vse.Compressorname[0] <= 31 { +			return `"` + util.EscapeUnprintables(string(vse.Compressorname[1:vse.Compressorname[0]+1])) + `"`, true +		} +		return "", false +	default: +		return "", false +	} +} + +type AudioSampleEntry struct { +	SampleEntry   `mp4:"0,extend,opt=dynamic"` +	EntryVersion  uint16    `mp4:"1,size=16,opt=dynamic"` +	Reserved      [3]uint16 `mp4:"2,size=16,opt=dynamic,const=0"` +	ChannelCount  uint16    `mp4:"3,size=16,opt=dynamic"` +	SampleSize    uint16    `mp4:"4,size=16,opt=dynamic"` +	PreDefined    uint16    `mp4:"5,size=16,opt=dynamic"` +	Reserved2     uint16    `mp4:"6,size=16,opt=dynamic,const=0"` +	SampleRate    uint32    `mp4:"7,size=32,opt=dynamic"` +	QuickTimeData []byte    `mp4:"8,size=8,opt=dynamic,len=dynamic"` +} + +func (ase *AudioSampleEntry) IsOptFieldEnabled(name string, ctx Context) bool { +	if name == "QuickTimeData" { +		return ctx.IsQuickTimeCompatible && (ctx.UnderWave || ase.EntryVersion == 1 || ase.EntryVersion == 2) +	} +	if ctx.IsQuickTimeCompatible && ctx.UnderWave { +		return false +	} +	return true +} + +func (ase *AudioSampleEntry) GetFieldLength(name string, ctx Context) uint { +	if name == "QuickTimeData" && ctx.IsQuickTimeCompatible { +		if ctx.UnderWave { +			return LengthUnlimited +		} else if ase.EntryVersion == 1 { +			return 16 +		} else if ase.EntryVersion == 2 { +			return 36 +		} +	} +	return 0 +} + +const ( +	AVCBaselineProfile uint8 = 66  // 0x42 +	AVCMainProfile     uint8 = 77  // 0x4d +	AVCExtendedProfile uint8 = 88  // 0x58 +	AVCHighProfile     uint8 = 100 // 0x64 +	AVCHigh10Profile   uint8 = 110 // 0x6e +	AVCHigh422Profile  uint8 = 122 // 0x7a +) + +type AVCDecoderConfiguration struct { +	AnyTypeBox +	ConfigurationVersion         uint8             `mp4:"0,size=8"` +	Profile                      uint8             `mp4:"1,size=8"` +	ProfileCompatibility         uint8             `mp4:"2,size=8"` +	Level                        uint8             `mp4:"3,size=8"` +	Reserved                     uint8             `mp4:"4,size=6,const=63"` +	LengthSizeMinusOne           uint8             `mp4:"5,size=2"` +	Reserved2                    uint8             `mp4:"6,size=3,const=7"` +	NumOfSequenceParameterSets   uint8             `mp4:"7,size=5"` +	SequenceParameterSets        []AVCParameterSet `mp4:"8,len=dynamic"` +	NumOfPictureParameterSets    uint8             `mp4:"9,size=8"` +	PictureParameterSets         []AVCParameterSet `mp4:"10,len=dynamic"` +	HighProfileFieldsEnabled     bool              `mp4:"11,hidden"` +	Reserved3                    uint8             `mp4:"12,size=6,opt=dynamic,const=63"` +	ChromaFormat                 uint8             `mp4:"13,size=2,opt=dynamic"` +	Reserved4                    uint8             `mp4:"14,size=5,opt=dynamic,const=31"` +	BitDepthLumaMinus8           uint8             `mp4:"15,size=3,opt=dynamic"` +	Reserved5                    uint8             `mp4:"16,size=5,opt=dynamic,const=31"` +	BitDepthChromaMinus8         uint8             `mp4:"17,size=3,opt=dynamic"` +	NumOfSequenceParameterSetExt uint8             `mp4:"18,size=8,opt=dynamic"` +	SequenceParameterSetsExt     []AVCParameterSet `mp4:"19,len=dynamic,opt=dynamic"` +} + +func (avcc *AVCDecoderConfiguration) GetFieldLength(name string, ctx Context) uint { +	switch name { +	case "SequenceParameterSets": +		return uint(avcc.NumOfSequenceParameterSets) +	case "PictureParameterSets": +		return uint(avcc.NumOfPictureParameterSets) +	case "SequenceParameterSetsExt": +		return uint(avcc.NumOfSequenceParameterSetExt) +	} +	return 0 +} + +func (avcc *AVCDecoderConfiguration) IsOptFieldEnabled(name string, ctx Context) bool { +	switch name { +	case "Reserved3", +		"ChromaFormat", +		"Reserved4", +		"BitDepthLumaMinus8", +		"Reserved5", +		"BitDepthChromaMinus8", +		"NumOfSequenceParameterSetExt", +		"SequenceParameterSetsExt": +		return avcc.HighProfileFieldsEnabled +	} +	return false +} + +func (avcc *AVCDecoderConfiguration) OnReadField(name string, r bitio.ReadSeeker, leftBits uint64, ctx Context) (rbits uint64, override bool, err error) { +	if name == "HighProfileFieldsEnabled" { +		avcc.HighProfileFieldsEnabled = leftBits >= 32 && +			(avcc.Profile == AVCHighProfile || +				avcc.Profile == AVCHigh10Profile || +				avcc.Profile == AVCHigh422Profile || +				avcc.Profile == 144) +		return 0, true, nil +	} +	return 0, false, nil +} + +func (avcc *AVCDecoderConfiguration) OnWriteField(name string, w bitio.Writer, ctx Context) (wbits uint64, override bool, err error) { +	if name == "HighProfileFieldsEnabled" { +		if avcc.HighProfileFieldsEnabled && +			avcc.Profile != AVCHighProfile && +			avcc.Profile != AVCHigh10Profile && +			avcc.Profile != AVCHigh422Profile && +			avcc.Profile != 144 { +			return 0, false, errors.New("each values of Profile and HighProfileFieldsEnabled are inconsistent") +		} +		return 0, true, nil +	} +	return 0, false, nil +} + +type AVCParameterSet struct { +	BaseCustomFieldObject +	Length  uint16 `mp4:"0,size=16"` +	NALUnit []byte `mp4:"1,size=8,len=dynamic"` +} + +func (s *AVCParameterSet) GetFieldLength(name string, ctx Context) uint { +	switch name { +	case "NALUnit": +		return uint(s.Length) +	} +	return 0 +} + +type PixelAspectRatioBox struct { +	AnyTypeBox +	HSpacing uint32 `mp4:"0,size=32"` +	VSpacing uint32 `mp4:"1,size=32"` +} + +/*************************** sbgp ****************************/ + +func BoxTypeSbgp() BoxType { return StrToBoxType("sbgp") } + +func init() { +	AddBoxDef(&Sbgp{}, 0, 1) +} + +type Sbgp struct { +	FullBox               `mp4:"0,extend"` +	GroupingType          uint32      `mp4:"1,size=32"` +	GroupingTypeParameter uint32      `mp4:"2,size=32,ver=1"` +	EntryCount            uint32      `mp4:"3,size=32"` +	Entries               []SbgpEntry `mp4:"4,len=dynamic,size=64"` +} + +type SbgpEntry struct { +	SampleCount           uint32 `mp4:"0,size=32"` +	GroupDescriptionIndex uint32 `mp4:"1,size=32"` +} + +func (sbgp *Sbgp) GetFieldLength(name string, ctx Context) uint { +	switch name { +	case "Entries": +		return uint(sbgp.EntryCount) +	} +	panic(fmt.Errorf("invalid name of dynamic-length field: boxType=sbgp fieldName=%s", name)) +} + +func (*Sbgp) GetType() BoxType { +	return BoxTypeSbgp() +} + +/*************************** schi ****************************/ + +func BoxTypeSchi() BoxType { return StrToBoxType("schi") } + +func init() { +	AddBoxDef(&Schi{}) +} + +type Schi struct { +	Box +} + +func (*Schi) GetType() BoxType { +	return BoxTypeSchi() +} + +/*************************** schm ****************************/ + +func BoxTypeSchm() BoxType { return StrToBoxType("schm") } + +func init() { +	AddBoxDef(&Schm{}, 0) +} + +type Schm struct { +	FullBox       `mp4:"0,extend"` +	SchemeType    [4]byte `mp4:"1,size=8,string"` +	SchemeVersion uint32  `mp4:"2,size=32,hex"` +	SchemeUri     []byte  `mp4:"3,size=8,opt=0x000001,string"` +} + +func (*Schm) GetType() BoxType { +	return BoxTypeSchm() +} + +/*************************** sdtp ****************************/ + +func BoxTypeSdtp() BoxType { return StrToBoxType("sdtp") } + +func init() { +	AddBoxDef(&Sdtp{}, 0) +} + +type Sdtp struct { +	FullBox `mp4:"0,extend"` +	Samples []SdtpSampleElem `mp4:"1,size=8"` +} + +type SdtpSampleElem struct { +	IsLeading           uint8 `mp4:"0,size=2"` +	SampleDependsOn     uint8 `mp4:"1,size=2"` +	SampleIsDependedOn  uint8 `mp4:"2,size=2"` +	SampleHasRedundancy uint8 `mp4:"3,size=2"` +} + +func (*Sdtp) GetType() BoxType { +	return BoxTypeSdtp() +} + +/*************************** sgpd ****************************/ + +func BoxTypeSgpd() BoxType { return StrToBoxType("sgpd") } + +func init() { +	AddBoxDef(&Sgpd{}, 1, 2) // version 0 is deprecated by ISO/IEC 14496-12 +} + +type Sgpd struct { +	FullBox                       `mp4:"0,extend"` +	GroupingType                  [4]byte                    `mp4:"1,size=8,string"` +	DefaultLength                 uint32                     `mp4:"2,size=32,ver=1"` +	DefaultSampleDescriptionIndex uint32                     `mp4:"3,size=32,ver=2"` +	EntryCount                    uint32                     `mp4:"4,size=32"` +	RollDistances                 []int16                    `mp4:"5,size=16,opt=dynamic"` +	RollDistancesL                []RollDistanceWithLength   `mp4:"6,size=16,opt=dynamic"` +	AlternativeStartupEntries     []AlternativeStartupEntry  `mp4:"7,size=dynamic,len=dynamic,opt=dynamic"` +	AlternativeStartupEntriesL    []AlternativeStartupEntryL `mp4:"8,len=dynamic,opt=dynamic"` +	VisualRandomAccessEntries     []VisualRandomAccessEntry  `mp4:"9,len=dynamic,opt=dynamic"` +	VisualRandomAccessEntriesL    []VisualRandomAccessEntryL `mp4:"10,len=dynamic,opt=dynamic"` +	TemporalLevelEntries          []TemporalLevelEntry       `mp4:"11,len=dynamic,opt=dynamic"` +	TemporalLevelEntriesL         []TemporalLevelEntryL      `mp4:"12,len=dynamic,opt=dynamic"` +	Unsupported                   []byte                     `mp4:"13,size=8,opt=dynamic"` +} + +type RollDistanceWithLength struct { +	DescriptionLength uint32 `mp4:"0,size=32"` +	RollDistance      int16  `mp4:"1,size=16"` +} + +type AlternativeStartupEntry struct { +	BaseCustomFieldObject +	RollCount         uint16                       `mp4:"0,size=16"` +	FirstOutputSample uint16                       `mp4:"1,size=16"` +	SampleOffset      []uint32                     `mp4:"2,size=32,len=dynamic"` +	Opts              []AlternativeStartupEntryOpt `mp4:"3,size=32"` +} + +type AlternativeStartupEntryL struct { +	DescriptionLength       uint32 `mp4:"0,size=32"` +	AlternativeStartupEntry `mp4:"1,extend,size=dynamic"` +} + +type AlternativeStartupEntryOpt struct { +	NumOutputSamples uint16 `mp4:"0,size=16"` +	NumTotalSamples  uint16 `mp4:"1,size=16"` +} + +type VisualRandomAccessEntry struct { +	NumLeadingSamplesKnown bool  `mp4:"0,size=1"` +	NumLeadingSamples      uint8 `mp4:"1,size=7"` +} + +type VisualRandomAccessEntryL struct { +	DescriptionLength       uint32 `mp4:"0,size=32"` +	VisualRandomAccessEntry `mp4:"1,extend"` +} + +type TemporalLevelEntry struct { +	LevelIndependentlyDecodable bool  `mp4:"0,size=1"` +	Reserved                    uint8 `mp4:"1,size=7,const=0"` +} + +type TemporalLevelEntryL struct { +	DescriptionLength  uint32 `mp4:"0,size=32"` +	TemporalLevelEntry `mp4:"1,extend"` +} + +func (sgpd *Sgpd) GetFieldSize(name string, ctx Context) uint { +	switch name { +	case "AlternativeStartupEntries": +		return uint(sgpd.DefaultLength * 8) +	} +	return 0 +} + +func (sgpd *Sgpd) GetFieldLength(name string, ctx Context) uint { +	switch name { +	case "RollDistances", "RollDistancesL", +		"AlternativeStartupEntries", "AlternativeStartupEntriesL", +		"VisualRandomAccessEntries", "VisualRandomAccessEntriesL", +		"TemporalLevelEntries", "TemporalLevelEntriesL": +		return uint(sgpd.EntryCount) +	} +	return 0 +} + +func (sgpd *Sgpd) IsOptFieldEnabled(name string, ctx Context) bool { +	noDefaultLength := sgpd.Version == 1 && sgpd.DefaultLength == 0 +	rollDistances := sgpd.GroupingType == [4]byte{'r', 'o', 'l', 'l'} || +		sgpd.GroupingType == [4]byte{'p', 'r', 'o', 'l'} +	alternativeStartupEntries := sgpd.GroupingType == [4]byte{'a', 'l', 's', 't'} +	visualRandomAccessEntries := sgpd.GroupingType == [4]byte{'r', 'a', 'p', ' '} +	temporalLevelEntries := sgpd.GroupingType == [4]byte{'t', 'e', 'l', 'e'} +	switch name { +	case "RollDistances": +		return rollDistances && !noDefaultLength +	case "RollDistancesL": +		return rollDistances && noDefaultLength +	case "AlternativeStartupEntries": +		return alternativeStartupEntries && !noDefaultLength +	case "AlternativeStartupEntriesL": +		return alternativeStartupEntries && noDefaultLength +	case "VisualRandomAccessEntries": +		return visualRandomAccessEntries && !noDefaultLength +	case "VisualRandomAccessEntriesL": +		return visualRandomAccessEntries && noDefaultLength +	case "TemporalLevelEntries": +		return temporalLevelEntries && !noDefaultLength +	case "TemporalLevelEntriesL": +		return temporalLevelEntries && noDefaultLength +	case "Unsupported": +		return !rollDistances && +			!alternativeStartupEntries && +			!visualRandomAccessEntries && +			!temporalLevelEntries +	default: +		return false +	} +} + +func (*Sgpd) GetType() BoxType { +	return BoxTypeSgpd() +} + +func (entry *AlternativeStartupEntry) GetFieldLength(name string, ctx Context) uint { +	switch name { +	case "SampleOffset": +		return uint(entry.RollCount) +	} +	return 0 +} + +func (entry *AlternativeStartupEntryL) GetFieldSize(name string, ctx Context) uint { +	switch name { +	case "AlternativeStartupEntry": +		return uint(entry.DescriptionLength * 8) +	} +	return 0 +} + +/*************************** sidx ****************************/ + +func BoxTypeSidx() BoxType { return StrToBoxType("sidx") } + +func init() { +	AddBoxDef(&Sidx{}, 0, 1) +} + +type Sidx struct { +	FullBox                    `mp4:"0,extend"` +	ReferenceID                uint32          `mp4:"1,size=32"` +	Timescale                  uint32          `mp4:"2,size=32"` +	EarliestPresentationTimeV0 uint32          `mp4:"3,size=32,ver=0"` +	FirstOffsetV0              uint32          `mp4:"4,size=32,ver=0"` +	EarliestPresentationTimeV1 uint64          `mp4:"5,size=64,nver=0"` +	FirstOffsetV1              uint64          `mp4:"6,size=64,nver=0"` +	Reserved                   uint16          `mp4:"7,size=16,const=0"` +	ReferenceCount             uint16          `mp4:"8,size=16"` +	References                 []SidxReference `mp4:"9,size=96,len=dynamic"` +} + +type SidxReference struct { +	ReferenceType      bool   `mp4:"0,size=1"` +	ReferencedSize     uint32 `mp4:"1,size=31"` +	SubsegmentDuration uint32 `mp4:"2,size=32"` +	StartsWithSAP      bool   `mp4:"3,size=1"` +	SAPType            uint32 `mp4:"4,size=3"` +	SAPDeltaTime       uint32 `mp4:"5,size=28"` +} + +func (*Sidx) GetType() BoxType { +	return BoxTypeSidx() +} + +func (sidx *Sidx) GetFieldLength(name string, ctx Context) uint { +	switch name { +	case "References": +		return uint(sidx.ReferenceCount) +	} +	panic(fmt.Errorf("invalid name of dynamic-length field: boxType=sidx fieldName=%s", name)) +} + +func (sidx *Sidx) GetEarliestPresentationTime() uint64 { +	switch sidx.GetVersion() { +	case 0: +		return uint64(sidx.EarliestPresentationTimeV0) +	case 1: +		return sidx.EarliestPresentationTimeV1 +	default: +		return 0 +	} +} + +func (sidx *Sidx) GetFirstOffset() uint64 { +	switch sidx.GetVersion() { +	case 0: +		return uint64(sidx.FirstOffsetV0) +	case 1: +		return sidx.FirstOffsetV1 +	default: +		return 0 +	} +} + +/*************************** sinf ****************************/ + +func BoxTypeSinf() BoxType { return StrToBoxType("sinf") } + +func init() { +	AddBoxDef(&Sinf{}) +} + +type Sinf struct { +	Box +} + +func (*Sinf) GetType() BoxType { +	return BoxTypeSinf() +} + +/*************************** smhd ****************************/ + +func BoxTypeSmhd() BoxType { return StrToBoxType("smhd") } + +func init() { +	AddBoxDef(&Smhd{}, 0) +} + +type Smhd struct { +	FullBox  `mp4:"0,extend"` +	Balance  int16  `mp4:"1,size=16"` // fixed-point 8.8 template=0 +	Reserved uint16 `mp4:"2,size=16,const=0"` +} + +func (*Smhd) GetType() BoxType { +	return BoxTypeSmhd() +} + +// StringifyField returns field value as string +func (smhd *Smhd) StringifyField(name string, indent string, depth int, ctx Context) (string, bool) { +	switch name { +	case "Balance": +		return util.FormatSignedFixedFloat88(smhd.Balance), true +	default: +		return "", false +	} +} + +// GetBalance returns value of width as float32 +func (smhd *Smhd) GetBalance() float32 { +	return float32(smhd.Balance) / (1 << 8) +} + +// GetBalanceInt returns value of width as int8 +func (smhd *Smhd) GetBalanceInt() int8 { +	return int8(smhd.Balance >> 8) +} + +/*************************** stbl ****************************/ + +func BoxTypeStbl() BoxType { return StrToBoxType("stbl") } + +func init() { +	AddBoxDef(&Stbl{}) +} + +// Stbl is ISOBMFF stbl box type +type Stbl struct { +	Box +} + +// GetType returns the BoxType +func (*Stbl) GetType() BoxType { +	return BoxTypeStbl() +} + +/*************************** stco ****************************/ + +func BoxTypeStco() BoxType { return StrToBoxType("stco") } + +func init() { +	AddBoxDef(&Stco{}, 0) +} + +// Stco is ISOBMFF stco box type +type Stco struct { +	FullBox     `mp4:"0,extend"` +	EntryCount  uint32   `mp4:"1,size=32"` +	ChunkOffset []uint32 `mp4:"2,size=32,len=dynamic"` +} + +// GetType returns the BoxType +func (*Stco) GetType() BoxType { +	return BoxTypeStco() +} + +// GetFieldLength returns length of dynamic field +func (stco *Stco) GetFieldLength(name string, ctx Context) uint { +	switch name { +	case "ChunkOffset": +		return uint(stco.EntryCount) +	} +	panic(fmt.Errorf("invalid name of dynamic-length field: boxType=stco fieldName=%s", name)) +} + +/*************************** stsc ****************************/ + +func BoxTypeStsc() BoxType { return StrToBoxType("stsc") } + +func init() { +	AddBoxDef(&Stsc{}, 0) +} + +// Stsc is ISOBMFF stsc box type +type Stsc struct { +	FullBox    `mp4:"0,extend"` +	EntryCount uint32      `mp4:"1,size=32"` +	Entries    []StscEntry `mp4:"2,len=dynamic,size=96"` +} + +type StscEntry struct { +	FirstChunk             uint32 `mp4:"0,size=32"` +	SamplesPerChunk        uint32 `mp4:"1,size=32"` +	SampleDescriptionIndex uint32 `mp4:"2,size=32"` +} + +// GetType returns the BoxType +func (*Stsc) GetType() BoxType { +	return BoxTypeStsc() +} + +// GetFieldLength returns length of dynamic field +func (stsc *Stsc) GetFieldLength(name string, ctx Context) uint { +	switch name { +	case "Entries": +		return uint(stsc.EntryCount) +	} +	panic(fmt.Errorf("invalid name of dynamic-length field: boxType=stsc fieldName=%s", name)) +} + +/*************************** stsd ****************************/ + +func BoxTypeStsd() BoxType { return StrToBoxType("stsd") } + +func init() { +	AddBoxDef(&Stsd{}, 0) +} + +// Stsd is ISOBMFF stsd box type +type Stsd struct { +	FullBox    `mp4:"0,extend"` +	EntryCount uint32 `mp4:"1,size=32"` +} + +// GetType returns the BoxType +func (*Stsd) GetType() BoxType { +	return BoxTypeStsd() +} + +/*************************** stss ****************************/ + +func BoxTypeStss() BoxType { return StrToBoxType("stss") } + +func init() { +	AddBoxDef(&Stss{}, 0) +} + +type Stss struct { +	FullBox      `mp4:"0,extend"` +	EntryCount   uint32   `mp4:"1,size=32"` +	SampleNumber []uint32 `mp4:"2,len=dynamic,size=32"` +} + +// GetType returns the BoxType +func (*Stss) GetType() BoxType { +	return BoxTypeStss() +} + +// GetFieldLength returns length of dynamic field +func (stss *Stss) GetFieldLength(name string, ctx Context) uint { +	switch name { +	case "SampleNumber": +		return uint(stss.EntryCount) +	} +	panic(fmt.Errorf("invalid name of dynamic-length field: boxType=stss fieldName=%s", name)) +} + +/*************************** stsz ****************************/ + +func BoxTypeStsz() BoxType { return StrToBoxType("stsz") } + +func init() { +	AddBoxDef(&Stsz{}, 0) +} + +// Stsz is ISOBMFF stsz box type +type Stsz struct { +	FullBox     `mp4:"0,extend"` +	SampleSize  uint32   `mp4:"1,size=32"` +	SampleCount uint32   `mp4:"2,size=32"` +	EntrySize   []uint32 `mp4:"3,size=32,len=dynamic"` +} + +// GetType returns the BoxType +func (*Stsz) GetType() BoxType { +	return BoxTypeStsz() +} + +// GetFieldLength returns length of dynamic field +func (stsz *Stsz) GetFieldLength(name string, ctx Context) uint { +	switch name { +	case "EntrySize": +		if stsz.SampleSize == 0 { +			return uint(stsz.SampleCount) +		} +		return 0 +	} +	panic(fmt.Errorf("invalid name of dynamic-length field: boxType=stsz fieldName=%s", name)) +} + +/*************************** stts ****************************/ + +func BoxTypeStts() BoxType { return StrToBoxType("stts") } + +func init() { +	AddBoxDef(&Stts{}, 0) +} + +// Stts is ISOBMFF stts box type +type Stts struct { +	FullBox    `mp4:"0,extend"` +	EntryCount uint32      `mp4:"1,size=32"` +	Entries    []SttsEntry `mp4:"2,len=dynamic,size=64"` +} + +type SttsEntry struct { +	SampleCount uint32 `mp4:"0,size=32"` +	SampleDelta uint32 `mp4:"1,size=32"` +} + +// GetType returns the BoxType +func (*Stts) GetType() BoxType { +	return BoxTypeStts() +} + +// GetFieldLength returns length of dynamic field +func (stts *Stts) GetFieldLength(name string, ctx Context) uint { +	switch name { +	case "Entries": +		return uint(stts.EntryCount) +	} +	panic(fmt.Errorf("invalid name of dynamic-length field: boxType=stts fieldName=%s", name)) +} + +/*************************** styp ****************************/ + +func BoxTypeStyp() BoxType { return StrToBoxType("styp") } + +func init() { +	AddBoxDef(&Styp{}) +} + +type Styp struct { +	Box +	MajorBrand       [4]byte               `mp4:"0,size=8,string"` +	MinorVersion     uint32                `mp4:"1,size=32"` +	CompatibleBrands []CompatibleBrandElem `mp4:"2,size=32"` // reach to end of the box +} + +func (*Styp) GetType() BoxType { +	return BoxTypeStyp() +} + +/*************************** tenc ****************************/ + +func BoxTypeTenc() BoxType { return StrToBoxType("tenc") } + +func init() { +	AddBoxDef(&Tenc{}, 0, 1) +} + +// Tenc is ISOBMFF tenc box type +type Tenc struct { +	FullBox                `mp4:"0,extend"` +	Reserved               uint8    `mp4:"1,size=8,dec"` +	DefaultCryptByteBlock  uint8    `mp4:"2,size=4,dec"` // always 0 on version 0 +	DefaultSkipByteBlock   uint8    `mp4:"3,size=4,dec"` // always 0 on version 0 +	DefaultIsProtected     uint8    `mp4:"4,size=8,dec"` +	DefaultPerSampleIVSize uint8    `mp4:"5,size=8,dec"` +	DefaultKID             [16]byte `mp4:"6,size=8,uuid"` +	DefaultConstantIVSize  uint8    `mp4:"7,size=8,opt=dynamic,dec"` +	DefaultConstantIV      []byte   `mp4:"8,size=8,opt=dynamic,len=dynamic"` +} + +func (tenc *Tenc) IsOptFieldEnabled(name string, ctx Context) bool { +	switch name { +	case "DefaultConstantIVSize", "DefaultConstantIV": +		return tenc.DefaultIsProtected == 1 && tenc.DefaultPerSampleIVSize == 0 +	} +	return false +} + +func (tenc *Tenc) GetFieldLength(name string, ctx Context) uint { +	switch name { +	case "DefaultConstantIV": +		return uint(tenc.DefaultConstantIVSize) +	} +	panic(fmt.Errorf("invalid name of dynamic-length field: boxType=tenc fieldName=%s", name)) +} + +// GetType returns the BoxType +func (*Tenc) GetType() BoxType { +	return BoxTypeTenc() +} + +/*************************** tfdt ****************************/ + +func BoxTypeTfdt() BoxType { return StrToBoxType("tfdt") } + +func init() { +	AddBoxDef(&Tfdt{}, 0, 1) +} + +// Tfdt is ISOBMFF tfdt box type +type Tfdt struct { +	FullBox               `mp4:"0,extend"` +	BaseMediaDecodeTimeV0 uint32 `mp4:"1,size=32,ver=0"` +	BaseMediaDecodeTimeV1 uint64 `mp4:"2,size=64,ver=1"` +} + +// GetType returns the BoxType +func (*Tfdt) GetType() BoxType { +	return BoxTypeTfdt() +} + +func (tfdt *Tfdt) GetBaseMediaDecodeTime() uint64 { +	switch tfdt.GetVersion() { +	case 0: +		return uint64(tfdt.BaseMediaDecodeTimeV0) +	case 1: +		return tfdt.BaseMediaDecodeTimeV1 +	default: +		return 0 +	} +} + +/*************************** tfhd ****************************/ + +func BoxTypeTfhd() BoxType { return StrToBoxType("tfhd") } + +func init() { +	AddBoxDef(&Tfhd{}, 0) +} + +// Tfhd is ISOBMFF tfhd box type +type Tfhd struct { +	FullBox `mp4:"0,extend"` +	TrackID uint32 `mp4:"1,size=32"` + +	// optional +	BaseDataOffset         uint64 `mp4:"2,size=64,opt=0x000001"` +	SampleDescriptionIndex uint32 `mp4:"3,size=32,opt=0x000002"` +	DefaultSampleDuration  uint32 `mp4:"4,size=32,opt=0x000008"` +	DefaultSampleSize      uint32 `mp4:"5,size=32,opt=0x000010"` +	DefaultSampleFlags     uint32 `mp4:"6,size=32,opt=0x000020,hex"` +} + +const ( +	TfhdBaseDataOffsetPresent         = 0x000001 +	TfhdSampleDescriptionIndexPresent = 0x000002 +	TfhdDefaultSampleDurationPresent  = 0x000008 +	TfhdDefaultSampleSizePresent      = 0x000010 +	TfhdDefaultSampleFlagsPresent     = 0x000020 +	TfhdDurationIsEmpty               = 0x010000 +	TfhdDefaultBaseIsMoof             = 0x020000 +) + +// GetType returns the BoxType +func (*Tfhd) GetType() BoxType { +	return BoxTypeTfhd() +} + +/*************************** tfra ****************************/ + +func BoxTypeTfra() BoxType { return StrToBoxType("tfra") } + +func init() { +	AddBoxDef(&Tfra{}, 0, 1) +} + +// Tfra is ISOBMFF tfra box type +type Tfra struct { +	FullBox               `mp4:"0,extend"` +	TrackID               uint32      `mp4:"1,size=32"` +	Reserved              uint32      `mp4:"2,size=26,const=0"` +	LengthSizeOfTrafNum   byte        `mp4:"3,size=2"` +	LengthSizeOfTrunNum   byte        `mp4:"4,size=2"` +	LengthSizeOfSampleNum byte        `mp4:"5,size=2"` +	NumberOfEntry         uint32      `mp4:"6,size=32"` +	Entries               []TfraEntry `mp4:"7,len=dynamic,size=dynamic"` +} + +type TfraEntry struct { +	TimeV0       uint32 `mp4:"0,size=32,ver=0"` +	MoofOffsetV0 uint32 `mp4:"1,size=32,ver=0"` +	TimeV1       uint64 `mp4:"2,size=64,ver=1"` +	MoofOffsetV1 uint64 `mp4:"3,size=64,ver=1"` +	TrafNumber   uint32 `mp4:"4,size=dynamic"` +	TrunNumber   uint32 `mp4:"5,size=dynamic"` +	SampleNumber uint32 `mp4:"6,size=dynamic"` +} + +// GetType returns the BoxType +func (*Tfra) GetType() BoxType { +	return BoxTypeTfra() +} + +// GetFieldSize returns size of dynamic field +func (tfra *Tfra) GetFieldSize(name string, ctx Context) uint { +	switch name { +	case "TrafNumber": +		return (uint(tfra.LengthSizeOfTrafNum) + 1) * 8 +	case "TrunNumber": +		return (uint(tfra.LengthSizeOfTrunNum) + 1) * 8 +	case "SampleNumber": +		return (uint(tfra.LengthSizeOfSampleNum) + 1) * 8 +	case "Entries": +		switch tfra.GetVersion() { +		case 0: +			return 0 + +				/* TimeV0       */ 32 + +				/* MoofOffsetV0 */ 32 + +				/* TrafNumber   */ (uint(tfra.LengthSizeOfTrafNum)+1)*8 + +				/* TrunNumber   */ (uint(tfra.LengthSizeOfTrunNum)+1)*8 + +				/* SampleNumber */ (uint(tfra.LengthSizeOfSampleNum)+1)*8 +		case 1: +			return 0 + +				/* TimeV1       */ 64 + +				/* MoofOffsetV1 */ 64 + +				/* TrafNumber   */ (uint(tfra.LengthSizeOfTrafNum)+1)*8 + +				/* TrunNumber   */ (uint(tfra.LengthSizeOfTrunNum)+1)*8 + +				/* SampleNumber */ (uint(tfra.LengthSizeOfSampleNum)+1)*8 +		} +	} +	panic(fmt.Errorf("invalid name of dynamic-size field: boxType=tfra fieldName=%s", name)) +} + +// GetFieldLength returns length of dynamic field +func (tfra *Tfra) GetFieldLength(name string, ctx Context) uint { +	switch name { +	case "Entries": +		return uint(tfra.NumberOfEntry) +	} +	panic(fmt.Errorf("invalid name of dynamic-length field: boxType=tfra fieldName=%s", name)) +} + +func (tfra *Tfra) GetTime(index int) uint64 { +	switch tfra.GetVersion() { +	case 0: +		return uint64(tfra.Entries[index].TimeV0) +	case 1: +		return tfra.Entries[index].TimeV1 +	default: +		return 0 +	} +} + +func (tfra *Tfra) GetMoofOffset(index int) uint64 { +	switch tfra.GetVersion() { +	case 0: +		return uint64(tfra.Entries[index].MoofOffsetV0) +	case 1: +		return tfra.Entries[index].MoofOffsetV1 +	default: +		return 0 +	} +} + +/*************************** tkhd ****************************/ + +func BoxTypeTkhd() BoxType { return StrToBoxType("tkhd") } + +func init() { +	AddBoxDef(&Tkhd{}, 0, 1) +} + +// Tkhd is ISOBMFF tkhd box type +type Tkhd struct { +	FullBox            `mp4:"0,extend"` +	CreationTimeV0     uint32 `mp4:"1,size=32,ver=0"` +	ModificationTimeV0 uint32 `mp4:"2,size=32,ver=0"` +	CreationTimeV1     uint64 `mp4:"3,size=64,ver=1"` +	ModificationTimeV1 uint64 `mp4:"4,size=64,ver=1"` +	TrackID            uint32 `mp4:"5,size=32"` +	Reserved0          uint32 `mp4:"6,size=32,const=0"` +	DurationV0         uint32 `mp4:"7,size=32,ver=0"` +	DurationV1         uint64 `mp4:"8,size=64,ver=1"` +	// +	Reserved1      [2]uint32 `mp4:"9,size=32,const=0"` +	Layer          int16     `mp4:"10,size=16"` // template=0 +	AlternateGroup int16     `mp4:"11,size=16"` // template=0 +	Volume         int16     `mp4:"12,size=16"` // template={if track_is_audio 0x0100 else 0} +	Reserved2      uint16    `mp4:"13,size=16,const=0"` +	Matrix         [9]int32  `mp4:"14,size=32,hex"` // template={ 0x00010000,0,0,0,0x00010000,0,0,0,0x40000000 }; +	Width          uint32    `mp4:"15,size=32"`     // fixed-point 16.16 +	Height         uint32    `mp4:"16,size=32"`     // fixed-point 16.16 +} + +// GetType returns the BoxType +func (*Tkhd) GetType() BoxType { +	return BoxTypeTkhd() +} + +// StringifyField returns field value as string +func (tkhd *Tkhd) StringifyField(name string, indent string, depth int, ctx Context) (string, bool) { +	switch name { +	case "Width": +		return util.FormatUnsignedFixedFloat1616(tkhd.Width), true +	case "Height": +		return util.FormatUnsignedFixedFloat1616(tkhd.Height), true +	default: +		return "", false +	} +} + +func (tkhd *Tkhd) GetCreationTime() uint64 { +	switch tkhd.GetVersion() { +	case 0: +		return uint64(tkhd.CreationTimeV0) +	case 1: +		return tkhd.CreationTimeV1 +	default: +		return 0 +	} +} + +func (tkhd *Tkhd) GetModificationTime() uint64 { +	switch tkhd.GetVersion() { +	case 0: +		return uint64(tkhd.ModificationTimeV0) +	case 1: +		return tkhd.ModificationTimeV1 +	default: +		return 0 +	} +} + +func (tkhd *Tkhd) GetDuration() uint64 { +	switch tkhd.GetVersion() { +	case 0: +		return uint64(tkhd.DurationV0) +	case 1: +		return tkhd.DurationV1 +	default: +		return 0 +	} +} + +// GetWidth returns value of width as float64 +func (tkhd *Tkhd) GetWidth() float64 { +	return float64(tkhd.Width) / (1 << 16) +} + +// GetWidthInt returns value of width as uint16 +func (tkhd *Tkhd) GetWidthInt() uint16 { +	return uint16(tkhd.Width >> 16) +} + +// GetHeight returns value of height as float64 +func (tkhd *Tkhd) GetHeight() float64 { +	return float64(tkhd.Height) / (1 << 16) +} + +// GetHeightInt returns value of height as uint16 +func (tkhd *Tkhd) GetHeightInt() uint16 { +	return uint16(tkhd.Height >> 16) +} + +/*************************** traf ****************************/ + +func BoxTypeTraf() BoxType { return StrToBoxType("traf") } + +func init() { +	AddBoxDef(&Traf{}) +} + +// Traf is ISOBMFF traf box type +type Traf struct { +	Box +} + +// GetType returns the BoxType +func (*Traf) GetType() BoxType { +	return BoxTypeTraf() +} + +/*************************** trak ****************************/ + +func BoxTypeTrak() BoxType { return StrToBoxType("trak") } + +func init() { +	AddBoxDef(&Trak{}) +} + +// Trak is ISOBMFF trak box type +type Trak struct { +	Box +} + +// GetType returns the BoxType +func (*Trak) GetType() BoxType { +	return BoxTypeTrak() +} + +/*************************** trep ****************************/ + +func BoxTypeTrep() BoxType { return StrToBoxType("trep") } + +func init() { +	AddBoxDef(&Trep{}, 0) +} + +// Trep is ISOBMFF trep box type +type Trep struct { +	FullBox `mp4:"0,extend"` +	TrackID uint32 `mp4:"1,size=32"` +} + +// GetType returns the BoxType +func (*Trep) GetType() BoxType { +	return BoxTypeTrep() +} + +/*************************** trex ****************************/ + +func BoxTypeTrex() BoxType { return StrToBoxType("trex") } + +func init() { +	AddBoxDef(&Trex{}, 0) +} + +// Trex is ISOBMFF trex box type +type Trex struct { +	FullBox                       `mp4:"0,extend"` +	TrackID                       uint32 `mp4:"1,size=32"` +	DefaultSampleDescriptionIndex uint32 `mp4:"2,size=32"` +	DefaultSampleDuration         uint32 `mp4:"3,size=32"` +	DefaultSampleSize             uint32 `mp4:"4,size=32"` +	DefaultSampleFlags            uint32 `mp4:"5,size=32,hex"` +} + +// GetType returns the BoxType +func (*Trex) GetType() BoxType { +	return BoxTypeTrex() +} + +/*************************** trun ****************************/ + +func BoxTypeTrun() BoxType { return StrToBoxType("trun") } + +func init() { +	AddBoxDef(&Trun{}, 0, 1) +} + +// Trun is ISOBMFF trun box type +type Trun struct { +	FullBox     `mp4:"0,extend"` +	SampleCount uint32 `mp4:"1,size=32"` + +	// optional fields +	DataOffset       int32       `mp4:"2,size=32,opt=0x000001"` +	FirstSampleFlags uint32      `mp4:"3,size=32,opt=0x000004,hex"` +	Entries          []TrunEntry `mp4:"4,len=dynamic,size=dynamic"` +} + +type TrunEntry struct { +	SampleDuration                uint32 `mp4:"0,size=32,opt=0x000100"` +	SampleSize                    uint32 `mp4:"1,size=32,opt=0x000200"` +	SampleFlags                   uint32 `mp4:"2,size=32,opt=0x000400,hex"` +	SampleCompositionTimeOffsetV0 uint32 `mp4:"3,size=32,opt=0x000800,ver=0"` +	SampleCompositionTimeOffsetV1 int32  `mp4:"4,size=32,opt=0x000800,nver=0"` +} + +// GetType returns the BoxType +func (*Trun) GetType() BoxType { +	return BoxTypeTrun() +} + +// GetFieldSize returns size of dynamic field +func (trun *Trun) GetFieldSize(name string, ctx Context) uint { +	switch name { +	case "Entries": +		var size uint +		flags := trun.GetFlags() +		if flags&0x100 != 0 { +			size += 32 // SampleDuration +		} +		if flags&0x200 != 0 { +			size += 32 // SampleSize +		} +		if flags&0x400 != 0 { +			size += 32 // SampleFlags +		} +		if flags&0x800 != 0 { +			size += 32 // SampleCompositionTimeOffsetV0 or V1 +		} +		return size +	} +	panic(fmt.Errorf("invalid name of dynamic-size field: boxType=trun fieldName=%s", name)) +} + +// GetFieldLength returns length of dynamic field +func (trun *Trun) GetFieldLength(name string, ctx Context) uint { +	switch name { +	case "Entries": +		return uint(trun.SampleCount) +	} +	panic(fmt.Errorf("invalid name of dynamic-length field: boxType=trun fieldName=%s", name)) +} + +func (trun *Trun) GetSampleCompositionTimeOffset(index int) int64 { +	switch trun.GetVersion() { +	case 0: +		return int64(trun.Entries[index].SampleCompositionTimeOffsetV0) +	case 1: +		return int64(trun.Entries[index].SampleCompositionTimeOffsetV1) +	default: +		return 0 +	} +} + +/*************************** udta ****************************/ + +func BoxTypeUdta() BoxType { return StrToBoxType("udta") } + +var udta3GppMetaBoxTypes = []BoxType{ +	StrToBoxType("titl"), +	StrToBoxType("dscp"), +	StrToBoxType("cprt"), +	StrToBoxType("perf"), +	StrToBoxType("auth"), +	StrToBoxType("gnre"), +} + +func init() { +	AddBoxDef(&Udta{}) +	for _, bt := range udta3GppMetaBoxTypes { +		AddAnyTypeBoxDefEx(&Udta3GppString{}, bt, isUnderUdta, 0) +	} +} + +// Udta is ISOBMFF udta box type +type Udta struct { +	Box +} + +// GetType returns the BoxType +func (*Udta) GetType() BoxType { +	return BoxTypeUdta() +} + +type Udta3GppString struct { +	AnyTypeBox +	FullBox  `mp4:"0,extend"` +	Pad      bool    `mp4:"1,size=1,hidden"` +	Language [3]byte `mp4:"2,size=5,iso639-2"` // ISO-639-2/T language code +	Data     []byte  `mp4:"3,size=8,string"` +} + +func isUnderUdta(ctx Context) bool { +	return ctx.UnderUdta +} + +/*************************** vmhd ****************************/ + +func BoxTypeVmhd() BoxType { return StrToBoxType("vmhd") } + +func init() { +	AddBoxDef(&Vmhd{}, 0) +} + +// Vmhd is ISOBMFF vmhd box type +type Vmhd struct { +	FullBox      `mp4:"0,extend"` +	Graphicsmode uint16    `mp4:"1,size=16"` // template=0 +	Opcolor      [3]uint16 `mp4:"2,size=16"` // template={0, 0, 0} +} + +// GetType returns the BoxType +func (*Vmhd) GetType() BoxType { +	return BoxTypeVmhd() +} + +/*************************** wave ****************************/ + +func BoxTypeWave() BoxType { return StrToBoxType("wave") } + +func init() { +	AddBoxDef(&Wave{}) +} + +// Wave is QuickTime wave box +type Wave struct { +	Box +} + +// GetType returns the BoxType +func (*Wave) GetType() BoxType { +	return BoxTypeWave() +} diff --git a/vendor/github.com/abema/go-mp4/extract.go b/vendor/github.com/abema/go-mp4/extract.go new file mode 100644 index 000000000..7de36b06a --- /dev/null +++ b/vendor/github.com/abema/go-mp4/extract.go @@ -0,0 +1,98 @@ +package mp4 + +import ( +	"errors" +	"io" +) + +type BoxInfoWithPayload struct { +	Info    BoxInfo +	Payload IBox +} + +func ExtractBoxWithPayload(r io.ReadSeeker, parent *BoxInfo, path BoxPath) ([]*BoxInfoWithPayload, error) { +	return ExtractBoxesWithPayload(r, parent, []BoxPath{path}) +} + +func ExtractBoxesWithPayload(r io.ReadSeeker, parent *BoxInfo, paths []BoxPath) ([]*BoxInfoWithPayload, error) { +	bis, err := ExtractBoxes(r, parent, paths) +	if err != nil { +		return nil, err +	} + +	bs := make([]*BoxInfoWithPayload, 0, len(bis)) +	for _, bi := range bis { +		if _, err := bi.SeekToPayload(r); err != nil { +			return nil, err +		} + +		var ctx Context +		if parent != nil { +			ctx = parent.Context +		} +		box, _, err := UnmarshalAny(r, bi.Type, bi.Size-bi.HeaderSize, ctx) +		if err != nil { +			return nil, err +		} +		bs = append(bs, &BoxInfoWithPayload{ +			Info:    *bi, +			Payload: box, +		}) +	} +	return bs, nil +} + +func ExtractBox(r io.ReadSeeker, parent *BoxInfo, path BoxPath) ([]*BoxInfo, error) { +	return ExtractBoxes(r, parent, []BoxPath{path}) +} + +func ExtractBoxes(r io.ReadSeeker, parent *BoxInfo, paths []BoxPath) ([]*BoxInfo, error) { +	if len(paths) == 0 { +		return nil, nil +	} + +	for i := range paths { +		if len(paths[i]) == 0 { +			return nil, errors.New("box path must not be empty") +		} +	} + +	boxes := make([]*BoxInfo, 0, 8) + +	handler := func(handle *ReadHandle) (interface{}, error) { +		path := handle.Path +		if parent != nil { +			path = path[1:] +		} +		if handle.BoxInfo.Type == BoxTypeAny() { +			return nil, nil +		} +		fm, m := matchPath(paths, path) +		if m { +			boxes = append(boxes, &handle.BoxInfo) +		} + +		if fm { +			if _, err := handle.Expand(); err != nil { +				return nil, err +			} +		} +		return nil, nil +	} + +	if parent != nil { +		_, err := ReadBoxStructureFromInternal(r, parent, handler) +		return boxes, err +	} +	_, err := ReadBoxStructure(r, handler) +	return boxes, err +} + +func matchPath(paths []BoxPath, path BoxPath) (forwardMatch bool, match bool) { +	for i := range paths { +		fm, m := path.compareWith(paths[i]) +		forwardMatch = forwardMatch || fm +		match = match || m +	} +	return +} diff --git a/vendor/github.com/abema/go-mp4/field.go b/vendor/github.com/abema/go-mp4/field.go new file mode 100644 index 000000000..585833e0d --- /dev/null +++ b/vendor/github.com/abema/go-mp4/field.go @@ -0,0 +1,290 @@ +package mp4 + +import ( +	"fmt" +	"os" +	"reflect" +	"sort" +	"strconv" +	"strings" +) + +type ( +	stringType uint8 +	fieldFlag  uint16 +) + +const ( +	stringType_C stringType = iota +	stringType_C_P + +	fieldString        fieldFlag = 1 << iota // 0 +	fieldExtend                              // 1 +	fieldDec                                 // 2 +	fieldHex                                 // 3 +	fieldISO639_2                            // 4 +	fieldUUID                                // 5 +	fieldHidden                              // 6 +	fieldOptDynamic                          // 7 +	fieldVarint                              // 8 +	fieldSizeDynamic                         // 9 +	fieldLengthDynamic                       // 10 +) + +type field struct { +	children []*field +	name     string +	cnst     string +	order    int +	optFlag  uint32 +	nOptFlag uint32 +	size     uint +	length   uint +	flags    fieldFlag +	strType  stringType +	version  uint8 +	nVersion uint8 +} + +func (f *field) set(flag fieldFlag) { +	f.flags |= flag +} + +func (f *field) is(flag fieldFlag) bool { +	return f.flags&flag != 0 +} + +func buildFields(box IImmutableBox) []*field { +	t := reflect.TypeOf(box).Elem() +	return buildFieldsStruct(t) +} + +func buildFieldsStruct(t reflect.Type) []*field { +	fs := make([]*field, 0, 8) +	for i := 0; i < t.NumField(); i++ { +		ft := t.Field(i).Type +		tag, ok := t.Field(i).Tag.Lookup("mp4") +		if !ok { +			continue +		} +		f := buildField(t.Field(i).Name, tag) +		f.children = buildFieldsAny(ft) +		fs = append(fs, f) +	} +	sort.SliceStable(fs, func(i, j int) bool { +		return fs[i].order < fs[j].order +	}) +	return fs +} + +func buildFieldsAny(t reflect.Type) []*field { +	switch t.Kind() { +	case reflect.Struct: +		return buildFieldsStruct(t) +	case reflect.Ptr, reflect.Array, reflect.Slice: +		return buildFieldsAny(t.Elem()) +	default: +		return nil +	} +} + +func buildField(fieldName string, tag string) *field { +	f := &field{ +		name: fieldName, +	} +	tagMap := parseFieldTag(tag) +	for key, val := range tagMap { +		if val != "" { +			continue +		} +		if order, err := strconv.Atoi(key); err == nil { +			f.order = order +			break +		} +	} + +	if val, contained := tagMap["string"]; contained { +		f.set(fieldString) +		if val == "c_p" { +			f.strType = stringType_C_P +			fmt.Fprint(os.Stderr, "go-mp4: string=c_p tag is deprecated!! See https://github.com/abema/go-mp4/issues/76\n") +		} +	} + +	if _, contained := tagMap["varint"]; contained { +		f.set(fieldVarint) +	} + +	if val, contained := tagMap["opt"]; contained { +		if val == "dynamic" { +			f.set(fieldOptDynamic) +		} else { +			base := 10 +			if strings.HasPrefix(val, "0x") { +				val = val[2:] +				base = 16 +			} +			opt, err := strconv.ParseUint(val, base, 32) +			if err != nil { +				panic(err) +			} +			f.optFlag = uint32(opt) +		} +	} + +	if val, contained := tagMap["nopt"]; contained { +		base := 10 +		if strings.HasPrefix(val, "0x") { +			val = val[2:] +			base = 16 +		} +		nopt, err := strconv.ParseUint(val, base, 32) +		if err != nil { +			panic(err) +		} +		f.nOptFlag = uint32(nopt) +	} + +	if _, contained := tagMap["extend"]; contained { +		f.set(fieldExtend) +	} + +	if _, contained := tagMap["dec"]; contained { +		f.set(fieldDec) +	} + +	if _, contained := tagMap["hex"]; contained { +		f.set(fieldHex) +	} + +	if _, contained := tagMap["iso639-2"]; contained { +		f.set(fieldISO639_2) +	} + +	if _, contained := tagMap["uuid"]; contained { +		f.set(fieldUUID) +	} + +	if _, contained := tagMap["hidden"]; contained { +		f.set(fieldHidden) +	} + +	if val, contained := tagMap["const"]; contained { +		f.cnst = val +	} + +	f.version = anyVersion +	if val, contained := tagMap["ver"]; contained { +		ver, err := strconv.Atoi(val) +		if err != nil { +			panic(err) +		} +		f.version = uint8(ver) +	} + +	f.nVersion = anyVersion +	if val, contained := tagMap["nver"]; contained { +		ver, err := strconv.Atoi(val) +		if err != nil { +			panic(err) +		} +		f.nVersion = uint8(ver) +	} + +	if val, contained := tagMap["size"]; contained { +		if val == "dynamic" { +			f.set(fieldSizeDynamic) +		} else { +			size, err := strconv.ParseUint(val, 10, 32) +			if err != nil { +				panic(err) +			} +			f.size = uint(size) +		} +	} + +	f.length = LengthUnlimited +	if val, contained := tagMap["len"]; contained { +		if val == "dynamic" { +			f.set(fieldLengthDynamic) +		} else { +			l, err := strconv.ParseUint(val, 10, 32) +			if err != nil { +				panic(err) +			} +			f.length = uint(l) +		} +	} + +	return f +} + +func parseFieldTag(str string) map[string]string { +	tag := make(map[string]string, 8) + +	list := strings.Split(str, ",") +	for _, e := range list { +		kv := strings.SplitN(e, "=", 2) +		if len(kv) == 2 { +			tag[strings.Trim(kv[0], " ")] = strings.Trim(kv[1], " ") +		} else { +			tag[strings.Trim(kv[0], " ")] = "" +		} +	} + +	return tag +} + +type fieldInstance struct { +	field +	cfo ICustomFieldObject +} + +func resolveFieldInstance(f *field, box IImmutableBox, parent reflect.Value, ctx Context) *fieldInstance { +	fi := fieldInstance{ +		field: *f, +	} + +	cfo, ok := parent.Addr().Interface().(ICustomFieldObject) +	if ok { +		fi.cfo = cfo +	} else { +		fi.cfo = box +	} + +	if fi.is(fieldSizeDynamic) { +		fi.size = fi.cfo.GetFieldSize(f.name, ctx) +	} + +	if fi.is(fieldLengthDynamic) { +		fi.length = fi.cfo.GetFieldLength(f.name, ctx) +	} + +	return &fi +} + +func isTargetField(box IImmutableBox, fi *fieldInstance, ctx Context) bool { +	if box.GetVersion() != anyVersion { +		if fi.version != anyVersion && box.GetVersion() != fi.version { +			return false +		} + +		if fi.nVersion != anyVersion && box.GetVersion() == fi.nVersion { +			return false +		} +	} + +	if fi.optFlag != 0 && box.GetFlags()&fi.optFlag == 0 { +		return false +	} + +	if fi.nOptFlag != 0 && box.GetFlags()&fi.nOptFlag != 0 { +		return false +	} + +	if fi.is(fieldOptDynamic) && !fi.cfo.IsOptFieldEnabled(fi.name, ctx) { +		return false +	} + +	return true +} diff --git a/vendor/github.com/abema/go-mp4/marshaller.go b/vendor/github.com/abema/go-mp4/marshaller.go new file mode 100644 index 000000000..b5c66860b --- /dev/null +++ b/vendor/github.com/abema/go-mp4/marshaller.go @@ -0,0 +1,639 @@ +package mp4 + +import ( +	"bytes" +	"errors" +	"fmt" +	"io" +	"math" +	"reflect" + +	"github.com/abema/go-mp4/bitio" +) + +const ( +	anyVersion = math.MaxUint8 +) + +var ErrUnsupportedBoxVersion = errors.New("unsupported box version") + +type marshaller struct { +	writer bitio.Writer +	wbits  uint64 +	src    IImmutableBox +	ctx    Context +} + +func Marshal(w io.Writer, src IImmutableBox, ctx Context) (n uint64, err error) { +	boxDef := src.GetType().getBoxDef(ctx) +	if boxDef == nil { +		return 0, ErrBoxInfoNotFound +	} + +	v := reflect.ValueOf(src).Elem() + +	m := &marshaller{ +		writer: bitio.NewWriter(w), +		src:    src, +		ctx:    ctx, +	} + +	if err := m.marshalStruct(v, boxDef.fields); err != nil { +		return 0, err +	} + +	if m.wbits%8 != 0 { +		return 0, fmt.Errorf("box size is not multiple of 8 bits: type=%s, bits=%d", src.GetType().String(), m.wbits) +	} + +	return m.wbits / 8, nil +} + +func (m *marshaller) marshal(v reflect.Value, fi *fieldInstance) error { +	switch v.Type().Kind() { +	case reflect.Ptr: +		return m.marshalPtr(v, fi) +	case reflect.Struct: +		return m.marshalStruct(v, fi.children) +	case reflect.Array: +		return m.marshalArray(v, fi) +	case reflect.Slice: +		return m.marshalSlice(v, fi) +	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: +		return m.marshalInt(v, fi) +	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: +		return m.marshalUint(v, fi) +	case reflect.Bool: +		return m.marshalBool(v, fi) +	case reflect.String: +		return m.marshalString(v) +	default: +		return fmt.Errorf("unsupported type: %s", v.Type().Kind()) +	} +} + +func (m *marshaller) marshalPtr(v reflect.Value, fi *fieldInstance) error { +	return m.marshal(v.Elem(), fi) +} + +func (m *marshaller) marshalStruct(v reflect.Value, fs []*field) error { +	for _, f := range fs { +		fi := resolveFieldInstance(f, m.src, v, m.ctx) + +		if !isTargetField(m.src, fi, m.ctx) { +			continue +		} + +		wbits, override, err := fi.cfo.OnWriteField(f.name, m.writer, m.ctx) +		if err != nil { +			return err +		} +		m.wbits += wbits +		if override { +			continue +		} + +		err = m.marshal(v.FieldByName(f.name), fi) +		if err != nil { +			return err +		} +	} + +	return nil +} + +func (m *marshaller) marshalArray(v reflect.Value, fi *fieldInstance) error { +	size := v.Type().Size() +	for i := 0; i < int(size)/int(v.Type().Elem().Size()); i++ { +		var err error +		err = m.marshal(v.Index(i), fi) +		if err != nil { +			return err +		} +	} +	return nil +} + +func (m *marshaller) marshalSlice(v reflect.Value, fi *fieldInstance) error { +	length := uint64(v.Len()) +	if fi.length != LengthUnlimited { +		if length < uint64(fi.length) { +			return fmt.Errorf("the slice has too few elements: required=%d actual=%d", fi.length, length) +		} +		length = uint64(fi.length) +	} + +	elemType := v.Type().Elem() +	if elemType.Kind() == reflect.Uint8 && fi.size == 8 && m.wbits%8 == 0 { +		if _, err := io.CopyN(m.writer, bytes.NewBuffer(v.Bytes()), int64(length)); err != nil { +			return err +		} +		m.wbits += length * 8 +		return nil +	} + +	for i := 0; i < int(length); i++ { +		m.marshal(v.Index(i), fi) +	} +	return nil +} + +func (m *marshaller) marshalInt(v reflect.Value, fi *fieldInstance) error { +	signed := v.Int() + +	if fi.is(fieldVarint) { +		return errors.New("signed varint is unsupported") +	} + +	signBit := signed < 0 +	val := uint64(signed) +	for i := uint(0); i < fi.size; i += 8 { +		v := val +		size := uint(8) +		if fi.size > i+8 { +			v = v >> (fi.size - (i + 8)) +		} else if fi.size < i+8 { +			size = fi.size - i +		} + +		// set sign bit +		if i == 0 { +			if signBit { +				v |= 0x1 << (size - 1) +			} else { +				v &= 0x1<<(size-1) - 1 +			} +		} + +		if err := m.writer.WriteBits([]byte{byte(v)}, size); err != nil { +			return err +		} +		m.wbits += uint64(size) +	} + +	return nil +} + +func (m *marshaller) marshalUint(v reflect.Value, fi *fieldInstance) error { +	val := v.Uint() + +	if fi.is(fieldVarint) { +		m.writeUvarint(val) +		return nil +	} + +	for i := uint(0); i < fi.size; i += 8 { +		v := val +		size := uint(8) +		if fi.size > i+8 { +			v = v >> (fi.size - (i + 8)) +		} else if fi.size < i+8 { +			size = fi.size - i +		} +		if err := m.writer.WriteBits([]byte{byte(v)}, size); err != nil { +			return err +		} +		m.wbits += uint64(size) +	} + +	return nil +} + +func (m *marshaller) marshalBool(v reflect.Value, fi *fieldInstance) error { +	var val byte +	if v.Bool() { +		val = 0xff +	} else { +		val = 0x00 +	} +	if err := m.writer.WriteBits([]byte{val}, fi.size); err != nil { +		return err +	} +	m.wbits += uint64(fi.size) +	return nil +} + +func (m *marshaller) marshalString(v reflect.Value) error { +	data := []byte(v.String()) +	for _, b := range data { +		if err := m.writer.WriteBits([]byte{b}, 8); err != nil { +			return err +		} +		m.wbits += 8 +	} +	// null character +	if err := m.writer.WriteBits([]byte{0x00}, 8); err != nil { +		return err +	} +	m.wbits += 8 +	return nil +} + +func (m *marshaller) writeUvarint(u uint64) error { +	for i := 21; i > 0; i -= 7 { +		if err := m.writer.WriteBits([]byte{(byte(u >> uint(i))) | 0x80}, 8); err != nil { +			return err +		} +		m.wbits += 8 +	} + +	if err := m.writer.WriteBits([]byte{byte(u) & 0x7f}, 8); err != nil { +		return err +	} +	m.wbits += 8 + +	return nil +} + +type unmarshaller struct { +	reader bitio.ReadSeeker +	dst    IBox +	size   uint64 +	rbits  uint64 +	ctx    Context +} + +func UnmarshalAny(r io.ReadSeeker, boxType BoxType, payloadSize uint64, ctx Context) (box IBox, n uint64, err error) { +	dst, err := boxType.New(ctx) +	if err != nil { +		return nil, 0, err +	} +	n, err = Unmarshal(r, payloadSize, dst, ctx) +	return dst, n, err +} + +func Unmarshal(r io.ReadSeeker, payloadSize uint64, dst IBox, ctx Context) (n uint64, err error) { +	boxDef := dst.GetType().getBoxDef(ctx) +	if boxDef == nil { +		return 0, ErrBoxInfoNotFound +	} + +	v := reflect.ValueOf(dst).Elem() + +	dst.SetVersion(anyVersion) + +	u := &unmarshaller{ +		reader: bitio.NewReadSeeker(r), +		dst:    dst, +		size:   payloadSize, +		ctx:    ctx, +	} + +	if n, override, err := dst.BeforeUnmarshal(r, payloadSize, u.ctx); err != nil { +		return 0, err +	} else if override { +		return n, nil +	} else { +		u.rbits = n * 8 +	} + +	sn, err := r.Seek(0, io.SeekCurrent) +	if err != nil { +		return 0, err +	} + +	if err := u.unmarshalStruct(v, boxDef.fields); err != nil { +		if err == ErrUnsupportedBoxVersion { +			r.Seek(sn, io.SeekStart) +		} +		return 0, err +	} + +	if u.rbits%8 != 0 { +		return 0, fmt.Errorf("box size is not multiple of 8 bits: type=%s, size=%d, bits=%d", dst.GetType().String(), u.size, u.rbits) +	} + +	if u.rbits > u.size*8 { +		return 0, fmt.Errorf("overrun error: type=%s, size=%d, bits=%d", dst.GetType().String(), u.size, u.rbits) +	} + +	return u.rbits / 8, nil +} + +func (u *unmarshaller) unmarshal(v reflect.Value, fi *fieldInstance) error { +	var err error +	switch v.Type().Kind() { +	case reflect.Ptr: +		err = u.unmarshalPtr(v, fi) +	case reflect.Struct: +		err = u.unmarshalStructInternal(v, fi) +	case reflect.Array: +		err = u.unmarshalArray(v, fi) +	case reflect.Slice: +		err = u.unmarshalSlice(v, fi) +	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: +		err = u.unmarshalInt(v, fi) +	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: +		err = u.unmarshalUint(v, fi) +	case reflect.Bool: +		err = u.unmarshalBool(v, fi) +	case reflect.String: +		err = u.unmarshalString(v, fi) +	default: +		return fmt.Errorf("unsupported type: %s", v.Type().Kind()) +	} +	return err +} + +func (u *unmarshaller) unmarshalPtr(v reflect.Value, fi *fieldInstance) error { +	v.Set(reflect.New(v.Type().Elem())) +	return u.unmarshal(v.Elem(), fi) +} + +func (u *unmarshaller) unmarshalStructInternal(v reflect.Value, fi *fieldInstance) error { +	if fi.size != 0 && fi.size%8 == 0 { +		u2 := *u +		u2.size = uint64(fi.size / 8) +		u2.rbits = 0 +		if err := u2.unmarshalStruct(v, fi.children); err != nil { +			return err +		} +		u.rbits += u2.rbits +		if u2.rbits != uint64(fi.size) { +			return errors.New("invalid alignment") +		} +		return nil +	} + +	return u.unmarshalStruct(v, fi.children) +} + +func (u *unmarshaller) unmarshalStruct(v reflect.Value, fs []*field) error { +	for _, f := range fs { +		fi := resolveFieldInstance(f, u.dst, v, u.ctx) + +		if !isTargetField(u.dst, fi, u.ctx) { +			continue +		} + +		rbits, override, err := fi.cfo.OnReadField(f.name, u.reader, u.size*8-u.rbits, u.ctx) +		if err != nil { +			return err +		} +		u.rbits += rbits +		if override { +			continue +		} + +		err = u.unmarshal(v.FieldByName(f.name), fi) +		if err != nil { +			return err +		} + +		if v.FieldByName(f.name).Type() == reflect.TypeOf(FullBox{}) && !u.dst.GetType().IsSupportedVersion(u.dst.GetVersion(), u.ctx) { +			return ErrUnsupportedBoxVersion +		} +	} + +	return nil +} + +func (u *unmarshaller) unmarshalArray(v reflect.Value, fi *fieldInstance) error { +	size := v.Type().Size() +	for i := 0; i < int(size)/int(v.Type().Elem().Size()); i++ { +		var err error +		err = u.unmarshal(v.Index(i), fi) +		if err != nil { +			return err +		} +	} +	return nil +} + +func (u *unmarshaller) unmarshalSlice(v reflect.Value, fi *fieldInstance) error { +	var slice reflect.Value +	elemType := v.Type().Elem() + +	length := uint64(fi.length) +	if fi.length == LengthUnlimited { +		if fi.size != 0 { +			left := (u.size)*8 - u.rbits +			if left%uint64(fi.size) != 0 { +				return errors.New("invalid alignment") +			} +			length = left / uint64(fi.size) +		} else { +			length = 0 +		} +	} + +	if length > math.MaxInt32 { +		return fmt.Errorf("out of memory: requestedSize=%d", length) +	} + +	if fi.size != 0 && fi.size%8 == 0 && u.rbits%8 == 0 && elemType.Kind() == reflect.Uint8 && fi.size == 8 { +		totalSize := length * uint64(fi.size) / 8 +		buf := bytes.NewBuffer(make([]byte, 0, totalSize)) +		if _, err := io.CopyN(buf, u.reader, int64(totalSize)); err != nil { +			return err +		} +		slice = reflect.ValueOf(buf.Bytes()) +		u.rbits += uint64(totalSize) * 8 + +	} else { +		slice = reflect.MakeSlice(v.Type(), 0, int(length)) +		for i := 0; ; i++ { +			if fi.length != LengthUnlimited && uint(i) >= fi.length { +				break +			} +			if fi.length == LengthUnlimited && u.rbits >= u.size*8 { +				break +			} +			slice = reflect.Append(slice, reflect.Zero(elemType)) +			if err := u.unmarshal(slice.Index(i), fi); err != nil { +				return err +			} +			if u.rbits > u.size*8 { +				return fmt.Errorf("failed to read array completely: fieldName=\"%s\"", fi.name) +			} +		} +	} + +	v.Set(slice) +	return nil +} + +func (u *unmarshaller) unmarshalInt(v reflect.Value, fi *fieldInstance) error { +	if fi.is(fieldVarint) { +		return errors.New("signed varint is unsupported") +	} + +	if fi.size == 0 { +		return fmt.Errorf("size must not be zero: %s", fi.name) +	} + +	data, err := u.reader.ReadBits(fi.size) +	if err != nil { +		return err +	} +	u.rbits += uint64(fi.size) + +	signBit := false +	if len(data) > 0 { +		signMask := byte(0x01) << ((fi.size - 1) % 8) +		signBit = data[0]&signMask != 0 +		if signBit { +			data[0] |= ^(signMask - 1) +		} +	} + +	var val uint64 +	if signBit { +		val = ^uint64(0) +	} +	for i := range data { +		val <<= 8 +		val |= uint64(data[i]) +	} +	v.SetInt(int64(val)) +	return nil +} + +func (u *unmarshaller) unmarshalUint(v reflect.Value, fi *fieldInstance) error { +	if fi.is(fieldVarint) { +		val, err := u.readUvarint() +		if err != nil { +			return err +		} +		v.SetUint(val) +		return nil +	} + +	if fi.size == 0 { +		return fmt.Errorf("size must not be zero: %s", fi.name) +	} + +	data, err := u.reader.ReadBits(fi.size) +	if err != nil { +		return err +	} +	u.rbits += uint64(fi.size) + +	val := uint64(0) +	for i := range data { +		val <<= 8 +		val |= uint64(data[i]) +	} +	v.SetUint(val) + +	return nil +} + +func (u *unmarshaller) unmarshalBool(v reflect.Value, fi *fieldInstance) error { +	if fi.size == 0 { +		return fmt.Errorf("size must not be zero: %s", fi.name) +	} + +	data, err := u.reader.ReadBits(fi.size) +	if err != nil { +		return err +	} +	u.rbits += uint64(fi.size) + +	val := false +	for _, b := range data { +		val = val || (b != byte(0)) +	} +	v.SetBool(val) + +	return nil +} + +func (u *unmarshaller) unmarshalString(v reflect.Value, fi *fieldInstance) error { +	switch fi.strType { +	case stringType_C: +		return u.unmarshalStringC(v) +	case stringType_C_P: +		return u.unmarshalStringCP(v, fi) +	default: +		return fmt.Errorf("unknown string type: %d", fi.strType) +	} +} + +func (u *unmarshaller) unmarshalStringC(v reflect.Value) error { +	data := make([]byte, 0, 16) +	for { +		if u.rbits >= u.size*8 { +			break +		} + +		c, err := u.reader.ReadBits(8) +		if err != nil { +			return err +		} +		u.rbits += 8 + +		if c[0] == 0 { +			break // null character +		} + +		data = append(data, c[0]) +	} +	v.SetString(string(data)) + +	return nil +} + +func (u *unmarshaller) unmarshalStringCP(v reflect.Value, fi *fieldInstance) error { +	if ok, err := u.tryReadPString(v, fi); err != nil { +		return err +	} else if ok { +		return nil +	} +	return u.unmarshalStringC(v) +} + +func (u *unmarshaller) tryReadPString(v reflect.Value, fi *fieldInstance) (ok bool, err error) { +	remainingSize := (u.size*8 - u.rbits) / 8 +	if remainingSize < 2 { +		return false, nil +	} + +	offset, err := u.reader.Seek(0, io.SeekCurrent) +	if err != nil { +		return false, err +	} +	defer func() { +		if err == nil && !ok { +			_, err = u.reader.Seek(offset, io.SeekStart) +		} +	}() + +	buf0 := make([]byte, 1) +	if _, err := io.ReadFull(u.reader, buf0); err != nil { +		return false, err +	} +	remainingSize-- +	plen := buf0[0] +	if uint64(plen) > remainingSize { +		return false, nil +	} +	buf := make([]byte, int(plen)) +	if _, err := io.ReadFull(u.reader, buf); err != nil { +		return false, err +	} +	remainingSize -= uint64(plen) +	if fi.cfo.IsPString(fi.name, buf, remainingSize, u.ctx) { +		u.rbits += uint64(len(buf)+1) * 8 +		v.SetString(string(buf)) +		return true, nil +	} +	return false, nil +} + +func (u *unmarshaller) readUvarint() (uint64, error) { +	var val uint64 +	for { +		octet, err := u.reader.ReadBits(8) +		if err != nil { +			return 0, err +		} +		u.rbits += 8 + +		val = (val << 7) + uint64(octet[0]&0x7f) + +		if octet[0]&0x80 == 0 { +			return val, nil +		} +	} +} diff --git a/vendor/github.com/abema/go-mp4/mp4.go b/vendor/github.com/abema/go-mp4/mp4.go new file mode 100644 index 000000000..6aa5b307a --- /dev/null +++ b/vendor/github.com/abema/go-mp4/mp4.go @@ -0,0 +1,151 @@ +package mp4 + +import ( +	"errors" +	"fmt" +	"reflect" +	"strings" +) + +var ErrBoxInfoNotFound = errors.New("box info not found") + +// BoxType is mpeg box type +type BoxType [4]byte + +func StrToBoxType(code string) BoxType { +	if len(code) != 4 { +		panic(fmt.Errorf("invalid box type id length: [%s]", code)) +	} +	return BoxType{code[0], code[1], code[2], code[3]} +} + +func (boxType BoxType) String() string { +	if isPrintable(boxType[0]) && isPrintable(boxType[1]) && isPrintable(boxType[2]) && isPrintable(boxType[3]) { +		s := string([]byte{boxType[0], boxType[1], boxType[2], boxType[3]}) +		s = strings.ReplaceAll(s, string([]byte{0xa9}), "(c)") +		return s +	} +	return fmt.Sprintf("0x%02x%02x%02x%02x", boxType[0], boxType[1], boxType[2], boxType[3]) +} + +func isASCII(c byte) bool { +	return c >= 0x20 && c <= 0x7e +} + +func isPrintable(c byte) bool { +	return isASCII(c) || c == 0xa9 +} + +func (lhs BoxType) MatchWith(rhs BoxType) bool { +	if lhs == boxTypeAny || rhs == boxTypeAny { +		return true +	} +	return lhs == rhs +} + +var boxTypeAny = BoxType{0x00, 0x00, 0x00, 0x00} + +func BoxTypeAny() BoxType { +	return boxTypeAny +} + +type boxDef struct { +	dataType reflect.Type +	versions []uint8 +	isTarget func(Context) bool +	fields   []*field +} + +var boxMap = make(map[BoxType][]boxDef, 64) + +func AddBoxDef(payload IBox, versions ...uint8) { +	boxMap[payload.GetType()] = append(boxMap[payload.GetType()], boxDef{ +		dataType: reflect.TypeOf(payload).Elem(), +		versions: versions, +		fields:   buildFields(payload), +	}) +} + +func AddBoxDefEx(payload IBox, isTarget func(Context) bool, versions ...uint8) { +	boxMap[payload.GetType()] = append(boxMap[payload.GetType()], boxDef{ +		dataType: reflect.TypeOf(payload).Elem(), +		versions: versions, +		isTarget: isTarget, +		fields:   buildFields(payload), +	}) +} + +func AddAnyTypeBoxDef(payload IAnyType, boxType BoxType, versions ...uint8) { +	boxMap[boxType] = append(boxMap[boxType], boxDef{ +		dataType: reflect.TypeOf(payload).Elem(), +		versions: versions, +		fields:   buildFields(payload), +	}) +} + +func AddAnyTypeBoxDefEx(payload IAnyType, boxType BoxType, isTarget func(Context) bool, versions ...uint8) { +	boxMap[boxType] = append(boxMap[boxType], boxDef{ +		dataType: reflect.TypeOf(payload).Elem(), +		versions: versions, +		isTarget: isTarget, +		fields:   buildFields(payload), +	}) +} + +func (boxType BoxType) getBoxDef(ctx Context) *boxDef { +	boxDefs := boxMap[boxType] +	for i := len(boxDefs) - 1; i >= 0; i-- { +		boxDef := &boxDefs[i] +		if boxDef.isTarget == nil || boxDef.isTarget(ctx) { +			return boxDef +		} +	} +	return nil +} + +func (boxType BoxType) IsSupported(ctx Context) bool { +	return boxType.getBoxDef(ctx) != nil +} + +func (boxType BoxType) New(ctx Context) (IBox, error) { +	boxDef := boxType.getBoxDef(ctx) +	if boxDef == nil { +		return nil, ErrBoxInfoNotFound +	} + +	box, ok := reflect.New(boxDef.dataType).Interface().(IBox) +	if !ok { +		return nil, fmt.Errorf("box type not implements IBox interface: %s", boxType.String()) +	} + +	anyTypeBox, ok := box.(IAnyType) +	if ok { +		anyTypeBox.SetType(boxType) +	} + +	return box, nil +} + +func (boxType BoxType) GetSupportedVersions(ctx Context) ([]uint8, error) { +	boxDef := boxType.getBoxDef(ctx) +	if boxDef == nil { +		return nil, ErrBoxInfoNotFound +	} +	return boxDef.versions, nil +} + +func (boxType BoxType) IsSupportedVersion(ver uint8, ctx Context) bool { +	boxDef := boxType.getBoxDef(ctx) +	if boxDef == nil { +		return false +	} +	if len(boxDef.versions) == 0 { +		return true +	} +	for _, sver := range boxDef.versions { +		if ver == sver { +			return true +		} +	} +	return false +} diff --git a/vendor/github.com/abema/go-mp4/probe.go b/vendor/github.com/abema/go-mp4/probe.go new file mode 100644 index 000000000..812be5bb6 --- /dev/null +++ b/vendor/github.com/abema/go-mp4/probe.go @@ -0,0 +1,673 @@ +package mp4 + +import ( +	"bytes" +	"errors" +	"io" + +	"github.com/abema/go-mp4/bitio" +) + +type ProbeInfo struct { +	MajorBrand       [4]byte +	MinorVersion     uint32 +	CompatibleBrands [][4]byte +	FastStart        bool +	Timescale        uint32 +	Duration         uint64 +	Tracks           Tracks +	Segments         Segments +} + +// Deprecated: replace with ProbeInfo +type FraProbeInfo = ProbeInfo + +type Tracks []*Track + +// Deprecated: replace with Track +type TrackInfo = Track + +type Track struct { +	TrackID   uint32 +	Timescale uint32 +	Duration  uint64 +	Codec     Codec +	Encrypted bool +	EditList  EditList +	Samples   Samples +	Chunks    Chunks +	AVC       *AVCDecConfigInfo +	MP4A      *MP4AInfo +} + +type Codec int + +const ( +	CodecUnknown Codec = iota +	CodecAVC1 +	CodecMP4A +) + +type EditList []*EditListEntry + +type EditListEntry struct { +	MediaTime       int64 +	SegmentDuration uint64 +} + +type Samples []*Sample + +type Sample struct { +	Size                  uint32 +	TimeDelta             uint32 +	CompositionTimeOffset int64 +} + +type Chunks []*Chunk + +type Chunk struct { +	DataOffset      uint32 +	SamplesPerChunk uint32 +} + +type AVCDecConfigInfo struct { +	ConfigurationVersion uint8 +	Profile              uint8 +	ProfileCompatibility uint8 +	Level                uint8 +	LengthSize           uint16 +	Width                uint16 +	Height               uint16 +} + +type MP4AInfo struct { +	OTI          uint8 +	AudOTI       uint8 +	ChannelCount uint16 +} + +type Segments []*Segment + +// Deprecated: replace with Segment +type SegmentInfo = Segment + +type Segment struct { +	TrackID               uint32 +	MoofOffset            uint64 +	BaseMediaDecodeTime   uint64 +	DefaultSampleDuration uint32 +	SampleCount           uint32 +	Duration              uint32 +	CompositionTimeOffset int32 +	Size                  uint32 +} + +// Probe probes MP4 file +func Probe(r io.ReadSeeker) (*ProbeInfo, error) { +	probeInfo := &ProbeInfo{ +		Tracks:   make([]*Track, 0, 8), +		Segments: make([]*Segment, 0, 8), +	} +	bis, err := ExtractBoxes(r, nil, []BoxPath{ +		{BoxTypeFtyp()}, +		{BoxTypeMoov()}, +		{BoxTypeMoov(), BoxTypeMvhd()}, +		{BoxTypeMoov(), BoxTypeTrak()}, +		{BoxTypeMoof()}, +		{BoxTypeMdat()}, +	}) +	if err != nil { +		return nil, err +	} +	var mdatAppeared bool +	for _, bi := range bis { +		switch bi.Type { +		case BoxTypeFtyp(): +			var ftyp Ftyp +			if _, err := bi.SeekToPayload(r); err != nil { +				return nil, err +			} +			if _, err := Unmarshal(r, bi.Size-bi.HeaderSize, &ftyp, bi.Context); err != nil { +				return nil, err +			} +			probeInfo.MajorBrand = ftyp.MajorBrand +			probeInfo.MinorVersion = ftyp.MinorVersion +			probeInfo.CompatibleBrands = make([][4]byte, 0, len(ftyp.CompatibleBrands)) +			for _, entry := range ftyp.CompatibleBrands { +				probeInfo.CompatibleBrands = append(probeInfo.CompatibleBrands, entry.CompatibleBrand) +			} +		case BoxTypeMoov(): +			probeInfo.FastStart = !mdatAppeared +		case BoxTypeMvhd(): +			var mvhd Mvhd +			if _, err := bi.SeekToPayload(r); err != nil { +				return nil, err +			} +			if _, err := Unmarshal(r, bi.Size-bi.HeaderSize, &mvhd, bi.Context); err != nil { +				return nil, err +			} +			probeInfo.Timescale = mvhd.Timescale +			if mvhd.GetVersion() == 0 { +				probeInfo.Duration = uint64(mvhd.DurationV0) +			} else { +				probeInfo.Duration = mvhd.DurationV1 +			} +		case BoxTypeTrak(): +			track, err := probeTrak(r, bi) +			if err != nil { +				return nil, err +			} +			probeInfo.Tracks = append(probeInfo.Tracks, track) +		case BoxTypeMoof(): +			segment, err := probeMoof(r, bi) +			if err != nil { +				return nil, err +			} +			probeInfo.Segments = append(probeInfo.Segments, segment) +		case BoxTypeMdat(): +			mdatAppeared = true +		} +	} +	return probeInfo, nil +} + +// ProbeFra probes fragmented MP4 file +// Deprecated: replace with Probe +func ProbeFra(r io.ReadSeeker) (*FraProbeInfo, error) { +	probeInfo, err := Probe(r) +	return (*FraProbeInfo)(probeInfo), err +} + +func probeTrak(r io.ReadSeeker, bi *BoxInfo) (*Track, error) { +	track := new(Track) + +	bips, err := ExtractBoxesWithPayload(r, bi, []BoxPath{ +		{BoxTypeTkhd()}, +		{BoxTypeEdts(), BoxTypeElst()}, +		{BoxTypeMdia(), BoxTypeMdhd()}, +		{BoxTypeMdia(), BoxTypeMinf(), BoxTypeStbl(), BoxTypeStsd(), BoxTypeAvc1()}, +		{BoxTypeMdia(), BoxTypeMinf(), BoxTypeStbl(), BoxTypeStsd(), BoxTypeAvc1(), BoxTypeAvcC()}, +		{BoxTypeMdia(), BoxTypeMinf(), BoxTypeStbl(), BoxTypeStsd(), BoxTypeEncv()}, +		{BoxTypeMdia(), BoxTypeMinf(), BoxTypeStbl(), BoxTypeStsd(), BoxTypeEncv(), BoxTypeAvcC()}, +		{BoxTypeMdia(), BoxTypeMinf(), BoxTypeStbl(), BoxTypeStsd(), BoxTypeMp4a()}, +		{BoxTypeMdia(), BoxTypeMinf(), BoxTypeStbl(), BoxTypeStsd(), BoxTypeMp4a(), BoxTypeEsds()}, +		{BoxTypeMdia(), BoxTypeMinf(), BoxTypeStbl(), BoxTypeStsd(), BoxTypeMp4a(), BoxTypeWave(), BoxTypeEsds()}, +		{BoxTypeMdia(), BoxTypeMinf(), BoxTypeStbl(), BoxTypeStsd(), BoxTypeEnca()}, +		{BoxTypeMdia(), BoxTypeMinf(), BoxTypeStbl(), BoxTypeStsd(), BoxTypeEnca(), BoxTypeEsds()}, +		{BoxTypeMdia(), BoxTypeMinf(), BoxTypeStbl(), BoxTypeStco()}, +		{BoxTypeMdia(), BoxTypeMinf(), BoxTypeStbl(), BoxTypeStts()}, +		{BoxTypeMdia(), BoxTypeMinf(), BoxTypeStbl(), BoxTypeCtts()}, +		{BoxTypeMdia(), BoxTypeMinf(), BoxTypeStbl(), BoxTypeStsc()}, +		{BoxTypeMdia(), BoxTypeMinf(), BoxTypeStbl(), BoxTypeStsz()}, +	}) +	if err != nil { +		return nil, err +	} +	var tkhd *Tkhd +	var elst *Elst +	var mdhd *Mdhd +	var avc1 *VisualSampleEntry +	var avcC *AVCDecoderConfiguration +	var audioSampleEntry *AudioSampleEntry +	var esds *Esds +	var stco *Stco +	var stts *Stts +	var stsc *Stsc +	var ctts *Ctts +	var stsz *Stsz +	for _, bip := range bips { +		switch bip.Info.Type { +		case BoxTypeTkhd(): +			tkhd = bip.Payload.(*Tkhd) +		case BoxTypeElst(): +			elst = bip.Payload.(*Elst) +		case BoxTypeMdhd(): +			mdhd = bip.Payload.(*Mdhd) +		case BoxTypeAvc1(): +			track.Codec = CodecAVC1 +			avc1 = bip.Payload.(*VisualSampleEntry) +		case BoxTypeAvcC(): +			avcC = bip.Payload.(*AVCDecoderConfiguration) +		case BoxTypeEncv(): +			track.Codec = CodecAVC1 +			track.Encrypted = true +		case BoxTypeMp4a(): +			track.Codec = CodecMP4A +			audioSampleEntry = bip.Payload.(*AudioSampleEntry) +		case BoxTypeEnca(): +			track.Codec = CodecMP4A +			track.Encrypted = true +			audioSampleEntry = bip.Payload.(*AudioSampleEntry) +		case BoxTypeEsds(): +			esds = bip.Payload.(*Esds) +		case BoxTypeStco(): +			stco = bip.Payload.(*Stco) +		case BoxTypeStts(): +			stts = bip.Payload.(*Stts) +		case BoxTypeStsc(): +			stsc = bip.Payload.(*Stsc) +		case BoxTypeCtts(): +			ctts = bip.Payload.(*Ctts) +		case BoxTypeStsz(): +			stsz = bip.Payload.(*Stsz) +		} +	} + +	if tkhd == nil { +		return nil, errors.New("tkhd box not found") +	} +	track.TrackID = tkhd.TrackID + +	if elst != nil { +		editList := make([]*EditListEntry, 0, len(elst.Entries)) +		for i := range elst.Entries { +			editList = append(editList, &EditListEntry{ +				MediaTime:       elst.GetMediaTime(i), +				SegmentDuration: elst.GetSegmentDuration(i), +			}) +		} +		track.EditList = editList +	} + +	if mdhd == nil { +		return nil, errors.New("mdhd box not found") +	} +	track.Timescale = mdhd.Timescale +	track.Duration = mdhd.GetDuration() + +	if avc1 != nil && avcC != nil { +		track.AVC = &AVCDecConfigInfo{ +			ConfigurationVersion: avcC.ConfigurationVersion, +			Profile:              avcC.Profile, +			ProfileCompatibility: avcC.ProfileCompatibility, +			Level:                avcC.Level, +			LengthSize:           uint16(avcC.LengthSizeMinusOne) + 1, +			Width:                avc1.Width, +			Height:               avc1.Height, +		} +	} + +	if audioSampleEntry != nil && esds != nil { +		oti, audOTI, err := detectAACProfile(esds) +		if err != nil { +			return nil, err +		} +		track.MP4A = &MP4AInfo{ +			OTI:          oti, +			AudOTI:       audOTI, +			ChannelCount: audioSampleEntry.ChannelCount, +		} +	} + +	if stco == nil { +		return nil, errors.New("stco box not found") +	} +	track.Chunks = make([]*Chunk, 0) +	for _, offset := range stco.ChunkOffset { +		track.Chunks = append(track.Chunks, &Chunk{ +			DataOffset: offset, +		}) +	} + +	if stts == nil { +		return nil, errors.New("stts box not found") +	} +	track.Samples = make([]*Sample, 0) +	for _, entry := range stts.Entries { +		for i := uint32(0); i < entry.SampleCount; i++ { +			track.Samples = append(track.Samples, &Sample{ +				TimeDelta: entry.SampleDelta, +			}) +		} +	} + +	if stsc == nil { +		return nil, errors.New("stsc box not found") +	} +	for si, entry := range stsc.Entries { +		end := uint32(len(track.Chunks)) +		if si != len(stsc.Entries)-1 && stsc.Entries[si+1].FirstChunk-1 < end { +			end = stsc.Entries[si+1].FirstChunk - 1 +		} +		for ci := entry.FirstChunk - 1; ci < end; ci++ { +			track.Chunks[ci].SamplesPerChunk = entry.SamplesPerChunk +		} +	} + +	if ctts != nil { +		var si uint32 +		for ci, entry := range ctts.Entries { +			for i := uint32(0); i < entry.SampleCount; i++ { +				if si >= uint32(len(track.Samples)) { +					break +				} +				track.Samples[si].CompositionTimeOffset = ctts.GetSampleOffset(ci) +				si++ +			} +		} +	} + +	if stsz != nil { +		for i := 0; i < len(stsz.EntrySize) && i < len(track.Samples); i++ { +			track.Samples[i].Size = stsz.EntrySize[i] +		} +	} + +	return track, nil +} + +func detectAACProfile(esds *Esds) (oti, audOTI uint8, err error) { +	configDscr := findDescriptorByTag(esds.Descriptors, DecoderConfigDescrTag) +	if configDscr == nil || configDscr.DecoderConfigDescriptor == nil { +		return 0, 0, nil +	} +	if configDscr.DecoderConfigDescriptor.ObjectTypeIndication != 0x40 { +		return configDscr.DecoderConfigDescriptor.ObjectTypeIndication, 0, nil +	} + +	specificDscr := findDescriptorByTag(esds.Descriptors, DecSpecificInfoTag) +	if specificDscr == nil { +		return 0, 0, errors.New("DecoderSpecificationInfoDescriptor not found") +	} + +	r := bitio.NewReader(bytes.NewReader(specificDscr.Data)) +	remaining := len(specificDscr.Data) * 8 + +	// audio object type +	audioObjectType, read, err := getAudioObjectType(r) +	if err != nil { +		return 0, 0, err +	} +	remaining -= read + +	// sampling frequency index +	samplingFrequencyIndex, err := r.ReadBits(4) +	if err != nil { +		return 0, 0, err +	} +	remaining -= 4 +	if samplingFrequencyIndex[0] == 0x0f { +		if _, err = r.ReadBits(24); err != nil { +			return 0, 0, err +		} +		remaining -= 24 +	} + +	if audioObjectType == 2 && remaining >= 20 { +		if _, err = r.ReadBits(4); err != nil { +			return 0, 0, err +		} +		remaining -= 4 +		syncExtensionType, err := r.ReadBits(11) +		if err != nil { +			return 0, 0, err +		} +		remaining -= 11 +		if syncExtensionType[0] == 0x2 && syncExtensionType[1] == 0xb7 { +			extAudioObjectType, _, err := getAudioObjectType(r) +			if err != nil { +				return 0, 0, err +			} +			if extAudioObjectType == 5 || extAudioObjectType == 22 { +				sbr, err := r.ReadBits(1) +				if err != nil { +					return 0, 0, err +				} +				remaining-- +				if sbr[0] != 0 { +					if extAudioObjectType == 5 { +						sfi, err := r.ReadBits(4) +						if err != nil { +							return 0, 0, err +						} +						remaining -= 4 +						if sfi[0] == 0xf { +							if _, err := r.ReadBits(24); err != nil { +								return 0, 0, err +							} +							remaining -= 24 +						} +						if remaining >= 12 { +							syncExtensionType, err := r.ReadBits(11) +							if err != nil { +								return 0, 0, err +							} +							if syncExtensionType[0] == 0x5 && syncExtensionType[1] == 0x48 { +								ps, err := r.ReadBits(1) +								if err != nil { +									return 0, 0, err +								} +								if ps[0] != 0 { +									return 0x40, 29, nil +								} +							} +						} +					} +					return 0x40, 5, nil +				} +			} +		} +	} +	return 0x40, audioObjectType, nil +} + +func findDescriptorByTag(dscrs []Descriptor, tag int8) *Descriptor { +	for _, dscr := range dscrs { +		if dscr.Tag == tag { +			return &dscr +		} +	} +	return nil +} + +func getAudioObjectType(r bitio.Reader) (byte, int, error) { +	audioObjectType, err := r.ReadBits(5) +	if err != nil { +		return 0, 0, err +	} +	if audioObjectType[0] != 0x1f { +		return audioObjectType[0], 5, nil +	} +	audioObjectType, err = r.ReadBits(6) +	if err != nil { +		return 0, 0, err +	} +	return audioObjectType[0] + 32, 11, nil +} + +func probeMoof(r io.ReadSeeker, bi *BoxInfo) (*Segment, error) { +	bips, err := ExtractBoxesWithPayload(r, bi, []BoxPath{ +		{BoxTypeTraf(), BoxTypeTfhd()}, +		{BoxTypeTraf(), BoxTypeTfdt()}, +		{BoxTypeTraf(), BoxTypeTrun()}, +	}) +	if err != nil { +		return nil, err +	} + +	var tfhd *Tfhd +	var tfdt *Tfdt +	var trun *Trun + +	segment := &Segment{ +		MoofOffset: bi.Offset, +	} +	for _, bip := range bips { +		switch bip.Info.Type { +		case BoxTypeTfhd(): +			tfhd = bip.Payload.(*Tfhd) +		case BoxTypeTfdt(): +			tfdt = bip.Payload.(*Tfdt) +		case BoxTypeTrun(): +			trun = bip.Payload.(*Trun) +		} +	} + +	if tfhd == nil { +		return nil, errors.New("tfhd not found") +	} +	segment.TrackID = tfhd.TrackID +	segment.DefaultSampleDuration = tfhd.DefaultSampleDuration + +	if tfdt != nil { +		if tfdt.Version == 0 { +			segment.BaseMediaDecodeTime = uint64(tfdt.BaseMediaDecodeTimeV0) +		} else { +			segment.BaseMediaDecodeTime = tfdt.BaseMediaDecodeTimeV1 +		} +	} + +	if trun != nil { +		segment.SampleCount = trun.SampleCount + +		if trun.CheckFlag(0x000100) { +			segment.Duration = 0 +			for ei := range trun.Entries { +				segment.Duration += trun.Entries[ei].SampleDuration +			} +		} else { +			segment.Duration = tfhd.DefaultSampleDuration * segment.SampleCount +		} + +		if trun.CheckFlag(0x000200) { +			segment.Size = 0 +			for ei := range trun.Entries { +				segment.Size += trun.Entries[ei].SampleSize +			} +		} else { +			segment.Size = tfhd.DefaultSampleSize * segment.SampleCount +		} + +		var duration uint32 +		for ei := range trun.Entries { +			offset := int32(duration) + int32(trun.GetSampleCompositionTimeOffset(ei)) +			if ei == 0 || offset < segment.CompositionTimeOffset { +				segment.CompositionTimeOffset = offset +			} +			if trun.CheckFlag(0x000100) { +				duration += trun.Entries[ei].SampleDuration +			} else { +				duration += tfhd.DefaultSampleDuration +			} +		} +	} + +	return segment, nil +} + +func FindIDRFrames(r io.ReadSeeker, trackInfo *TrackInfo) ([]int, error) { +	if trackInfo.AVC == nil { +		return nil, nil +	} +	lengthSize := uint32(trackInfo.AVC.LengthSize) + +	var si int +	idxs := make([]int, 0, 8) +	for _, chunk := range trackInfo.Chunks { +		end := si + int(chunk.SamplesPerChunk) +		dataOffset := chunk.DataOffset +		for ; si < end && si < len(trackInfo.Samples); si++ { +			sample := trackInfo.Samples[si] +			if sample.Size == 0 { +				continue +			} +			for nalOffset := uint32(0); nalOffset+lengthSize+1 <= sample.Size; { +				if _, err := r.Seek(int64(dataOffset+nalOffset), io.SeekStart); err != nil { +					return nil, err +				} +				data := make([]byte, lengthSize+1) +				if _, err := io.ReadFull(r, data); err != nil { +					return nil, err +				} +				var length uint32 +				for i := 0; i < int(lengthSize); i++ { +					length = (length << 8) + uint32(data[i]) +				} +				nalHeader := data[lengthSize] +				nalType := nalHeader & 0x1f +				if nalType == 5 { +					idxs = append(idxs, si) +					break +				} +				nalOffset += lengthSize + length +			} +			dataOffset += sample.Size +		} +	} +	return idxs, nil +} + +func (samples Samples) GetBitrate(timescale uint32) uint64 { +	var totalSize uint64 +	var totalDuration uint64 +	for _, sample := range samples { +		totalSize += uint64(sample.Size) +		totalDuration += uint64(sample.TimeDelta) +	} +	if totalDuration == 0 { +		return 0 +	} +	return 8 * totalSize * uint64(timescale) / totalDuration +} + +func (samples Samples) GetMaxBitrate(timescale uint32, timeDelta uint64) uint64 { +	if timeDelta == 0 { +		return 0 +	} +	var maxBitrate uint64 +	var size uint64 +	var duration uint64 +	var begin int +	var end int +	for end < len(samples) { +		for { +			size += uint64(samples[end].Size) +			duration += uint64(samples[end].TimeDelta) +			end++ +			if duration >= timeDelta || end == len(samples) { +				break +			} +		} +		bitrate := 8 * size * uint64(timescale) / duration +		if bitrate > maxBitrate { +			maxBitrate = bitrate +		} +		for { +			size -= uint64(samples[begin].Size) +			duration -= uint64(samples[begin].TimeDelta) +			begin++ +			if duration < timeDelta { +				break +			} +		} +	} +	return maxBitrate +} + +func (segments Segments) GetBitrate(trackID uint32, timescale uint32) uint64 { +	var totalSize uint64 +	var totalDuration uint64 +	for _, segment := range segments { +		if segment.TrackID == trackID { +			totalSize += uint64(segment.Size) +			totalDuration += uint64(segment.Duration) +		} +	} +	if totalDuration == 0 { +		return 0 +	} +	return 8 * totalSize * uint64(timescale) / totalDuration +} + +func (segments Segments) GetMaxBitrate(trackID uint32, timescale uint32) uint64 { +	var maxBitrate uint64 +	for _, segment := range segments { +		if segment.TrackID == trackID && segment.Duration != 0 { +			bitrate := 8 * uint64(segment.Size) * uint64(timescale) / uint64(segment.Duration) +			if bitrate > maxBitrate { +				maxBitrate = bitrate +			} +		} +	} +	return maxBitrate +} diff --git a/vendor/github.com/abema/go-mp4/read.go b/vendor/github.com/abema/go-mp4/read.go new file mode 100644 index 000000000..fa69561aa --- /dev/null +++ b/vendor/github.com/abema/go-mp4/read.go @@ -0,0 +1,182 @@ +package mp4 + +import ( +	"errors" +	"fmt" +	"io" +) + +type BoxPath []BoxType + +func (lhs BoxPath) compareWith(rhs BoxPath) (forwardMatch bool, match bool) { +	if len(lhs) > len(rhs) { +		return false, false +	} +	for i := 0; i < len(lhs); i++ { +		if !lhs[i].MatchWith(rhs[i]) { +			return false, false +		} +	} +	if len(lhs) < len(rhs) { +		return true, false +	} +	return false, true +} + +type ReadHandle struct { +	Params      []interface{} +	BoxInfo     BoxInfo +	Path        BoxPath +	ReadPayload func() (box IBox, n uint64, err error) +	ReadData    func(io.Writer) (n uint64, err error) +	Expand      func(params ...interface{}) (vals []interface{}, err error) +} + +type ReadHandler func(handle *ReadHandle) (val interface{}, err error) + +func ReadBoxStructure(r io.ReadSeeker, handler ReadHandler, params ...interface{}) ([]interface{}, error) { +	if _, err := r.Seek(0, io.SeekStart); err != nil { +		return nil, err +	} +	return readBoxStructure(r, 0, true, nil, Context{}, handler, params) +} + +func ReadBoxStructureFromInternal(r io.ReadSeeker, bi *BoxInfo, handler ReadHandler, params ...interface{}) (interface{}, error) { +	return readBoxStructureFromInternal(r, bi, nil, handler, params) +} + +func readBoxStructureFromInternal(r io.ReadSeeker, bi *BoxInfo, path BoxPath, handler ReadHandler, params []interface{}) (interface{}, error) { +	if _, err := bi.SeekToPayload(r); err != nil { +		return nil, err +	} + +	// check comatible-brands +	if len(path) == 0 && bi.Type == BoxTypeFtyp() { +		var ftyp Ftyp +		if _, err := Unmarshal(r, bi.Size-bi.HeaderSize, &ftyp, bi.Context); err != nil { +			return nil, err +		} +		if ftyp.HasCompatibleBrand(BrandQT()) { +			bi.IsQuickTimeCompatible = true +		} +		if _, err := bi.SeekToPayload(r); err != nil { +			return nil, err +		} +	} + +	ctx := bi.Context +	if bi.Type == BoxTypeWave() { +		ctx.UnderWave = true +	} else if bi.Type == BoxTypeIlst() { +		ctx.UnderIlst = true +	} else if bi.UnderIlst && !bi.UnderIlstMeta && IsIlstMetaBoxType(bi.Type) { +		ctx.UnderIlstMeta = true +		if bi.Type == StrToBoxType("----") { +			ctx.UnderIlstFreeMeta = true +		} +	} else if bi.Type == BoxTypeUdta() { +		ctx.UnderUdta = true +	} + +	newPath := make(BoxPath, len(path)+1) +	copy(newPath, path) +	newPath[len(path)] = bi.Type + +	h := &ReadHandle{ +		Params:  params, +		BoxInfo: *bi, +		Path:    newPath, +	} + +	var childrenOffset uint64 + +	h.ReadPayload = func() (IBox, uint64, error) { +		if _, err := bi.SeekToPayload(r); err != nil { +			return nil, 0, err +		} + +		box, n, err := UnmarshalAny(r, bi.Type, bi.Size-bi.HeaderSize, bi.Context) +		if err != nil { +			return nil, 0, err +		} +		childrenOffset = bi.Offset + bi.HeaderSize + n +		return box, n, nil +	} + +	h.ReadData = func(w io.Writer) (uint64, error) { +		if _, err := bi.SeekToPayload(r); err != nil { +			return 0, err +		} + +		size := bi.Size - bi.HeaderSize +		if _, err := io.CopyN(w, r, int64(size)); err != nil { +			return 0, err +		} +		return size, nil +	} + +	h.Expand = func(params ...interface{}) ([]interface{}, error) { +		if childrenOffset == 0 { +			if _, err := bi.SeekToPayload(r); err != nil { +				return nil, err +			} + +			_, n, err := UnmarshalAny(r, bi.Type, bi.Size-bi.HeaderSize, bi.Context) +			if err != nil { +				return nil, err +			} +			childrenOffset = bi.Offset + bi.HeaderSize + n +		} else { +			if _, err := r.Seek(int64(childrenOffset), io.SeekStart); err != nil { +				return nil, err +			} +		} + +		childrenSize := bi.Offset + bi.Size - childrenOffset +		return readBoxStructure(r, childrenSize, false, newPath, ctx, handler, params) +	} + +	if val, err := handler(h); err != nil { +		return nil, err +	} else if _, err := bi.SeekToEnd(r); err != nil { +		return nil, err +	} else { +		return val, nil +	} +} + +func readBoxStructure(r io.ReadSeeker, totalSize uint64, isRoot bool, path BoxPath, ctx Context, handler ReadHandler, params []interface{}) ([]interface{}, error) { +	vals := make([]interface{}, 0, 8) + +	for isRoot || totalSize != 0 { +		bi, err := ReadBoxInfo(r) +		if isRoot && err == io.EOF { +			return vals, nil +		} else if err != nil { +			return nil, err +		} + +		if !isRoot && bi.Size > totalSize { +			return nil, fmt.Errorf("too large box size: type=%s, size=%d, actualBufSize=%d", bi.Type.String(), bi.Size, totalSize) +		} +		totalSize -= bi.Size + +		bi.Context = ctx + +		val, err := readBoxStructureFromInternal(r, bi, path, handler, params) +		if err != nil { +			return nil, err +		} +		vals = append(vals, val) + +		if bi.IsQuickTimeCompatible { +			ctx.IsQuickTimeCompatible = true +		} +	} + +	if totalSize != 0 { +		return nil, errors.New("Unexpected EOF") +	} + +	return vals, nil +} diff --git a/vendor/github.com/abema/go-mp4/string.go b/vendor/github.com/abema/go-mp4/string.go new file mode 100644 index 000000000..56afff1bb --- /dev/null +++ b/vendor/github.com/abema/go-mp4/string.go @@ -0,0 +1,261 @@ +package mp4 + +import ( +	"bytes" +	"fmt" +	"io" +	"reflect" +	"strconv" + +	"github.com/abema/go-mp4/util" +) + +type stringifier struct { +	buf    *bytes.Buffer +	src    IImmutableBox +	indent string +	ctx    Context +} + +func Stringify(src IImmutableBox, ctx Context) (string, error) { +	return StringifyWithIndent(src, "", ctx) +} + +func StringifyWithIndent(src IImmutableBox, indent string, ctx Context) (string, error) { +	boxDef := src.GetType().getBoxDef(ctx) +	if boxDef == nil { +		return "", ErrBoxInfoNotFound +	} + +	v := reflect.ValueOf(src).Elem() + +	m := &stringifier{ +		buf:    bytes.NewBuffer(nil), +		src:    src, +		indent: indent, +		ctx:    ctx, +	} + +	err := m.stringifyStruct(v, boxDef.fields, 0, true) +	if err != nil { +		return "", err +	} + +	return m.buf.String(), nil +} + +func (m *stringifier) stringify(v reflect.Value, fi *fieldInstance, depth int) error { +	switch v.Type().Kind() { +	case reflect.Ptr: +		return m.stringifyPtr(v, fi, depth) +	case reflect.Struct: +		return m.stringifyStruct(v, fi.children, depth, fi.is(fieldExtend)) +	case reflect.Array: +		return m.stringifyArray(v, fi, depth) +	case reflect.Slice: +		return m.stringifySlice(v, fi, depth) +	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: +		return m.stringifyInt(v, fi, depth) +	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: +		return m.stringifyUint(v, fi, depth) +	case reflect.Bool: +		return m.stringifyBool(v, depth) +	case reflect.String: +		return m.stringifyString(v, depth) +	default: +		return fmt.Errorf("unsupported type: %s", v.Type().Kind()) +	} +} + +func (m *stringifier) stringifyPtr(v reflect.Value, fi *fieldInstance, depth int) error { +	return m.stringify(v.Elem(), fi, depth) +} + +func (m *stringifier) stringifyStruct(v reflect.Value, fs []*field, depth int, extended bool) error { +	if !extended { +		m.buf.WriteString("{") +		if m.indent != "" { +			m.buf.WriteString("\n") +		} +		depth++ +	} + +	for _, f := range fs { +		fi := resolveFieldInstance(f, m.src, v, m.ctx) + +		if !isTargetField(m.src, fi, m.ctx) { +			continue +		} + +		if f.cnst != "" || f.is(fieldHidden) { +			continue +		} + +		if !f.is(fieldExtend) { +			if m.indent != "" { +				writeIndent(m.buf, m.indent, depth+1) +			} else if m.buf.Len() != 0 && m.buf.Bytes()[m.buf.Len()-1] != '{' { +				m.buf.WriteString(" ") +			} +			m.buf.WriteString(f.name) +			m.buf.WriteString("=") +		} + +		str, ok := fi.cfo.StringifyField(f.name, m.indent, depth+1, m.ctx) +		if ok { +			m.buf.WriteString(str) +			if !f.is(fieldExtend) && m.indent != "" { +				m.buf.WriteString("\n") +			} +			continue +		} + +		if f.name == "Version" { +			m.buf.WriteString(strconv.Itoa(int(m.src.GetVersion()))) +		} else if f.name == "Flags" { +			fmt.Fprintf(m.buf, "0x%06x", m.src.GetFlags()) +		} else { +			err := m.stringify(v.FieldByName(f.name), fi, depth) +			if err != nil { +				return err +			} +		} + +		if !f.is(fieldExtend) && m.indent != "" { +			m.buf.WriteString("\n") +		} +	} + +	if !extended { +		if m.indent != "" { +			writeIndent(m.buf, m.indent, depth) +		} +		m.buf.WriteString("}") +	} + +	return nil +} + +func (m *stringifier) stringifyArray(v reflect.Value, fi *fieldInstance, depth int) error { +	begin, sep, end := "[", ", ", "]" +	if fi.is(fieldString) || fi.is(fieldISO639_2) { +		begin, sep, end = "\"", "", "\"" +	} else if fi.is(fieldUUID) { +		begin, sep, end = "", "", "" +	} + +	m.buf.WriteString(begin) + +	m2 := *m +	if fi.is(fieldString) { +		m2.buf = bytes.NewBuffer(nil) +	} +	size := v.Type().Size() +	for i := 0; i < int(size)/int(v.Type().Elem().Size()); i++ { +		if i != 0 { +			m2.buf.WriteString(sep) +		} + +		if err := m2.stringify(v.Index(i), fi, depth+1); err != nil { +			return err +		} + +		if fi.is(fieldUUID) && (i == 3 || i == 5 || i == 7 || i == 9) { +			m.buf.WriteString("-") +		} +	} +	if fi.is(fieldString) { +		m.buf.WriteString(util.EscapeUnprintables(m2.buf.String())) +	} + +	m.buf.WriteString(end) + +	return nil +} + +func (m *stringifier) stringifySlice(v reflect.Value, fi *fieldInstance, depth int) error { +	begin, sep, end := "[", ", ", "]" +	if fi.is(fieldString) || fi.is(fieldISO639_2) { +		begin, sep, end = "\"", "", "\"" +	} + +	m.buf.WriteString(begin) + +	m2 := *m +	if fi.is(fieldString) { +		m2.buf = bytes.NewBuffer(nil) +	} +	for i := 0; i < v.Len(); i++ { +		if fi.length != LengthUnlimited && uint(i) >= fi.length { +			break +		} + +		if i != 0 { +			m2.buf.WriteString(sep) +		} + +		if err := m2.stringify(v.Index(i), fi, depth+1); err != nil { +			return err +		} +	} +	if fi.is(fieldString) { +		m.buf.WriteString(util.EscapeUnprintables(m2.buf.String())) +	} + +	m.buf.WriteString(end) + +	return nil +} + +func (m *stringifier) stringifyInt(v reflect.Value, fi *fieldInstance, depth int) error { +	if fi.is(fieldHex) { +		val := v.Int() +		if val >= 0 { +			m.buf.WriteString("0x") +			m.buf.WriteString(strconv.FormatInt(val, 16)) +		} else { +			m.buf.WriteString("-0x") +			m.buf.WriteString(strconv.FormatInt(-val, 16)) +		} +	} else { +		m.buf.WriteString(strconv.FormatInt(v.Int(), 10)) +	} +	return nil +} + +func (m *stringifier) stringifyUint(v reflect.Value, fi *fieldInstance, depth int) error { +	if fi.is(fieldISO639_2) { +		m.buf.WriteString(string([]byte{byte(v.Uint() + 0x60)})) +	} else if fi.is(fieldUUID) { +		fmt.Fprintf(m.buf, "%02x", v.Uint()) +	} else if fi.is(fieldString) { +		m.buf.WriteString(string([]byte{byte(v.Uint())})) +	} else if fi.is(fieldHex) || (!fi.is(fieldDec) && v.Type().Kind() == reflect.Uint8) || v.Type().Kind() == reflect.Uintptr { +		m.buf.WriteString("0x") +		m.buf.WriteString(strconv.FormatUint(v.Uint(), 16)) +	} else { +		m.buf.WriteString(strconv.FormatUint(v.Uint(), 10)) +	} + +	return nil +} + +func (m *stringifier) stringifyBool(v reflect.Value, depth int) error { +	m.buf.WriteString(strconv.FormatBool(v.Bool())) + +	return nil +} + +func (m *stringifier) stringifyString(v reflect.Value, depth int) error { +	m.buf.WriteString("\"") +	m.buf.WriteString(util.EscapeUnprintables(v.String())) +	m.buf.WriteString("\"") + +	return nil +} + +func writeIndent(w io.Writer, indent string, depth int) { +	for i := 0; i < depth; i++ { +		io.WriteString(w, indent) +	} +} diff --git a/vendor/github.com/abema/go-mp4/util/io.go b/vendor/github.com/abema/go-mp4/util/io.go new file mode 100644 index 000000000..1e4681186 --- /dev/null +++ b/vendor/github.com/abema/go-mp4/util/io.go @@ -0,0 +1,30 @@ +package util + +import ( +	"bytes" +	"io" +) + +func ReadString(r io.Reader) (string, error) { +	b := make([]byte, 1) +	buf := bytes.NewBuffer(nil) +	for { +		if _, err := r.Read(b); err != nil { +			return "", err +		} +		if b[0] == 0 { +			return buf.String(), nil +		} +		buf.Write(b) +	} +} + +func WriteString(w io.Writer, s string) error { +	if _, err := w.Write([]byte(s)); err != nil { +		return err +	} +	if _, err := w.Write([]byte{0}); err != nil { +		return err +	} +	return nil +} diff --git a/vendor/github.com/abema/go-mp4/util/string.go b/vendor/github.com/abema/go-mp4/util/string.go new file mode 100644 index 000000000..b38251bb3 --- /dev/null +++ b/vendor/github.com/abema/go-mp4/util/string.go @@ -0,0 +1,42 @@ +package util + +import ( +	"strconv" +	"strings" +	"unicode" +) + +func FormatSignedFixedFloat1616(val int32) string { +	if val&0xffff == 0 { +		return strconv.Itoa(int(val >> 16)) +	} else { +		return strconv.FormatFloat(float64(val)/(1<<16), 'f', 5, 64) +	} +} + +func FormatUnsignedFixedFloat1616(val uint32) string { +	if val&0xffff == 0 { +		return strconv.Itoa(int(val >> 16)) +	} else { +		return strconv.FormatFloat(float64(val)/(1<<16), 'f', 5, 64) +	} +} + +func FormatSignedFixedFloat88(val int16) string { +	if val&0xff == 0 { +		return strconv.Itoa(int(val >> 8)) +	} else { +		return strconv.FormatFloat(float64(val)/(1<<8), 'f', 3, 32) +	} +} + +func EscapeUnprintable(r rune) rune { +	if unicode.IsGraphic(r) { +		return r +	} +	return rune('.') +} + +func EscapeUnprintables(src string) string { +	return strings.Map(EscapeUnprintable, src) +} diff --git a/vendor/github.com/abema/go-mp4/write.go b/vendor/github.com/abema/go-mp4/write.go new file mode 100644 index 000000000..72d464444 --- /dev/null +++ b/vendor/github.com/abema/go-mp4/write.go @@ -0,0 +1,68 @@ +package mp4 + +import ( +	"errors" +	"io" +) + +type Writer struct { +	writer  io.WriteSeeker +	biStack []*BoxInfo +} + +func NewWriter(w io.WriteSeeker) *Writer { +	return &Writer{ +		writer: w, +	} +} + +func (w *Writer) Write(p []byte) (int, error) { +	return w.writer.Write(p) +} + +func (w *Writer) Seek(offset int64, whence int) (int64, error) { +	return w.writer.Seek(offset, whence) +} + +func (w *Writer) StartBox(bi *BoxInfo) (*BoxInfo, error) { +	bi, err := WriteBoxInfo(w.writer, bi) +	if err != nil { +		return nil, err +	} +	w.biStack = append(w.biStack, bi) +	return bi, nil +} + +func (w *Writer) EndBox() (*BoxInfo, error) { +	bi := w.biStack[len(w.biStack)-1] +	w.biStack = w.biStack[:len(w.biStack)-1] +	end, err := w.writer.Seek(0, io.SeekCurrent) +	if err != nil { +		return nil, err +	} +	bi.Size = uint64(end) - bi.Offset +	if _, err = bi.SeekToStart(w.writer); err != nil { +		return nil, err +	} +	if bi2, err := WriteBoxInfo(w.writer, bi); err != nil { +		return nil, err +	} else if bi.HeaderSize != bi2.HeaderSize { +		return nil, errors.New("header size changed") +	} +	if _, err := w.writer.Seek(end, io.SeekStart); err != nil { +		return nil, err +	} +	return bi, nil +} + +func (w *Writer) CopyBox(r io.ReadSeeker, bi *BoxInfo) error { +	if _, err := bi.SeekToStart(r); err != nil { +		return err +	} +	if n, err := io.CopyN(w, r, int64(bi.Size)); err != nil { +		return err +	} else if n != int64(bi.Size) { +		return errors.New("failed to copy box") +	} +	return nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 17d211245..9763b2605 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -66,6 +66,11 @@ codeberg.org/gruf/go-sched  codeberg.org/gruf/go-store/v2/kv  codeberg.org/gruf/go-store/v2/storage  codeberg.org/gruf/go-store/v2/util +# github.com/abema/go-mp4 v0.8.0 +## explicit; go 1.14 +github.com/abema/go-mp4 +github.com/abema/go-mp4/bitio +github.com/abema/go-mp4/util  # github.com/aymerick/douceur v0.2.0  ## explicit  github.com/aymerick/douceur/css  | 
