mirror of https://github.com/dsoprea/go-exif.git
ifd_builder: Can now build a complete chain from existing EXIF.
- ifd_builder: - We now validate that `builderTag` instances are either a byte array or a child Ifd before allowing to add. - Renamed `builderTag.valueBytes` to `builderTag.value` (it's not necessarily bytes). - `builderTag` now gets the child IFD name if representing a child IFD. - Added ability to dump structure to string slice. - Added NewIfdBuilderFromExistingChain to reconstruct whole chain from a read IFD chain (collection). - Wrote IB dumper. - Added structural tests. - Moved ifd_tag_entry types to separate file. - tags_unknown: Unknown-type tags can now satisfy the `UnknownTagValue` interface to return `[]byte`. - `IfdTagEntry.ValueBytes` automatically uses this. Else, error if not `[]byte` or `string`. - ifd_enumerate - Now embed `ByteOrder` in `Ifd` (we otherwise require it to be passed a bit too often). - Renamed some occurences of "IFD" to "IB" where an actual IB in order to not be confusing.pull/3/head
parent
50eafa98d6
commit
4bea09dc78
237
ifd_builder.go
237
ifd_builder.go
|
@ -4,6 +4,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"bytes"
|
||||
"strings"
|
||||
|
||||
"encoding/binary"
|
||||
|
||||
|
@ -20,20 +21,37 @@ var (
|
|||
|
||||
|
||||
// TODO(dustin): !! Make sure we either replace existing IFDs or validate that the IFD doesn't already exist.
|
||||
// TODO(dustin): !! Add test for NewIfdBuilderWithExistingIfd.
|
||||
|
||||
|
||||
type builderTag struct {
|
||||
// ifdName is non-empty if represents a child-IFD.
|
||||
ifdName string
|
||||
|
||||
tagId uint16
|
||||
|
||||
// value is either a value that can be encoded, an IfdBuilder instance (for
|
||||
// child IFDs), or an IfdTagEntry instance representing an existing,
|
||||
// previously-stored tag.
|
||||
valueBytes interface{}
|
||||
value interface{}
|
||||
}
|
||||
|
||||
func (bt builderTag) String() string {
|
||||
return fmt.Sprintf("BuilderTag<TAG-ID=(0x%02x) IFD=[%s] VALUE=[%v]>", bt.tagId, bt.ifdName, bt.valueBytes)
|
||||
valuePhrase := ""
|
||||
switch bt.value.(type) {
|
||||
case []byte:
|
||||
valueBytes := bt.value.([]byte)
|
||||
|
||||
if len(valueBytes) <= 8 {
|
||||
valuePhrase = fmt.Sprintf("%v", valueBytes)
|
||||
} else {
|
||||
valuePhrase = fmt.Sprintf("%v...", valueBytes[:8])
|
||||
}
|
||||
default:
|
||||
valuePhrase = fmt.Sprintf("%v", bt.value)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("BuilderTag<TAG-ID=(0x%02x) IFD=[%s] VALUE=[%v]>", bt.tagId, bt.ifdName, valuePhrase)
|
||||
}
|
||||
|
||||
|
||||
|
@ -57,21 +75,12 @@ type IfdBuilder struct {
|
|||
}
|
||||
|
||||
func NewIfdBuilder(ifdName string, byteOrder binary.ByteOrder) (ib *IfdBuilder) {
|
||||
found := false
|
||||
for _, validName := range validIfds {
|
||||
if validName == ifdName {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if found == false {
|
||||
log.Panicf("ifd not found: [%s]", ifdName)
|
||||
}
|
||||
|
||||
ib = &IfdBuilder{
|
||||
ifdName: ifdName,
|
||||
|
||||
// ifdName is empty unless it's a child-IFD.
|
||||
ifdTagId: IfdTagIds[ifdName],
|
||||
|
||||
byteOrder: byteOrder,
|
||||
tags: make([]builderTag, 0),
|
||||
}
|
||||
|
@ -79,16 +88,65 @@ func NewIfdBuilder(ifdName string, byteOrder binary.ByteOrder) (ib *IfdBuilder)
|
|||
return ib
|
||||
}
|
||||
|
||||
func NewIfdBuilderWithExistingIfd(ifd *Ifd, byteOrder binary.ByteOrder) (ib *IfdBuilder) {
|
||||
// NewIfdBuilderWithExistingIfd creates a new IB using the same header type
|
||||
// information as the given IFD.
|
||||
func NewIfdBuilderWithExistingIfd(ifd *Ifd) (ib *IfdBuilder) {
|
||||
ifdTagId, found := IfdTagIds[ifd.Name]
|
||||
if found == false {
|
||||
log.Panicf("tag-ID for IFD not found: [%s]", ifd.Name)
|
||||
}
|
||||
|
||||
ib = &IfdBuilder{
|
||||
ifdName: ifd.Name,
|
||||
byteOrder: byteOrder,
|
||||
ifdTagId: ifdTagId,
|
||||
byteOrder: ifd.ByteOrder,
|
||||
existingOffset: ifd.Offset,
|
||||
}
|
||||
|
||||
return ib
|
||||
}
|
||||
|
||||
// NewIfdBuilderFromExistingChain creates a chain of IB instances from an
|
||||
// IFD chain generated from real data.
|
||||
func NewIfdBuilderFromExistingChain(rootIfd *Ifd, exifData []byte) (rootIb *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
|
||||
for thisExistingIfd := rootIfd; thisExistingIfd != nil; thisExistingIfd = thisExistingIfd.NextIfd {
|
||||
lastIb := newIb
|
||||
|
||||
ifdName := thisExistingIfd.Name
|
||||
if ifdName == "" {
|
||||
ifdName = IfdStandard
|
||||
}
|
||||
|
||||
newIb = NewIfdBuilder(ifdName, binary.BigEndian)
|
||||
if lastIb != nil {
|
||||
lastIb.SetNextIfd(newIb)
|
||||
}
|
||||
|
||||
if rootIb == nil {
|
||||
rootIb = newIb
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
return rootIb
|
||||
}
|
||||
|
||||
func (ib *IfdBuilder) String() string {
|
||||
nextIfdPhrase := ""
|
||||
if ib.nextIfd != nil {
|
||||
|
@ -114,6 +172,90 @@ func (ioi *ifdOffsetIterator) Offset() uint32 {
|
|||
return ioi.offset
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
ti := NewTagIndex()
|
||||
|
||||
if len(ib.tags) > 0 {
|
||||
fmt.Printf("\n")
|
||||
|
||||
for i, tag := range ib.tags {
|
||||
_, isChildIb := IfdTagNames[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.ifdName, 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 {
|
||||
fmt.Printf("\n")
|
||||
|
||||
childIb := tag.value.(*IfdBuilder)
|
||||
childIb.dump(levels + 1)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
}
|
||||
|
||||
func (ib *IfdBuilder) Dump() {
|
||||
ib.dump(0)
|
||||
}
|
||||
|
||||
func (ib *IfdBuilder) dumpToStrings(thisIb *IfdBuilder, prefix string, lines []string) (linesOutput []string) {
|
||||
if lines == nil {
|
||||
linesOutput = make([]string, 0)
|
||||
} else {
|
||||
linesOutput = lines
|
||||
}
|
||||
|
||||
for i, tag := range thisIb.tags {
|
||||
line := fmt.Sprintf("<PARENTS=[%s] IFD-NAME=[%s]> IFD-TAG-ID=(0x%02x) CHILD-IFD=[%s] INDEX=(%d) TAG=[0x%02x]", prefix, thisIb.ifdName, thisIb.ifdTagId, tag.ifdName, i, tag.tagId)
|
||||
linesOutput = append(linesOutput, line)
|
||||
|
||||
if tag.ifdName != "" {
|
||||
childPrefix := ""
|
||||
if prefix == "" {
|
||||
childPrefix = fmt.Sprintf("%s", thisIb.ifdName)
|
||||
} else {
|
||||
childPrefix = fmt.Sprintf("%s->%s", prefix, thisIb.ifdName)
|
||||
}
|
||||
|
||||
linesOutput = thisIb.dumpToStrings(tag.value.(*IfdBuilder), childPrefix, linesOutput)
|
||||
}
|
||||
}
|
||||
|
||||
return linesOutput
|
||||
}
|
||||
|
||||
func (ib *IfdBuilder) DumpToStrings() (lines []string) {
|
||||
return ib.dumpToStrings(ib, "", lines)
|
||||
}
|
||||
|
||||
// calculateRawTableSize returns the number of bytes required just to store the
|
||||
// basic IFD header and tags. This needs to be called before we can even write
|
||||
|
@ -262,24 +404,6 @@ func (ib *IfdBuilder) BuildExif() (new []byte, err error) {
|
|||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
func (ib *IfdBuilder) Tags() (tags []builderTag) {
|
||||
return ib.tags
|
||||
}
|
||||
|
||||
func (ib *IfdBuilder) Dump() {
|
||||
fmt.Printf("IFD: %s\n", ib)
|
||||
|
||||
if len(ib.tags) > 0 {
|
||||
fmt.Printf("\n")
|
||||
|
||||
for i, tag := range ib.tags {
|
||||
fmt.Printf(" (%d): %s\n", i, tag)
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
}
|
||||
|
||||
func (ib *IfdBuilder) SetNextIfd(nextIfd *IfdBuilder) (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
|
@ -437,43 +561,45 @@ func (ib *IfdBuilder) Add(bt builderTag) (err error) {
|
|||
}
|
||||
}()
|
||||
|
||||
if bt.value != nil {
|
||||
switch bt.value.(type) {
|
||||
case []byte:
|
||||
default:
|
||||
log.Panicf("tag value must be a byte-slice or a child IFD-builder: %v", bt)
|
||||
}
|
||||
}
|
||||
|
||||
ib.tags = append(ib.tags, bt)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ib *IfdBuilder) AddChildIfd(childIfd *IfdBuilder) (err error) {
|
||||
func (ib *IfdBuilder) AddChildIb(childIb *IfdBuilder) (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
// TODO(dustin): !! We might not want to take an actual IfdBuilder instance, as
|
||||
// these are mutable in nature (unless we definitely want to
|
||||
// allow them to tbe chnaged right up until they're actually
|
||||
// written). We might be better with a final, immutable tag
|
||||
// container insted.
|
||||
|
||||
if childIfd.ifdTagId == 0 {
|
||||
log.Panicf("IFD [%s] can not be used as a child IFD (not associated with a tag-ID)")
|
||||
} else if childIfd.byteOrder != ib.byteOrder {
|
||||
log.Panicf("Child IFD does not have the same byte-order: [%s] != [%s]", childIfd.byteOrder, ib.byteOrder)
|
||||
if childIb.ifdTagId == 0 {
|
||||
log.Panicf("IFD can not be used as a child IFD (not associated with a tag-ID): %v", childIb)
|
||||
} else if childIb.byteOrder != ib.byteOrder {
|
||||
log.Panicf("Child IFD does not have the same byte-order: [%s] != [%s]", childIb.byteOrder, ib.byteOrder)
|
||||
}
|
||||
|
||||
bt := builderTag{
|
||||
ifdName: childIfd.ifdName,
|
||||
tagId: childIfd.ifdTagId,
|
||||
valueBytes: childIfd,
|
||||
ifdName: childIb.ifdName,
|
||||
tagId: childIb.ifdTagId,
|
||||
value: childIb,
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
ib.tags = append(ib.tags, bt)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddTagsFromExisting does a verbatim copy of the entries in `ifd` to this
|
||||
// builder. It excludes child IFDs. This must be added explicitly via
|
||||
// `AddChildIfd()`.
|
||||
// `AddChildIb()`.
|
||||
func (ib *IfdBuilder) AddTagsFromExisting(ifd *Ifd, itevr *IfdTagEntryValueResolver, includeTagIds []uint16, excludeTagIds []uint16) (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
|
@ -542,8 +668,15 @@ func (ib *IfdBuilder) AddTagsFromExisting(ifd *Ifd, itevr *IfdTagEntryValueResol
|
|||
if itevr != nil {
|
||||
var err error
|
||||
|
||||
bt.valueBytes, err = itevr.ValueBytes(&ite)
|
||||
log.PanicIf(err)
|
||||
bt.value, 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)
|
||||
}
|
||||
}
|
||||
|
||||
err := ib.Add(bt)
|
||||
|
|
|
@ -3,6 +3,8 @@ package exif
|
|||
import (
|
||||
"testing"
|
||||
"reflect"
|
||||
"bytes"
|
||||
"path"
|
||||
|
||||
"encoding/binary"
|
||||
|
||||
|
@ -18,33 +20,37 @@ func TestAdd(t *testing.T) {
|
|||
|
||||
bt := builderTag{
|
||||
tagId: 0x11,
|
||||
valueBytes: "test string",
|
||||
value: []byte("test string"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err := ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x22,
|
||||
valueBytes: "test string2",
|
||||
value: []byte("test string2"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x33,
|
||||
valueBytes: "test string3",
|
||||
value: []byte("test string3"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
originalShorts := []uint16 { 0x111, 0x222, 0x333 }
|
||||
originalBytes := []byte { 0x11, 0x22, 0x33 }
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x44,
|
||||
valueBytes: originalShorts,
|
||||
value: originalBytes,
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ib.ifdName != IfdStandard {
|
||||
t.Fatalf("IFD name not correct.")
|
||||
|
@ -64,25 +70,25 @@ func TestAdd(t *testing.T) {
|
|||
|
||||
if tags[0].tagId != 0x11 {
|
||||
t.Fatalf("tag (0) tag-ID not correct")
|
||||
} else if tags[0].valueBytes != "test string" {
|
||||
} else if bytes.Compare(tags[0].value.([]byte), []byte("test string")) != 0 {
|
||||
t.Fatalf("tag (0) value not correct")
|
||||
}
|
||||
|
||||
if tags[1].tagId != 0x22 {
|
||||
t.Fatalf("tag (1) tag-ID not correct")
|
||||
} else if tags[1].valueBytes != "test string2" {
|
||||
} else if bytes.Compare(tags[1].value.([]byte), []byte("test string2")) != 0 {
|
||||
t.Fatalf("tag (1) value not correct")
|
||||
}
|
||||
|
||||
if tags[2].tagId != 0x33 {
|
||||
t.Fatalf("tag (2) tag-ID not correct")
|
||||
} else if tags[2].valueBytes != "test string3" {
|
||||
} else if bytes.Compare(tags[2].value.([]byte), []byte("test string3")) != 0 {
|
||||
t.Fatalf("tag (2) value not correct")
|
||||
}
|
||||
|
||||
if tags[3].tagId != 0x44 {
|
||||
t.Fatalf("tag (3) tag-ID not correct")
|
||||
} else if reflect.DeepEqual(tags[3].valueBytes.([]uint16), originalShorts) != true {
|
||||
} else if bytes.Compare(tags[3].value.([]byte), originalBytes) != 0 {
|
||||
t.Fatalf("tag (3) value not correct")
|
||||
}
|
||||
}
|
||||
|
@ -105,33 +111,35 @@ func TestSetNextIfd(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAddChildIfd(t *testing.T) {
|
||||
func TestAddChildIb(t *testing.T) {
|
||||
|
||||
ib := NewIfdBuilder(IfdStandard, binary.BigEndian)
|
||||
|
||||
bt := builderTag{
|
||||
tagId: 0x11,
|
||||
valueBytes: "test string",
|
||||
value: []byte("test string"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err := ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
ibChild := NewIfdBuilder(IfdExif, binary.BigEndian)
|
||||
err := ib.AddChildIfd(ibChild)
|
||||
err = ib.AddChildIb(ibChild)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x22,
|
||||
valueBytes: "test string",
|
||||
value: []byte("test string"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ib.tags[0].tagId != 0x11 {
|
||||
t.Fatalf("first tag not correct")
|
||||
} else if ib.tags[1].tagId != ibChild.ifdTagId {
|
||||
t.Fatalf("second tag ID does not match child-IFD tag-ID")
|
||||
} else if ib.tags[1].valueBytes != ibChild {
|
||||
t.Fatalf("second tag ID does not match child-IFD tag-ID: (0x%02x) != (0x%02x)", ib.tags[1].tagId, ibChild.ifdTagId)
|
||||
} else if ib.tags[1].value != ibChild {
|
||||
t.Fatalf("second tagvalue does not match child-IFD")
|
||||
} else if ib.tags[2].tagId != 0x22 {
|
||||
t.Fatalf("third tag not correct")
|
||||
|
@ -241,24 +249,27 @@ func TestFindN_First_1(t *testing.T) {
|
|||
|
||||
bt := builderTag{
|
||||
tagId: 0x11,
|
||||
valueBytes: "test string",
|
||||
value: []byte("test string"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err := ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x22,
|
||||
valueBytes: "test string2",
|
||||
value: []byte("test string2"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x33,
|
||||
valueBytes: "test string3",
|
||||
value: []byte("test string3"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
found, err := ib.FindN(0x11, 1)
|
||||
log.PanicIf(err)
|
||||
|
@ -282,24 +293,27 @@ func TestFindN_First_2_1Returned(t *testing.T) {
|
|||
|
||||
bt := builderTag{
|
||||
tagId: 0x11,
|
||||
valueBytes: "test string",
|
||||
value: []byte("test string"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err := ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x22,
|
||||
valueBytes: "test string2",
|
||||
value: []byte("test string2"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x33,
|
||||
valueBytes: "test string3",
|
||||
value: []byte("test string3"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
found, err := ib.FindN(0x11, 2)
|
||||
log.PanicIf(err)
|
||||
|
@ -323,38 +337,43 @@ func TestFindN_First_2_2Returned(t *testing.T) {
|
|||
|
||||
bt := builderTag{
|
||||
tagId: 0x11,
|
||||
valueBytes: "test string",
|
||||
value: []byte("test string"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err := ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x22,
|
||||
valueBytes: "test string2",
|
||||
value: []byte("test string2"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x33,
|
||||
valueBytes: "test string3",
|
||||
value: []byte("test string3"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x11,
|
||||
valueBytes: "test string4",
|
||||
value: []byte("test string4"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x11,
|
||||
valueBytes: "test string5",
|
||||
value: []byte("test string5"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
found, err := ib.FindN(0x11, 2)
|
||||
log.PanicIf(err)
|
||||
|
@ -370,13 +389,13 @@ func TestFindN_First_2_2Returned(t *testing.T) {
|
|||
tags := ib.Tags()
|
||||
|
||||
bt = tags[found[0]]
|
||||
if bt.tagId != 0x11 || bt.valueBytes != "test string" {
|
||||
log.Panicf("Found entry 0 is not correct: (0x%02x) [%s]", bt.tagId, bt.valueBytes)
|
||||
if bt.tagId != 0x11 || bytes.Compare(bt.value.([]byte), []byte("test string")) != 0 {
|
||||
log.Panicf("Found entry 0 is not correct: (0x%02x) [%s]", bt.tagId, bt.value)
|
||||
}
|
||||
|
||||
bt = tags[found[1]]
|
||||
if bt.tagId != 0x11 || bt.valueBytes != "test string4" {
|
||||
log.Panicf("Found entry 1 is not correct: (0x%02x) [%s]", bt.tagId, bt.valueBytes)
|
||||
if bt.tagId != 0x11 || bytes.Compare(bt.value.([]byte), []byte("test string4")) != 0 {
|
||||
log.Panicf("Found entry 1 is not correct: (0x%02x) [%s]", bt.tagId, bt.value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -385,45 +404,51 @@ func TestFindN_Middle_WithDuplicates(t *testing.T) {
|
|||
|
||||
bt := builderTag{
|
||||
tagId: 0x11,
|
||||
valueBytes: "test string",
|
||||
value: []byte("test string"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err := ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x22,
|
||||
valueBytes: "test string2",
|
||||
value: []byte("test string2"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x33,
|
||||
valueBytes: "test string3",
|
||||
value: []byte("test string3"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x11,
|
||||
valueBytes: "test string4",
|
||||
value: []byte("test string4"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x11,
|
||||
valueBytes: "test string5",
|
||||
value: []byte("test string5"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x33,
|
||||
valueBytes: "test string6",
|
||||
value: []byte("test string6"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
found, err := ib.FindN(0x33, 1)
|
||||
log.PanicIf(err)
|
||||
|
@ -447,31 +472,35 @@ func TestFindN_Middle_NoDuplicates(t *testing.T) {
|
|||
|
||||
bt := builderTag{
|
||||
tagId: 0x11,
|
||||
valueBytes: "test string",
|
||||
value: []byte("test string"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err := ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x22,
|
||||
valueBytes: "test string2",
|
||||
value: []byte("test string2"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x33,
|
||||
valueBytes: "test string3",
|
||||
value: []byte("test string3"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x11,
|
||||
valueBytes: "test string4",
|
||||
value: []byte("test string4"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
found, err := ib.FindN(0x33, 1)
|
||||
log.PanicIf(err)
|
||||
|
@ -506,31 +535,35 @@ func TestFind_Hit(t *testing.T) {
|
|||
|
||||
bt := builderTag{
|
||||
tagId: 0x11,
|
||||
valueBytes: "test string",
|
||||
value: []byte("test string"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err := ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x22,
|
||||
valueBytes: "test string2",
|
||||
value: []byte("test string2"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x33,
|
||||
valueBytes: "test string3",
|
||||
value: []byte("test string3"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x11,
|
||||
valueBytes: "test string4",
|
||||
value: []byte("test string4"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
position, err := ib.Find(0x33)
|
||||
log.PanicIf(err)
|
||||
|
@ -552,33 +585,37 @@ func TestFind_Miss(t *testing.T) {
|
|||
|
||||
bt := builderTag{
|
||||
tagId: 0x11,
|
||||
valueBytes: "test string",
|
||||
value: []byte("test string"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err := ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x22,
|
||||
valueBytes: "test string2",
|
||||
value: []byte("test string2"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x33,
|
||||
valueBytes: "test string3",
|
||||
value: []byte("test string3"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x11,
|
||||
valueBytes: "test string4",
|
||||
value: []byte("test string4"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
_, err := ib.Find(0x99)
|
||||
_, err = ib.Find(0x99)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected an error.")
|
||||
} else if log.Is(err, ErrTagEntryNotFound) == false {
|
||||
|
@ -591,24 +628,27 @@ func TestReplace(t *testing.T) {
|
|||
|
||||
bt := builderTag{
|
||||
tagId: 0x11,
|
||||
valueBytes: "test string",
|
||||
value: []byte("test string"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err := ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x22,
|
||||
valueBytes: "test string2",
|
||||
value: []byte("test string2"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x33,
|
||||
valueBytes: "test string3",
|
||||
value: []byte("test string3"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
currentIds := make([]uint16, 3)
|
||||
for i, bt := range ib.Tags() {
|
||||
|
@ -621,10 +661,10 @@ func TestReplace(t *testing.T) {
|
|||
|
||||
bt = builderTag{
|
||||
tagId: 0x99,
|
||||
valueBytes: "test string4",
|
||||
value: []byte("test string4"),
|
||||
}
|
||||
|
||||
err := ib.Replace(0x22, bt)
|
||||
err = ib.Replace(0x22, bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
currentIds = make([]uint16, 3)
|
||||
|
@ -642,24 +682,27 @@ func TestReplaceN(t *testing.T) {
|
|||
|
||||
bt := builderTag{
|
||||
tagId: 0x11,
|
||||
valueBytes: "test string",
|
||||
value: []byte("test string"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err := ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x22,
|
||||
valueBytes: "test string2",
|
||||
value: []byte("test string2"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x33,
|
||||
valueBytes: "test string3",
|
||||
value: []byte("test string3"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
currentIds := make([]uint16, 3)
|
||||
for i, bt := range ib.Tags() {
|
||||
|
@ -672,10 +715,10 @@ func TestReplaceN(t *testing.T) {
|
|||
|
||||
bt = builderTag{
|
||||
tagId: 0xA9,
|
||||
valueBytes: "test string4",
|
||||
value: []byte("test string4"),
|
||||
}
|
||||
|
||||
err := ib.ReplaceAt(1, bt)
|
||||
err = ib.ReplaceAt(1, bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
currentIds = make([]uint16, 3)
|
||||
|
@ -693,31 +736,35 @@ func TestDeleteFirst(t *testing.T) {
|
|||
|
||||
bt := builderTag{
|
||||
tagId: 0x11,
|
||||
valueBytes: "test string",
|
||||
value: []byte("test string"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err := ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x22,
|
||||
valueBytes: "test string2",
|
||||
value: []byte("test string2"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x22,
|
||||
valueBytes: "test string3",
|
||||
value: []byte("test string3"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x33,
|
||||
valueBytes: "test string4",
|
||||
value: []byte("test string4"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
|
||||
if len(ib.Tags()) != 4 {
|
||||
|
@ -734,7 +781,7 @@ func TestDeleteFirst(t *testing.T) {
|
|||
}
|
||||
|
||||
|
||||
err := ib.DeleteFirst(0x22)
|
||||
err = ib.DeleteFirst(0x22)
|
||||
log.PanicIf(err)
|
||||
|
||||
if len(ib.Tags()) != 3 {
|
||||
|
@ -781,31 +828,35 @@ func TestDeleteN(t *testing.T) {
|
|||
|
||||
bt := builderTag{
|
||||
tagId: 0x11,
|
||||
valueBytes: "test string",
|
||||
value: []byte("test string"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err := ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x22,
|
||||
valueBytes: "test string2",
|
||||
value: []byte("test string2"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x22,
|
||||
valueBytes: "test string3",
|
||||
value: []byte("test string3"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x33,
|
||||
valueBytes: "test string4",
|
||||
value: []byte("test string4"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
|
||||
if len(ib.Tags()) != 4 {
|
||||
|
@ -822,7 +873,7 @@ func TestDeleteN(t *testing.T) {
|
|||
}
|
||||
|
||||
|
||||
err := ib.DeleteN(0x22, 1)
|
||||
err = ib.DeleteN(0x22, 1)
|
||||
log.PanicIf(err)
|
||||
|
||||
if len(ib.Tags()) != 3 {
|
||||
|
@ -869,31 +920,35 @@ func TestDeleteN_Two(t *testing.T) {
|
|||
|
||||
bt := builderTag{
|
||||
tagId: 0x11,
|
||||
valueBytes: "test string",
|
||||
value: []byte("test string"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err := ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x22,
|
||||
valueBytes: "test string2",
|
||||
value: []byte("test string2"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x22,
|
||||
valueBytes: "test string3",
|
||||
value: []byte("test string3"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x33,
|
||||
valueBytes: "test string4",
|
||||
value: []byte("test string4"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
|
||||
if len(ib.Tags()) != 4 {
|
||||
|
@ -910,7 +965,7 @@ func TestDeleteN_Two(t *testing.T) {
|
|||
}
|
||||
|
||||
|
||||
err := ib.DeleteN(0x22, 2)
|
||||
err = ib.DeleteN(0x22, 2)
|
||||
log.PanicIf(err)
|
||||
|
||||
if len(ib.Tags()) != 2 {
|
||||
|
@ -940,31 +995,35 @@ func TestDeleteAll(t *testing.T) {
|
|||
|
||||
bt := builderTag{
|
||||
tagId: 0x11,
|
||||
valueBytes: "test string",
|
||||
value: []byte("test string"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err := ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x22,
|
||||
valueBytes: "test string2",
|
||||
value: []byte("test string2"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x22,
|
||||
valueBytes: "test string3",
|
||||
value: []byte("test string3"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
bt = builderTag{
|
||||
tagId: 0x33,
|
||||
valueBytes: "test string4",
|
||||
value: []byte("test string4"),
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
err = ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
|
||||
|
||||
if len(ib.Tags()) != 4 {
|
||||
|
@ -1007,3 +1066,87 @@ func TestDeleteAll(t *testing.T) {
|
|||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewIfdBuilderFromExistingChain(t *testing.T) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err := log.Wrap(state.(error))
|
||||
log.PrintErrorf(err, "Test failure.")
|
||||
}
|
||||
}()
|
||||
|
||||
e := NewExif()
|
||||
|
||||
filepath := path.Join(assetsPath, "NDM_8901.jpg")
|
||||
|
||||
exifData, err := e.SearchAndExtractExif(filepath)
|
||||
log.PanicIf(err)
|
||||
|
||||
_, index, err := e.Collect(exifData)
|
||||
log.PanicIf(err)
|
||||
|
||||
ib := NewIfdBuilderFromExistingChain(index.RootIfd, exifData)
|
||||
lines := ib.DumpToStrings()
|
||||
|
||||
expected := []string {
|
||||
"<PARENTS=[] IFD-NAME=[IFD]> IFD-TAG-ID=(0x00) CHILD-IFD=[] INDEX=(0) TAG=[0x10f]",
|
||||
"<PARENTS=[] IFD-NAME=[IFD]> IFD-TAG-ID=(0x00) CHILD-IFD=[] INDEX=(1) TAG=[0x110]",
|
||||
"<PARENTS=[] IFD-NAME=[IFD]> IFD-TAG-ID=(0x00) CHILD-IFD=[] INDEX=(2) TAG=[0x112]",
|
||||
"<PARENTS=[] IFD-NAME=[IFD]> IFD-TAG-ID=(0x00) CHILD-IFD=[] INDEX=(3) TAG=[0x11a]",
|
||||
"<PARENTS=[] IFD-NAME=[IFD]> IFD-TAG-ID=(0x00) CHILD-IFD=[] INDEX=(4) TAG=[0x11b]",
|
||||
"<PARENTS=[] IFD-NAME=[IFD]> IFD-TAG-ID=(0x00) CHILD-IFD=[] INDEX=(5) TAG=[0x128]",
|
||||
"<PARENTS=[] IFD-NAME=[IFD]> IFD-TAG-ID=(0x00) CHILD-IFD=[] INDEX=(6) TAG=[0x132]",
|
||||
"<PARENTS=[] IFD-NAME=[IFD]> IFD-TAG-ID=(0x00) CHILD-IFD=[] INDEX=(7) TAG=[0x13b]",
|
||||
"<PARENTS=[] IFD-NAME=[IFD]> IFD-TAG-ID=(0x00) CHILD-IFD=[] INDEX=(8) TAG=[0x213]",
|
||||
"<PARENTS=[] IFD-NAME=[IFD]> IFD-TAG-ID=(0x00) CHILD-IFD=[] INDEX=(9) TAG=[0x8298]",
|
||||
"<PARENTS=[] IFD-NAME=[IFD]> IFD-TAG-ID=(0x00) CHILD-IFD=[Exif] INDEX=(10) TAG=[0x8769]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(0) TAG=[0x829a]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(1) TAG=[0x829d]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(2) TAG=[0x8822]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(3) TAG=[0x8827]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(4) TAG=[0x8830]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(5) TAG=[0x8832]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(6) TAG=[0x9000]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(7) TAG=[0x9003]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(8) TAG=[0x9004]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(9) TAG=[0x9101]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(10) TAG=[0x9201]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(11) TAG=[0x9202]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(12) TAG=[0x9204]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(13) TAG=[0x9207]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(14) TAG=[0x9209]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(15) TAG=[0x920a]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(16) TAG=[0x927c]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(17) TAG=[0x9286]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(18) TAG=[0x9290]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(19) TAG=[0x9291]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(20) TAG=[0x9292]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(21) TAG=[0xa000]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(22) TAG=[0xa001]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(23) TAG=[0xa002]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(24) TAG=[0xa003]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(25) TAG=[0xa20e]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(26) TAG=[0xa20f]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(27) TAG=[0xa210]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(28) TAG=[0xa401]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(29) TAG=[0xa402]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(30) TAG=[0xa403]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(31) TAG=[0xa406]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(32) TAG=[0xa430]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(33) TAG=[0xa431]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(34) TAG=[0xa432]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(35) TAG=[0xa434]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[] INDEX=(36) TAG=[0xa435]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[Exif]> IFD-TAG-ID=(0x8769) CHILD-IFD=[Iop] INDEX=(37) TAG=[0xa005]",
|
||||
"<PARENTS=[IFD->Exif] IFD-NAME=[Iop]> IFD-TAG-ID=(0xa005) CHILD-IFD=[] INDEX=(0) TAG=[0x01]",
|
||||
"<PARENTS=[IFD->Exif] IFD-NAME=[Iop]> IFD-TAG-ID=(0xa005) CHILD-IFD=[] INDEX=(1) TAG=[0x02]",
|
||||
"<PARENTS=[] IFD-NAME=[IFD]> IFD-TAG-ID=(0x00) CHILD-IFD=[GPSInfo] INDEX=(11) TAG=[0x8825]",
|
||||
"<PARENTS=[IFD] IFD-NAME=[GPSInfo]> IFD-TAG-ID=(0x8825) CHILD-IFD=[] INDEX=(0) TAG=[0x00]",
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(lines, expected) == false {
|
||||
t.Fatalf("IB did not [correctly] duplicate the IFD structure")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(dustin): !! Test with a GPS-attached image.
|
||||
|
|
105
ifd_enumerate.go
105
ifd_enumerate.go
|
@ -124,112 +124,12 @@ func (ie *IfdEnumerate) getTagEnumerator(ifdOffset uint32) (ite *IfdTagEnumerato
|
|||
return ite
|
||||
}
|
||||
|
||||
|
||||
// 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(indexedIfdName string, tagId uint16, tagType TagType, valueContext ValueContext) (err error)
|
||||
|
||||
|
||||
type IfdTagEntry struct {
|
||||
TagId uint16
|
||||
TagIndex int
|
||||
TagType uint16
|
||||
UnitCount uint32
|
||||
ValueOffset uint32
|
||||
RawValueOffset []byte
|
||||
|
||||
// ChildIfdName is a name if this tag represents a child IFD.
|
||||
ChildIfdName string
|
||||
|
||||
// IfdName is the IFD that this tag belongs to.
|
||||
IfdName string
|
||||
}
|
||||
|
||||
func (ite IfdTagEntry) String() string {
|
||||
return fmt.Sprintf("IfdTagEntry<TAG-IFD=[%s] TAG-ID=(0x%02x) TAG-TYPE=[%s] UNIT-COUNT=(%d)>", ite.ChildIfdName, ite.TagId, TypeNames[ite.TagType], ite.UnitCount)
|
||||
}
|
||||
|
||||
func (ite IfdTagEntry) ValueBytes(addressableData []byte, byteOrder binary.ByteOrder) (value []byte, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
if ite.TagType == TypeUndefined {
|
||||
valueContext := ValueContext{
|
||||
UnitCount: ite.UnitCount,
|
||||
ValueOffset: ite.ValueOffset,
|
||||
RawValueOffset: ite.RawValueOffset,
|
||||
AddressableData: addressableData,
|
||||
}
|
||||
|
||||
value, err := UndefinedValue(ite.IfdName, ite.TagId, valueContext, byteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
switch value.(type) {
|
||||
case []byte:
|
||||
return value.([]byte), nil
|
||||
case string:
|
||||
return []byte(value.(string)), nil
|
||||
default:
|
||||
// TODO(dustin): !! Finish translating the rest of the types (make reusable and replace into other similar implementations?)
|
||||
log.Panicf("can not produce bytes for unknown-type tag (0x%02x)", ite.TagId)
|
||||
}
|
||||
}
|
||||
|
||||
originalType := NewTagType(ite.TagType, byteOrder)
|
||||
byteCount := uint32(originalType.Size()) * ite.UnitCount
|
||||
|
||||
tt := NewTagType(TypeByte, byteOrder)
|
||||
|
||||
if tt.ValueIsEmbedded(byteCount) == true {
|
||||
typeDecodeLogger.Debugf(nil, "Reading BYTE value (ITE; embedded).")
|
||||
|
||||
// In this case, the bytes normally used for the offset are actually
|
||||
// data.
|
||||
value, err = tt.ParseBytes(ite.RawValueOffset, byteCount)
|
||||
log.PanicIf(err)
|
||||
} else {
|
||||
typeDecodeLogger.Debugf(nil, "Reading BYTE value (ITE; at offset).")
|
||||
|
||||
value, err = tt.ParseBytes(addressableData[ite.ValueOffset:], byteCount)
|
||||
log.PanicIf(err)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
|
||||
type IfdTagEntryValueResolver struct {
|
||||
addressableData []byte
|
||||
byteOrder binary.ByteOrder
|
||||
}
|
||||
|
||||
func NewIfdTagEntryValueResolver(exifData []byte, byteOrder binary.ByteOrder) (itevr *IfdTagEntryValueResolver) {
|
||||
// Make it obvious what data we expect and when we don't get it.
|
||||
if IsExif(exifData) == false {
|
||||
log.Panicf("not exif data")
|
||||
}
|
||||
|
||||
return &IfdTagEntryValueResolver{
|
||||
addressableData: exifData[ExifAddressableAreaStart:],
|
||||
byteOrder: byteOrder,
|
||||
}
|
||||
}
|
||||
|
||||
func (itevr *IfdTagEntryValueResolver) ValueBytes(ite *IfdTagEntry) (value []byte, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
value, err = ite.ValueBytes(itevr.addressableData, itevr.byteOrder)
|
||||
return value, err
|
||||
}
|
||||
|
||||
|
||||
// ParseIfd decodes the IFD block that we're currently sitting on the first
|
||||
// byte of.
|
||||
func (ie *IfdEnumerate) ParseIfd(ifdName string, ifdIndex int, ifdOffset uint32, visitor TagVisitor, doDescend bool) (nextIfdOffset uint32, entries []IfdTagEntry, err error) {
|
||||
|
@ -347,6 +247,8 @@ func (ie *IfdEnumerate) Scan(ifdName string, ifdOffset uint32, visitor TagVisito
|
|||
|
||||
|
||||
type Ifd struct {
|
||||
ByteOrder binary.ByteOrder
|
||||
|
||||
Id int
|
||||
ParentIfd *Ifd
|
||||
Name string
|
||||
|
@ -450,6 +352,7 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
|
|||
id := len(ifds)
|
||||
|
||||
ifd := Ifd{
|
||||
ByteOrder: ie.byteOrder,
|
||||
Id: id,
|
||||
ParentIfd: parentIfd,
|
||||
Name: name,
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
package exif
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
)
|
||||
|
||||
var (
|
||||
iteLogger = log.NewLogger("exif.ifd_tag_entry")
|
||||
)
|
||||
|
||||
type IfdTagEntry struct {
|
||||
TagId uint16
|
||||
TagIndex int
|
||||
TagType uint16
|
||||
UnitCount uint32
|
||||
ValueOffset uint32
|
||||
RawValueOffset []byte
|
||||
|
||||
// ChildIfdName is a name if this tag represents a child IFD.
|
||||
ChildIfdName string
|
||||
|
||||
// IfdName is the IFD that this tag belongs to.
|
||||
IfdName string
|
||||
}
|
||||
|
||||
func (ite IfdTagEntry) String() string {
|
||||
return fmt.Sprintf("IfdTagEntry<TAG-IFD=[%s] TAG-ID=(0x%02x) TAG-TYPE=[%s] UNIT-COUNT=(%d)>", ite.ChildIfdName, ite.TagId, TypeNames[ite.TagType], ite.UnitCount)
|
||||
}
|
||||
|
||||
func (ite IfdTagEntry) ValueBytes(addressableData []byte, byteOrder binary.ByteOrder) (value []byte, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
if ite.TagType == TypeUndefined {
|
||||
valueContext := ValueContext{
|
||||
UnitCount: ite.UnitCount,
|
||||
ValueOffset: ite.ValueOffset,
|
||||
RawValueOffset: ite.RawValueOffset,
|
||||
AddressableData: addressableData,
|
||||
}
|
||||
|
||||
value, err := UndefinedValue(ite.IfdName, ite.TagId, valueContext, byteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
switch value.(type) {
|
||||
case []byte:
|
||||
return value.([]byte), nil
|
||||
case string:
|
||||
return []byte(value.(string)), nil
|
||||
case UnknownTagValue:
|
||||
valueBytes, err := value.(UnknownTagValue).ValueBytes()
|
||||
log.PanicIf(err)
|
||||
|
||||
return valueBytes, nil
|
||||
default:
|
||||
// TODO(dustin): !! Finish translating the rest of the types (make reusable and replace into other similar implementations?)
|
||||
log.Panicf("can not produce bytes for unknown-type tag (0x%02x): [%s]", ite.TagId, reflect.TypeOf(value))
|
||||
}
|
||||
}
|
||||
|
||||
originalType := NewTagType(ite.TagType, byteOrder)
|
||||
byteCount := uint32(originalType.Size()) * ite.UnitCount
|
||||
|
||||
tt := NewTagType(TypeByte, byteOrder)
|
||||
|
||||
if tt.ValueIsEmbedded(byteCount) == true {
|
||||
iteLogger.Debugf(nil, "Reading BYTE value (ITE; embedded).")
|
||||
|
||||
// In this case, the bytes normally used for the offset are actually
|
||||
// data.
|
||||
value, err = tt.ParseBytes(ite.RawValueOffset, byteCount)
|
||||
log.PanicIf(err)
|
||||
} else {
|
||||
iteLogger.Debugf(nil, "Reading BYTE value (ITE; at offset).")
|
||||
|
||||
value, err = tt.ParseBytes(addressableData[ite.ValueOffset:], byteCount)
|
||||
log.PanicIf(err)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
|
||||
// IfdTagEntryValueResolver instances know how to resolve the values for any
|
||||
// tag for a particular EXIF block.
|
||||
type IfdTagEntryValueResolver struct {
|
||||
addressableData []byte
|
||||
byteOrder binary.ByteOrder
|
||||
}
|
||||
|
||||
func NewIfdTagEntryValueResolver(exifData []byte, byteOrder binary.ByteOrder) (itevr *IfdTagEntryValueResolver) {
|
||||
// Make it obvious what data we expect and when we don't get it.
|
||||
if IsExif(exifData) == false {
|
||||
log.Panicf("not exif data")
|
||||
}
|
||||
|
||||
return &IfdTagEntryValueResolver{
|
||||
addressableData: exifData[ExifAddressableAreaStart:],
|
||||
byteOrder: byteOrder,
|
||||
}
|
||||
}
|
||||
|
||||
func (itevr *IfdTagEntryValueResolver) ValueBytes(ite *IfdTagEntry) (value []byte, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
value, err = ite.ValueBytes(itevr.addressableData, itevr.byteOrder)
|
||||
return value, err
|
||||
}
|
6
tags.go
6
tags.go
|
@ -181,9 +181,9 @@ func (ti *TagIndex) Get(ifdName string, id uint16) (it *IndexedTag, err error) {
|
|||
return it, nil
|
||||
}
|
||||
|
||||
// GetIfdName returns the known index name for the tags that are expected/
|
||||
// allowed for the IFD. If there's an error, returns "". If returns "", the IFD
|
||||
// should be skipped.
|
||||
// IfdName returns the known index name for the tags that are expected/allowed
|
||||
// for the IFD. If there's an error, returns "". If returns "", the IFD should
|
||||
// be skipped.
|
||||
func IfdName(ifdName string, ifdIndex int) string {
|
||||
// There's an IFD0 and IFD1, but the others must be unique.
|
||||
if ifdName == IfdStandard && ifdIndex > 1 {
|
||||
|
|
|
@ -3,6 +3,8 @@ package exif
|
|||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -69,6 +71,19 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
|
||||
type UnknownTagValue interface {
|
||||
ValueBytes() ([]byte, error)
|
||||
}
|
||||
|
||||
|
||||
type TagUnknownType_GeneralString string
|
||||
|
||||
func (gs TagUnknownType_GeneralString) ValueBytes() (value []byte, err error) {
|
||||
return []byte(gs), nil
|
||||
}
|
||||
|
||||
|
||||
type TagUnknownType_9298_UserComment struct {
|
||||
EncodingType int
|
||||
EncodingBytes []byte
|
||||
|
@ -78,6 +93,21 @@ func (uc TagUnknownType_9298_UserComment) String() string {
|
|||
return fmt.Sprintf("UserComment<ENCODING=[%s] V=%v>", TagUnknownType_9298_UserComment_Encoding_Names[uc.EncodingType], uc.EncodingBytes)
|
||||
}
|
||||
|
||||
func (uc TagUnknownType_9298_UserComment) ValueBytes() (value []byte, err error) {
|
||||
encodingTypeBytes, found := TagUnknownType_9298_UserComment_Encodings[uc.EncodingType]
|
||||
if found == false {
|
||||
log.Panicf("encoding-type not valid for unknown-type tag 9298 (UserComment): (%d)", uc.EncodingType)
|
||||
}
|
||||
|
||||
value = make([]byte, len(uc.EncodingBytes) + 8)
|
||||
copy(value[:8], encodingTypeBytes)
|
||||
|
||||
// TODO(dustin): !! With undefined-encoded comments, we always make this empty. However, it comes in with a set of zero bytes. Is there a problem if we send it out with just the encoding bytes?
|
||||
copy(value[8:], uc.EncodingBytes)
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
|
||||
type TagUnknownType_927C_MakerNote struct {
|
||||
MakerNoteType []byte
|
||||
|
@ -93,6 +123,10 @@ func (mn TagUnknownType_927C_MakerNote) String() string {
|
|||
return fmt.Sprintf("MakerNote<TYPE-ID=[%s]>", strings.Join(parts, " "))
|
||||
}
|
||||
|
||||
func (uc TagUnknownType_927C_MakerNote) ValueBytes() (value []byte, err error) {
|
||||
return uc.MakerNoteBytes, nil
|
||||
}
|
||||
|
||||
|
||||
type TagUnknownType_9101_ComponentsConfiguration struct {
|
||||
ConfigurationId int
|
||||
|
@ -102,3 +136,7 @@ type TagUnknownType_9101_ComponentsConfiguration struct {
|
|||
func (cc TagUnknownType_9101_ComponentsConfiguration) String() string {
|
||||
return fmt.Sprintf("ComponentsConfiguration<ID=[%s] BYTES=%v>", TagUnknownType_9101_ComponentsConfiguration_Names[cc.ConfigurationId], cc.ConfigurationBytes)
|
||||
}
|
||||
|
||||
func (uc TagUnknownType_9101_ComponentsConfiguration) ValueBytes() (value []byte, err error) {
|
||||
return uc.ConfigurationBytes, nil
|
||||
}
|
||||
|
|
4
type.go
4
type.go
|
@ -36,10 +36,6 @@ var (
|
|||
)
|
||||
|
||||
var (
|
||||
// ErrCantDetermineTagValueSize is used when we're trying to determine a
|
||||
//size for a non-standard/undefined type.
|
||||
ErrCantDetermineTagValueSize = errors.New("can not determine tag-value size")
|
||||
|
||||
// ErrNotEnoughData is used when there isn't enough data to accomodate what
|
||||
// we're trying to parse (sizeof(type) * unit_count).
|
||||
ErrNotEnoughData = errors.New("not enough data for type")
|
||||
|
|
|
@ -66,7 +66,7 @@ func (tt TagType) Size() int {
|
|||
} else if tt.tagType == TypeSignedRational {
|
||||
return 8
|
||||
} else {
|
||||
log.Panic(ErrCantDetermineTagValueSize)
|
||||
log.Panicf("can not determine tag-value size for type (%d): [%s]", tt.tagType, TypeNames[tt.tagType])
|
||||
|
||||
// Never called.
|
||||
return 0
|
||||
|
@ -609,19 +609,19 @@ func UndefinedValue(indexedIfdName string, tagId uint16, valueContext ValueConte
|
|||
|
||||
tt := NewTagType(TypeAsciiNoNul, byteOrder)
|
||||
|
||||
value, err = tt.ReadAsciiValue(valueContext)
|
||||
valueString, err := tt.ReadAsciiValue(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
return value, nil
|
||||
return TagUnknownType_GeneralString(valueString), nil
|
||||
} else if tagId == 0xa000 {
|
||||
// FlashpixVersion
|
||||
|
||||
tt := NewTagType(TypeAsciiNoNul, byteOrder)
|
||||
|
||||
value, err = tt.ReadAsciiValue(valueContext)
|
||||
valueString, err := tt.ReadAsciiValue(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
return value, nil
|
||||
return TagUnknownType_GeneralString(valueString), nil
|
||||
} else if tagId == 0x9286 {
|
||||
// UserComment
|
||||
|
||||
|
@ -719,19 +719,19 @@ func UndefinedValue(indexedIfdName string, tagId uint16, valueContext ValueConte
|
|||
|
||||
tt := NewTagType(TypeAsciiNoNul, byteOrder)
|
||||
|
||||
value, err = tt.ReadAsciiValue(valueContext)
|
||||
valueString, err := tt.ReadAsciiValue(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
return value, nil
|
||||
return TagUnknownType_GeneralString(valueString), nil
|
||||
} else if tagId == 0x001b {
|
||||
// GPSProcessingMethod
|
||||
|
||||
tt := NewTagType(TypeAsciiNoNul, byteOrder)
|
||||
|
||||
value, err = tt.ReadAsciiValue(valueContext)
|
||||
valueString, err := tt.ReadAsciiValue(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
return value, nil
|
||||
return TagUnknownType_GeneralString(valueString), nil
|
||||
}
|
||||
} else if indexedIfdName == IfdName(IfdIop, 0) {
|
||||
if tagId == 0x0002 {
|
||||
|
@ -739,10 +739,10 @@ func UndefinedValue(indexedIfdName string, tagId uint16, valueContext ValueConte
|
|||
|
||||
tt := NewTagType(TypeAsciiNoNul, byteOrder)
|
||||
|
||||
value, err := tt.ReadAsciiNoNulValue(valueContext)
|
||||
valueString, err := tt.ReadAsciiValue(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
return value, nil
|
||||
return TagUnknownType_GeneralString(valueString), nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue