mirror of
https://github.com/vbatts/freezing-octo-hipster.git
synced 2025-07-03 07:48:29 +00:00
go*: one go module for the repo, no more nested
Signed-off-by: Vincent Batts <vbatts@hashbangbash.com>
This commit is contained in:
parent
a788e9ac95
commit
4ab3be9bc6
632 changed files with 153930 additions and 133148 deletions
4
vendor/github.com/rwcarlsen/goexif/exif/README.md
generated
vendored
Normal file
4
vendor/github.com/rwcarlsen/goexif/exif/README.md
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
|
||||
To regenerate the regression test data, run `go generate` inside the exif
|
||||
package directory and commit the changes to *regress_expected_test.go*.
|
||||
|
655
vendor/github.com/rwcarlsen/goexif/exif/exif.go
generated
vendored
Normal file
655
vendor/github.com/rwcarlsen/goexif/exif/exif.go
generated
vendored
Normal file
|
@ -0,0 +1,655 @@
|
|||
// Package exif implements decoding of EXIF data as defined in the EXIF 2.2
|
||||
// specification (http://www.exif.org/Exif2-2.PDF).
|
||||
package exif
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rwcarlsen/goexif/tiff"
|
||||
)
|
||||
|
||||
const (
|
||||
jpeg_APP1 = 0xE1
|
||||
|
||||
exifPointer = 0x8769
|
||||
gpsPointer = 0x8825
|
||||
interopPointer = 0xA005
|
||||
)
|
||||
|
||||
// A decodeError is returned when the image cannot be decoded as a tiff image.
|
||||
type decodeError struct {
|
||||
cause error
|
||||
}
|
||||
|
||||
func (de decodeError) Error() string {
|
||||
return fmt.Sprintf("exif: decode failed (%v) ", de.cause.Error())
|
||||
}
|
||||
|
||||
// IsShortReadTagValueError identifies a ErrShortReadTagValue error.
|
||||
func IsShortReadTagValueError(err error) bool {
|
||||
de, ok := err.(decodeError)
|
||||
if ok {
|
||||
return de.cause == tiff.ErrShortReadTagValue
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// A TagNotPresentError is returned when the requested field is not
|
||||
// present in the EXIF.
|
||||
type TagNotPresentError FieldName
|
||||
|
||||
func (tag TagNotPresentError) Error() string {
|
||||
return fmt.Sprintf("exif: tag %q is not present", string(tag))
|
||||
}
|
||||
|
||||
func IsTagNotPresentError(err error) bool {
|
||||
_, ok := err.(TagNotPresentError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Parser allows the registration of custom parsing and field loading
|
||||
// in the Decode function.
|
||||
type Parser interface {
|
||||
// Parse should read data from x and insert parsed fields into x via
|
||||
// LoadTags.
|
||||
Parse(x *Exif) error
|
||||
}
|
||||
|
||||
var parsers []Parser
|
||||
|
||||
func init() {
|
||||
RegisterParsers(&parser{})
|
||||
}
|
||||
|
||||
// RegisterParsers registers one or more parsers to be automatically called
|
||||
// when decoding EXIF data via the Decode function.
|
||||
func RegisterParsers(ps ...Parser) {
|
||||
parsers = append(parsers, ps...)
|
||||
}
|
||||
|
||||
type parser struct{}
|
||||
|
||||
type tiffErrors map[tiffError]string
|
||||
|
||||
func (te tiffErrors) Error() string {
|
||||
var allErrors []string
|
||||
for k, v := range te {
|
||||
allErrors = append(allErrors, fmt.Sprintf("%s: %v\n", stagePrefix[k], v))
|
||||
}
|
||||
return strings.Join(allErrors, "\n")
|
||||
}
|
||||
|
||||
// IsCriticalError, given the error returned by Decode, reports whether the
|
||||
// returned *Exif may contain usable information.
|
||||
func IsCriticalError(err error) bool {
|
||||
_, ok := err.(tiffErrors)
|
||||
return !ok
|
||||
}
|
||||
|
||||
// IsExifError reports whether the error happened while decoding the EXIF
|
||||
// sub-IFD.
|
||||
func IsExifError(err error) bool {
|
||||
if te, ok := err.(tiffErrors); ok {
|
||||
_, isExif := te[loadExif]
|
||||
return isExif
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsGPSError reports whether the error happened while decoding the GPS sub-IFD.
|
||||
func IsGPSError(err error) bool {
|
||||
if te, ok := err.(tiffErrors); ok {
|
||||
_, isGPS := te[loadExif]
|
||||
return isGPS
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsInteroperabilityError reports whether the error happened while decoding the
|
||||
// Interoperability sub-IFD.
|
||||
func IsInteroperabilityError(err error) bool {
|
||||
if te, ok := err.(tiffErrors); ok {
|
||||
_, isInterop := te[loadInteroperability]
|
||||
return isInterop
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type tiffError int
|
||||
|
||||
const (
|
||||
loadExif tiffError = iota
|
||||
loadGPS
|
||||
loadInteroperability
|
||||
)
|
||||
|
||||
var stagePrefix = map[tiffError]string{
|
||||
loadExif: "loading EXIF sub-IFD",
|
||||
loadGPS: "loading GPS sub-IFD",
|
||||
loadInteroperability: "loading Interoperability sub-IFD",
|
||||
}
|
||||
|
||||
// Parse reads data from the tiff data in x and populates the tags
|
||||
// in x. If parsing a sub-IFD fails, the error is recorded and
|
||||
// parsing continues with the remaining sub-IFDs.
|
||||
func (p *parser) Parse(x *Exif) error {
|
||||
if len(x.Tiff.Dirs) == 0 {
|
||||
return errors.New("Invalid exif data")
|
||||
}
|
||||
x.LoadTags(x.Tiff.Dirs[0], exifFields, false)
|
||||
|
||||
// thumbnails
|
||||
if len(x.Tiff.Dirs) >= 2 {
|
||||
x.LoadTags(x.Tiff.Dirs[1], thumbnailFields, false)
|
||||
}
|
||||
|
||||
te := make(tiffErrors)
|
||||
|
||||
// recurse into exif, gps, and interop sub-IFDs
|
||||
if err := loadSubDir(x, ExifIFDPointer, exifFields); err != nil {
|
||||
te[loadExif] = err.Error()
|
||||
}
|
||||
if err := loadSubDir(x, GPSInfoIFDPointer, gpsFields); err != nil {
|
||||
te[loadGPS] = err.Error()
|
||||
}
|
||||
|
||||
if err := loadSubDir(x, InteroperabilityIFDPointer, interopFields); err != nil {
|
||||
te[loadInteroperability] = err.Error()
|
||||
}
|
||||
if len(te) > 0 {
|
||||
return te
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadSubDir(x *Exif, ptr FieldName, fieldMap map[uint16]FieldName) error {
|
||||
r := bytes.NewReader(x.Raw)
|
||||
|
||||
tag, err := x.Get(ptr)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
offset, err := tag.Int64(0)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = r.Seek(offset, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("exif: seek to sub-IFD %s failed: %v", ptr, err)
|
||||
}
|
||||
subDir, _, err := tiff.DecodeDir(r, x.Tiff.Order)
|
||||
if err != nil {
|
||||
return fmt.Errorf("exif: sub-IFD %s decode failed: %v", ptr, err)
|
||||
}
|
||||
x.LoadTags(subDir, fieldMap, false)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exif provides access to decoded EXIF metadata fields and values.
|
||||
type Exif struct {
|
||||
Tiff *tiff.Tiff
|
||||
main map[FieldName]*tiff.Tag
|
||||
Raw []byte
|
||||
}
|
||||
|
||||
// Decode parses EXIF data from r (a TIFF, JPEG, or raw EXIF block)
|
||||
// and returns a queryable Exif object. After the EXIF data section is
|
||||
// called and the TIFF structure is decoded, each registered parser is
|
||||
// called (in order of registration). If one parser returns an error,
|
||||
// decoding terminates and the remaining parsers are not called.
|
||||
//
|
||||
// The error can be inspected with functions such as IsCriticalError
|
||||
// to determine whether the returned object might still be usable.
|
||||
func Decode(r io.Reader) (*Exif, error) {
|
||||
|
||||
// EXIF data in JPEG is stored in the APP1 marker. EXIF data uses the TIFF
|
||||
// format to store data.
|
||||
// If we're parsing a TIFF image, we don't need to strip away any data.
|
||||
// If we're parsing a JPEG image, we need to strip away the JPEG APP1
|
||||
// marker and also the EXIF header.
|
||||
|
||||
header := make([]byte, 4)
|
||||
n, err := io.ReadFull(r, header)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("exif: error reading 4 byte header, got %d, %v", n, err)
|
||||
}
|
||||
|
||||
var isTiff bool
|
||||
var isRawExif bool
|
||||
var assumeJPEG bool
|
||||
switch string(header) {
|
||||
case "II*\x00":
|
||||
// TIFF - Little endian (Intel)
|
||||
isTiff = true
|
||||
case "MM\x00*":
|
||||
// TIFF - Big endian (Motorola)
|
||||
isTiff = true
|
||||
case "Exif":
|
||||
isRawExif = true
|
||||
default:
|
||||
// Not TIFF, assume JPEG
|
||||
assumeJPEG = true
|
||||
}
|
||||
|
||||
// Put the header bytes back into the reader.
|
||||
r = io.MultiReader(bytes.NewReader(header), r)
|
||||
var (
|
||||
er *bytes.Reader
|
||||
tif *tiff.Tiff
|
||||
sec *appSec
|
||||
)
|
||||
|
||||
switch {
|
||||
case isRawExif:
|
||||
var header [6]byte
|
||||
if _, err := io.ReadFull(r, header[:]); err != nil {
|
||||
return nil, fmt.Errorf("exif: unexpected raw exif header read error")
|
||||
}
|
||||
if got, want := string(header[:]), "Exif\x00\x00"; got != want {
|
||||
return nil, fmt.Errorf("exif: unexpected raw exif header; got %q, want %q", got, want)
|
||||
}
|
||||
fallthrough
|
||||
case isTiff:
|
||||
// Functions below need the IFDs from the TIFF data to be stored in a
|
||||
// *bytes.Reader. We use TeeReader to get a copy of the bytes as a
|
||||
// side-effect of tiff.Decode() doing its work.
|
||||
b := &bytes.Buffer{}
|
||||
tr := io.TeeReader(r, b)
|
||||
tif, err = tiff.Decode(tr)
|
||||
er = bytes.NewReader(b.Bytes())
|
||||
case assumeJPEG:
|
||||
// Locate the JPEG APP1 header.
|
||||
sec, err = newAppSec(jpeg_APP1, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Strip away EXIF header.
|
||||
er, err = sec.exifReader()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tif, err = tiff.Decode(er)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, decodeError{cause: err}
|
||||
}
|
||||
|
||||
er.Seek(0, 0)
|
||||
raw, err := ioutil.ReadAll(er)
|
||||
if err != nil {
|
||||
return nil, decodeError{cause: err}
|
||||
}
|
||||
|
||||
// build an exif structure from the tiff
|
||||
x := &Exif{
|
||||
main: map[FieldName]*tiff.Tag{},
|
||||
Tiff: tif,
|
||||
Raw: raw,
|
||||
}
|
||||
|
||||
for i, p := range parsers {
|
||||
if err := p.Parse(x); err != nil {
|
||||
if _, ok := err.(tiffErrors); ok {
|
||||
return x, err
|
||||
}
|
||||
// This should never happen, as Parse always returns a tiffError
|
||||
// for now, but that could change.
|
||||
return x, fmt.Errorf("exif: parser %v failed (%v)", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// LoadTags loads tags into the available fields from the tiff Directory
|
||||
// using the given tagid-fieldname mapping. Used to load makernote and
|
||||
// other meta-data. If showMissing is true, tags in d that are not in the
|
||||
// fieldMap will be loaded with the FieldName UnknownPrefix followed by the
|
||||
// tag ID (in hex format).
|
||||
func (x *Exif) LoadTags(d *tiff.Dir, fieldMap map[uint16]FieldName, showMissing bool) {
|
||||
for _, tag := range d.Tags {
|
||||
name := fieldMap[tag.Id]
|
||||
if name == "" {
|
||||
if !showMissing {
|
||||
continue
|
||||
}
|
||||
name = FieldName(fmt.Sprintf("%v%x", UnknownPrefix, tag.Id))
|
||||
}
|
||||
x.main[name] = tag
|
||||
}
|
||||
}
|
||||
|
||||
// Get retrieves the EXIF tag for the given field name.
|
||||
//
|
||||
// If the tag is not known or not present, an error is returned. If the
|
||||
// tag name is known, the error will be a TagNotPresentError.
|
||||
func (x *Exif) Get(name FieldName) (*tiff.Tag, error) {
|
||||
if tg, ok := x.main[name]; ok {
|
||||
return tg, nil
|
||||
}
|
||||
return nil, TagNotPresentError(name)
|
||||
}
|
||||
|
||||
// Walker is the interface used to traverse all fields of an Exif object.
|
||||
type Walker interface {
|
||||
// Walk is called for each non-nil EXIF field. Returning a non-nil
|
||||
// error aborts the walk/traversal.
|
||||
Walk(name FieldName, tag *tiff.Tag) error
|
||||
}
|
||||
|
||||
// Walk calls the Walk method of w with the name and tag for every non-nil
|
||||
// EXIF field. If w aborts the walk with an error, that error is returned.
|
||||
func (x *Exif) Walk(w Walker) error {
|
||||
for name, tag := range x.main {
|
||||
if err := w.Walk(name, tag); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DateTime returns the EXIF's "DateTimeOriginal" field, which
|
||||
// is the creation time of the photo. If not found, it tries
|
||||
// the "DateTime" (which is meant as the modtime) instead.
|
||||
// The error will be TagNotPresentErr if none of those tags
|
||||
// were found, or a generic error if the tag value was
|
||||
// not a string, or the error returned by time.Parse.
|
||||
//
|
||||
// If the EXIF lacks timezone information or GPS time, the returned
|
||||
// time's Location will be time.Local.
|
||||
func (x *Exif) DateTime() (time.Time, error) {
|
||||
var dt time.Time
|
||||
tag, err := x.Get(DateTimeOriginal)
|
||||
if err != nil {
|
||||
tag, err = x.Get(DateTime)
|
||||
if err != nil {
|
||||
return dt, err
|
||||
}
|
||||
}
|
||||
if tag.Format() != tiff.StringVal {
|
||||
return dt, errors.New("DateTime[Original] not in string format")
|
||||
}
|
||||
exifTimeLayout := "2006:01:02 15:04:05"
|
||||
dateStr := strings.TrimRight(string(tag.Val), "\x00")
|
||||
// TODO(bradfitz,mpl): look for timezone offset, GPS time, etc.
|
||||
timeZone := time.Local
|
||||
if tz, _ := x.TimeZone(); tz != nil {
|
||||
timeZone = tz
|
||||
}
|
||||
return time.ParseInLocation(exifTimeLayout, dateStr, timeZone)
|
||||
}
|
||||
|
||||
func (x *Exif) TimeZone() (*time.Location, error) {
|
||||
// TODO: parse more timezone fields (e.g. Nikon WorldTime).
|
||||
timeInfo, err := x.Get("Canon.TimeInfo")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if timeInfo.Count < 2 {
|
||||
return nil, errors.New("Canon.TimeInfo does not contain timezone")
|
||||
}
|
||||
offsetMinutes, err := timeInfo.Int(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return time.FixedZone("", offsetMinutes*60), nil
|
||||
}
|
||||
|
||||
func ratFloat(num, dem int64) float64 {
|
||||
return float64(num) / float64(dem)
|
||||
}
|
||||
|
||||
// Tries to parse a Geo degrees value from a string as it was found in some
|
||||
// EXIF data.
|
||||
// Supported formats so far:
|
||||
// - "52,00000,50,00000,34,01180" ==> 52 deg 50'34.0118"
|
||||
// Probably due to locale the comma is used as decimal mark as well as the
|
||||
// separator of three floats (degrees, minutes, seconds)
|
||||
// http://en.wikipedia.org/wiki/Decimal_mark#Hindu.E2.80.93Arabic_numeral_system
|
||||
// - "52.0,50.0,34.01180" ==> 52deg50'34.0118"
|
||||
// - "52,50,34.01180" ==> 52deg50'34.0118"
|
||||
func parseTagDegreesString(s string) (float64, error) {
|
||||
const unparsableErrorFmt = "Unknown coordinate format: %s"
|
||||
isSplitRune := func(c rune) bool {
|
||||
return c == ',' || c == ';'
|
||||
}
|
||||
parts := strings.FieldsFunc(s, isSplitRune)
|
||||
var degrees, minutes, seconds float64
|
||||
var err error
|
||||
switch len(parts) {
|
||||
case 6:
|
||||
degrees, err = strconv.ParseFloat(parts[0]+"."+parts[1], 64)
|
||||
if err != nil {
|
||||
return 0.0, fmt.Errorf(unparsableErrorFmt, s)
|
||||
}
|
||||
minutes, err = strconv.ParseFloat(parts[2]+"."+parts[3], 64)
|
||||
if err != nil {
|
||||
return 0.0, fmt.Errorf(unparsableErrorFmt, s)
|
||||
}
|
||||
minutes = math.Copysign(minutes, degrees)
|
||||
seconds, err = strconv.ParseFloat(parts[4]+"."+parts[5], 64)
|
||||
if err != nil {
|
||||
return 0.0, fmt.Errorf(unparsableErrorFmt, s)
|
||||
}
|
||||
seconds = math.Copysign(seconds, degrees)
|
||||
case 3:
|
||||
degrees, err = strconv.ParseFloat(parts[0], 64)
|
||||
if err != nil {
|
||||
return 0.0, fmt.Errorf(unparsableErrorFmt, s)
|
||||
}
|
||||
minutes, err = strconv.ParseFloat(parts[1], 64)
|
||||
if err != nil {
|
||||
return 0.0, fmt.Errorf(unparsableErrorFmt, s)
|
||||
}
|
||||
minutes = math.Copysign(minutes, degrees)
|
||||
seconds, err = strconv.ParseFloat(parts[2], 64)
|
||||
if err != nil {
|
||||
return 0.0, fmt.Errorf(unparsableErrorFmt, s)
|
||||
}
|
||||
seconds = math.Copysign(seconds, degrees)
|
||||
default:
|
||||
return 0.0, fmt.Errorf(unparsableErrorFmt, s)
|
||||
}
|
||||
return degrees + minutes/60.0 + seconds/3600.0, nil
|
||||
}
|
||||
|
||||
func parse3Rat2(tag *tiff.Tag) ([3]float64, error) {
|
||||
v := [3]float64{}
|
||||
for i := range v {
|
||||
num, den, err := tag.Rat2(i)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
v[i] = ratFloat(num, den)
|
||||
if tag.Count < uint32(i+2) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func tagDegrees(tag *tiff.Tag) (float64, error) {
|
||||
switch tag.Format() {
|
||||
case tiff.RatVal:
|
||||
// The usual case, according to the Exif spec
|
||||
// (http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf,
|
||||
// sec 4.6.6, p. 52 et seq.)
|
||||
v, err := parse3Rat2(tag)
|
||||
if err != nil {
|
||||
return 0.0, err
|
||||
}
|
||||
return v[0] + v[1]/60 + v[2]/3600.0, nil
|
||||
case tiff.StringVal:
|
||||
// Encountered this weird case with a panorama picture taken with a HTC phone
|
||||
s, err := tag.StringVal()
|
||||
if err != nil {
|
||||
return 0.0, err
|
||||
}
|
||||
return parseTagDegreesString(s)
|
||||
default:
|
||||
// don't know how to parse value, give up
|
||||
return 0.0, fmt.Errorf("Malformed EXIF Tag Degrees")
|
||||
}
|
||||
}
|
||||
|
||||
// LatLong returns the latitude and longitude of the photo and
|
||||
// whether it was present.
|
||||
func (x *Exif) LatLong() (lat, long float64, err error) {
|
||||
// All calls of x.Get might return an TagNotPresentError
|
||||
longTag, err := x.Get(FieldName("GPSLongitude"))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ewTag, err := x.Get(FieldName("GPSLongitudeRef"))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
latTag, err := x.Get(FieldName("GPSLatitude"))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
nsTag, err := x.Get(FieldName("GPSLatitudeRef"))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if long, err = tagDegrees(longTag); err != nil {
|
||||
return 0, 0, fmt.Errorf("Cannot parse longitude: %v", err)
|
||||
}
|
||||
if lat, err = tagDegrees(latTag); err != nil {
|
||||
return 0, 0, fmt.Errorf("Cannot parse latitude: %v", err)
|
||||
}
|
||||
ew, err := ewTag.StringVal()
|
||||
if err == nil && ew == "W" {
|
||||
long *= -1.0
|
||||
} else if err != nil {
|
||||
return 0, 0, fmt.Errorf("Cannot parse longitude: %v", err)
|
||||
}
|
||||
ns, err := nsTag.StringVal()
|
||||
if err == nil && ns == "S" {
|
||||
lat *= -1.0
|
||||
} else if err != nil {
|
||||
return 0, 0, fmt.Errorf("Cannot parse longitude: %v", err)
|
||||
}
|
||||
return lat, long, nil
|
||||
}
|
||||
|
||||
// String returns a pretty text representation of the decoded exif data.
|
||||
func (x *Exif) String() string {
|
||||
var buf bytes.Buffer
|
||||
for name, tag := range x.main {
|
||||
fmt.Fprintf(&buf, "%s: %s\n", name, tag)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// JpegThumbnail returns the jpeg thumbnail if it exists. If it doesn't exist,
|
||||
// TagNotPresentError will be returned
|
||||
func (x *Exif) JpegThumbnail() ([]byte, error) {
|
||||
offset, err := x.Get(ThumbJPEGInterchangeFormat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
start, err := offset.Int(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
length, err := x.Get(ThumbJPEGInterchangeFormatLength)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l, err := length.Int(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return x.Raw[start : start+l], nil
|
||||
}
|
||||
|
||||
// MarshalJson implements the encoding/json.Marshaler interface providing output of
|
||||
// all EXIF fields present (names and values).
|
||||
func (x Exif) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(x.main)
|
||||
}
|
||||
|
||||
type appSec struct {
|
||||
marker byte
|
||||
data []byte
|
||||
}
|
||||
|
||||
// newAppSec finds marker in r and returns the corresponding application data
|
||||
// section.
|
||||
func newAppSec(marker byte, r io.Reader) (*appSec, error) {
|
||||
br := bufio.NewReader(r)
|
||||
app := &appSec{marker: marker}
|
||||
var dataLen int
|
||||
|
||||
// seek to marker
|
||||
for dataLen == 0 {
|
||||
if _, err := br.ReadBytes(0xFF); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, err := br.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if c != marker {
|
||||
continue
|
||||
}
|
||||
|
||||
dataLenBytes := make([]byte, 2)
|
||||
for k, _ := range dataLenBytes {
|
||||
c, err := br.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dataLenBytes[k] = c
|
||||
}
|
||||
dataLen = int(binary.BigEndian.Uint16(dataLenBytes)) - 2
|
||||
}
|
||||
|
||||
// read section data
|
||||
nread := 0
|
||||
for nread < dataLen {
|
||||
s := make([]byte, dataLen-nread)
|
||||
n, err := br.Read(s)
|
||||
nread += n
|
||||
if err != nil && nread < dataLen {
|
||||
return nil, err
|
||||
}
|
||||
app.data = append(app.data, s[:n]...)
|
||||
}
|
||||
return app, nil
|
||||
}
|
||||
|
||||
// reader returns a reader on this appSec.
|
||||
func (app *appSec) reader() *bytes.Reader {
|
||||
return bytes.NewReader(app.data)
|
||||
}
|
||||
|
||||
// exifReader returns a reader on this appSec with the read cursor advanced to
|
||||
// the start of the exif's tiff encoded portion.
|
||||
func (app *appSec) exifReader() (*bytes.Reader, error) {
|
||||
if len(app.data) < 6 {
|
||||
return nil, errors.New("exif: failed to find exif intro marker")
|
||||
}
|
||||
|
||||
// read/check for exif special mark
|
||||
exif := app.data[:6]
|
||||
if !bytes.Equal(exif, append([]byte("Exif"), 0x00, 0x00)) {
|
||||
return nil, errors.New("exif: failed to find exif intro marker")
|
||||
}
|
||||
return bytes.NewReader(app.data[6:]), nil
|
||||
}
|
309
vendor/github.com/rwcarlsen/goexif/exif/fields.go
generated
vendored
Normal file
309
vendor/github.com/rwcarlsen/goexif/exif/fields.go
generated
vendored
Normal file
|
@ -0,0 +1,309 @@
|
|||
package exif
|
||||
|
||||
type FieldName string
|
||||
|
||||
// UnknownPrefix is used as the first part of field names for decoded tags for
|
||||
// which there is no known/supported EXIF field.
|
||||
const UnknownPrefix = "UnknownTag_"
|
||||
|
||||
// Primary EXIF fields
|
||||
const (
|
||||
ImageWidth FieldName = "ImageWidth"
|
||||
ImageLength FieldName = "ImageLength" // Image height called Length by EXIF spec
|
||||
BitsPerSample FieldName = "BitsPerSample"
|
||||
Compression FieldName = "Compression"
|
||||
PhotometricInterpretation FieldName = "PhotometricInterpretation"
|
||||
Orientation FieldName = "Orientation"
|
||||
SamplesPerPixel FieldName = "SamplesPerPixel"
|
||||
PlanarConfiguration FieldName = "PlanarConfiguration"
|
||||
YCbCrSubSampling FieldName = "YCbCrSubSampling"
|
||||
YCbCrPositioning FieldName = "YCbCrPositioning"
|
||||
XResolution FieldName = "XResolution"
|
||||
YResolution FieldName = "YResolution"
|
||||
ResolutionUnit FieldName = "ResolutionUnit"
|
||||
DateTime FieldName = "DateTime"
|
||||
ImageDescription FieldName = "ImageDescription"
|
||||
Make FieldName = "Make"
|
||||
Model FieldName = "Model"
|
||||
Software FieldName = "Software"
|
||||
Artist FieldName = "Artist"
|
||||
Copyright FieldName = "Copyright"
|
||||
ExifIFDPointer FieldName = "ExifIFDPointer"
|
||||
GPSInfoIFDPointer FieldName = "GPSInfoIFDPointer"
|
||||
InteroperabilityIFDPointer FieldName = "InteroperabilityIFDPointer"
|
||||
ExifVersion FieldName = "ExifVersion"
|
||||
FlashpixVersion FieldName = "FlashpixVersion"
|
||||
ColorSpace FieldName = "ColorSpace"
|
||||
ComponentsConfiguration FieldName = "ComponentsConfiguration"
|
||||
CompressedBitsPerPixel FieldName = "CompressedBitsPerPixel"
|
||||
PixelXDimension FieldName = "PixelXDimension"
|
||||
PixelYDimension FieldName = "PixelYDimension"
|
||||
MakerNote FieldName = "MakerNote"
|
||||
UserComment FieldName = "UserComment"
|
||||
RelatedSoundFile FieldName = "RelatedSoundFile"
|
||||
DateTimeOriginal FieldName = "DateTimeOriginal"
|
||||
DateTimeDigitized FieldName = "DateTimeDigitized"
|
||||
SubSecTime FieldName = "SubSecTime"
|
||||
SubSecTimeOriginal FieldName = "SubSecTimeOriginal"
|
||||
SubSecTimeDigitized FieldName = "SubSecTimeDigitized"
|
||||
ImageUniqueID FieldName = "ImageUniqueID"
|
||||
ExposureTime FieldName = "ExposureTime"
|
||||
FNumber FieldName = "FNumber"
|
||||
ExposureProgram FieldName = "ExposureProgram"
|
||||
SpectralSensitivity FieldName = "SpectralSensitivity"
|
||||
ISOSpeedRatings FieldName = "ISOSpeedRatings"
|
||||
OECF FieldName = "OECF"
|
||||
ShutterSpeedValue FieldName = "ShutterSpeedValue"
|
||||
ApertureValue FieldName = "ApertureValue"
|
||||
BrightnessValue FieldName = "BrightnessValue"
|
||||
ExposureBiasValue FieldName = "ExposureBiasValue"
|
||||
MaxApertureValue FieldName = "MaxApertureValue"
|
||||
SubjectDistance FieldName = "SubjectDistance"
|
||||
MeteringMode FieldName = "MeteringMode"
|
||||
LightSource FieldName = "LightSource"
|
||||
Flash FieldName = "Flash"
|
||||
FocalLength FieldName = "FocalLength"
|
||||
SubjectArea FieldName = "SubjectArea"
|
||||
FlashEnergy FieldName = "FlashEnergy"
|
||||
SpatialFrequencyResponse FieldName = "SpatialFrequencyResponse"
|
||||
FocalPlaneXResolution FieldName = "FocalPlaneXResolution"
|
||||
FocalPlaneYResolution FieldName = "FocalPlaneYResolution"
|
||||
FocalPlaneResolutionUnit FieldName = "FocalPlaneResolutionUnit"
|
||||
SubjectLocation FieldName = "SubjectLocation"
|
||||
ExposureIndex FieldName = "ExposureIndex"
|
||||
SensingMethod FieldName = "SensingMethod"
|
||||
FileSource FieldName = "FileSource"
|
||||
SceneType FieldName = "SceneType"
|
||||
CFAPattern FieldName = "CFAPattern"
|
||||
CustomRendered FieldName = "CustomRendered"
|
||||
ExposureMode FieldName = "ExposureMode"
|
||||
WhiteBalance FieldName = "WhiteBalance"
|
||||
DigitalZoomRatio FieldName = "DigitalZoomRatio"
|
||||
FocalLengthIn35mmFilm FieldName = "FocalLengthIn35mmFilm"
|
||||
SceneCaptureType FieldName = "SceneCaptureType"
|
||||
GainControl FieldName = "GainControl"
|
||||
Contrast FieldName = "Contrast"
|
||||
Saturation FieldName = "Saturation"
|
||||
Sharpness FieldName = "Sharpness"
|
||||
DeviceSettingDescription FieldName = "DeviceSettingDescription"
|
||||
SubjectDistanceRange FieldName = "SubjectDistanceRange"
|
||||
LensMake FieldName = "LensMake"
|
||||
LensModel FieldName = "LensModel"
|
||||
)
|
||||
|
||||
// Windows-specific tags
|
||||
const (
|
||||
XPTitle FieldName = "XPTitle"
|
||||
XPComment FieldName = "XPComment"
|
||||
XPAuthor FieldName = "XPAuthor"
|
||||
XPKeywords FieldName = "XPKeywords"
|
||||
XPSubject FieldName = "XPSubject"
|
||||
)
|
||||
|
||||
// thumbnail fields
|
||||
const (
|
||||
ThumbJPEGInterchangeFormat FieldName = "ThumbJPEGInterchangeFormat" // offset to thumb jpeg SOI
|
||||
ThumbJPEGInterchangeFormatLength FieldName = "ThumbJPEGInterchangeFormatLength" // byte length of thumb
|
||||
)
|
||||
|
||||
// GPS fields
|
||||
const (
|
||||
GPSVersionID FieldName = "GPSVersionID"
|
||||
GPSLatitudeRef FieldName = "GPSLatitudeRef"
|
||||
GPSLatitude FieldName = "GPSLatitude"
|
||||
GPSLongitudeRef FieldName = "GPSLongitudeRef"
|
||||
GPSLongitude FieldName = "GPSLongitude"
|
||||
GPSAltitudeRef FieldName = "GPSAltitudeRef"
|
||||
GPSAltitude FieldName = "GPSAltitude"
|
||||
GPSTimeStamp FieldName = "GPSTimeStamp"
|
||||
GPSSatelites FieldName = "GPSSatelites"
|
||||
GPSStatus FieldName = "GPSStatus"
|
||||
GPSMeasureMode FieldName = "GPSMeasureMode"
|
||||
GPSDOP FieldName = "GPSDOP"
|
||||
GPSSpeedRef FieldName = "GPSSpeedRef"
|
||||
GPSSpeed FieldName = "GPSSpeed"
|
||||
GPSTrackRef FieldName = "GPSTrackRef"
|
||||
GPSTrack FieldName = "GPSTrack"
|
||||
GPSImgDirectionRef FieldName = "GPSImgDirectionRef"
|
||||
GPSImgDirection FieldName = "GPSImgDirection"
|
||||
GPSMapDatum FieldName = "GPSMapDatum"
|
||||
GPSDestLatitudeRef FieldName = "GPSDestLatitudeRef"
|
||||
GPSDestLatitude FieldName = "GPSDestLatitude"
|
||||
GPSDestLongitudeRef FieldName = "GPSDestLongitudeRef"
|
||||
GPSDestLongitude FieldName = "GPSDestLongitude"
|
||||
GPSDestBearingRef FieldName = "GPSDestBearingRef"
|
||||
GPSDestBearing FieldName = "GPSDestBearing"
|
||||
GPSDestDistanceRef FieldName = "GPSDestDistanceRef"
|
||||
GPSDestDistance FieldName = "GPSDestDistance"
|
||||
GPSProcessingMethod FieldName = "GPSProcessingMethod"
|
||||
GPSAreaInformation FieldName = "GPSAreaInformation"
|
||||
GPSDateStamp FieldName = "GPSDateStamp"
|
||||
GPSDifferential FieldName = "GPSDifferential"
|
||||
)
|
||||
|
||||
// interoperability fields
|
||||
const (
|
||||
InteroperabilityIndex FieldName = "InteroperabilityIndex"
|
||||
)
|
||||
|
||||
var exifFields = map[uint16]FieldName{
|
||||
/////////////////////////////////////
|
||||
////////// IFD 0 ////////////////////
|
||||
/////////////////////////////////////
|
||||
|
||||
// image data structure for the thumbnail
|
||||
0x0100: ImageWidth,
|
||||
0x0101: ImageLength,
|
||||
0x0102: BitsPerSample,
|
||||
0x0103: Compression,
|
||||
0x0106: PhotometricInterpretation,
|
||||
0x0112: Orientation,
|
||||
0x0115: SamplesPerPixel,
|
||||
0x011C: PlanarConfiguration,
|
||||
0x0212: YCbCrSubSampling,
|
||||
0x0213: YCbCrPositioning,
|
||||
0x011A: XResolution,
|
||||
0x011B: YResolution,
|
||||
0x0128: ResolutionUnit,
|
||||
|
||||
// Other tags
|
||||
0x0132: DateTime,
|
||||
0x010E: ImageDescription,
|
||||
0x010F: Make,
|
||||
0x0110: Model,
|
||||
0x0131: Software,
|
||||
0x013B: Artist,
|
||||
0x8298: Copyright,
|
||||
|
||||
// Windows-specific tags
|
||||
0x9c9b: XPTitle,
|
||||
0x9c9c: XPComment,
|
||||
0x9c9d: XPAuthor,
|
||||
0x9c9e: XPKeywords,
|
||||
0x9c9f: XPSubject,
|
||||
|
||||
// private tags
|
||||
exifPointer: ExifIFDPointer,
|
||||
|
||||
/////////////////////////////////////
|
||||
////////// Exif sub IFD /////////////
|
||||
/////////////////////////////////////
|
||||
|
||||
gpsPointer: GPSInfoIFDPointer,
|
||||
interopPointer: InteroperabilityIFDPointer,
|
||||
|
||||
0x9000: ExifVersion,
|
||||
0xA000: FlashpixVersion,
|
||||
|
||||
0xA001: ColorSpace,
|
||||
|
||||
0x9101: ComponentsConfiguration,
|
||||
0x9102: CompressedBitsPerPixel,
|
||||
0xA002: PixelXDimension,
|
||||
0xA003: PixelYDimension,
|
||||
|
||||
0x927C: MakerNote,
|
||||
0x9286: UserComment,
|
||||
|
||||
0xA004: RelatedSoundFile,
|
||||
0x9003: DateTimeOriginal,
|
||||
0x9004: DateTimeDigitized,
|
||||
0x9290: SubSecTime,
|
||||
0x9291: SubSecTimeOriginal,
|
||||
0x9292: SubSecTimeDigitized,
|
||||
|
||||
0xA420: ImageUniqueID,
|
||||
|
||||
// picture conditions
|
||||
0x829A: ExposureTime,
|
||||
0x829D: FNumber,
|
||||
0x8822: ExposureProgram,
|
||||
0x8824: SpectralSensitivity,
|
||||
0x8827: ISOSpeedRatings,
|
||||
0x8828: OECF,
|
||||
0x9201: ShutterSpeedValue,
|
||||
0x9202: ApertureValue,
|
||||
0x9203: BrightnessValue,
|
||||
0x9204: ExposureBiasValue,
|
||||
0x9205: MaxApertureValue,
|
||||
0x9206: SubjectDistance,
|
||||
0x9207: MeteringMode,
|
||||
0x9208: LightSource,
|
||||
0x9209: Flash,
|
||||
0x920A: FocalLength,
|
||||
0x9214: SubjectArea,
|
||||
0xA20B: FlashEnergy,
|
||||
0xA20C: SpatialFrequencyResponse,
|
||||
0xA20E: FocalPlaneXResolution,
|
||||
0xA20F: FocalPlaneYResolution,
|
||||
0xA210: FocalPlaneResolutionUnit,
|
||||
0xA214: SubjectLocation,
|
||||
0xA215: ExposureIndex,
|
||||
0xA217: SensingMethod,
|
||||
0xA300: FileSource,
|
||||
0xA301: SceneType,
|
||||
0xA302: CFAPattern,
|
||||
0xA401: CustomRendered,
|
||||
0xA402: ExposureMode,
|
||||
0xA403: WhiteBalance,
|
||||
0xA404: DigitalZoomRatio,
|
||||
0xA405: FocalLengthIn35mmFilm,
|
||||
0xA406: SceneCaptureType,
|
||||
0xA407: GainControl,
|
||||
0xA408: Contrast,
|
||||
0xA409: Saturation,
|
||||
0xA40A: Sharpness,
|
||||
0xA40B: DeviceSettingDescription,
|
||||
0xA40C: SubjectDistanceRange,
|
||||
0xA433: LensMake,
|
||||
0xA434: LensModel,
|
||||
}
|
||||
|
||||
var gpsFields = map[uint16]FieldName{
|
||||
/////////////////////////////////////
|
||||
//// GPS sub-IFD ////////////////////
|
||||
/////////////////////////////////////
|
||||
0x0: GPSVersionID,
|
||||
0x1: GPSLatitudeRef,
|
||||
0x2: GPSLatitude,
|
||||
0x3: GPSLongitudeRef,
|
||||
0x4: GPSLongitude,
|
||||
0x5: GPSAltitudeRef,
|
||||
0x6: GPSAltitude,
|
||||
0x7: GPSTimeStamp,
|
||||
0x8: GPSSatelites,
|
||||
0x9: GPSStatus,
|
||||
0xA: GPSMeasureMode,
|
||||
0xB: GPSDOP,
|
||||
0xC: GPSSpeedRef,
|
||||
0xD: GPSSpeed,
|
||||
0xE: GPSTrackRef,
|
||||
0xF: GPSTrack,
|
||||
0x10: GPSImgDirectionRef,
|
||||
0x11: GPSImgDirection,
|
||||
0x12: GPSMapDatum,
|
||||
0x13: GPSDestLatitudeRef,
|
||||
0x14: GPSDestLatitude,
|
||||
0x15: GPSDestLongitudeRef,
|
||||
0x16: GPSDestLongitude,
|
||||
0x17: GPSDestBearingRef,
|
||||
0x18: GPSDestBearing,
|
||||
0x19: GPSDestDistanceRef,
|
||||
0x1A: GPSDestDistance,
|
||||
0x1B: GPSProcessingMethod,
|
||||
0x1C: GPSAreaInformation,
|
||||
0x1D: GPSDateStamp,
|
||||
0x1E: GPSDifferential,
|
||||
}
|
||||
|
||||
var interopFields = map[uint16]FieldName{
|
||||
/////////////////////////////////////
|
||||
//// Interoperability sub-IFD ///////
|
||||
/////////////////////////////////////
|
||||
0x1: InteroperabilityIndex,
|
||||
}
|
||||
|
||||
var thumbnailFields = map[uint16]FieldName{
|
||||
0x0201: ThumbJPEGInterchangeFormat,
|
||||
0x0202: ThumbJPEGInterchangeFormatLength,
|
||||
}
|
BIN
vendor/github.com/rwcarlsen/goexif/exif/sample1.jpg
generated
vendored
Normal file
BIN
vendor/github.com/rwcarlsen/goexif/exif/sample1.jpg
generated
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 79 KiB |
Loading…
Add table
Add a link
Reference in a new issue