summaryrefslogtreecommitdiff
path: root/vendor/github.com/dsoprea/go-exif/ifd_enumerate.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/dsoprea/go-exif/ifd_enumerate.go')
-rw-r--r--vendor/github.com/dsoprea/go-exif/ifd_enumerate.go1356
1 files changed, 1356 insertions, 0 deletions
diff --git a/vendor/github.com/dsoprea/go-exif/ifd_enumerate.go b/vendor/github.com/dsoprea/go-exif/ifd_enumerate.go
new file mode 100644
index 000000000..317e847a9
--- /dev/null
+++ b/vendor/github.com/dsoprea/go-exif/ifd_enumerate.go
@@ -0,0 +1,1356 @@
+package exif
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "reflect"
+ "strconv"
+ "strings"
+ "time"
+
+ "encoding/binary"
+
+ "github.com/dsoprea/go-logging"
+)
+
+var (
+ ifdEnumerateLogger = log.NewLogger("exifjpeg.ifd")
+)
+
+var (
+ ErrNoThumbnail = errors.New("no thumbnail")
+ ErrNoGpsTags = errors.New("no gps tags")
+ ErrTagTypeNotValid = errors.New("tag type invalid")
+)
+
+var (
+ ValidGpsVersions = [][4]byte{
+ {2, 2, 0, 0},
+
+ // Suddenly appeared at the default in 2.31: https://home.jeita.or.jp/tsc/std-pdf/CP-3451D.pdf
+ //
+ // Note that the presence of 2.3.0.0 doesn't seem to guarantee
+ // coordinates. In some cases, we seen just the following:
+ //
+ // GPS Tag Version |2.3.0.0
+ // GPS Receiver Status |V
+ // Geodetic Survey Data|WGS-84
+ // GPS Differential Cor|0
+ //
+ {2, 3, 0, 0},
+ }
+)
+
+// IfdTagEnumerator knows how to decode an IFD and all of the tags it
+// describes.
+//
+// The IFDs and the actual values can float throughout the EXIF block, but the
+// IFD itself is just a minor header followed by a set of repeating,
+// statically-sized records. So, the tags (though notnecessarily their values)
+// are fairly simple to enumerate.
+type IfdTagEnumerator struct {
+ byteOrder binary.ByteOrder
+ addressableData []byte
+ ifdOffset uint32
+ buffer *bytes.Buffer
+}
+
+func NewIfdTagEnumerator(addressableData []byte, byteOrder binary.ByteOrder, ifdOffset uint32) (ite *IfdTagEnumerator) {
+ ite = &IfdTagEnumerator{
+ addressableData: addressableData,
+ byteOrder: byteOrder,
+ buffer: bytes.NewBuffer(addressableData[ifdOffset:]),
+ }
+
+ return ite
+}
+
+// getUint16 reads a uint16 and advances both our current and our current
+// accumulator (which allows us to know how far to seek to the beginning of the
+// next IFD when it's time to jump).
+func (ife *IfdTagEnumerator) getUint16() (value uint16, raw []byte, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ needBytes := 2
+ offset := 0
+ raw = make([]byte, needBytes)
+
+ for offset < needBytes {
+ n, err := ife.buffer.Read(raw[offset:])
+ log.PanicIf(err)
+
+ offset += n
+ }
+
+ value = ife.byteOrder.Uint16(raw)
+
+ return value, raw, nil
+}
+
+// getUint32 reads a uint32 and advances both our current and our current
+// accumulator (which allows us to know how far to seek to the beginning of the
+// next IFD when it's time to jump).
+func (ife *IfdTagEnumerator) getUint32() (value uint32, raw []byte, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ needBytes := 4
+ offset := 0
+ raw = make([]byte, needBytes)
+
+ for offset < needBytes {
+ n, err := ife.buffer.Read(raw[offset:])
+ log.PanicIf(err)
+
+ offset += n
+ }
+
+ value = ife.byteOrder.Uint32(raw)
+
+ return value, raw, nil
+}
+
+type IfdEnumerate struct {
+ exifData []byte
+ buffer *bytes.Buffer
+ byteOrder binary.ByteOrder
+ currentOffset uint32
+ tagIndex *TagIndex
+ ifdMapping *IfdMapping
+}
+
+func NewIfdEnumerate(ifdMapping *IfdMapping, tagIndex *TagIndex, exifData []byte, byteOrder binary.ByteOrder) *IfdEnumerate {
+ return &IfdEnumerate{
+ exifData: exifData,
+ buffer: bytes.NewBuffer(exifData),
+ byteOrder: byteOrder,
+ ifdMapping: ifdMapping,
+ tagIndex: tagIndex,
+ }
+}
+
+func (ie *IfdEnumerate) getTagEnumerator(ifdOffset uint32) (ite *IfdTagEnumerator) {
+ ite = NewIfdTagEnumerator(
+ ie.exifData[ExifAddressableAreaStart:],
+ ie.byteOrder,
+ ifdOffset)
+
+ return ite
+}
+
+func (ie *IfdEnumerate) parseTag(fqIfdPath string, tagPosition int, ite *IfdTagEnumerator, resolveValue bool) (tag *IfdTagEntry, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ tagId, _, err := ite.getUint16()
+ log.PanicIf(err)
+
+ tagTypeRaw, _, err := ite.getUint16()
+ log.PanicIf(err)
+
+ tagType := TagTypePrimitive(tagTypeRaw)
+
+ unitCount, _, err := ite.getUint32()
+ log.PanicIf(err)
+
+ valueOffset, rawValueOffset, err := ite.getUint32()
+ log.PanicIf(err)
+
+ if _, found := TypeNames[tagType]; found == false {
+ log.Panic(ErrTagTypeNotValid)
+ }
+
+ ifdPath, err := ie.ifdMapping.StripPathPhraseIndices(fqIfdPath)
+ log.PanicIf(err)
+
+ tag = &IfdTagEntry{
+ IfdPath: ifdPath,
+ TagId: tagId,
+ TagIndex: tagPosition,
+ TagType: tagType,
+ UnitCount: unitCount,
+ ValueOffset: valueOffset,
+ RawValueOffset: rawValueOffset,
+ }
+
+ if resolveValue == true {
+ value, isUnhandledUnknown, err := ie.resolveTagValue(tag)
+ log.PanicIf(err)
+
+ tag.value = value
+ tag.isUnhandledUnknown = isUnhandledUnknown
+ }
+
+ // If it's an IFD but not a standard one, it'll just be seen as a LONG
+ // (the standard IFD tag type), later, unless we skip it because it's
+ // [likely] not even in the standard list of known tags.
+ mi, err := ie.ifdMapping.GetChild(ifdPath, tagId)
+ if err == nil {
+ tag.ChildIfdName = mi.Name
+ tag.ChildIfdPath = mi.PathPhrase()
+ tag.ChildFqIfdPath = fmt.Sprintf("%s/%s", fqIfdPath, mi.Name)
+
+ // 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 {
+ log.Panic(err)
+ }
+
+ return tag, nil
+}
+
+func (ie *IfdEnumerate) GetValueContext(ite *IfdTagEntry) *ValueContext {
+
+ // TODO(dustin): Add test
+
+ addressableData := ie.exifData[ExifAddressableAreaStart:]
+
+ return newValueContextFromTag(
+ ite,
+ addressableData,
+ ie.byteOrder)
+}
+
+func (ie *IfdEnumerate) resolveTagValue(ite *IfdTagEntry) (valueBytes []byte, isUnhandledUnknown bool, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ addressableData := ie.exifData[ExifAddressableAreaStart:]
+
+ // Return the exact bytes of the unknown-type value. Returning a string
+ // (`ValueString`) is easy because we can just pass everything to
+ // `Sprintf()`. Returning the raw, typed value (`Value`) is easy
+ // (obviously). However, here, in order to produce the list of bytes, we
+ // need to coerce whatever `Undefined()` returns.
+ if ite.TagType == TypeUndefined {
+ valueContext := ie.GetValueContext(ite)
+
+ value, err := valueContext.Undefined()
+ if err != nil {
+ if err == ErrUnhandledUnknownTypedTag {
+ valueBytes = []byte(UnparseableUnknownTagValuePlaceholder)
+ return valueBytes, true, nil
+ }
+
+ log.Panic(err)
+ } else {
+ switch value.(type) {
+ case []byte:
+ return value.([]byte), false, nil
+ case TagUnknownType_UnknownValue:
+ b := []byte(value.(TagUnknownType_UnknownValue))
+ return b, false, nil
+ case string:
+ return []byte(value.(string)), false, nil
+ case UnknownTagValue:
+ valueBytes, err := value.(UnknownTagValue).ValueBytes()
+ log.PanicIf(err)
+
+ return valueBytes, false, nil
+ default:
+ // TODO(dustin): !! Finish translating the rest of the types (make reusable and replace into other similar implementations?)
+ log.Panicf("can not produce bytes for unknown-type tag (0x%04x) (1): [%s]", ite.TagId, reflect.TypeOf(value))
+ }
+ }
+ } else {
+ originalType := NewTagType(ite.TagType, ie.byteOrder)
+ byteCount := uint32(originalType.Type().Size()) * ite.UnitCount
+
+ tt := NewTagType(TypeByte, ie.byteOrder)
+
+ if tt.valueIsEmbedded(byteCount) == true {
+ iteLogger.Debugf(nil, "Reading BYTE value (ITE; embedded).")
+
+ // In this case, the bytes normally used for the offset are actually
+ // data.
+ valueBytes, err = tt.ParseBytes(ite.RawValueOffset, byteCount)
+ log.PanicIf(err)
+ } else {
+ iteLogger.Debugf(nil, "Reading BYTE value (ITE; at offset).")
+
+ valueBytes, err = tt.ParseBytes(addressableData[ite.ValueOffset:], byteCount)
+ log.PanicIf(err)
+ }
+ }
+
+ return valueBytes, false, nil
+}
+
+// RawTagVisitorPtr is an optional callback that can get hit for every tag we parse
+// through. `addressableData` is the byte array startign after the EXIF header
+// (where the offsets of all IFDs and values are calculated from).
+//
+// This was reimplemented as an interface to allow for simpler change management
+// in the future.
+type RawTagWalk interface {
+ Visit(fqIfdPath string, ifdIndex int, tagId uint16, tagType TagType, valueContext *ValueContext) (err error)
+}
+
+type RawTagWalkLegacyWrapper struct {
+ legacyVisitor RawTagVisitor
+}
+
+func (rtwlw RawTagWalkLegacyWrapper) Visit(fqIfdPath string, ifdIndex int, tagId uint16, tagType TagType, valueContext *ValueContext) (err error) {
+ return rtwlw.legacyVisitor(fqIfdPath, ifdIndex, tagId, tagType, *valueContext)
+}
+
+// RawTagVisitor is an optional callback that can get hit for every tag we parse
+// through. `addressableData` is the byte array startign after the EXIF header
+// (where the offsets of all IFDs and values are calculated from).
+//
+// DEPRECATED(dustin): Use a RawTagWalk instead.
+type RawTagVisitor func(fqIfdPath string, ifdIndex int, tagId uint16, tagType TagType, valueContext ValueContext) (err error)
+
+// ParseIfd decodes the IFD block that we're currently sitting on the first
+// byte of.
+func (ie *IfdEnumerate) ParseIfd(fqIfdPath string, ifdIndex int, ite *IfdTagEnumerator, visitor interface{}, doDescend bool, resolveValues bool) (nextIfdOffset uint32, entries []*IfdTagEntry, thumbnailData []byte, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ var visitorWrapper RawTagWalk
+
+ if visitor != nil {
+ var ok bool
+
+ visitorWrapper, ok = visitor.(RawTagWalk)
+ if ok == false {
+ // Legacy usage.
+
+ // `ok` can be `true` but `legacyVisitor` can still be `nil` (when
+ // passed as nil).
+ if legacyVisitor, ok := visitor.(RawTagVisitor); ok == true && legacyVisitor != nil {
+ visitorWrapper = RawTagWalkLegacyWrapper{
+ legacyVisitor: legacyVisitor,
+ }
+ }
+ }
+ }
+
+ tagCount, _, err := ite.getUint16()
+ log.PanicIf(err)
+
+ ifdEnumerateLogger.Debugf(nil, "Current IFD tag-count: (%d)", tagCount)
+
+ entries = make([]*IfdTagEntry, 0)
+
+ var iteThumbnailOffset *IfdTagEntry
+ var iteThumbnailSize *IfdTagEntry
+
+ for i := 0; i < int(tagCount); i++ {
+ tag, err := ie.parseTag(fqIfdPath, i, ite, resolveValues)
+ if err != nil {
+ if log.Is(err, ErrTagTypeNotValid) == true {
+ ifdEnumerateLogger.Warningf(nil, "Tag in IFD [%s] at position (%d) has invalid type and will be skipped.", fqIfdPath, i)
+ continue
+ }
+
+ log.Panic(err)
+ }
+
+ if tag.TagId == ThumbnailOffsetTagId {
+ iteThumbnailOffset = tag
+
+ continue
+ } else if tag.TagId == ThumbnailSizeTagId {
+ iteThumbnailSize = tag
+ continue
+ }
+
+ if visitorWrapper != nil {
+ tt := NewTagType(tag.TagType, ie.byteOrder)
+
+ valueContext := ie.GetValueContext(tag)
+
+ err := visitorWrapper.Visit(fqIfdPath, ifdIndex, tag.TagId, tt, valueContext)
+ log.PanicIf(err)
+ }
+
+ // If it's an IFD but not a standard one, it'll just be seen as a LONG
+ // (the standard IFD tag type), later, unless we skip it because it's
+ // [likely] not even in the standard list of known tags.
+ if tag.ChildIfdPath != "" {
+ if doDescend == true {
+ ifdEnumerateLogger.Debugf(nil, "Descending to IFD [%s].", tag.ChildIfdPath)
+
+ err := ie.scan(tag.ChildFqIfdPath, tag.ValueOffset, visitor, resolveValues)
+ log.PanicIf(err)
+ }
+ }
+
+ entries = append(entries, tag)
+ }
+
+ if iteThumbnailOffset != nil && iteThumbnailSize != nil {
+ thumbnailData, err = ie.parseThumbnail(iteThumbnailOffset, iteThumbnailSize)
+ log.PanicIf(err)
+ }
+
+ nextIfdOffset, _, err = ite.getUint32()
+ log.PanicIf(err)
+
+ ifdEnumerateLogger.Debugf(nil, "Next IFD at offset: (%08x)", nextIfdOffset)
+
+ return nextIfdOffset, entries, thumbnailData, nil
+}
+
+func (ie *IfdEnumerate) parseThumbnail(offsetIte, lengthIte *IfdTagEntry) (thumbnailData []byte, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ addressableData := ie.exifData[ExifAddressableAreaStart:]
+
+ vRaw, err := lengthIte.Value(addressableData, ie.byteOrder)
+ log.PanicIf(err)
+
+ vList := vRaw.([]uint32)
+ if len(vList) != 1 {
+ log.Panicf("not exactly one long: (%d)", len(vList))
+ }
+
+ length := vList[0]
+
+ // The tag is official a LONG type, but it's actually an offset to a blob of bytes.
+ offsetIte.TagType = TypeByte
+ offsetIte.UnitCount = length
+
+ thumbnailData, err = offsetIte.ValueBytes(addressableData, ie.byteOrder)
+ log.PanicIf(err)
+
+ return thumbnailData, nil
+}
+
+// Scan enumerates the different EXIF's IFD blocks.
+func (ie *IfdEnumerate) scan(fqIfdName string, ifdOffset uint32, visitor interface{}, resolveValues bool) (err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ for ifdIndex := 0; ; ifdIndex++ {
+ ifdEnumerateLogger.Debugf(nil, "Parsing IFD [%s] (%d) at offset (%04x).", fqIfdName, ifdIndex, ifdOffset)
+ ite := ie.getTagEnumerator(ifdOffset)
+
+ nextIfdOffset, _, _, err := ie.ParseIfd(fqIfdName, ifdIndex, ite, visitor, true, resolveValues)
+ log.PanicIf(err)
+
+ if nextIfdOffset == 0 {
+ break
+ }
+
+ ifdOffset = nextIfdOffset
+ }
+
+ return nil
+}
+
+// Scan enumerates the different EXIF blocks (called IFDs). `rootIfdName` will
+// be "IFD" in the TIFF standard.
+func (ie *IfdEnumerate) Scan(rootIfdName string, ifdOffset uint32, visitor RawTagVisitor, resolveValue bool) (err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ err = ie.scan(rootIfdName, ifdOffset, visitor, resolveValue)
+ log.PanicIf(err)
+
+ return nil
+}
+
+// Ifd represents a single parsed IFD.
+type Ifd struct {
+
+ // TODO(dustin): !! Why are all of these public? Privatize them and then add NextIfd().
+
+ // This is just for convenience, just so that we can easily get the values
+ // and not involve other projects in semantics that they won't otherwise
+ // need to know.
+ addressableData []byte
+
+ ByteOrder binary.ByteOrder
+
+ // Name is the name of the IFD (the rightmost name in the path, sans any
+ // indices).
+ Name string
+
+ // IfdPath is a simple IFD path (e.g. IFD/GPSInfo). No indices.
+ IfdPath string
+
+ // FqIfdPath is a fully-qualified IFD path (e.g. IFD0/GPSInfo0). With
+ // indices.
+ FqIfdPath string
+
+ TagId uint16
+
+ Id int
+
+ ParentIfd *Ifd
+
+ // ParentTagIndex is our tag position in the parent IFD, if we had a parent
+ // (if `ParentIfd` is not nil and we weren't an IFD referenced as a sibling
+ // instead of as a child).
+ ParentTagIndex int
+
+ // Name string
+ Index int
+ Offset uint32
+
+ Entries []*IfdTagEntry
+ EntriesByTagId map[uint16][]*IfdTagEntry
+
+ Children []*Ifd
+
+ ChildIfdIndex map[string]*Ifd
+
+ NextIfdOffset uint32
+ NextIfd *Ifd
+
+ thumbnailData []byte
+
+ ifdMapping *IfdMapping
+ tagIndex *TagIndex
+}
+
+func (ifd *Ifd) ChildWithIfdPath(ifdPath string) (childIfd *Ifd, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ for _, childIfd := range ifd.Children {
+ if childIfd.IfdPath == ifdPath {
+ return childIfd, nil
+ }
+ }
+
+ log.Panic(ErrTagNotFound)
+ return nil, nil
+}
+
+func (ifd *Ifd) TagValue(ite *IfdTagEntry) (value interface{}, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ value, err = ite.Value(ifd.addressableData, ifd.ByteOrder)
+ log.PanicIf(err)
+
+ return value, nil
+}
+
+func (ifd *Ifd) TagValueBytes(ite *IfdTagEntry) (value []byte, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ value, err = ite.ValueBytes(ifd.addressableData, ifd.ByteOrder)
+ log.PanicIf(err)
+
+ return value, nil
+}
+
+// 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))
+ }
+ }()
+
+ it, err := ifd.tagIndex.GetWithName(ifd.IfdPath, 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 {
+ parentOffset = ifd.ParentIfd.Offset
+ }
+
+ return fmt.Sprintf("Ifd<ID=(%d) IFD-PATH=[%s] INDEX=(%d) COUNT=(%d) OFF=(0x%04x) CHILDREN=(%d) PARENT=(0x%04x) NEXT-IFD=(0x%04x)>", ifd.Id, ifd.IfdPath, ifd.Index, len(ifd.Entries), ifd.Offset, len(ifd.Children), parentOffset, ifd.NextIfdOffset)
+}
+
+func (ifd *Ifd) Thumbnail() (data []byte, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ if ifd.thumbnailData == nil {
+ log.Panic(ErrNoThumbnail)
+ }
+
+ return ifd.thumbnailData, nil
+}
+
+func (ifd *Ifd) dumpTags(tags []*IfdTagEntry) []*IfdTagEntry {
+ if tags == nil {
+ tags = make([]*IfdTagEntry, 0)
+ }
+
+ // Now, print the tags while also descending to child-IFDS as we encounter them.
+
+ ifdsFoundCount := 0
+
+ for _, tag := range ifd.Entries {
+ tags = append(tags, tag)
+
+ if tag.ChildIfdPath != "" {
+ ifdsFoundCount++
+
+ childIfd, found := ifd.ChildIfdIndex[tag.ChildIfdPath]
+ if found != true {
+ log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdPath)
+ }
+
+ tags = childIfd.dumpTags(tags)
+ }
+ }
+
+ if len(ifd.Children) != ifdsFoundCount {
+ log.Panicf("have one or more dangling child IFDs: (%d) != (%d)", len(ifd.Children), ifdsFoundCount)
+ }
+
+ if ifd.NextIfd != nil {
+ tags = ifd.NextIfd.dumpTags(tags)
+ }
+
+ return tags
+}
+
+// DumpTags prints the IFD hierarchy.
+func (ifd *Ifd) DumpTags() []*IfdTagEntry {
+ return ifd.dumpTags(nil)
+}
+
+func (ifd *Ifd) printTagTree(populateValues bool, index, level int, nextLink bool) {
+ indent := strings.Repeat(" ", level*2)
+
+ prefix := " "
+ if nextLink {
+ prefix = ">"
+ }
+
+ fmt.Printf("%s%sIFD: %s\n", indent, prefix, ifd)
+
+ // Now, print the tags while also descending to child-IFDS as we encounter them.
+
+ ifdsFoundCount := 0
+
+ for _, tag := range ifd.Entries {
+ if tag.ChildIfdPath != "" {
+ fmt.Printf("%s - TAG: %s\n", indent, tag)
+ } else {
+ it, err := ifd.tagIndex.Get(ifd.IfdPath, tag.TagId)
+
+ tagName := ""
+ if err == nil {
+ tagName = it.Name
+ }
+
+ var value interface{}
+ if populateValues == true {
+ var err error
+
+ value, err = ifd.TagValue(tag)
+ if err != nil {
+ if err == ErrUnhandledUnknownTypedTag {
+ value = UnparseableUnknownTagValuePlaceholder
+ } else {
+ log.Panic(err)
+ }
+ }
+ }
+
+ fmt.Printf("%s - TAG: %s NAME=[%s] VALUE=[%v]\n", indent, tag, tagName, value)
+ }
+
+ if tag.ChildIfdPath != "" {
+ ifdsFoundCount++
+
+ childIfd, found := ifd.ChildIfdIndex[tag.ChildIfdPath]
+ if found != true {
+ log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdPath)
+ }
+
+ childIfd.printTagTree(populateValues, 0, level+1, false)
+ }
+ }
+
+ if len(ifd.Children) != ifdsFoundCount {
+ log.Panicf("have one or more dangling child IFDs: (%d) != (%d)", len(ifd.Children), ifdsFoundCount)
+ }
+
+ if ifd.NextIfd != nil {
+ ifd.NextIfd.printTagTree(populateValues, index+1, level, true)
+ }
+}
+
+// PrintTagTree prints the IFD hierarchy.
+func (ifd *Ifd) PrintTagTree(populateValues bool) {
+ ifd.printTagTree(populateValues, 0, 0, false)
+}
+
+func (ifd *Ifd) printIfdTree(level int, nextLink bool) {
+ indent := strings.Repeat(" ", level*2)
+
+ prefix := " "
+ if nextLink {
+ prefix = ">"
+ }
+
+ fmt.Printf("%s%s%s\n", indent, prefix, ifd)
+
+ // Now, print the tags while also descending to child-IFDS as we encounter them.
+
+ ifdsFoundCount := 0
+
+ for _, tag := range ifd.Entries {
+ if tag.ChildIfdPath != "" {
+ ifdsFoundCount++
+
+ childIfd, found := ifd.ChildIfdIndex[tag.ChildIfdPath]
+ if found != true {
+ log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdPath)
+ }
+
+ childIfd.printIfdTree(level+1, false)
+ }
+ }
+
+ if len(ifd.Children) != ifdsFoundCount {
+ log.Panicf("have one or more dangling child IFDs: (%d) != (%d)", len(ifd.Children), ifdsFoundCount)
+ }
+
+ if ifd.NextIfd != nil {
+ ifd.NextIfd.printIfdTree(level, true)
+ }
+}
+
+// PrintIfdTree prints the IFD hierarchy.
+func (ifd *Ifd) PrintIfdTree() {
+ ifd.printIfdTree(0, false)
+}
+
+func (ifd *Ifd) dumpTree(tagsDump []string, level int) []string {
+ if tagsDump == nil {
+ tagsDump = make([]string, 0)
+ }
+
+ indent := strings.Repeat(" ", level*2)
+
+ var ifdPhrase string
+ if ifd.ParentIfd != nil {
+ ifdPhrase = fmt.Sprintf("[%s]->[%s]:(%d)", ifd.ParentIfd.IfdPath, ifd.IfdPath, ifd.Index)
+ } else {
+ ifdPhrase = fmt.Sprintf("[ROOT]->[%s]:(%d)", ifd.IfdPath, ifd.Index)
+ }
+
+ startBlurb := fmt.Sprintf("%s> IFD %s TOP", indent, ifdPhrase)
+ tagsDump = append(tagsDump, startBlurb)
+
+ ifdsFoundCount := 0
+ for _, tag := range ifd.Entries {
+ tagsDump = append(tagsDump, fmt.Sprintf("%s - (0x%04x)", indent, tag.TagId))
+
+ if tag.ChildIfdPath != "" {
+ ifdsFoundCount++
+
+ childIfd, found := ifd.ChildIfdIndex[tag.ChildIfdPath]
+ if found != true {
+ log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdPath)
+ }
+
+ tagsDump = childIfd.dumpTree(tagsDump, level+1)
+ }
+ }
+
+ if len(ifd.Children) != ifdsFoundCount {
+ log.Panicf("have one or more dangling child IFDs: (%d) != (%d)", len(ifd.Children), ifdsFoundCount)
+ }
+
+ finishBlurb := fmt.Sprintf("%s< IFD %s BOTTOM", indent, ifdPhrase)
+ tagsDump = append(tagsDump, finishBlurb)
+
+ if ifd.NextIfd != nil {
+ siblingBlurb := fmt.Sprintf("%s* LINKING TO SIBLING IFD [%s]:(%d)", indent, ifd.NextIfd.IfdPath, ifd.NextIfd.Index)
+ tagsDump = append(tagsDump, siblingBlurb)
+
+ tagsDump = ifd.NextIfd.dumpTree(tagsDump, level)
+ }
+
+ return tagsDump
+}
+
+// DumpTree returns a list of strings describing the IFD hierarchy.
+func (ifd *Ifd) DumpTree() []string {
+ return ifd.dumpTree(nil, 0)
+}
+
+// GpsInfo parses and consolidates the GPS info. This can only be called on the
+// GPS IFD.
+func (ifd *Ifd) GpsInfo() (gi *GpsInfo, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ // TODO(dustin): !! Also add functionality to update the GPS info.
+
+ gi = new(GpsInfo)
+
+ if ifd.IfdPath != IfdPathStandardGps {
+ log.Panicf("GPS can only be read on GPS IFD: [%s] != [%s]", ifd.IfdPath, IfdPathStandardGps)
+ }
+
+ if tags, found := ifd.EntriesByTagId[TagVersionId]; found == false {
+ // We've seen this. We'll just have to default to assuming we're in a
+ // 2.2.0.0 format.
+ ifdEnumerateLogger.Warningf(nil, "No GPS version tag (0x%04x) found.", TagVersionId)
+ } else {
+ hit := false
+ for _, acceptedGpsVersion := range ValidGpsVersions {
+ if bytes.Compare(tags[0].value, acceptedGpsVersion[:]) == 0 {
+ hit = true
+ break
+ }
+ }
+
+ if hit != true {
+ ifdEnumerateLogger.Warningf(nil, "GPS version not supported: %v", tags[0].value)
+ log.Panic(ErrNoGpsTags)
+ }
+ }
+
+ tags, found := ifd.EntriesByTagId[TagLatitudeId]
+ if found == false {
+ ifdEnumerateLogger.Warningf(nil, "latitude not found")
+ log.Panic(ErrNoGpsTags)
+ }
+
+ latitudeValue, err := ifd.TagValue(tags[0])
+ log.PanicIf(err)
+
+ // Look for whether North or South.
+ tags, found = ifd.EntriesByTagId[TagLatitudeRefId]
+ if found == false {
+ ifdEnumerateLogger.Warningf(nil, "latitude-ref not found")
+ log.Panic(ErrNoGpsTags)
+ }
+
+ latitudeRefValue, err := ifd.TagValue(tags[0])
+ log.PanicIf(err)
+
+ tags, found = ifd.EntriesByTagId[TagLongitudeId]
+ if found == false {
+ ifdEnumerateLogger.Warningf(nil, "longitude not found")
+ log.Panic(ErrNoGpsTags)
+ }
+
+ longitudeValue, err := ifd.TagValue(tags[0])
+ log.PanicIf(err)
+
+ // Look for whether West or East.
+ tags, found = ifd.EntriesByTagId[TagLongitudeRefId]
+ if found == false {
+ ifdEnumerateLogger.Warningf(nil, "longitude-ref not found")
+ log.Panic(ErrNoGpsTags)
+ }
+
+ longitudeRefValue, err := ifd.TagValue(tags[0])
+ log.PanicIf(err)
+
+ // Parse location.
+
+ latitudeRaw := latitudeValue.([]Rational)
+
+ gi.Latitude = GpsDegrees{
+ Orientation: latitudeRefValue.(string)[0],
+ Degrees: float64(latitudeRaw[0].Numerator) / float64(latitudeRaw[0].Denominator),
+ Minutes: float64(latitudeRaw[1].Numerator) / float64(latitudeRaw[1].Denominator),
+ Seconds: float64(latitudeRaw[2].Numerator) / float64(latitudeRaw[2].Denominator),
+ }
+
+ longitudeRaw := longitudeValue.([]Rational)
+
+ gi.Longitude = GpsDegrees{
+ Orientation: longitudeRefValue.(string)[0],
+ Degrees: float64(longitudeRaw[0].Numerator) / float64(longitudeRaw[0].Denominator),
+ Minutes: float64(longitudeRaw[1].Numerator) / float64(longitudeRaw[1].Denominator),
+ Seconds: float64(longitudeRaw[2].Numerator) / float64(longitudeRaw[2].Denominator),
+ }
+
+ // Parse altitude.
+
+ altitudeTags, foundAltitude := ifd.EntriesByTagId[TagAltitudeId]
+ altitudeRefTags, foundAltitudeRef := ifd.EntriesByTagId[TagAltitudeRefId]
+
+ if foundAltitude == true && foundAltitudeRef == true {
+ altitudeValue, err := ifd.TagValue(altitudeTags[0])
+ log.PanicIf(err)
+
+ altitudeRefValue, err := ifd.TagValue(altitudeRefTags[0])
+ log.PanicIf(err)
+
+ altitudeRaw := altitudeValue.([]Rational)
+ altitude := int(altitudeRaw[0].Numerator / altitudeRaw[0].Denominator)
+ if altitudeRefValue.([]byte)[0] == 1 {
+ altitude *= -1
+ }
+
+ gi.Altitude = altitude
+ }
+
+ // Parse time.
+
+ timestampTags, foundTimestamp := ifd.EntriesByTagId[TagTimestampId]
+ datestampTags, foundDatestamp := ifd.EntriesByTagId[TagDatestampId]
+
+ if foundTimestamp == true && foundDatestamp == true {
+ datestampValue, err := ifd.TagValue(datestampTags[0])
+ log.PanicIf(err)
+
+ dateParts := strings.Split(datestampValue.(string), ":")
+
+ year, err1 := strconv.ParseUint(dateParts[0], 10, 16)
+ month, err2 := strconv.ParseUint(dateParts[1], 10, 8)
+ day, err3 := strconv.ParseUint(dateParts[2], 10, 8)
+
+ if err1 == nil && err2 == nil && err3 == nil {
+ timestampValue, err := ifd.TagValue(timestampTags[0])
+ log.PanicIf(err)
+
+ timestampRaw := timestampValue.([]Rational)
+
+ hour := int(timestampRaw[0].Numerator / timestampRaw[0].Denominator)
+ minute := int(timestampRaw[1].Numerator / timestampRaw[1].Denominator)
+ second := int(timestampRaw[2].Numerator / timestampRaw[2].Denominator)
+
+ gi.Timestamp = time.Date(int(year), time.Month(month), int(day), hour, minute, second, 0, time.UTC)
+ }
+ }
+
+ return gi, nil
+}
+
+type ParsedTagVisitor func(*Ifd, *IfdTagEntry) error
+
+func (ifd *Ifd) EnumerateTagsRecursively(visitor ParsedTagVisitor) (err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ for ptr := ifd; ptr != nil; ptr = ptr.NextIfd {
+ for _, ite := range ifd.Entries {
+ if ite.ChildIfdPath != "" {
+ childIfd := ifd.ChildIfdIndex[ite.ChildIfdPath]
+
+ err := childIfd.EnumerateTagsRecursively(visitor)
+ log.PanicIf(err)
+ } else {
+ err := visitor(ifd, ite)
+ log.PanicIf(err)
+ }
+ }
+ }
+
+ return nil
+}
+
+func (ifd *Ifd) GetValueContext(ite *IfdTagEntry) *ValueContext {
+ return newValueContextFromTag(
+ ite,
+ ifd.addressableData,
+ ifd.ByteOrder)
+}
+
+type QueuedIfd struct {
+ Name string
+ IfdPath string
+ FqIfdPath string
+
+ TagId uint16
+
+ Index int
+ Offset uint32
+ Parent *Ifd
+
+ // ParentTagIndex is our tag position in the parent IFD, if we had a parent
+ // (if `ParentIfd` is not nil and we weren't an IFD referenced as a sibling
+ // instead of as a child).
+ ParentTagIndex int
+}
+
+type IfdIndex struct {
+ RootIfd *Ifd
+ Ifds []*Ifd
+ Tree map[int]*Ifd
+ Lookup map[string][]*Ifd
+}
+
+// Scan enumerates the different EXIF blocks (called IFDs).
+func (ie *IfdEnumerate) Collect(rootIfdOffset uint32, resolveValues bool) (index IfdIndex, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ tree := make(map[int]*Ifd)
+ ifds := make([]*Ifd, 0)
+ lookup := make(map[string][]*Ifd)
+
+ queue := []QueuedIfd{
+ {
+ Name: IfdStandard,
+ IfdPath: IfdStandard,
+ FqIfdPath: IfdStandard,
+
+ TagId: 0xffff,
+
+ Index: 0,
+ Offset: rootIfdOffset,
+ },
+ }
+
+ edges := make(map[uint32]*Ifd)
+
+ for {
+ if len(queue) == 0 {
+ break
+ }
+
+ qi := queue[0]
+
+ name := qi.Name
+ ifdPath := qi.IfdPath
+ fqIfdPath := qi.FqIfdPath
+
+ index := qi.Index
+ offset := qi.Offset
+ parentIfd := qi.Parent
+
+ queue = queue[1:]
+
+ ifdEnumerateLogger.Debugf(nil, "Parsing IFD [%s] (%d) at offset (%04x).", ifdPath, index, offset)
+ ite := ie.getTagEnumerator(offset)
+
+ nextIfdOffset, entries, thumbnailData, err := ie.ParseIfd(fqIfdPath, index, ite, nil, false, resolveValues)
+ log.PanicIf(err)
+
+ 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{
+ addressableData: ie.exifData[ExifAddressableAreaStart:],
+
+ ByteOrder: ie.byteOrder,
+
+ Name: name,
+ IfdPath: ifdPath,
+ FqIfdPath: fqIfdPath,
+
+ TagId: qi.TagId,
+
+ Id: id,
+
+ ParentIfd: parentIfd,
+ ParentTagIndex: qi.ParentTagIndex,
+
+ Index: index,
+ Offset: offset,
+ Entries: entries,
+ EntriesByTagId: entriesByTagId,
+
+ // This is populated as each child is processed.
+ Children: make([]*Ifd, 0),
+
+ NextIfdOffset: nextIfdOffset,
+ thumbnailData: thumbnailData,
+
+ ifdMapping: ie.ifdMapping,
+ tagIndex: ie.tagIndex,
+ }
+
+ // Add ourselves to a big list of IFDs.
+ ifds = append(ifds, ifd)
+
+ // Install ourselves into a by-id lookup table (keys are unique).
+ tree[id] = ifd
+
+ // Install into by-name buckets.
+
+ if list_, found := lookup[ifdPath]; found == true {
+ lookup[ifdPath] = append(list_, ifd)
+ } else {
+ list_ = make([]*Ifd, 1)
+ list_[0] = ifd
+
+ lookup[ifdPath] = list_
+ }
+
+ // Add a link from the previous IFD in the chain to us.
+ if previousIfd, found := edges[offset]; found == true {
+ previousIfd.NextIfd = ifd
+ }
+
+ // Attach as a child to our parent (where we appeared as a tag in
+ // that IFD).
+ if parentIfd != nil {
+ parentIfd.Children = append(parentIfd.Children, ifd)
+ }
+
+ // Determine if any of our entries is a child IFD and queue it.
+ for i, entry := range entries {
+ if entry.ChildIfdPath == "" {
+ continue
+ }
+
+ qi := QueuedIfd{
+ Name: entry.ChildIfdName,
+ IfdPath: entry.ChildIfdPath,
+ FqIfdPath: entry.ChildFqIfdPath,
+ TagId: entry.TagId,
+
+ Index: 0,
+ Offset: entry.ValueOffset,
+ Parent: ifd,
+ ParentTagIndex: i,
+ }
+
+ queue = append(queue, qi)
+ }
+
+ // If there's another IFD in the chain.
+ if nextIfdOffset != 0 {
+ // Allow the next link to know what the previous link was.
+ edges[nextIfdOffset] = ifd
+
+ siblingIndex := index + 1
+
+ var fqIfdPath string
+ if parentIfd != nil {
+ fqIfdPath = fmt.Sprintf("%s/%s%d", parentIfd.FqIfdPath, name, siblingIndex)
+ } else {
+ fqIfdPath = fmt.Sprintf("%s%d", name, siblingIndex)
+ }
+
+ qi := QueuedIfd{
+ Name: name,
+ IfdPath: ifdPath,
+ FqIfdPath: fqIfdPath,
+ TagId: 0xffff,
+ Index: siblingIndex,
+ Offset: nextIfdOffset,
+ }
+
+ queue = append(queue, qi)
+ }
+ }
+
+ index.RootIfd = tree[0]
+ index.Ifds = ifds
+ index.Tree = tree
+ index.Lookup = lookup
+
+ err = ie.setChildrenIndex(index.RootIfd)
+ log.PanicIf(err)
+
+ return index, nil
+}
+
+func (ie *IfdEnumerate) setChildrenIndex(ifd *Ifd) (err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ childIfdIndex := make(map[string]*Ifd)
+ for _, childIfd := range ifd.Children {
+ childIfdIndex[childIfd.IfdPath] = childIfd
+ }
+
+ ifd.ChildIfdIndex = childIfdIndex
+
+ for _, childIfd := range ifd.Children {
+ err := ie.setChildrenIndex(childIfd)
+ log.PanicIf(err)
+ }
+
+ return nil
+}
+
+// ParseOneIfd is a hack to use an IE to parse a raw IFD block. Can be used for
+// testing.
+func ParseOneIfd(ifdMapping *IfdMapping, tagIndex *TagIndex, fqIfdPath, ifdPath string, byteOrder binary.ByteOrder, ifdBlock []byte, visitor RawTagVisitor, resolveValues bool) (nextIfdOffset uint32, entries []*IfdTagEntry, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ ie := NewIfdEnumerate(ifdMapping, tagIndex, make([]byte, 0), byteOrder)
+ ite := NewIfdTagEnumerator(ifdBlock, byteOrder, 0)
+
+ nextIfdOffset, entries, _, err = ie.ParseIfd(fqIfdPath, 0, ite, visitor, true, resolveValues)
+ log.PanicIf(err)
+
+ return nextIfdOffset, entries, nil
+}
+
+// ParseOneTag is a hack to use an IE to parse a raw tag block.
+func ParseOneTag(ifdMapping *IfdMapping, tagIndex *TagIndex, fqIfdPath, ifdPath string, byteOrder binary.ByteOrder, tagBlock []byte, resolveValue bool) (tag *IfdTagEntry, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ ie := NewIfdEnumerate(ifdMapping, tagIndex, make([]byte, 0), byteOrder)
+ ite := NewIfdTagEnumerator(tagBlock, byteOrder, 0)
+
+ tag, err = ie.parseTag(fqIfdPath, 0, ite, resolveValue)
+ log.PanicIf(err)
+
+ return tag, nil
+}
+
+func FindIfdFromRootIfd(rootIfd *Ifd, ifdPath string) (ifd *Ifd, err error) {
+ defer func() {
+ if state := recover(); state != nil {
+ err = log.Wrap(state.(error))
+ }
+ }()
+
+ // TODO(dustin): !! Add test.
+
+ lineage, err := rootIfd.ifdMapping.ResolvePath(ifdPath)
+ log.PanicIf(err)
+
+ // Confirm the first IFD is our root IFD type, and then prune it because
+ // from then on we'll be searching down through our children.
+
+ if len(lineage) == 0 {
+ log.Panicf("IFD path must be non-empty.")
+ } else if lineage[0].Name != IfdStandard {
+ log.Panicf("First IFD path item must be [%s].", IfdStandard)
+ }
+
+ desiredRootIndex := lineage[0].Index
+ lineage = lineage[1:]
+
+ // TODO(dustin): !! This is a poorly conceived fix that just doubles the work we already have to do below, which then interacts badly with the indices not being properly represented in the IFD-phrase.
+ // TODO(dustin): !! <-- However, we're not sure whether we shouldn't store a secondary IFD-path with the indices. Some IFDs may not necessarily restrict which IFD indices they can be a child of (only the IFD itself matters). Validation should be delegated to the caller.
+ thisIfd := rootIfd
+ for currentRootIndex := 0; currentRootIndex < desiredRootIndex; currentRootIndex++ {
+ if thisIfd.NextIfd == nil {
+ log.Panicf("Root-IFD index (%d) does not exist in the data.", currentRootIndex)
+ }
+
+ thisIfd = thisIfd.NextIfd
+ }
+
+ for i, itii := range lineage {
+ var hit *Ifd
+ for _, childIfd := range thisIfd.Children {
+ if childIfd.TagId == itii.TagId {
+ hit = childIfd
+ break
+ }
+ }
+
+ // If we didn't find the child, add it.
+ if hit == nil {
+ log.Panicf("IFD [%s] in [%s] not found: %s", itii.Name, ifdPath, thisIfd.Children)
+ }
+
+ thisIfd = hit
+
+ // If we didn't find the sibling, add it.
+ for i = 0; i < itii.Index; i++ {
+ if thisIfd.NextIfd == nil {
+ log.Panicf("IFD [%s] does not have (%d) occurrences/siblings\n", thisIfd.IfdPath, itii.Index)
+ }
+
+ thisIfd = thisIfd.NextIfd
+ }
+ }
+
+ return thisIfd, nil
+}