mirror of https://github.com/dsoprea/go-exif.git
ifd: Implemented recursion. Refactored IFD seeking.
- tags.yaml: Uncommented previously-unhandleable tags.pull/3/head
parent
34fb63841d
commit
172013e0f0
312
assets/tags.yaml
312
assets/tags.yaml
|
@ -71,10 +71,9 @@ GPSInfo:
|
||||||
name: GPSDateStamp
|
name: GPSDateStamp
|
||||||
- id: 0x001e
|
- id: 0x001e
|
||||||
name: GPSDifferential
|
name: GPSDifferential
|
||||||
Image:
|
IFD:
|
||||||
# Conflicts with GPSDOP and may not even be official.
|
- id: 0x000b
|
||||||
#- id: 0x000b
|
name: ProcessingSoftware
|
||||||
# name: ProcessingSoftware
|
|
||||||
- id: 0x00fe
|
- id: 0x00fe
|
||||||
name: NewSubfileType
|
name: NewSubfileType
|
||||||
- id: 0x00ff
|
- id: 0x00ff
|
||||||
|
@ -483,158 +482,153 @@ Image:
|
||||||
name: OpcodeList3
|
name: OpcodeList3
|
||||||
- id: 0xc761
|
- id: 0xc761
|
||||||
name: NoiseProfile
|
name: NoiseProfile
|
||||||
# Note sure about this. ExifTool doesn't recognize it and 0x0001 conflicts with GPSLatitudeRef.
|
Iop:
|
||||||
#
|
- id: 0x0001
|
||||||
# Iop:
|
name: InteroperabilityIndex
|
||||||
# - id: 0x0001
|
- id: 0x0002
|
||||||
# name: InteroperabilityIndex
|
name: InteroperabilityVersion
|
||||||
# - id: 0x0002
|
- id: 0x1000
|
||||||
# name: InteroperabilityVersion
|
name: RelatedImageFileFormat
|
||||||
# - id: 0x1000
|
- id: 0x1001
|
||||||
# name: RelatedImageFileFormat
|
name: RelatedImageWidth
|
||||||
# - id: 0x1001
|
- id: 0x1002
|
||||||
# name: RelatedImageWidth
|
name: RelatedImageLength
|
||||||
# - id: 0x1002
|
Exif:
|
||||||
# name: RelatedImageLength
|
- id: 0x829a
|
||||||
#
|
name: ExposureTime
|
||||||
# Aren't recognized by ExifTool nor the PDF documentation (included).
|
- id: 0x829d
|
||||||
#
|
name: FNumber
|
||||||
# Photo:
|
- id: 0x8822
|
||||||
# - id: 0x829a
|
name: ExposureProgram
|
||||||
# name: ExposureTime
|
- id: 0x8824
|
||||||
# - id: 0x829d
|
name: SpectralSensitivity
|
||||||
# name: FNumber
|
- id: 0x8827
|
||||||
# - id: 0x8822
|
name: ISOSpeedRatings
|
||||||
# name: ExposureProgram
|
- id: 0x8828
|
||||||
# - id: 0x8824
|
name: OECF
|
||||||
# name: SpectralSensitivity
|
- id: 0x8830
|
||||||
# - id: 0x8827
|
name: SensitivityType
|
||||||
# name: ISOSpeedRatings
|
- id: 0x8831
|
||||||
# - id: 0x8828
|
name: StandardOutputSensitivity
|
||||||
# name: OECF
|
- id: 0x8832
|
||||||
# - id: 0x8830
|
name: RecommendedExposureIndex
|
||||||
# name: SensitivityType
|
- id: 0x8833
|
||||||
# - id: 0x8831
|
name: ISOSpeed
|
||||||
# name: StandardOutputSensitivity
|
- id: 0x8834
|
||||||
# - id: 0x8832
|
name: ISOSpeedLatitudeyyy
|
||||||
# name: RecommendedExposureIndex
|
- id: 0x8835
|
||||||
# - id: 0x8833
|
name: ISOSpeedLatitudezzz
|
||||||
# name: ISOSpeed
|
- id: 0x9000
|
||||||
# - id: 0x8834
|
name: ExifVersion
|
||||||
# name: ISOSpeedLatitudeyyy
|
- id: 0x9003
|
||||||
# - id: 0x8835
|
name: DateTimeOriginal
|
||||||
# name: ISOSpeedLatitudezzz
|
- id: 0x9004
|
||||||
# - id: 0x9000
|
name: DateTimeDigitized
|
||||||
# name: ExifVersion
|
- id: 0x9101
|
||||||
# - id: 0x9003
|
name: ComponentsConfiguration
|
||||||
# name: DateTimeOriginal
|
- id: 0x9102
|
||||||
# - id: 0x9004
|
name: CompressedBitsPerPixel
|
||||||
# name: DateTimeDigitized
|
- id: 0x9201
|
||||||
# - id: 0x9101
|
name: ShutterSpeedValue
|
||||||
# name: ComponentsConfiguration
|
- id: 0x9202
|
||||||
# - id: 0x9102
|
name: ApertureValue
|
||||||
# name: CompressedBitsPerPixel
|
- id: 0x9203
|
||||||
# - id: 0x9201
|
name: BrightnessValue
|
||||||
# name: ShutterSpeedValue
|
- id: 0x9204
|
||||||
# - id: 0x9202
|
name: ExposureBiasValue
|
||||||
# name: ApertureValue
|
- id: 0x9205
|
||||||
# - id: 0x9203
|
name: MaxApertureValue
|
||||||
# name: BrightnessValue
|
- id: 0x9206
|
||||||
# - id: 0x9204
|
name: SubjectDistance
|
||||||
# name: ExposureBiasValue
|
- id: 0x9207
|
||||||
# - id: 0x9205
|
name: MeteringMode
|
||||||
# name: MaxApertureValue
|
- id: 0x9208
|
||||||
# - id: 0x9206
|
name: LightSource
|
||||||
# name: SubjectDistance
|
- id: 0x9209
|
||||||
# - id: 0x9207
|
name: Flash
|
||||||
# name: MeteringMode
|
- id: 0x920a
|
||||||
# - id: 0x9208
|
name: FocalLength
|
||||||
# name: LightSource
|
- id: 0x9214
|
||||||
# - id: 0x9209
|
name: SubjectArea
|
||||||
# name: Flash
|
- id: 0x927c
|
||||||
# - id: 0x920a
|
name: MakerNote
|
||||||
# name: FocalLength
|
- id: 0x9286
|
||||||
# - id: 0x9214
|
name: UserComment
|
||||||
# name: SubjectArea
|
- id: 0x9290
|
||||||
# - id: 0x927c
|
name: SubSecTime
|
||||||
# name: MakerNote
|
- id: 0x9291
|
||||||
# - id: 0x9286
|
name: SubSecTimeOriginal
|
||||||
# name: UserComment
|
- id: 0x9292
|
||||||
# - id: 0x9290
|
name: SubSecTimeDigitized
|
||||||
# name: SubSecTime
|
- id: 0xa000
|
||||||
# - id: 0x9291
|
name: FlashpixVersion
|
||||||
# name: SubSecTimeOriginal
|
- id: 0xa001
|
||||||
# - id: 0x9292
|
name: ColorSpace
|
||||||
# name: SubSecTimeDigitized
|
- id: 0xa002
|
||||||
# - id: 0xa000
|
name: PixelXDimension
|
||||||
# name: FlashpixVersion
|
- id: 0xa003
|
||||||
# - id: 0xa001
|
name: PixelYDimension
|
||||||
# name: ColorSpace
|
- id: 0xa004
|
||||||
# - id: 0xa002
|
name: RelatedSoundFile
|
||||||
# name: PixelXDimension
|
- id: 0xa005
|
||||||
# - id: 0xa003
|
name: InteroperabilityTag
|
||||||
# name: PixelYDimension
|
- id: 0xa20b
|
||||||
# - id: 0xa004
|
name: FlashEnergy
|
||||||
# name: RelatedSoundFile
|
- id: 0xa20c
|
||||||
# - id: 0xa005
|
name: SpatialFrequencyResponse
|
||||||
# name: InteroperabilityTag
|
- id: 0xa20e
|
||||||
# - id: 0xa20b
|
name: FocalPlaneXResolution
|
||||||
# name: FlashEnergy
|
- id: 0xa20f
|
||||||
# - id: 0xa20c
|
name: FocalPlaneYResolution
|
||||||
# name: SpatialFrequencyResponse
|
- id: 0xa210
|
||||||
# - id: 0xa20e
|
name: FocalPlaneResolutionUnit
|
||||||
# name: FocalPlaneXResolution
|
- id: 0xa214
|
||||||
# - id: 0xa20f
|
name: SubjectLocation
|
||||||
# name: FocalPlaneYResolution
|
- id: 0xa215
|
||||||
# - id: 0xa210
|
name: ExposureIndex
|
||||||
# name: FocalPlaneResolutionUnit
|
- id: 0xa217
|
||||||
# - id: 0xa214
|
name: SensingMethod
|
||||||
# name: SubjectLocation
|
- id: 0xa300
|
||||||
# - id: 0xa215
|
name: FileSource
|
||||||
# name: ExposureIndex
|
- id: 0xa301
|
||||||
# - id: 0xa217
|
name: SceneType
|
||||||
# name: SensingMethod
|
- id: 0xa302
|
||||||
# - id: 0xa300
|
name: CFAPattern
|
||||||
# name: FileSource
|
- id: 0xa401
|
||||||
# - id: 0xa301
|
name: CustomRendered
|
||||||
# name: SceneType
|
- id: 0xa402
|
||||||
# - id: 0xa302
|
name: ExposureMode
|
||||||
# name: CFAPattern
|
- id: 0xa403
|
||||||
# - id: 0xa401
|
name: WhiteBalance
|
||||||
# name: CustomRendered
|
- id: 0xa404
|
||||||
# - id: 0xa402
|
name: DigitalZoomRatio
|
||||||
# name: ExposureMode
|
- id: 0xa405
|
||||||
# - id: 0xa403
|
name: FocalLengthIn35mmFilm
|
||||||
# name: WhiteBalance
|
- id: 0xa406
|
||||||
# - id: 0xa404
|
name: SceneCaptureType
|
||||||
# name: DigitalZoomRatio
|
- id: 0xa407
|
||||||
# - id: 0xa405
|
name: GainControl
|
||||||
# name: FocalLengthIn35mmFilm
|
- id: 0xa408
|
||||||
# - id: 0xa406
|
name: Contrast
|
||||||
# name: SceneCaptureType
|
- id: 0xa409
|
||||||
# - id: 0xa407
|
name: Saturation
|
||||||
# name: GainControl
|
- id: 0xa40a
|
||||||
# - id: 0xa408
|
name: Sharpness
|
||||||
# name: Contrast
|
- id: 0xa40b
|
||||||
# - id: 0xa409
|
name: DeviceSettingDescription
|
||||||
# name: Saturation
|
- id: 0xa40c
|
||||||
# - id: 0xa40a
|
name: SubjectDistanceRange
|
||||||
# name: Sharpness
|
- id: 0xa420
|
||||||
# - id: 0xa40b
|
name: ImageUniqueID
|
||||||
# name: DeviceSettingDescription
|
- id: 0xa430
|
||||||
# - id: 0xa40c
|
name: CameraOwnerName
|
||||||
# name: SubjectDistanceRange
|
- id: 0xa431
|
||||||
# - id: 0xa420
|
name: BodySerialNumber
|
||||||
# name: ImageUniqueID
|
- id: 0xa432
|
||||||
# - id: 0xa430
|
name: LensSpecification
|
||||||
# name: CameraOwnerName
|
- id: 0xa433
|
||||||
# - id: 0xa431
|
name: LensMake
|
||||||
# name: BodySerialNumber
|
- id: 0xa434
|
||||||
# - id: 0xa432
|
name: LensModel
|
||||||
# name: LensSpecification
|
- id: 0xa435
|
||||||
# - id: 0xa433
|
name: LensSerialNumber
|
||||||
# name: LensMake
|
|
||||||
# - id: 0xa434
|
|
||||||
# name: LensModel
|
|
||||||
# - id: 0xa435
|
|
||||||
# name: LensSerialNumber
|
|
||||||
|
|
2
exif.go
2
exif.go
|
@ -69,7 +69,7 @@ func (e *Exif) Parse(data []byte, visitor TagVisitor) (err error) {
|
||||||
|
|
||||||
ifd := NewIfd(data, byteOrder)
|
ifd := NewIfd(data, byteOrder)
|
||||||
|
|
||||||
err = ifd.Scan(visitor, firstIfdOffset)
|
err = ifd.Scan(IfdStandard, firstIfdOffset, visitor)
|
||||||
log.PanicIf(err)
|
log.PanicIf(err)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
103
exif_test.go
103
exif_test.go
|
@ -34,6 +34,13 @@ func TestIsExif_False(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParse(t *testing.T) {
|
func TestParse(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
if state := recover(); state != nil {
|
||||||
|
err := log.Wrap(state.(error))
|
||||||
|
log.PrintErrorf(err, "Exif failure.")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Open the file.
|
// Open the file.
|
||||||
|
|
||||||
filepath := path.Join(assetsPath, "NDM_8901.jpg")
|
filepath := path.Join(assetsPath, "NDM_8901.jpg")
|
||||||
|
@ -67,7 +74,7 @@ func TestParse(t *testing.T) {
|
||||||
ti := NewTagIndex()
|
ti := NewTagIndex()
|
||||||
tags := make([]string, 0)
|
tags := make([]string, 0)
|
||||||
|
|
||||||
visitor := func(tagId uint16, tagType TagType, valueContext ValueContext) (err error) {
|
visitor := func(indexedIfdName string, tagId uint16, tagType TagType, valueContext ValueContext) (err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if state := recover(); state != nil {
|
if state := recover(); state != nil {
|
||||||
err = log.Wrap(state.(error))
|
err = log.Wrap(state.(error))
|
||||||
|
@ -75,19 +82,26 @@ func TestParse(t *testing.T) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
it, err := ti.GetWithTagId(tagId)
|
it, err := ti.Get(indexedIfdName, tagId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == ErrTagNotFound {
|
if log.Is(err, ErrTagNotFound) {
|
||||||
|
fmt.Printf("Unknown tag: [%s] (%04x)\n", indexedIfdName, tagId)
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
valueString, err := tagType.ValueString(valueContext, true)
|
// TODO(dustin): Finish case-specific parsing of known undefined values.
|
||||||
log.PanicIf(err)
|
valueString := ""
|
||||||
|
if tagType.Type() == TypeUndefined {
|
||||||
|
valueString = "!UNDEFINED!"
|
||||||
|
} else {
|
||||||
|
valueString, err = tagType.ValueString(valueContext, true)
|
||||||
|
log.PanicIf(err)
|
||||||
|
}
|
||||||
|
|
||||||
description := fmt.Sprintf("ID=(0x%04x) NAME=[%s] IFD=[%s] COUNT=(%d) TYPE=[%s] VALUE=[%s]", tagId, it.Name, it.Ifd, valueContext.UnitCount, tagType.Name(), valueString)
|
description := fmt.Sprintf("IFD=[%s] ID=(0x%04x) NAME=[%s] COUNT=(%d) TYPE=[%s] VALUE=[%s]", indexedIfdName, tagId, it.Name, valueContext.UnitCount, tagType.Name(), valueString)
|
||||||
tags = append(tags, description)
|
tags = append(tags, description)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -97,24 +111,65 @@ func TestParse(t *testing.T) {
|
||||||
log.PanicIf(err)
|
log.PanicIf(err)
|
||||||
|
|
||||||
expected := []string {
|
expected := []string {
|
||||||
"ID=(0x010f) NAME=[Make] IFD=[Image] COUNT=(6) TYPE=[ASCII] VALUE=[Canon]",
|
"IFD=[IFD] ID=(0x010f) NAME=[Make] COUNT=(6) TYPE=[ASCII] VALUE=[Canon]",
|
||||||
"ID=(0x0110) NAME=[Model] IFD=[Image] COUNT=(22) TYPE=[ASCII] VALUE=[Canon EOS 5D Mark III]",
|
"IFD=[IFD] ID=(0x0110) NAME=[Model] COUNT=(22) TYPE=[ASCII] VALUE=[Canon EOS 5D Mark III]",
|
||||||
"ID=(0x0112) NAME=[Orientation] IFD=[Image] COUNT=(1) TYPE=[SHORT] VALUE=[1]",
|
"IFD=[IFD] ID=(0x0112) NAME=[Orientation] COUNT=(1) TYPE=[SHORT] VALUE=[1]",
|
||||||
"ID=(0x011a) NAME=[XResolution] IFD=[Image] COUNT=(1) TYPE=[RATIONAL] VALUE=[72/1]",
|
"IFD=[IFD] ID=(0x011a) NAME=[XResolution] COUNT=(1) TYPE=[RATIONAL] VALUE=[72/1]",
|
||||||
"ID=(0x011b) NAME=[YResolution] IFD=[Image] COUNT=(1) TYPE=[RATIONAL] VALUE=[72/1]",
|
"IFD=[IFD] ID=(0x011b) NAME=[YResolution] COUNT=(1) TYPE=[RATIONAL] VALUE=[72/1]",
|
||||||
"ID=(0x0128) NAME=[ResolutionUnit] IFD=[Image] COUNT=(1) TYPE=[SHORT] VALUE=[2]",
|
"IFD=[IFD] ID=(0x0128) NAME=[ResolutionUnit] COUNT=(1) TYPE=[SHORT] VALUE=[2]",
|
||||||
"ID=(0x0132) NAME=[DateTime] IFD=[Image] COUNT=(20) TYPE=[ASCII] VALUE=[2017:12:02 08:18:50]",
|
"IFD=[IFD] ID=(0x0132) NAME=[DateTime] COUNT=(20) TYPE=[ASCII] VALUE=[2017:12:02 08:18:50]",
|
||||||
"ID=(0x013b) NAME=[Artist] IFD=[Image] COUNT=(1) TYPE=[ASCII] VALUE=[]",
|
"IFD=[IFD] ID=(0x013b) NAME=[Artist] COUNT=(1) TYPE=[ASCII] VALUE=[]",
|
||||||
"ID=(0x0213) NAME=[YCbCrPositioning] IFD=[Image] COUNT=(1) TYPE=[SHORT] VALUE=[2]",
|
"IFD=[IFD] ID=(0x0213) NAME=[YCbCrPositioning] COUNT=(1) TYPE=[SHORT] VALUE=[2]",
|
||||||
"ID=(0x8298) NAME=[Copyright] IFD=[Image] COUNT=(1) TYPE=[ASCII] VALUE=[]",
|
"IFD=[IFD] ID=(0x8298) NAME=[Copyright] COUNT=(1) TYPE=[ASCII] VALUE=[]",
|
||||||
"ID=(0x8769) NAME=[ExifTag] IFD=[Image] COUNT=(1) TYPE=[LONG] VALUE=[360]",
|
"IFD=[IFD] ID=(0x8769) NAME=[ExifTag] COUNT=(1) TYPE=[LONG] VALUE=[360]",
|
||||||
"ID=(0x8825) NAME=[GPSTag] IFD=[Image] COUNT=(1) TYPE=[LONG] VALUE=[9554]",
|
"IFD=[Exif] ID=(0x829a) NAME=[ExposureTime] COUNT=(1) TYPE=[RATIONAL] VALUE=[1/640]",
|
||||||
"ID=(0x0103) NAME=[Compression] IFD=[Image] COUNT=(1) TYPE=[SHORT] VALUE=[6]",
|
"IFD=[Exif] ID=(0x829d) NAME=[FNumber] COUNT=(1) TYPE=[RATIONAL] VALUE=[4/1]",
|
||||||
"ID=(0x011a) NAME=[XResolution] IFD=[Image] COUNT=(1) TYPE=[RATIONAL] VALUE=[72/1]",
|
"IFD=[Exif] ID=(0x8822) NAME=[ExposureProgram] COUNT=(1) TYPE=[SHORT] VALUE=[4]",
|
||||||
"ID=(0x011b) NAME=[YResolution] IFD=[Image] COUNT=(1) TYPE=[RATIONAL] VALUE=[72/1]",
|
"IFD=[Exif] ID=(0x8827) NAME=[ISOSpeedRatings] COUNT=(1) TYPE=[SHORT] VALUE=[1600]",
|
||||||
"ID=(0x0128) NAME=[ResolutionUnit] IFD=[Image] COUNT=(1) TYPE=[SHORT] VALUE=[2]",
|
"IFD=[Exif] ID=(0x8830) NAME=[SensitivityType] COUNT=(1) TYPE=[SHORT] VALUE=[2]",
|
||||||
"ID=(0x0201) NAME=[JPEGInterchangeFormat] IFD=[Image] COUNT=(1) TYPE=[LONG] VALUE=[11444]",
|
"IFD=[Exif] ID=(0x8832) NAME=[RecommendedExposureIndex] COUNT=(1) TYPE=[LONG] VALUE=[1600]",
|
||||||
"ID=(0x0202) NAME=[JPEGInterchangeFormatLength] IFD=[Image] COUNT=(1) TYPE=[LONG] VALUE=[21491]",
|
"IFD=[Exif] ID=(0x9000) NAME=[ExifVersion] COUNT=(4) TYPE=[UNDEFINED] VALUE=[!UNDEFINED!]",
|
||||||
|
"IFD=[Exif] ID=(0x9003) NAME=[DateTimeOriginal] COUNT=(20) TYPE=[ASCII] VALUE=[2017:12:02 08:18:50]",
|
||||||
|
"IFD=[Exif] ID=(0x9004) NAME=[DateTimeDigitized] COUNT=(20) TYPE=[ASCII] VALUE=[2017:12:02 08:18:50]",
|
||||||
|
"IFD=[Exif] ID=(0x9101) NAME=[ComponentsConfiguration] COUNT=(4) TYPE=[UNDEFINED] VALUE=[!UNDEFINED!]",
|
||||||
|
"IFD=[Exif] ID=(0x9201) NAME=[ShutterSpeedValue] COUNT=(1) TYPE=[SRATIONAL] VALUE=[614400/65536]",
|
||||||
|
"IFD=[Exif] ID=(0x9202) NAME=[ApertureValue] COUNT=(1) TYPE=[RATIONAL] VALUE=[262144/65536]",
|
||||||
|
"IFD=[Exif] ID=(0x9204) NAME=[ExposureBiasValue] COUNT=(1) TYPE=[SRATIONAL] VALUE=[0/1]",
|
||||||
|
"IFD=[Exif] ID=(0x9207) NAME=[MeteringMode] COUNT=(1) TYPE=[SHORT] VALUE=[5]",
|
||||||
|
"IFD=[Exif] ID=(0x9209) NAME=[Flash] COUNT=(1) TYPE=[SHORT] VALUE=[16]",
|
||||||
|
"IFD=[Exif] ID=(0x920a) NAME=[FocalLength] COUNT=(1) TYPE=[RATIONAL] VALUE=[16/1]",
|
||||||
|
"IFD=[Exif] ID=(0x927c) NAME=[MakerNote] COUNT=(8152) TYPE=[UNDEFINED] VALUE=[!UNDEFINED!]",
|
||||||
|
"IFD=[Exif] ID=(0x9286) NAME=[UserComment] COUNT=(264) TYPE=[UNDEFINED] VALUE=[!UNDEFINED!]",
|
||||||
|
"IFD=[Exif] ID=(0x9290) NAME=[SubSecTime] COUNT=(3) TYPE=[ASCII] VALUE=[00]",
|
||||||
|
"IFD=[Exif] ID=(0x9291) NAME=[SubSecTimeOriginal] COUNT=(3) TYPE=[ASCII] VALUE=[00]",
|
||||||
|
"IFD=[Exif] ID=(0x9292) NAME=[SubSecTimeDigitized] COUNT=(3) TYPE=[ASCII] VALUE=[00]",
|
||||||
|
"IFD=[Exif] ID=(0xa000) NAME=[FlashpixVersion] COUNT=(4) TYPE=[UNDEFINED] VALUE=[!UNDEFINED!]",
|
||||||
|
"IFD=[Exif] ID=(0xa001) NAME=[ColorSpace] COUNT=(1) TYPE=[SHORT] VALUE=[1]",
|
||||||
|
"IFD=[Exif] ID=(0xa002) NAME=[PixelXDimension] COUNT=(1) TYPE=[SHORT] VALUE=[3840]",
|
||||||
|
"IFD=[Exif] ID=(0xa003) NAME=[PixelYDimension] COUNT=(1) TYPE=[SHORT] VALUE=[2560]",
|
||||||
|
"IFD=[Exif] ID=(0xa005) NAME=[InteroperabilityTag] COUNT=(1) TYPE=[LONG] VALUE=[9326]",
|
||||||
|
"IFD=[Iop] ID=(0x0001) NAME=[InteroperabilityIndex] COUNT=(4) TYPE=[ASCII] VALUE=[R98]",
|
||||||
|
"IFD=[Iop] ID=(0x0002) NAME=[InteroperabilityVersion] COUNT=(4) TYPE=[UNDEFINED] VALUE=[!UNDEFINED!]",
|
||||||
|
"IFD=[Exif] ID=(0xa20e) NAME=[FocalPlaneXResolution] COUNT=(1) TYPE=[RATIONAL] VALUE=[3840000/1461]",
|
||||||
|
"IFD=[Exif] ID=(0xa20f) NAME=[FocalPlaneYResolution] COUNT=(1) TYPE=[RATIONAL] VALUE=[2560000/972]",
|
||||||
|
"IFD=[Exif] ID=(0xa210) NAME=[FocalPlaneResolutionUnit] COUNT=(1) TYPE=[SHORT] VALUE=[2]",
|
||||||
|
"IFD=[Exif] ID=(0xa401) NAME=[CustomRendered] COUNT=(1) TYPE=[SHORT] VALUE=[0]",
|
||||||
|
"IFD=[Exif] ID=(0xa402) NAME=[ExposureMode] COUNT=(1) TYPE=[SHORT] VALUE=[0]",
|
||||||
|
"IFD=[Exif] ID=(0xa403) NAME=[WhiteBalance] COUNT=(1) TYPE=[SHORT] VALUE=[0]",
|
||||||
|
"IFD=[Exif] ID=(0xa406) NAME=[SceneCaptureType] COUNT=(1) TYPE=[SHORT] VALUE=[0]",
|
||||||
|
"IFD=[Exif] ID=(0xa430) NAME=[CameraOwnerName] COUNT=(1) TYPE=[ASCII] VALUE=[]",
|
||||||
|
"IFD=[Exif] ID=(0xa431) NAME=[BodySerialNumber] COUNT=(13) TYPE=[ASCII] VALUE=[063024020097]",
|
||||||
|
"IFD=[Exif] ID=(0xa432) NAME=[LensSpecification] COUNT=(4) TYPE=[RATIONAL] VALUE=[16/1]",
|
||||||
|
"IFD=[Exif] ID=(0xa434) NAME=[LensModel] COUNT=(22) TYPE=[ASCII] VALUE=[EF16-35mm f/4L IS USM]",
|
||||||
|
"IFD=[Exif] ID=(0xa435) NAME=[LensSerialNumber] COUNT=(11) TYPE=[ASCII] VALUE=[2400001068]",
|
||||||
|
"IFD=[IFD] ID=(0x8825) NAME=[GPSTag] COUNT=(1) TYPE=[LONG] VALUE=[9554]",
|
||||||
|
"IFD=[GPSInfo] ID=(0x0000) NAME=[GPSVersionID] COUNT=(4) TYPE=[BYTE] VALUE=[2]",
|
||||||
|
"IFD=[IFD] ID=(0x0103) NAME=[Compression] COUNT=(1) TYPE=[SHORT] VALUE=[6]",
|
||||||
|
"IFD=[IFD] ID=(0x011a) NAME=[XResolution] COUNT=(1) TYPE=[RATIONAL] VALUE=[72/1]",
|
||||||
|
"IFD=[IFD] ID=(0x011b) NAME=[YResolution] COUNT=(1) TYPE=[RATIONAL] VALUE=[72/1]",
|
||||||
|
"IFD=[IFD] ID=(0x0128) NAME=[ResolutionUnit] COUNT=(1) TYPE=[SHORT] VALUE=[2]",
|
||||||
|
"IFD=[IFD] ID=(0x0201) NAME=[JPEGInterchangeFormat] COUNT=(1) TYPE=[LONG] VALUE=[11444]",
|
||||||
|
"IFD=[IFD] ID=(0x0202) NAME=[JPEGInterchangeFormatLength] COUNT=(1) TYPE=[LONG] VALUE=[21491]",
|
||||||
}
|
}
|
||||||
|
|
||||||
if reflect.DeepEqual(tags, expected) == false {
|
if reflect.DeepEqual(tags, expected) == false {
|
||||||
|
|
230
ifd.go
230
ifd.go
|
@ -2,7 +2,6 @@ package exif
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
|
||||||
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
|
||||||
|
@ -14,6 +13,77 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
// IfdTagEnumerator knows how to decode an IFD and all of the tags it
|
||||||
|
// describes. Note that the IFDs and the actual values floating throughout the
|
||||||
|
// whole EXIF block, but the IFD itself has just a minor header and a set of
|
||||||
|
// repeating, statically-sized records. So, the tags (though not their values)
|
||||||
|
// are fairly simple to enumerate.
|
||||||
|
type IfdTagEnumerator struct {
|
||||||
|
byteOrder IfdByteOrder
|
||||||
|
rawExif []byte
|
||||||
|
ifdOffset uint32
|
||||||
|
buffer *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIfdTagEnumerator(rawExif []byte, byteOrder IfdByteOrder, ifdOffset uint32) (ite *IfdTagEnumerator) {
|
||||||
|
ite = &IfdTagEnumerator{
|
||||||
|
rawExif: rawExif,
|
||||||
|
byteOrder: byteOrder,
|
||||||
|
buffer: bytes.NewBuffer(rawExif[ifdOffset:]),
|
||||||
|
}
|
||||||
|
|
||||||
|
return ite
|
||||||
|
}
|
||||||
|
|
||||||
|
// getUint16 reads a uint16 and advances both our current and our current
|
||||||
|
// accumulator (which allows us to know how far to seek to the beginning of the
|
||||||
|
// next IFD when it's time to jump).
|
||||||
|
func (ife *IfdTagEnumerator) getUint16() (value uint16, raw []byte, err error) {
|
||||||
|
defer func() {
|
||||||
|
if state := recover(); state != nil {
|
||||||
|
err = log.Wrap(state.(error))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
raw = make([]byte, 2)
|
||||||
|
|
||||||
|
_, err = ife.buffer.Read(raw)
|
||||||
|
log.PanicIf(err)
|
||||||
|
|
||||||
|
if ife.byteOrder.IsLittleEndian() == true {
|
||||||
|
value = binary.LittleEndian.Uint16(raw)
|
||||||
|
} else {
|
||||||
|
value = binary.BigEndian.Uint16(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, raw, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getUint32 reads a uint32 and advances both our current and our current
|
||||||
|
// accumulator (which allows us to know how far to seek to the beginning of the
|
||||||
|
// next IFD when it's time to jump).
|
||||||
|
func (ife *IfdTagEnumerator) getUint32() (value uint32, raw []byte, err error) {
|
||||||
|
defer func() {
|
||||||
|
if state := recover(); state != nil {
|
||||||
|
err = log.Wrap(state.(error))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
raw = make([]byte, 4)
|
||||||
|
|
||||||
|
_, err = ife.buffer.Read(raw)
|
||||||
|
log.PanicIf(err)
|
||||||
|
|
||||||
|
if ife.byteOrder.IsLittleEndian() == true {
|
||||||
|
value = binary.LittleEndian.Uint32(raw)
|
||||||
|
} else {
|
||||||
|
value = binary.BigEndian.Uint32(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, raw, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
type Ifd struct {
|
type Ifd struct {
|
||||||
data []byte
|
data []byte
|
||||||
buffer *bytes.Buffer
|
buffer *bytes.Buffer
|
||||||
|
@ -31,70 +101,6 @@ func NewIfd(data []byte, byteOrder IfdByteOrder) *Ifd {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// read is a wrapper around the built-in reader that applies which endianness
|
|
||||||
// we are.
|
|
||||||
func (ifd *Ifd) read(r io.Reader, into interface{}) (err error) {
|
|
||||||
defer func() {
|
|
||||||
if state := recover(); state != nil {
|
|
||||||
err = log.Wrap(state.(error))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if ifd.byteOrder.IsLittleEndian() == true {
|
|
||||||
err := binary.Read(r, binary.LittleEndian, into)
|
|
||||||
log.PanicIf(err)
|
|
||||||
} else {
|
|
||||||
err := binary.Read(r, binary.BigEndian, into)
|
|
||||||
log.PanicIf(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getUint16 reads a uint16 and advances both our current and our current
|
|
||||||
// accumulator (which allows us to know how far to seek to the beginning of the
|
|
||||||
// next IFD when it's time to jump).
|
|
||||||
func (ifd *Ifd) getUint16() (value uint16, err error) {
|
|
||||||
defer func() {
|
|
||||||
if state := recover(); state != nil {
|
|
||||||
err = log.Wrap(state.(error))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
err = ifd.read(ifd.buffer, &value)
|
|
||||||
log.PanicIf(err)
|
|
||||||
|
|
||||||
ifd.currentOffset += 2
|
|
||||||
|
|
||||||
return value, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getUint32 reads a uint32 and advances both our current and our current
|
|
||||||
// accumulator (which allows us to know how far to seek to the beginning of the
|
|
||||||
// next IFD when it's time to jump).
|
|
||||||
func (ifd *Ifd) getUint32() (value uint32, raw []byte, err error) {
|
|
||||||
defer func() {
|
|
||||||
if state := recover(); state != nil {
|
|
||||||
err = log.Wrap(state.(error))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
raw = make([]byte, 4)
|
|
||||||
|
|
||||||
_, err = ifd.buffer.Read(raw)
|
|
||||||
log.PanicIf(err)
|
|
||||||
|
|
||||||
ifd.currentOffset += 4
|
|
||||||
|
|
||||||
if ifd.byteOrder.IsBigEndian() {
|
|
||||||
value = binary.BigEndian.Uint32(raw)
|
|
||||||
} else {
|
|
||||||
value = binary.LittleEndian.Uint32(raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
return value, raw, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValueContext describes all of the parameters required to find and extract
|
// ValueContext describes all of the parameters required to find and extract
|
||||||
// the actual tag value.
|
// the actual tag value.
|
||||||
type ValueContext struct {
|
type ValueContext struct {
|
||||||
|
@ -104,44 +110,65 @@ type ValueContext struct {
|
||||||
RawExif []byte
|
RawExif []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ifd *Ifd) getTagEnumerator(ifdOffset uint32) (ite *IfdTagEnumerator) {
|
||||||
|
ite = NewIfdTagEnumerator(
|
||||||
|
ifd.data[ifd.ifdTopOffset:],
|
||||||
|
ifd.byteOrder,
|
||||||
|
ifdOffset)
|
||||||
|
|
||||||
|
return ite
|
||||||
|
}
|
||||||
|
|
||||||
// TagVisitor is an optional callback that can get hit for every tag we parse
|
// TagVisitor is an optional callback that can get hit for every tag we parse
|
||||||
// through. `rawExif` is the byte array startign after the EXIF header (where
|
// through. `rawExif` is the byte array startign after the EXIF header (where
|
||||||
// the offsets of all IFDs and values are calculated from).
|
// the offsets of all IFDs and values are calculated from).
|
||||||
type TagVisitor func(tagId uint16, tagType TagType, valueContext ValueContext) (err error)
|
type TagVisitor func(indexedIfdName string, tagId uint16, tagType TagType, valueContext ValueContext) (err error)
|
||||||
|
|
||||||
// parseCurrentIfd decodes the IFD block that we're currently sitting on the
|
// parseIfd decodes the IFD block that we're currently sitting on the first
|
||||||
// first byte of.
|
// byte of.
|
||||||
func (ifd *Ifd) parseCurrentIfd(visitor TagVisitor) (nextIfdOffset uint32, err error) {
|
func (ifd *Ifd) parseIfd(ifdName string, ifdIndex int, ifdOffset uint32, visitor TagVisitor) (nextIfdOffset uint32, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if state := recover(); state != nil {
|
if state := recover(); state != nil {
|
||||||
err = log.Wrap(state.(error))
|
err = log.Wrap(state.(error))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
ifdLogger.Debugf(nil, "Parsing IFD [%s] (%d) at offset (%04x).", ifdName, ifdIndex, ifdOffset)
|
||||||
|
|
||||||
tagCount, err := ifd.getUint16()
|
// Return the name of the IFD as its known in our tag-index. We should skip
|
||||||
|
// over the current IFD if this is empty (which means we don't recognize/
|
||||||
|
// understand the IFD and, therefore, don't know the tags that are valid for
|
||||||
|
// it). Note that we could leave ignoring the tags as a responsibility for
|
||||||
|
// the visitor, but then it'd be easy for people to integrate that logic and
|
||||||
|
// not realize that they needed to specially handle an empty IFD name until
|
||||||
|
// they happened upon some obscure media one day and suddenly have issue if
|
||||||
|
// they unwittingly write something that breaks in that situation.
|
||||||
|
indexedIfdName := IfdName(ifdName, ifdIndex)
|
||||||
|
if indexedIfdName == "" {
|
||||||
|
ifdLogger.Debugf(nil, "IFD not known and will not be visited: [%s] (%d)", ifdName, ifdIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
ite := ifd.getTagEnumerator(ifdOffset)
|
||||||
|
|
||||||
|
tagCount, _, err := ite.getUint16()
|
||||||
log.PanicIf(err)
|
log.PanicIf(err)
|
||||||
|
|
||||||
ifdLogger.Debugf(nil, "Current IFD tag-count: (%d)", tagCount)
|
ifdLogger.Debugf(nil, "Current IFD tag-count: (%d)", tagCount)
|
||||||
|
|
||||||
for i := uint16(0); i < tagCount; i++ {
|
for i := uint16(0); i < tagCount; i++ {
|
||||||
|
tagId, _, err := ite.getUint16()
|
||||||
// TODO(dustin): !! 0x8769 tag-IDs are child IFDs. We need to be able to recurse.
|
|
||||||
|
|
||||||
tagId, err := ifd.getUint16()
|
|
||||||
log.PanicIf(err)
|
log.PanicIf(err)
|
||||||
|
|
||||||
tagType, err := ifd.getUint16()
|
tagType, _, err := ite.getUint16()
|
||||||
log.PanicIf(err)
|
log.PanicIf(err)
|
||||||
|
|
||||||
unitCount, _, err := ifd.getUint32()
|
unitCount, _, err := ite.getUint32()
|
||||||
log.PanicIf(err)
|
log.PanicIf(err)
|
||||||
|
|
||||||
valueOffset, rawValueOffset, err := ifd.getUint32()
|
valueOffset, rawValueOffset, err := ite.getUint32()
|
||||||
log.PanicIf(err)
|
log.PanicIf(err)
|
||||||
|
|
||||||
if visitor != nil {
|
if visitor != nil && indexedIfdName != "" {
|
||||||
tt := NewTagType(tagType, ifd.byteOrder)
|
tt := NewTagType(tagType, ifd.byteOrder)
|
||||||
|
|
||||||
vc := ValueContext{
|
vc := ValueContext{
|
||||||
|
@ -151,12 +178,20 @@ func (ifd *Ifd) parseCurrentIfd(visitor TagVisitor) (nextIfdOffset uint32, err e
|
||||||
RawExif: ifd.data[ifd.ifdTopOffset:],
|
RawExif: ifd.data[ifd.ifdTopOffset:],
|
||||||
}
|
}
|
||||||
|
|
||||||
err := visitor(tagId, tt, vc)
|
err := visitor(indexedIfdName, tagId, tt, vc)
|
||||||
|
log.PanicIf(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
childIfdName, isIfd := IsIfdTag(tagId)
|
||||||
|
if isIfd == true {
|
||||||
|
ifdLogger.Debugf(nil, "Descending to IFD [%s].", childIfdName)
|
||||||
|
|
||||||
|
err := ifd.Scan(childIfdName, valueOffset, visitor)
|
||||||
log.PanicIf(err)
|
log.PanicIf(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nextIfdOffset, _, err = ifd.getUint32()
|
nextIfdOffset, _, err = ite.getUint32()
|
||||||
log.PanicIf(err)
|
log.PanicIf(err)
|
||||||
|
|
||||||
ifdLogger.Debugf(nil, "Next IFD at offset: (%08x)", nextIfdOffset)
|
ifdLogger.Debugf(nil, "Next IFD at offset: (%08x)", nextIfdOffset)
|
||||||
|
@ -164,50 +199,23 @@ func (ifd *Ifd) parseCurrentIfd(visitor TagVisitor) (nextIfdOffset uint32, err e
|
||||||
return nextIfdOffset, nil
|
return nextIfdOffset, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// forwardToIfd jumps to the beginning of an IFD block that starts on or after
|
|
||||||
// the current position.
|
|
||||||
func (ifd *Ifd) forwardToIfd(ifdOffset uint32) (err error) {
|
|
||||||
defer func() {
|
|
||||||
if state := recover(); state != nil {
|
|
||||||
err = log.Wrap(state.(error))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
ifdLogger.Debugf(nil, "Forwarding to IFD. TOP-OFFSET=(%d) IFD-OFFSET=(%d)", ifd.ifdTopOffset, ifdOffset)
|
|
||||||
|
|
||||||
nextOffset := ifd.ifdTopOffset + ifdOffset
|
|
||||||
|
|
||||||
// We're assuming the guarantee that the next IFD will follow the
|
|
||||||
// current one. So, figure out how far it is from our current position.
|
|
||||||
delta := nextOffset - ifd.currentOffset
|
|
||||||
ifd.buffer.Next(int(delta))
|
|
||||||
|
|
||||||
ifd.currentOffset = nextOffset
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan enumerates the different EXIF blocks (called IFDs).
|
// Scan enumerates the different EXIF blocks (called IFDs).
|
||||||
func (ifd *Ifd) Scan(visitor TagVisitor, firstIfdOffset uint32) (err error) {
|
func (ifd *Ifd) Scan(ifdName string, ifdOffset uint32, visitor TagVisitor) (err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if state := recover(); state != nil {
|
if state := recover(); state != nil {
|
||||||
err = log.Wrap(state.(error))
|
err = log.Wrap(state.(error))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err = ifd.forwardToIfd(firstIfdOffset)
|
for ifdIndex := 0;; ifdIndex++ {
|
||||||
log.PanicIf(err)
|
nextIfdOffset, err := ifd.parseIfd(ifdName, ifdIndex, ifdOffset, visitor)
|
||||||
|
|
||||||
for {
|
|
||||||
nextIfdOffset, err := ifd.parseCurrentIfd(visitor)
|
|
||||||
log.PanicIf(err)
|
log.PanicIf(err)
|
||||||
|
|
||||||
if nextIfdOffset == 0 {
|
if nextIfdOffset == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ifd.forwardToIfd(nextIfdOffset)
|
ifdOffset = nextIfdOffset
|
||||||
log.PanicIf(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
57
tags.go
57
tags.go
|
@ -10,8 +10,21 @@ import (
|
||||||
"github.com/dsoprea/go-logging"
|
"github.com/dsoprea/go-logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
IfdStandard = "IFD"
|
||||||
|
IfdExif = "Exif"
|
||||||
|
IfdGps = "GPSInfo"
|
||||||
|
IfdIop = "Iop"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
tagDataFilepath = ""
|
tagDataFilepath = ""
|
||||||
|
|
||||||
|
IfdTags = map[uint16]string {
|
||||||
|
0x8769: IfdExif,
|
||||||
|
0x8825: IfdGps,
|
||||||
|
0xA005: IfdIop,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -52,7 +65,6 @@ func (it IndexedTag) Is(id uint16) bool {
|
||||||
|
|
||||||
type TagIndex struct {
|
type TagIndex struct {
|
||||||
tagsByIfd map[string]map[uint16]*IndexedTag
|
tagsByIfd map[string]map[uint16]*IndexedTag
|
||||||
tagsById map[uint16]*IndexedTag
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTagIndex() *TagIndex {
|
func NewTagIndex() *TagIndex {
|
||||||
|
@ -87,7 +99,6 @@ func (ti *TagIndex) load() (err error) {
|
||||||
|
|
||||||
// Load structure.
|
// Load structure.
|
||||||
|
|
||||||
tagsById := make(map[uint16]*IndexedTag)
|
|
||||||
tagsByIfd := make(map[string]map[uint16]*IndexedTag)
|
tagsByIfd := make(map[string]map[uint16]*IndexedTag)
|
||||||
|
|
||||||
count := 0
|
count := 0
|
||||||
|
@ -102,25 +113,22 @@ func (ti *TagIndex) load() (err error) {
|
||||||
Name: tagName,
|
Name: tagName,
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, found := tagsById[tagId]; found == true {
|
|
||||||
log.Panicf("tag-ID defined more than once: (%02x)", tagId)
|
|
||||||
}
|
|
||||||
|
|
||||||
tagsById[tagId] = tag
|
|
||||||
|
|
||||||
family, found := tagsByIfd[ifdName]
|
family, found := tagsByIfd[ifdName]
|
||||||
if found == false {
|
if found == false {
|
||||||
family = make(map[uint16]*IndexedTag)
|
family = make(map[uint16]*IndexedTag)
|
||||||
tagsByIfd[ifdName] = family
|
tagsByIfd[ifdName] = family
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, found := family[tagId]; found == true {
|
||||||
|
log.Panicf("tag-ID defined more than once for IFD [%s]: (%02x)", ifdName, tagId)
|
||||||
|
}
|
||||||
|
|
||||||
family[tagId] = tag
|
family[tagId] = tag
|
||||||
|
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ti.tagsById = tagsById
|
|
||||||
ti.tagsByIfd = tagsByIfd
|
ti.tagsByIfd = tagsByIfd
|
||||||
|
|
||||||
tagsLogger.Debugf(nil, "(%d) tags loaded.", count)
|
tagsLogger.Debugf(nil, "(%d) tags loaded.", count)
|
||||||
|
@ -128,14 +136,19 @@ func (ti *TagIndex) load() (err error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ti *TagIndex) GetWithTagId(id uint16) (it *IndexedTag, err error) {
|
func (ti *TagIndex) Get(ifdName string, id uint16) (it *IndexedTag, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if state := recover(); state != nil {
|
if state := recover(); state != nil {
|
||||||
err = log.Wrap(state.(error))
|
err = log.Wrap(state.(error))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
it, found := ti.tagsById[id]
|
family, found := ti.tagsByIfd[ifdName]
|
||||||
|
if found == false {
|
||||||
|
log.Panic(ErrTagNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
it, found = family[id]
|
||||||
if found == false {
|
if found == false {
|
||||||
log.Panic(ErrTagNotFound)
|
log.Panic(ErrTagNotFound)
|
||||||
}
|
}
|
||||||
|
@ -143,6 +156,28 @@ func (ti *TagIndex) GetWithTagId(id uint16) (it *IndexedTag, err error) {
|
||||||
return it, nil
|
return it, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetIfdName returns the known index name for the tags that are expected/
|
||||||
|
// allowed for the IFD. If there's an error, returns "". If returns "", the IFD
|
||||||
|
// should be skipped.
|
||||||
|
func IfdName(ifdName string, ifdIndex int) string {
|
||||||
|
// There's an IFD0 and IFD1, but the others must be unique.
|
||||||
|
if ifdName == IfdStandard && ifdIndex > 1 {
|
||||||
|
tagsLogger.Errorf(nil, "The 'IFD' IFD can not occur more than twice: [%s]. Ignoring IFD.", ifdName)
|
||||||
|
return ""
|
||||||
|
} else if ifdName != IfdStandard && ifdIndex > 0 {
|
||||||
|
tagsLogger.Errorf(nil, "Only the 'IFD' IFD can be repeated: [%s]. Ignoring IFD.", ifdName)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return ifdName
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsIfdTag returns true if the given tag points to a child IFD block.
|
||||||
|
func IsIfdTag(tagId uint16) (name string, found bool) {
|
||||||
|
name, found = IfdTags[tagId]
|
||||||
|
return name, found
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
goPath := os.Getenv("GOPATH")
|
goPath := os.Getenv("GOPATH")
|
||||||
if goPath == "" {
|
if goPath == "" {
|
||||||
|
|
|
@ -6,13 +6,15 @@ import (
|
||||||
"github.com/dsoprea/go-logging"
|
"github.com/dsoprea/go-logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetWithTagId(t *testing.T) {
|
func TestGet(t *testing.T) {
|
||||||
ti := NewTagIndex()
|
ti := NewTagIndex()
|
||||||
|
|
||||||
it, err := ti.GetWithTagId(0x10f)
|
indexedIfdName := IfdName(IfdStandard, 0)
|
||||||
|
|
||||||
|
it, err := ti.Get(indexedIfdName, 0x10f)
|
||||||
log.PanicIf(err)
|
log.PanicIf(err)
|
||||||
|
|
||||||
if it.Is(0x10f) == false || it.IsName("Image", "Make") == false {
|
if it.Is(0x10f) == false || it.IsName("IFD", "Make") == false {
|
||||||
t.Fatalf("tag info not correct")
|
t.Fatalf("tag info not correct")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
6
type.go
6
type.go
|
@ -16,7 +16,7 @@ const (
|
||||||
TypeShort = uint16(3)
|
TypeShort = uint16(3)
|
||||||
TypeLong = uint16(4)
|
TypeLong = uint16(4)
|
||||||
TypeRational = uint16(5)
|
TypeRational = uint16(5)
|
||||||
TypeUndefined = uint16(6)
|
TypeUndefined = uint16(7)
|
||||||
TypeSignedLong = uint16(9)
|
TypeSignedLong = uint16(9)
|
||||||
TypeSignedRational = uint16(10)
|
TypeSignedRational = uint16(10)
|
||||||
)
|
)
|
||||||
|
@ -586,7 +586,7 @@ func (tt TagType) ValueString(valueContext ValueContext, justFirst bool) (value
|
||||||
} else {
|
} else {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
} else if tt.Type() == TypeRational {
|
} else if tt.Type() == TypeSignedRational {
|
||||||
raw, err := tt.ReadSignedRationalValues(valueContext)
|
raw, err := tt.ReadSignedRationalValues(valueContext)
|
||||||
log.PanicIf(err)
|
log.PanicIf(err)
|
||||||
|
|
||||||
|
@ -603,7 +603,7 @@ func (tt TagType) ValueString(valueContext ValueContext, justFirst bool) (value
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Panicf("value of type [%s] is unparseable", tt)
|
log.Panicf("value of type (%d) [%s] is unparseable", tt.Type(), tt)
|
||||||
|
|
||||||
// Never called.
|
// Never called.
|
||||||
return "", nil
|
return "", nil
|
||||||
|
|
Loading…
Reference in New Issue