diff options
author | Christopher Speller <crspeller@gmail.com> | 2016-05-12 15:08:58 -0400 |
---|---|---|
committer | Christopher Speller <crspeller@gmail.com> | 2016-05-12 16:37:29 -0400 |
commit | 84d2482ddbff9564c9ad75b2d30af66e3ddfd44d (patch) | |
tree | 8bfa567d2b6381f4a996ada2deff8a16aa85a3ac /vendor/github.com/rwcarlsen/goexif/exif | |
parent | d1efb66ad7b017f0fbfe6f0c20843b30f396e504 (diff) | |
download | chat-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/goexif/exif')
-rw-r--r-- | vendor/github.com/rwcarlsen/goexif/exif/README.md | 4 | ||||
-rw-r--r-- | vendor/github.com/rwcarlsen/goexif/exif/exif.go | 619 | ||||
-rw-r--r-- | vendor/github.com/rwcarlsen/goexif/exif/fields.go | 293 | ||||
-rw-r--r-- | vendor/github.com/rwcarlsen/goexif/exif/regen_regress.go | 79 | ||||
-rw-r--r-- | vendor/github.com/rwcarlsen/goexif/exif/sample1.jpg | bin | 0 -> 80603 bytes |
5 files changed, 995 insertions, 0 deletions
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(®resswalk{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 Binary files differnew file mode 100644 index 000000000..87bcf8e33 --- /dev/null +++ b/vendor/github.com/rwcarlsen/goexif/exif/sample1.jpg |