ifd_enumerate: Added find-by-id and find-by-name to tags in `ifd`.

- Made `(Ifd).Entries` a slice of pointers ([]*IfdTagEntry).
pull/3/head
Dustin Oprea 2018-05-03 04:09:06 -04:00
parent 84fe4298c4
commit 8c09d04212
6 changed files with 205 additions and 30 deletions

10
error.go Normal file
View File

@ -0,0 +1,10 @@
package exif
import (
"errors"
)
var (
ErrTagNotFound = errors.New("tag not found")
ErrTagNotStandard = errors.New("tag not a standard tag")
)

View File

@ -664,7 +664,7 @@ func (ib *IfdBuilder) AddTagsFromExisting(ifd *Ifd, itevr *IfdTagEntryValueResol
} else {
var err error
valueBytes, err := itevr.ValueBytes(&ite)
valueBytes, err := itevr.ValueBytes(ite)
if err != nil {
if log.Is(err, ErrUnhandledUnknownTypedTag) == true {
ifdBuilderLogger.Warningf(nil, "Unknown-type tag can't be parsed so it can't be copied to the new IFD.")

View File

@ -152,21 +152,21 @@ func TestAddTagsFromExisting(t *testing.T) {
ib := NewIfdBuilder(RootIi, TestDefaultByteOrder)
entries := make([]IfdTagEntry, 3)
entries := make([]*IfdTagEntry, 3)
entries[0] = IfdTagEntry{
entries[0] = &IfdTagEntry{
TagId: 0x11,
TagType: TypeByte,
UnitCount: 4,
RawValueOffset: []byte { 0x12, 0, 0, 0 },
}
entries[1] = IfdTagEntry{
entries[1] = &IfdTagEntry{
TagId: 0x22,
ChildIfdName: "some ifd",
}
entries[2] = IfdTagEntry{
entries[2] = &IfdTagEntry{
TagId: 0x33,
TagType: TypeByte,
UnitCount: 4,
@ -192,18 +192,18 @@ func TestAddTagsFromExisting(t *testing.T) {
func TestAddTagsFromExisting__Includes(t *testing.T) {
ib := NewIfdBuilder(RootIi, TestDefaultByteOrder)
entries := make([]IfdTagEntry, 3)
entries := make([]*IfdTagEntry, 3)
entries[0] = IfdTagEntry{
entries[0] = &IfdTagEntry{
TagId: 0x11,
}
entries[1] = IfdTagEntry{
entries[1] = &IfdTagEntry{
TagId: 0x22,
ChildIfdName: "some ifd",
}
entries[2] = IfdTagEntry{
entries[2] = &IfdTagEntry{
TagId: 0x33,
}
@ -224,18 +224,18 @@ func TestAddTagsFromExisting__Includes(t *testing.T) {
func TestAddTagsFromExisting__Excludes(t *testing.T) {
ib := NewIfdBuilder(RootIi, TestDefaultByteOrder)
entries := make([]IfdTagEntry, 3)
entries := make([]*IfdTagEntry, 3)
entries[0] = IfdTagEntry{
entries[0] = &IfdTagEntry{
TagId: 0x11,
}
entries[1] = IfdTagEntry{
entries[1] = &IfdTagEntry{
TagId: 0x22,
ChildIfdName: "some ifd",
}
entries[2] = IfdTagEntry{
entries[2] = &IfdTagEntry{
TagId: 0x33,
}

View File

@ -126,7 +126,7 @@ func (ie *IfdEnumerate) getTagEnumerator(ifdOffset uint32) (ite *IfdTagEnumerato
return ite
}
func (ie *IfdEnumerate) parseTag(ii IfdIdentity, tagIndex int, ite *IfdTagEnumerator) (tag IfdTagEntry, err error) {
func (ie *IfdEnumerate) parseTag(ii IfdIdentity, tagIndex int, ite *IfdTagEnumerator) (tag *IfdTagEntry, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
@ -145,7 +145,7 @@ func (ie *IfdEnumerate) parseTag(ii IfdIdentity, tagIndex int, ite *IfdTagEnumer
valueOffset, rawValueOffset, err := ite.getUint32()
log.PanicIf(err)
tag = IfdTagEntry{
tag = &IfdTagEntry{
Ii: ii,
TagId: tagId,
TagIndex: tagIndex,
@ -173,7 +173,7 @@ type TagVisitor func(ii IfdIdentity, ifdIndex int, tagId uint16, tagType TagType
// 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) (nextIfdOffset uint32, entries []IfdTagEntry, err error) {
func (ie *IfdEnumerate) ParseIfd(ii IfdIdentity, ifdIndex int, ite *IfdTagEnumerator, visitor TagVisitor, doDescend bool) (nextIfdOffset uint32, entries []*IfdTagEntry, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
@ -185,7 +185,7 @@ func (ie *IfdEnumerate) ParseIfd(ii IfdIdentity, ifdIndex int, ite *IfdTagEnumer
ifdEnumerateLogger.Debugf(nil, "Current IFD tag-count: (%d)", tagCount)
entries = make([]IfdTagEntry, tagCount)
entries = make([]*IfdTagEntry, tagCount)
for i := 0; i < int(tagCount); i++ {
tag, err := ie.parseTag(ii, i, ite)
@ -269,6 +269,7 @@ func (ie *IfdEnumerate) Scan(ifdOffset uint32, visitor TagVisitor) (err error) {
return nil
}
type Ifd struct {
ByteOrder binary.ByteOrder
@ -278,14 +279,64 @@ type Ifd struct {
Index int
Offset uint32
// TODO(dustin): !! Add a find method.
Entries []IfdTagEntry
Entries []*IfdTagEntry
EntriesByTagId map[uint16][]*IfdTagEntry
Children []*Ifd
NextIfdOffset uint32
NextIfd *Ifd
}
// FindTagWithId returns a list of tags (usually just zero or one) that match
// the given tag ID. This is efficient.
func (ifd Ifd) FindTagWithId(tagId uint16) (results []*IfdTagEntry, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
results, found := ifd.EntriesByTagId[tagId]
if found != true {
log.Panic(ErrTagNotFound)
}
return results, nil
}
// FindTagWithName returns a list of tags (usually just zero or one) that match
// the given tag name. This is not efficient (though the labor is trivial).
func (ifd Ifd) FindTagWithName(tagName string) (results []*IfdTagEntry, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
ti := NewTagIndex()
ii := ifd.Identity()
it, err := ti.GetWithName(ii, tagName)
if log.Is(err, ErrTagNotFound) == true {
log.Panic(ErrTagNotStandard)
} else if err != nil {
log.Panic(err)
}
results = make([]*IfdTagEntry, 0)
for _, ite := range ifd.Entries {
if ite.TagId == it.Id {
results = append(results, ite)
}
}
if len(results) == 0 {
log.Panic(ErrTagNotFound)
}
return results, nil
}
func (ifd Ifd) String() string {
parentOffset := uint32(0)
if ifd.ParentIfd != nil {
@ -359,7 +410,7 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
ifds := make([]*Ifd, 0)
lookup := make(map[IfdIdentity][]*Ifd)
queue := []QueuedIfd {
queue := []QueuedIfd{
{
Ii: RootIi,
Index: 0,
@ -390,6 +441,16 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
id := len(ifds)
entriesByTagId := make(map[uint16][]*IfdTagEntry)
for _, tag := range entries {
tags, found := entriesByTagId[tag.TagId]
if found == false {
tags = make([]*IfdTagEntry, 0)
}
entriesByTagId[tag.TagId] = append(tags, tag)
}
ifd := Ifd{
ByteOrder: ie.byteOrder,
Id: id,
@ -398,6 +459,7 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
Index: index,
Offset: offset,
Entries: entries,
EntriesByTagId: entriesByTagId,
Children: make([]*Ifd, 0),
NextIfdOffset: nextIfdOffset,
}
@ -441,7 +503,7 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
IfdName: entry.ChildIfdName,
}
qi := QueuedIfd {
qi := QueuedIfd{
Ii: childId,
Index: 0,
Offset: entry.ValueOffset,
@ -456,7 +518,7 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
// Allow the next link to know what the previous link was.
edges[nextIfdOffset] = &ifd
qi := QueuedIfd {
qi := QueuedIfd{
Ii: ii,
Index: index + 1,
Offset: nextIfdOffset,
@ -476,7 +538,7 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
// 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) (nextIfdOffset uint32, entries []IfdTagEntry, err error) {
func ParseOneIfd(ii IfdIdentity, byteOrder binary.ByteOrder, ifdBlock []byte, visitor TagVisitor) (nextIfdOffset uint32, entries []*IfdTagEntry, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
@ -496,7 +558,7 @@ func ParseOneIfd(ii IfdIdentity, byteOrder binary.ByteOrder, ifdBlock []byte, vi
}
// ParseOneTag is a hack to use an IE to parse a raw tag block.
func ParseOneTag(ii IfdIdentity, byteOrder binary.ByteOrder, tagBlock []byte) (tag IfdTagEntry, err error) {
func ParseOneTag(ii IfdIdentity, byteOrder binary.ByteOrder, tagBlock []byte) (tag *IfdTagEntry, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))

View File

@ -58,7 +58,7 @@ func TestIfdTagEntry_ValueBytes_RealData(t *testing.T) {
var ite *IfdTagEntry
for _, thisIte := range index.RootIfd.Entries {
if thisIte.TagId == 0x0110 {
ite = &thisIte
ite = thisIte
break
}
}
@ -102,7 +102,7 @@ func TestIfdTagEntry_Resolver_ValueBytes(t *testing.T) {
var ite *IfdTagEntry
for _, thisIte := range index.RootIfd.Entries {
if thisIte.TagId == 0x0110 {
ite = &thisIte
ite = thisIte
break
}
}
@ -150,7 +150,7 @@ func TestIfdTagEntry_Resolver_ValueBytes__Unknown_Field_And_Nonroot_Ifd(t *testi
var ite *IfdTagEntry
for _, thisIte := range ifdExif.Entries {
if thisIte.TagId == 0x9000 {
ite = &thisIte
ite = thisIte
break
}
}
@ -172,3 +172,108 @@ func TestIfdTagEntry_Resolver_ValueBytes__Unknown_Field_And_Nonroot_Ifd(t *testi
t.Fatalf("Recovered unknown value is not correct.")
}
}
func Test_Ifd_FindTagWithId_Hit(t *testing.T) {
filepath := path.Join(assetsPath, "NDM_8901.jpg")
e := NewExif()
rawExif, err := e.SearchAndExtractExif(filepath)
log.PanicIf(err)
_, index, err := e.Collect(rawExif)
log.PanicIf(err)
ifd := index.RootIfd
results, err := ifd.FindTagWithId(0x011b)
if len(results) != 1 {
t.Fatalf("Exactly one result was not found: (%d)", len(results))
} else if results[0].TagId != 0x011b {
t.Fatalf("The result was not expected: %v", results[0])
}
}
func Test_Ifd_FindTagWithId_Miss(t *testing.T) {
filepath := path.Join(assetsPath, "NDM_8901.jpg")
e := NewExif()
rawExif, err := e.SearchAndExtractExif(filepath)
log.PanicIf(err)
_, index, err := e.Collect(rawExif)
log.PanicIf(err)
ifd := index.RootIfd
_, err = ifd.FindTagWithId(0xffff)
if err == nil {
t.Fatalf("Expected error for not-found tag.")
} else if log.Is(err, ErrTagNotFound) == false {
log.Panic(err)
}
}
func Test_Ifd_FindTagWithName_Hit(t *testing.T) {
filepath := path.Join(assetsPath, "NDM_8901.jpg")
e := NewExif()
rawExif, err := e.SearchAndExtractExif(filepath)
log.PanicIf(err)
_, index, err := e.Collect(rawExif)
log.PanicIf(err)
ifd := index.RootIfd
results, err := ifd.FindTagWithName("YResolution")
if len(results) != 1 {
t.Fatalf("Exactly one result was not found: (%d)", len(results))
} else if results[0].TagId != 0x011b {
t.Fatalf("The result was not expected: %v", results[0])
}
}
func Test_Ifd_FindTagWithName_Miss(t *testing.T) {
filepath := path.Join(assetsPath, "NDM_8901.jpg")
e := NewExif()
rawExif, err := e.SearchAndExtractExif(filepath)
log.PanicIf(err)
_, index, err := e.Collect(rawExif)
log.PanicIf(err)
ifd := index.RootIfd
_, err = ifd.FindTagWithName("PlanarConfiguration")
if err == nil {
t.Fatalf("Expected error for not-found tag.")
} else if log.Is(err, ErrTagNotFound) == false {
log.Panic(err)
}
}
func Test_Ifd_FindTagWithName_NonStandard(t *testing.T) {
filepath := path.Join(assetsPath, "NDM_8901.jpg")
e := NewExif()
rawExif, err := e.SearchAndExtractExif(filepath)
log.PanicIf(err)
_, index, err := e.Collect(rawExif)
log.PanicIf(err)
ifd := index.RootIfd
_, err = ifd.FindTagWithName("GeorgeNotAtHome")
if err == nil {
t.Fatalf("Expected error for not-found tag.")
} else if log.Is(err, ErrTagNotStandard) == false {
log.Panic(err)
}
}

View File

@ -4,7 +4,6 @@ import (
"os"
"path"
"fmt"
"errors"
"gopkg.in/yaml.v2"
"github.com/dsoprea/go-logging"
@ -81,7 +80,6 @@ var (
var (
tagsLogger = log.NewLogger("exif.tags")
ErrTagNotFound = errors.New("tag not found")
)
@ -270,7 +268,7 @@ func (ti *TagIndex) GetWithName(ii IfdIdentity, name string) (it *IndexedTag, er
it, found := ti.tagsByIfdR[ii.IfdName][name]
if found != true {
log.Panicf("tag with IFD [%s] and name [%s] not known", ii.IfdName, name)
log.Panic(ErrTagNotFound)
}
return it, nil