mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
[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:
1
vendor/github.com/abema/go-mp4/.gitignore
generated
vendored
Normal file
1
vendor/github.com/abema/go-mp4/.gitignore
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
vendor
|
21
vendor/github.com/abema/go-mp4/LICENSE
generated
vendored
Normal file
21
vendor/github.com/abema/go-mp4/LICENSE
generated
vendored
Normal 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
153
vendor/github.com/abema/go-mp4/README.md
generated
vendored
Normal file
@ -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)
|
||||
```
|
19
vendor/github.com/abema/go-mp4/anytype.go
generated
vendored
Normal file
19
vendor/github.com/abema/go-mp4/anytype.go
generated
vendored
Normal 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
8
vendor/github.com/abema/go-mp4/bitio/bitio.go
generated
vendored
Normal 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
97
vendor/github.com/abema/go-mp4/bitio/read.go
generated
vendored
Normal 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
61
vendor/github.com/abema/go-mp4/bitio/write.go
generated
vendored
Normal 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
188
vendor/github.com/abema/go-mp4/box.go
generated
vendored
Normal 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
155
vendor/github.com/abema/go-mp4/box_info.go
generated
vendored
Normal 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
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
98
vendor/github.com/abema/go-mp4/extract.go
generated
vendored
Normal 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
290
vendor/github.com/abema/go-mp4/field.go
generated
vendored
Normal 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
639
vendor/github.com/abema/go-mp4/marshaller.go
generated
vendored
Normal 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
151
vendor/github.com/abema/go-mp4/mp4.go
generated
vendored
Normal 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
673
vendor/github.com/abema/go-mp4/probe.go
generated
vendored
Normal 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
182
vendor/github.com/abema/go-mp4/read.go
generated
vendored
Normal 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
261
vendor/github.com/abema/go-mp4/string.go
generated
vendored
Normal 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
30
vendor/github.com/abema/go-mp4/util/io.go
generated
vendored
Normal 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
42
vendor/github.com/abema/go-mp4/util/string.go
generated
vendored
Normal 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
68
vendor/github.com/abema/go-mp4/write.go
generated
vendored
Normal 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
|
||||
}
|
Reference in New Issue
Block a user