mirror of
https://github.com/dsoprea/go-exif.git
synced 2025-07-10 20:48:24 +00:00
- We were ambiguously treating it as both the parent II in some places and the child II in others. It's supposed to be the parent II.
806 lines
21 KiB
Go
806 lines
21 KiB
Go
package exif
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"encoding/binary"
|
|
|
|
"github.com/dsoprea/go-logging"
|
|
)
|
|
|
|
var (
|
|
ifdEnumerateLogger = log.NewLogger("exifjpeg.ifd")
|
|
)
|
|
|
|
|
|
// 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))
|
|
}
|
|
}()
|
|
|
|
raw = make([]byte, 2)
|
|
|
|
_, err = ife.buffer.Read(raw)
|
|
log.PanicIf(err)
|
|
|
|
if ife.byteOrder == binary.BigEndian {
|
|
value = binary.BigEndian.Uint16(raw)
|
|
} else {
|
|
value = binary.LittleEndian.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))
|
|
}
|
|
}()
|
|
|
|
raw = make([]byte, 4)
|
|
|
|
_, err = ife.buffer.Read(raw)
|
|
log.PanicIf(err)
|
|
|
|
if ife.byteOrder == binary.BigEndian {
|
|
value = binary.BigEndian.Uint32(raw)
|
|
} else {
|
|
value = binary.LittleEndian.Uint32(raw)
|
|
}
|
|
|
|
return value, raw, nil
|
|
}
|
|
|
|
|
|
type IfdEnumerate struct {
|
|
exifData []byte
|
|
buffer *bytes.Buffer
|
|
byteOrder binary.ByteOrder
|
|
currentOffset uint32
|
|
}
|
|
|
|
func NewIfdEnumerate(exifData []byte, byteOrder binary.ByteOrder) *IfdEnumerate {
|
|
// Make it obvious what data we expect and when we don't get it.
|
|
if IsExif(exifData) == false {
|
|
log.Panicf("not exif data")
|
|
}
|
|
|
|
return &IfdEnumerate{
|
|
exifData: exifData,
|
|
buffer: bytes.NewBuffer(exifData),
|
|
byteOrder: byteOrder,
|
|
}
|
|
}
|
|
|
|
// ValueContext describes all of the parameters required to find and extract
|
|
// the actual tag value.
|
|
type ValueContext struct {
|
|
UnitCount uint32
|
|
ValueOffset uint32
|
|
RawValueOffset []byte
|
|
AddressableData []byte
|
|
}
|
|
|
|
func (ie *IfdEnumerate) getTagEnumerator(ifdOffset uint32) (ite *IfdTagEnumerator) {
|
|
ite = NewIfdTagEnumerator(
|
|
ie.exifData[ExifAddressableAreaStart:],
|
|
ie.byteOrder,
|
|
ifdOffset)
|
|
|
|
return ite
|
|
}
|
|
|
|
func (ie *IfdEnumerate) parseTag(ii IfdIdentity, tagIndex int, ite *IfdTagEnumerator) (tag *IfdTagEntry, err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
tagId, _, err := ite.getUint16()
|
|
log.PanicIf(err)
|
|
|
|
tagType, _, err := ite.getUint16()
|
|
log.PanicIf(err)
|
|
|
|
unitCount, _, err := ite.getUint32()
|
|
log.PanicIf(err)
|
|
|
|
valueOffset, rawValueOffset, err := ite.getUint32()
|
|
log.PanicIf(err)
|
|
|
|
tag = &IfdTagEntry{
|
|
Ii: ii,
|
|
TagId: tagId,
|
|
TagIndex: tagIndex,
|
|
TagType: tagType,
|
|
UnitCount: unitCount,
|
|
ValueOffset: valueOffset,
|
|
RawValueOffset: rawValueOffset,
|
|
}
|
|
|
|
// 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.
|
|
childIfdName, isIfd := IfdTagNameWithId(ii.IfdName, tagId)
|
|
if isIfd == true {
|
|
tag.ChildIfdName = childIfdName
|
|
}
|
|
|
|
return tag, nil
|
|
}
|
|
|
|
// TagVisitor 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).
|
|
type TagVisitor func(ii IfdIdentity, 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(ii IfdIdentity, ifdIndex int, ite *IfdTagEnumerator, visitor TagVisitor, doDescend bool) (nextIfdOffset uint32, entries []*IfdTagEntry, err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
tagCount, _, err := ite.getUint16()
|
|
log.PanicIf(err)
|
|
|
|
ifdEnumerateLogger.Debugf(nil, "Current IFD tag-count: (%d)", tagCount)
|
|
|
|
entries = make([]*IfdTagEntry, tagCount)
|
|
|
|
for i := 0; i < int(tagCount); i++ {
|
|
tag, err := ie.parseTag(ii, i, ite)
|
|
log.PanicIf(err)
|
|
|
|
if visitor != nil {
|
|
tt := NewTagType(tag.TagType, ie.byteOrder)
|
|
|
|
vc := ValueContext{
|
|
UnitCount: tag.UnitCount,
|
|
ValueOffset: tag.ValueOffset,
|
|
RawValueOffset: tag.RawValueOffset,
|
|
AddressableData: ie.exifData[ExifAddressableAreaStart:],
|
|
}
|
|
|
|
err := visitor(ii, ifdIndex, tag.TagId, tt, vc)
|
|
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.ChildIfdName != "" && doDescend == true {
|
|
ifdEnumerateLogger.Debugf(nil, "Descending to IFD [%s].", tag.ChildIfdName)
|
|
|
|
childIi, _ := IfdIdOrFail(ii.IfdName, tag.ChildIfdName)
|
|
|
|
err := ie.scan(childIi, tag.ValueOffset, visitor)
|
|
log.PanicIf(err)
|
|
}
|
|
|
|
entries[i] = tag
|
|
}
|
|
|
|
nextIfdOffset, _, err = ite.getUint32()
|
|
log.PanicIf(err)
|
|
|
|
ifdEnumerateLogger.Debugf(nil, "Next IFD at offset: (%08x)", nextIfdOffset)
|
|
|
|
return nextIfdOffset, entries, nil
|
|
}
|
|
|
|
// Scan enumerates the different EXIF's IFD blocks.
|
|
func (ie *IfdEnumerate) scan(ii IfdIdentity, ifdOffset uint32, visitor TagVisitor) (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).", ii.IfdName, ifdIndex, ifdOffset)
|
|
ite := ie.getTagEnumerator(ifdOffset)
|
|
|
|
nextIfdOffset, _, err := ie.ParseIfd(ii, ifdIndex, ite, visitor, true)
|
|
log.PanicIf(err)
|
|
|
|
if nextIfdOffset == 0 {
|
|
break
|
|
}
|
|
|
|
ifdOffset = nextIfdOffset
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Scan enumerates the different EXIF blocks (called IFDs).
|
|
func (ie *IfdEnumerate) Scan(ifdOffset uint32, visitor TagVisitor) (err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
ii, _ := IfdIdOrFail("", IfdStandard)
|
|
|
|
err = ie.scan(ii, ifdOffset, visitor)
|
|
log.PanicIf(err)
|
|
|
|
return nil
|
|
}
|
|
|
|
|
|
// Ifd represents a single parsed IFD.
|
|
type Ifd struct {
|
|
// 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
|
|
|
|
Ii IfdIdentity
|
|
|
|
Id int
|
|
ParentIfd *Ifd
|
|
Name string
|
|
Index int
|
|
Offset uint32
|
|
|
|
Entries []*IfdTagEntry
|
|
EntriesByTagId map[uint16][]*IfdTagEntry
|
|
|
|
Children []*Ifd
|
|
NextIfdOffset uint32
|
|
NextIfd *Ifd
|
|
}
|
|
|
|
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))
|
|
}
|
|
}()
|
|
|
|
ti := NewTagIndex()
|
|
|
|
ii := ifd.Identity()
|
|
it, err := ti.GetWithName(ii, 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) PARENT-IFD=[%s] IFD=[%s] IDX=(%d) COUNT=(%d) OFF=(0x%04x) CHILDREN=(%d) PARENT=(0x%04x) NEXT-IFD=(0x%04x)", ifd.Id, ifd.Ii.ParentIfdName, ifd.Ii.IfdName, ifd.Index, len(ifd.Entries), ifd.Offset, len(ifd.Children), parentOffset, ifd.NextIfdOffset)
|
|
}
|
|
|
|
func (ifd Ifd) Identity() IfdIdentity {
|
|
return ifd.Ii
|
|
}
|
|
|
|
func (ifd Ifd) dumpTags(tags []*IfdTagEntry) []*IfdTagEntry {
|
|
if tags == nil {
|
|
tags = make([]*IfdTagEntry, 0)
|
|
}
|
|
|
|
// Quickly create an index of the child-IFDs.
|
|
|
|
childIfdIndex := make(map[string]*Ifd)
|
|
for _, childIfd := range ifd.Children {
|
|
childIfdIndex[childIfd.Ii.IfdName] = childIfd
|
|
}
|
|
|
|
// 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.ChildIfdName != "" {
|
|
ifdsFoundCount++
|
|
|
|
childIfd, found := childIfdIndex[tag.ChildIfdName]
|
|
if found != true {
|
|
log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdName)
|
|
}
|
|
|
|
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(level int, nextLink bool) {
|
|
indent := strings.Repeat(" ", level * 2)
|
|
|
|
prefix := " "
|
|
if nextLink {
|
|
prefix = ">"
|
|
}
|
|
|
|
fmt.Printf("%s%sIFD: %s\n", indent, prefix, ifd)
|
|
|
|
// Quickly create an index of the child-IFDs.
|
|
|
|
childIfdIndex := make(map[string]*Ifd)
|
|
for _, childIfd := range ifd.Children {
|
|
childIfdIndex[childIfd.Ii.IfdName] = childIfd
|
|
}
|
|
|
|
// Now, print the tags while also descending to child-IFDS as we encounter them.
|
|
|
|
ifdsFoundCount := 0
|
|
|
|
ti := NewTagIndex()
|
|
for _, tag := range ifd.Entries {
|
|
if tag.ChildIfdName != "" {
|
|
fmt.Printf("%s - TAG: %s\n", indent, tag)
|
|
} else {
|
|
it, err := ti.Get(ifd.Identity(), tag.TagId)
|
|
|
|
tagName := ""
|
|
if err == nil {
|
|
tagName = it.Name
|
|
}
|
|
|
|
fmt.Printf("%s - TAG: %s NAME=[%s]\n", indent, tag, tagName)
|
|
}
|
|
|
|
if tag.ChildIfdName != "" {
|
|
ifdsFoundCount++
|
|
|
|
childIfd, found := childIfdIndex[tag.ChildIfdName]
|
|
if found != true {
|
|
log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdName)
|
|
}
|
|
|
|
childIfd.printTagTree(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(level, true)
|
|
}
|
|
}
|
|
|
|
// PrintTagTree prints the IFD hierarchy.
|
|
func (ifd Ifd) PrintTagTree() {
|
|
ifd.printTagTree(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)
|
|
|
|
// Quickly create an index of the child-IFDs.
|
|
|
|
childIfdIndex := make(map[string]*Ifd)
|
|
for _, childIfd := range ifd.Children {
|
|
childIfdIndex[childIfd.Ii.IfdName] = childIfd
|
|
}
|
|
|
|
// Now, print the tags while also descending to child-IFDS as we encounter them.
|
|
|
|
ifdsFoundCount := 0
|
|
|
|
for _, tag := range ifd.Entries {
|
|
if tag.ChildIfdName != "" {
|
|
ifdsFoundCount++
|
|
|
|
childIfd, found := childIfdIndex[tag.ChildIfdName]
|
|
if found != true {
|
|
log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdName)
|
|
}
|
|
|
|
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)
|
|
|
|
// Quickly create an index of the child-IFDs.
|
|
|
|
childIfdIndex := make(map[string]*Ifd)
|
|
for _, childIfd := range ifd.Children {
|
|
childIfdIndex[childIfd.Ii.IfdName] = childIfd
|
|
}
|
|
|
|
var ifdPhrase string
|
|
if ifd.ParentIfd != nil {
|
|
ifdPhrase = fmt.Sprintf("[%s]->[%s]:(%d)", ifd.ParentIfd.Ii.IfdName, ifd.Ii.IfdName, ifd.Index)
|
|
} else {
|
|
ifdPhrase = fmt.Sprintf("[ROOT]->[%s]:(%d)", ifd.Ii.IfdName, 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.ChildIfdName != "" {
|
|
ifdsFoundCount++
|
|
|
|
childIfd, found := childIfdIndex[tag.ChildIfdName]
|
|
if found != true {
|
|
log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdName)
|
|
}
|
|
|
|
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.Ii.IfdName, 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)
|
|
}
|
|
|
|
type QueuedIfd struct {
|
|
Ii IfdIdentity
|
|
Index int
|
|
Offset uint32
|
|
Parent *Ifd
|
|
}
|
|
|
|
|
|
type IfdIndex struct {
|
|
RootIfd *Ifd
|
|
Ifds []*Ifd
|
|
Tree map[int]*Ifd
|
|
Lookup map[IfdIdentity][]*Ifd
|
|
}
|
|
|
|
|
|
// Scan enumerates the different EXIF blocks (called IFDs).
|
|
func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (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[IfdIdentity][]*Ifd)
|
|
|
|
queue := []QueuedIfd{
|
|
{
|
|
Ii: RootIi,
|
|
Index: 0,
|
|
Offset: rootIfdOffset,
|
|
},
|
|
}
|
|
|
|
edges := make(map[uint32]*Ifd)
|
|
|
|
for {
|
|
if len(queue) == 0 {
|
|
break
|
|
}
|
|
|
|
ii := queue[0].Ii
|
|
|
|
|
|
name := ii.IfdName
|
|
index := queue[0].Index
|
|
offset := queue[0].Offset
|
|
|
|
parentIfd := queue[0].Parent
|
|
|
|
queue = queue[1:]
|
|
|
|
ifdEnumerateLogger.Debugf(nil, "Parsing IFD [%s] (%d) at offset (%04x).", ii.IfdName, index, offset)
|
|
ite := ie.getTagEnumerator(offset)
|
|
|
|
nextIfdOffset, entries, err := ie.ParseIfd(ii, index, ite, nil, false)
|
|
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,
|
|
Ii: ii,
|
|
Id: id,
|
|
ParentIfd: parentIfd,
|
|
Name: name,
|
|
Index: index,
|
|
Offset: offset,
|
|
Entries: entries,
|
|
EntriesByTagId: entriesByTagId,
|
|
Children: make([]*Ifd, 0),
|
|
NextIfdOffset: nextIfdOffset,
|
|
}
|
|
|
|
// 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[ii]; found == true {
|
|
lookup[ii] = append(list_, &ifd)
|
|
} else {
|
|
list_ = make([]*Ifd, 1)
|
|
list_[0] = &ifd
|
|
|
|
lookup[ii] = 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 _, entry := range entries {
|
|
if entry.ChildIfdName == "" {
|
|
continue
|
|
}
|
|
|
|
childIi := IfdIdentity{
|
|
ParentIfdName: name,
|
|
IfdName: entry.ChildIfdName,
|
|
}
|
|
|
|
qi := QueuedIfd{
|
|
Ii: childIi,
|
|
Index: 0,
|
|
Offset: entry.ValueOffset,
|
|
Parent: &ifd,
|
|
}
|
|
|
|
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
|
|
|
|
qi := QueuedIfd{
|
|
Ii: ii,
|
|
Index: index + 1,
|
|
Offset: nextIfdOffset,
|
|
}
|
|
|
|
queue = append(queue, qi)
|
|
}
|
|
}
|
|
|
|
index.RootIfd = tree[0]
|
|
index.Ifds = ifds
|
|
index.Tree = tree
|
|
index.Lookup = lookup
|
|
|
|
return index, nil
|
|
}
|
|
|
|
// ParseOneIfd is a hack to use an IE to parse a raw IFD block. Can be used for
|
|
// testing.
|
|
func ParseOneIfd(ii IfdIdentity, byteOrder binary.ByteOrder, ifdBlock []byte, visitor TagVisitor) (nextIfdOffset uint32, entries []*IfdTagEntry, err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
ie := &IfdEnumerate{
|
|
byteOrder: byteOrder,
|
|
}
|
|
|
|
ite := NewIfdTagEnumerator(ifdBlock, byteOrder, 0)
|
|
|
|
nextIfdOffset, entries, err = ie.ParseIfd(ii, 0, ite, visitor, true)
|
|
log.PanicIf(err)
|
|
|
|
return nextIfdOffset, entries, nil
|
|
}
|
|
|
|
// ParseOneTag is a hack to use an IE to parse a raw tag block.
|
|
func ParseOneTag(ii IfdIdentity, byteOrder binary.ByteOrder, tagBlock []byte) (tag *IfdTagEntry, err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
ie := &IfdEnumerate{
|
|
byteOrder: byteOrder,
|
|
}
|
|
|
|
ite := NewIfdTagEnumerator(tagBlock, byteOrder, 0)
|
|
|
|
tag, err = ie.parseTag(ii, 0, ite)
|
|
log.PanicIf(err)
|
|
|
|
return tag, nil
|
|
}
|