Skip to content

Commit

Permalink
mp4: Nicer major brand and handle some qt brand short strings better
Browse files Browse the repository at this point in the history
  • Loading branch information
wader committed Jul 26, 2023
1 parent 25c1394 commit 97194ad
Show file tree
Hide file tree
Showing 8 changed files with 529 additions and 511 deletions.
30 changes: 23 additions & 7 deletions format/mp4/boxes.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,10 @@ func decodeBoxesWithParentData(ctx *decodeContext, d *decode.D, parentData any,
}
}

type rootBox struct {
ftypMajorBrand string
}

type irefBox struct {
version int
}
Expand Down Expand Up @@ -358,11 +362,15 @@ func decodeBoxIrefEntry(ctx *decodeContext, d *decode.D) {
})
}

func decodeBoxFtyp(d *decode.D) {
brand := d.FieldUTF8("major_brand", 4)
func decodeBoxFtyp(ctx *decodeContext, d *decode.D) {
root := ctx.rootBox()

brand := d.FieldUTF8("major_brand", 4, scalar.ActualTrimSpace)
root.ftypMajorBrand = brand

d.FieldU32("minor_version", scalar.UintFn(func(s scalar.Uint) (scalar.Uint, error) {
switch brand {
case "qt ":
case "qt":
// https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap1/qtff1.html#//apple_ref/doc/uid/TP40000939-CH203-BBCGDDDF
// "For QuickTime movie files, this takes the form of four binary-coded decimal values, indicating the century,
// year, and month of the QuickTime File Format Specification, followed by a binary coded decimal zero. For example,
Expand All @@ -382,9 +390,9 @@ func decodeBoxFtyp(d *decode.D) {
func decodeBox(ctx *decodeContext, d *decode.D, typ string) {
switch typ {
case "ftyp":
decodeBoxFtyp(d)
decodeBoxFtyp(ctx, d)
case "styp":
decodeBoxFtyp(d)
decodeBoxFtyp(ctx, d)
case "mvhd":
version := d.FieldU8("version")
d.FieldU24("flags")
Expand Down Expand Up @@ -510,15 +518,23 @@ func decodeBox(ctx *decodeContext, d *decode.D, typ string) {
d.FieldU16("value")
})
case "hdlr":
majorBrand := ctx.rootBox().ftypMajorBrand

d.FieldU8("version")
d.FieldU24("flags")
d.FieldUTF8NullFixedLen("component_type", 4)
subType := d.FieldUTF8("component_subtype", 4, subTypeNames, scalar.ActualTrimSpace)
d.FieldUTF8NullFixedLen("component_manufacturer", 4)
d.FieldU32("component_flags")
d.FieldU32("component_flags_mask")
// TODO: sometimes has a length prefix byte, how to know?
d.FieldUTF8NullFixedLen("component_name", int(d.BitsLeft()/8))

switch majorBrand {
case "qt":
// qt brand seems to use length prefixed strings
d.FieldUTF8ShortStringFixedLen("component_name", int(d.BitsLeft()/8))
default:
d.FieldUTF8NullFixedLen("component_name", int(d.BitsLeft()/8))
}

if t := ctx.currentTrack(); t != nil {
t.seenHdlr = true
Expand Down
7 changes: 7 additions & 0 deletions format/mp4/mp4.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@ func (ctx *decodeContext) findParent(typ string) any {
return nil
}

func (ctx *decodeContext) rootBox() *rootBox {
t, _ := ctx.findParent("").(*rootBox)
return t
}

func (ctx *decodeContext) currentTrakBox() *trakBox {
t, _ := ctx.findParent("trak").(*trakBox)
return t
Expand Down Expand Up @@ -448,6 +453,8 @@ func mp4Decode(d *decode.D) any {

d.SeekRel(-8 * 8)

ctx.path = []pathEntry{{typ: "", data: &rootBox{}}}

decodeBoxes(ctx, d)
if len(ctx.tracks) > 0 {
mp4Tracks(d, ctx)
Expand Down
6 changes: 3 additions & 3 deletions format/mp4/testdata/in24.fqtest
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ $ fq dv in24.mp4
| | | [0]{}: box 0x0-0x13.7 (20)
0x000|00 00 00 14 |.... | size: 20 0x0-0x3.7 (4)
0x000| 66 74 79 70 | ftyp | type: "ftyp" (File type and compatibility) 0x4-0x7.7 (4)
0x000| 71 74 20 20 | qt | major_brand: "qt " 0x8-0xb.7 (4)
0x000| 71 74 20 20 | qt | major_brand: "qt" 0x8-0xb.7 (4)
0x000| 00 00 02 00| ....| minor_version: 512 (0000.02) 0xc-0xf.7 (4)
| | | brands[0:1]: 0x10-0x13.7 (4)
0x010|71 74 20 20 |qt | [0]: "qt" brand 0x10-0x13.7 (4)
Expand Down Expand Up @@ -128,7 +128,7 @@ $ fq dv in24.mp4
0x260| 00 00 00 00 | .... | component_manufacturer: "" 0x264-0x267.7 (4)
0x260| 00 00 00 00 | .... | component_flags: 0 0x268-0x26b.7 (4)
0x260| 00 00 00 00| ....| component_flags_mask: 0 0x26c-0x26f.7 (4)
0x270|0c 53 6f 75 6e 64 48 61 6e 64 6c 65 72 |.SoundHandler | component_name: "\fSoundHandler" 0x270-0x27c.7 (13)
0x270|0c 53 6f 75 6e 64 48 61 6e 64 6c 65 72 |.SoundHandler | component_name: "SoundHandler" 0x270-0x27c.7 (13)
| | | [2]{}: box 0x27d-0x3ca.7 (334)
0x270| 00 00 01| ...| size: 334 0x27d-0x280.7 (4)
0x280|4e |N |
Expand All @@ -154,7 +154,7 @@ $ fq dv in24.mp4
0x2a0| 00 00 00| ...| component_flags: 0 0x2ad-0x2b0.7 (4)
0x2b0|00 |. |
0x2b0| 00 00 00 00 | .... | component_flags_mask: 0 0x2b1-0x2b4.7 (4)
0x2b0| 0b 44 61 74 61 48 61 6e 64 6c 65| .DataHandle| component_name: "\vDataHandler" 0x2b5-0x2c0.7 (12)
0x2b0| 0b 44 61 74 61 48 61 6e 64 6c 65| .DataHandle| component_name: "DataHandler" 0x2b5-0x2c0.7 (12)
0x2c0|72 |r |
| | | [2]{}: box 0x2c1-0x2e4.7 (36)
0x2c0| 00 00 00 24 | ...$ | size: 36 0x2c1-0x2c4.7 (4)
Expand Down
6 changes: 3 additions & 3 deletions format/mp4/testdata/lpcm.fqtest
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ $ fq dv lpcm.mp4
| | | [0]{}: box 0x0-0x13.7 (20)
0x000|00 00 00 14 |.... | size: 20 0x0-0x3.7 (4)
0x000| 66 74 79 70 | ftyp | type: "ftyp" (File type and compatibility) 0x4-0x7.7 (4)
0x000| 71 74 20 20 | qt | major_brand: "qt " 0x8-0xb.7 (4)
0x000| 71 74 20 20 | qt | major_brand: "qt" 0x8-0xb.7 (4)
0x000| 00 00 02 00| ....| minor_version: 512 (0000.02) 0xc-0xf.7 (4)
| | | brands[0:1]: 0x10-0x13.7 (4)
0x010|71 74 20 20 |qt | [0]: "qt" brand 0x10-0x13.7 (4)
Expand Down Expand Up @@ -129,7 +129,7 @@ $ fq dv lpcm.mp4
0x390| 00 00 00 00| ....| component_manufacturer: "" 0x39c-0x39f.7 (4)
0x3a0|00 00 00 00 |.... | component_flags: 0 0x3a0-0x3a3.7 (4)
0x3a0| 00 00 00 00 | .... | component_flags_mask: 0 0x3a4-0x3a7.7 (4)
0x3a0| 0c 53 6f 75 6e 64 48 61| .SoundHa| component_name: "\fSoundHandler" 0x3a8-0x3b4.7 (13)
0x3a0| 0c 53 6f 75 6e 64 48 61| .SoundHa| component_name: "SoundHandler" 0x3a8-0x3b4.7 (13)
0x3b0|6e 64 6c 65 72 |ndler |
| | | [2]{}: box 0x3b5-0x4f0.7 (316)
0x3b0| 00 00 01 3c | ...< | size: 316 0x3b5-0x3b8.7 (4)
Expand All @@ -155,7 +155,7 @@ $ fq dv lpcm.mp4
0x3e0| 00 00 00 00 | .... | component_manufacturer: "" 0x3e1-0x3e4.7 (4)
0x3e0| 00 00 00 00 | .... | component_flags: 0 0x3e5-0x3e8.7 (4)
0x3e0| 00 00 00 00 | .... | component_flags_mask: 0 0x3e9-0x3ec.7 (4)
0x3e0| 0b 44 61| .Da| component_name: "\vDataHandler" 0x3ed-0x3f8.7 (12)
0x3e0| 0b 44 61| .Da| component_name: "DataHandler" 0x3ed-0x3f8.7 (12)
0x3f0|74 61 48 61 6e 64 6c 65 72 |taHandler |
| | | [2]{}: box 0x3f9-0x41c.7 (36)
0x3f0| 00 00 00 24 | ...$ | size: 36 0x3f9-0x3fc.7 (4)
Expand Down
6 changes: 3 additions & 3 deletions format/prores/testdata/prores_frame.fqtest
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ $ fq -d mp4 dv prores_frame.mov
| | | [0]{}: box 0x0-0x13.7 (20)
0x0000|00 00 00 14 |.... | size: 20 0x0-0x3.7 (4)
0x0000| 66 74 79 70 | ftyp | type: "ftyp" (File type and compatibility) 0x4-0x7.7 (4)
0x0000| 71 74 20 20 | qt | major_brand: "qt " 0x8-0xb.7 (4)
0x0000| 71 74 20 20 | qt | major_brand: "qt" 0x8-0xb.7 (4)
0x0000| 00 00 02 00| ....| minor_version: 512 (0000.02) 0xc-0xf.7 (4)
| | | brands[0:1]: 0x10-0x13.7 (4)
0x0010|71 74 20 20 |qt | [0]: "qt" brand 0x10-0x13.7 (4)
Expand Down Expand Up @@ -149,7 +149,7 @@ $ fq -d mp4 dv prores_frame.mov
0x6d70| 00 00 00 00 | .... | component_flags: 0 0x6d79-0x6d7c.7 (4)
0x6d70| 00 00 00| ...| component_flags_mask: 0 0x6d7d-0x6d80.7 (4)
0x6d80|00 |. |
0x6d80| 0c 56 69 64 65 6f 48 61 6e 64 6c 65 72 | .VideoHandler | component_name: "\fVideoHandler" 0x6d81-0x6d8d.7 (13)
0x6d80| 0c 56 69 64 65 6f 48 61 6e 64 6c 65 72 | .VideoHandler | component_name: "VideoHandler" 0x6d81-0x6d8d.7 (13)
| | | [2]{}: box 0x6d8e-0x6edd.7 (336)
0x6d80| 00 00| ..| size: 336 0x6d8e-0x6d91.7 (4)
0x6d90|01 50 |.P |
Expand Down Expand Up @@ -178,7 +178,7 @@ $ fq -d mp4 dv prores_frame.mov
0x6dc0|00 00 |.. |
0x6dc0| 00 00 00 00 | .... | component_flags: 0 0x6dc2-0x6dc5.7 (4)
0x6dc0| 00 00 00 00 | .... | component_flags_mask: 0 0x6dc6-0x6dc9.7 (4)
0x6dc0| 0b 44 61 74 61 48| .DataH| component_name: "\vDataHandler" 0x6dca-0x6dd5.7 (12)
0x6dc0| 0b 44 61 74 61 48| .DataH| component_name: "DataHandler" 0x6dca-0x6dd5.7 (12)
0x6dd0|61 6e 64 6c 65 72 |andler |
| | | [2]{}: box 0x6dd6-0x6df9.7 (36)
0x6dd0| 00 00 00 24 | ...$ | size: 36 0x6dd6-0x6dd9.7 (4)
Expand Down
12 changes: 6 additions & 6 deletions pkg/decode/decode_gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -19509,11 +19509,11 @@ func (d *D) FieldUTF16BE(name string, nBytes int, sms ...scalar.StrMapper) strin
// Reader UTF8ShortString

// TryUTF8ShortString tries to read one byte length fixed UTF8 string
func (d *D) TryUTF8ShortString() (string, error) { return d.tryTextLenPrefixed(8, -1, UTF8BOM) }
func (d *D) TryUTF8ShortString() (string, error) { return d.tryTextLenPrefixed(1, -1, UTF8BOM) }

// UTF8ShortString reads one byte length fixed UTF8 string
func (d *D) UTF8ShortString() string {
v, err := d.tryTextLenPrefixed(8, -1, UTF8BOM)
v, err := d.tryTextLenPrefixed(1, -1, UTF8BOM)
if err != nil {
panic(IOError{Err: err, Op: "UTF8ShortString", Pos: d.Pos()})
}
Expand All @@ -19523,7 +19523,7 @@ func (d *D) UTF8ShortString() string {
// TryFieldScalarUTF8ShortString tries to add a field and read one byte length fixed UTF8 string
func (d *D) TryFieldScalarUTF8ShortString(name string, sms ...scalar.StrMapper) (*scalar.Str, error) {
s, err := d.TryFieldScalarStrFn(name, func(d *D) (scalar.Str, error) {
v, err := d.tryTextLenPrefixed(8, -1, UTF8BOM)
v, err := d.tryTextLenPrefixed(1, -1, UTF8BOM)
return scalar.Str{Actual: v}, err
}, sms...)
if err != nil {
Expand Down Expand Up @@ -19556,12 +19556,12 @@ func (d *D) FieldUTF8ShortString(name string, sms ...scalar.StrMapper) string {

// TryUTF8ShortStringFixedLen tries to read fixedBytes bytes long one byte length prefixed UTF8 string
func (d *D) TryUTF8ShortStringFixedLen(fixedBytes int) (string, error) {
return d.tryTextLenPrefixed(8, fixedBytes, UTF8BOM)
return d.tryTextLenPrefixed(1, fixedBytes, UTF8BOM)
}

// UTF8ShortStringFixedLen reads fixedBytes bytes long one byte length prefixed UTF8 string
func (d *D) UTF8ShortStringFixedLen(fixedBytes int) string {
v, err := d.tryTextLenPrefixed(8, fixedBytes, UTF8BOM)
v, err := d.tryTextLenPrefixed(1, fixedBytes, UTF8BOM)
if err != nil {
panic(IOError{Err: err, Op: "UTF8ShortStringFixedLen", Pos: d.Pos()})
}
Expand All @@ -19571,7 +19571,7 @@ func (d *D) UTF8ShortStringFixedLen(fixedBytes int) string {
// TryFieldScalarUTF8ShortStringFixedLen tries to add a field and read fixedBytes bytes long one byte length prefixed UTF8 string
func (d *D) TryFieldScalarUTF8ShortStringFixedLen(name string, fixedBytes int, sms ...scalar.StrMapper) (*scalar.Str, error) {
s, err := d.TryFieldScalarStrFn(name, func(d *D) (scalar.Str, error) {
v, err := d.tryTextLenPrefixed(8, fixedBytes, UTF8BOM)
v, err := d.tryTextLenPrefixed(1, fixedBytes, UTF8BOM)
return scalar.Str{Actual: v}, err
}, sms...)
if err != nil {
Expand Down
25 changes: 10 additions & 15 deletions pkg/decode/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,43 +146,38 @@ func (d *D) tryText(nBytes int, e encoding.Encoding) (string, error) {
}

// read length prefixed text (ex pascal short string)
// lBits length prefix
// lenBytes length prefix
// fixedBytes if != -1 read nBytes but trim to length
//
//nolint:unparam
func (d *D) tryTextLenPrefixed(lenBits int, fixedBytes int, e encoding.Encoding) (string, error) {
if lenBits < 0 {
return "", fmt.Errorf("tryTextLenPrefixed lenBits must be >= 0 (%d)", lenBits)
}
if fixedBytes < 0 {
return "", fmt.Errorf("tryTextLenPrefixed fixedBytes must be >= 0 (%d)", fixedBytes)
func (d *D) tryTextLenPrefixed(prefixLenBytes int, fixedBytes int, e encoding.Encoding) (string, error) {
if prefixLenBytes < 0 {
return "", fmt.Errorf("tryTextLenPrefixed lenBytes must be >= 0 (%d)", prefixLenBytes)
}
bytesLeft := d.BitsLeft() / 8
if int64(fixedBytes) > bytesLeft {
return "", fmt.Errorf("tryTextLenPrefixed fixedBytes %d outside, %d bytes left", fixedBytes, bytesLeft)
}

p := d.Pos()
l, err := d.TryUintBits(lenBits)
lenBytes, err := d.TryUintBits(prefixLenBytes * 8)
if err != nil {
return "", err
}

n := int(l)
readBytes := int(lenBytes)
if fixedBytes != -1 {
n = fixedBytes - 1
// TODO: error?
if l > uint64(n) {
l = uint64(n)
}
readBytes = fixedBytes - prefixLenBytes
lenBytes = mathex.Min(lenBytes, uint64(readBytes))
}

bs, err := d.TryBytesLen(n)
bs, err := d.TryBytesLen(readBytes)
if err != nil {
d.SeekAbs(p)
return "", err
}
return e.NewDecoder().String(string(bs[0:l]))
return e.NewDecoder().String(string(bs[0:lenBytes]))
}

func (d *D) tryTextNull(charBytes int, e encoding.Encoding) (string, error) {
Expand Down

0 comments on commit 97194ad

Please sign in to comment.