summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/rwcarlsen
diff options
context:
space:
mode:
authorChristopher Speller <crspeller@gmail.com>2016-05-12 15:08:58 -0400
committerChristopher Speller <crspeller@gmail.com>2016-05-12 16:37:29 -0400
commit84d2482ddbff9564c9ad75b2d30af66e3ddfd44d (patch)
tree8bfa567d2b6381f4a996ada2deff8a16aa85a3ac /vendor/github.com/rwcarlsen
parentd1efb66ad7b017f0fbfe6f0c20843b30f396e504 (diff)
downloadchat-84d2482ddbff9564c9ad75b2d30af66e3ddfd44d.tar.gz
chat-84d2482ddbff9564c9ad75b2d30af66e3ddfd44d.tar.bz2
chat-84d2482ddbff9564c9ad75b2d30af66e3ddfd44d.zip
Updating go depencancies. Switching to go1.6 vendoring (#2949)
Diffstat (limited to 'vendor/github.com/rwcarlsen')
-rw-r--r--vendor/github.com/rwcarlsen/goexif/LICENSE24
-rw-r--r--vendor/github.com/rwcarlsen/goexif/exif/README.md4
-rw-r--r--vendor/github.com/rwcarlsen/goexif/exif/exif.go619
-rw-r--r--vendor/github.com/rwcarlsen/goexif/exif/fields.go293
-rw-r--r--vendor/github.com/rwcarlsen/goexif/exif/regen_regress.go79
-rw-r--r--vendor/github.com/rwcarlsen/goexif/exif/sample1.jpgbin0 -> 80603 bytes
-rw-r--r--vendor/github.com/rwcarlsen/goexif/tiff/sample1.tifbin0 -> 18382 bytes
-rw-r--r--vendor/github.com/rwcarlsen/goexif/tiff/tag.go438
-rw-r--r--vendor/github.com/rwcarlsen/goexif/tiff/tiff.go153
9 files changed, 1610 insertions, 0 deletions
diff --git a/vendor/github.com/rwcarlsen/goexif/LICENSE b/vendor/github.com/rwcarlsen/goexif/LICENSE
new file mode 100644
index 000000000..aa6250465
--- /dev/null
+++ b/vendor/github.com/rwcarlsen/goexif/LICENSE
@@ -0,0 +1,24 @@
+
+Copyright (c) 2012, Robert Carlsen & Contributors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/rwcarlsen/goexif/exif/README.md b/vendor/github.com/rwcarlsen/goexif/exif/README.md
new file mode 100644
index 000000000..b3bf5fa0e
--- /dev/null
+++ b/vendor/github.com/rwcarlsen/goexif/exif/README.md
@@ -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*.
+
diff --git a/vendor/github.com/rwcarlsen/goexif/exif/exif.go b/vendor/github.com/rwcarlsen/goexif/exif/exif.go
new file mode 100644
index 000000000..b420729da
--- /dev/null
+++ b/vendor/github.com/rwcarlsen/goexif/exif/exif.go
@@ -0,0 +1,619 @@
+// 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 {
+ 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-encoded data from r and returns a queryable Exif
+// object. After the exif data section is called and the tiff structure
+// 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 := r.Read(header)
+ if err != nil {
+ return nil, err
+ }
+ if n < len(header) {
+ return nil, errors.New("exif: short read on header")
+ }
+
+ var isTiff bool
+ switch string(header) {
+ case "II*\x00":
+ // TIFF - Little endian (Intel)
+ isTiff = true
+ case "MM\x00*":
+ // TIFF - Big endian (Motorola)
+ isTiff = true
+ default:
+ // Not TIFF, assume JPEG
+ }
+
+ // Put the header bytes back into the reader.
+ r = io.MultiReader(bytes.NewReader(header), r)
+ var (
+ er *bytes.Reader
+ tif *tiff.Tiff
+ )
+
+ if 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())
+ } else {
+ // Locate the JPEG APP1 header.
+ var sec *appSec
+ 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.
+ // For now, just always return the time.Local timezone.
+ return time.ParseInLocation(exifTimeLayout, dateStr, time.Local)
+}
+
+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
+}
diff --git a/vendor/github.com/rwcarlsen/goexif/exif/fields.go b/vendor/github.com/rwcarlsen/goexif/exif/fields.go
new file mode 100644
index 000000000..0388d2390
--- /dev/null
+++ b/vendor/github.com/rwcarlsen/goexif/exif/fields.go
@@ -0,0 +1,293 @@
+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 = "ImageLength" // Image height called Length by EXIF spec
+ BitsPerSample = "BitsPerSample"
+ Compression = "Compression"
+ PhotometricInterpretation = "PhotometricInterpretation"
+ Orientation = "Orientation"
+ SamplesPerPixel = "SamplesPerPixel"
+ PlanarConfiguration = "PlanarConfiguration"
+ YCbCrSubSampling = "YCbCrSubSampling"
+ YCbCrPositioning = "YCbCrPositioning"
+ XResolution = "XResolution"
+ YResolution = "YResolution"
+ ResolutionUnit = "ResolutionUnit"
+ DateTime = "DateTime"
+ ImageDescription = "ImageDescription"
+ Make = "Make"
+ Model = "Model"
+ Software = "Software"
+ Artist = "Artist"
+ Copyright = "Copyright"
+ ExifIFDPointer = "ExifIFDPointer"
+ GPSInfoIFDPointer = "GPSInfoIFDPointer"
+ InteroperabilityIFDPointer = "InteroperabilityIFDPointer"
+ ExifVersion = "ExifVersion"
+ FlashpixVersion = "FlashpixVersion"
+ ColorSpace = "ColorSpace"
+ ComponentsConfiguration = "ComponentsConfiguration"
+ CompressedBitsPerPixel = "CompressedBitsPerPixel"
+ PixelXDimension = "PixelXDimension"
+ PixelYDimension = "PixelYDimension"
+ MakerNote = "MakerNote"
+ UserComment = "UserComment"
+ RelatedSoundFile = "RelatedSoundFile"
+ DateTimeOriginal = "DateTimeOriginal"
+ DateTimeDigitized = "DateTimeDigitized"
+ SubSecTime = "SubSecTime"
+ SubSecTimeOriginal = "SubSecTimeOriginal"
+ SubSecTimeDigitized = "SubSecTimeDigitized"
+ ImageUniqueID = "ImageUniqueID"
+ ExposureTime = "ExposureTime"
+ FNumber = "FNumber"
+ ExposureProgram = "ExposureProgram"
+ SpectralSensitivity = "SpectralSensitivity"
+ ISOSpeedRatings = "ISOSpeedRatings"
+ OECF = "OECF"
+ ShutterSpeedValue = "ShutterSpeedValue"
+ ApertureValue = "ApertureValue"
+ BrightnessValue = "BrightnessValue"
+ ExposureBiasValue = "ExposureBiasValue"
+ MaxApertureValue = "MaxApertureValue"
+ SubjectDistance = "SubjectDistance"
+ MeteringMode = "MeteringMode"
+ LightSource = "LightSource"
+ Flash = "Flash"
+ FocalLength = "FocalLength"
+ SubjectArea = "SubjectArea"
+ FlashEnergy = "FlashEnergy"
+ SpatialFrequencyResponse = "SpatialFrequencyResponse"
+ FocalPlaneXResolution = "FocalPlaneXResolution"
+ FocalPlaneYResolution = "FocalPlaneYResolution"
+ FocalPlaneResolutionUnit = "FocalPlaneResolutionUnit"
+ SubjectLocation = "SubjectLocation"
+ ExposureIndex = "ExposureIndex"
+ SensingMethod = "SensingMethod"
+ FileSource = "FileSource"
+ SceneType = "SceneType"
+ CFAPattern = "CFAPattern"
+ CustomRendered = "CustomRendered"
+ ExposureMode = "ExposureMode"
+ WhiteBalance = "WhiteBalance"
+ DigitalZoomRatio = "DigitalZoomRatio"
+ FocalLengthIn35mmFilm = "FocalLengthIn35mmFilm"
+ SceneCaptureType = "SceneCaptureType"
+ GainControl = "GainControl"
+ Contrast = "Contrast"
+ Saturation = "Saturation"
+ Sharpness = "Sharpness"
+ DeviceSettingDescription = "DeviceSettingDescription"
+ SubjectDistanceRange = "SubjectDistanceRange"
+ LensMake = "LensMake"
+ LensModel = "LensModel"
+)
+
+// thumbnail fields
+const (
+ ThumbJPEGInterchangeFormat = "ThumbJPEGInterchangeFormat" // offset to thumb jpeg SOI
+ ThumbJPEGInterchangeFormatLength = "ThumbJPEGInterchangeFormatLength" // byte length of thumb
+)
+
+// GPS fields
+const (
+ GPSVersionID FieldName = "GPSVersionID"
+ GPSLatitudeRef = "GPSLatitudeRef"
+ GPSLatitude = "GPSLatitude"
+ GPSLongitudeRef = "GPSLongitudeRef"
+ GPSLongitude = "GPSLongitude"
+ GPSAltitudeRef = "GPSAltitudeRef"
+ GPSAltitude = "GPSAltitude"
+ GPSTimeStamp = "GPSTimeStamp"
+ GPSSatelites = "GPSSatelites"
+ GPSStatus = "GPSStatus"
+ GPSMeasureMode = "GPSMeasureMode"
+ GPSDOP = "GPSDOP"
+ GPSSpeedRef = "GPSSpeedRef"
+ GPSSpeed = "GPSSpeed"
+ GPSTrackRef = "GPSTrackRef"
+ GPSTrack = "GPSTrack"
+ GPSImgDirectionRef = "GPSImgDirectionRef"
+ GPSImgDirection = "GPSImgDirection"
+ GPSMapDatum = "GPSMapDatum"
+ GPSDestLatitudeRef = "GPSDestLatitudeRef"
+ GPSDestLatitude = "GPSDestLatitude"
+ GPSDestLongitudeRef = "GPSDestLongitudeRef"
+ GPSDestLongitude = "GPSDestLongitude"
+ GPSDestBearingRef = "GPSDestBearingRef"
+ GPSDestBearing = "GPSDestBearing"
+ GPSDestDistanceRef = "GPSDestDistanceRef"
+ GPSDestDistance = "GPSDestDistance"
+ GPSProcessingMethod = "GPSProcessingMethod"
+ GPSAreaInformation = "GPSAreaInformation"
+ GPSDateStamp = "GPSDateStamp"
+ GPSDifferential = "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,
+
+ // 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,
+}
diff --git a/vendor/github.com/rwcarlsen/goexif/exif/regen_regress.go b/vendor/github.com/rwcarlsen/goexif/exif/regen_regress.go
new file mode 100644
index 000000000..17bac5287
--- /dev/null
+++ b/vendor/github.com/rwcarlsen/goexif/exif/regen_regress.go
@@ -0,0 +1,79 @@
+// +build ignore
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/rwcarlsen/goexif/exif"
+ "github.com/rwcarlsen/goexif/tiff"
+)
+
+func main() {
+ flag.Parse()
+ fname := flag.Arg(0)
+
+ dst, err := os.Create(fname)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer dst.Close()
+
+ dir, err := os.Open("samples")
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer dir.Close()
+
+ names, err := dir.Readdirnames(0)
+ if err != nil {
+ log.Fatal(err)
+ }
+ for i, name := range names {
+ names[i] = filepath.Join("samples", name)
+ }
+ makeExpected(names, dst)
+}
+
+func makeExpected(files []string, w io.Writer) {
+ fmt.Fprintf(w, "package exif\n\n")
+ fmt.Fprintf(w, "var regressExpected = map[string]map[FieldName]string{\n")
+
+ for _, name := range files {
+ f, err := os.Open(name)
+ if err != nil {
+ continue
+ }
+
+ x, err := exif.Decode(f)
+ if err != nil {
+ f.Close()
+ continue
+ }
+
+ fmt.Fprintf(w, "\"%v\": map[FieldName]string{\n", filepath.Base(name))
+ x.Walk(&regresswalk{w})
+ fmt.Fprintf(w, "},\n")
+ f.Close()
+ }
+ fmt.Fprintf(w, "}")
+}
+
+type regresswalk struct {
+ wr io.Writer
+}
+
+func (w *regresswalk) Walk(name exif.FieldName, tag *tiff.Tag) error {
+ if strings.HasPrefix(string(name), exif.UnknownPrefix) {
+ fmt.Fprintf(w.wr, "\"%v\": `%v`,\n", name, tag.String())
+ } else {
+ fmt.Fprintf(w.wr, "%v: `%v`,\n", name, tag.String())
+ }
+ return nil
+}
diff --git a/vendor/github.com/rwcarlsen/goexif/exif/sample1.jpg b/vendor/github.com/rwcarlsen/goexif/exif/sample1.jpg
new file mode 100644
index 000000000..87bcf8e33
--- /dev/null
+++ b/vendor/github.com/rwcarlsen/goexif/exif/sample1.jpg
Binary files differ
diff --git a/vendor/github.com/rwcarlsen/goexif/tiff/sample1.tif b/vendor/github.com/rwcarlsen/goexif/tiff/sample1.tif
new file mode 100644
index 000000000..fe51399c5
--- /dev/null
+++ b/vendor/github.com/rwcarlsen/goexif/tiff/sample1.tif
Binary files differ
diff --git a/vendor/github.com/rwcarlsen/goexif/tiff/tag.go b/vendor/github.com/rwcarlsen/goexif/tiff/tag.go
new file mode 100644
index 000000000..66b68e334
--- /dev/null
+++ b/vendor/github.com/rwcarlsen/goexif/tiff/tag.go
@@ -0,0 +1,438 @@
+package tiff
+
+import (
+ "bytes"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "io"
+ "math/big"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+)
+
+// Format specifies the Go type equivalent used to represent the basic
+// tiff data types.
+type Format int
+
+const (
+ IntVal Format = iota
+ FloatVal
+ RatVal
+ StringVal
+ UndefVal
+ OtherVal
+)
+
+var ErrShortReadTagValue = errors.New("tiff: short read of tag value")
+
+var formatNames = map[Format]string{
+ IntVal: "int",
+ FloatVal: "float",
+ RatVal: "rational",
+ StringVal: "string",
+ UndefVal: "undefined",
+ OtherVal: "other",
+}
+
+// DataType represents the basic tiff tag data types.
+type DataType uint16
+
+const (
+ DTByte DataType = 1
+ DTAscii = 2
+ DTShort = 3
+ DTLong = 4
+ DTRational = 5
+ DTSByte = 6
+ DTUndefined = 7
+ DTSShort = 8
+ DTSLong = 9
+ DTSRational = 10
+ DTFloat = 11
+ DTDouble = 12
+)
+
+var typeNames = map[DataType]string{
+ DTByte: "byte",
+ DTAscii: "ascii",
+ DTShort: "short",
+ DTLong: "long",
+ DTRational: "rational",
+ DTSByte: "signed byte",
+ DTUndefined: "undefined",
+ DTSShort: "signed short",
+ DTSLong: "signed long",
+ DTSRational: "signed rational",
+ DTFloat: "float",
+ DTDouble: "double",
+}
+
+// typeSize specifies the size in bytes of each type.
+var typeSize = map[DataType]uint32{
+ DTByte: 1,
+ DTAscii: 1,
+ DTShort: 2,
+ DTLong: 4,
+ DTRational: 8,
+ DTSByte: 1,
+ DTUndefined: 1,
+ DTSShort: 2,
+ DTSLong: 4,
+ DTSRational: 8,
+ DTFloat: 4,
+ DTDouble: 8,
+}
+
+// Tag reflects the parsed content of a tiff IFD tag.
+type Tag struct {
+ // Id is the 2-byte tiff tag identifier.
+ Id uint16
+ // Type is an integer (1 through 12) indicating the tag value's data type.
+ Type DataType
+ // Count is the number of type Type stored in the tag's value (i.e. the
+ // tag's value is an array of type Type and length Count).
+ Count uint32
+ // Val holds the bytes that represent the tag's value.
+ Val []byte
+ // ValOffset holds byte offset of the tag value w.r.t. the beginning of the
+ // reader it was decoded from. Zero if the tag value fit inside the offset
+ // field.
+ ValOffset uint32
+
+ order binary.ByteOrder
+ intVals []int64
+ floatVals []float64
+ ratVals [][]int64
+ strVal string
+ format Format
+}
+
+// DecodeTag parses a tiff-encoded IFD tag from r and returns a Tag object. The
+// first read from r should be the first byte of the tag. ReadAt offsets should
+// generally be relative to the beginning of the tiff structure (not relative
+// to the beginning of the tag).
+func DecodeTag(r ReadAtReader, order binary.ByteOrder) (*Tag, error) {
+ t := new(Tag)
+ t.order = order
+
+ err := binary.Read(r, order, &t.Id)
+ if err != nil {
+ return nil, errors.New("tiff: tag id read failed: " + err.Error())
+ }
+
+ err = binary.Read(r, order, &t.Type)
+ if err != nil {
+ return nil, errors.New("tiff: tag type read failed: " + err.Error())
+ }
+
+ err = binary.Read(r, order, &t.Count)
+ if err != nil {
+ return nil, errors.New("tiff: tag component count read failed: " + err.Error())
+ }
+
+ // There seems to be a relatively common corrupt tag which has a Count of
+ // MaxUint32. This is probably not a valid value, so return early.
+ if t.Count == 1<<32-1 {
+ return t, errors.New("invalid Count offset in tag")
+ }
+
+ valLen := typeSize[t.Type] * t.Count
+ if valLen == 0 {
+ return t, errors.New("zero length tag value")
+ }
+
+ if valLen > 4 {
+ binary.Read(r, order, &t.ValOffset)
+
+ // Use a bytes.Buffer so we don't allocate a huge slice if the tag
+ // is corrupt.
+ var buff bytes.Buffer
+ sr := io.NewSectionReader(r, int64(t.ValOffset), int64(valLen))
+ n, err := io.Copy(&buff, sr)
+ if err != nil {
+ return t, errors.New("tiff: tag value read failed: " + err.Error())
+ } else if n != int64(valLen) {
+ return t, ErrShortReadTagValue
+ }
+ t.Val = buff.Bytes()
+
+ } else {
+ val := make([]byte, valLen)
+ if _, err = io.ReadFull(r, val); err != nil {
+ return t, errors.New("tiff: tag offset read failed: " + err.Error())
+ }
+ // ignore padding.
+ if _, err = io.ReadFull(r, make([]byte, 4-valLen)); err != nil {
+ return t, errors.New("tiff: tag offset read failed: " + err.Error())
+ }
+
+ t.Val = val
+ }
+
+ return t, t.convertVals()
+}
+
+func (t *Tag) convertVals() error {
+ r := bytes.NewReader(t.Val)
+
+ switch t.Type {
+ case DTAscii:
+ if len(t.Val) > 0 {
+ t.strVal = string(t.Val[:len(t.Val)-1]) // ignore the last byte (NULL).
+ }
+ case DTByte:
+ var v uint8
+ t.intVals = make([]int64, int(t.Count))
+ for i := range t.intVals {
+ err := binary.Read(r, t.order, &v)
+ if err != nil {
+ return err
+ }
+ t.intVals[i] = int64(v)
+ }
+ case DTShort:
+ var v uint16
+ t.intVals = make([]int64, int(t.Count))
+ for i := range t.intVals {
+ err := binary.Read(r, t.order, &v)
+ if err != nil {
+ return err
+ }
+ t.intVals[i] = int64(v)
+ }
+ case DTLong:
+ var v uint32
+ t.intVals = make([]int64, int(t.Count))
+ for i := range t.intVals {
+ err := binary.Read(r, t.order, &v)
+ if err != nil {
+ return err
+ }
+ t.intVals[i] = int64(v)
+ }
+ case DTSByte:
+ var v int8
+ t.intVals = make([]int64, int(t.Count))
+ for i := range t.intVals {
+ err := binary.Read(r, t.order, &v)
+ if err != nil {
+ return err
+ }
+ t.intVals[i] = int64(v)
+ }
+ case DTSShort:
+ var v int16
+ t.intVals = make([]int64, int(t.Count))
+ for i := range t.intVals {
+ err := binary.Read(r, t.order, &v)
+ if err != nil {
+ return err
+ }
+ t.intVals[i] = int64(v)
+ }
+ case DTSLong:
+ var v int32
+ t.intVals = make([]int64, int(t.Count))
+ for i := range t.intVals {
+ err := binary.Read(r, t.order, &v)
+ if err != nil {
+ return err
+ }
+ t.intVals[i] = int64(v)
+ }
+ case DTRational:
+ t.ratVals = make([][]int64, int(t.Count))
+ for i := range t.ratVals {
+ var n, d uint32
+ err := binary.Read(r, t.order, &n)
+ if err != nil {
+ return err
+ }
+ err = binary.Read(r, t.order, &d)
+ if err != nil {
+ return err
+ }
+ t.ratVals[i] = []int64{int64(n), int64(d)}
+ }
+ case DTSRational:
+ t.ratVals = make([][]int64, int(t.Count))
+ for i := range t.ratVals {
+ var n, d int32
+ err := binary.Read(r, t.order, &n)
+ if err != nil {
+ return err
+ }
+ err = binary.Read(r, t.order, &d)
+ if err != nil {
+ return err
+ }
+ t.ratVals[i] = []int64{int64(n), int64(d)}
+ }
+ case DTFloat: // float32
+ t.floatVals = make([]float64, int(t.Count))
+ for i := range t.floatVals {
+ var v float32
+ err := binary.Read(r, t.order, &v)
+ if err != nil {
+ return err
+ }
+ t.floatVals[i] = float64(v)
+ }
+ case DTDouble:
+ t.floatVals = make([]float64, int(t.Count))
+ for i := range t.floatVals {
+ var u float64
+ err := binary.Read(r, t.order, &u)
+ if err != nil {
+ return err
+ }
+ t.floatVals[i] = u
+ }
+ }
+
+ switch t.Type {
+ case DTByte, DTShort, DTLong, DTSByte, DTSShort, DTSLong:
+ t.format = IntVal
+ case DTRational, DTSRational:
+ t.format = RatVal
+ case DTFloat, DTDouble:
+ t.format = FloatVal
+ case DTAscii:
+ t.format = StringVal
+ case DTUndefined:
+ t.format = UndefVal
+ default:
+ t.format = OtherVal
+ }
+
+ return nil
+}
+
+// Format returns a value indicating which method can be called to retrieve the
+// tag's value properly typed (e.g. integer, rational, etc.).
+func (t *Tag) Format() Format { return t.format }
+
+func (t *Tag) typeErr(to Format) error {
+ return &wrongFmtErr{typeNames[t.Type], formatNames[to]}
+}
+
+// Rat returns the tag's i'th value as a rational number. It returns a nil and
+// an error if this tag's Format is not RatVal. It panics for zero deminators
+// or if i is out of range.
+func (t *Tag) Rat(i int) (*big.Rat, error) {
+ n, d, err := t.Rat2(i)
+ if err != nil {
+ return nil, err
+ }
+ return big.NewRat(n, d), nil
+}
+
+// Rat2 returns the tag's i'th value as a rational number represented by a
+// numerator-denominator pair. It returns an error if the tag's Format is not
+// RatVal. It panics if i is out of range.
+func (t *Tag) Rat2(i int) (num, den int64, err error) {
+ if t.format != RatVal {
+ return 0, 0, t.typeErr(RatVal)
+ }
+ return t.ratVals[i][0], t.ratVals[i][1], nil
+}
+
+// Int64 returns the tag's i'th value as an integer. It returns an error if the
+// tag's Format is not IntVal. It panics if i is out of range.
+func (t *Tag) Int64(i int) (int64, error) {
+ if t.format != IntVal {
+ return 0, t.typeErr(IntVal)
+ }
+ return t.intVals[i], nil
+}
+
+// Int returns the tag's i'th value as an integer. It returns an error if the
+// tag's Format is not IntVal. It panics if i is out of range.
+func (t *Tag) Int(i int) (int, error) {
+ if t.format != IntVal {
+ return 0, t.typeErr(IntVal)
+ }
+ return int(t.intVals[i]), nil
+}
+
+// Float returns the tag's i'th value as a float. It returns an error if the
+// tag's Format is not IntVal. It panics if i is out of range.
+func (t *Tag) Float(i int) (float64, error) {
+ if t.format != FloatVal {
+ return 0, t.typeErr(FloatVal)
+ }
+ return t.floatVals[i], nil
+}
+
+// StringVal returns the tag's value as a string. It returns an error if the
+// tag's Format is not StringVal. It panics if i is out of range.
+func (t *Tag) StringVal() (string, error) {
+ if t.format != StringVal {
+ return "", t.typeErr(StringVal)
+ }
+ return t.strVal, nil
+}
+
+// String returns a nicely formatted version of the tag.
+func (t *Tag) String() string {
+ data, err := t.MarshalJSON()
+ if err != nil {
+ return "ERROR: " + err.Error()
+ }
+
+ if t.Count == 1 {
+ return strings.Trim(fmt.Sprintf("%s", data), "[]")
+ }
+ return fmt.Sprintf("%s", data)
+}
+
+func (t *Tag) MarshalJSON() ([]byte, error) {
+ switch t.format {
+ case StringVal, UndefVal:
+ return nullString(t.Val), nil
+ case OtherVal:
+ return []byte(fmt.Sprintf("unknown tag type '%v'", t.Type)), nil
+ }
+
+ rv := []string{}
+ for i := 0; i < int(t.Count); i++ {
+ switch t.format {
+ case RatVal:
+ n, d, _ := t.Rat2(i)
+ rv = append(rv, fmt.Sprintf(`"%v/%v"`, n, d))
+ case FloatVal:
+ v, _ := t.Float(i)
+ rv = append(rv, fmt.Sprintf("%v", v))
+ case IntVal:
+ v, _ := t.Int(i)
+ rv = append(rv, fmt.Sprintf("%v", v))
+ }
+ }
+ return []byte(fmt.Sprintf(`[%s]`, strings.Join(rv, ","))), nil
+}
+
+func nullString(in []byte) []byte {
+ rv := bytes.Buffer{}
+ rv.WriteByte('"')
+ for _, b := range in {
+ if unicode.IsPrint(rune(b)) {
+ rv.WriteByte(b)
+ }
+ }
+ rv.WriteByte('"')
+ rvb := rv.Bytes()
+ if utf8.Valid(rvb) {
+ return rvb
+ }
+ return []byte(`""`)
+}
+
+type wrongFmtErr struct {
+ From, To string
+}
+
+func (e *wrongFmtErr) Error() string {
+ return fmt.Sprintf("cannot convert tag type '%v' into '%v'", e.From, e.To)
+}
diff --git a/vendor/github.com/rwcarlsen/goexif/tiff/tiff.go b/vendor/github.com/rwcarlsen/goexif/tiff/tiff.go
new file mode 100644
index 000000000..771e91878
--- /dev/null
+++ b/vendor/github.com/rwcarlsen/goexif/tiff/tiff.go
@@ -0,0 +1,153 @@
+// Package tiff implements TIFF decoding as defined in TIFF 6.0 specification at
+// http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf
+package tiff
+
+import (
+ "bytes"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+)
+
+// ReadAtReader is used when decoding Tiff tags and directories
+type ReadAtReader interface {
+ io.Reader
+ io.ReaderAt
+}
+
+// Tiff provides access to a decoded tiff data structure.
+type Tiff struct {
+ // Dirs is an ordered slice of the tiff's Image File Directories (IFDs).
+ // The IFD at index 0 is IFD0.
+ Dirs []*Dir
+ // The tiff's byte-encoding (i.e. big/little endian).
+ Order binary.ByteOrder
+}
+
+// Decode parses tiff-encoded data from r and returns a Tiff struct that
+// reflects the structure and content of the tiff data. The first read from r
+// should be the first byte of the tiff-encoded data and not necessarily the
+// first byte of an os.File object.
+func Decode(r io.Reader) (*Tiff, error) {
+ data, err := ioutil.ReadAll(r)
+ if err != nil {
+ return nil, errors.New("tiff: could not read data")
+ }
+ buf := bytes.NewReader(data)
+
+ t := new(Tiff)
+
+ // read byte order
+ bo := make([]byte, 2)
+ if _, err = io.ReadFull(buf, bo); err != nil {
+ return nil, errors.New("tiff: could not read tiff byte order")
+ }
+ if string(bo) == "II" {
+ t.Order = binary.LittleEndian
+ } else if string(bo) == "MM" {
+ t.Order = binary.BigEndian
+ } else {
+ return nil, errors.New("tiff: could not read tiff byte order")
+ }
+
+ // check for special tiff marker
+ var sp int16
+ err = binary.Read(buf, t.Order, &sp)
+ if err != nil || 42 != sp {
+ return nil, errors.New("tiff: could not find special tiff marker")
+ }
+
+ // load offset to first IFD
+ var offset int32
+ err = binary.Read(buf, t.Order, &offset)
+ if err != nil {
+ return nil, errors.New("tiff: could not read offset to first IFD")
+ }
+
+ // load IFD's
+ var d *Dir
+ prev := offset
+ for offset != 0 {
+ // seek to offset
+ _, err := buf.Seek(int64(offset), 0)
+ if err != nil {
+ return nil, errors.New("tiff: seek to IFD failed")
+ }
+
+ if buf.Len() == 0 {
+ return nil, errors.New("tiff: seek offset after EOF")
+ }
+
+ // load the dir
+ d, offset, err = DecodeDir(buf, t.Order)
+ if err != nil {
+ return nil, err
+ }
+
+ if offset == prev {
+ return nil, errors.New("tiff: recursive IFD")
+ }
+ prev = offset
+
+ t.Dirs = append(t.Dirs, d)
+ }
+
+ return t, nil
+}
+
+func (tf *Tiff) String() string {
+ var buf bytes.Buffer
+ fmt.Fprint(&buf, "Tiff{")
+ for _, d := range tf.Dirs {
+ fmt.Fprintf(&buf, "%s, ", d.String())
+ }
+ fmt.Fprintf(&buf, "}")
+ return buf.String()
+}
+
+// Dir provides access to the parsed content of a tiff Image File Directory (IFD).
+type Dir struct {
+ Tags []*Tag
+}
+
+// DecodeDir parses a tiff-encoded IFD from r and returns a Dir object. offset
+// is the offset to the next IFD. The first read from r should be at the first
+// byte of the IFD. ReadAt offsets should generally be relative to the
+// beginning of the tiff structure (not relative to the beginning of the IFD).
+func DecodeDir(r ReadAtReader, order binary.ByteOrder) (d *Dir, offset int32, err error) {
+ d = new(Dir)
+
+ // get num of tags in ifd
+ var nTags int16
+ err = binary.Read(r, order, &nTags)
+ if err != nil {
+ return nil, 0, errors.New("tiff: failed to read IFD tag count: " + err.Error())
+ }
+
+ // load tags
+ for n := 0; n < int(nTags); n++ {
+ t, err := DecodeTag(r, order)
+ if err != nil {
+ return nil, 0, err
+ }
+ d.Tags = append(d.Tags, t)
+ }
+
+ // get offset to next ifd
+ err = binary.Read(r, order, &offset)
+ if err != nil {
+ return nil, 0, errors.New("tiff: falied to read offset to next IFD: " + err.Error())
+ }
+
+ return d, offset, nil
+}
+
+func (d *Dir) String() string {
+ s := "Dir{"
+ for _, t := range d.Tags {
+ s += t.String() + ", "
+ }
+ return s + "}"
+}