summaryrefslogtreecommitdiff
path: root/vendor/github.com/dsoprea/go-exif/v3/common/ifd.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/dsoprea/go-exif/v3/common/ifd.go')
-rw-r--r--vendor/github.com/dsoprea/go-exif/v3/common/ifd.go651
1 files changed, 651 insertions, 0 deletions
diff --git a/vendor/github.com/dsoprea/go-exif/v3/common/ifd.go b/vendor/github.com/dsoprea/go-exif/v3/common/ifd.go
new file mode 100644
index 000000000..01886e966
--- /dev/null
+++ b/vendor/github.com/dsoprea/go-exif/v3/common/ifd.go
@@ -0,0 +1,651 @@
+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, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ im := NewIfdMapping()
+
+ err = LoadStandardIfds(im)
+ log.PanicIf(err)
+
+ return im, nil
+}
+
+// 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
+ tagId uint16
+ name string
+}
+
+func NewIfdTag(parentIfdTag *IfdTag, tagId uint16, name string) IfdTag {
+ return IfdTag{
+ parentIfdTag: parentIfdTag,
+ tagId: tagId,
+ name: name,
+ }
+}
+
+// ParentIfd returns the IfdTag of this IFD's parent.
+func (it IfdTag) ParentIfd() *IfdTag {
+ return it.parentIfdTag
+}
+
+// TagId returns the tag-ID of this IFD.
+func (it IfdTag) TagId() uint16 {
+ return it.tagId
+}
+
+// Name returns the simple name of this IFD.
+func (it IfdTag) Name() string {
+ return it.name
+}
+
+// String returns a descriptive string.
+func (it IfdTag) String() string {
+ parentIfdPhrase := ""
+ if it.parentIfdTag != nil {
+ parentIfdPhrase = fmt.Sprintf(" PARENT=(0x%04x)[%s]", it.parentIfdTag.tagId, it.parentIfdTag.name)
+ }
+
+ return fmt.Sprintf("IfdTag<TAG-ID=(0x%04x) NAME=[%s]%s>", it.tagId, it.name, parentIfdPhrase)
+}
+
+var (
+ // rootStandardIfd is the standard root IFD.
+ rootStandardIfd = NewIfdTag(nil, 0x0000, "IFD") // IFD
+
+ // exifStandardIfd is the standard "Exif" IFD.
+ exifStandardIfd = NewIfdTag(&rootStandardIfd, 0x8769, "Exif") // IFD/Exif
+
+ // iopStandardIfd is the standard "Iop" IFD.
+ iopStandardIfd = NewIfdTag(&exifStandardIfd, 0xA005, "Iop") // IFD/Exif/Iop
+
+ // gpsInfoStandardIfd is the standard "GPS" IFD.
+ gpsInfoStandardIfd = NewIfdTag(&rootStandardIfd, 0x8825, "GPSInfo") // IFD/GPSInfo
+)
+
+// IfdIdentityPart represents one component in an IFD path.
+type IfdIdentityPart struct {
+ Name string
+ Index int
+}
+
+// String returns a fully-qualified IFD path.
+func (iip IfdIdentityPart) String() string {
+ if iip.Index > 0 {
+ return fmt.Sprintf("%s%d", iip.Name, iip.Index)
+ } else {
+ return iip.Name
+ }
+}
+
+// UnindexedString returned a non-fully-qualified IFD path.
+func (iip IfdIdentityPart) UnindexedString() string {
+ return iip.Name
+}
+
+// IfdIdentity represents a single IFD path and provides access to various
+// information and representations.
+//
+// Only global instances can be used for equality checks.
+type IfdIdentity struct {
+ ifdTag IfdTag
+ parts []IfdIdentityPart
+ ifdPath string
+ fqIfdPath string
+}
+
+// NewIfdIdentity returns a new IfdIdentity struct.
+func NewIfdIdentity(ifdTag IfdTag, parts ...IfdIdentityPart) (ii *IfdIdentity) {
+ ii = &IfdIdentity{
+ ifdTag: ifdTag,
+ parts: parts,
+ }
+
+ ii.ifdPath = ii.getIfdPath()
+ ii.fqIfdPath = ii.getFqIfdPath()
+
+ 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 {
+ partPhrases[i] = iip.String()
+ }
+
+ return strings.Join(partPhrases, "/")
+}
+
+func (ii *IfdIdentity) getIfdPath() string {
+ partPhrases := make([]string, len(ii.parts))
+ for i, iip := range ii.parts {
+ partPhrases[i] = iip.UnindexedString()
+ }
+
+ return strings.Join(partPhrases, "/")
+}
+
+// String returns a fully-qualified IFD path.
+func (ii *IfdIdentity) String() string {
+ return ii.fqIfdPath
+}
+
+// UnindexedString returns a non-fully-qualified IFD path.
+func (ii *IfdIdentity) UnindexedString() string {
+ return ii.ifdPath
+}
+
+// IfdTag returns the tag struct behind this IFD.
+func (ii *IfdIdentity) IfdTag() IfdTag {
+ return ii.ifdTag
+}
+
+// TagId returns the tag-ID of the IFD.
+func (ii *IfdIdentity) TagId() uint16 {
+ return ii.ifdTag.TagId()
+}
+
+// LeafPathPart returns the last right-most path-part, which represents the
+// current IFD.
+func (ii *IfdIdentity) LeafPathPart() IfdIdentityPart {
+ return ii.parts[len(ii.parts)-1]
+}
+
+// Name returns the simple name of this IFD.
+func (ii *IfdIdentity) Name() string {
+ return ii.LeafPathPart().Name
+}
+
+// Index returns the index of this IFD (more then one IFD under a parent IFD
+// will be numbered [0..n]).
+func (ii *IfdIdentity) Index() int {
+ return ii.LeafPathPart().Index
+}
+
+// Equals returns true if the two IfdIdentity instances are effectively
+// identical.
+//
+// Since there's no way to get a specific fully-qualified IFD path without a
+// certain slice of parts and all other fields are also derived from this,
+// checking that the fully-qualified IFD path is equals is sufficient.
+func (ii *IfdIdentity) Equals(ii2 *IfdIdentity) bool {
+ return ii.String() == ii2.String()
+}
+
+// NewChild creates an IfdIdentity for an IFD that is a child of the current
+// IFD.
+func (ii *IfdIdentity) NewChild(childIfdTag IfdTag, index int) (iiChild *IfdIdentity) {
+ if *childIfdTag.parentIfdTag != ii.ifdTag {
+ log.Panicf("can not add child; we are not the parent:\nUS=%v\nCHILD=%v", ii.ifdTag, childIfdTag)
+ }
+
+ childPart := IfdIdentityPart{childIfdTag.name, index}
+ childParts := append(ii.parts, childPart)
+
+ iiChild = NewIfdIdentity(childIfdTag, childParts...)
+ return iiChild
+}
+
+// NewSibling creates an IfdIdentity for an IFD that is a sibling to the current
+// one.
+func (ii *IfdIdentity) NewSibling(index int) (iiSibling *IfdIdentity) {
+ parts := make([]IfdIdentityPart, len(ii.parts))
+
+ copy(parts, ii.parts)
+ parts[len(parts)-1].Index = index
+
+ iiSibling = NewIfdIdentity(ii.ifdTag, parts...)
+ return iiSibling
+}
+
+var (
+ // IfdStandardIfdIdentity represents the IFD path for IFD0.
+ IfdStandardIfdIdentity = NewIfdIdentity(rootStandardIfd, IfdIdentityPart{"IFD", 0})
+
+ // IfdExifStandardIfdIdentity represents the IFD path for IFD0/Exif0.
+ IfdExifStandardIfdIdentity = IfdStandardIfdIdentity.NewChild(exifStandardIfd, 0)
+
+ // IfdExifIopStandardIfdIdentity represents the IFD path for IFD0/Exif0/Iop0.
+ IfdExifIopStandardIfdIdentity = IfdExifStandardIfdIdentity.NewChild(iopStandardIfd, 0)
+
+ // IfdGPSInfoStandardIfdIdentity represents the IFD path for IFD0/GPSInfo0.
+ IfdGpsInfoStandardIfdIdentity = IfdStandardIfdIdentity.NewChild(gpsInfoStandardIfd, 0)
+
+ // Ifd1StandardIfdIdentity represents the IFD path for IFD1.
+ Ifd1StandardIfdIdentity = NewIfdIdentity(rootStandardIfd, IfdIdentityPart{"IFD", 1})
+)