mirror of https://github.com/dsoprea/go-exif.git
ifd: Added recursive tag enumerator.
- We now embed a child-IFD index on the `Ifd`.pull/3/head
parent
71dbc9ff87
commit
86422559be
2
exif.go
2
exif.go
|
@ -167,7 +167,7 @@ func ParseExifHeader(data []byte) (eh ExifHeader, err error) {
|
|||
}
|
||||
|
||||
// Visit recursively invokes a callback for every tag.
|
||||
func Visit(exifData []byte, visitor TagVisitor) (eh ExifHeader, err error) {
|
||||
func Visit(exifData []byte, visitor RawTagVisitor) (eh ExifHeader, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
|
|
104
ifd_enumerate.go
104
ifd_enumerate.go
|
@ -248,14 +248,14 @@ func (ie *IfdEnumerate) resolveTagValue(ite *IfdTagEntry) (valueBytes []byte, is
|
|||
return valueBytes, false, nil
|
||||
}
|
||||
|
||||
// TagVisitor is an optional callback that can get hit for every tag we parse
|
||||
// RawTagVisitor is an optional callback that can get hit for every tag we parse
|
||||
// through. `addressableData` is the byte array startign after the EXIF header
|
||||
// (where the offsets of all IFDs and values are calculated from).
|
||||
type TagVisitor func(ii IfdIdentity, ifdIndex int, tagId uint16, tagType TagType, valueContext ValueContext) (err error)
|
||||
type RawTagVisitor func(ii IfdIdentity, ifdIndex int, tagId uint16, tagType TagType, valueContext ValueContext) (err error)
|
||||
|
||||
// ParseIfd decodes the IFD block that we're currently sitting on the first
|
||||
// byte of.
|
||||
func (ie *IfdEnumerate) ParseIfd(ii IfdIdentity, ifdIndex int, ite *IfdTagEnumerator, visitor TagVisitor, doDescend bool, resolveValues bool) (nextIfdOffset uint32, entries []*IfdTagEntry, thumbnailData []byte, err error) {
|
||||
func (ie *IfdEnumerate) ParseIfd(ii IfdIdentity, ifdIndex int, ite *IfdTagEnumerator, visitor RawTagVisitor, doDescend bool, resolveValues bool) (nextIfdOffset uint32, entries []*IfdTagEntry, thumbnailData []byte, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
|
@ -358,7 +358,7 @@ func (ie *IfdEnumerate) parseThumbnail(offsetIte, lengthIte *IfdTagEntry) (thumb
|
|||
}
|
||||
|
||||
// Scan enumerates the different EXIF's IFD blocks.
|
||||
func (ie *IfdEnumerate) scan(ii IfdIdentity, ifdOffset uint32, visitor TagVisitor, resolveValues bool) (err error) {
|
||||
func (ie *IfdEnumerate) scan(ii IfdIdentity, ifdOffset uint32, visitor RawTagVisitor, resolveValues bool) (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
|
@ -383,7 +383,7 @@ func (ie *IfdEnumerate) scan(ii IfdIdentity, ifdOffset uint32, visitor TagVisito
|
|||
}
|
||||
|
||||
// Scan enumerates the different EXIF blocks (called IFDs).
|
||||
func (ie *IfdEnumerate) Scan(ifdOffset uint32, visitor TagVisitor, resolveValue bool) (err error) {
|
||||
func (ie *IfdEnumerate) Scan(ifdOffset uint32, visitor RawTagVisitor, resolveValue bool) (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
|
@ -428,6 +428,9 @@ type Ifd struct {
|
|||
EntriesByTagId map[uint16][]*IfdTagEntry
|
||||
|
||||
Children []*Ifd
|
||||
|
||||
ChildIfdIndex map[string]*Ifd
|
||||
|
||||
NextIfdOffset uint32
|
||||
NextIfd *Ifd
|
||||
|
||||
|
@ -574,13 +577,6 @@ func (ifd *Ifd) dumpTags(tags []*IfdTagEntry) []*IfdTagEntry {
|
|||
tags = make([]*IfdTagEntry, 0)
|
||||
}
|
||||
|
||||
// Quickly create an index of the child-IFDs.
|
||||
|
||||
childIfdIndex := make(map[string]*Ifd)
|
||||
for _, childIfd := range ifd.Children {
|
||||
childIfdIndex[childIfd.Ii.IfdName] = childIfd
|
||||
}
|
||||
|
||||
// Now, print the tags while also descending to child-IFDS as we encounter them.
|
||||
|
||||
ifdsFoundCount := 0
|
||||
|
@ -591,7 +587,7 @@ func (ifd *Ifd) dumpTags(tags []*IfdTagEntry) []*IfdTagEntry {
|
|||
if tag.ChildIfdName != "" {
|
||||
ifdsFoundCount++
|
||||
|
||||
childIfd, found := childIfdIndex[tag.ChildIfdName]
|
||||
childIfd, found := ifd.ChildIfdIndex[tag.ChildIfdName]
|
||||
if found != true {
|
||||
log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdName)
|
||||
}
|
||||
|
@ -626,13 +622,6 @@ func (ifd *Ifd) printTagTree(populateValues bool, index, level int, nextLink boo
|
|||
|
||||
fmt.Printf("%s%sIFD: %s\n", indent, prefix, ifd)
|
||||
|
||||
// Quickly create an index of the child-IFDs.
|
||||
|
||||
childIfdIndex := make(map[string]*Ifd)
|
||||
for _, childIfd := range ifd.Children {
|
||||
childIfdIndex[childIfd.Ii.IfdName] = childIfd
|
||||
}
|
||||
|
||||
// Now, print the tags while also descending to child-IFDS as we encounter them.
|
||||
|
||||
ifdsFoundCount := 0
|
||||
|
@ -668,7 +657,7 @@ func (ifd *Ifd) printTagTree(populateValues bool, index, level int, nextLink boo
|
|||
if tag.ChildIfdName != "" {
|
||||
ifdsFoundCount++
|
||||
|
||||
childIfd, found := childIfdIndex[tag.ChildIfdName]
|
||||
childIfd, found := ifd.ChildIfdIndex[tag.ChildIfdName]
|
||||
if found != true {
|
||||
log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdName)
|
||||
}
|
||||
|
@ -701,13 +690,6 @@ func (ifd *Ifd) printIfdTree(level int, nextLink bool) {
|
|||
|
||||
fmt.Printf("%s%s%s\n", indent, prefix, ifd)
|
||||
|
||||
// Quickly create an index of the child-IFDs.
|
||||
|
||||
childIfdIndex := make(map[string]*Ifd)
|
||||
for _, childIfd := range ifd.Children {
|
||||
childIfdIndex[childIfd.Ii.IfdName] = childIfd
|
||||
}
|
||||
|
||||
// Now, print the tags while also descending to child-IFDS as we encounter them.
|
||||
|
||||
ifdsFoundCount := 0
|
||||
|
@ -716,7 +698,7 @@ func (ifd *Ifd) printIfdTree(level int, nextLink bool) {
|
|||
if tag.ChildIfdName != "" {
|
||||
ifdsFoundCount++
|
||||
|
||||
childIfd, found := childIfdIndex[tag.ChildIfdName]
|
||||
childIfd, found := ifd.ChildIfdIndex[tag.ChildIfdName]
|
||||
if found != true {
|
||||
log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdName)
|
||||
}
|
||||
|
@ -746,13 +728,6 @@ func (ifd *Ifd) dumpTree(tagsDump []string, level int) []string {
|
|||
|
||||
indent := strings.Repeat(" ", level * 2)
|
||||
|
||||
// Quickly create an index of the child-IFDs.
|
||||
|
||||
childIfdIndex := make(map[string]*Ifd)
|
||||
for _, childIfd := range ifd.Children {
|
||||
childIfdIndex[childIfd.Ii.IfdName] = childIfd
|
||||
}
|
||||
|
||||
var ifdPhrase string
|
||||
if ifd.ParentIfd != nil {
|
||||
ifdPhrase = fmt.Sprintf("[%s]->[%s]:(%d)", ifd.ParentIfd.Ii.IfdName, ifd.Ii.IfdName, ifd.Index)
|
||||
|
@ -770,7 +745,7 @@ func (ifd *Ifd) dumpTree(tagsDump []string, level int) []string {
|
|||
if tag.ChildIfdName != "" {
|
||||
ifdsFoundCount++
|
||||
|
||||
childIfd, found := childIfdIndex[tag.ChildIfdName]
|
||||
childIfd, found := ifd.ChildIfdIndex[tag.ChildIfdName]
|
||||
if found != true {
|
||||
log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdName)
|
||||
}
|
||||
|
@ -936,6 +911,33 @@ func (ifd *Ifd) GpsInfo() (gi *GpsInfo, err error) {
|
|||
}
|
||||
|
||||
|
||||
type ParsedTagVisitor func(*Ifd, *IfdTagEntry) error
|
||||
|
||||
func (ifd *Ifd) EnumerateTagsRecursively(visitor ParsedTagVisitor) (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
for ptr := ifd; ptr != nil; ptr = ptr.NextIfd {
|
||||
for _, ite := range ifd.Entries {
|
||||
if ite.ChildIfdName != "" {
|
||||
childIfd := ifd.ChildIfdIndex[ite.ChildIfdName]
|
||||
|
||||
err := childIfd.EnumerateTagsRecursively(visitor)
|
||||
log.PanicIf(err)
|
||||
} else {
|
||||
err := visitor(ifd, ite)
|
||||
log.PanicIf(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
type QueuedIfd struct {
|
||||
Ii IfdIdentity
|
||||
TagId uint16
|
||||
|
@ -1116,12 +1118,38 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32, resolveValues bool) (index
|
|||
index.Tree = tree
|
||||
index.Lookup = lookup
|
||||
|
||||
err = ie.setChildrenIndex(index.RootIfd)
|
||||
log.PanicIf(err)
|
||||
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (ie *IfdEnumerate) setChildrenIndex(ifd *Ifd) (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
childIfdIndex := make(map[string]*Ifd)
|
||||
for _, childIfd := range ifd.Children {
|
||||
childIfdIndex[childIfd.Ii.IfdName] = childIfd
|
||||
}
|
||||
|
||||
ifd.ChildIfdIndex = childIfdIndex
|
||||
|
||||
for _, childIfd := range ifd.Children {
|
||||
err := ie.setChildrenIndex(childIfd)
|
||||
log.PanicIf(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// ParseOneIfd is a hack to use an IE to parse a raw IFD block. Can be used for
|
||||
// testing.
|
||||
func ParseOneIfd(ii IfdIdentity, byteOrder binary.ByteOrder, ifdBlock []byte, visitor TagVisitor, resolveValues bool) (nextIfdOffset uint32, entries []*IfdTagEntry, err error) {
|
||||
func ParseOneIfd(ii IfdIdentity, byteOrder binary.ByteOrder, ifdBlock []byte, visitor RawTagVisitor, resolveValues bool) (nextIfdOffset uint32, entries []*IfdTagEntry, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"testing"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"encoding/binary"
|
||||
"io/ioutil"
|
||||
|
@ -328,6 +329,160 @@ func TestIfd_GpsInfo(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestIfdEnumerate_EnumerateTagsRecursively(t *testing.T) {
|
||||
|
||||
|
||||
filepath := path.Join(assetsPath, "NDM_8901.jpg")
|
||||
|
||||
rawExif, err := SearchFileAndExtractExif(filepath)
|
||||
log.PanicIf(err)
|
||||
|
||||
_, index, err := Collect(rawExif)
|
||||
log.PanicIf(err)
|
||||
|
||||
|
||||
collected := make([][2]interface{}, 0)
|
||||
|
||||
cb := func(ifd *Ifd, ite *IfdTagEntry) error {
|
||||
item := [2]interface{} {
|
||||
ifd.Ii.IfdName,
|
||||
int(ite.TagId),
|
||||
}
|
||||
|
||||
collected = append(collected, item)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err = index.RootIfd.EnumerateTagsRecursively(cb)
|
||||
log.PanicIf(err)
|
||||
|
||||
expected := [][2]interface{} {
|
||||
[2]interface{} { "IFD", 0x010f },
|
||||
[2]interface{} { "IFD", 0x0110 },
|
||||
[2]interface{} { "IFD", 0x0112 },
|
||||
[2]interface{} { "IFD", 0x011a },
|
||||
[2]interface{} { "IFD", 0x011b },
|
||||
[2]interface{} { "IFD", 0x0128 },
|
||||
[2]interface{} { "IFD", 0x0132 },
|
||||
[2]interface{} { "IFD", 0x013b },
|
||||
[2]interface{} { "IFD", 0x0213 },
|
||||
[2]interface{} { "IFD", 0x8298 },
|
||||
[2]interface{} { "Exif", 0x829a },
|
||||
[2]interface{} { "Exif", 0x829d },
|
||||
[2]interface{} { "Exif", 0x8822 },
|
||||
[2]interface{} { "Exif", 0x8827 },
|
||||
[2]interface{} { "Exif", 0x8830 },
|
||||
[2]interface{} { "Exif", 0x8832 },
|
||||
[2]interface{} { "Exif", 0x9000 },
|
||||
[2]interface{} { "Exif", 0x9003 },
|
||||
[2]interface{} { "Exif", 0x9004 },
|
||||
[2]interface{} { "Exif", 0x9101 },
|
||||
[2]interface{} { "Exif", 0x9201 },
|
||||
[2]interface{} { "Exif", 0x9202 },
|
||||
[2]interface{} { "Exif", 0x9204 },
|
||||
[2]interface{} { "Exif", 0x9207 },
|
||||
[2]interface{} { "Exif", 0x9209 },
|
||||
[2]interface{} { "Exif", 0x920a },
|
||||
[2]interface{} { "Exif", 0x927c },
|
||||
[2]interface{} { "Exif", 0x9286 },
|
||||
[2]interface{} { "Exif", 0x9290 },
|
||||
[2]interface{} { "Exif", 0x9291 },
|
||||
[2]interface{} { "Exif", 0x9292 },
|
||||
[2]interface{} { "Exif", 0xa000 },
|
||||
[2]interface{} { "Exif", 0xa001 },
|
||||
[2]interface{} { "Exif", 0xa002 },
|
||||
[2]interface{} { "Exif", 0xa003 },
|
||||
[2]interface{} { "Iop", 0x0001 },
|
||||
[2]interface{} { "Iop", 0x0002 },
|
||||
[2]interface{} { "Exif", 0xa20e },
|
||||
[2]interface{} { "Exif", 0xa20f },
|
||||
[2]interface{} { "Exif", 0xa210 },
|
||||
[2]interface{} { "Exif", 0xa401 },
|
||||
[2]interface{} { "Exif", 0xa402 },
|
||||
[2]interface{} { "Exif", 0xa403 },
|
||||
[2]interface{} { "Exif", 0xa406 },
|
||||
[2]interface{} { "Exif", 0xa430 },
|
||||
[2]interface{} { "Exif", 0xa431 },
|
||||
[2]interface{} { "Exif", 0xa432 },
|
||||
[2]interface{} { "Exif", 0xa434 },
|
||||
[2]interface{} { "Exif", 0xa435 },
|
||||
[2]interface{} { "GPSInfo", 0x0000 },
|
||||
[2]interface{} { "IFD", 0x010f },
|
||||
[2]interface{} { "IFD", 0x0110 },
|
||||
[2]interface{} { "IFD", 0x0112 },
|
||||
[2]interface{} { "IFD", 0x011a },
|
||||
[2]interface{} { "IFD", 0x011b },
|
||||
[2]interface{} { "IFD", 0x0128 },
|
||||
[2]interface{} { "IFD", 0x0132 },
|
||||
[2]interface{} { "IFD", 0x013b },
|
||||
[2]interface{} { "IFD", 0x0213 },
|
||||
[2]interface{} { "IFD", 0x8298 },
|
||||
[2]interface{} { "Exif", 0x829a },
|
||||
[2]interface{} { "Exif", 0x829d },
|
||||
[2]interface{} { "Exif", 0x8822 },
|
||||
[2]interface{} { "Exif", 0x8827 },
|
||||
[2]interface{} { "Exif", 0x8830 },
|
||||
[2]interface{} { "Exif", 0x8832 },
|
||||
[2]interface{} { "Exif", 0x9000 },
|
||||
[2]interface{} { "Exif", 0x9003 },
|
||||
[2]interface{} { "Exif", 0x9004 },
|
||||
[2]interface{} { "Exif", 0x9101 },
|
||||
[2]interface{} { "Exif", 0x9201 },
|
||||
[2]interface{} { "Exif", 0x9202 },
|
||||
[2]interface{} { "Exif", 0x9204 },
|
||||
[2]interface{} { "Exif", 0x9207 },
|
||||
[2]interface{} { "Exif", 0x9209 },
|
||||
[2]interface{} { "Exif", 0x920a },
|
||||
[2]interface{} { "Exif", 0x927c },
|
||||
[2]interface{} { "Exif", 0x9286 },
|
||||
[2]interface{} { "Exif", 0x9290 },
|
||||
[2]interface{} { "Exif", 0x9291 },
|
||||
[2]interface{} { "Exif", 0x9292 },
|
||||
[2]interface{} { "Exif", 0xa000 },
|
||||
[2]interface{} { "Exif", 0xa001 },
|
||||
[2]interface{} { "Exif", 0xa002 },
|
||||
[2]interface{} { "Exif", 0xa003 },
|
||||
[2]interface{} { "Iop", 0x0001 },
|
||||
[2]interface{} { "Iop", 0x0002 },
|
||||
[2]interface{} { "Exif", 0xa20e },
|
||||
[2]interface{} { "Exif", 0xa20f },
|
||||
[2]interface{} { "Exif", 0xa210 },
|
||||
[2]interface{} { "Exif", 0xa401 },
|
||||
[2]interface{} { "Exif", 0xa402 },
|
||||
[2]interface{} { "Exif", 0xa403 },
|
||||
[2]interface{} { "Exif", 0xa406 },
|
||||
[2]interface{} { "Exif", 0xa430 },
|
||||
[2]interface{} { "Exif", 0xa431 },
|
||||
[2]interface{} { "Exif", 0xa432 },
|
||||
[2]interface{} { "Exif", 0xa434 },
|
||||
[2]interface{} { "Exif", 0xa435 },
|
||||
[2]interface{} { "GPSInfo", 0x0000 },
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(collected, expected) != true {
|
||||
fmt.Printf("ACTUAL:\n")
|
||||
fmt.Printf("\n")
|
||||
|
||||
for _, item := range collected {
|
||||
fmt.Printf("[2]interface{} { \"%s\", 0x%04x },\n", item[0], item[1])
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
|
||||
fmt.Printf("EXPECTED:\n")
|
||||
fmt.Printf("\n")
|
||||
|
||||
for _, item := range expected {
|
||||
fmt.Printf("[2]interface{} { \"%s\", 0x%04x },\n", item[0], item[1])
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
|
||||
t.Fatalf("tags not visited correctly: (%d) != (%d)", len(collected), len(expected))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func ExampleIfd_GpsInfo() {
|
||||
filepath := path.Join(assetsPath, "gps.jpg")
|
||||
|
@ -349,5 +504,3 @@ func ExampleIfd_GpsInfo() {
|
|||
// Output:
|
||||
// GpsInfo<LAT=(26.58667) LON=(-80.05361) ALT=(0) TIME=[2018-04-29 01:22:57 +0000 UTC]>
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue