ifd: Added recursive tag enumerator.

- We now embed a child-IFD index on the `Ifd`.
pull/3/head
Dustin Oprea 2018-06-14 03:50:46 -04:00
parent 71dbc9ff87
commit 86422559be
3 changed files with 222 additions and 41 deletions

View File

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

View File

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

View File

@ -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]>
}