summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/rwcarlsen/goexif/exif/exif.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/rwcarlsen/goexif/exif/exif.go')
-rw-r--r--vendor/github.com/rwcarlsen/goexif/exif/exif.go619
1 files changed, 619 insertions, 0 deletions
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
+}