[feature] Enable basic video support (mp4 only) (#1274)

* [feature] basic video support

* fix missing semicolon

* replace text shadow with stacked icons

Co-authored-by: f0x <f0x@cthu.lu>
This commit is contained in:
tobi
2022-12-17 05:38:56 +01:00
committed by GitHub
parent 0f38e7c9b0
commit 2bbc64be43
39 changed files with 6276 additions and 93 deletions

1
vendor/github.com/abema/go-mp4/.gitignore generated vendored Normal file
View File

@ -0,0 +1 @@
vendor

21
vendor/github.com/abema/go-mp4/LICENSE generated vendored Normal file
View File

@ -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.

153
vendor/github.com/abema/go-mp4/README.md generated vendored Normal file
View File

@ -0,0 +1,153 @@
go-mp4
------
[![Go Reference](https://pkg.go.dev/badge/github.com/abema/go-mp4.svg)](https://pkg.go.dev/github.com/abema/go-mp4)
![Test](https://github.com/abema/go-mp4/actions/workflows/test.yml/badge.svg)
[![Coverage Status](https://coveralls.io/repos/github/abema/go-mp4/badge.svg)](https://coveralls.io/github/abema/go-mp4)
[![Go Report Card](https://goreportcard.com/badge/github.com/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)
```

19
vendor/github.com/abema/go-mp4/anytype.go generated vendored Normal file
View File

@ -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
}

8
vendor/github.com/abema/go-mp4/bitio/bitio.go generated vendored Normal file
View File

@ -0,0 +1,8 @@
package bitio
import "errors"
var (
ErrInvalidAlignment = errors.New("invalid alignment")
ErrDiscouragedReader = errors.New("discouraged reader implementation")
)

97
vendor/github.com/abema/go-mp4/bitio/read.go generated vendored Normal file
View File

@ -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
}

61
vendor/github.com/abema/go-mp4/bitio/write.go generated vendored Normal file
View File

@ -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
}

188
vendor/github.com/abema/go-mp4/box.go generated vendored Normal file
View File

@ -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))
}

155
vendor/github.com/abema/go-mp4/box_info.go generated vendored Normal file
View File

@ -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)
}

2745
vendor/github.com/abema/go-mp4/box_types.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

98
vendor/github.com/abema/go-mp4/extract.go generated vendored Normal file
View File

@ -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
}

290
vendor/github.com/abema/go-mp4/field.go generated vendored Normal file
View File

@ -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
}

639
vendor/github.com/abema/go-mp4/marshaller.go generated vendored Normal file
View File

@ -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
}
}
}

151
vendor/github.com/abema/go-mp4/mp4.go generated vendored Normal file
View File

@ -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
}

673
vendor/github.com/abema/go-mp4/probe.go generated vendored Normal file
View File

@ -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
}

182
vendor/github.com/abema/go-mp4/read.go generated vendored Normal file
View File

@ -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
}

261
vendor/github.com/abema/go-mp4/string.go generated vendored Normal file
View File

@ -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)
}
}

30
vendor/github.com/abema/go-mp4/util/io.go generated vendored Normal file
View File

@ -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
}

42
vendor/github.com/abema/go-mp4/util/string.go generated vendored Normal file
View File

@ -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)
}

68
vendor/github.com/abema/go-mp4/write.go generated vendored Normal file
View File

@ -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
}