tags.go: Add 'universal tags' support

- This will allow tags to be matched from any IFD if not found in the
  primary and to go with whatever type is encoded into the tag even if
  it disagrees with what is officially supported.

Supports https://github.com/dsoprea/go-exif/issues/53
pull/56/head
Dustin Oprea 2021-01-31 17:13:15 -05:00
parent 4ec6f89b9f
commit d42f8ce9cd
4 changed files with 101 additions and 8 deletions

View File

@ -56,6 +56,7 @@ type parameters struct {
ThumbnailOutputFilepath string `short:"t" long:"thumbnail-output-filepath" description:"File-path to write thumbnail to (if present)"`
DoNotPrintTags bool `short:"n" long:"no-tags" description:"Do not actually print tags. Good for auditing the logs or merely checking the EXIF structure for errors."`
SkipBlocks int `short:"s" long:"skip" description:"Skip this many EXIF blocks before returning"`
DoUniversalTagSearch bool `short:"u" long:"universal-tags" description:"If tags not found in known mapped IFDs, fallback to trying all IFDs."`
}
var (
@ -107,7 +108,7 @@ func main() {
// Run the parse.
entries, _, err := exif.GetFlatExifData(rawExif, nil)
entries, _, err := exif.GetFlatExifDataUniversalSearch(rawExif, nil, arguments.DoUniversalTagSearch)
if err != nil {
if arguments.SkipBlocks > 0 {
mainLogger.Warningf(nil, "Encountered an error. This might be related to the request to skip EXIF blocks.")

View File

@ -261,7 +261,10 @@ func (ie *IfdEnumerate) parseTag(ii *exifcommon.IfdIdentity, tagPosition int, bp
log.Panic(err)
}
if it.DoesSupportType(tagType) == false {
// If we're trying to be as forgiving as possible then use whatever type was
// reported in the format. Otherwise, only accept a type that's expected for
// this tag.
if ie.tagIndex.UniversalSearch() == false && it.DoesSupportType(tagType) == false {
// The type in the stream disagrees with the type that this tag is
// expected to have. This can present issues with how we handle the
// special-case tags (e.g. thumbnails, GPS, etc..) when those tags
@ -405,7 +408,7 @@ func (ie *IfdEnumerate) tagPostParse(ite *IfdTagEntry, med *MiscellaneousExifDat
// tag should ever be repeated, and b) all but one had an incorrect
// type and caused parsing/conversion woes. So, this is a quick fix
// for those scenarios.
if it.DoesSupportType(tagType) == false {
if ie.tagIndex.UniversalSearch() == false && it.DoesSupportType(tagType) == false {
ifdEnumerateLogger.Warningf(nil,
"Skipping tag [%s] (0x%04x) [%s] with an unexpected type: %v ∉ %v",
ii.UnindexedString(), tagId, it.Name,

View File

@ -180,6 +180,8 @@ type TagIndex struct {
tagsByIfdR map[string]map[string]*IndexedTag
mutex sync.Mutex
doUniversalSearch bool
}
// NewTagIndex returns a new TagIndex struct.
@ -192,6 +194,16 @@ func NewTagIndex() *TagIndex {
return ti
}
// SetUniversalSearch enables a fallback to matching tags under *any* IFD.
func (ti *TagIndex) SetUniversalSearch(flag bool) {
ti.doUniversalSearch = flag
}
// UniversalSearch enables a fallback to matching tags under *any* IFD.
func (ti *TagIndex) UniversalSearch() bool {
return ti.doUniversalSearch
}
// Add registers a new tag to be recognized during the parse.
func (ti *TagIndex) Add(it *IndexedTag) (err error) {
defer func() {
@ -234,9 +246,7 @@ func (ti *TagIndex) Add(it *IndexedTag) (err error) {
return nil
}
// Get returns information about the non-IFD tag given a tag ID. `ifdPath` must
// not be fully-qualified.
func (ti *TagIndex) Get(ii *exifcommon.IfdIdentity, id uint16) (it *IndexedTag, err error) {
func (ti *TagIndex) getOne(ifdPath string, id uint16) (it *IndexedTag, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
@ -251,8 +261,6 @@ func (ti *TagIndex) Get(ii *exifcommon.IfdIdentity, id uint16) (it *IndexedTag,
ti.mutex.Lock()
defer ti.mutex.Unlock()
ifdPath := ii.UnindexedString()
family, found := ti.tagsByIfd[ifdPath]
if found == false {
return nil, ErrTagNotFound
@ -266,6 +274,53 @@ func (ti *TagIndex) Get(ii *exifcommon.IfdIdentity, id uint16) (it *IndexedTag,
return it, nil
}
// Get returns information about the non-IFD tag given a tag ID. `ifdPath` must
// not be fully-qualified.
func (ti *TagIndex) Get(ii *exifcommon.IfdIdentity, id uint16) (it *IndexedTag, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
ifdPath := ii.UnindexedString()
it, err = ti.getOne(ifdPath, id)
if err == nil {
return it, nil
} else if err != ErrTagNotFound {
log.Panic(err)
}
if ti.doUniversalSearch == false {
return nil, ErrTagNotFound
}
// We've been told to fallback to look for the tag in other IFDs.
skipIfdPath := ii.UnindexedString()
for currentIfdPath, _ := range ti.tagsByIfd {
if currentIfdPath == skipIfdPath {
// Skip the primary IFD, which has already been checked.
continue
}
it, err = ti.getOne(currentIfdPath, id)
if err == nil {
tagsLogger.Warningf(nil,
"Found tag (0x%02x) in the wrong IFD: [%s] != [%s]",
id, currentIfdPath, ifdPath)
return it, nil
} else if err != ErrTagNotFound {
log.Panic(err)
}
}
return nil, ErrTagNotFound
}
var (
// tagGuessDefaultIfdIdentities describes which IFDs we'll look for a given
// tag-ID in, if it's not found where it's supposed to be. We suppose that

View File

@ -76,6 +76,36 @@ func GetFlatExifData(exifData []byte, so *ScanOptions) (exifTags []ExifTag, med
}
}()
exifTags, med, err = getFlatExifDataUniversalSearch(exifData, so, false)
log.PanicIf(err)
return exifTags, med, nil
}
// RELEASE(dustin): GetFlatExifDataUniversalSearch is a kludge to allow univeral tag searching in a backwards-compatible manner. For the next release, undo this and simply add the flag to GetFlatExifData.
// GetFlatExifDataUniversalSearch returns a simple, flat representation of all tags.
func GetFlatExifDataUniversalSearch(exifData []byte, so *ScanOptions, doUniversalSearch bool) (exifTags []ExifTag, med *MiscellaneousExifData, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
exifTags, med, err = getFlatExifDataUniversalSearch(exifData, so, doUniversalSearch)
log.PanicIf(err)
return exifTags, med, nil
}
// GetFlatExifData returns a simple, flat representation of all tags.
func getFlatExifDataUniversalSearch(exifData []byte, so *ScanOptions, doUniversalSearch bool) (exifTags []ExifTag, med *MiscellaneousExifData, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
eh, err := ParseExifHeader(exifData)
log.PanicIf(err)
@ -84,6 +114,10 @@ func GetFlatExifData(exifData []byte, so *ScanOptions) (exifTags []ExifTag, med
ti := NewTagIndex()
if doUniversalSearch == true {
ti.SetUniversalSearch(true)
}
ebs := NewExifReadSeekerWithBytes(exifData)
ie := NewIfdEnumerate(im, ti, ebs, eh.ByteOrder)