From 8c09d04212e2f10d1c6d5a2947636ab0c5483f5f Mon Sep 17 00:00:00 2001 From: Dustin Oprea Date: Thu, 3 May 2018 04:09:06 -0400 Subject: [PATCH] ifd_enumerate: Added find-by-id and find-by-name to tags in `ifd`. - Made `(Ifd).Entries` a slice of pointers ([]*IfdTagEntry). --- error.go | 10 ++++ ifd_builder.go | 2 +- ifd_builder_test.go | 24 ++++----- ifd_enumerate.go | 84 +++++++++++++++++++++++++++----- ifd_enumerate_test.go | 111 ++++++++++++++++++++++++++++++++++++++++-- tags.go | 4 +- 6 files changed, 205 insertions(+), 30 deletions(-) create mode 100644 error.go diff --git a/error.go b/error.go new file mode 100644 index 0000000..dc9ebe5 --- /dev/null +++ b/error.go @@ -0,0 +1,10 @@ +package exif + +import ( + "errors" +) + +var ( + ErrTagNotFound = errors.New("tag not found") + ErrTagNotStandard = errors.New("tag not a standard tag") +) diff --git a/ifd_builder.go b/ifd_builder.go index 81038b6..f70efd9 100644 --- a/ifd_builder.go +++ b/ifd_builder.go @@ -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.") diff --git a/ifd_builder_test.go b/ifd_builder_test.go index 8b4652d..41406c7 100644 --- a/ifd_builder_test.go +++ b/ifd_builder_test.go @@ -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, } diff --git a/ifd_enumerate.go b/ifd_enumerate.go index 7112c18..2aff1e5 100644 --- a/ifd_enumerate.go +++ b/ifd_enumerate.go @@ -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)) diff --git a/ifd_enumerate_test.go b/ifd_enumerate_test.go index 266b294..0df85c9 100644 --- a/ifd_enumerate_test.go +++ b/ifd_enumerate_test.go @@ -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) + } +} diff --git a/tags.go b/tags.go index 33d106b..2de9643 100644 --- a/tags.go +++ b/tags.go @@ -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