mirror of https://github.com/perkeep/perkeep.git
goexif: sync with upsteam
Change-Id: Idbb4a38760132b3a782d9cf3898875472411d2f0
This commit is contained in:
parent
c0d20d6bfc
commit
3d128a5191
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue