mirror of https://github.com/dsoprea/go-exif.git
ifd.go: Moved implementations of IfdMapping and LoadStandardIfds to exifcommon.ifd
In lieu of dropping in next release. We needed this in order to write some unit-tests for exifcommon functionality, and this is common functionality anyway (by any definition of common thus far). - common/ifd.go: Added NewIfdIdentityFromString. The above allowed us to cover this with unit-tests. - This was required for go-exif-knife, to get IFD-paths from the command-line.dustin/master
parent
2a1e3f0fa1
commit
1a62daf305
440
v2/common/ifd.go
440
v2/common/ifd.go
|
@ -1,12 +1,407 @@
|
|||
package exifcommon
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
)
|
||||
|
||||
var (
|
||||
ifdLogger = log.NewLogger("exifcommon.ifd")
|
||||
)
|
||||
|
||||
var (
|
||||
ErrChildIfdNotMapped = errors.New("no child-IFD for that tag-ID under parent")
|
||||
)
|
||||
|
||||
// MappedIfd is one node in the IFD-mapping.
|
||||
type MappedIfd struct {
|
||||
ParentTagId uint16
|
||||
Placement []uint16
|
||||
Path []string
|
||||
|
||||
Name string
|
||||
TagId uint16
|
||||
Children map[uint16]*MappedIfd
|
||||
}
|
||||
|
||||
// String returns a descriptive string.
|
||||
func (mi *MappedIfd) String() string {
|
||||
pathPhrase := mi.PathPhrase()
|
||||
return fmt.Sprintf("MappedIfd<(0x%04X) [%s] PATH=[%s]>", mi.TagId, mi.Name, pathPhrase)
|
||||
}
|
||||
|
||||
// PathPhrase returns a non-fully-qualified IFD path.
|
||||
func (mi *MappedIfd) PathPhrase() string {
|
||||
return strings.Join(mi.Path, "/")
|
||||
}
|
||||
|
||||
// TODO(dustin): Refactor this to use IfdIdentity structs.
|
||||
|
||||
// IfdMapping describes all of the IFDs that we currently recognize.
|
||||
type IfdMapping struct {
|
||||
rootNode *MappedIfd
|
||||
}
|
||||
|
||||
// NewIfdMapping returns a new IfdMapping struct.
|
||||
func NewIfdMapping() (ifdMapping *IfdMapping) {
|
||||
rootNode := &MappedIfd{
|
||||
Path: make([]string, 0),
|
||||
Children: make(map[uint16]*MappedIfd),
|
||||
}
|
||||
|
||||
return &IfdMapping{
|
||||
rootNode: rootNode,
|
||||
}
|
||||
}
|
||||
|
||||
// NewIfdMappingWithStandard retruns a new IfdMapping struct preloaded with the
|
||||
// standard IFDs.
|
||||
func NewIfdMappingWithStandard() (ifdMapping *IfdMapping) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err := log.Wrap(state.(error))
|
||||
log.Panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// RELEASE(dustin): Add error return on next release
|
||||
|
||||
im := NewIfdMapping()
|
||||
|
||||
err := LoadStandardIfds(im)
|
||||
log.PanicIf(err)
|
||||
|
||||
return im
|
||||
}
|
||||
|
||||
// Get returns the node given the path slice.
|
||||
func (im *IfdMapping) Get(parentPlacement []uint16) (childIfd *MappedIfd, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
ptr := im.rootNode
|
||||
for _, tagId := range parentPlacement {
|
||||
if descendantPtr, found := ptr.Children[tagId]; found == false {
|
||||
log.Panicf("ifd child with tag-ID (%04x) not registered: [%s]", tagId, ptr.PathPhrase())
|
||||
} else {
|
||||
ptr = descendantPtr
|
||||
}
|
||||
}
|
||||
|
||||
return ptr, nil
|
||||
}
|
||||
|
||||
// GetWithPath returns the node given the path string.
|
||||
func (im *IfdMapping) GetWithPath(pathPhrase string) (mi *MappedIfd, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
if pathPhrase == "" {
|
||||
log.Panicf("path-phrase is empty")
|
||||
}
|
||||
|
||||
path := strings.Split(pathPhrase, "/")
|
||||
ptr := im.rootNode
|
||||
|
||||
for _, name := range path {
|
||||
var hit *MappedIfd
|
||||
for _, mi := range ptr.Children {
|
||||
if mi.Name == name {
|
||||
hit = mi
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if hit == nil {
|
||||
log.Panicf("ifd child with name [%s] not registered: [%s]", name, ptr.PathPhrase())
|
||||
}
|
||||
|
||||
ptr = hit
|
||||
}
|
||||
|
||||
return ptr, nil
|
||||
}
|
||||
|
||||
// GetChild is a convenience function to get the child path for a given parent
|
||||
// placement and child tag-ID.
|
||||
func (im *IfdMapping) GetChild(parentPathPhrase string, tagId uint16) (mi *MappedIfd, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
mi, err = im.GetWithPath(parentPathPhrase)
|
||||
log.PanicIf(err)
|
||||
|
||||
for _, childMi := range mi.Children {
|
||||
if childMi.TagId == tagId {
|
||||
return childMi, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Whether or not an IFD is defined in data, such an IFD is not registered
|
||||
// and would be unknown.
|
||||
log.Panic(ErrChildIfdNotMapped)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// IfdTagIdAndIndex represents a specific part of the IFD path.
|
||||
//
|
||||
// This is a legacy type.
|
||||
type IfdTagIdAndIndex struct {
|
||||
Name string
|
||||
TagId uint16
|
||||
Index int
|
||||
}
|
||||
|
||||
// String returns a descriptive string.
|
||||
func (itii IfdTagIdAndIndex) String() string {
|
||||
return fmt.Sprintf("IfdTagIdAndIndex<NAME=[%s] ID=(%04x) INDEX=(%d)>", itii.Name, itii.TagId, itii.Index)
|
||||
}
|
||||
|
||||
// ResolvePath takes a list of names, which can also be suffixed with indices
|
||||
// (to identify the second, third, etc.. sibling IFD) and returns a list of
|
||||
// tag-IDs and those indices.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// - IFD/Exif/Iop
|
||||
// - IFD0/Exif/Iop
|
||||
//
|
||||
// This is the only call that supports adding the numeric indices.
|
||||
func (im *IfdMapping) ResolvePath(pathPhrase string) (lineage []IfdTagIdAndIndex, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
pathPhrase = strings.TrimSpace(pathPhrase)
|
||||
|
||||
if pathPhrase == "" {
|
||||
log.Panicf("can not resolve empty path-phrase")
|
||||
}
|
||||
|
||||
path := strings.Split(pathPhrase, "/")
|
||||
lineage = make([]IfdTagIdAndIndex, len(path))
|
||||
|
||||
ptr := im.rootNode
|
||||
empty := IfdTagIdAndIndex{}
|
||||
for i, name := range path {
|
||||
indexByte := name[len(name)-1]
|
||||
index := 0
|
||||
if indexByte >= '0' && indexByte <= '9' {
|
||||
index = int(indexByte - '0')
|
||||
name = name[:len(name)-1]
|
||||
}
|
||||
|
||||
itii := IfdTagIdAndIndex{}
|
||||
for _, mi := range ptr.Children {
|
||||
if mi.Name != name {
|
||||
continue
|
||||
}
|
||||
|
||||
itii.Name = name
|
||||
itii.TagId = mi.TagId
|
||||
itii.Index = index
|
||||
|
||||
ptr = mi
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if itii == empty {
|
||||
log.Panicf("ifd child with name [%s] not registered: [%s]", name, pathPhrase)
|
||||
}
|
||||
|
||||
lineage[i] = itii
|
||||
}
|
||||
|
||||
return lineage, nil
|
||||
}
|
||||
|
||||
// FqPathPhraseFromLineage returns the fully-qualified IFD path from the slice.
|
||||
func (im *IfdMapping) FqPathPhraseFromLineage(lineage []IfdTagIdAndIndex) (fqPathPhrase string) {
|
||||
fqPathParts := make([]string, len(lineage))
|
||||
for i, itii := range lineage {
|
||||
if itii.Index > 0 {
|
||||
fqPathParts[i] = fmt.Sprintf("%s%d", itii.Name, itii.Index)
|
||||
} else {
|
||||
fqPathParts[i] = itii.Name
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(fqPathParts, "/")
|
||||
}
|
||||
|
||||
// PathPhraseFromLineage returns the non-fully-qualified IFD path from the
|
||||
// slice.
|
||||
func (im *IfdMapping) PathPhraseFromLineage(lineage []IfdTagIdAndIndex) (pathPhrase string) {
|
||||
pathParts := make([]string, len(lineage))
|
||||
for i, itii := range lineage {
|
||||
pathParts[i] = itii.Name
|
||||
}
|
||||
|
||||
return strings.Join(pathParts, "/")
|
||||
}
|
||||
|
||||
// StripPathPhraseIndices returns a non-fully-qualified path-phrase (no
|
||||
// indices).
|
||||
func (im *IfdMapping) StripPathPhraseIndices(pathPhrase string) (strippedPathPhrase string, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
lineage, err := im.ResolvePath(pathPhrase)
|
||||
log.PanicIf(err)
|
||||
|
||||
strippedPathPhrase = im.PathPhraseFromLineage(lineage)
|
||||
return strippedPathPhrase, nil
|
||||
}
|
||||
|
||||
// Add puts the given IFD at the given position of the tree. The position of the
|
||||
// tree is referred to as the placement and is represented by a set of tag-IDs,
|
||||
// where the leftmost is the root tag and the tags going to the right are
|
||||
// progressive descendants.
|
||||
func (im *IfdMapping) Add(parentPlacement []uint16, tagId uint16, name string) (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
// TODO(dustin): !! It would be nicer to provide a list of names in the placement rather than tag-IDs.
|
||||
|
||||
ptr, err := im.Get(parentPlacement)
|
||||
log.PanicIf(err)
|
||||
|
||||
path := make([]string, len(parentPlacement)+1)
|
||||
if len(parentPlacement) > 0 {
|
||||
copy(path, ptr.Path)
|
||||
}
|
||||
|
||||
path[len(path)-1] = name
|
||||
|
||||
placement := make([]uint16, len(parentPlacement)+1)
|
||||
if len(placement) > 0 {
|
||||
copy(placement, ptr.Placement)
|
||||
}
|
||||
|
||||
placement[len(placement)-1] = tagId
|
||||
|
||||
childIfd := &MappedIfd{
|
||||
ParentTagId: ptr.TagId,
|
||||
Path: path,
|
||||
Placement: placement,
|
||||
Name: name,
|
||||
TagId: tagId,
|
||||
Children: make(map[uint16]*MappedIfd),
|
||||
}
|
||||
|
||||
if _, found := ptr.Children[tagId]; found == true {
|
||||
log.Panicf("child IFD with tag-ID (%04x) already registered under IFD [%s] with tag-ID (%04x)", tagId, ptr.Name, ptr.TagId)
|
||||
}
|
||||
|
||||
ptr.Children[tagId] = childIfd
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (im *IfdMapping) dumpLineages(stack []*MappedIfd, input []string) (output []string, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
currentIfd := stack[len(stack)-1]
|
||||
|
||||
output = input
|
||||
for _, childIfd := range currentIfd.Children {
|
||||
stackCopy := make([]*MappedIfd, len(stack)+1)
|
||||
|
||||
copy(stackCopy, stack)
|
||||
stackCopy[len(stack)] = childIfd
|
||||
|
||||
// Add to output, but don't include the obligatory root node.
|
||||
parts := make([]string, len(stackCopy)-1)
|
||||
for i, mi := range stackCopy[1:] {
|
||||
parts[i] = mi.Name
|
||||
}
|
||||
|
||||
output = append(output, strings.Join(parts, "/"))
|
||||
|
||||
output, err = im.dumpLineages(stackCopy, output)
|
||||
log.PanicIf(err)
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// DumpLineages returns a slice of strings representing all mappings.
|
||||
func (im *IfdMapping) DumpLineages() (output []string, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
stack := []*MappedIfd{im.rootNode}
|
||||
output = make([]string, 0)
|
||||
|
||||
output, err = im.dumpLineages(stack, output)
|
||||
log.PanicIf(err)
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// LoadStandardIfds loads the standard IFDs into the mapping.
|
||||
func LoadStandardIfds(im *IfdMapping) (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
err = im.Add(
|
||||
[]uint16{},
|
||||
IfdStandardIfdIdentity.TagId(), IfdStandardIfdIdentity.Name())
|
||||
|
||||
log.PanicIf(err)
|
||||
|
||||
err = im.Add(
|
||||
[]uint16{IfdStandardIfdIdentity.TagId()},
|
||||
IfdExifStandardIfdIdentity.TagId(), IfdExifStandardIfdIdentity.Name())
|
||||
|
||||
log.PanicIf(err)
|
||||
|
||||
err = im.Add(
|
||||
[]uint16{IfdStandardIfdIdentity.TagId(), IfdExifStandardIfdIdentity.TagId()},
|
||||
IfdExifIopStandardIfdIdentity.TagId(), IfdExifIopStandardIfdIdentity.Name())
|
||||
|
||||
log.PanicIf(err)
|
||||
|
||||
err = im.Add(
|
||||
[]uint16{IfdStandardIfdIdentity.TagId()},
|
||||
IfdGpsInfoStandardIfdIdentity.TagId(), IfdGpsInfoStandardIfdIdentity.Name())
|
||||
|
||||
log.PanicIf(err)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IfdTag describes a single IFD tag and its parent (if any).
|
||||
type IfdTag struct {
|
||||
parentIfdTag *IfdTag
|
||||
|
@ -105,6 +500,51 @@ func NewIfdIdentity(ifdTag IfdTag, parts ...IfdIdentityPart) (ii *IfdIdentity) {
|
|||
return ii
|
||||
}
|
||||
|
||||
// NewIfdIdentityFromString parses a string like "IFD/Exif" or "IFD1" or
|
||||
// something more exotic with custom IFDs ("SomeIFD4/SomeChildIFD6"). Note that
|
||||
// this will valid the unindexed IFD structure (because the standard tags from
|
||||
// the specification are unindexed), but not, obviously, any indices (e.g.
|
||||
// the numbers in "IFD0", "IFD1", "SomeIFD4/SomeChildIFD6"). It is
|
||||
// required for the caller to check whether these specific instances
|
||||
// were actually parsed out of the stream.
|
||||
func NewIfdIdentityFromString(im *IfdMapping, fqIfdPath string) (ii *IfdIdentity, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
lineage, err := im.ResolvePath(fqIfdPath)
|
||||
log.PanicIf(err)
|
||||
|
||||
var lastIt *IfdTag
|
||||
identityParts := make([]IfdIdentityPart, len(lineage))
|
||||
for i, itii := range lineage {
|
||||
// Build out the tag that will eventually point to the IFD represented
|
||||
// by the right-most part in the IFD path.
|
||||
|
||||
it := &IfdTag{
|
||||
parentIfdTag: lastIt,
|
||||
tagId: itii.TagId,
|
||||
name: itii.Name,
|
||||
}
|
||||
|
||||
lastIt = it
|
||||
|
||||
// Create the next IfdIdentity part.
|
||||
|
||||
iip := IfdIdentityPart{
|
||||
Name: itii.Name,
|
||||
Index: itii.Index,
|
||||
}
|
||||
|
||||
identityParts[i] = iip
|
||||
}
|
||||
|
||||
ii = NewIfdIdentity(*lastIt, identityParts...)
|
||||
return ii, nil
|
||||
}
|
||||
|
||||
func (ii *IfdIdentity) getFqIfdPath() string {
|
||||
partPhrases := make([]string, len(ii.parts))
|
||||
for i, iip := range ii.parts {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package exif
|
||||
package exifcommon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -7,8 +7,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
|
||||
"github.com/dsoprea/go-exif/v2/common"
|
||||
)
|
||||
|
||||
func TestIfdMapping_Add(t *testing.T) {
|
||||
|
@ -109,16 +107,16 @@ func TestIfdMapping_Get(t *testing.T) {
|
|||
log.PanicIf(err)
|
||||
|
||||
mi, err := im.Get([]uint16{
|
||||
exifcommon.IfdStandardIfdIdentity.TagId(),
|
||||
exifcommon.IfdExifStandardIfdIdentity.TagId(),
|
||||
exifcommon.IfdExifIopStandardIfdIdentity.TagId(),
|
||||
IfdStandardIfdIdentity.TagId(),
|
||||
IfdExifStandardIfdIdentity.TagId(),
|
||||
IfdExifIopStandardIfdIdentity.TagId(),
|
||||
})
|
||||
|
||||
log.PanicIf(err)
|
||||
|
||||
if mi.ParentTagId != exifcommon.IfdExifStandardIfdIdentity.TagId() {
|
||||
if mi.ParentTagId != IfdExifStandardIfdIdentity.TagId() {
|
||||
t.Fatalf("Parent tag-ID not correct")
|
||||
} else if mi.TagId != exifcommon.IfdExifIopStandardIfdIdentity.TagId() {
|
||||
} else if mi.TagId != IfdExifIopStandardIfdIdentity.TagId() {
|
||||
t.Fatalf("Tag-ID not correct")
|
||||
} else if mi.Name != "Iop" {
|
||||
t.Fatalf("name not correct")
|
||||
|
@ -136,9 +134,9 @@ func TestIfdMapping_GetWithPath(t *testing.T) {
|
|||
mi, err := im.GetWithPath("IFD/Exif/Iop")
|
||||
log.PanicIf(err)
|
||||
|
||||
if mi.ParentTagId != exifcommon.IfdExifStandardIfdIdentity.TagId() {
|
||||
if mi.ParentTagId != IfdExifStandardIfdIdentity.TagId() {
|
||||
t.Fatalf("Parent tag-ID not correct")
|
||||
} else if mi.TagId != exifcommon.IfdExifIopStandardIfdIdentity.TagId() {
|
||||
} else if mi.TagId != IfdExifIopStandardIfdIdentity.TagId() {
|
||||
t.Fatalf("Tag-ID not correct")
|
||||
} else if mi.Name != "Iop" {
|
||||
t.Fatalf("name not correct")
|
||||
|
@ -269,3 +267,67 @@ func TestIfdMapping_NewIfdMappingWithStandard(t *testing.T) {
|
|||
t.Fatalf("Standard IFDs not loaded correctly.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewIfdIdentityFromString_Valid_WithoutIndexes(t *testing.T) {
|
||||
im := NewIfdMapping()
|
||||
|
||||
err := LoadStandardIfds(im)
|
||||
log.PanicIf(err)
|
||||
|
||||
fqIfdPath := "IFD/Exif"
|
||||
|
||||
ii, err := NewIfdIdentityFromString(im, fqIfdPath)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ii.String() != fqIfdPath {
|
||||
t.Fatalf("'%s' IFD-path was not parsed correctly: [%s]", fqIfdPath, ii.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewIfdIdentityFromString_Valid_WithIndexes(t *testing.T) {
|
||||
im := NewIfdMapping()
|
||||
|
||||
err := LoadStandardIfds(im)
|
||||
log.PanicIf(err)
|
||||
|
||||
fqIfdPath := "IFD2/Exif4"
|
||||
|
||||
ii, err := NewIfdIdentityFromString(im, fqIfdPath)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ii.String() != fqIfdPath {
|
||||
t.Fatalf("'%s' IFD-path was not parsed correctly: [%s]", fqIfdPath, ii.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewIfdIdentityFromString_Invalid_IfdPathJustRoot(t *testing.T) {
|
||||
im := NewIfdMapping()
|
||||
|
||||
err := LoadStandardIfds(im)
|
||||
log.PanicIf(err)
|
||||
|
||||
fqIfdPath := "XYZ"
|
||||
|
||||
_, err = NewIfdIdentityFromString(im, fqIfdPath)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error from invalid path.")
|
||||
} else if err.Error() != "ifd child with name [XYZ] not registered: [XYZ]" {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewIfdIdentityFromString_Invalid_IfdPathWithSubdirectory(t *testing.T) {
|
||||
im := NewIfdMapping()
|
||||
|
||||
err := LoadStandardIfds(im)
|
||||
log.PanicIf(err)
|
||||
|
||||
fqIfdPath := "IFD/XYZ"
|
||||
|
||||
_, err = NewIfdIdentityFromString(im, fqIfdPath)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error from invalid path.")
|
||||
} else if err.Error() != "ifd child with name [XYZ] not registered: [IFD/XYZ]" {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
|
@ -192,7 +192,7 @@ func ParseExifHeader(data []byte) (eh ExifHeader, err error) {
|
|||
}
|
||||
|
||||
// Visit recursively invokes a callback for every tag.
|
||||
func Visit(rootIfdIdentity *exifcommon.IfdIdentity, ifdMapping *IfdMapping, tagIndex *TagIndex, exifData []byte, visitor TagVisitorFn) (eh ExifHeader, furthestOffset uint32, err error) {
|
||||
func Visit(rootIfdIdentity *exifcommon.IfdIdentity, ifdMapping *exifcommon.IfdMapping, tagIndex *TagIndex, exifData []byte, visitor TagVisitorFn) (eh ExifHeader, furthestOffset uint32, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
|
@ -213,7 +213,7 @@ func Visit(rootIfdIdentity *exifcommon.IfdIdentity, ifdMapping *IfdMapping, tagI
|
|||
}
|
||||
|
||||
// Collect recursively builds a static structure of all IFDs and tags.
|
||||
func Collect(ifdMapping *IfdMapping, tagIndex *TagIndex, exifData []byte) (eh ExifHeader, index IfdIndex, err error) {
|
||||
func Collect(ifdMapping *exifcommon.IfdMapping, tagIndex *TagIndex, exifData []byte) (eh ExifHeader, index IfdIndex, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
|
|
387
v2/ifd.go
387
v2/ifd.go
|
@ -1,402 +1,39 @@
|
|||
package exif
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
|
||||
"github.com/dsoprea/go-exif/v2/common"
|
||||
)
|
||||
|
||||
var (
|
||||
ifdLogger = log.NewLogger("exif.ifd")
|
||||
)
|
||||
|
||||
var (
|
||||
ErrChildIfdNotMapped = errors.New("no child-IFD for that tag-ID under parent")
|
||||
)
|
||||
|
||||
// MappedIfd is one node in the IFD-mapping.
|
||||
type MappedIfd struct {
|
||||
ParentTagId uint16
|
||||
Placement []uint16
|
||||
Path []string
|
||||
|
||||
Name string
|
||||
TagId uint16
|
||||
Children map[uint16]*MappedIfd
|
||||
}
|
||||
|
||||
// String returns a descriptive string.
|
||||
func (mi *MappedIfd) String() string {
|
||||
pathPhrase := mi.PathPhrase()
|
||||
return fmt.Sprintf("MappedIfd<(0x%04X) [%s] PATH=[%s]>", mi.TagId, mi.Name, pathPhrase)
|
||||
}
|
||||
|
||||
// PathPhrase returns a non-fully-qualified IFD path.
|
||||
func (mi *MappedIfd) PathPhrase() string {
|
||||
return strings.Join(mi.Path, "/")
|
||||
}
|
||||
|
||||
// TODO(dustin): Refactor this to use IfdIdentity structs.
|
||||
|
||||
// IfdMapping describes all of the IFDs that we currently recognize.
|
||||
type IfdMapping struct {
|
||||
rootNode *MappedIfd
|
||||
}
|
||||
// TODO(dustin): This file now exists for backwards-compatibility only.
|
||||
|
||||
// NewIfdMapping returns a new IfdMapping struct.
|
||||
func NewIfdMapping() (ifdMapping *IfdMapping) {
|
||||
rootNode := &MappedIfd{
|
||||
Path: make([]string, 0),
|
||||
Children: make(map[uint16]*MappedIfd),
|
||||
}
|
||||
|
||||
return &IfdMapping{
|
||||
rootNode: rootNode,
|
||||
}
|
||||
//
|
||||
// RELEASE(dustin): This is a bridging function for backwards-compatibility. Remove this in the next release.
|
||||
func NewIfdMapping() (ifdMapping *exifcommon.IfdMapping) {
|
||||
return exifcommon.NewIfdMapping()
|
||||
}
|
||||
|
||||
// NewIfdMappingWithStandard retruns a new IfdMapping struct preloaded with the
|
||||
// standard IFDs.
|
||||
func NewIfdMappingWithStandard() (ifdMapping *IfdMapping) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err := log.Wrap(state.(error))
|
||||
log.Panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
im := NewIfdMapping()
|
||||
|
||||
err := LoadStandardIfds(im)
|
||||
log.PanicIf(err)
|
||||
|
||||
return im
|
||||
}
|
||||
|
||||
// Get returns the node given the path slice.
|
||||
func (im *IfdMapping) Get(parentPlacement []uint16) (childIfd *MappedIfd, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
ptr := im.rootNode
|
||||
for _, tagId := range parentPlacement {
|
||||
if descendantPtr, found := ptr.Children[tagId]; found == false {
|
||||
log.Panicf("ifd child with tag-ID (%04x) not registered: [%s]", tagId, ptr.PathPhrase())
|
||||
} else {
|
||||
ptr = descendantPtr
|
||||
}
|
||||
}
|
||||
|
||||
return ptr, nil
|
||||
}
|
||||
|
||||
// GetWithPath returns the node given the path string.
|
||||
func (im *IfdMapping) GetWithPath(pathPhrase string) (mi *MappedIfd, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
if pathPhrase == "" {
|
||||
log.Panicf("path-phrase is empty")
|
||||
}
|
||||
|
||||
path := strings.Split(pathPhrase, "/")
|
||||
ptr := im.rootNode
|
||||
|
||||
for _, name := range path {
|
||||
var hit *MappedIfd
|
||||
for _, mi := range ptr.Children {
|
||||
if mi.Name == name {
|
||||
hit = mi
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if hit == nil {
|
||||
log.Panicf("ifd child with name [%s] not registered: [%s]", name, ptr.PathPhrase())
|
||||
}
|
||||
|
||||
ptr = hit
|
||||
}
|
||||
|
||||
return ptr, nil
|
||||
}
|
||||
|
||||
// GetChild is a convenience function to get the child path for a given parent
|
||||
// placement and child tag-ID.
|
||||
func (im *IfdMapping) GetChild(parentPathPhrase string, tagId uint16) (mi *MappedIfd, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
mi, err = im.GetWithPath(parentPathPhrase)
|
||||
log.PanicIf(err)
|
||||
|
||||
for _, childMi := range mi.Children {
|
||||
if childMi.TagId == tagId {
|
||||
return childMi, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Whether or not an IFD is defined in data, such an IFD is not registered
|
||||
// and would be unknown.
|
||||
log.Panic(ErrChildIfdNotMapped)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// IfdTagIdAndIndex represents a specific part of the IFD path.
|
||||
//
|
||||
// This is a legacy type.
|
||||
type IfdTagIdAndIndex struct {
|
||||
Name string
|
||||
TagId uint16
|
||||
Index int
|
||||
}
|
||||
|
||||
// String returns a descriptive string.
|
||||
func (itii IfdTagIdAndIndex) String() string {
|
||||
return fmt.Sprintf("IfdTagIdAndIndex<NAME=[%s] ID=(%04x) INDEX=(%d)>", itii.Name, itii.TagId, itii.Index)
|
||||
}
|
||||
|
||||
// ResolvePath takes a list of names, which can also be suffixed with indices
|
||||
// (to identify the second, third, etc.. sibling IFD) and returns a list of
|
||||
// tag-IDs and those indices.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// - IFD/Exif/Iop
|
||||
// - IFD0/Exif/Iop
|
||||
//
|
||||
// This is the only call that supports adding the numeric indices.
|
||||
func (im *IfdMapping) ResolvePath(pathPhrase string) (lineage []IfdTagIdAndIndex, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
pathPhrase = strings.TrimSpace(pathPhrase)
|
||||
|
||||
if pathPhrase == "" {
|
||||
log.Panicf("can not resolve empty path-phrase")
|
||||
}
|
||||
|
||||
path := strings.Split(pathPhrase, "/")
|
||||
lineage = make([]IfdTagIdAndIndex, len(path))
|
||||
|
||||
ptr := im.rootNode
|
||||
empty := IfdTagIdAndIndex{}
|
||||
for i, name := range path {
|
||||
indexByte := name[len(name)-1]
|
||||
index := 0
|
||||
if indexByte >= '0' && indexByte <= '9' {
|
||||
index = int(indexByte - '0')
|
||||
name = name[:len(name)-1]
|
||||
}
|
||||
|
||||
itii := IfdTagIdAndIndex{}
|
||||
for _, mi := range ptr.Children {
|
||||
if mi.Name != name {
|
||||
continue
|
||||
}
|
||||
|
||||
itii.Name = name
|
||||
itii.TagId = mi.TagId
|
||||
itii.Index = index
|
||||
|
||||
ptr = mi
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if itii == empty {
|
||||
log.Panicf("ifd child with name [%s] not registered: [%s]", name, pathPhrase)
|
||||
}
|
||||
|
||||
lineage[i] = itii
|
||||
}
|
||||
|
||||
return lineage, nil
|
||||
}
|
||||
|
||||
// FqPathPhraseFromLineage returns the fully-qualified IFD path from the slice.
|
||||
func (im *IfdMapping) FqPathPhraseFromLineage(lineage []IfdTagIdAndIndex) (fqPathPhrase string) {
|
||||
fqPathParts := make([]string, len(lineage))
|
||||
for i, itii := range lineage {
|
||||
if itii.Index > 0 {
|
||||
fqPathParts[i] = fmt.Sprintf("%s%d", itii.Name, itii.Index)
|
||||
} else {
|
||||
fqPathParts[i] = itii.Name
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(fqPathParts, "/")
|
||||
}
|
||||
|
||||
// PathPhraseFromLineage returns the non-fully-qualified IFD path from the
|
||||
// slice.
|
||||
func (im *IfdMapping) PathPhraseFromLineage(lineage []IfdTagIdAndIndex) (pathPhrase string) {
|
||||
pathParts := make([]string, len(lineage))
|
||||
for i, itii := range lineage {
|
||||
pathParts[i] = itii.Name
|
||||
}
|
||||
|
||||
return strings.Join(pathParts, "/")
|
||||
}
|
||||
|
||||
// StripPathPhraseIndices returns a non-fully-qualified path-phrase (no
|
||||
// indices).
|
||||
func (im *IfdMapping) StripPathPhraseIndices(pathPhrase string) (strippedPathPhrase string, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
lineage, err := im.ResolvePath(pathPhrase)
|
||||
log.PanicIf(err)
|
||||
|
||||
strippedPathPhrase = im.PathPhraseFromLineage(lineage)
|
||||
return strippedPathPhrase, nil
|
||||
}
|
||||
|
||||
// Add puts the given IFD at the given position of the tree. The position of the
|
||||
// tree is referred to as the placement and is represented by a set of tag-IDs,
|
||||
// where the leftmost is the root tag and the tags going to the right are
|
||||
// progressive descendants.
|
||||
func (im *IfdMapping) Add(parentPlacement []uint16, tagId uint16, name string) (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
// TODO(dustin): !! It would be nicer to provide a list of names in the placement rather than tag-IDs.
|
||||
|
||||
ptr, err := im.Get(parentPlacement)
|
||||
log.PanicIf(err)
|
||||
|
||||
path := make([]string, len(parentPlacement)+1)
|
||||
if len(parentPlacement) > 0 {
|
||||
copy(path, ptr.Path)
|
||||
}
|
||||
|
||||
path[len(path)-1] = name
|
||||
|
||||
placement := make([]uint16, len(parentPlacement)+1)
|
||||
if len(placement) > 0 {
|
||||
copy(placement, ptr.Placement)
|
||||
}
|
||||
|
||||
placement[len(placement)-1] = tagId
|
||||
|
||||
childIfd := &MappedIfd{
|
||||
ParentTagId: ptr.TagId,
|
||||
Path: path,
|
||||
Placement: placement,
|
||||
Name: name,
|
||||
TagId: tagId,
|
||||
Children: make(map[uint16]*MappedIfd),
|
||||
}
|
||||
|
||||
if _, found := ptr.Children[tagId]; found == true {
|
||||
log.Panicf("child IFD with tag-ID (%04x) already registered under IFD [%s] with tag-ID (%04x)", tagId, ptr.Name, ptr.TagId)
|
||||
}
|
||||
|
||||
ptr.Children[tagId] = childIfd
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (im *IfdMapping) dumpLineages(stack []*MappedIfd, input []string) (output []string, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
currentIfd := stack[len(stack)-1]
|
||||
|
||||
output = input
|
||||
for _, childIfd := range currentIfd.Children {
|
||||
stackCopy := make([]*MappedIfd, len(stack)+1)
|
||||
|
||||
copy(stackCopy, stack)
|
||||
stackCopy[len(stack)] = childIfd
|
||||
|
||||
// Add to output, but don't include the obligatory root node.
|
||||
parts := make([]string, len(stackCopy)-1)
|
||||
for i, mi := range stackCopy[1:] {
|
||||
parts[i] = mi.Name
|
||||
}
|
||||
|
||||
output = append(output, strings.Join(parts, "/"))
|
||||
|
||||
output, err = im.dumpLineages(stackCopy, output)
|
||||
log.PanicIf(err)
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// DumpLineages returns a slice of strings representing all mappings.
|
||||
func (im *IfdMapping) DumpLineages() (output []string, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
stack := []*MappedIfd{im.rootNode}
|
||||
output = make([]string, 0)
|
||||
|
||||
output, err = im.dumpLineages(stack, output)
|
||||
log.PanicIf(err)
|
||||
|
||||
return output, nil
|
||||
// RELEASE(dustin): This is a bridging function for backwards-compatibility. Remove this in the next release.
|
||||
func NewIfdMappingWithStandard() (ifdMapping *exifcommon.IfdMapping) {
|
||||
return exifcommon.NewIfdMappingWithStandard()
|
||||
}
|
||||
|
||||
// LoadStandardIfds loads the standard IFDs into the mapping.
|
||||
func LoadStandardIfds(im *IfdMapping) (err error) {
|
||||
//
|
||||
// RELEASE(dustin): This is a bridging function for backwards-compatibility. Remove this in the next release.
|
||||
func LoadStandardIfds(im *exifcommon.IfdMapping) (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
err = im.Add(
|
||||
[]uint16{},
|
||||
exifcommon.IfdStandardIfdIdentity.TagId(), exifcommon.IfdStandardIfdIdentity.Name())
|
||||
|
||||
log.PanicIf(err)
|
||||
|
||||
err = im.Add(
|
||||
[]uint16{exifcommon.IfdStandardIfdIdentity.TagId()},
|
||||
exifcommon.IfdExifStandardIfdIdentity.TagId(), exifcommon.IfdExifStandardIfdIdentity.Name())
|
||||
|
||||
log.PanicIf(err)
|
||||
|
||||
err = im.Add(
|
||||
[]uint16{exifcommon.IfdStandardIfdIdentity.TagId(), exifcommon.IfdExifStandardIfdIdentity.TagId()},
|
||||
exifcommon.IfdExifIopStandardIfdIdentity.TagId(), exifcommon.IfdExifIopStandardIfdIdentity.Name())
|
||||
|
||||
log.PanicIf(err)
|
||||
|
||||
err = im.Add(
|
||||
[]uint16{exifcommon.IfdStandardIfdIdentity.TagId()},
|
||||
exifcommon.IfdGpsInfoStandardIfdIdentity.TagId(), exifcommon.IfdGpsInfoStandardIfdIdentity.Name())
|
||||
|
||||
err = exifcommon.LoadStandardIfds(im)
|
||||
log.PanicIf(err)
|
||||
|
||||
return nil
|
||||
|
|
|
@ -238,11 +238,11 @@ type IfdBuilder struct {
|
|||
// data. Otherwise, it's nil.
|
||||
thumbnailData []byte
|
||||
|
||||
ifdMapping *IfdMapping
|
||||
ifdMapping *exifcommon.IfdMapping
|
||||
tagIndex *TagIndex
|
||||
}
|
||||
|
||||
func NewIfdBuilder(ifdMapping *IfdMapping, tagIndex *TagIndex, ii *exifcommon.IfdIdentity, byteOrder binary.ByteOrder) (ib *IfdBuilder) {
|
||||
func NewIfdBuilder(ifdMapping *exifcommon.IfdMapping, tagIndex *TagIndex, ii *exifcommon.IfdIdentity, byteOrder binary.ByteOrder) (ib *IfdBuilder) {
|
||||
ib = &IfdBuilder{
|
||||
ifdIdentity: ii,
|
||||
|
||||
|
@ -332,7 +332,7 @@ func (ib *IfdBuilder) ChildWithTagId(childIfdTagId uint16) (childIb *IfdBuilder,
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func getOrCreateIbFromRootIbInner(rootIb *IfdBuilder, parentIb *IfdBuilder, currentLineage []IfdTagIdAndIndex) (ib *IfdBuilder, err error) {
|
||||
func getOrCreateIbFromRootIbInner(rootIb *IfdBuilder, parentIb *IfdBuilder, currentLineage []exifcommon.IfdTagIdAndIndex) (ib *IfdBuilder, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
|
@ -346,7 +346,7 @@ func getOrCreateIbFromRootIbInner(rootIb *IfdBuilder, parentIb *IfdBuilder, curr
|
|||
// Since we're calling ourselves recursively with incrementally different
|
||||
// paths, the FQ IFD-path of the parent that called us needs to be passed
|
||||
// in, in order for us to know it.
|
||||
var parentLineage []IfdTagIdAndIndex
|
||||
var parentLineage []exifcommon.IfdTagIdAndIndex
|
||||
if parentIb != nil {
|
||||
var err error
|
||||
|
||||
|
@ -555,7 +555,7 @@ func (ib *IfdBuilder) printTagTree(levels int) {
|
|||
_, err := ib.ifdMapping.GetChild(currentIb.IfdIdentity().UnindexedString(), tag.tagId)
|
||||
if err == nil {
|
||||
isChildIb = true
|
||||
} else if log.Is(err, ErrChildIfdNotMapped) == false {
|
||||
} else if log.Is(err, exifcommon.ErrChildIfdNotMapped) == false {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
|
@ -624,7 +624,7 @@ func (ib *IfdBuilder) printIfdTree(levels int) {
|
|||
_, err := ib.ifdMapping.GetChild(currentIb.IfdIdentity().UnindexedString(), tag.tagId)
|
||||
if err == nil {
|
||||
isChildIb = true
|
||||
} else if log.Is(err, ErrChildIfdNotMapped) == false {
|
||||
} else if log.Is(err, exifcommon.ErrChildIfdNotMapped) == false {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
|
|
|
@ -171,12 +171,12 @@ type IfdEnumerate struct {
|
|||
buffer *bytes.Buffer
|
||||
byteOrder binary.ByteOrder
|
||||
tagIndex *TagIndex
|
||||
ifdMapping *IfdMapping
|
||||
ifdMapping *exifcommon.IfdMapping
|
||||
furthestOffset uint32
|
||||
}
|
||||
|
||||
// NewIfdEnumerate returns a new instance of IfdEnumerate.
|
||||
func NewIfdEnumerate(ifdMapping *IfdMapping, tagIndex *TagIndex, exifData []byte, byteOrder binary.ByteOrder) *IfdEnumerate {
|
||||
func NewIfdEnumerate(ifdMapping *exifcommon.IfdMapping, tagIndex *TagIndex, exifData []byte, byteOrder binary.ByteOrder) *IfdEnumerate {
|
||||
return &IfdEnumerate{
|
||||
exifData: exifData,
|
||||
buffer: bytes.NewBuffer(exifData),
|
||||
|
@ -266,7 +266,7 @@ func (ie *IfdEnumerate) parseTag(ii *exifcommon.IfdIdentity, tagPosition int, bp
|
|||
|
||||
// We also need to set `tag.ChildFqIfdPath` but can't do it here
|
||||
// because we don't have the IFD index.
|
||||
} else if log.Is(err, ErrChildIfdNotMapped) == false {
|
||||
} else if log.Is(err, exifcommon.ErrChildIfdNotMapped) == false {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
|
@ -656,7 +656,7 @@ type Ifd struct {
|
|||
|
||||
thumbnailData []byte
|
||||
|
||||
ifdMapping *IfdMapping
|
||||
ifdMapping *exifcommon.IfdMapping
|
||||
tagIndex *TagIndex
|
||||
}
|
||||
|
||||
|
@ -1387,7 +1387,7 @@ func (ie *IfdEnumerate) FurthestOffset() uint32 {
|
|||
// in that the numeric index will always be zero (the zeroth child) rather than
|
||||
// the proper number (if its actually a sibling to the first child, for
|
||||
// instance).
|
||||
func ParseOneIfd(ifdMapping *IfdMapping, tagIndex *TagIndex, ii *exifcommon.IfdIdentity, byteOrder binary.ByteOrder, ifdBlock []byte, visitor TagVisitorFn) (nextIfdOffset uint32, entries []*IfdTagEntry, err error) {
|
||||
func ParseOneIfd(ifdMapping *exifcommon.IfdMapping, tagIndex *TagIndex, ii *exifcommon.IfdIdentity, byteOrder binary.ByteOrder, ifdBlock []byte, visitor TagVisitorFn) (nextIfdOffset uint32, entries []*IfdTagEntry, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
|
@ -1412,7 +1412,7 @@ func ParseOneIfd(ifdMapping *IfdMapping, tagIndex *TagIndex, ii *exifcommon.IfdI
|
|||
}
|
||||
|
||||
// ParseOneTag is a hack to use an IE to parse a raw tag block.
|
||||
func ParseOneTag(ifdMapping *IfdMapping, tagIndex *TagIndex, ii *exifcommon.IfdIdentity, byteOrder binary.ByteOrder, tagBlock []byte) (ite *IfdTagEntry, err error) {
|
||||
func ParseOneTag(ifdMapping *exifcommon.IfdMapping, tagIndex *TagIndex, ii *exifcommon.IfdIdentity, byteOrder binary.ByteOrder, tagBlock []byte) (ite *IfdTagEntry, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
|
|
Loading…
Reference in New Issue