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"
)
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

View File

@ -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",
}