mirror of https://github.com/dsoprea/go-exif.git
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
parent
84fe4298c4
commit
8c09d04212
|
@ -0,0 +1,10 @@
|
|||
package exif
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrTagNotFound = errors.New("tag not found")
|
||||
ErrTagNotStandard = errors.New("tag not a standard tag")
|
||||
)
|
|
@ -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.")
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
4
tags.go
4
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
|
||||
|
|
Loading…
Reference in New Issue