From 139cd8bd01a4f39d3a3faa94bdd1e7392558fb36 Mon Sep 17 00:00:00 2001 From: mpl Date: Fri, 13 Apr 2018 00:07:32 +0200 Subject: [PATCH] vendor: add go4.org/media/heif At rev 7b81d6948d11710f710d0c4ef52daac1dc7c936b Updates issue #969 Change-Id: I6f21de58c0865d3cbc8186b3a6834444b6d1206e --- Gopkg.lock | 7 +- Gopkg.toml | 3 +- pkg/index/receive.go | 1 + .../go4.org/cloud/cloudlaunch/cloudlaunch.go | 6 +- vendor/go4.org/lock/lock_windows.go | 78 ++ vendor/go4.org/media/heif/bmff/bmff.go | 818 ++++++++++++++++++ vendor/go4.org/media/heif/heif.go | 272 ++++++ 7 files changed, 1180 insertions(+), 5 deletions(-) create mode 100644 vendor/go4.org/lock/lock_windows.go create mode 100644 vendor/go4.org/media/heif/bmff/bmff.go create mode 100644 vendor/go4.org/media/heif/heif.go diff --git a/Gopkg.lock b/Gopkg.lock index 4ab9476ee..6585bcb3e 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -297,6 +297,7 @@ revision = "9e1ac2306c701ca7477a169b2b49902b7b4c58bf" [[projects]] + branch = "master" name = "go4.org" packages = [ "cloud/cloudlaunch", @@ -308,6 +309,8 @@ "jsonconfig", "legal", "lock", + "media/heif", + "media/heif/bmff", "net/throttle", "oauthutil", "readerutil", @@ -319,7 +322,7 @@ "wkfs/gcs", "writerutil" ] - revision = "c3a8ba339e20006b054736f8eb9fc5e1d5fa6eab" + revision = "7b81d6948d11710f710d0c4ef52daac1dc7c936b" [[projects]] name = "golang.org/x/crypto" @@ -567,6 +570,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "ea9200f608a8ca6a12f16d664cf7f251bac039dac1f45eae9ff0edd41c5d656b" + inputs-digest = "aeffe8df9be98b70a039ac4878af79fd9ce3832218db0f1a62e349bfe4639c74" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index e49186a11..6fe8b87fb 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -226,8 +226,7 @@ ignored = [ [[constraint]] name = "go4.org" - revision = "c3a8ba339e20006b054736f8eb9fc5e1d5fa6eab" - + branch = "master" # golang/x diff --git a/pkg/index/receive.go b/pkg/index/receive.go index e1f84f505..753d8cacc 100644 --- a/pkg/index/receive.go +++ b/pkg/index/receive.go @@ -38,6 +38,7 @@ import ( "github.com/hjfreyer/taglib-go/taglib" "github.com/rwcarlsen/goexif/exif" "github.com/rwcarlsen/goexif/tiff" + _ "go4.org/media/heif" "go4.org/readerutil" "go4.org/types" "perkeep.org/internal/images" diff --git a/vendor/go4.org/cloud/cloudlaunch/cloudlaunch.go b/vendor/go4.org/cloud/cloudlaunch/cloudlaunch.go index 9c7267635..b87cf81b4 100644 --- a/vendor/go4.org/cloud/cloudlaunch/cloudlaunch.go +++ b/vendor/go4.org/cloud/cloudlaunch/cloudlaunch.go @@ -35,6 +35,7 @@ import ( "go4.org/cloud/google/gceutil" + "cloud.google.com/go/compute/metadata" "cloud.google.com/go/storage" "golang.org/x/net/context" "golang.org/x/oauth2" @@ -194,10 +195,13 @@ func (c *Config) MaybeDeploy() { } func (c *Config) restartLoop() { + if !metadata.OnGCE() { + return + } if c.RestartPolicy == RestartNever { return } - url := "https://storage.googleapis.com/" + c.BinaryBucket + "/" + c.binaryObject() + url := c.binaryURL() var lastEtag string for { res, err := http.Head(url + "?" + fmt.Sprint(time.Now().Unix())) diff --git a/vendor/go4.org/lock/lock_windows.go b/vendor/go4.org/lock/lock_windows.go new file mode 100644 index 000000000..425748790 --- /dev/null +++ b/vendor/go4.org/lock/lock_windows.go @@ -0,0 +1,78 @@ +/* +Copyright 2013 The Go 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 lock + +import ( + "fmt" + "io" + "os" + "sync" + + "golang.org/x/sys/windows" +) + +func init() { + lockFn = lockWindows +} + +type winUnlocker struct { + h windows.Handle + abs string + // err holds the error returned by Close. + err error + // once guards the close method call. + once sync.Once +} + +func (u *winUnlocker) Close() error { + u.once.Do(u.close) + return u.err +} + +func (u *winUnlocker) close() { + lockmu.Lock() + defer lockmu.Unlock() + delete(locked, u.abs) + + u.err = windows.CloseHandle(u.h) +} + +func lockWindows(name string) (io.Closer, error) { + fi, err := os.Stat(name) + if err == nil && fi.Size() > 0 { + return nil, fmt.Errorf("can't lock file %q: %s", name, "has non-zero size") + } + + handle, err := winCreateEphemeral(name) + if err != nil { + return nil, fmt.Errorf("creation of lock %s failed: %v", name, err) + } + + return &winUnlocker{h: handle, abs: name}, nil +} + +func winCreateEphemeral(name string) (windows.Handle, error) { + const ( + FILE_ATTRIBUTE_TEMPORARY = 0x100 + FILE_FLAG_DELETE_ON_CLOSE = 0x04000000 + ) + handle, err := windows.CreateFile(windows.StringToUTF16Ptr(name), 0, 0, nil, windows.OPEN_ALWAYS, FILE_ATTRIBUTE_TEMPORARY|FILE_FLAG_DELETE_ON_CLOSE, 0) + if err != nil { + return 0, err + } + return handle, nil +} diff --git a/vendor/go4.org/media/heif/bmff/bmff.go b/vendor/go4.org/media/heif/bmff/bmff.go new file mode 100644 index 000000000..f833a818e --- /dev/null +++ b/vendor/go4.org/media/heif/bmff/bmff.go @@ -0,0 +1,818 @@ +/* +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("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 +} diff --git a/vendor/go4.org/media/heif/heif.go b/vendor/go4.org/media/heif/heif.go new file mode 100644 index 000000000..d9b84d191 --- /dev/null +++ b/vendor/go4.org/media/heif/heif.go @@ -0,0 +1,272 @@ +/* +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 heif reads HEIF containers, as found in Apple HEIC/HEVC images. +// This package does not decode images; it only reads the metadata. +// +// This package is a work in progress and makes no API compatibility +// promises. +package heif + +import ( + "errors" + "fmt" + "io" + "log" + + "go4.org/media/heif/bmff" +) + +// File represents a HEIF file. +// +// Methods on File should not be called concurrently. +type File struct { + ra io.ReaderAt + primary *Item + + // Populated lazily, by getMeta: + metaErr error + meta *BoxMeta +} + +// BoxMeta contains the low-level BMFF metadata boxes. +type BoxMeta struct { + FileType *bmff.FileTypeBox + Handler *bmff.HandlerBox + PrimaryItem *bmff.PrimaryItemBox + ItemInfo *bmff.ItemInfoBox + Properties *bmff.ItemPropertiesBox + ItemLocation *bmff.ItemLocationBox +} + +// EXIFItemID returns the item ID of the EXIF part, or 0 if not found. +func (m *BoxMeta) EXIFItemID() uint32 { + if m.ItemInfo == nil { + return 0 + } + for _, ife := range m.ItemInfo.ItemInfos { + if ife.ItemType == "Exif" { + return uint32(ife.ItemID) + } + } + return 0 +} + +// Item represents an item in a HEIF file. +type Item struct { + f *File + + ID uint32 + Info *bmff.ItemInfoEntry + Location *bmff.ItemLocationBoxEntry // location in file + Properties []bmff.Box +} + +// SpatialExtents returns the item's spatial extents property values, if present, +// not correcting from any camera rotation metadata. +func (it *Item) SpatialExtents() (width, height int, ok bool) { + for _, p := range it.Properties { + if p, ok := p.(*bmff.ImageSpatialExtentsProperty); ok { + return int(p.ImageWidth), int(p.ImageHeight), true + } + } + return +} + +// TODO: add HEIF irot rotation accessor, like Image.SpatialExtents. +// And imir (mirroring). + +// Open returns a handle to access a HEIF file. +func Open(f io.ReaderAt) *File { + return &File{ra: f} +} + +// ErrNoEXIF is returned by File.EXIF when a file does not contain an EXIF item. +var ErrNoEXIF = errors.New("heif: no EXIF found") + +// ErrUnknownItem is returned by File.ItemByID for unknown items. +var ErrUnknownItem = errors.New("heif: unknown item") + +// EXIF returns the raw EXIF data from the file. +// The error is ErrNoEXIF if the file did not contain EXIF. +// +// The raw EXIF data can be parsed by the +// github.com/rwcarlsen/goexif/exif package's Decode function. +func (f *File) EXIF() ([]byte, error) { + meta, err := f.getMeta() + if err != nil { + return nil, err + } + exifID := meta.EXIFItemID() + if exifID == 0 { + return nil, ErrNoEXIF + } + it, err := f.ItemByID(exifID) + if err != nil { + return nil, err + } + if it.Location == nil { + return nil, errors.New("heif: file said it contained EXIF, but didn't say where") + } + if n := len(it.Location.Extents); n != 1 { + return nil, fmt.Errorf("heif: expected 1 EXIF section, saw %d", n) + } + offLen := it.Location.Extents[0] + const maxSize = 20 << 10 // 20MB of EXIF seems excessive; cap it for sanity + if offLen.Length > maxSize { + return nil, fmt.Errorf("heif: declared EXIF size %d exceeds threshold of %d bytes", offLen.Length, maxSize) + } + buf := make([]byte, offLen.Length-4) + n, err := f.ra.ReadAt(buf, int64(offLen.Offset)+4) // TODO: why 4? did I miss something? + if err != nil { + log.Printf("Read %d bytes + %v: %q", n, err, buf) + return nil, err + } + return buf, nil +} + +func (f *File) setMetaErr(err error) error { + if f.metaErr != nil { + f.metaErr = err + } + return err +} + +func (f *File) getMeta() (*BoxMeta, error) { + if f.metaErr != nil { + return nil, f.metaErr + } + if f.meta != nil { + return f.meta, nil + } + const assumedMaxSize = 5 << 40 // arbitrary + sr := io.NewSectionReader(f.ra, 0, assumedMaxSize) + bmr := bmff.NewReader(sr) + + meta := &BoxMeta{} + + pbox, err := bmr.ReadAndParseBox(bmff.TypeFtyp) + if err != nil { + return nil, f.setMetaErr(err) + } + meta.FileType = pbox.(*bmff.FileTypeBox) + + pbox, err = bmr.ReadAndParseBox(bmff.TypeMeta) + if err != nil { + return nil, f.setMetaErr(err) + } + metabox := pbox.(*bmff.MetaBox) + + for _, box := range metabox.Children { + boxp, err := box.Parse() + if err == bmff.ErrUnknownBox { + continue + } + if err != nil { + return nil, f.setMetaErr(err) + } + switch v := boxp.(type) { + case *bmff.HandlerBox: + meta.Handler = v + case *bmff.PrimaryItemBox: + meta.PrimaryItem = v + case *bmff.ItemInfoBox: + meta.ItemInfo = v + case *bmff.ItemPropertiesBox: + meta.Properties = v + case *bmff.ItemLocationBox: + meta.ItemLocation = v + } + } + + f.meta = meta + return f.meta, nil +} + +// PrimaryItem returns the HEIF file's primary item. +func (f *File) PrimaryItem() (*Item, error) { + meta, err := f.getMeta() + if err != nil { + return nil, err + } + if meta.PrimaryItem == nil { + return nil, errors.New("heif: HEIF file lacks primary item box") + } + return f.ItemByID(uint32(meta.PrimaryItem.ItemID)) +} + +// ItemByID by returns the file's Item of a given ID. +// If the ID is known, the returned error is ErrUnknownItem. +func (f *File) ItemByID(id uint32) (*Item, error) { + meta, err := f.getMeta() + if err != nil { + return nil, err + } + it := &Item{ + f: f, + ID: id, + } + if meta.ItemLocation != nil { + for _, ilbe := range meta.ItemLocation.Items { + if uint32(ilbe.ItemID) == id { + shallowCopy := ilbe + it.Location = &shallowCopy + } + } + } + if meta.ItemInfo != nil { + for _, iie := range meta.ItemInfo.ItemInfos { + if uint32(iie.ItemID) == id { + it.Info = iie + } + } + } + if it.Info == nil { + return nil, ErrUnknownItem + } + if meta.Properties != nil { + allProps := meta.Properties.PropertyContainer.Properties + for _, ipa := range meta.Properties.Associations { + // TODO: I've never seen a file with more than + // top-level ItemPropertyAssociation box, but + // apparently they can exist with different + // versions/flags. For now we just merge them + // all together, but that's not really right. + // So for now, just bail once a previous loop + // found anything. + if len(it.Properties) > 0 { + break + } + + for _, ipai := range ipa.Entries { + if ipai.ItemID != id { + continue + } + for _, ass := range ipai.Associations { + if ass.Index != 0 && int(ass.Index) <= len(allProps) { + box := allProps[ass.Index-1] + boxp, err := box.Parse() + if err == nil { + box = boxp + } + it.Properties = append(it.Properties, box) + } + } + } + } + } + return it, nil +}