diff options
Diffstat (limited to 'vendor/github.com')
20 files changed, 5882 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 +} |