diff --git a/third_party/github.com/camlistore/goexif/exif/exif.go b/third_party/github.com/camlistore/goexif/exif/exif.go index ee60adae9..fbdb30c21 100644 --- a/third_party/github.com/camlistore/goexif/exif/exif.go +++ b/third_party/github.com/camlistore/goexif/exif/exif.go @@ -16,6 +16,21 @@ import ( "camlistore.org/third_party/github.com/camlistore/goexif/tiff" ) +var validField map[FieldName]bool + +func init() { + validField = make(map[FieldName]bool) + for _, name := range exifFields { + validField[name] = true + } + for _, name := range gpsFields { + validField[name] = true + } + for _, name := range interopFields { + validField[name] = true + } +} + const ( exifPointer = 0x8769 gpsPointer = 0x8825 @@ -38,7 +53,7 @@ func isTagNotPresentErr(err error) bool { type Exif struct { tif *tiff.Tiff - main map[uint16]*tiff.Tag + main map[FieldName]*tiff.Tag } // Decode parses EXIF-encoded data from r and returns a queryable Exif object. @@ -58,31 +73,32 @@ func Decode(r io.Reader) (*Exif, error) { // build an exif structure from the tiff x := &Exif{ - main: map[uint16]*tiff.Tag{}, + main: map[FieldName]*tiff.Tag{}, tif: tif, } ifd0 := tif.Dirs[0] for _, tag := range ifd0.Tags { - x.main[tag.Id] = tag + name := exifFields[tag.Id] + x.main[name] = tag } // recurse into exif, gps, and interop sub-IFDs - if err = x.loadSubDir(er, exifPointer); err != nil { + if err = x.loadSubDir(er, exifIFDPointer, exifFields); err != nil { return x, err } - if err = x.loadSubDir(er, gpsPointer); err != nil { + if err = x.loadSubDir(er, gpsInfoIFDPointer, gpsFields); err != nil { return x, err } - if err = x.loadSubDir(er, interopPointer); err != nil { + if err = x.loadSubDir(er, interoperabilityIFDPointer, interopFields); err != nil { return x, err } return x, nil } -func (x *Exif) loadSubDir(r *bytes.Reader, tagId uint16) error { - tag, ok := x.main[tagId] +func (x *Exif) loadSubDir(r *bytes.Reader, ptrName FieldName, fieldMap map[uint16]FieldName) error { + tag, ok := x.main[ptrName] if !ok { return nil } @@ -97,7 +113,8 @@ func (x *Exif) loadSubDir(r *bytes.Reader, tagId uint16) error { return errors.New("exif: sub-IFD decode failed: " + err.Error()) } for _, tag := range subDir.Tags { - x.main[tag.Id] = tag + name := fieldMap[tag.Id] + x.main[name] = tag } return nil } @@ -107,11 +124,9 @@ func (x *Exif) loadSubDir(r *bytes.Reader, tagId uint16) error { // 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) { - id, ok := fields[name] - if !ok { + if !validField[name] { return nil, fmt.Errorf("exif: invalid tag name %q", name) - } - if tg, ok := x.main[id]; ok { + } else if tg, ok := x.main[name]; ok { return tg, nil } return nil, TagNotPresentError(name) @@ -126,16 +141,8 @@ type Walker interface { // Walk calls the Walk method of w with the name and tag for every non-nil exif // field. func (x *Exif) Walk(w Walker) error { - for name, _ := range fields { - tag, err := x.Get(name) - if isTagNotPresentErr(err) { - continue - } else if err != nil { - panic("field list access/construction is broken - this should never happen") - } - - err = w.Walk(name, tag) - if err != nil { + for name, tag := range x.main { + if err := w.Walk(name, tag); err != nil { return err } } @@ -168,24 +175,14 @@ func (x *Exif) DateTime() (time.Time, error) { // String returns a pretty text representation of the decoded exif data. func (x *Exif) String() string { var buf bytes.Buffer - for name, id := range fields { - if tag, ok := x.main[id]; ok { - fmt.Fprintf(&buf, "%s: %s\n", name, tag) - } + for name, tag := range x.main { + fmt.Fprintf(&buf, "%s: %s\n", name, tag) } return buf.String() } func (x Exif) MarshalJSON() ([]byte, error) { - m := map[string]interface{}{} - - for name, id := range fields { - if tag, ok := x.main[id]; ok { - m[string(name)] = tag - } - } - - return json.Marshal(m) + return json.Marshal(x.main) } type appSec struct { @@ -196,43 +193,41 @@ type appSec struct { // 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} - - buf := bufio.NewReader(r) + var dataLen uint16 // seek to marker - for { - b, err := buf.ReadByte() + 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, err := br.Peek(2) if err != nil { return nil, err } - n, err := buf.Peek(1) - if b == 0xFF && n[0] == marker { - buf.ReadByte() - break - } + dataLen = binary.BigEndian.Uint16(dataLenBytes) } - // read section size - var dataLen uint16 - err := binary.Read(buf, binary.BigEndian, &dataLen) - if err != nil { - return nil, err - } - dataLen -= 2 // subtract length of the 2 byte size marker itself - // read section data nread := 0 for nread < int(dataLen) { s := make([]byte, int(dataLen)-nread) - n, err := buf.Read(s) - if err != nil { + n, err := br.Read(s) + nread += n + if err != nil && nread < int(dataLen) { return nil, err } - nread += n - app.data = append(app.data, s...) + app.data = append(app.data, s[:n]...) } - + app.data = app.data[2:] // exclude dataLenBytes return app, nil } @@ -244,13 +239,13 @@ func (app *appSec) reader() *bytes.Reader { // 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) { - // read/check for exif special mark 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 string(exif) != "Exif"+string([]byte{0x00, 0x00}) { + 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/third_party/github.com/camlistore/goexif/exif/fields.go b/third_party/github.com/camlistore/goexif/exif/fields.go index e99bc6644..08b5a3266 100644 --- a/third_party/github.com/camlistore/goexif/exif/fields.go +++ b/third_party/github.com/camlistore/goexif/exif/fields.go @@ -9,146 +9,156 @@ const ( DateTime FieldName = "DateTime" ) -var fields = map[FieldName]uint16{ +const ( + exifIFDPointer FieldName = "ExifIFDPointer" + gpsInfoIFDPointer = "GPSInfoIFDPointer" + interoperabilityIFDPointer = "InteroperabilityIFDPointer" +) + +var exifFields = map[uint16]FieldName{ ///////////////////////////////////// ////////// IFD 0 //////////////////// ///////////////////////////////////// // image data structure - "ImageWidth": 0x0100, - "ImageLength": 0x0101, - "BitsPerSample": 0x0102, - "Compression": 0x0103, - "PhotometricInterpretation": 0x0106, - "Orientation": 0x0112, - "SamplesPerPixel": 0x0115, - "PlanarConfiguration": 0x011C, - "YCbCrSubSampling": 0x0212, - "YCbCrPositioning": 0x0213, - "XResolution": 0x011A, - "YResolution": 0x011B, - "ResolutionUnit": 0x0128, + 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 - "DateTime": 0x0132, - "ImageDescription": 0x010E, - "Make": 0x010F, - "Model": 0x0110, - "Software": 0x0131, - "Artist": 0x010e, - "Copyright": 0x010e, + 0x0132: "DateTime", + 0x010E: "ImageDescription", + 0x010F: "Make", + 0x0110: "Model", + 0x0131: "Software", + 0x013B: "Artist", + 0x8298: "Copyright", // private tags - "ExifIFDPointer": exifPointer, + exifPointer: "ExifIFDPointer", ///////////////////////////////////// ////////// Exif sub IFD ///////////// ///////////////////////////////////// - "GPSInfoIFDPointer": gpsPointer, - "InteroperabilityIFDPointer": interopPointer, + gpsPointer: "GPSInfoIFDPointer", + interopPointer: "InteroperabilityIFDPointer", - "ExifVersion": 0x9000, - "FlashpixVersion": 0xA000, + 0x9000: "ExifVersion", + 0xA000: "FlashpixVersion", - "ColorSpace": 0xA001, + 0xA001: "ColorSpace", - "ComponentsConfiguration": 0x9101, - "CompressedBitsPerPixel": 0x9102, - "PixelXDimension": 0xA002, - "PixelYDimension": 0xA003, + 0x9101: "ComponentsConfiguration", + 0x9102: "CompressedBitsPerPixel", + 0xA002: "PixelXDimension", + 0xA003: "PixelYDimension", - "MakerNote": 0x927C, - "UserComment": 0x9286, + 0x927C: "MakerNote", + 0x9286: "UserComment", - "RelatedSoundFile": 0xA004, - "DateTimeOriginal": 0x9003, - "DateTimeDigitized": 0x9004, - "SubSecTime": 0x9290, - "SubSecTimeOriginal": 0x9291, - "SubSecTimeDigitized": 0x9292, + 0xA004: "RelatedSoundFile", + 0x9003: "DateTimeOriginal", + 0x9004: "DateTimeDigitized", + 0x9290: "SubSecTime", + 0x9291: "SubSecTimeOriginal", + 0x9292: "SubSecTimeDigitized", - "ImageUniqueID": 0xA420, + 0xA420: "ImageUniqueID", // picture conditions - "ExposureTime": 0x829A, - "FNumber": 0x829D, - "ExposureProgram": 0x8822, - "SpectralSensitivity": 0x8824, - "ISOSpeedRatings": 0x8827, - "OECF": 0x8828, - "ShutterSpeedValue": 0x9201, - "ApertureValue": 0x9202, - "BrightnessValue": 0x9203, - "ExposureBiasValue": 0x9204, - "MaxApertureValue": 0x9205, - "SubjectDistance": 0x9206, - "MeteringMode": 0x9207, - "LightSource": 0x9208, - "Flash": 0x9209, - "FocalLength": 0x920A, - "SubjectArea": 0x9214, - "FlashEnergy": 0xA20B, - "SpatialFrequencyResponse": 0xA20C, - "FocalPlaneXResolution": 0xA20E, - "FocalPlaneYResolution": 0xA20F, - "FocalPlaneResolutionUnit": 0xA210, - "SubjectLocation": 0xA214, - "ExposureIndex": 0xA215, - "SensingMethod": 0xA217, - "FileSource": 0xA300, - "SceneType": 0xA301, - "CFAPattern": 0xA302, - "CustomRendered": 0xA401, - "ExposureMode": 0xA402, - "WhiteBalance": 0xA403, - "DigitalZoomRatio": 0xA404, - "FocalLengthIn35mmFilm": 0xA405, - "SceneCaptureType": 0xA406, - "GainControl": 0xA407, - "Contrast": 0xA408, - "Saturation": 0xA409, - "Sharpness": 0xA40A, - "DeviceSettingDescription": 0xA40B, - "SubjectDistanceRange": 0xA40C, + 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", +} +var gpsFields = map[uint16]FieldName{ ///////////////////////////////////// //// GPS sub-IFD //////////////////// ///////////////////////////////////// - "GPSVersionID": 0x0, - "GPSLatitudeRef": 0x1, - "GPSLatitude": 0x2, - "GPSLongitudeRef": 0x3, - "GPSLongitude": 0x4, - "GPSAltitudeRef": 0x5, - "GPSAltitude": 0x6, - "GPSTimeStamp": 0x7, - "GPSSatelites": 0x8, - "GPSStatus": 0x9, - "GPSMeasureMode": 0xA, - "GPSDOP": 0xB, - "GPSSpeedRef": 0xC, - "GPSSpeed": 0xD, - "GPSTrackRef": 0xE, - "GPSTrack": 0xF, - "GPSImgDirectionRef": 0x10, - "GPSImgDirection": 0x11, - "GPSMapDatum": 0x12, - "GPSDestLatitudeRef": 0x13, - "GPSDestLatitude": 0x14, - "GPSDestLongitudeRef": 0x15, - "GPSDestLongitude": 0x16, - "GPSDestBearingRef": 0x17, - "GPSDestBearing": 0x18, - "GPSDestDistanceRef": 0x19, - "GPSDestDistance": 0x1A, - "GPSProcessingMethod": 0x1B, - "GPSAreaInformation": 0x1C, - "GPSDateStamp": 0x1D, - "GPSDifferential": 0x1E, + 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 /////// ///////////////////////////////////// - "InteroperabilityIndex": 0x1, + 0x1: "InteroperabilityIndex", }