Working auth and photo json endpoint

Signed-off-by: Kris Nóva <kris@nivenly.com>
This commit is contained in:
Kris Nóva 2021-02-09 11:17:06 -08:00
parent ef275f97f4
commit e4323b6047
2032 changed files with 821464 additions and 52 deletions

8
vendor/go4.org/AUTHORS generated vendored Normal file
View file

@ -0,0 +1,8 @@
# This is the official list of go4 authors for copyright purposes.
# This is distinct from the CONTRIBUTORS file, which is the list of
# people who have contributed, even if they don't own the copyright on
# their work.
Mathieu Lonjaret <mathieu.lonjaret@gmail.com>
Daniel Theophanes <kardianos@gmail.com>
Google

202
vendor/go4.org/LICENSE generated vendored Normal file
View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
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.

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

@ -0,0 +1,833 @@
/*
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
}

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

@ -0,0 +1,292 @@
/*
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
}
// Rotations returns the number of 90 degree rotations counter-clockwise that this
// image should be rendered at, in the range [0,3].
func (it *Item) Rotations() int {
for _, p := range it.Properties {
if p, ok := p.(*bmff.ImageRotation); ok {
return int(p.Angle)
}
}
return 0
}
// VisualDimensions returns the item's width and height after correcting
// for any rotations.
func (it *Item) VisualDimensions() (width, height int, ok bool) {
width, height, ok = it.SpatialExtents()
for i := 0; i < it.Rotations(); i++ {
width, height = height, width
}
return
}
// TODO: add HEIF imir (mirroring) accessor, like Image.SpatialExtents.
// 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
}