diff --git a/atom/box.go b/atom/box.go index 6f534fa..00fcc87 100644 --- a/atom/box.go +++ b/atom/box.go @@ -17,6 +17,7 @@ type Mp4Reader struct { Reader io.ReaderAt Ftyp *FtypBox Moov *MoovBox + Moof *MoofBox Mdat *MdatBox Uuids []*UuidBox Size int64 @@ -48,6 +49,9 @@ func (m *Mp4Reader) Parse() error { m.Moov = &MoovBox{Box: box} _ = m.Moov.parse() m.IsFragmented = m.Moov.IsFragmented + case "moof": + m.Moof = &MoofBox{Box: box} + _ = m.Moof.parse() case "uuid": uuidBox := &UuidBox{Box: box} _ = uuidBox.parse() diff --git a/atom/mfhd.go b/atom/mfhd.go new file mode 100644 index 0000000..2da2d36 --- /dev/null +++ b/atom/mfhd.go @@ -0,0 +1,29 @@ +package atom + +import "encoding/binary" + +// MfhdBox - Movie Fragment Header Box +// Box Type: mfhd +// Container: Movie Fragment Box (moof) +// Mandatory: Yes +// Quantity: Exactly one. +// +// The movie fragment header contains a sequence number, as a safety check. The sequence number +// usually starts at 1 and increases for each movie fragment in the file, in the order in which they occur. +// This allows readers to verify integrity of the sequence in environments where undesired re‐ordering +// might occur. +type MfhdBox struct { + *Box + Version byte + Flags uint32 + SequenceNumber uint32 +} + +func (b *MfhdBox) parse() error { + data := b.ReadBoxData() + b.Version = data[0] + b.Flags = binary.BigEndian.Uint32(data[0:4]) + b.SequenceNumber = binary.BigEndian.Uint32(data[4:8]) + + return nil +} diff --git a/atom/moof.go b/atom/moof.go new file mode 100644 index 0000000..41cf22f --- /dev/null +++ b/atom/moof.go @@ -0,0 +1,35 @@ +package atom + +// MoofBox - Movie Fragment Box +// Box Type: moof +// Container: File +// Mandatory: No +// Quantity: Zero or more. +// +// The movie fragments extend the presentation in time. They provide the information that would +// previously have been in the Movie Box. The actual samples are in Media Data Boxes, as usual, if they are +// in the same file. The data reference index is in the sample description, so it is possible to build +// incremental presentations where the media data is in files other than the file containing the Movie Box. +type MoofBox struct { + *Box + Mfhd *MfhdBox + Traf []*TrafBox +} + +func (b *MoofBox) parse() error { + boxes := readBoxes(b.Reader, b.Start+BoxHeaderSize, b.Size-BoxHeaderSize) + + for _, box := range boxes { + switch box.Name { + case "mfhd": + b.Mfhd = &MfhdBox{Box: box} + b.Mfhd.parse() + + case "traf": + traf := &TrafBox{Box: box} + traf.parse() + b.Traf = append(b.Traf, traf) + } + } + return nil +} diff --git a/atom/tfhd.go b/atom/tfhd.go new file mode 100644 index 0000000..2418310 --- /dev/null +++ b/atom/tfhd.go @@ -0,0 +1,55 @@ +package atom + +import ( + "encoding/binary" +) + +// TfhdBox - Track Fragment Header Box +// Box Type: tfhd +// Container: Track Fragment Box (traf) +// Mandatory: Yes +// Quantity: Exactly one. +type TfhdBox struct { + *Box + Version byte + Flags uint32 + TrackID uint32 + //Optional fields + BaseDataOffset uint64 + SampleDescriptionIndex uint32 + DefaultSampleDuration uint32 + DefaultSampleSize uint32 + DefaultSampleFlags uint32 +} + +func (b *TfhdBox) parse() error { + data := b.ReadBoxData() + + b.Version = data[0] + b.Flags = binary.BigEndian.Uint32(data[0:4]) + b.TrackID = binary.BigEndian.Uint32(data[4:8]) + + oPos := 8 + if b.Flags&1 != 0 { + b.BaseDataOffset = binary.BigEndian.Uint64(data[16:24]) + oPos += 8 + } + if b.Flags&2 != 0 { + b.SampleDescriptionIndex = binary.BigEndian.Uint32(data[oPos : oPos+4]) + oPos += 4 + } + if b.Flags&8 != 0 { + b.DefaultSampleDuration = binary.BigEndian.Uint32(data[oPos : oPos+4]) + oPos += 4 + } + if b.Flags&10 != 0 { + b.DefaultSampleSize = binary.BigEndian.Uint32(data[oPos : oPos+4]) + oPos += 4 + } + if b.Flags&20 != 0 { + b.DefaultSampleFlags = binary.BigEndian.Uint32(data[oPos : oPos+4]) + oPos += 4 + } + + return nil +} diff --git a/atom/traf.go b/atom/traf.go new file mode 100644 index 0000000..f980721 --- /dev/null +++ b/atom/traf.go @@ -0,0 +1,29 @@ +package atom + +// TrafBox - Track Fragment Box +// Box Type: traf +// Container: Track Fragment Box (traf) +// Mandatory: Yes +// Quantity: Zero or more. +type TrafBox struct { + *Box + Tfhd *TfhdBox + Trun *TrunBox +} + +func (b *TrafBox) parse() error { + boxes := readBoxes(b.Reader, b.Start+BoxHeaderSize, b.Size-BoxHeaderSize) + + for _, box := range boxes { + switch box.Name { + case "tfhd": + b.Tfhd = &TfhdBox{Box: box} + b.Tfhd.parse() + + case "trun": + b.Trun = &TrunBox{Box: box} + b.Trun.parse() + } + } + return nil +} diff --git a/atom/trun.go b/atom/trun.go new file mode 100644 index 0000000..4cf4088 --- /dev/null +++ b/atom/trun.go @@ -0,0 +1,27 @@ +package atom + +import ( + "encoding/binary" +) + +// TrunBox - Track Fragment Run Box +// Box Type: trun +// Container: Track Fragment Box (traf) +// Mandatory: No +// Quantity: Zero or more. +type TrunBox struct { + *Box + Version byte + Flags uint32 + SampleCount uint32 + //TODO Optional fields not yet implemented +} + +func (b *TrunBox) parse() error { + data := b.ReadBoxData() + b.Version = data[0] + // b.Flags = [3]byte{data[1], data[2], data[3]} + b.Flags = binary.BigEndian.Uint32(data[0:4]) + b.SampleCount = binary.BigEndian.Uint32(data[4:8]) + return nil +} diff --git a/cmd/mp4info/mp4info.go b/cmd/mp4info/mp4info.go index eceaf1b..d73cc3d 100644 --- a/cmd/mp4info/mp4info.go +++ b/cmd/mp4info/mp4info.go @@ -33,7 +33,9 @@ Track: {{$trak.Tkhd.TrackID}} width: {{to16 $trak.Tkhd.Width}} height: {{to16 $trak.Tkhd.Height}} media: + {{- if (ne $trak.Mdia.Minf.Stbl.Stts.EntryCount 0) }} sample count: {{index $trak.Mdia.Minf.Stbl.Stts.SampleCounts 0}} + {{- end}} timescale: {{$trak.Mdia.Mdhd.Timescale}} duration: {{$trak.Mdia.Mdhd.Duration}} (media timescale units) duration: {{getDurationMS $trak.Mdia.Mdhd.Duration $trak.Mdia.Mdhd.Timescale}} (ms)