vendor: add go4.org/media/heif

At rev 7b81d6948d11710f710d0c4ef52daac1dc7c936b

Updates issue #969

Change-Id: I6f21de58c0865d3cbc8186b3a6834444b6d1206e
This commit is contained in:
mpl 2018-04-13 00:07:32 +02:00
parent 326a10c2cb
commit 139cd8bd01
7 changed files with 1180 additions and 5 deletions

7
Gopkg.lock generated
View File

@ -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

View File

@ -226,8 +226,7 @@ ignored = [
[[constraint]]
name = "go4.org"
revision = "c3a8ba339e20006b054736f8eb9fc5e1d5fa6eab"
branch = "master"
# golang/x

View File

@ -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"

View File

@ -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()))

78
vendor/go4.org/lock/lock_windows.go generated vendored Normal file
View File

@ -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
}

818
vendor/go4.org/media/heif/bmff/bmff.go generated vendored Normal file
View File

@ -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
}

272
vendor/go4.org/media/heif/heif.go generated vendored Normal file
View File

@ -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
}