mirror of https://github.com/dsoprea/go-exif.git
ifd_builder_encoder: Fixed subtle but detrimental bugs.
- ifd_builder_encode - Bugfix: We were assigning the "next" IFD in correctly (so, chaining was broken). - Bugfix: We weren't using the right value when *writing* the "next" IFD offset (so, chaining was, again, broken). - The same IFD was showing as both a child and a sibling, so we were seeing the EXIF IFD also being chained-to by the first root IFD instead of the *actual* sibling, but only after encoding and then decoding (so, this had to be tracked down in the encoding semantics, which was non-trivial). - Added a test for the stuff that we fixed. - Implemented journaling, where we can dump a hierarchically-formatted list of operations involved in a completed encode. - ifd_enumerate: Added (Ifd).TagValueBytes() to return the raw tag- value's bytes (rather than just having (Ifd).TagValue(), which returns the value marshaled into a string). Good for comparing raw encoded and decoded values without imposing interpretation (which gets us into trouble with unknown-type tags whose values are non-standard and undocumentedand, therefore, not actually parseable). - Rewired a lot of print and dump functions. - For naming-specificity as well as functionality. - Removed from debugging.pull/3/head
parent
6207bd6200
commit
3cee9b956b
166
ifd_builder.go
166
ifd_builder.go
|
@ -289,27 +289,23 @@ func NewIfdBuilderWithExistingIfd(ifd *Ifd) (ib *IfdBuilder) {
|
|||
|
||||
// NewIfdBuilderFromExistingChain creates a chain of IB instances from an
|
||||
// IFD chain generated from real data.
|
||||
func NewIfdBuilderFromExistingChain(rootIfd *Ifd, exifData []byte) (rootIb *IfdBuilder) {
|
||||
func NewIfdBuilderFromExistingChain(rootIfd *Ifd, exifData []byte) (firstIb *IfdBuilder) {
|
||||
itevr := NewIfdTagEntryValueResolver(exifData, rootIfd.ByteOrder)
|
||||
|
||||
// TODO(dustin): !! When we actually write the code to flatten the IB to bytes, make sure to skip the tags that have a nil value (which will happen when we add-from-exsting without a resolver instance).
|
||||
|
||||
var newIb *IfdBuilder
|
||||
var lastIb *IfdBuilder
|
||||
i := 0
|
||||
for thisExistingIfd := rootIfd; thisExistingIfd != nil; thisExistingIfd = thisExistingIfd.NextIfd {
|
||||
lastIb := newIb
|
||||
|
||||
ii := thisExistingIfd.Identity()
|
||||
|
||||
newIb = NewIfdBuilder(ii, rootIfd.ByteOrder)
|
||||
if lastIb != nil {
|
||||
fmt.Printf("SETTING NEXT-IB: %s\n", newIb)
|
||||
newIb := NewIfdBuilder(ii, thisExistingIfd.ByteOrder)
|
||||
if firstIb == nil {
|
||||
firstIb = newIb
|
||||
} else {
|
||||
lastIb.SetNextIb(newIb)
|
||||
}
|
||||
|
||||
if rootIb == nil {
|
||||
rootIb = newIb
|
||||
}
|
||||
|
||||
err := newIb.AddTagsFromExisting(thisExistingIfd, itevr, nil, nil)
|
||||
log.PanicIf(err)
|
||||
|
||||
|
@ -321,9 +317,12 @@ func NewIfdBuilderFromExistingChain(rootIfd *Ifd, exifData []byte) (rootIb *IfdB
|
|||
err = newIb.AddChildIb(childIb)
|
||||
log.PanicIf(err)
|
||||
}
|
||||
|
||||
lastIb = newIb
|
||||
i++
|
||||
}
|
||||
|
||||
return rootIb
|
||||
return firstIb
|
||||
}
|
||||
|
||||
func (ib *IfdBuilder) String() string {
|
||||
|
@ -333,66 +332,110 @@ func (ib *IfdBuilder) String() string {
|
|||
nextIfdPhrase = ib.nextIb.ii.IfdName
|
||||
}
|
||||
|
||||
return fmt.Sprintf("IfdBuilder<NAME=[%s] TAG-ID=(0x%02x) BO=[%s] COUNT=(%d) OFFSET=(0x%04x) NEXT-IFD=(0x%04x)>", ib.ii, ib.ifdTagId, ib.byteOrder, len(ib.tags), ib.existingOffset, nextIfdPhrase)
|
||||
return fmt.Sprintf("IfdBuilder<PARENT-IFD=[%s] IFD=[%s] TAG-ID=(0x%02x) COUNT=(%d) OFF=(0x%04x) NEXT-IFD=(0x%04x)>", ib.ii.ParentIfdName, ib.ii.IfdName, ib.ifdTagId, len(ib.tags), ib.existingOffset, nextIfdPhrase)
|
||||
}
|
||||
|
||||
func (ib *IfdBuilder) Tags() (tags []builderTag) {
|
||||
return ib.tags
|
||||
}
|
||||
|
||||
func (ib *IfdBuilder) dump(levels int) {
|
||||
indent := strings.Repeat(" ", levels * 4)
|
||||
|
||||
if levels == 0 {
|
||||
fmt.Printf("%sIFD: %s\n", indent, ib)
|
||||
} else {
|
||||
fmt.Printf("%sChild IFD: %s\n", indent, ib)
|
||||
}
|
||||
func (ib *IfdBuilder) printTagTree(levels int) {
|
||||
indent := strings.Repeat(" ", levels * 2)
|
||||
|
||||
ti := NewTagIndex()
|
||||
|
||||
if len(ib.tags) > 0 {
|
||||
fmt.Printf("\n")
|
||||
|
||||
for i, tag := range ib.tags {
|
||||
_, isChildIb := IfdTagNameWithId(ib.ii.IfdName, tag.tagId)
|
||||
|
||||
tagName := ""
|
||||
|
||||
// If a normal tag (not a child IFD) get the name.
|
||||
if isChildIb == true {
|
||||
tagName = "<Child IFD>"
|
||||
} else {
|
||||
it, err := ti.Get(tag.ii, tag.tagId)
|
||||
if log.Is(err, ErrTagNotFound) == true {
|
||||
tagName = "<UNKNOWN>"
|
||||
} else if err != nil {
|
||||
log.Panic(err)
|
||||
} else {
|
||||
tagName = it.Name
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("%s (%d): [%s] %s\n", indent, i, tagName, tag)
|
||||
|
||||
if isChildIb == true {
|
||||
if tag.value.IsIb() == false {
|
||||
log.Panicf("tag-ID (0x%02x) is an IFD but the tag value is not an IB instance: %v", tag.tagId, tag)
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
|
||||
childIb := tag.value.Ib()
|
||||
childIb.dump(levels + 1)
|
||||
}
|
||||
i := 0
|
||||
for currentIb := ib; currentIb != nil; currentIb = currentIb.nextIb {
|
||||
prefix := " "
|
||||
if i > 0 {
|
||||
prefix = ">"
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
if levels == 0 {
|
||||
fmt.Printf("%s%sIFD: %s\n", indent, prefix, currentIb)
|
||||
} else {
|
||||
fmt.Printf("%s%sChild IFD: %s\n", indent, prefix, currentIb)
|
||||
}
|
||||
|
||||
if len(currentIb.tags) > 0 {
|
||||
fmt.Printf("\n")
|
||||
|
||||
for i, tag := range currentIb.tags {
|
||||
_, isChildIb := IfdTagNameWithId(currentIb.ii.IfdName, tag.tagId)
|
||||
|
||||
tagName := ""
|
||||
|
||||
// If a normal tag (not a child IFD) get the name.
|
||||
if isChildIb == true {
|
||||
tagName = "<Child IFD>"
|
||||
} else {
|
||||
it, err := ti.Get(tag.ii, tag.tagId)
|
||||
if log.Is(err, ErrTagNotFound) == true {
|
||||
tagName = "<UNKNOWN>"
|
||||
} else if err != nil {
|
||||
log.Panic(err)
|
||||
} else {
|
||||
tagName = it.Name
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("%s (%d): [%s] %s\n", indent, i, tagName, tag)
|
||||
|
||||
if isChildIb == true {
|
||||
if tag.value.IsIb() == false {
|
||||
log.Panicf("tag-ID (0x%02x) is an IFD but the tag value is not an IB instance: %v", tag.tagId, tag)
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
|
||||
childIb := tag.value.Ib()
|
||||
childIb.printTagTree(levels + 1)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func (ib *IfdBuilder) Dump() {
|
||||
ib.dump(0)
|
||||
func (ib *IfdBuilder) PrintTagTree() {
|
||||
ib.printTagTree(0)
|
||||
}
|
||||
|
||||
func (ib *IfdBuilder) printIfdTree(levels int) {
|
||||
indent := strings.Repeat(" ", levels * 2)
|
||||
|
||||
i := 0
|
||||
for currentIb := ib; currentIb != nil; currentIb = currentIb.nextIb {
|
||||
prefix := " "
|
||||
if i > 0 {
|
||||
prefix = ">"
|
||||
}
|
||||
|
||||
fmt.Printf("%s%s%s\n", indent, prefix,currentIb)
|
||||
|
||||
if len(currentIb.tags) > 0 {
|
||||
for _, tag := range currentIb.tags {
|
||||
_, isChildIb := IfdTagNameWithId(currentIb.ii.IfdName, tag.tagId)
|
||||
|
||||
if isChildIb == true {
|
||||
if tag.value.IsIb() == false {
|
||||
log.Panicf("tag-ID (0x%02x) is an IFD but the tag value is not an IB instance: %v", tag.tagId, tag)
|
||||
}
|
||||
|
||||
childIb := tag.value.Ib()
|
||||
childIb.printIfdTree(levels + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func (ib *IfdBuilder) PrintIfdTree() {
|
||||
ib.printIfdTree(0)
|
||||
}
|
||||
|
||||
func (ib *IfdBuilder) dumpToStrings(thisIb *IfdBuilder, prefix string, lines []string) (linesOutput []string) {
|
||||
|
@ -789,11 +832,6 @@ func (ib *IfdBuilder) SetFromConfigWithName(tagName string, value interface{}) (
|
|||
i, err := ib.Find(bt.tagId)
|
||||
log.PanicIf(err)
|
||||
|
||||
fmt.Printf("FOUND [%s] AT POSITION (%d) OF (%d)\n", tagName, i + 1, len(ib.tags))
|
||||
|
||||
fmt.Printf("ORIGINAL TAG: %s\n", ib.tags[i])
|
||||
fmt.Printf("UPDATED TAG: %s\n", bt)
|
||||
|
||||
ib.tags[i] = bt
|
||||
|
||||
return nil
|
||||
|
|
|
@ -3,6 +3,7 @@ package exif
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"encoding/binary"
|
||||
|
||||
|
@ -121,10 +122,18 @@ func (ida *ifdDataAllocator) Bytes() []byte {
|
|||
// out all of the allocations and indirection that is required for extended
|
||||
// data.
|
||||
type IfdByteEncoder struct {
|
||||
// journal holds a list of actions taken while encoding.
|
||||
journal [][3]string
|
||||
}
|
||||
|
||||
func NewIfdByteEncoder() (ibe *IfdByteEncoder) {
|
||||
return new(IfdByteEncoder)
|
||||
return &IfdByteEncoder{
|
||||
journal: make([][3]string, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (ibe *IfdByteEncoder) Journal() [][3]string {
|
||||
return ibe.journal
|
||||
}
|
||||
|
||||
func (ibe *IfdByteEncoder) TableSize(entryCount int) uint32 {
|
||||
|
@ -132,6 +141,61 @@ func (ibe *IfdByteEncoder) TableSize(entryCount int) uint32 {
|
|||
return uint32(2) + (IfdTagEntrySize * uint32(entryCount)) + uint32(4)
|
||||
}
|
||||
|
||||
func (ibe *IfdByteEncoder) pushToJournal(where, direction, format string, args ...interface{}) {
|
||||
event := [3]string {
|
||||
direction,
|
||||
where,
|
||||
fmt.Sprintf(format, args...),
|
||||
}
|
||||
|
||||
ibe.journal = append(ibe.journal, event)
|
||||
}
|
||||
|
||||
// PrintJournal prints a hierarchical representation of the steps taken during
|
||||
// encoding.
|
||||
func (ibe *IfdByteEncoder) PrintJournal() {
|
||||
maxWhereLength := 0
|
||||
for _, event := range ibe.journal {
|
||||
where := event[1]
|
||||
|
||||
len_ := len(where)
|
||||
if len_ > maxWhereLength {
|
||||
maxWhereLength = len_
|
||||
}
|
||||
}
|
||||
|
||||
level := 0
|
||||
for i, event := range ibe.journal {
|
||||
direction := event[0]
|
||||
where := event[1]
|
||||
message := event[2]
|
||||
|
||||
if direction != ">" && direction != "<" && direction != "-" {
|
||||
log.Panicf("journal operation not valid: [%s]", direction)
|
||||
}
|
||||
|
||||
if direction == "<" {
|
||||
if level <= 0 {
|
||||
log.Panicf("journal operations unbalanced (too many closes)")
|
||||
}
|
||||
|
||||
level--
|
||||
}
|
||||
|
||||
indent := strings.Repeat(" ", level)
|
||||
|
||||
fmt.Printf("%3d %s%s %s: %s\n", i, indent, direction, where, message)
|
||||
|
||||
if direction == ">" {
|
||||
level++
|
||||
}
|
||||
}
|
||||
|
||||
if level != 0 {
|
||||
log.Panicf("journal operations unbalanced (too many opens)")
|
||||
}
|
||||
}
|
||||
|
||||
// encodeTagToBytes encodes the given tag to a byte stream. If
|
||||
// `nextIfdOffsetToWrite` is more than (0), recurse into child IFDs
|
||||
// (`nextIfdOffsetToWrite` is required in order for them to know where the its
|
||||
|
@ -144,12 +208,6 @@ func (ibe *IfdByteEncoder) encodeTagToBytes(ib *IfdBuilder, bt *builderTag, bw *
|
|||
}
|
||||
}()
|
||||
|
||||
// TODO(dustin): !! This function is running more times than it should.
|
||||
|
||||
if bt.tagId == 0x9286 {
|
||||
fmt.Printf("ENCODING/WRITING: %s\n", bt)
|
||||
}
|
||||
|
||||
// Write tag-ID.
|
||||
err = bw.WriteUint16(bt.tagId)
|
||||
log.PanicIf(err)
|
||||
|
@ -188,24 +246,12 @@ func (ibe *IfdByteEncoder) encodeTagToBytes(ib *IfdBuilder, bt *builderTag, bw *
|
|||
// Write four-byte value/offset.
|
||||
|
||||
if len_ > 4 {
|
||||
if bt.tagId == 0x9286 {
|
||||
fmt.Printf("USERCOMMENTS: Allocating: [%s]\n", string(valueBytes[:5]))
|
||||
}
|
||||
|
||||
offset, err := ida.Allocate(valueBytes)
|
||||
log.PanicIf(err)
|
||||
|
||||
if bt.tagId == 0x9286 {
|
||||
fmt.Printf("USERCOMMENTS: Offset: (%d) [0x%02x]\n", offset, offset)
|
||||
}
|
||||
|
||||
err = bw.WriteUint32(offset)
|
||||
log.PanicIf(err)
|
||||
} else {
|
||||
if bt.tagId == 0x9286 {
|
||||
fmt.Printf("USERCOMMENTS: Not allocating: [%v]\n", valueBytes)
|
||||
}
|
||||
|
||||
fourBytes := make([]byte, 4)
|
||||
copy(fourBytes, valueBytes)
|
||||
|
||||
|
@ -224,10 +270,14 @@ func (ibe *IfdByteEncoder) encodeTagToBytes(ib *IfdBuilder, bt *builderTag, bw *
|
|||
if nextIfdOffsetToWrite > 0 {
|
||||
var err error
|
||||
|
||||
ibe.pushToJournal("encodeTagToBytes", ">", "[%s]->[%s]", ib.ii.IfdName, bt.value.Ib().ii.IfdName)
|
||||
|
||||
// Create the block of IFD data and everything it requires.
|
||||
childIfdBlock, err = ibe.encodeAndAttachIfd(bt.value.Ib(), nextIfdOffsetToWrite)
|
||||
log.PanicIf(err)
|
||||
|
||||
ibe.pushToJournal("encodeTagToBytes", "<", "[%s]->[%s]", bt.value.Ib().ii.IfdName, ib.ii.IfdName)
|
||||
|
||||
// Use the next-IFD offset for it. The IFD will actually get
|
||||
// attached after we return.
|
||||
err = bw.WriteUint32(nextIfdOffsetToWrite)
|
||||
|
@ -237,6 +287,8 @@ func (ibe *IfdByteEncoder) encodeTagToBytes(ib *IfdBuilder, bt *builderTag, bw *
|
|||
// No child-IFDs are to be allocated. Finish the entry with a NULL
|
||||
// pointer.
|
||||
|
||||
ibe.pushToJournal("encodeTagToBytes", "-", "*Not* descending to child: [%s]", bt.value.Ib().ii.IfdName)
|
||||
|
||||
err = bw.WriteUint32(0)
|
||||
log.PanicIf(err)
|
||||
}
|
||||
|
@ -261,6 +313,8 @@ func (ibe *IfdByteEncoder) encodeIfdToBytes(ib *IfdBuilder, ifdAddressableOffset
|
|||
}
|
||||
}()
|
||||
|
||||
ibe.pushToJournal("encodeIfdToBytes", ">", "%s", ib)
|
||||
|
||||
tableSize = ibe.TableSize(len(ib.tags))
|
||||
|
||||
b := new(bytes.Buffer)
|
||||
|
@ -309,10 +363,18 @@ func (ibe *IfdByteEncoder) encodeIfdToBytes(ib *IfdBuilder, ifdAddressableOffset
|
|||
// Write address of next IFD in chain. This will be the original
|
||||
// allocation offset plus the size of everything we have allocated for
|
||||
// this IFD and its child-IFDs.
|
||||
//
|
||||
// It is critical that this number is stepped properly. We experienced
|
||||
// an issue whereby it first looked like we were duplicating the IFD and
|
||||
// then that we were duplicating the tags in the wrong IFD, and then
|
||||
// finally we determined that the next-IFD offset for the first IFD was
|
||||
// accidentally pointing back to the EXIF IFD, so we were visiting it
|
||||
// twice when visiting through the tags after decoding. It was an
|
||||
// expensive bug to find.
|
||||
|
||||
offset := ifdAddressableOffset + dataSize
|
||||
ibe.pushToJournal("encodeIfdToBytes", "-", "Setting 'next' IFD to (0x%08x).", nextIfdOffsetToWrite)
|
||||
|
||||
err := bw.WriteUint32(offset)
|
||||
err := bw.WriteUint32(nextIfdOffsetToWrite)
|
||||
log.PanicIf(err)
|
||||
} else {
|
||||
err := bw.WriteUint32(0)
|
||||
|
@ -336,6 +398,8 @@ func (ibe *IfdByteEncoder) encodeIfdToBytes(ib *IfdBuilder, ifdAddressableOffset
|
|||
log.PanicIf(err)
|
||||
}
|
||||
|
||||
ibe.pushToJournal("encodeIfdToBytes", "<", "%s", ib)
|
||||
|
||||
return b.Bytes(), tableSize, dataSize, childIfdSizes, nil
|
||||
}
|
||||
|
||||
|
@ -347,6 +411,8 @@ func (ibe *IfdByteEncoder) encodeAndAttachIfd(ib *IfdBuilder, ifdAddressableOffs
|
|||
}
|
||||
}()
|
||||
|
||||
ibe.pushToJournal("encodeAndAttachIfd", ">", "%s", ib)
|
||||
|
||||
if len(ib.tags) == 0 {
|
||||
log.Panicf("trying to encode an IfdBuilder that doesn't have any tags")
|
||||
}
|
||||
|
@ -354,12 +420,19 @@ func (ibe *IfdByteEncoder) encodeAndAttachIfd(ib *IfdBuilder, ifdAddressableOffs
|
|||
b := new(bytes.Buffer)
|
||||
|
||||
nextIfdOffsetToWrite := uint32(0)
|
||||
i := 0
|
||||
for thisIb := ib; thisIb != nil; thisIb = thisIb.nextIb {
|
||||
// Do a dry-run in order to pre-determine its size requirement.
|
||||
|
||||
_, tableSize, allocatedDataSize, _, err := ibe.encodeIfdToBytes(ib, ifdAddressableOffset, 0, false)
|
||||
ibe.pushToJournal("encodeAndAttachIfd", ">", "Beginning encoding process: (%d) [%s] NEXT-IFD-OFFSET-TO-WRITE=(0x%08x)", i, thisIb.ii.IfdName, nextIfdOffsetToWrite)
|
||||
|
||||
ibe.pushToJournal("encodeAndAttachIfd", ">", "Calculating size: (%d) [%s]", i, thisIb.ii.IfdName)
|
||||
|
||||
_, tableSize, allocatedDataSize, _, err := ibe.encodeIfdToBytes(thisIb, ifdAddressableOffset, 0, false)
|
||||
log.PanicIf(err)
|
||||
|
||||
ibe.pushToJournal("encodeAndAttachIfd", "<", "Finished calculating size: (%d) [%s]", i, thisIb.ii.IfdName)
|
||||
|
||||
ifdAddressableOffset += tableSize
|
||||
nextIfdOffsetToWrite = ifdAddressableOffset + allocatedDataSize
|
||||
|
||||
|
@ -368,9 +441,15 @@ func (ibe *IfdByteEncoder) encodeAndAttachIfd(ib *IfdBuilder, ifdAddressableOffs
|
|||
|
||||
setNextIb := thisIb.nextIb != nil
|
||||
|
||||
tableAndAllocated, tableSize, allocatedDataSize, childIfdSizes, err := ibe.encodeIfdToBytes(ib, ifdAddressableOffset, nextIfdOffsetToWrite, setNextIb)
|
||||
ibe.pushToJournal("encodeAndAttachIfd", ">", "Encoding starting: (%d) [%s] NEXT-IFD-OFFSET-TO-WRITE=(0x%08x)", i, thisIb.ii.IfdName, nextIfdOffsetToWrite)
|
||||
|
||||
tableAndAllocated, tableSize, allocatedDataSize, childIfdSizes, err :=
|
||||
ibe.encodeIfdToBytes(thisIb, ifdAddressableOffset, nextIfdOffsetToWrite, setNextIb)
|
||||
|
||||
log.PanicIf(err)
|
||||
|
||||
ibe.pushToJournal("encodeAndAttachIfd", "<", "Encoding done: (%d) [%s]", i, thisIb.ii.IfdName)
|
||||
|
||||
totalChildIfdSize := uint32(0)
|
||||
for _, childIfdSize := range childIfdSizes {
|
||||
totalChildIfdSize += childIfdSize
|
||||
|
@ -383,10 +462,17 @@ func (ibe *IfdByteEncoder) encodeAndAttachIfd(ib *IfdBuilder, ifdAddressableOffs
|
|||
_, err = b.Write(tableAndAllocated)
|
||||
log.PanicIf(err)
|
||||
|
||||
// TODO(dustin): !! The core problem is the offset being the same for two IFDs.
|
||||
// Advance past what we've allocated, thus far.
|
||||
ifdAddressableOffset = nextIfdOffsetToWrite
|
||||
|
||||
ibe.pushToJournal("encodeAndAttachIfd", "<", "Finishing encoding process: (%d) [%s] [FINAL:] NEXT-IFD-OFFSET-TO-WRITE=(0x%08x)", i, ib.ii.IfdName, nextIfdOffsetToWrite)
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
ibe.pushToJournal("encodeAndAttachIfd", "<", "%s", ib)
|
||||
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
"reflect"
|
||||
"fmt"
|
||||
"strings"
|
||||
"path"
|
||||
"sort"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
)
|
||||
|
@ -925,3 +927,113 @@ func ExampleIfdByteEncoder_EncodeToExif() {
|
|||
// 4: IfdTagEntry<TAG-IFD=[] TAG-ID=(0x13e) TAG-TYPE=[RATIONAL] UNIT-COUNT=(1)> [[{286335522 858997828}]]
|
||||
// 5: IfdTagEntry<TAG-IFD=[] TAG-ID=(0x9201) TAG-TYPE=[SRATIONAL] UNIT-COUNT=(1)> [[{286335522 858997828}]]
|
||||
}
|
||||
|
||||
func TestNewIfdBuilderFromExistingChain_RealData(t *testing.T) {
|
||||
filepath := path.Join(assetsPath, "NDM_8901.jpg")
|
||||
|
||||
e := NewExif()
|
||||
|
||||
|
||||
rawExif, err := e.SearchAndExtractExif(filepath)
|
||||
log.PanicIf(err)
|
||||
|
||||
// Decode from binary.
|
||||
|
||||
_, index, err := e.Collect(rawExif)
|
||||
log.PanicIf(err)
|
||||
|
||||
originalTags := index.RootIfd.DumpTags()
|
||||
|
||||
// Encode back to binary.
|
||||
|
||||
ibe := NewIfdByteEncoder()
|
||||
|
||||
rootIb := NewIfdBuilderFromExistingChain(index.RootIfd, rawExif)
|
||||
|
||||
updatedExif, err := ibe.EncodeToExif(rootIb)
|
||||
log.PanicIf(err)
|
||||
|
||||
// Parse again.
|
||||
|
||||
_, index, err = e.Collect(updatedExif)
|
||||
log.PanicIf(err)
|
||||
|
||||
recoveredTags := index.RootIfd.DumpTags()
|
||||
|
||||
|
||||
// Validate that all of the same IFDs were presented.
|
||||
|
||||
originalIfdTags := make([][2]interface{}, 0)
|
||||
for _, ite := range originalTags {
|
||||
if ite.ChildIfdName != "" {
|
||||
originalIfdTags = append(originalIfdTags, [2]interface{} { ite.Ii, ite.TagId })
|
||||
}
|
||||
}
|
||||
|
||||
recoveredIfdTags := make([][2]interface{}, 0)
|
||||
for _, ite := range recoveredTags {
|
||||
if ite.ChildIfdName != "" {
|
||||
recoveredIfdTags = append(recoveredIfdTags, [2]interface{} { ite.Ii, ite.TagId })
|
||||
}
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(recoveredIfdTags, originalIfdTags) != true {
|
||||
fmt.Printf("Original IFD tags:\n\n")
|
||||
|
||||
for i, x := range originalIfdTags {
|
||||
fmt.Printf(" %02d %v\n", i, x)
|
||||
}
|
||||
|
||||
fmt.Printf("\nRecovered IFD tags:\n\n")
|
||||
|
||||
for i, x := range recoveredIfdTags {
|
||||
fmt.Printf(" %02d %v\n", i, x)
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
|
||||
t.Fatalf("Recovered IFD tags are not correct.")
|
||||
}
|
||||
|
||||
|
||||
// Validate that all of the tags owned by the IFDs were presented. The tags
|
||||
// might not be in the same order since the IFD tags are allocated after
|
||||
// the non-IFD ones.
|
||||
|
||||
originalNonIfdTags := make([]string, 0)
|
||||
for _, ite := range originalTags {
|
||||
if ite.ChildIfdName == "" {
|
||||
originalNonIfdTags = append(originalNonIfdTags, fmt.Sprintf("%s 0x%02x", ite.Ii, ite.TagId))
|
||||
}
|
||||
}
|
||||
|
||||
sort.StringSlice(originalNonIfdTags).Sort()
|
||||
|
||||
recoveredNonIfdTags := make([]string, 0)
|
||||
for _, ite := range recoveredTags {
|
||||
if ite.ChildIfdName == "" {
|
||||
recoveredNonIfdTags = append(recoveredNonIfdTags, fmt.Sprintf("%s 0x%02x", ite.Ii, ite.TagId))
|
||||
}
|
||||
}
|
||||
|
||||
sort.StringSlice(recoveredNonIfdTags).Sort()
|
||||
|
||||
|
||||
if reflect.DeepEqual(recoveredNonIfdTags, originalNonIfdTags) != true {
|
||||
fmt.Printf("Original non-IFD tags:\n\n")
|
||||
|
||||
for i, x := range originalNonIfdTags {
|
||||
fmt.Printf(" %02d %v\n", i, x)
|
||||
}
|
||||
|
||||
fmt.Printf("\nRecovered non-IFD tags:\n\n")
|
||||
|
||||
for i, x := range recoveredNonIfdTags {
|
||||
fmt.Printf(" %02d %v\n", i, x)
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
|
||||
t.Fatalf("Recovered non-IFD tags are not correct.")
|
||||
}
|
||||
}
|
||||
|
|
150
ifd_enumerate.go
150
ifd_enumerate.go
|
@ -228,7 +228,7 @@ func (ie *IfdEnumerate) ParseIfd(ii IfdIdentity, ifdIndex int, ite *IfdTagEnumer
|
|||
return nextIfdOffset, entries, nil
|
||||
}
|
||||
|
||||
// Scan enumerates the different EXIF blocks (called IFDs).
|
||||
// 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 {
|
||||
|
@ -270,6 +270,7 @@ func (ie *IfdEnumerate) Scan(ifdOffset uint32, visitor TagVisitor) (err error) {
|
|||
}
|
||||
|
||||
|
||||
// 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
|
||||
|
@ -307,6 +308,19 @@ func (ifd *Ifd) TagValue(ite *IfdTagEntry) (value interface{}, err error) {
|
|||
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) {
|
||||
|
@ -363,14 +377,61 @@ func (ifd Ifd) String() string {
|
|||
parentOffset = ifd.ParentIfd.Offset
|
||||
}
|
||||
|
||||
return fmt.Sprintf("IFD<ID=(%d) N=[%s] IDX=(%d) OFF=(0x%04x) COUNT=(%d) CHILDREN=(%d) PARENT=(0x%04x) NEXT-IFD=(0x%04x)", ifd.Id, ifd.Name, ifd.Index, ifd.Offset, len(ifd.Entries), len(ifd.Children), parentOffset, ifd.NextIfdOffset)
|
||||
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) printTree(level int, printTags bool, nextLink bool) {
|
||||
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
|
||||
}
|
||||
|
||||
// PrintTagTree 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 := " "
|
||||
|
@ -393,19 +454,17 @@ func (ifd Ifd) printTree(level int, printTags bool, nextLink bool) {
|
|||
|
||||
ti := NewTagIndex()
|
||||
for _, tag := range ifd.Entries {
|
||||
if printTags == true {
|
||||
if tag.ChildIfdName != "" {
|
||||
fmt.Printf("%s - TAG: %s\n", indent, tag)
|
||||
} else {
|
||||
it, err := ti.Get(ifd.Identity(), tag.TagId)
|
||||
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)
|
||||
tagName := ""
|
||||
if err == nil {
|
||||
tagName = it.Name
|
||||
}
|
||||
|
||||
fmt.Printf("%s - TAG: %s NAME=[%s]\n", indent, tag, tagName)
|
||||
}
|
||||
|
||||
if tag.ChildIfdName != "" {
|
||||
|
@ -416,7 +475,7 @@ func (ifd Ifd) printTree(level int, printTags bool, nextLink bool) {
|
|||
log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdName)
|
||||
}
|
||||
|
||||
childIfd.printTree(level + 1, printTags, false)
|
||||
childIfd.printTagTree(level + 1, false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -425,13 +484,61 @@ func (ifd Ifd) printTree(level int, printTags bool, nextLink bool) {
|
|||
}
|
||||
|
||||
if ifd.NextIfd != nil {
|
||||
ifd.NextIfd.printTree(level, printTags, true)
|
||||
ifd.NextIfd.printTagTree(level, true)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintTree prints the IFD hierarchy.
|
||||
func (ifd Ifd) PrintTree(printTags bool) {
|
||||
ifd.printTree(0, printTags, false)
|
||||
// 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 {
|
||||
|
@ -545,6 +652,7 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
|
|||
name := ii.IfdName
|
||||
index := queue[0].Index
|
||||
offset := queue[0].Offset
|
||||
|
||||
parentIfd := queue[0].Parent
|
||||
|
||||
queue = queue[1:]
|
||||
|
@ -617,13 +725,13 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
|
|||
continue
|
||||
}
|
||||
|
||||
childId := IfdIdentity{
|
||||
childIi := IfdIdentity{
|
||||
ParentIfdName: name,
|
||||
IfdName: entry.ChildIfdName,
|
||||
}
|
||||
|
||||
qi := QueuedIfd{
|
||||
Ii: childId,
|
||||
Ii: childIi,
|
||||
Index: 0,
|
||||
Offset: entry.ValueOffset,
|
||||
Parent: &ifd,
|
||||
|
|
Loading…
Reference in New Issue