diff options
Diffstat (limited to 'vendor/github.com/dsoprea/go-exif/v3/ifd_enumerate.go')
-rw-r--r-- | vendor/github.com/dsoprea/go-exif/v3/ifd_enumerate.go | 1672 |
1 files changed, 0 insertions, 1672 deletions
diff --git a/vendor/github.com/dsoprea/go-exif/v3/ifd_enumerate.go b/vendor/github.com/dsoprea/go-exif/v3/ifd_enumerate.go deleted file mode 100644 index 3167596ef..000000000 --- a/vendor/github.com/dsoprea/go-exif/v3/ifd_enumerate.go +++ /dev/null @@ -1,1672 +0,0 @@ -package exif - -import ( - "bytes" - "errors" - "fmt" - "io" - "strconv" - "strings" - "time" - - "encoding/binary" - - "github.com/dsoprea/go-logging" - - "github.com/dsoprea/go-exif/v3/common" - "github.com/dsoprea/go-exif/v3/undefined" -) - -var ( - ifdEnumerateLogger = log.NewLogger("exif.ifd_enumerate") -) - -var ( - // ErrNoThumbnail means that no thumbnail was found. - ErrNoThumbnail = errors.New("no thumbnail") - - // ErrNoGpsTags means that no GPS info was found. - ErrNoGpsTags = errors.New("no gps tags") - - // ErrTagTypeNotValid means that the tag-type is not valid. - ErrTagTypeNotValid = errors.New("tag type invalid") - - // ErrOffsetInvalid means that the file offset is not valid. - ErrOffsetInvalid = errors.New("file offset invalid") -) - -var ( - // ValidGpsVersions is the list of recognized EXIF GPS versions/signatures. - ValidGpsVersions = [][4]byte{ - // 2.0.0.0 appears to have a very similar format to 2.2.0.0, so enabling - // it under that assumption. - // - // IFD-PATH=[IFD] ID=(0x8825) NAME=[GPSTag] COUNT=(1) TYPE=[LONG] VALUE=[114] - // IFD-PATH=[IFD/GPSInfo] ID=(0x0000) NAME=[GPSVersionID] COUNT=(4) TYPE=[BYTE] VALUE=[02 00 00 00] - // IFD-PATH=[IFD/GPSInfo] ID=(0x0001) NAME=[GPSLatitudeRef] COUNT=(2) TYPE=[ASCII] VALUE=[S] - // IFD-PATH=[IFD/GPSInfo] ID=(0x0002) NAME=[GPSLatitude] COUNT=(3) TYPE=[RATIONAL] VALUE=[38/1...] - // IFD-PATH=[IFD/GPSInfo] ID=(0x0003) NAME=[GPSLongitudeRef] COUNT=(2) TYPE=[ASCII] VALUE=[E] - // IFD-PATH=[IFD/GPSInfo] ID=(0x0004) NAME=[GPSLongitude] COUNT=(3) TYPE=[RATIONAL] VALUE=[144/1...] - // IFD-PATH=[IFD/GPSInfo] ID=(0x0012) NAME=[GPSMapDatum] COUNT=(7) TYPE=[ASCII] VALUE=[WGS-84] - // - {2, 0, 0, 0}, - - {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}, - } -) - -// byteParser 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 byteParser struct { - byteOrder binary.ByteOrder - rs io.ReadSeeker - ifdOffset uint32 - currentOffset uint32 -} - -// newByteParser returns a new byteParser struct. -// -// initialOffset is for arithmetic-based tracking of where we should be at in -// the stream. -func newByteParser(rs io.ReadSeeker, byteOrder binary.ByteOrder, initialOffset uint32) (bp *byteParser, err error) { - // TODO(dustin): Add test - - bp = &byteParser{ - rs: rs, - byteOrder: byteOrder, - currentOffset: initialOffset, - } - - return bp, nil -} - -// 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 (bp *byteParser) getUint16() (value uint16, raw []byte, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - // TODO(dustin): Add test - - needBytes := 2 - - raw = make([]byte, needBytes) - - _, err = io.ReadFull(bp.rs, raw) - log.PanicIf(err) - - value = bp.byteOrder.Uint16(raw) - - bp.currentOffset += uint32(needBytes) - - 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 (bp *byteParser) getUint32() (value uint32, raw []byte, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - // TODO(dustin): Add test - - needBytes := 4 - - raw = make([]byte, needBytes) - - _, err = io.ReadFull(bp.rs, raw) - log.PanicIf(err) - - value = bp.byteOrder.Uint32(raw) - - bp.currentOffset += uint32(needBytes) - - return value, raw, nil -} - -// CurrentOffset returns the starting offset but the number of bytes that we -// have parsed. This is arithmetic-based tracking, not a seek(0) operation. -func (bp *byteParser) CurrentOffset() uint32 { - return bp.currentOffset -} - -// IfdEnumerate is the main enumeration type. It knows how to parse the IFD -// containers in the EXIF blob. -type IfdEnumerate struct { - ebs ExifBlobSeeker - byteOrder binary.ByteOrder - tagIndex *TagIndex - ifdMapping *exifcommon.IfdMapping - furthestOffset uint32 - - visitedIfdOffsets map[uint32]struct{} -} - -// NewIfdEnumerate returns a new instance of IfdEnumerate. -func NewIfdEnumerate(ifdMapping *exifcommon.IfdMapping, tagIndex *TagIndex, ebs ExifBlobSeeker, byteOrder binary.ByteOrder) *IfdEnumerate { - return &IfdEnumerate{ - ebs: ebs, - byteOrder: byteOrder, - ifdMapping: ifdMapping, - tagIndex: tagIndex, - - visitedIfdOffsets: make(map[uint32]struct{}), - } -} - -func (ie *IfdEnumerate) getByteParser(ifdOffset uint32) (bp *byteParser, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - initialOffset := ExifAddressableAreaStart + ifdOffset - - rs, err := ie.ebs.GetReadSeeker(int64(initialOffset)) - log.PanicIf(err) - - bp, err = - newByteParser( - rs, - ie.byteOrder, - initialOffset) - - if err != nil { - if err == ErrOffsetInvalid { - return nil, err - } - - log.Panic(err) - } - - return bp, nil -} - -func (ie *IfdEnumerate) parseTag(ii *exifcommon.IfdIdentity, tagPosition int, bp *byteParser) (ite *IfdTagEntry, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - tagId, _, err := bp.getUint16() - log.PanicIf(err) - - tagTypeRaw, _, err := bp.getUint16() - log.PanicIf(err) - - tagType := exifcommon.TagTypePrimitive(tagTypeRaw) - - unitCount, _, err := bp.getUint32() - log.PanicIf(err) - - valueOffset, rawValueOffset, err := bp.getUint32() - log.PanicIf(err) - - // Check whether the embedded type indicator is valid. - - if tagType.IsValid() == false { - // Technically, we have the type on-file in the tags-index, but - // if the type stored alongside the data disagrees with it, - // which it apparently does, all bets are off. - ifdEnumerateLogger.Warningf(nil, - "Tag (0x%04x) in IFD [%s] at position (%d) has invalid type (0x%04x) and will be skipped.", - tagId, ii, tagPosition, int(tagType)) - - ite = &IfdTagEntry{ - tagId: tagId, - tagType: tagType, - } - - return ite, ErrTagTypeNotValid - } - - // Check whether the embedded type is listed among the supported types for - // the registered tag. If not, skip processing the tag. - - it, err := ie.tagIndex.Get(ii, tagId) - if err != nil { - if log.Is(err, ErrTagNotFound) == true { - ifdEnumerateLogger.Warningf(nil, "Tag (0x%04x) is not known and will be skipped.", tagId) - - ite = &IfdTagEntry{ - tagId: tagId, - } - - return ite, ErrTagNotFound - } - - log.Panic(err) - } - - // 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 - // suddenly have data that we no longer manipulate correctly/ - // accurately. - ifdEnumerateLogger.Warningf(nil, - "Tag (0x%04x) in IFD [%s] at position (%d) has unsupported type (0x%02x) and will be skipped.", - tagId, ii, tagPosition, int(tagType)) - - return nil, ErrTagTypeNotValid - } - - // Construct tag struct. - - rs, err := ie.ebs.GetReadSeeker(0) - log.PanicIf(err) - - ite = newIfdTagEntry( - ii, - tagId, - tagPosition, - tagType, - unitCount, - valueOffset, - rawValueOffset, - rs, - ie.byteOrder) - - ifdPath := ii.UnindexedString() - - // 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 { - currentIfdTag := ii.IfdTag() - - childIt := exifcommon.NewIfdTag(¤tIfdTag, tagId, mi.Name) - iiChild := ii.NewChild(childIt, 0) - ite.SetChildIfd(iiChild) - - // 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, exifcommon.ErrChildIfdNotMapped) == false { - log.Panic(err) - } - - return ite, nil -} - -// TagVisitorFn is called for each tag when enumerating through the EXIF. -type TagVisitorFn func(ite *IfdTagEntry) (err error) - -// tagPostParse do some tag-level processing here following the parse of each. -func (ie *IfdEnumerate) tagPostParse(ite *IfdTagEntry, med *MiscellaneousExifData) (err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - // TODO(dustin): Add test - - ii := ite.IfdIdentity() - - tagId := ite.TagId() - tagType := ite.TagType() - - it, err := ie.tagIndex.Get(ii, tagId) - if err == nil { - ite.setTagName(it.Name) - } else { - if err != ErrTagNotFound { - log.Panic(err) - } - - // This is an unknown tag. - - originalBt := exifcommon.BasicTag{ - FqIfdPath: ii.String(), - IfdPath: ii.UnindexedString(), - TagId: tagId, - } - - if med != nil { - med.unknownTags[originalBt] = exifcommon.BasicTag{} - } - - utilityLogger.Debugf(nil, - "Tag (0x%04x) is not valid for IFD [%s]. Attempting secondary "+ - "lookup.", tagId, ii.String()) - - // This will overwrite the existing `it` and `err`. Since `FindFirst()` - // might generate different Errors than `Get()`, the log message above - // is import to try and mitigate confusion in that case. - it, err = ie.tagIndex.FindFirst(tagId, tagType, nil) - if err != nil { - if err != ErrTagNotFound { - log.Panic(err) - } - - // This is supposed to be a convenience function and if we were - // to keep the name empty or set it to some placeholder, it - // might be mismanaged by the package that is calling us. If - // they want to specifically manage these types of tags, they - // can use more advanced functionality to specifically -handle - // unknown tags. - utilityLogger.Warningf(nil, - "Tag with ID (0x%04x) in IFD [%s] is not recognized and "+ - "will be ignored.", tagId, ii.String()) - - return ErrTagNotFound - } - - ite.setTagName(it.Name) - - utilityLogger.Warningf(nil, - "Tag with ID (0x%04x) is not valid for IFD [%s], but it *is* "+ - "valid as tag [%s] under IFD [%s] and has the same type "+ - "[%s], so we will use that. This EXIF blob was probably "+ - "written by a buggy implementation.", - tagId, ii.UnindexedString(), it.Name, it.IfdPath, - tagType) - - if med != nil { - med.unknownTags[originalBt] = exifcommon.BasicTag{ - IfdPath: it.IfdPath, - TagId: tagId, - } - } - } - - // This is a known tag (from the standard, unless the user did - // something different). - - // Skip any tags that have a type that doesn't match the type in the - // index (which is loaded with the standard and accept tag - // information unless configured otherwise). - // - // We've run into multiple instances of the same tag, where a) no - // 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 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, - tagType, it.SupportedTypes) - - return ErrTagNotFound - } - - return nil -} - -// parseIfd decodes the IFD block that we're currently sitting on the first -// byte of. -func (ie *IfdEnumerate) parseIfd(ii *exifcommon.IfdIdentity, bp *byteParser, visitor TagVisitorFn, doDescend bool, med *MiscellaneousExifData) (nextIfdOffset uint32, entries []*IfdTagEntry, thumbnailData []byte, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - tagCount, _, err := bp.getUint16() - log.PanicIf(err) - - ifdEnumerateLogger.Debugf(nil, "IFD [%s] tag-count: (%d)", ii.String(), tagCount) - - entries = make([]*IfdTagEntry, 0) - - var enumeratorThumbnailOffset *IfdTagEntry - var enumeratorThumbnailSize *IfdTagEntry - - for i := 0; i < int(tagCount); i++ { - ite, err := ie.parseTag(ii, i, bp) - if err != nil { - if log.Is(err, ErrTagNotFound) == true || log.Is(err, ErrTagTypeNotValid) == true { - // These tags should've been fully logged in parseTag(). The - // ITE returned is nil so we can't print anything about them, now. - continue - } - - log.Panic(err) - } - - err = ie.tagPostParse(ite, med) - if err == nil { - if err == ErrTagNotFound { - continue - } - - log.PanicIf(err) - } - - tagId := ite.TagId() - - if visitor != nil { - err := visitor(ite) - log.PanicIf(err) - } - - if ite.IsThumbnailOffset() == true { - ifdEnumerateLogger.Debugf(nil, "Skipping the thumbnail offset tag (0x%04x). Use accessors to get it or set it.", tagId) - - enumeratorThumbnailOffset = ite - entries = append(entries, ite) - - continue - } else if ite.IsThumbnailSize() == true { - ifdEnumerateLogger.Debugf(nil, "Skipping the thumbnail size tag (0x%04x). Use accessors to get it or set it.", tagId) - - enumeratorThumbnailSize = ite - entries = append(entries, ite) - - continue - } - - if ite.TagType() != exifcommon.TypeUndefined { - // If this tag's value is an offset, bump our max-offset value to - // what that offset is plus however large that value is. - - vc := ite.getValueContext() - - farOffset, err := vc.GetFarOffset() - if err == nil { - candidateOffset := farOffset + uint32(vc.SizeInBytes()) - if candidateOffset > ie.furthestOffset { - ie.furthestOffset = candidateOffset - } - } else if err != exifcommon.ErrNotFarValue { - 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 ite.ChildIfdPath() != "" { - if doDescend == true { - ifdEnumerateLogger.Debugf(nil, "Descending from IFD [%s] to IFD [%s].", ii, ite.ChildIfdPath()) - - currentIfdTag := ii.IfdTag() - - childIfdTag := - exifcommon.NewIfdTag( - ¤tIfdTag, - ite.TagId(), - ite.ChildIfdName()) - - iiChild := ii.NewChild(childIfdTag, 0) - - err := ie.scan(iiChild, ite.getValueOffset(), visitor, med) - log.PanicIf(err) - - ifdEnumerateLogger.Debugf(nil, "Ascending from IFD [%s] to IFD [%s].", ite.ChildIfdPath(), ii) - } - } - - entries = append(entries, ite) - } - - if enumeratorThumbnailOffset != nil && enumeratorThumbnailSize != nil { - thumbnailData, err = ie.parseThumbnail(enumeratorThumbnailOffset, enumeratorThumbnailSize) - if err != nil { - ifdEnumerateLogger.Errorf( - nil, err, - "We tried to bump our furthest-offset counter but there was an issue first seeking past the thumbnail.") - } else { - // In this case, the value is always an offset. - offset := enumeratorThumbnailOffset.getValueOffset() - - // This this case, the value is always a length. - length := enumeratorThumbnailSize.getValueOffset() - - ifdEnumerateLogger.Debugf(nil, "Found thumbnail in IFD [%s]. Its offset is (%d) and is (%d) bytes.", ii, offset, length) - - furthestOffset := offset + length - - if furthestOffset > ie.furthestOffset { - ie.furthestOffset = furthestOffset - } - } - } - - nextIfdOffset, _, err = bp.getUint32() - log.PanicIf(err) - - _, alreadyVisited := ie.visitedIfdOffsets[nextIfdOffset] - - if alreadyVisited == true { - ifdEnumerateLogger.Warningf(nil, "IFD at offset (0x%08x) has been linked-to more than once. There might be a cycle in the IFD chain. Not reparsing.", nextIfdOffset) - nextIfdOffset = 0 - } - - if nextIfdOffset != 0 { - ie.visitedIfdOffsets[nextIfdOffset] = struct{}{} - ifdEnumerateLogger.Debugf(nil, "[%s] Next IFD at offset: (0x%08x)", ii.String(), nextIfdOffset) - } else { - ifdEnumerateLogger.Debugf(nil, "[%s] IFD chain has terminated.", ii.String()) - } - - 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)) - } - }() - - vRaw, err := lengthIte.Value() - 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.updateTagType(exifcommon.TypeByte) - offsetIte.updateUnitCount(length) - - thumbnailData, err = offsetIte.GetRawBytes() - log.PanicIf(err) - - return thumbnailData, nil -} - -// scan parses and enumerates the different IFD blocks and invokes a visitor -// callback for each tag. No information is kept or returned. -func (ie *IfdEnumerate) scan(iiGeneral *exifcommon.IfdIdentity, ifdOffset uint32, visitor TagVisitorFn, med *MiscellaneousExifData) (err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - // TODO(dustin): Add test - - for ifdIndex := 0; ; ifdIndex++ { - iiSibling := iiGeneral.NewSibling(ifdIndex) - - ifdEnumerateLogger.Debugf(nil, "Parsing IFD [%s] at offset (0x%04x) (scan).", iiSibling.String(), ifdOffset) - - bp, err := ie.getByteParser(ifdOffset) - if err != nil { - if err == ErrOffsetInvalid { - ifdEnumerateLogger.Errorf(nil, nil, "IFD [%s] at offset (0x%04x) is unreachable. Terminating scan.", iiSibling.String(), ifdOffset) - break - } - - log.Panic(err) - } - - nextIfdOffset, _, _, err := ie.parseIfd(iiSibling, bp, visitor, true, med) - log.PanicIf(err) - - currentOffset := bp.CurrentOffset() - if currentOffset > ie.furthestOffset { - ie.furthestOffset = currentOffset - } - - if nextIfdOffset == 0 { - break - } - - ifdOffset = nextIfdOffset - } - - return nil -} - -// MiscellaneousExifData is reports additional data collected during the parse. -type MiscellaneousExifData struct { - // UnknownTags contains all tags that were invalid for their containing - // IFDs. The values represent alternative IFDs that were correctly matched - // to those tags and used instead. - unknownTags map[exifcommon.BasicTag]exifcommon.BasicTag -} - -// UnknownTags returns the unknown tags encountered during the scan. -func (med *MiscellaneousExifData) UnknownTags() map[exifcommon.BasicTag]exifcommon.BasicTag { - return med.unknownTags -} - -// ScanOptions tweaks parser behavior/choices. -type ScanOptions struct { - // NOTE(dustin): Reserved for future usage. -} - -// Scan enumerates the different EXIF blocks (called IFDs). `rootIfdName` will -// be "IFD" in the TIFF standard. -func (ie *IfdEnumerate) Scan(iiRoot *exifcommon.IfdIdentity, ifdOffset uint32, visitor TagVisitorFn, so *ScanOptions) (med *MiscellaneousExifData, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - // TODO(dustin): Add test - - med = &MiscellaneousExifData{ - unknownTags: make(map[exifcommon.BasicTag]exifcommon.BasicTag), - } - - err = ie.scan(iiRoot, ifdOffset, visitor, med) - log.PanicIf(err) - - ifdEnumerateLogger.Debugf(nil, "Scan: It looks like the furthest offset that contained EXIF data in the EXIF blob was (%d) (Scan).", ie.FurthestOffset()) - - return med, nil -} - -// Ifd represents a single, parsed IFD. -type Ifd struct { - ifdIdentity *exifcommon.IfdIdentity - - ifdMapping *exifcommon.IfdMapping - tagIndex *TagIndex - - offset uint32 - byteOrder binary.ByteOrder - 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 - - entries []*IfdTagEntry - entriesByTagId map[uint16][]*IfdTagEntry - - children []*Ifd - childIfdIndex map[string]*Ifd - - thumbnailData []byte - - nextIfdOffset uint32 - nextIfd *Ifd -} - -// IfdIdentity returns IFD identity that this struct represents. -func (ifd *Ifd) IfdIdentity() *exifcommon.IfdIdentity { - return ifd.ifdIdentity -} - -// Entries returns a flat list of all tags for this IFD. -func (ifd *Ifd) Entries() []*IfdTagEntry { - - // TODO(dustin): Add test - - return ifd.entries -} - -// EntriesByTagId returns a map of all tags for this IFD. -func (ifd *Ifd) EntriesByTagId() map[uint16][]*IfdTagEntry { - - // TODO(dustin): Add test - - return ifd.entriesByTagId -} - -// Children returns a flat list of all child IFDs of this IFD. -func (ifd *Ifd) Children() []*Ifd { - - // TODO(dustin): Add test - - return ifd.children -} - -// ChildWithIfdPath returns a map of all child IFDs of this IFD. -func (ifd *Ifd) ChildIfdIndex() map[string]*Ifd { - - // TODO(dustin): Add test - - return ifd.childIfdIndex -} - -// ParentTagIndex returns the position of this IFD's tag in its parent IFD (*if* -// there is a parent). -func (ifd *Ifd) ParentTagIndex() int { - - // TODO(dustin): Add test - - return ifd.parentTagIndex -} - -// Offset returns the offset of the IFD in the stream. -func (ifd *Ifd) Offset() uint32 { - - // TODO(dustin): Add test - - return ifd.offset -} - -// Offset returns the offset of the IFD in the stream. -func (ifd *Ifd) ByteOrder() binary.ByteOrder { - - // TODO(dustin): Add test - - return ifd.byteOrder -} - -// NextIfd returns the Ifd struct for the next IFD in the chain. -func (ifd *Ifd) NextIfd() *Ifd { - - // TODO(dustin): Add test - - return ifd.nextIfd -} - -// ChildWithIfdPath returns an `Ifd` struct for the given child of the current -// IFD. -func (ifd *Ifd) ChildWithIfdPath(iiChild *exifcommon.IfdIdentity) (childIfd *Ifd, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - // TODO(dustin): This is a bridge while we're introducing the IFD type-system. We should be able to use the (IfdIdentity).Equals() method for this. - ifdPath := iiChild.UnindexedString() - - for _, childIfd := range ifd.children { - if childIfd.ifdIdentity.UnindexedString() == ifdPath { - return childIfd, nil - } - } - - log.Panic(ErrTagNotFound) - return nil, 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.ifdIdentity, tagName) - if log.Is(err, ErrTagNotFound) == true { - log.Panic(ErrTagNotKnown) - } 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 -} - -// String returns a description string. -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.ifdIdentity.UnindexedString(), ifd.ifdIdentity.Index(), len(ifd.entries), ifd.offset, len(ifd.children), parentOffset, ifd.nextIfdOffset) -} - -// Thumbnail returns the raw thumbnail bytes. This is typically directly -// readable by any standard image viewer. -func (ifd *Ifd) Thumbnail() (data []byte, err error) { - - if ifd.thumbnailData == nil { - return nil, ErrNoThumbnail - } - - return ifd.thumbnailData, nil -} - -// dumpTags recursively builds a list of tags from an IFD. -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 _, ite := range ifd.entries { - tags = append(tags, ite) - - childIfdPath := ite.ChildIfdPath() - if childIfdPath != "" { - ifdsFoundCount++ - - childIfd, found := ifd.childIfdIndex[childIfdPath] - if found != true { - log.Panicf("alien child IFD referenced by a tag: [%s]", 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 _, ite := range ifd.entries { - if ite.ChildIfdPath() != "" { - fmt.Printf("%s - TAG: %s\n", indent, ite) - } else { - // This will just add noise to the output (byte-tags are fully - // dumped). - if ite.IsThumbnailOffset() == true || ite.IsThumbnailSize() == true { - continue - } - - it, err := ifd.tagIndex.Get(ifd.ifdIdentity, ite.TagId()) - - tagName := "" - if err == nil { - tagName = it.Name - } - - var valuePhrase string - if populateValues == true { - var err error - - valuePhrase, err = ite.Format() - if err != nil { - if log.Is(err, exifcommon.ErrUnhandledUndefinedTypedTag) == true { - ifdEnumerateLogger.Warningf(nil, "Skipping non-standard undefined tag: [%s] (%04x)", ifd.ifdIdentity.UnindexedString(), ite.TagId()) - continue - } else if err == exifundefined.ErrUnparseableValue { - ifdEnumerateLogger.Warningf(nil, "Skipping unparseable undefined tag: [%s] (%04x) [%s]", ifd.ifdIdentity.UnindexedString(), ite.TagId(), it.Name) - continue - } - - log.Panic(err) - } - } else { - valuePhrase = "!UNRESOLVED" - } - - fmt.Printf("%s - TAG: %s NAME=[%s] VALUE=[%v]\n", indent, ite, tagName, valuePhrase) - } - - childIfdPath := ite.ChildIfdPath() - if childIfdPath != "" { - ifdsFoundCount++ - - childIfd, found := ifd.childIfdIndex[childIfdPath] - if found != true { - log.Panicf("alien child IFD referenced by a tag: [%s]", 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 _, ite := range ifd.entries { - childIfdPath := ite.ChildIfdPath() - if childIfdPath != "" { - ifdsFoundCount++ - - childIfd, found := ifd.childIfdIndex[childIfdPath] - if found != true { - log.Panicf("alien child IFD referenced by a tag: [%s]", 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.ifdIdentity.UnindexedString(), ifd.ifdIdentity.UnindexedString(), ifd.ifdIdentity.Index()) - } else { - ifdPhrase = fmt.Sprintf("[ROOT]->[%s]:(%d)", ifd.ifdIdentity.UnindexedString(), ifd.ifdIdentity.Index()) - } - - startBlurb := fmt.Sprintf("%s> IFD %s TOP", indent, ifdPhrase) - tagsDump = append(tagsDump, startBlurb) - - ifdsFoundCount := 0 - for _, ite := range ifd.entries { - tagsDump = append(tagsDump, fmt.Sprintf("%s - (0x%04x)", indent, ite.TagId())) - - childIfdPath := ite.ChildIfdPath() - if childIfdPath != "" { - ifdsFoundCount++ - - childIfd, found := ifd.childIfdIndex[childIfdPath] - if found != true { - log.Panicf("alien child IFD referenced by a tag: [%s]", 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.ifdIdentity.UnindexedString(), ifd.nextIfd.ifdIdentity.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)) - } - }() - - gi = new(GpsInfo) - - if ifd.ifdIdentity.Equals(exifcommon.IfdGpsInfoStandardIfdIdentity) == false { - log.Panicf("GPS can only be read on GPS IFD: [%s]", ifd.ifdIdentity.UnindexedString()) - } - - if tags, found := ifd.entriesByTagId[TagGpsVersionId]; 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.", TagGpsVersionId) - } else { - versionBytes, err := tags[0].GetRawBytes() - log.PanicIf(err) - - hit := false - for _, acceptedGpsVersion := range ValidGpsVersions { - if bytes.Compare(versionBytes, acceptedGpsVersion[:]) == 0 { - hit = true - break - } - } - - if hit != true { - ifdEnumerateLogger.Warningf(nil, "GPS version not supported: %v", versionBytes) - log.Panic(ErrNoGpsTags) - } - } - - tags, found := ifd.entriesByTagId[TagLatitudeId] - if found == false { - ifdEnumerateLogger.Warningf(nil, "latitude not found") - log.Panic(ErrNoGpsTags) - } - - latitudeValue, err := tags[0].Value() - 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 := tags[0].Value() - log.PanicIf(err) - - tags, found = ifd.entriesByTagId[TagLongitudeId] - if found == false { - ifdEnumerateLogger.Warningf(nil, "longitude not found") - log.Panic(ErrNoGpsTags) - } - - longitudeValue, err := tags[0].Value() - 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 := tags[0].Value() - log.PanicIf(err) - - // Parse location. - - latitudeRaw := latitudeValue.([]exifcommon.Rational) - - gi.Latitude, err = NewGpsDegreesFromRationals(latitudeRefValue.(string), latitudeRaw) - log.PanicIf(err) - - longitudeRaw := longitudeValue.([]exifcommon.Rational) - - gi.Longitude, err = NewGpsDegreesFromRationals(longitudeRefValue.(string), longitudeRaw) - log.PanicIf(err) - - // Parse altitude. - - altitudeTags, foundAltitude := ifd.entriesByTagId[TagAltitudeId] - altitudeRefTags, foundAltitudeRef := ifd.entriesByTagId[TagAltitudeRefId] - - if foundAltitude == true && foundAltitudeRef == true { - altitudePhrase, err := altitudeTags[0].Format() - log.PanicIf(err) - - ifdEnumerateLogger.Debugf(nil, "Altitude is [%s].", altitudePhrase) - - altitudeValue, err := altitudeTags[0].Value() - log.PanicIf(err) - - altitudeRefPhrase, err := altitudeRefTags[0].Format() - log.PanicIf(err) - - ifdEnumerateLogger.Debugf(nil, "Altitude-reference is [%s].", altitudeRefPhrase) - - altitudeRefValue, err := altitudeRefTags[0].Value() - log.PanicIf(err) - - altitudeRaw := altitudeValue.([]exifcommon.Rational) - if altitudeRaw[0].Denominator > 0 { - altitude := int(altitudeRaw[0].Numerator / altitudeRaw[0].Denominator) - - if altitudeRefValue.([]byte)[0] == 1 { - altitude *= -1 - } - - gi.Altitude = altitude - } - } - - // Parse timestamp from separate date and time tags. - - timestampTags, foundTimestamp := ifd.entriesByTagId[TagTimestampId] - datestampTags, foundDatestamp := ifd.entriesByTagId[TagDatestampId] - - if foundTimestamp == true && foundDatestamp == true { - datestampValue, err := datestampTags[0].Value() - log.PanicIf(err) - - datePhrase := datestampValue.(string) - ifdEnumerateLogger.Debugf(nil, "Date tag value is [%s].", datePhrase) - - // Normalize the separators. - datePhrase = strings.ReplaceAll(datePhrase, "-", ":") - - dateParts := strings.Split(datePhrase, ":") - - 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 := timestampTags[0].Value() - log.PanicIf(err) - - timePhrase, err := timestampTags[0].Format() - log.PanicIf(err) - - ifdEnumerateLogger.Debugf(nil, "Time tag value is [%s].", timePhrase) - - timestampRaw := timestampValue.([]exifcommon.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 -} - -// ParsedTagVisitor is a callback used if wanting to visit through all tags and -// child IFDs from the current IFD and going down. -type ParsedTagVisitor func(*Ifd, *IfdTagEntry) error - -// EnumerateTagsRecursively calls the given visitor function for every tag and -// IFD in the current IFD, recursively. -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 { - childIfdPath := ite.ChildIfdPath() - if childIfdPath != "" { - childIfd := ifd.childIfdIndex[childIfdPath] - - err := childIfd.EnumerateTagsRecursively(visitor) - log.PanicIf(err) - } else { - err := visitor(ifd, ite) - log.PanicIf(err) - } - } - } - - return nil -} - -// QueuedIfd is one IFD that has been identified but yet to be processed. -type QueuedIfd struct { - IfdIdentity *exifcommon.IfdIdentity - - 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 -} - -// IfdIndex collects a bunch of IFD and tag information stored in several -// different ways in order to provide convenient lookups. -type IfdIndex struct { - RootIfd *Ifd - Ifds []*Ifd - Tree map[int]*Ifd - Lookup map[string]*Ifd -} - -// Collect enumerates the different EXIF blocks (called IFDs) and builds out an -// index struct for referencing all of the parsed data. -func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - // TODO(dustin): Add MiscellaneousExifData to IfdIndex - - tree := make(map[int]*Ifd) - ifds := make([]*Ifd, 0) - lookup := make(map[string]*Ifd) - - queue := []QueuedIfd{ - { - IfdIdentity: exifcommon.IfdStandardIfdIdentity, - Offset: rootIfdOffset, - }, - } - - edges := make(map[uint32]*Ifd) - - for { - if len(queue) == 0 { - break - } - - qi := queue[0] - ii := qi.IfdIdentity - - offset := qi.Offset - parentIfd := qi.Parent - - queue = queue[1:] - - ifdEnumerateLogger.Debugf(nil, "Parsing IFD [%s] (%d) at offset (0x%04x) (Collect).", ii.String(), ii.Index(), offset) - - bp, err := ie.getByteParser(offset) - if err != nil { - if err == ErrOffsetInvalid { - return index, err - } - - log.Panic(err) - } - - // TODO(dustin): We don't need to pass the index in as a separate argument. Get from the II. - - nextIfdOffset, entries, thumbnailData, err := ie.parseIfd(ii, bp, nil, false, nil) - log.PanicIf(err) - - currentOffset := bp.CurrentOffset() - if currentOffset > ie.furthestOffset { - ie.furthestOffset = currentOffset - } - - id := len(ifds) - - entriesByTagId := make(map[uint16][]*IfdTagEntry) - for _, ite := range entries { - tagId := ite.TagId() - - tags, found := entriesByTagId[tagId] - if found == false { - tags = make([]*IfdTagEntry, 0) - } - - entriesByTagId[tagId] = append(tags, ite) - } - - ifd := &Ifd{ - ifdIdentity: ii, - - byteOrder: ie.byteOrder, - - id: id, - - parentIfd: parentIfd, - parentTagIndex: qi.ParentTagIndex, - - 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. - lookup[ii.String()] = ifd - - // 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, ite := range entries { - if ite.ChildIfdPath() == "" { - continue - } - - tagId := ite.TagId() - childIfdName := ite.ChildIfdName() - - currentIfdTag := ii.IfdTag() - - childIfdTag := - exifcommon.NewIfdTag( - ¤tIfdTag, - tagId, - childIfdName) - - iiChild := ii.NewChild(childIfdTag, 0) - - qi := QueuedIfd{ - IfdIdentity: iiChild, - - Offset: ite.getValueOffset(), - Parent: ifd, - ParentTagIndex: i, - } - - queue = append(queue, qi) - } - - // If there's another IFD in the chain. - if nextIfdOffset != 0 { - iiSibling := ii.NewSibling(ii.Index() + 1) - - // Allow the next link to know what the previous link was. - edges[nextIfdOffset] = ifd - - qi := QueuedIfd{ - IfdIdentity: iiSibling, - 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) - - ifdEnumerateLogger.Debugf(nil, "Collect: It looks like the furthest offset that contained EXIF data in the EXIF blob was (%d).", ie.FurthestOffset()) - - 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.ifdIdentity.UnindexedString()] = childIfd - } - - ifd.childIfdIndex = childIfdIndex - - for _, childIfd := range ifd.children { - err := ie.setChildrenIndex(childIfd) - log.PanicIf(err) - } - - return nil -} - -// FurthestOffset returns the furthest offset visited in the EXIF blob. This -// *does not* account for the locations of any undefined tags since we always -// evaluate the furthest offset, whether or not the user wants to know it. -// -// We are not willing to incur the cost of actually parsing those tags just to -// know their length when there are still undefined tags that are out there -// that we still won't have any idea how to parse, thus making this an -// approximation regardless of how clever we get. -func (ie *IfdEnumerate) FurthestOffset() uint32 { - - // TODO(dustin): Add test - - return ie.furthestOffset -} - -// parseOneIfd is a hack to use an IE to parse a raw IFD block. Can be used for -// testing. The fqIfdPath ("fully-qualified IFD path") will be less qualified -// 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 *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)) - } - }() - - // TODO(dustin): Add test - - ebs := NewExifReadSeekerWithBytes(ifdBlock) - - rs, err := ebs.GetReadSeeker(0) - log.PanicIf(err) - - bp, err := newByteParser(rs, byteOrder, 0) - if err != nil { - if err == ErrOffsetInvalid { - return 0, nil, err - } - - log.Panic(err) - } - - dummyEbs := NewExifReadSeekerWithBytes([]byte{}) - ie := NewIfdEnumerate(ifdMapping, tagIndex, dummyEbs, byteOrder) - - nextIfdOffset, entries, _, err = ie.parseIfd(ii, bp, visitor, true, nil) - log.PanicIf(err) - - return nextIfdOffset, entries, nil -} - -// parseOneTag is a hack to use an IE to parse a raw tag block. -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)) - } - }() - - // TODO(dustin): Add test - - ebs := NewExifReadSeekerWithBytes(tagBlock) - - rs, err := ebs.GetReadSeeker(0) - log.PanicIf(err) - - bp, err := newByteParser(rs, byteOrder, 0) - if err != nil { - if err == ErrOffsetInvalid { - return nil, err - } - - log.Panic(err) - } - - dummyEbs := NewExifReadSeekerWithBytes([]byte{}) - ie := NewIfdEnumerate(ifdMapping, tagIndex, dummyEbs, byteOrder) - - ite, err = ie.parseTag(ii, 0, bp) - log.PanicIf(err) - - err = ie.tagPostParse(ite, nil) - if err != nil { - if err == ErrTagNotFound { - return nil, err - } - - log.Panic(err) - } - - return ite, nil -} - -// FindIfdFromRootIfd returns the given `Ifd` given the root-IFD and path of the -// desired IFD. -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 != exifcommon.IfdStandardIfdIdentity.Name() { - log.Panicf("First IFD path item must be [%s].", exifcommon.IfdStandardIfdIdentity.Name()) - } - - 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 _, itii := range lineage { - var hit *Ifd - for _, childIfd := range thisIfd.children { - if childIfd.ifdIdentity.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", thisIfd.ifdIdentity.UnindexedString(), itii.Index) - } - - thisIfd = thisIfd.nextIfd - } - } - - return thisIfd, nil -} |