encode: We now encode child IFDs as they occur.

- ..rather than after the normal tags.
pull/3/head
Dustin Oprea 2018-05-28 20:04:02 -04:00
parent 5996e32e27
commit 3c1c668a9f
3 changed files with 184 additions and 83 deletions

View File

@ -298,9 +298,7 @@ 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) (firstIb *IfdBuilder) {
itevr := NewIfdTagEntryValueResolver(exifData, rootIfd.ByteOrder)
func NewIfdBuilderFromExistingChain(rootIfd *Ifd, itevr *IfdTagEntryValueResolver) (firstIb *IfdBuilder) {
// 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 lastIb *IfdBuilder
@ -318,15 +316,6 @@ func NewIfdBuilderFromExistingChain(rootIfd *Ifd, exifData []byte) (firstIb *Ifd
err := newIb.AddTagsFromExisting(thisExistingIfd, itevr, nil, nil)
log.PanicIf(err)
// Any child IFDs will still not be copied. Do that now.
for _, childIfd := range thisExistingIfd.Children {
childIb := NewIfdBuilderFromExistingChain(childIfd, exifData)
err = newIb.AddChildIb(childIb)
log.PanicIf(err)
}
lastIb = newIb
i++
}
@ -375,14 +364,14 @@ func (ib *IfdBuilder) SetThumbnail(data []byte) (err error) {
if data == nil || len(data) == 0 {
// TODO(dustin): !! Debugging.
fmt.Printf("Thumbnail empty.\n")
// fmt.Printf("Thumbnail empty.\n")
log.Panic("thumbnail is empty")
}
ib.thumbnailData = data
fmt.Printf("SETTING THUMBNAIL.\n")
// fmt.Printf("SETTING THUMBNAIL.\n")
ibtvfb := NewIfdBuilderTagValueFromBytes(ib.thumbnailData)
offsetBt := NewBuilderTag(ib.ii, ThumbnailOffsetTagId, TypeLong, ibtvfb)
@ -398,7 +387,7 @@ fmt.Printf("SETTING THUMBNAIL.\n")
// TODO(dustin): !! Debugging.
fmt.Printf("Set thumbnail into IB.\n")
// fmt.Printf("Set thumbnail into IB.\n")
return nil
@ -664,7 +653,7 @@ func (ib *IfdBuilder) Set(bt builderTag) (err error) {
if err == nil {
ib.tags[position] = bt
} else if log.Is(err, ErrTagEntryNotFound) == true {
err = ib.Add(bt)
err = ib.add(bt)
log.PanicIf(err)
} else {
log.Panic(err)
@ -711,7 +700,7 @@ func (ib *IfdBuilder) Find(tagId uint16) (position int, err error) {
return found[0], nil
}
func (ib *IfdBuilder) Add(bt builderTag) (err error) {
func (ib *IfdBuilder) add(bt builderTag) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
@ -726,11 +715,24 @@ func (ib *IfdBuilder) Add(bt builderTag) (err error) {
log.Panicf("builderTag value is not set: %s", bt)
}
ib.tags = append(ib.tags, bt)
return nil
}
func (ib *IfdBuilder) Add(bt builderTag) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if bt.value.IsIb() == true {
log.Panicf("child IfdBuilders must be added via AddChildIb() not Add()")
log.Panicf("child IfdBuilders must be added via AddChildIb() or AddTagsFromExisting(), not Add()")
}
ib.tags = append(ib.tags, bt)
err = ib.add(bt)
log.PanicIf(err)
return nil
}
@ -757,16 +759,28 @@ func (ib *IfdBuilder) AddChildIb(childIb *IfdBuilder) (err error) {
}
}
bt := ib.NewBuilderTagFromBuilder(childIb)
ib.tags = append(ib.tags, bt)
return nil
}
func (ib *IfdBuilder) NewBuilderTagFromBuilder(childIb *IfdBuilder) (bt builderTag) {
defer func() {
if state := recover(); state != nil {
err := log.Wrap(state.(error))
log.Panic(err)
}
}()
value := NewIfdBuilderTagValueFromIfdBuilder(childIb)
bt := NewChildIfdBuilderTag(
bt = NewChildIfdBuilderTag(
ib.ii,
childIb.ifdTagId,
value)
ib.tags = append(ib.tags, bt)
return nil
return bt
}
// AddTagsFromExisting does a verbatim copy of the entries in `ifd` to this
@ -782,8 +796,10 @@ func (ib *IfdBuilder) AddTagsFromExisting(ifd *Ifd, itevr *IfdTagEntryValueResol
thumbnailData, err := ifd.Thumbnail()
if err == nil {
// TODO(dustin): The thumbnail tags will be added out of order.
// TODO(dustin): !! Debugging.
fmt.Printf("Importing thumbnail: %s\n", ifd.Identity())
// fmt.Printf("Importing thumbnail: %s\n", ifd.Identity())
err = ib.SetThumbnail(thumbnailData)
log.PanicIf(err)
@ -792,17 +808,12 @@ fmt.Printf("Importing thumbnail: %s\n", ifd.Identity())
} else {
// TODO(dustin): !! Debugging.
fmt.Printf("NO THUMBNAIL FOUND: %s\n", ifd.Identity())
// fmt.Printf("NO THUMBNAIL FOUND: %s\n", ifd.Identity())
}
for _, ite := range ifd.Entries {
if ite.ChildIfdName != "" {
// If we want to add an IFD tag, we'll have to build it first and
// *then* add it via a different method.
continue
} else if ite.TagId == ThumbnailOffsetTagId || ite.TagId == ThumbnailSizeTagId {
for i, ite := range ifd.Entries {
if ite.TagId == ThumbnailOffsetTagId || ite.TagId == ThumbnailSizeTagId {
// These will be added on-the-fly when we encode.
continue
@ -838,34 +849,79 @@ fmt.Printf("NO THUMBNAIL FOUND: %s\n", ifd.Identity())
}
}
var value *IfdBuilderTagValue
var bt builderTag
if itevr == nil {
// rawValueOffsetCopy is our own private copy of the original data.
// It should always be four-bytes, but just copy whatever there is.
rawValueOffsetCopy := make([]byte, len(ite.RawValueOffset))
copy(rawValueOffsetCopy, ite.RawValueOffset)
if ite.ChildIfdName != "" {
// If we want to add an IFD tag, we'll have to build it first and
// *then* add it via a different method.
value = NewIfdBuilderTagValueFromBytes(rawValueOffsetCopy)
} else {
var err error
if itevr == nil {
// We don't have any ability to resolve the structure of the
// child-IFD. Just install it as a normal tag rather than a
// fully-structured child-IFD. We're going to blank the value,
// though, since its original offset will no longer be valid
// (nor does it matter since this is just a temporary
// placeholder, in this situation).
value := NewIfdBuilderTagValueFromBytes([]byte { 0, 0, 0, 0 })
bt = NewBuilderTag(ite.Ii, ite.TagId, ite.TagType, value)
} else {
// Figure out which of the child-IFDs that are associated with
// this IFD represents this specific child IFD.
valueBytes, err := itevr.ValueBytes(ite)
if err != nil {
if log.Is(err, ErrUnhandledUnknownTypedTag) == true {
ifdBuilderLogger.Warningf(nil, "Unknown-type tag can't be parsed so it can't be copied to the new IFD.")
continue
var childIfd *Ifd
for _, thisChildIfd := range ifd.Children {
if thisChildIfd.ParentTagIndex != i {
continue
} else if thisChildIfd.TagId != 0xffff && thisChildIfd.TagId != ite.TagId {
log.Panicf("child-IFD tag is not correct: TAG-POSITION=(%d) ITE=%s CHILD-IFD=%s", thisChildIfd.ParentTagIndex, ite, thisChildIfd)
}
childIfd = thisChildIfd
break
}
log.Panic(err)
if childIfd == nil {
childTagIds := make([]string, len(ifd.Children))
for j, childIfd := range ifd.Children {
childTagIds[j] = fmt.Sprintf("0x%02x (parent tag-position %d)", childIfd.TagId, childIfd.ParentTagIndex)
}
log.Panicf("could not find child IFD for child ITE: II=[%s] TAG-ID=(0x%02x) CURRENT-TAG-POSITION=(%d) CHILDREN=%v", ite.Ii, ite.TagId, i, childTagIds)
}
childIb := NewIfdBuilderFromExistingChain(childIfd, itevr)
bt = ib.NewBuilderTagFromBuilder(childIb)
}
} else {
var value *IfdBuilderTagValue
if itevr == nil {
// rawValueOffsetCopy is our own private copy of the original data.
// It should always be four-bytes, but just copy whatever there is.
rawValueOffsetCopy := make([]byte, len(ite.RawValueOffset))
copy(rawValueOffsetCopy, ite.RawValueOffset)
value = NewIfdBuilderTagValueFromBytes(rawValueOffsetCopy)
} else {
var err error
valueBytes, err := itevr.ValueBytes(ite)
if err != nil {
if log.Is(err, ErrUnhandledUnknownTypedTag) == true {
ifdBuilderLogger.Warningf(nil, "Unknown-type tag can't be parsed so it can't be copied to the new IFD.")
continue
}
log.Panic(err)
}
value = NewIfdBuilderTagValueFromBytes(valueBytes)
}
value = NewIfdBuilderTagValueFromBytes(valueBytes)
bt = NewBuilderTag(ifd.Ii, ite.TagId, ite.TagType, value)
}
bt := NewBuilderTag(ifd.Ii, ite.TagId, ite.TagType, value)
err := ib.Add(bt)
err := ib.add(bt)
log.PanicIf(err)
}
@ -883,7 +939,7 @@ func (ib *IfdBuilder) AddFromConfig(tagId uint16, value interface{}) (err error)
bt := NewStandardBuilderTagFromConfig(ib.ii, tagId, ib.byteOrder, value)
err = ib.Add(bt)
err = ib.add(bt)
log.PanicIf(err)
return nil
@ -922,7 +978,7 @@ func (ib *IfdBuilder) AddFromConfigWithName(tagName string, value interface{}) (
bt := NewStandardBuilderTagFromConfigWithName(ib.ii, tagName, ib.byteOrder, value)
err = ib.Add(bt)
err = ib.add(bt)
log.PanicIf(err)
return nil

View File

@ -1211,7 +1211,7 @@ func TestDeleteAll(t *testing.T) {
}
}
func TestNewIfdBuilderFromExistingChain(t *testing.T) {
func Test_IfdBuilder_CreateIfdBuilderFromExistingChain(t *testing.T) {
defer func() {
if state := recover(); state != nil {
err := log.Wrap(state.(error))
@ -1223,13 +1223,15 @@ func TestNewIfdBuilderFromExistingChain(t *testing.T) {
filepath := path.Join(assetsPath, "NDM_8901.jpg")
exifData, err := e.SearchAndExtractExif(filepath)
rawExif, err := e.SearchAndExtractExif(filepath)
log.PanicIf(err)
_, index, err := e.Collect(exifData)
_, index, err := e.Collect(rawExif)
log.PanicIf(err)
ib := NewIfdBuilderFromExistingChain(index.RootIfd, exifData)
itevr := NewIfdTagEntryValueResolver(rawExif, index.RootIfd.ByteOrder)
ib := NewIfdBuilderFromExistingChain(index.RootIfd, itevr)
lines := ib.DumpToStrings()
expected := []string {
@ -1295,7 +1297,7 @@ func TestNewIfdBuilderFromExistingChain(t *testing.T) {
// TODO(dustin): !! Test with an actual GPS-attached image.
func TestNewIfdBuilderFromExistingChain_RealData(t *testing.T) {
func Test_IfdBuilder_CreateIfdBuilderFromExistingChain_RealData(t *testing.T) {
filepath := path.Join(assetsPath, "NDM_8901.jpg")
e := NewExif()
@ -1310,12 +1312,14 @@ func TestNewIfdBuilderFromExistingChain_RealData(t *testing.T) {
log.PanicIf(err)
originalTags := index.RootIfd.DumpTags()
index.RootIfd.PrintTagTree(true)
// Encode back to binary.
ibe := NewIfdByteEncoder()
rootIb := NewIfdBuilderFromExistingChain(index.RootIfd, rawExif)
itevr := NewIfdTagEntryValueResolver(rawExif, index.RootIfd.ByteOrder)
rootIb := NewIfdBuilderFromExistingChain(index.RootIfd, itevr)
updatedExif, err := ibe.EncodeToExif(rootIb)
log.PanicIf(err)
@ -1326,8 +1330,11 @@ func TestNewIfdBuilderFromExistingChain_RealData(t *testing.T) {
log.PanicIf(err)
recoveredTags := index.RootIfd.DumpTags()
index.RootIfd.PrintTagTree(true)
return
// Validate that all of the same IFDs were presented.
originalIfdTags := make([][2]interface{}, 0)
@ -1405,7 +1412,7 @@ func TestNewIfdBuilderFromExistingChain_RealData(t *testing.T) {
}
}
func TestNewIfdBuilderWithExistingIfd(t *testing.T) {
func Test_IfdBuilder_CreateIfdBuilderWithExistingIfd(t *testing.T) {
tagId := IfdTagIdWithIdentityOrFail(GpsIi)
parentIfd := &Ifd{

View File

@ -330,9 +330,17 @@ type Ifd struct {
ByteOrder binary.ByteOrder
Ii IfdIdentity
TagId uint16
Id int
ParentIfd *Ifd
// ParentTagIndex is our tag position in the parent IFD, if we had a parent
// (if `ParentIfd` is not nil and we weren't an IFD referenced as a sibling
// instead of as a child).
ParentTagIndex int
Name string
Index int
Offset uint32
@ -429,7 +437,7 @@ func (ifd Ifd) String() string {
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)
return fmt.Sprintf("Ifd<ID=(%d) PARENT-IFD=[%s] IFD=[%s] INDEX=(%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) Thumbnail() (data []byte, err error) {
@ -497,7 +505,7 @@ func (ifd *Ifd) DumpTags() []*IfdTagEntry {
return ifd.dumpTags(nil)
}
func (ifd *Ifd) printTagTree(index, level int, nextLink bool) {
func (ifd *Ifd) printTagTree(populateValues bool, index, level int, nextLink bool) {
indent := strings.Repeat(" ", level * 2)
prefix := " "
@ -505,7 +513,7 @@ func (ifd *Ifd) printTagTree(index, level int, nextLink bool) {
prefix = ">"
}
fmt.Printf("%s%sIFD: %s INDEX=(%d)\n", indent, prefix, ifd, index)
fmt.Printf("%s%sIFD: %s\n", indent, prefix, ifd)
// Quickly create an index of the child-IFDs.
@ -530,7 +538,15 @@ func (ifd *Ifd) printTagTree(index, level int, nextLink bool) {
tagName = it.Name
}
fmt.Printf("%s - TAG: %s NAME=[%s]\n", indent, tag, tagName)
var value interface{}
if populateValues == true {
var err error
value, err = ifd.TagValue(tag)
log.PanicIf(err)
}
fmt.Printf("%s - TAG: %s NAME=[%s] VALUE=[%v]\n", indent, tag, tagName, value)
}
if tag.ChildIfdName != "" {
@ -541,7 +557,7 @@ func (ifd *Ifd) printTagTree(index, level int, nextLink bool) {
log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdName)
}
childIfd.printTagTree(0, level + 1, false)
childIfd.printTagTree(populateValues, 0, level + 1, false)
}
}
@ -550,13 +566,13 @@ func (ifd *Ifd) printTagTree(index, level int, nextLink bool) {
}
if ifd.NextIfd != nil {
ifd.NextIfd.printTagTree(index + 1, level, true)
ifd.NextIfd.printTagTree(populateValues, index + 1, level, true)
}
}
// PrintTagTree prints the IFD hierarchy.
func (ifd *Ifd) PrintTagTree() {
ifd.printTagTree(0, 0, false)
func (ifd *Ifd) PrintTagTree(populateValues bool) {
ifd.printTagTree(populateValues, 0, 0, false)
}
func (ifd *Ifd) printIfdTree(level int, nextLink bool) {
@ -671,9 +687,16 @@ func (ifd *Ifd) DumpTree() []string {
type QueuedIfd struct {
Ii IfdIdentity
TagId uint16
Index int
Offset uint32
Parent *Ifd
// ParentTagIndex is our tag position in the parent IFD, if we had a parent
// (if `ParentIfd` is not nil and we weren't an IFD referenced as a sibling
// instead of as a child).
ParentTagIndex int
}
@ -700,6 +723,8 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
queue := []QueuedIfd{
{
Ii: RootIi,
TagId: 0xffff,
Index: 0,
Offset: rootIfdOffset,
},
@ -712,14 +737,14 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
break
}
ii := queue[0].Ii
qi := queue[0]
ii := qi.Ii
name := ii.IfdName
index := queue[0].Index
offset := queue[0].Offset
parentIfd := queue[0].Parent
index := qi.Index
offset := qi.Offset
parentIfd := qi.Parent
queue = queue[1:]
@ -741,53 +766,62 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
entriesByTagId[tag.TagId] = append(tags, tag)
}
ifd := Ifd{
ifd := &Ifd{
addressableData: ie.exifData[ExifAddressableAreaStart:],
ByteOrder: ie.byteOrder,
Ii: ii,
TagId: qi.TagId,
Id: id,
ParentIfd: parentIfd,
ParentTagIndex: qi.ParentTagIndex,
Name: name,
Index: index,
Offset: offset,
Entries: entries,
EntriesByTagId: entriesByTagId,
// This is populated as each child is processed.
Children: make([]*Ifd, 0),
NextIfdOffset: nextIfdOffset,
thumbnailData: thumbnailData,
}
// Add ourselves to a big list of IFDs.
ifds = append(ifds, &ifd)
ifds = append(ifds, ifd)
// Install ourselves into a by-id lookup table (keys are unique).
tree[id] = &ifd
tree[id] = ifd
// Install into by-name buckets.
if list_, found := lookup[ii]; found == true {
lookup[ii] = append(list_, &ifd)
lookup[ii] = append(list_, ifd)
} else {
list_ = make([]*Ifd, 1)
list_[0] = &ifd
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
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)
parentIfd.Children = append(parentIfd.Children, ifd)
}
// Determine if any of our entries is a child IFD and queue it.
for _, entry := range entries {
for i, entry := range entries {
if entry.ChildIfdName == "" {
continue
}
@ -799,9 +833,12 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
qi := QueuedIfd{
Ii: childIi,
TagId: entry.TagId,
Index: 0,
Offset: entry.ValueOffset,
Parent: &ifd,
Parent: ifd,
ParentTagIndex: i,
}
queue = append(queue, qi)
@ -810,10 +847,11 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
// 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
edges[nextIfdOffset] = ifd
qi := QueuedIfd{
Ii: ii,
TagId: 0xffff,
Index: index + 1,
Offset: nextIfdOffset,
}