2022-12-17 04:38:56 +00:00
|
|
|
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)
|
|
|
|
|
2023-09-25 10:33:10 +00:00
|
|
|
go-mp4 is Go library and CLI tool which provide low-level I/O interfaces of MP4.
|
|
|
|
This library supports you to parse or build any MP4 boxes(atoms) directly.
|
2022-12-17 04:38:56 +00:00
|
|
|
|
|
|
|
## 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
|
2023-09-25 10:33:10 +00:00
|
|
|
go install github.com/abema/go-mp4/cmd/mp4tool@latest
|
2022-12-17 04:38:56 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
```
|