goexif: sync with upsteam

Change-Id: Idbb4a38760132b3a782d9cf3898875472411d2f0
This commit is contained in:
mpl 2013-01-21 16:28:58 +01:00
parent c0d20d6bfc
commit 3d128a5191
2 changed files with 177 additions and 172 deletions

View File

@ -16,6 +16,21 @@ import (
"camlistore.org/third_party/github.com/camlistore/goexif/tiff" "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 ( const (
exifPointer = 0x8769 exifPointer = 0x8769
gpsPointer = 0x8825 gpsPointer = 0x8825
@ -38,7 +53,7 @@ func isTagNotPresentErr(err error) bool {
type Exif struct { type Exif struct {
tif *tiff.Tiff 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. // 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 // build an exif structure from the tiff
x := &Exif{ x := &Exif{
main: map[uint16]*tiff.Tag{}, main: map[FieldName]*tiff.Tag{},
tif: tif, tif: tif,
} }
ifd0 := tif.Dirs[0] ifd0 := tif.Dirs[0]
for _, tag := range ifd0.Tags { 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 // 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 return x, err
} }
if err = x.loadSubDir(er, gpsPointer); err != nil { if err = x.loadSubDir(er, gpsInfoIFDPointer, gpsFields); err != nil {
return x, err 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, err
} }
return x, nil return x, nil
} }
func (x *Exif) loadSubDir(r *bytes.Reader, tagId uint16) error { func (x *Exif) loadSubDir(r *bytes.Reader, ptrName FieldName, fieldMap map[uint16]FieldName) error {
tag, ok := x.main[tagId] tag, ok := x.main[ptrName]
if !ok { if !ok {
return nil 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()) return errors.New("exif: sub-IFD decode failed: " + err.Error())
} }
for _, tag := range subDir.Tags { for _, tag := range subDir.Tags {
x.main[tag.Id] = tag name := fieldMap[tag.Id]
x.main[name] = tag
} }
return nil 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 // 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. // tag name is known, the error will be a TagNotPresentError.
func (x *Exif) Get(name FieldName) (*tiff.Tag, error) { func (x *Exif) Get(name FieldName) (*tiff.Tag, error) {
id, ok := fields[name] if !validField[name] {
if !ok {
return nil, fmt.Errorf("exif: invalid tag name %q", name) return nil, fmt.Errorf("exif: invalid tag name %q", name)
} } else if tg, ok := x.main[name]; ok {
if tg, ok := x.main[id]; ok {
return tg, nil return tg, nil
} }
return nil, TagNotPresentError(name) 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 // Walk calls the Walk method of w with the name and tag for every non-nil exif
// field. // field.
func (x *Exif) Walk(w Walker) error { func (x *Exif) Walk(w Walker) error {
for name, _ := range fields { for name, tag := range x.main {
tag, err := x.Get(name) if err := w.Walk(name, tag); err != nil {
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 {
return err return err
} }
} }
@ -168,24 +175,14 @@ func (x *Exif) DateTime() (time.Time, error) {
// String returns a pretty text representation of the decoded exif data. // String returns a pretty text representation of the decoded exif data.
func (x *Exif) String() string { func (x *Exif) String() string {
var buf bytes.Buffer var buf bytes.Buffer
for name, id := range fields { for name, tag := range x.main {
if tag, ok := x.main[id]; ok { fmt.Fprintf(&buf, "%s: %s\n", name, tag)
fmt.Fprintf(&buf, "%s: %s\n", name, tag)
}
} }
return buf.String() return buf.String()
} }
func (x Exif) MarshalJSON() ([]byte, error) { func (x Exif) MarshalJSON() ([]byte, error) {
m := map[string]interface{}{} return json.Marshal(x.main)
for name, id := range fields {
if tag, ok := x.main[id]; ok {
m[string(name)] = tag
}
}
return json.Marshal(m)
} }
type appSec struct { type appSec struct {
@ -196,43 +193,41 @@ type appSec struct {
// newAppSec finds marker in r and returns the corresponding application data // newAppSec finds marker in r and returns the corresponding application data
// section. // section.
func newAppSec(marker byte, r io.Reader) (*appSec, error) { func newAppSec(marker byte, r io.Reader) (*appSec, error) {
br := bufio.NewReader(r)
app := &appSec{marker: marker} app := &appSec{marker: marker}
var dataLen uint16
buf := bufio.NewReader(r)
// seek to marker // seek to marker
for { for dataLen == 0 {
b, err := buf.ReadByte() 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 { if err != nil {
return nil, err return nil, err
} }
n, err := buf.Peek(1) dataLen = binary.BigEndian.Uint16(dataLenBytes)
if b == 0xFF && n[0] == marker {
buf.ReadByte()
break
}
} }
// 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 // read section data
nread := 0 nread := 0
for nread < int(dataLen) { for nread < int(dataLen) {
s := make([]byte, int(dataLen)-nread) s := make([]byte, int(dataLen)-nread)
n, err := buf.Read(s) n, err := br.Read(s)
if err != nil { nread += n
if err != nil && nread < int(dataLen) {
return nil, err return nil, err
} }
nread += n app.data = append(app.data, s[:n]...)
app.data = append(app.data, s...)
} }
app.data = app.data[2:] // exclude dataLenBytes
return app, nil 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 // exifReader returns a reader on this appSec with the read cursor advanced to
// the start of the exif's tiff encoded portion. // the start of the exif's tiff encoded portion.
func (app *appSec) exifReader() (*bytes.Reader, error) { func (app *appSec) exifReader() (*bytes.Reader, error) {
// read/check for exif special mark
if len(app.data) < 6 { if len(app.data) < 6 {
return nil, errors.New("exif: failed to find exif intro marker") return nil, errors.New("exif: failed to find exif intro marker")
} }
// read/check for exif special mark
exif := app.data[:6] 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 nil, errors.New("exif: failed to find exif intro marker")
} }
return bytes.NewReader(app.data[6:]), nil return bytes.NewReader(app.data[6:]), nil

View File

@ -9,146 +9,156 @@ const (
DateTime FieldName = "DateTime" DateTime FieldName = "DateTime"
) )
var fields = map[FieldName]uint16{ const (
exifIFDPointer FieldName = "ExifIFDPointer"
gpsInfoIFDPointer = "GPSInfoIFDPointer"
interoperabilityIFDPointer = "InteroperabilityIFDPointer"
)
var exifFields = map[uint16]FieldName{
///////////////////////////////////// /////////////////////////////////////
////////// IFD 0 //////////////////// ////////// IFD 0 ////////////////////
///////////////////////////////////// /////////////////////////////////////
// image data structure // image data structure
"ImageWidth": 0x0100, 0x0100: "ImageWidth",
"ImageLength": 0x0101, 0x0101: "ImageLength",
"BitsPerSample": 0x0102, 0x0102: "BitsPerSample",
"Compression": 0x0103, 0x0103: "Compression",
"PhotometricInterpretation": 0x0106, 0x0106: "PhotometricInterpretation",
"Orientation": 0x0112, 0x0112: "Orientation",
"SamplesPerPixel": 0x0115, 0x0115: "SamplesPerPixel",
"PlanarConfiguration": 0x011C, 0x011C: "PlanarConfiguration",
"YCbCrSubSampling": 0x0212, 0x0212: "YCbCrSubSampling",
"YCbCrPositioning": 0x0213, 0x0213: "YCbCrPositioning",
"XResolution": 0x011A, 0x011A: "XResolution",
"YResolution": 0x011B, 0x011B: "YResolution",
"ResolutionUnit": 0x0128, 0x0128: "ResolutionUnit",
// Other tags // Other tags
"DateTime": 0x0132, 0x0132: "DateTime",
"ImageDescription": 0x010E, 0x010E: "ImageDescription",
"Make": 0x010F, 0x010F: "Make",
"Model": 0x0110, 0x0110: "Model",
"Software": 0x0131, 0x0131: "Software",
"Artist": 0x010e, 0x013B: "Artist",
"Copyright": 0x010e, 0x8298: "Copyright",
// private tags // private tags
"ExifIFDPointer": exifPointer, exifPointer: "ExifIFDPointer",
///////////////////////////////////// /////////////////////////////////////
////////// Exif sub IFD ///////////// ////////// Exif sub IFD /////////////
///////////////////////////////////// /////////////////////////////////////
"GPSInfoIFDPointer": gpsPointer, gpsPointer: "GPSInfoIFDPointer",
"InteroperabilityIFDPointer": interopPointer, interopPointer: "InteroperabilityIFDPointer",
"ExifVersion": 0x9000, 0x9000: "ExifVersion",
"FlashpixVersion": 0xA000, 0xA000: "FlashpixVersion",
"ColorSpace": 0xA001, 0xA001: "ColorSpace",
"ComponentsConfiguration": 0x9101, 0x9101: "ComponentsConfiguration",
"CompressedBitsPerPixel": 0x9102, 0x9102: "CompressedBitsPerPixel",
"PixelXDimension": 0xA002, 0xA002: "PixelXDimension",
"PixelYDimension": 0xA003, 0xA003: "PixelYDimension",
"MakerNote": 0x927C, 0x927C: "MakerNote",
"UserComment": 0x9286, 0x9286: "UserComment",
"RelatedSoundFile": 0xA004, 0xA004: "RelatedSoundFile",
"DateTimeOriginal": 0x9003, 0x9003: "DateTimeOriginal",
"DateTimeDigitized": 0x9004, 0x9004: "DateTimeDigitized",
"SubSecTime": 0x9290, 0x9290: "SubSecTime",
"SubSecTimeOriginal": 0x9291, 0x9291: "SubSecTimeOriginal",
"SubSecTimeDigitized": 0x9292, 0x9292: "SubSecTimeDigitized",
"ImageUniqueID": 0xA420, 0xA420: "ImageUniqueID",
// picture conditions // picture conditions
"ExposureTime": 0x829A, 0x829A: "ExposureTime",
"FNumber": 0x829D, 0x829D: "FNumber",
"ExposureProgram": 0x8822, 0x8822: "ExposureProgram",
"SpectralSensitivity": 0x8824, 0x8824: "SpectralSensitivity",
"ISOSpeedRatings": 0x8827, 0x8827: "ISOSpeedRatings",
"OECF": 0x8828, 0x8828: "OECF",
"ShutterSpeedValue": 0x9201, 0x9201: "ShutterSpeedValue",
"ApertureValue": 0x9202, 0x9202: "ApertureValue",
"BrightnessValue": 0x9203, 0x9203: "BrightnessValue",
"ExposureBiasValue": 0x9204, 0x9204: "ExposureBiasValue",
"MaxApertureValue": 0x9205, 0x9205: "MaxApertureValue",
"SubjectDistance": 0x9206, 0x9206: "SubjectDistance",
"MeteringMode": 0x9207, 0x9207: "MeteringMode",
"LightSource": 0x9208, 0x9208: "LightSource",
"Flash": 0x9209, 0x9209: "Flash",
"FocalLength": 0x920A, 0x920A: "FocalLength",
"SubjectArea": 0x9214, 0x9214: "SubjectArea",
"FlashEnergy": 0xA20B, 0xA20B: "FlashEnergy",
"SpatialFrequencyResponse": 0xA20C, 0xA20C: "SpatialFrequencyResponse",
"FocalPlaneXResolution": 0xA20E, 0xA20E: "FocalPlaneXResolution",
"FocalPlaneYResolution": 0xA20F, 0xA20F: "FocalPlaneYResolution",
"FocalPlaneResolutionUnit": 0xA210, 0xA210: "FocalPlaneResolutionUnit",
"SubjectLocation": 0xA214, 0xA214: "SubjectLocation",
"ExposureIndex": 0xA215, 0xA215: "ExposureIndex",
"SensingMethod": 0xA217, 0xA217: "SensingMethod",
"FileSource": 0xA300, 0xA300: "FileSource",
"SceneType": 0xA301, 0xA301: "SceneType",
"CFAPattern": 0xA302, 0xA302: "CFAPattern",
"CustomRendered": 0xA401, 0xA401: "CustomRendered",
"ExposureMode": 0xA402, 0xA402: "ExposureMode",
"WhiteBalance": 0xA403, 0xA403: "WhiteBalance",
"DigitalZoomRatio": 0xA404, 0xA404: "DigitalZoomRatio",
"FocalLengthIn35mmFilm": 0xA405, 0xA405: "FocalLengthIn35mmFilm",
"SceneCaptureType": 0xA406, 0xA406: "SceneCaptureType",
"GainControl": 0xA407, 0xA407: "GainControl",
"Contrast": 0xA408, 0xA408: "Contrast",
"Saturation": 0xA409, 0xA409: "Saturation",
"Sharpness": 0xA40A, 0xA40A: "Sharpness",
"DeviceSettingDescription": 0xA40B, 0xA40B: "DeviceSettingDescription",
"SubjectDistanceRange": 0xA40C, 0xA40C: "SubjectDistanceRange",
}
var gpsFields = map[uint16]FieldName{
///////////////////////////////////// /////////////////////////////////////
//// GPS sub-IFD //////////////////// //// GPS sub-IFD ////////////////////
///////////////////////////////////// /////////////////////////////////////
"GPSVersionID": 0x0, 0x0: "GPSVersionID",
"GPSLatitudeRef": 0x1, 0x1: "GPSLatitudeRef",
"GPSLatitude": 0x2, 0x2: "GPSLatitude",
"GPSLongitudeRef": 0x3, 0x3: "GPSLongitudeRef",
"GPSLongitude": 0x4, 0x4: "GPSLongitude",
"GPSAltitudeRef": 0x5, 0x5: "GPSAltitudeRef",
"GPSAltitude": 0x6, 0x6: "GPSAltitude",
"GPSTimeStamp": 0x7, 0x7: "GPSTimeStamp",
"GPSSatelites": 0x8, 0x8: "GPSSatelites",
"GPSStatus": 0x9, 0x9: "GPSStatus",
"GPSMeasureMode": 0xA, 0xA: "GPSMeasureMode",
"GPSDOP": 0xB, 0xB: "GPSDOP",
"GPSSpeedRef": 0xC, 0xC: "GPSSpeedRef",
"GPSSpeed": 0xD, 0xD: "GPSSpeed",
"GPSTrackRef": 0xE, 0xE: "GPSTrackRef",
"GPSTrack": 0xF, 0xF: "GPSTrack",
"GPSImgDirectionRef": 0x10, 0x10: "GPSImgDirectionRef",
"GPSImgDirection": 0x11, 0x11: "GPSImgDirection",
"GPSMapDatum": 0x12, 0x12: "GPSMapDatum",
"GPSDestLatitudeRef": 0x13, 0x13: "GPSDestLatitudeRef",
"GPSDestLatitude": 0x14, 0x14: "GPSDestLatitude",
"GPSDestLongitudeRef": 0x15, 0x15: "GPSDestLongitudeRef",
"GPSDestLongitude": 0x16, 0x16: "GPSDestLongitude",
"GPSDestBearingRef": 0x17, 0x17: "GPSDestBearingRef",
"GPSDestBearing": 0x18, 0x18: "GPSDestBearing",
"GPSDestDistanceRef": 0x19, 0x19: "GPSDestDistanceRef",
"GPSDestDistance": 0x1A, 0x1A: "GPSDestDistance",
"GPSProcessingMethod": 0x1B, 0x1B: "GPSProcessingMethod",
"GPSAreaInformation": 0x1C, 0x1C: "GPSAreaInformation",
"GPSDateStamp": 0x1D, 0x1D: "GPSDateStamp",
"GPSDifferential": 0x1E, 0x1E: "GPSDifferential",
}
var interopFields = map[uint16]FieldName{
///////////////////////////////////// /////////////////////////////////////
//// Interoperability sub-IFD /////// //// Interoperability sub-IFD ///////
///////////////////////////////////// /////////////////////////////////////
"InteroperabilityIndex": 0x1, 0x1: "InteroperabilityIndex",
} }