834 lines
19 KiB
Go
834 lines
19 KiB
Go
|
/*
|
||
|
Copyright 2018 The go4 Authors
|
||
|
|
||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
you may not use this file except in compliance with the License.
|
||
|
You may obtain a copy of the License at
|
||
|
|
||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
||
|
Unless required by applicable law or agreed to in writing, software
|
||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
See the License for the specific language governing permissions and
|
||
|
limitations under the License.
|
||
|
*/
|
||
|
|
||
|
// Package bmff reads ISO BMFF boxes, as used by HEIF, etc.
|
||
|
//
|
||
|
// This is not so much as a generic BMFF reader as it is a BMFF reader
|
||
|
// as needed by HEIF, though that may change in time. For now, only
|
||
|
// boxes necessary for the go4.org/media/heif package have explicit
|
||
|
// parsers.
|
||
|
//
|
||
|
// This package makes no API compatibility promises; it exists
|
||
|
// primarily for use by the go4.org/media/heif package.
|
||
|
package bmff
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"bytes"
|
||
|
"encoding/binary"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"io/ioutil"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
func NewReader(r io.Reader) *Reader {
|
||
|
br, ok := r.(*bufio.Reader)
|
||
|
if !ok {
|
||
|
br = bufio.NewReader(r)
|
||
|
}
|
||
|
return &Reader{br: bufReader{Reader: br}}
|
||
|
}
|
||
|
|
||
|
type Reader struct {
|
||
|
br bufReader
|
||
|
lastBox Box // or nil
|
||
|
noMoreBoxes bool // a box with size 0 (the final box) was seen
|
||
|
}
|
||
|
|
||
|
type BoxType [4]byte
|
||
|
|
||
|
// Common box types.
|
||
|
var (
|
||
|
TypeFtyp = BoxType{'f', 't', 'y', 'p'}
|
||
|
TypeMeta = BoxType{'m', 'e', 't', 'a'}
|
||
|
)
|
||
|
|
||
|
func (t BoxType) String() string { return string(t[:]) }
|
||
|
|
||
|
func (t BoxType) EqualString(s string) bool {
|
||
|
// Could be cleaner, but see ohttps://github.com/golang/go/issues/24765
|
||
|
return len(s) == 4 && s[0] == t[0] && s[1] == t[1] && s[2] == t[2] && s[3] == t[3]
|
||
|
}
|
||
|
|
||
|
type parseFunc func(b box, br *bufio.Reader) (Box, error)
|
||
|
|
||
|
// Box represents a BMFF box.
|
||
|
type Box interface {
|
||
|
Size() int64 // 0 means unknown (will read to end of file)
|
||
|
Type() BoxType
|
||
|
|
||
|
// Parses parses the box, populating the fields
|
||
|
// in the returned concrete type.
|
||
|
//
|
||
|
// If Parse has already been called, Parse returns nil.
|
||
|
// If the box type is unknown, the returned error is ErrUnknownBox
|
||
|
// and it's guaranteed that no bytes have been read from the box.
|
||
|
Parse() (Box, error)
|
||
|
|
||
|
// Body returns the inner bytes of the box, ignoring the header.
|
||
|
// The body may start with the 4 byte header of a "Full Box" if the
|
||
|
// box's type derives from a full box. Most users will use Parse
|
||
|
// instead.
|
||
|
// Body will return a new reader at the beginning of the box if the
|
||
|
// outer box has already been parsed.
|
||
|
Body() io.Reader
|
||
|
}
|
||
|
|
||
|
// ErrUnknownBox is returned by Box.Parse for unrecognized box types.
|
||
|
var ErrUnknownBox = errors.New("heif: unknown box")
|
||
|
|
||
|
type parserFunc func(b *box, br *bufReader) (Box, error)
|
||
|
|
||
|
func boxType(s string) BoxType {
|
||
|
if len(s) != 4 {
|
||
|
panic("bogus boxType length")
|
||
|
}
|
||
|
return BoxType{s[0], s[1], s[2], s[3]}
|
||
|
}
|
||
|
|
||
|
var parsers = map[BoxType]parserFunc{
|
||
|
boxType("dinf"): parseDataInformationBox,
|
||
|
boxType("dref"): parseDataReferenceBox,
|
||
|
boxType("ftyp"): parseFileTypeBox,
|
||
|
boxType("hdlr"): parseHandlerBox,
|
||
|
boxType("iinf"): parseItemInfoBox,
|
||
|
boxType("infe"): parseItemInfoEntry,
|
||
|
boxType("iloc"): parseItemLocationBox,
|
||
|
boxType("ipco"): parseItemPropertyContainerBox,
|
||
|
boxType("ipma"): parseItemPropertyAssociation,
|
||
|
boxType("iprp"): parseItemPropertiesBox,
|
||
|
boxType("irot"): parseImageRotation,
|
||
|
boxType("ispe"): parseImageSpatialExtentsProperty,
|
||
|
boxType("meta"): parseMetaBox,
|
||
|
boxType("pitm"): parsePrimaryItemBox,
|
||
|
}
|
||
|
|
||
|
type box struct {
|
||
|
size int64 // 0 means unknown, will read to end of file (box container)
|
||
|
boxType BoxType
|
||
|
body io.Reader
|
||
|
parsed Box // if non-nil, the Parsed result
|
||
|
slurp []byte // if non-nil, the contents slurped to memory
|
||
|
}
|
||
|
|
||
|
func (b *box) Size() int64 { return b.size }
|
||
|
func (b *box) Type() BoxType { return b.boxType }
|
||
|
|
||
|
func (b *box) Body() io.Reader {
|
||
|
if b.slurp != nil {
|
||
|
return bytes.NewReader(b.slurp)
|
||
|
}
|
||
|
return b.body
|
||
|
}
|
||
|
|
||
|
func (b *box) Parse() (Box, error) {
|
||
|
if b.parsed != nil {
|
||
|
return b.parsed, nil
|
||
|
}
|
||
|
parser, ok := parsers[b.Type()]
|
||
|
if !ok {
|
||
|
return nil, ErrUnknownBox
|
||
|
}
|
||
|
v, err := parser(b, &bufReader{Reader: bufio.NewReader(b.Body())})
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
b.parsed = v
|
||
|
return v, nil
|
||
|
}
|
||
|
|
||
|
type FullBox struct {
|
||
|
*box
|
||
|
Version uint8
|
||
|
Flags uint32 // 24 bits
|
||
|
}
|
||
|
|
||
|
// ReadBox reads the next box.
|
||
|
//
|
||
|
// If the previously read box was not read to completion, ReadBox consumes
|
||
|
// the rest of its data.
|
||
|
//
|
||
|
// At the end, the error is io.EOF.
|
||
|
func (r *Reader) ReadBox() (Box, error) {
|
||
|
if r.noMoreBoxes {
|
||
|
return nil, io.EOF
|
||
|
}
|
||
|
if r.lastBox != nil {
|
||
|
if _, err := io.Copy(ioutil.Discard, r.lastBox.Body()); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
var buf [8]byte
|
||
|
|
||
|
_, err := io.ReadFull(r.br, buf[:4])
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
box := &box{
|
||
|
size: int64(binary.BigEndian.Uint32(buf[:4])),
|
||
|
}
|
||
|
|
||
|
_, err = io.ReadFull(r.br, box.boxType[:]) // 4 more bytes
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// Special cases for size:
|
||
|
var remain int64
|
||
|
switch box.size {
|
||
|
case 1:
|
||
|
// 1 means it's actually a 64-bit size, after the type.
|
||
|
_, err = io.ReadFull(r.br, buf[:8])
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
box.size = int64(binary.BigEndian.Uint64(buf[:8]))
|
||
|
if box.size < 0 {
|
||
|
// Go uses int64 for sizes typically, but BMFF uses uint64.
|
||
|
// We assume for now that nobody actually uses boxes larger
|
||
|
// than int64.
|
||
|
return nil, fmt.Errorf("unexpectedly large box %q", box.boxType)
|
||
|
}
|
||
|
remain = box.size - 2*4 - 8
|
||
|
case 0:
|
||
|
// 0 means unknown & to read to end of file. No more boxes.
|
||
|
r.noMoreBoxes = true
|
||
|
default:
|
||
|
remain = box.size - 2*4
|
||
|
}
|
||
|
if remain < 0 {
|
||
|
return nil, fmt.Errorf("Box header for %q has size %d, suggesting %d (negative) bytes remain", box.boxType, box.size, remain)
|
||
|
}
|
||
|
if box.size > 0 {
|
||
|
box.body = io.LimitReader(r.br, remain)
|
||
|
} else {
|
||
|
box.body = r.br
|
||
|
}
|
||
|
r.lastBox = box
|
||
|
return box, nil
|
||
|
}
|
||
|
|
||
|
// ReadAndParseBox wraps the ReadBox method, ensuring that the read box is of type typ
|
||
|
// and parses successfully. It returns the parsed box.
|
||
|
func (r *Reader) ReadAndParseBox(typ BoxType) (Box, error) {
|
||
|
box, err := r.ReadBox()
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("error reading %q box: %v", typ, err)
|
||
|
}
|
||
|
if box.Type() != typ {
|
||
|
return nil, fmt.Errorf("error reading %q box: got box type %q instead", typ, box.Type())
|
||
|
}
|
||
|
pbox, err := box.Parse()
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("error parsing read %q box: %v", typ, err)
|
||
|
}
|
||
|
return pbox, nil
|
||
|
}
|
||
|
|
||
|
func readFullBox(outer *box, br *bufReader) (fb FullBox, err error) {
|
||
|
fb.box = outer
|
||
|
// Parse FullBox header.
|
||
|
buf, err := br.Peek(4)
|
||
|
if err != nil {
|
||
|
return FullBox{}, fmt.Errorf("failed to read 4 bytes of FullBox: %v", err)
|
||
|
}
|
||
|
fb.Version = buf[0]
|
||
|
buf[0] = 0
|
||
|
fb.Flags = binary.BigEndian.Uint32(buf[:4])
|
||
|
br.Discard(4)
|
||
|
return fb, nil
|
||
|
}
|
||
|
|
||
|
type FileTypeBox struct {
|
||
|
*box
|
||
|
MajorBrand string // 4 bytes
|
||
|
MinorVersion string // 4 bytes
|
||
|
Compatible []string // all 4 bytes
|
||
|
}
|
||
|
|
||
|
func parseFileTypeBox(outer *box, br *bufReader) (Box, error) {
|
||
|
buf, err := br.Peek(8)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
ft := &FileTypeBox{
|
||
|
box: outer,
|
||
|
MajorBrand: string(buf[:4]),
|
||
|
MinorVersion: string(buf[4:8]),
|
||
|
}
|
||
|
br.Discard(8)
|
||
|
for {
|
||
|
buf, err := br.Peek(4)
|
||
|
if err == io.EOF {
|
||
|
return ft, nil
|
||
|
}
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
ft.Compatible = append(ft.Compatible, string(buf[:4]))
|
||
|
br.Discard(4)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type MetaBox struct {
|
||
|
FullBox
|
||
|
Children []Box
|
||
|
}
|
||
|
|
||
|
func parseMetaBox(outer *box, br *bufReader) (Box, error) {
|
||
|
fb, err := readFullBox(outer, br)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
mb := &MetaBox{FullBox: fb}
|
||
|
return mb, br.parseAppendBoxes(&mb.Children)
|
||
|
}
|
||
|
|
||
|
func (br *bufReader) parseAppendBoxes(dst *[]Box) error {
|
||
|
if br.err != nil {
|
||
|
return br.err
|
||
|
}
|
||
|
boxr := NewReader(br.Reader)
|
||
|
for {
|
||
|
inner, err := boxr.ReadBox()
|
||
|
if err == io.EOF {
|
||
|
return nil
|
||
|
}
|
||
|
if err != nil {
|
||
|
br.err = err
|
||
|
return err
|
||
|
}
|
||
|
slurp, err := ioutil.ReadAll(inner.Body())
|
||
|
if err != nil {
|
||
|
br.err = err
|
||
|
return err
|
||
|
}
|
||
|
inner.(*box).slurp = slurp
|
||
|
*dst = append(*dst, inner)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ItemInfoEntry represents an "infe" box.
|
||
|
//
|
||
|
// TODO: currently only parses Version 2 boxes.
|
||
|
type ItemInfoEntry struct {
|
||
|
FullBox
|
||
|
|
||
|
ItemID uint16
|
||
|
ProtectionIndex uint16
|
||
|
ItemType string // always 4 bytes
|
||
|
|
||
|
Name string
|
||
|
|
||
|
// If Type == "mime":
|
||
|
ContentType string
|
||
|
ContentEncoding string
|
||
|
|
||
|
// If Type == "uri ":
|
||
|
ItemURIType string
|
||
|
}
|
||
|
|
||
|
func parseItemInfoEntry(outer *box, br *bufReader) (Box, error) {
|
||
|
fb, err := readFullBox(outer, br)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
ie := &ItemInfoEntry{FullBox: fb}
|
||
|
if fb.Version != 2 {
|
||
|
return nil, fmt.Errorf("TODO: found version %d infe box. Only 2 is supported now.", fb.Version)
|
||
|
}
|
||
|
|
||
|
ie.ItemID, _ = br.readUint16()
|
||
|
ie.ProtectionIndex, _ = br.readUint16()
|
||
|
if !br.ok() {
|
||
|
return nil, br.err
|
||
|
}
|
||
|
buf, err := br.Peek(4)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
ie.ItemType = string(buf[:4])
|
||
|
ie.Name, _ = br.readString()
|
||
|
|
||
|
switch ie.ItemType {
|
||
|
case "mime":
|
||
|
ie.ContentType, _ = br.readString()
|
||
|
if br.anyRemain() {
|
||
|
ie.ContentEncoding, _ = br.readString()
|
||
|
}
|
||
|
case "uri ":
|
||
|
ie.ItemURIType, _ = br.readString()
|
||
|
}
|
||
|
if !br.ok() {
|
||
|
return nil, br.err
|
||
|
}
|
||
|
return ie, nil
|
||
|
}
|
||
|
|
||
|
// ItemInfoBox represents an "iinf" box.
|
||
|
type ItemInfoBox struct {
|
||
|
FullBox
|
||
|
Count uint16
|
||
|
ItemInfos []*ItemInfoEntry
|
||
|
}
|
||
|
|
||
|
func parseItemInfoBox(outer *box, br *bufReader) (Box, error) {
|
||
|
fb, err := readFullBox(outer, br)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
ib := &ItemInfoBox{FullBox: fb}
|
||
|
|
||
|
ib.Count, _ = br.readUint16()
|
||
|
|
||
|
var itemInfos []Box
|
||
|
br.parseAppendBoxes(&itemInfos)
|
||
|
if br.ok() {
|
||
|
for _, box := range itemInfos {
|
||
|
pb, err := box.Parse()
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("error parsing ItemInfoEntry in ItemInfoBox: %v", err)
|
||
|
}
|
||
|
if iie, ok := pb.(*ItemInfoEntry); ok {
|
||
|
ib.ItemInfos = append(ib.ItemInfos, iie)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if !br.ok() {
|
||
|
return FullBox{}, br.err
|
||
|
}
|
||
|
return ib, nil
|
||
|
}
|
||
|
|
||
|
// bufReader adds some HEIF/BMFF-specific methods around a *bufio.Reader.
|
||
|
type bufReader struct {
|
||
|
*bufio.Reader
|
||
|
err error // sticky error
|
||
|
}
|
||
|
|
||
|
// ok reports whether all previous reads have been error-free.
|
||
|
func (br *bufReader) ok() bool { return br.err == nil }
|
||
|
|
||
|
func (br *bufReader) anyRemain() bool {
|
||
|
if br.err != nil {
|
||
|
return false
|
||
|
}
|
||
|
_, err := br.Peek(1)
|
||
|
return err == nil
|
||
|
}
|
||
|
|
||
|
func (br *bufReader) readUintN(bits uint8) (uint64, error) {
|
||
|
if br.err != nil {
|
||
|
return 0, br.err
|
||
|
}
|
||
|
if bits == 0 {
|
||
|
return 0, nil
|
||
|
}
|
||
|
nbyte := bits / 8
|
||
|
buf, err := br.Peek(int(nbyte))
|
||
|
if err != nil {
|
||
|
br.err = err
|
||
|
return 0, err
|
||
|
}
|
||
|
defer br.Discard(int(nbyte))
|
||
|
switch bits {
|
||
|
case 8:
|
||
|
return uint64(buf[0]), nil
|
||
|
case 16:
|
||
|
return uint64(binary.BigEndian.Uint16(buf[:2])), nil
|
||
|
case 32:
|
||
|
return uint64(binary.BigEndian.Uint32(buf[:4])), nil
|
||
|
case 64:
|
||
|
return binary.BigEndian.Uint64(buf[:8]), nil
|
||
|
default:
|
||
|
br.err = fmt.Errorf("invalid uintn read size")
|
||
|
return 0, br.err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (br *bufReader) readUint8() (uint8, error) {
|
||
|
if br.err != nil {
|
||
|
return 0, br.err
|
||
|
}
|
||
|
v, err := br.ReadByte()
|
||
|
if err != nil {
|
||
|
br.err = err
|
||
|
return 0, err
|
||
|
}
|
||
|
return v, nil
|
||
|
}
|
||
|
|
||
|
func (br *bufReader) readUint16() (uint16, error) {
|
||
|
if br.err != nil {
|
||
|
return 0, br.err
|
||
|
}
|
||
|
buf, err := br.Peek(2)
|
||
|
if err != nil {
|
||
|
br.err = err
|
||
|
return 0, err
|
||
|
}
|
||
|
v := binary.BigEndian.Uint16(buf[:2])
|
||
|
br.Discard(2)
|
||
|
return v, nil
|
||
|
}
|
||
|
|
||
|
func (br *bufReader) readUint32() (uint32, error) {
|
||
|
if br.err != nil {
|
||
|
return 0, br.err
|
||
|
}
|
||
|
buf, err := br.Peek(4)
|
||
|
if err != nil {
|
||
|
br.err = err
|
||
|
return 0, err
|
||
|
}
|
||
|
v := binary.BigEndian.Uint32(buf[:4])
|
||
|
br.Discard(4)
|
||
|
return v, nil
|
||
|
}
|
||
|
|
||
|
func (br *bufReader) readString() (string, error) {
|
||
|
if br.err != nil {
|
||
|
return "", br.err
|
||
|
}
|
||
|
s0, err := br.ReadString(0)
|
||
|
if err != nil {
|
||
|
br.err = err
|
||
|
return "", err
|
||
|
}
|
||
|
s := strings.TrimSuffix(s0, "\x00")
|
||
|
if len(s) == len(s0) {
|
||
|
err = fmt.Errorf("unexpected non-null terminated string")
|
||
|
br.err = err
|
||
|
return "", err
|
||
|
}
|
||
|
return s, nil
|
||
|
}
|
||
|
|
||
|
// HEIF: ipco
|
||
|
type ItemPropertyContainerBox struct {
|
||
|
*box
|
||
|
Properties []Box // of ItemProperty or ItemFullProperty
|
||
|
}
|
||
|
|
||
|
func parseItemPropertyContainerBox(outer *box, br *bufReader) (Box, error) {
|
||
|
ipc := &ItemPropertyContainerBox{box: outer}
|
||
|
return ipc, br.parseAppendBoxes(&ipc.Properties)
|
||
|
}
|
||
|
|
||
|
// HEIF: iprp
|
||
|
type ItemPropertiesBox struct {
|
||
|
*box
|
||
|
PropertyContainer *ItemPropertyContainerBox
|
||
|
Associations []*ItemPropertyAssociation // at least 1
|
||
|
}
|
||
|
|
||
|
func parseItemPropertiesBox(outer *box, br *bufReader) (Box, error) {
|
||
|
ip := &ItemPropertiesBox{
|
||
|
box: outer,
|
||
|
}
|
||
|
|
||
|
var boxes []Box
|
||
|
err := br.parseAppendBoxes(&boxes)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if len(boxes) < 2 {
|
||
|
return nil, fmt.Errorf("expect at least 2 boxes in children; got 0")
|
||
|
}
|
||
|
|
||
|
cb, err := boxes[0].Parse()
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("failed to parse first box, %q: %v", boxes[0].Type(), err)
|
||
|
}
|
||
|
|
||
|
var ok bool
|
||
|
ip.PropertyContainer, ok = cb.(*ItemPropertyContainerBox)
|
||
|
if !ok {
|
||
|
return nil, fmt.Errorf("unexpected type %T for ItemPropertieBox.PropertyContainer", cb)
|
||
|
}
|
||
|
|
||
|
// Association boxes
|
||
|
ip.Associations = make([]*ItemPropertyAssociation, 0, len(boxes)-1)
|
||
|
for _, box := range boxes[1:] {
|
||
|
boxp, err := box.Parse()
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("failed to parse association box: %v", err)
|
||
|
}
|
||
|
ipa, ok := boxp.(*ItemPropertyAssociation)
|
||
|
if !ok {
|
||
|
return nil, fmt.Errorf("unexpected box %q instead of ItemPropertyAssociation", boxp.Type())
|
||
|
}
|
||
|
ip.Associations = append(ip.Associations, ipa)
|
||
|
}
|
||
|
return ip, nil
|
||
|
}
|
||
|
|
||
|
type ItemPropertyAssociation struct {
|
||
|
FullBox
|
||
|
EntryCount uint32
|
||
|
Entries []ItemPropertyAssociationItem
|
||
|
}
|
||
|
|
||
|
// not a box
|
||
|
type ItemProperty struct {
|
||
|
Essential bool
|
||
|
Index uint16
|
||
|
}
|
||
|
|
||
|
// not a box
|
||
|
type ItemPropertyAssociationItem struct {
|
||
|
ItemID uint32
|
||
|
AssociationsCount int // as declared
|
||
|
Associations []ItemProperty // as parsed
|
||
|
}
|
||
|
|
||
|
func parseItemPropertyAssociation(outer *box, br *bufReader) (Box, error) {
|
||
|
fb, err := readFullBox(outer, br)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
ipa := &ItemPropertyAssociation{FullBox: fb}
|
||
|
count, _ := br.readUint32()
|
||
|
ipa.EntryCount = count
|
||
|
|
||
|
for i := uint64(0); i < uint64(count) && br.ok(); i++ {
|
||
|
var itemID uint32
|
||
|
if fb.Version < 1 {
|
||
|
itemID16, _ := br.readUint16()
|
||
|
itemID = uint32(itemID16)
|
||
|
} else {
|
||
|
itemID, _ = br.readUint32()
|
||
|
}
|
||
|
assocCount, _ := br.readUint8()
|
||
|
ipai := ItemPropertyAssociationItem{
|
||
|
ItemID: itemID,
|
||
|
AssociationsCount: int(assocCount),
|
||
|
}
|
||
|
for j := 0; j < int(assocCount) && br.ok(); j++ {
|
||
|
first, _ := br.readUint8()
|
||
|
essential := first&(1<<7) != 0
|
||
|
first &^= byte(1 << 7)
|
||
|
|
||
|
var index uint16
|
||
|
if fb.Flags&1 != 0 {
|
||
|
second, _ := br.readUint8()
|
||
|
index = uint16(first)<<8 | uint16(second)
|
||
|
} else {
|
||
|
index = uint16(first)
|
||
|
}
|
||
|
ipai.Associations = append(ipai.Associations, ItemProperty{
|
||
|
Essential: essential,
|
||
|
Index: index,
|
||
|
})
|
||
|
}
|
||
|
ipa.Entries = append(ipa.Entries, ipai)
|
||
|
}
|
||
|
if !br.ok() {
|
||
|
return nil, br.err
|
||
|
}
|
||
|
return ipa, nil
|
||
|
}
|
||
|
|
||
|
type ImageSpatialExtentsProperty struct {
|
||
|
FullBox
|
||
|
ImageWidth uint32
|
||
|
ImageHeight uint32
|
||
|
}
|
||
|
|
||
|
func parseImageSpatialExtentsProperty(outer *box, br *bufReader) (Box, error) {
|
||
|
fb, err := readFullBox(outer, br)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
w, err := br.readUint32()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
h, err := br.readUint32()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return &ImageSpatialExtentsProperty{
|
||
|
FullBox: fb,
|
||
|
ImageWidth: w,
|
||
|
ImageHeight: h,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
type OffsetLength struct {
|
||
|
Offset, Length uint64
|
||
|
}
|
||
|
|
||
|
// not a box
|
||
|
type ItemLocationBoxEntry struct {
|
||
|
ItemID uint16
|
||
|
ConstructionMethod uint8 // actually uint4
|
||
|
DataReferenceIndex uint16
|
||
|
BaseOffset uint64 // uint32 or uint64, depending on encoding
|
||
|
ExtentCount uint16
|
||
|
Extents []OffsetLength
|
||
|
}
|
||
|
|
||
|
// box "iloc"
|
||
|
type ItemLocationBox struct {
|
||
|
FullBox
|
||
|
|
||
|
offsetSize, lengthSize, baseOffsetSize, indexSize uint8 // actually uint4
|
||
|
|
||
|
ItemCount uint16
|
||
|
Items []ItemLocationBoxEntry
|
||
|
}
|
||
|
|
||
|
func parseItemLocationBox(outer *box, br *bufReader) (Box, error) {
|
||
|
fb, err := readFullBox(outer, br)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
ilb := &ItemLocationBox{
|
||
|
FullBox: fb,
|
||
|
}
|
||
|
buf, err := br.Peek(4)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
ilb.offsetSize = buf[0] >> 4
|
||
|
ilb.lengthSize = buf[0] & 15
|
||
|
ilb.baseOffsetSize = buf[1] >> 4
|
||
|
if fb.Version > 0 { // version 1
|
||
|
ilb.indexSize = buf[1] & 15
|
||
|
}
|
||
|
|
||
|
ilb.ItemCount = binary.BigEndian.Uint16(buf[2:4])
|
||
|
br.Discard(4)
|
||
|
|
||
|
for i := 0; br.ok() && i < int(ilb.ItemCount); i++ {
|
||
|
var ent ItemLocationBoxEntry
|
||
|
ent.ItemID, _ = br.readUint16()
|
||
|
if fb.Version > 0 { // version 1
|
||
|
cmeth, _ := br.readUint16()
|
||
|
ent.ConstructionMethod = byte(cmeth & 15)
|
||
|
}
|
||
|
ent.DataReferenceIndex, _ = br.readUint16()
|
||
|
if br.ok() && ilb.baseOffsetSize > 0 {
|
||
|
br.Discard(int(ilb.baseOffsetSize) / 8)
|
||
|
}
|
||
|
ent.ExtentCount, _ = br.readUint16()
|
||
|
for j := 0; br.ok() && j < int(ent.ExtentCount); j++ {
|
||
|
var ol OffsetLength
|
||
|
ol.Offset, _ = br.readUintN(ilb.offsetSize * 8)
|
||
|
ol.Length, _ = br.readUintN(ilb.lengthSize * 8)
|
||
|
if br.err != nil {
|
||
|
return nil, br.err
|
||
|
}
|
||
|
ent.Extents = append(ent.Extents, ol)
|
||
|
}
|
||
|
ilb.Items = append(ilb.Items, ent)
|
||
|
}
|
||
|
if !br.ok() {
|
||
|
return nil, br.err
|
||
|
}
|
||
|
return ilb, nil
|
||
|
}
|
||
|
|
||
|
// a "hdlr" box.
|
||
|
type HandlerBox struct {
|
||
|
FullBox
|
||
|
HandlerType string // always 4 bytes; usually "pict" for iOS Camera images
|
||
|
Name string
|
||
|
}
|
||
|
|
||
|
func parseHandlerBox(gen *box, br *bufReader) (Box, error) {
|
||
|
fb, err := readFullBox(gen, br)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
hb := &HandlerBox{
|
||
|
FullBox: fb,
|
||
|
}
|
||
|
buf, err := br.Peek(20)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
hb.HandlerType = string(buf[4:8])
|
||
|
br.Discard(20)
|
||
|
|
||
|
hb.Name, _ = br.readString()
|
||
|
return hb, br.err
|
||
|
}
|
||
|
|
||
|
// a "dinf" box
|
||
|
type DataInformationBox struct {
|
||
|
*box
|
||
|
Children []Box
|
||
|
}
|
||
|
|
||
|
func parseDataInformationBox(gen *box, br *bufReader) (Box, error) {
|
||
|
dib := &DataInformationBox{box: gen}
|
||
|
return dib, br.parseAppendBoxes(&dib.Children)
|
||
|
}
|
||
|
|
||
|
// a "dref" box.
|
||
|
type DataReferenceBox struct {
|
||
|
FullBox
|
||
|
EntryCount uint32
|
||
|
Children []Box
|
||
|
}
|
||
|
|
||
|
func parseDataReferenceBox(gen *box, br *bufReader) (Box, error) {
|
||
|
fb, err := readFullBox(gen, br)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
drb := &DataReferenceBox{FullBox: fb}
|
||
|
drb.EntryCount, _ = br.readUint32()
|
||
|
return drb, br.parseAppendBoxes(&drb.Children)
|
||
|
}
|
||
|
|
||
|
// "pitm" box
|
||
|
type PrimaryItemBox struct {
|
||
|
FullBox
|
||
|
ItemID uint16
|
||
|
}
|
||
|
|
||
|
func parsePrimaryItemBox(gen *box, br *bufReader) (Box, error) {
|
||
|
fb, err := readFullBox(gen, br)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
pib := &PrimaryItemBox{FullBox: fb}
|
||
|
pib.ItemID, _ = br.readUint16()
|
||
|
if !br.ok() {
|
||
|
return nil, br.err
|
||
|
}
|
||
|
return pib, nil
|
||
|
}
|
||
|
|
||
|
// ImageRotation is a HEIF "irot" rotation property.
|
||
|
type ImageRotation struct {
|
||
|
*box
|
||
|
Angle uint8 // 1 means 90 degrees counter-clockwise, 2 means 180 counter-clockwise
|
||
|
}
|
||
|
|
||
|
func parseImageRotation(gen *box, br *bufReader) (Box, error) {
|
||
|
v, err := br.readUint8()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return &ImageRotation{box: gen, Angle: v & 3}, nil
|
||
|
}
|