diff --git a/hdlr.go b/hdlr.go index a9ec6da..4a20eb6 100644 --- a/hdlr.go +++ b/hdlr.go @@ -1,5 +1,7 @@ package mp4 +import "io" + func BoxTypeHdlr() BoxType { return StrToBoxType("hdlr") } func init() { @@ -19,3 +21,30 @@ type Hdlr struct { func (*Hdlr) GetType() BoxType { return BoxTypeHdlr() } + +const ( + hdlrSizeWithoutName = 24 // FullBox header(4bytes) + PreDefined(4bytes) + HandlerType(4bytes) + Reserved(12bytes) +) + +// handle a special case: the QuickTime files have a pascal +// string here, but ISO MP4 files have a C string. +// we try to detect a pascal encoding and correct it. +func (hdlr *Hdlr) unmarshalHandlerName(u *unmarshaller) error { + nameSize := u.size - hdlrSizeWithoutName + if nameSize <= 0 { + return nil + } + if _, err := u.reader.Seek(-int64(nameSize), io.SeekCurrent); err != nil { + return err + } + u.rbytes = hdlrSizeWithoutName + nameb := make([]byte, nameSize) + if _, err := io.ReadFull(u.reader, nameb); err != nil { + return err + } + u.rbytes += nameSize + if nameb[0] != 0x00 && nameb[0] == byte(nameSize-1) { + hdlr.Name = string(nameb[1:]) + } + return nil +} diff --git a/hdlr_test.go b/hdlr_test.go new file mode 100644 index 0000000..ae43117 --- /dev/null +++ b/hdlr_test.go @@ -0,0 +1,45 @@ +package mp4 + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestHdlrUnmarshalHandlerName(t *testing.T) { + testCases := []struct { + name string + bytes []byte + want string + }{ + {name: "NormalString", bytes: []byte("abema"), want: "abema"}, + {name: "EmptyString", bytes: nil, want: ""}, + {name: "AppleQuickTimePascalString", bytes: []byte{5, 'a', 'b', 'e', 'm', 'a'}, want: "abema"}, + {name: "AppleQuickTimePascalStringWithEmpty", bytes: []byte{0x00, 0x00}, want: ""}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + bin := []byte{ + 0, // version + 0x00, 0x00, 0x00, // flags + 0x00, 0x00, 0x00, 0x00, // predefined + 'v', 'i', 'd', 'e', // handler type + 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x00, 0x00, 0x00, // reserved + } + bin = append(bin, tc.bytes...) + + // unmarshal + dst := Hdlr{} + r := bytes.NewReader(bin) + n, err := Unmarshal(r, uint64(len(bin)), &dst) + assert.NoError(t, err) + assert.Equal(t, uint64(len(bin)), n) + assert.Equal(t, [4]byte{'v', 'i', 'd', 'e'}, dst.HandlerType) + assert.Equal(t, tc.want, dst.Name) + }) + } +} diff --git a/marshaller.go b/marshaller.go index 139dae0..d07a934 100644 --- a/marshaller.go +++ b/marshaller.go @@ -293,6 +293,14 @@ func Unmarshal(r io.ReadSeeker, payloadSize uint64, dst IBox) (n uint64, err err return 0, fmt.Errorf("overrun error: type=%s, size=%d, readBytes=%d, width=%d", dst.GetType().String(), u.size, u.rbytes, u.width) } + // for Apple Quick Time (hdlr handlerName) + if dst.GetType() == BoxTypeHdlr() { + hdlr := dst.(*Hdlr) + if err := hdlr.unmarshalHandlerName(u); err != nil { + return 0, err + } + } + // for Apple Quick Time if dst.GetType() == BoxTypeMeta() && (dst.GetVersion() != 0 || dst.GetFlags() != 0) { if _, err := r.Seek(-4, io.SeekCurrent); err != nil {