Empower ITEs to resolve values and raw bytes directly

They'll also now embed all of the information they need to know since it
is all known where the ITEs are created. This prevents the user from
having to be involved in it. This makes it much more straightforward and
enjoyable to use.

- ifd_tag_entry.go
  - newIfdTagEntry now takes and embeds `addressableBytes` and
    `byteOrder`.
  - We've dumped the `value` member that let the caller preload a parsed
    value (or bytes?). It's no longer necessary since this led to
    inconsistencies and the ITE can produce these values directly, now.
  - `Value()` obviously no longer takes `addressableValue` and
    `byteOrder` since now embedded.
  - `Value()` will now see and return ErrUnhandledUnknownTypedTag
    directly (not wrapping it, since this is a handled case).

- common/type.go: FormatFromType now uses Stringer as a fallback if
  possible. All undefined-tag wrapper types implement it, so the same
  function can handle both undefined and non-undefined values, and the
  individual types can control the strings presented in simple listing.

- Dropped "resolveValue" parameters from all of the collect, visit, and
  parsing functions. Resolution is now a later step performed by the
  caller on the ITEs, directly.

  - This parameter was protection against undefined-type values
    disrupting simple enumeration, but now the user can simply produce
    the list of tags and can either choose to decode their value or not,
    directly. If they do, they, as of earlier, recent commits, also have
    the ability to properly manage unhandled undefined-values so they
    don't crash.

- The ITEs can now create ValueContext structs directly
  (GetValueContext()), though it might not be necessary now that the
  ITEs can produce the values and encodings directly.

  - This also allowed us to dump several other GetValueContext()
    implementations elsewhere since it is now self-reliant on this type
    and those methods were essentially kludges for the lack of this.

- Dump a bunch of "Value" methods from ITEs which just weren't useful or
  simple enough. Replaced by the above.

- Fixed `(Ifd).String()` to have a pointer receiver.
for/master
Dustin Oprea 2020-01-06 01:03:16 -05:00
parent e7e1e89ffb
commit 1bdfa9b10e
14 changed files with 480 additions and 630 deletions

View File

@ -139,7 +139,10 @@ type SignedRational struct {
}
// Format returns a stringified value for the given encoding. Automatically
// parses. Automatically calculates count based on type size.
// parses. Automatically calculates count based on type size. This function
// also supports undefined-type values (the ones that we support, anyway) by
// way of the String() method that they all require. We can't be more specific
// because we're a base package and we can't refer to it.
func FormatFromType(value interface{}, justFirst bool) (phrase string, err error) {
defer func() {
if state := recover(); state != nil {
@ -247,6 +250,9 @@ func FormatFromType(value interface{}, justFirst bool) (phrase string, err error
}
return fmt.Sprintf("%v", parts), nil
case fmt.Stringer:
// An undefined value that is documented (or that we otherwise support).
return t.String(), nil
default:
// Affects only "unknown" values, in general.
log.Panicf("type can not be formatted into string: %v", reflect.TypeOf(value).Name())

View File

@ -189,7 +189,7 @@ func Visit(rootIfdName string, ifdMapping *IfdMapping, tagIndex *TagIndex, exifD
ie := NewIfdEnumerate(ifdMapping, tagIndex, exifData, eh.ByteOrder)
err = ie.Scan(rootIfdName, eh.FirstIfdOffset, visitor, true)
err = ie.Scan(rootIfdName, eh.FirstIfdOffset, visitor)
log.PanicIf(err)
return eh, nil
@ -208,7 +208,7 @@ func Collect(ifdMapping *IfdMapping, tagIndex *TagIndex, exifData []byte) (eh Ex
ie := NewIfdEnumerate(ifdMapping, tagIndex, exifData, eh.ByteOrder)
index, err = ie.Collect(eh.FirstIfdOffset, true)
index, err = ie.Collect(eh.FirstIfdOffset)
log.PanicIf(err)
return eh, index, nil

View File

@ -315,19 +315,19 @@ func TestCollect(t *testing.T) {
foundExif := 0
foundGps := 0
for _, ite := range lookup[exifcommon.IfdPathStandard][0].Entries {
if ite.ChildIfdPath == exifcommon.IfdPathStandardExif {
if ite.ChildIfdPath() == exifcommon.IfdPathStandardExif {
foundExif++
if ite.TagId != exifcommon.IfdExifId {
t.Fatalf("EXIF IFD tag-ID mismatch: (0x%04x) != (0x%04x)", ite.TagId, exifcommon.IfdExifId)
if ite.TagId() != exifcommon.IfdExifId {
t.Fatalf("EXIF IFD tag-ID mismatch: (0x%04x) != (0x%04x)", ite.TagId(), exifcommon.IfdExifId)
}
}
if ite.ChildIfdPath == exifcommon.IfdPathStandardGps {
if ite.ChildIfdPath() == exifcommon.IfdPathStandardGps {
foundGps++
if ite.TagId != exifcommon.IfdGpsId {
t.Fatalf("GPS IFD tag-ID mismatch: (0x%04x) != (0x%04x)", ite.TagId, exifcommon.IfdGpsId)
if ite.TagId() != exifcommon.IfdGpsId {
t.Fatalf("GPS IFD tag-ID mismatch: (0x%04x) != (0x%04x)", ite.TagId(), exifcommon.IfdGpsId)
}
}
}
@ -340,11 +340,11 @@ func TestCollect(t *testing.T) {
foundIop := 0
for _, ite := range lookup[exifcommon.IfdPathStandardExif][0].Entries {
if ite.ChildIfdPath == exifcommon.IfdPathStandardExifIop {
if ite.ChildIfdPath() == exifcommon.IfdPathStandardExifIop {
foundIop++
if ite.TagId != exifcommon.IfdIopId {
t.Fatalf("IOP IFD tag-ID mismatch: (0x%04x) != (0x%04x)", ite.TagId, exifcommon.IfdIopId)
if ite.TagId() != exifcommon.IfdIopId {
t.Fatalf("IOP IFD tag-ID mismatch: (0x%04x) != (0x%04x)", ite.TagId(), exifcommon.IfdIopId)
}
}
}

View File

@ -1042,7 +1042,7 @@ func (ib *IfdBuilder) AddTagsFromExisting(ifd *Ifd, includeTagIds []uint16, excl
}
for i, ite := range ifd.Entries {
if ite.TagId == ThumbnailOffsetTagId || ite.TagId == ThumbnailSizeTagId {
if ite.TagId() == ThumbnailOffsetTagId || ite.TagId() == ThumbnailSizeTagId {
// These will be added on-the-fly when we encode.
continue
}
@ -1050,7 +1050,7 @@ func (ib *IfdBuilder) AddTagsFromExisting(ifd *Ifd, includeTagIds []uint16, excl
if excludeTagIds != nil && len(excludeTagIds) > 0 {
found := false
for _, excludedTagId := range excludeTagIds {
if excludedTagId == ite.TagId {
if excludedTagId == ite.TagId() {
found = true
}
}
@ -1066,7 +1066,7 @@ func (ib *IfdBuilder) AddTagsFromExisting(ifd *Ifd, includeTagIds []uint16, excl
found := false
for _, includedTagId := range includeTagIds {
if includedTagId == ite.TagId {
if includedTagId == ite.TagId() {
found = true
break
}
@ -1079,7 +1079,7 @@ func (ib *IfdBuilder) AddTagsFromExisting(ifd *Ifd, includeTagIds []uint16, excl
var bt *BuilderTag
if ite.ChildIfdPath != "" {
if ite.ChildIfdPath() != "" {
// If we want to add an IFD tag, we'll have to build it first and
// *then* add it via a different method.
@ -1090,7 +1090,7 @@ func (ib *IfdBuilder) AddTagsFromExisting(ifd *Ifd, includeTagIds []uint16, excl
for _, thisChildIfd := range ifd.Children {
if thisChildIfd.ParentTagIndex != i {
continue
} else if thisChildIfd.TagId != 0xffff && thisChildIfd.TagId != ite.TagId {
} 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)
}
@ -1104,7 +1104,7 @@ func (ib *IfdBuilder) AddTagsFromExisting(ifd *Ifd, includeTagIds []uint16, excl
childTagIds[j] = fmt.Sprintf("0x%04x (parent tag-position %d)", childIfd.TagId, childIfd.ParentTagIndex)
}
log.Panicf("could not find child IFD for child ITE: IFD-PATH=[%s] TAG-ID=(0x%04x) CURRENT-TAG-POSITION=(%d) CHILDREN=%v", ite.IfdPath, ite.TagId, i, childTagIds)
log.Panicf("could not find child IFD for child ITE: IFD-PATH=[%s] TAG-ID=(0x%04x) CURRENT-TAG-POSITION=(%d) CHILDREN=%v", ite.IfdPath(), ite.TagId(), i, childTagIds)
}
childIb := NewIfdBuilderFromExistingChain(childIfd)
@ -1112,11 +1112,11 @@ func (ib *IfdBuilder) AddTagsFromExisting(ifd *Ifd, includeTagIds []uint16, excl
} else {
// Non-IFD tag.
valueContext := ifd.GetValueContext(ite)
valueContext := ite.GetValueContext()
var rawBytes []byte
if ite.TagType == exifcommon.TypeUndefined {
if ite.TagType() == exifcommon.TypeUndefined {
// It's an undefined-type value. Try to process (or skip if
// we don't know how to), and encode back to bytes. This is the
// cleanest way of using what we already have to both determine
@ -1149,8 +1149,8 @@ func (ib *IfdBuilder) AddTagsFromExisting(ifd *Ifd, includeTagIds []uint16, excl
bt = NewBuilderTag(
ifd.IfdPath,
ite.TagId,
ite.TagType,
ite.TagId(),
ite.TagType(),
value,
ib.byteOrder)
}

View File

@ -422,30 +422,28 @@ func Test_IfdByteEncoder_encodeTagToBytes_childIfd__withAllocate(t *testing.T) {
t.Fatalf("Child IFD is not the right size: (%d)", len(childIfdBlock))
}
iteV, err := ParseOneTag(im, ti, fmt.Sprintf("%s%d", exifcommon.IfdPathStandard, 0), exifcommon.IfdPathStandard, exifcommon.TestDefaultByteOrder, tagBytes, false)
iteV, err := ParseOneTag(im, ti, fmt.Sprintf("%s%d", exifcommon.IfdPathStandard, 0), exifcommon.IfdPathStandard, exifcommon.TestDefaultByteOrder, tagBytes)
log.PanicIf(err)
if iteV.TagId != exifcommon.IfdExifId {
t.Fatalf("IFD first tag-ID not correct: (0x%02x)", iteV.TagId)
} else if iteV.TagIndex != 0 {
t.Fatalf("IFD first tag index not correct: (%d)", iteV.TagIndex)
} else if iteV.TagType != exifcommon.TypeLong {
t.Fatalf("IFD first tag type not correct: (%d)", iteV.TagType)
} else if iteV.UnitCount != 1 {
t.Fatalf("IFD first tag unit-count not correct: (%d)", iteV.UnitCount)
} else if iteV.ValueOffset != nextIfdOffsetToWrite {
t.Fatalf("IFD's child-IFD offset (as offset) is not correct: (%d) != (%d)", iteV.ValueOffset, nextIfdOffsetToWrite)
} else if bytes.Compare(iteV.RawValueOffset, []byte{0x0, 0x0, 0x07, 0xd0}) != 0 {
t.Fatalf("IFD's child-IFD offset (as raw bytes) is not correct: [%x]", iteV.RawValueOffset)
} else if iteV.ChildIfdPath != exifcommon.IfdPathStandardExif {
t.Fatalf("IFD first tag IFD-name name not correct: [%s]", iteV.ChildIfdPath)
} else if iteV.IfdPath != exifcommon.IfdPathStandard {
t.Fatalf("IFD first tag parent IFD not correct: %v", iteV.IfdPath)
if iteV.TagId() != exifcommon.IfdExifId {
t.Fatalf("IFD first tag-ID not correct: (0x%02x)", iteV.TagId())
} else if iteV.tagIndex != 0 {
t.Fatalf("IFD first tag index not correct: (%d)", iteV.tagIndex)
} else if iteV.TagType() != exifcommon.TypeLong {
t.Fatalf("IFD first tag type not correct: (%d)", iteV.TagType())
} else if iteV.UnitCount() != 1 {
t.Fatalf("IFD first tag unit-count not correct: (%d)", iteV.UnitCount())
} else if iteV.valueOffset_() != nextIfdOffsetToWrite {
t.Fatalf("IFD's child-IFD offset (as offset) is not correct: (%d) != (%d)", iteV.valueOffset_(), nextIfdOffsetToWrite)
} else if iteV.ChildIfdPath() != exifcommon.IfdPathStandardExif {
t.Fatalf("IFD first tag IFD-name name not correct: [%s]", iteV.ChildIfdPath())
} else if iteV.IfdPath() != exifcommon.IfdPathStandard {
t.Fatalf("IFD first tag parent IFD not correct: %v", iteV.IfdPath())
}
// Validate the child's raw IFD bytes.
childNextIfdOffset, childEntries, err := ParseOneIfd(im, ti, "IFD0/Exif0", "IFD/Exif", exifcommon.TestDefaultByteOrder, childIfdBlock, nil, false)
childNextIfdOffset, childEntries, err := ParseOneIfd(im, ti, "IFD0/Exif0", "IFD/Exif", exifcommon.TestDefaultByteOrder, childIfdBlock, nil)
log.PanicIf(err)
if childNextIfdOffset != uint32(0) {
@ -456,22 +454,18 @@ func Test_IfdByteEncoder_encodeTagToBytes_childIfd__withAllocate(t *testing.T) {
ite := childEntries[0]
if ite.TagId != 0x8822 {
t.Fatalf("Child IFD first tag-ID not correct: (0x%02x)", ite.TagId)
} else if ite.TagIndex != 0 {
t.Fatalf("Child IFD first tag index not correct: (%d)", ite.TagIndex)
} else if ite.TagType != exifcommon.TypeShort {
t.Fatalf("Child IFD first tag type not correct: (%d)", ite.TagType)
} else if ite.UnitCount != 1 {
t.Fatalf("Child IFD first tag unit-count not correct: (%d)", ite.UnitCount)
} else if ite.ValueOffset != 0x12340000 {
t.Fatalf("Child IFD first tag value value (as offset) not correct: (0x%02x)", ite.ValueOffset)
} else if bytes.Compare(ite.RawValueOffset, []byte{0x12, 0x34, 0x0, 0x0}) != 0 {
t.Fatalf("Child IFD first tag value value (as raw bytes) not correct: [%v]", ite.RawValueOffset)
} else if ite.ChildIfdPath != "" {
t.Fatalf("Child IFD first tag IFD-name name not empty: [%s]", ite.ChildIfdPath)
} else if ite.IfdPath != exifcommon.IfdPathStandardExif {
t.Fatalf("Child IFD first tag parent IFD not correct: %v", ite.IfdPath)
if ite.TagId() != 0x8822 {
t.Fatalf("Child IFD first tag-ID not correct: (0x%02x)", ite.TagId())
} else if ite.tagIndex != 0 {
t.Fatalf("Child IFD first tag index not correct: (%d)", ite.tagIndex)
} else if ite.TagType() != exifcommon.TypeShort {
t.Fatalf("Child IFD first tag type not correct: (%d)", ite.TagType())
} else if ite.UnitCount() != 1 {
t.Fatalf("Child IFD first tag unit-count not correct: (%d)", ite.UnitCount())
} else if ite.ChildIfdPath() != "" {
t.Fatalf("Child IFD first tag IFD-name name not empty: [%s]", ite.ChildIfdPath())
} else if ite.IfdPath() != exifcommon.IfdPathStandardExif {
t.Fatalf("Child IFD first tag parent IFD not correct: %v", ite.IfdPath())
}
}
@ -531,25 +525,21 @@ func Test_IfdByteEncoder_encodeTagToBytes_simpleTag_allocate(t *testing.T) {
t.Fatalf("Child IFD not have been allocated.")
}
ite, err := ParseOneTag(im, ti, fmt.Sprintf("%s%d", exifcommon.IfdPathStandard, 0), exifcommon.IfdPathStandard, exifcommon.TestDefaultByteOrder, tagBytes, false)
ite, err := ParseOneTag(im, ti, fmt.Sprintf("%s%d", exifcommon.IfdPathStandard, 0), exifcommon.IfdPathStandard, exifcommon.TestDefaultByteOrder, tagBytes)
log.PanicIf(err)
if ite.TagId != 0x000b {
t.Fatalf("Tag-ID not correct: (0x%02x)", ite.TagId)
} else if ite.TagIndex != 0 {
t.Fatalf("Tag index not correct: (%d)", ite.TagIndex)
} else if ite.TagType != exifcommon.TypeAscii {
t.Fatalf("Tag type not correct: (%d)", ite.TagType)
} else if ite.UnitCount != (uint32(len(valueString) + 1)) {
t.Fatalf("Tag unit-count not correct: (%d)", ite.UnitCount)
} else if ite.ValueOffset != addressableOffset {
t.Fatalf("Tag's value (as offset) is not correct: (%d) != (%d)", ite.ValueOffset, addressableOffset)
} else if bytes.Compare(ite.RawValueOffset, []byte{0x0, 0x0, 0x12, 0x34}) != 0 {
t.Fatalf("Tag's value (as raw bytes) is not correct: [%x]", ite.RawValueOffset)
} else if ite.ChildIfdPath != "" {
t.Fatalf("Tag's IFD-name should be empty: [%s]", ite.ChildIfdPath)
} else if ite.IfdPath != exifcommon.IfdPathStandard {
t.Fatalf("Tag's parent IFD is not correct: %v", ite.IfdPath)
if ite.TagId() != 0x000b {
t.Fatalf("Tag-ID not correct: (0x%02x)", ite.TagId())
} else if ite.tagIndex != 0 {
t.Fatalf("Tag index not correct: (%d)", ite.tagIndex)
} else if ite.TagType() != exifcommon.TypeAscii {
t.Fatalf("Tag type not correct: (%d)", ite.TagType())
} else if ite.UnitCount() != (uint32(len(valueString) + 1)) {
t.Fatalf("Tag unit-count not correct: (%d)", ite.UnitCount())
} else if ite.ChildIfdPath() != "" {
t.Fatalf("Tag's IFD-name should be empty: [%s]", ite.ChildIfdPath())
} else if ite.IfdPath() != exifcommon.IfdPathStandard {
t.Fatalf("Tag's parent IFD is not correct: %v", ite.IfdPath())
}
expectedBuffer := bytes.NewBufferString(valueString)
@ -886,15 +876,11 @@ func ExampleIfdByteEncoder_EncodeToExif() {
_, index, err := Collect(im, ti, exifData)
log.PanicIf(err)
// addressableData is the byte-slice where the allocated data can be
// resolved (where position 0x0 will correlate with offset 0x0).
addressableData := exifData[ExifAddressableAreaStart:]
for i, e := range index.RootIfd.Entries {
value, err := e.Value(addressableData, exifcommon.TestDefaultByteOrder)
for i, ite := range index.RootIfd.Entries {
value, err := ite.Value()
log.PanicIf(err)
fmt.Printf("%d: %s [%v]\n", i, e, value)
fmt.Printf("%d: %s [%v]\n", i, ite, value)
}
// Output:

View File

@ -1435,15 +1435,15 @@ func TestIfdBuilder_CreateIfdBuilderFromExistingChain_RealData(t *testing.T) {
originalIfdTags := make([][2]interface{}, 0)
for _, ite := range originalTags {
if ite.ChildIfdPath != "" {
originalIfdTags = append(originalIfdTags, [2]interface{}{ite.IfdPath, ite.TagId})
if ite.ChildIfdPath() != "" {
originalIfdTags = append(originalIfdTags, [2]interface{}{ite.IfdPath(), ite.TagId()})
}
}
recoveredIfdTags := make([][2]interface{}, 0)
for _, ite := range recoveredTags {
if ite.ChildIfdPath != "" {
recoveredIfdTags = append(recoveredIfdTags, [2]interface{}{ite.IfdPath, ite.TagId})
if ite.ChildIfdPath() != "" {
recoveredIfdTags = append(recoveredIfdTags, [2]interface{}{ite.IfdPath(), ite.TagId()})
}
}
@ -1474,64 +1474,53 @@ func TestIfdBuilder_CreateIfdBuilderFromExistingChain_RealData(t *testing.T) {
}
for i, recoveredIte := range recoveredTags {
if recoveredIte.ChildIfdPath != "" {
if recoveredIte.ChildIfdPath() != "" {
continue
}
originalIte := originalTags[i]
if recoveredIte.IfdPath != originalIte.IfdPath {
t.Fatalf("IfdIdentity not as expected: %s != %s ITE=%s", recoveredIte.IfdPath, originalIte.IfdPath, recoveredIte)
} else if recoveredIte.TagId != originalIte.TagId {
t.Fatalf("Tag-ID not as expected: %d != %d ITE=%s", recoveredIte.TagId, originalIte.TagId, recoveredIte)
} else if recoveredIte.TagType != originalIte.TagType {
t.Fatalf("Tag-type not as expected: %d != %d ITE=%s", recoveredIte.TagType, originalIte.TagType, recoveredIte)
if recoveredIte.IfdPath() != originalIte.IfdPath() {
t.Fatalf("IfdIdentity not as expected: %s != %s ITE=%s", recoveredIte.IfdPath(), originalIte.IfdPath(), recoveredIte)
} else if recoveredIte.TagId() != originalIte.TagId() {
t.Fatalf("Tag-ID not as expected: %d != %d ITE=%s", recoveredIte.TagId(), originalIte.TagId(), recoveredIte)
} else if recoveredIte.TagType() != originalIte.TagType() {
t.Fatalf("Tag-type not as expected: %d != %d ITE=%s", recoveredIte.TagType(), originalIte.TagType(), recoveredIte)
}
var originalValueBytes []byte
if originalIte.TagType == exifcommon.TypeUndefined {
if originalIte.TagType() == exifcommon.TypeUndefined {
valueContext := originalIte.GetValueContext()
var err error
valueContext :=
newValueContextFromTag(
originalIte,
originalIndex.RootIfd.addressableData,
originalIndex.RootIfd.ByteOrder)
value, err := exifundefined.Decode(valueContext)
log.PanicIf(err)
originalValueBytes, _, err = exifundefined.Encode(value, originalIndex.RootIfd.ByteOrder)
} else {
var err error
// TODO(dustin): We're always accessing the addressable-data using the root-IFD. It shouldn't matter, but we'd rather access it from our specific IFD.
originalValueBytes, err = originalIte.ValueBytes(originalIndex.RootIfd.addressableData, originalIndex.RootIfd.ByteOrder)
originalValueBytes, err = originalIte.RawBytes()
log.PanicIf(err)
}
var recoveredValueBytes []byte
if recoveredIte.TagType == exifcommon.TypeUndefined {
if recoveredIte.TagType() == exifcommon.TypeUndefined {
valueContext := recoveredIte.GetValueContext()
var err error
valueContext :=
newValueContextFromTag(
recoveredIte,
recoveredIndex.RootIfd.addressableData,
recoveredIndex.RootIfd.ByteOrder)
value, err := exifundefined.Decode(valueContext)
log.PanicIf(err)
recoveredValueBytes, _, err = exifundefined.Encode(value, recoveredIndex.RootIfd.ByteOrder)
} else {
var err error
recoveredValueBytes, err = recoveredIte.ValueBytes(recoveredIndex.RootIfd.addressableData, recoveredIndex.RootIfd.ByteOrder)
recoveredValueBytes, err = recoveredIte.RawBytes()
log.PanicIf(err)
}
@ -1610,15 +1599,15 @@ func TestIfdBuilder_CreateIfdBuilderFromExistingChain_RealData(t *testing.T) {
// originalIfdTags := make([][2]interface{}, 0)
// for _, ite := range originalTags {
// if ite.ChildIfdPath != "" {
// originalIfdTags = append(originalIfdTags, [2]interface{}{ite.IfdPath, ite.TagId})
// if ite.ChildIfdPath() != "" {
// originalIfdTags = append(originalIfdTags, [2]interface{}{ite.IfdPath(), ite.TagId()})
// }
// }
// recoveredIfdTags := make([][2]interface{}, 0)
// for _, ite := range recoveredTags {
// if ite.ChildIfdPath != "" {
// recoveredIfdTags = append(recoveredIfdTags, [2]interface{}{ite.IfdPath, ite.TagId})
// if ite.ChildIfdPath() != "" {
// recoveredIfdTags = append(recoveredIfdTags, [2]interface{}{ite.IfdPath(), ite.TagId()})
// }
// }
@ -1649,18 +1638,18 @@ func TestIfdBuilder_CreateIfdBuilderFromExistingChain_RealData(t *testing.T) {
// }
// for i, recoveredIte := range recoveredTags {
// if recoveredIte.ChildIfdPath != "" {
// if recoveredIte.ChildIfdPath() != "" {
// continue
// }
// originalIte := originalTags[i]
// if recoveredIte.IfdPath != originalIte.IfdPath {
// t.Fatalf("IfdIdentity not as expected: %s != %s ITE=%s", recoveredIte.IfdPath, originalIte.IfdPath, recoveredIte)
// } else if recoveredIte.TagId != originalIte.TagId {
// t.Fatalf("Tag-ID not as expected: %d != %d ITE=%s", recoveredIte.TagId, originalIte.TagId, recoveredIte)
// } else if recoveredIte.TagType != originalIte.TagType {
// t.Fatalf("Tag-type not as expected: %d != %d ITE=%s", recoveredIte.TagType, originalIte.TagType, recoveredIte)
// if recoveredIte.IfdPath() != originalIte.IfdPath() {
// t.Fatalf("IfdIdentity not as expected: %s != %s ITE=%s", recoveredIte.IfdPath(), originalIte.IfdPath(), recoveredIte)
// } else if recoveredIte.TagId() != originalIte.TagId() {
// t.Fatalf("Tag-ID not as expected: %d != %d ITE=%s", recoveredIte.TagId(), originalIte.TagId(), recoveredIte)
// } else if recoveredIte.TagType() != originalIte.TagType() {
// t.Fatalf("Tag-type not as expected: %d != %d ITE=%s", recoveredIte.TagType(), originalIte.TagType(), recoveredIte)
// }
// originalValueBytes, err := originalIte.ValueBytes(originalIndex.RootIfd.addressableData, originalIndex.RootIfd.ByteOrder)
@ -1669,7 +1658,7 @@ func TestIfdBuilder_CreateIfdBuilderFromExistingChain_RealData(t *testing.T) {
// recoveredValueBytes, err := recoveredIte.ValueBytes(recoveredIndex.RootIfd.addressableData, recoveredIndex.RootIfd.ByteOrder)
// log.PanicIf(err)
// if recoveredIte.TagId == 0x9286 {
// if recoveredIte.TagId() == 0x9286 {
// expectedValueBytes := make([]byte, 0)
// expectedValueBytes = append(expectedValueBytes, []byte{'A', 'S', 'C', 'I', 'I', 0, 0, 0}...)
@ -1822,10 +1811,12 @@ func ExampleIfdBuilder_SetStandardWithName() {
log.PanicIf(err)
for _, ite := range results {
value, err := childIfd.TagValue(ite)
valueContext := ite.GetValueContext()
valueRaw, err := valueContext.Values()
log.PanicIf(err)
stringValue := value.(string)
stringValue := valueRaw.(string)
fmt.Println(stringValue)
}

View File

@ -58,14 +58,14 @@ type IfdTagEnumerator struct {
buffer *bytes.Buffer
}
func NewIfdTagEnumerator(addressableData []byte, byteOrder binary.ByteOrder, ifdOffset uint32) (ite *IfdTagEnumerator) {
ite = &IfdTagEnumerator{
func NewIfdTagEnumerator(addressableData []byte, byteOrder binary.ByteOrder, ifdOffset uint32) (enumerator *IfdTagEnumerator) {
enumerator = &IfdTagEnumerator{
addressableData: addressableData,
byteOrder: byteOrder,
buffer: bytes.NewBuffer(addressableData[ifdOffset:]),
}
return ite
return enumerator
}
// getUint16 reads a uint16 and advances both our current and our current
@ -139,16 +139,16 @@ func NewIfdEnumerate(ifdMapping *IfdMapping, tagIndex *TagIndex, exifData []byte
}
}
func (ie *IfdEnumerate) getTagEnumerator(ifdOffset uint32) (ite *IfdTagEnumerator) {
ite = NewIfdTagEnumerator(
func (ie *IfdEnumerate) getTagEnumerator(ifdOffset uint32) (enumerator *IfdTagEnumerator) {
enumerator = NewIfdTagEnumerator(
ie.exifData[ExifAddressableAreaStart:],
ie.byteOrder,
ifdOffset)
return ite
return enumerator
}
func (ie *IfdEnumerate) parseTag(fqIfdPath string, tagPosition int, enumerator *IfdTagEnumerator, resolveValue bool) (ite *IfdTagEntry, err error) {
func (ie *IfdEnumerate) parseTag(fqIfdPath string, tagPosition int, enumerator *IfdTagEnumerator) (ite *IfdTagEntry, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
@ -176,51 +176,26 @@ func (ie *IfdEnumerate) parseTag(fqIfdPath string, tagPosition int, enumerator *
ifdPath, err := ie.ifdMapping.StripPathPhraseIndices(fqIfdPath)
log.PanicIf(err)
ite = &IfdTagEntry{
IfdPath: ifdPath,
TagId: tagId,
TagIndex: tagPosition,
TagType: tagType,
UnitCount: unitCount,
ValueOffset: valueOffset,
RawValueOffset: rawValueOffset,
}
if resolveValue == true {
valueContext := ie.GetValueContext(ite)
if ite.TagType == exifcommon.TypeUndefined {
value, err := exifundefined.Decode(valueContext)
if err != nil {
if err == exifcommon.ErrUnhandledUnknownTypedTag {
ite.isUnhandledUnknown = true
} else {
log.Panic(err)
}
} else {
encodeable := value.(exifundefined.EncodeableValue)
var err error
ite.value, _, err = exifundefined.Encode(encodeable, ie.byteOrder)
log.PanicIf(err)
}
} else {
var err error
ite.value, err = valueContext.ReadRawEncoded()
log.PanicIf(err)
}
}
ite = newIfdTagEntry(
ifdPath,
tagId,
tagPosition,
tagType,
unitCount,
valueOffset,
rawValueOffset,
ie.exifData[ExifAddressableAreaStart:],
ie.byteOrder)
// If it's an IFD but not a standard one, it'll just be seen as a LONG
// (the standard IFD tag type), later, unless we skip it because it's
// [likely] not even in the standard list of known tags.
mi, err := ie.ifdMapping.GetChild(ifdPath, tagId)
if err == nil {
ite.ChildIfdName = mi.Name
ite.ChildIfdPath = mi.PathPhrase()
ite.ChildFqIfdPath = fmt.Sprintf("%s/%s", fqIfdPath, mi.Name)
ite.SetChildIfd(
fmt.Sprintf("%s/%s", fqIfdPath, mi.Name),
mi.PathPhrase(),
mi.Name)
// We also need to set `tag.ChildFqIfdPath` but can't do it here
// because we don't have the IFD index.
@ -231,18 +206,6 @@ func (ie *IfdEnumerate) parseTag(fqIfdPath string, tagPosition int, enumerator *
return ite, nil
}
func (ie *IfdEnumerate) GetValueContext(ite *IfdTagEntry) *exifcommon.ValueContext {
// TODO(dustin): Add test
addressableData := ie.exifData[ExifAddressableAreaStart:]
return newValueContextFromTag(
ite,
addressableData,
ie.byteOrder)
}
// RawTagWalk 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).
@ -255,25 +218,25 @@ type RawTagWalk interface {
// ParseIfd decodes the IFD block that we're currently sitting on the first
// byte of.
func (ie *IfdEnumerate) ParseIfd(fqIfdPath string, ifdIndex int, ite *IfdTagEnumerator, visitor RawTagWalk, doDescend bool, resolveValues bool) (nextIfdOffset uint32, entries []*IfdTagEntry, thumbnailData []byte, err error) {
func (ie *IfdEnumerate) ParseIfd(fqIfdPath string, ifdIndex int, enumerator *IfdTagEnumerator, visitor RawTagWalk, doDescend bool) (nextIfdOffset uint32, entries []*IfdTagEntry, thumbnailData []byte, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
tagCount, _, err := ite.getUint16()
tagCount, _, err := enumerator.getUint16()
log.PanicIf(err)
ifdEnumerateLogger.Debugf(nil, "Current IFD tag-count: (%d)", tagCount)
entries = make([]*IfdTagEntry, 0)
var iteThumbnailOffset *IfdTagEntry
var iteThumbnailSize *IfdTagEntry
var enumeratorThumbnailOffset *IfdTagEntry
var enumeratorThumbnailSize *IfdTagEntry
for i := 0; i < int(tagCount); i++ {
tag, err := ie.parseTag(fqIfdPath, i, ite, resolveValues)
ite, err := ie.parseTag(fqIfdPath, i, enumerator)
if err != nil {
if log.Is(err, ErrTagTypeNotValid) == true {
ifdEnumerateLogger.Warningf(nil, "Tag in IFD [%s] at position (%d) has invalid type and will be skipped.", fqIfdPath, i)
@ -283,43 +246,44 @@ func (ie *IfdEnumerate) ParseIfd(fqIfdPath string, ifdIndex int, ite *IfdTagEnum
log.Panic(err)
}
if tag.TagId == ThumbnailOffsetTagId {
iteThumbnailOffset = tag
tagId := ite.TagId()
if tagId == ThumbnailOffsetTagId {
enumeratorThumbnailOffset = ite
continue
} else if tag.TagId == ThumbnailSizeTagId {
iteThumbnailSize = tag
} else if tagId == ThumbnailSizeTagId {
enumeratorThumbnailSize = ite
continue
}
if visitor != nil {
valueContext := ie.GetValueContext(tag)
valueContext := ite.GetValueContext()
err := visitor.Visit(fqIfdPath, ifdIndex, tag.TagId, tag.TagType, valueContext)
err := visitor.Visit(fqIfdPath, ifdIndex, ite.TagId(), ite.TagType(), valueContext)
log.PanicIf(err)
}
// If it's an IFD but not a standard one, it'll just be seen as a LONG
// (the standard IFD tag type), later, unless we skip it because it's
// [likely] not even in the standard list of known tags.
if tag.ChildIfdPath != "" {
if ite.ChildIfdPath() != "" {
if doDescend == true {
ifdEnumerateLogger.Debugf(nil, "Descending to IFD [%s].", tag.ChildIfdPath)
ifdEnumerateLogger.Debugf(nil, "Descending to IFD [%s].", ite.ChildIfdPath())
err := ie.scan(tag.ChildFqIfdPath, tag.ValueOffset, visitor, resolveValues)
err := ie.scan(ite.ChildFqIfdPath(), ite.valueOffset_(), visitor)
log.PanicIf(err)
}
}
entries = append(entries, tag)
entries = append(entries, ite)
}
if iteThumbnailOffset != nil && iteThumbnailSize != nil {
thumbnailData, err = ie.parseThumbnail(iteThumbnailOffset, iteThumbnailSize)
if enumeratorThumbnailOffset != nil && enumeratorThumbnailSize != nil {
thumbnailData, err = ie.parseThumbnail(enumeratorThumbnailOffset, enumeratorThumbnailSize)
log.PanicIf(err)
}
nextIfdOffset, _, err = ite.getUint32()
nextIfdOffset, _, err = enumerator.getUint32()
log.PanicIf(err)
ifdEnumerateLogger.Debugf(nil, "Next IFD at offset: (%08x)", nextIfdOffset)
@ -334,9 +298,7 @@ func (ie *IfdEnumerate) parseThumbnail(offsetIte, lengthIte *IfdTagEntry) (thumb
}
}()
addressableData := ie.exifData[ExifAddressableAreaStart:]
vRaw, err := lengthIte.Value(addressableData, ie.byteOrder)
vRaw, err := lengthIte.Value()
log.PanicIf(err)
vList := vRaw.([]uint32)
@ -347,17 +309,17 @@ func (ie *IfdEnumerate) parseThumbnail(offsetIte, lengthIte *IfdTagEntry) (thumb
length := vList[0]
// The tag is official a LONG type, but it's actually an offset to a blob of bytes.
offsetIte.TagType = exifcommon.TypeByte
offsetIte.UnitCount = length
offsetIte.updateTagType(exifcommon.TypeByte)
offsetIte.updateUnitCount(length)
thumbnailData, err = offsetIte.ValueBytes(addressableData, ie.byteOrder)
thumbnailData, err = offsetIte.RawBytes()
log.PanicIf(err)
return thumbnailData, nil
}
// Scan enumerates the different EXIF's IFD blocks.
func (ie *IfdEnumerate) scan(fqIfdName string, ifdOffset uint32, visitor RawTagWalk, resolveValues bool) (err error) {
func (ie *IfdEnumerate) scan(fqIfdName string, ifdOffset uint32, visitor RawTagWalk) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
@ -366,9 +328,9 @@ func (ie *IfdEnumerate) scan(fqIfdName string, ifdOffset uint32, visitor RawTagW
for ifdIndex := 0; ; ifdIndex++ {
ifdEnumerateLogger.Debugf(nil, "Parsing IFD [%s] (%d) at offset (%04x).", fqIfdName, ifdIndex, ifdOffset)
ite := ie.getTagEnumerator(ifdOffset)
enumerator := ie.getTagEnumerator(ifdOffset)
nextIfdOffset, _, _, err := ie.ParseIfd(fqIfdName, ifdIndex, ite, visitor, true, resolveValues)
nextIfdOffset, _, _, err := ie.ParseIfd(fqIfdName, ifdIndex, enumerator, visitor, true)
log.PanicIf(err)
if nextIfdOffset == 0 {
@ -383,14 +345,14 @@ func (ie *IfdEnumerate) scan(fqIfdName string, ifdOffset uint32, visitor RawTagW
// Scan enumerates the different EXIF blocks (called IFDs). `rootIfdName` will
// be "IFD" in the TIFF standard.
func (ie *IfdEnumerate) Scan(rootIfdName string, ifdOffset uint32, visitor RawTagWalk, resolveValue bool) (err error) {
func (ie *IfdEnumerate) Scan(rootIfdName string, ifdOffset uint32, visitor RawTagWalk) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
err = ie.scan(rootIfdName, ifdOffset, visitor, resolveValue)
err = ie.scan(rootIfdName, ifdOffset, visitor)
log.PanicIf(err)
return nil
@ -399,7 +361,8 @@ func (ie *IfdEnumerate) Scan(rootIfdName string, ifdOffset uint32, visitor RawTa
// Ifd represents a single parsed IFD.
type Ifd struct {
// TODO(dustin): !! Why are all of these public? Privatize them and then add NextIfd().
// TODO(dustin): !! Why are all of these exported? Stop doing this in the next release.
// TODO(dustin): Add NextIfd().
// 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
@ -467,32 +430,6 @@ func (ifd *Ifd) ChildWithIfdPath(ifdPath string) (childIfd *Ifd, err error) {
return nil, nil
}
func (ifd *Ifd) TagValue(ite *IfdTagEntry) (value interface{}, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
value, err = ite.Value(ifd.addressableData, ifd.ByteOrder)
log.PanicIf(err)
return value, nil
}
func (ifd *Ifd) TagValueBytes(ite *IfdTagEntry) (value []byte, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
value, err = ite.ValueBytes(ifd.addressableData, ifd.ByteOrder)
log.PanicIf(err)
return value, nil
}
// FindTagWithId returns a list of tags (usually just zero or one) that match
// the given tag ID. This is efficient.
func (ifd *Ifd) FindTagWithId(tagId uint16) (results []*IfdTagEntry, err error) {
@ -528,7 +465,7 @@ func (ifd *Ifd) FindTagWithName(tagName string) (results []*IfdTagEntry, err err
results = make([]*IfdTagEntry, 0)
for _, ite := range ifd.Entries {
if ite.TagId == it.Id {
if ite.TagId() == it.Id {
results = append(results, ite)
}
}
@ -540,7 +477,7 @@ func (ifd *Ifd) FindTagWithName(tagName string) (results []*IfdTagEntry, err err
return results, nil
}
func (ifd Ifd) String() string {
func (ifd *Ifd) String() string {
parentOffset := uint32(0)
if ifd.ParentIfd != nil {
parentOffset = ifd.ParentIfd.Offset
@ -563,6 +500,7 @@ func (ifd *Ifd) Thumbnail() (data []byte, err error) {
return ifd.thumbnailData, nil
}
// dumpTags recursively builds a list of tags from an IFD.
func (ifd *Ifd) dumpTags(tags []*IfdTagEntry) []*IfdTagEntry {
if tags == nil {
tags = make([]*IfdTagEntry, 0)
@ -572,15 +510,16 @@ func (ifd *Ifd) dumpTags(tags []*IfdTagEntry) []*IfdTagEntry {
ifdsFoundCount := 0
for _, tag := range ifd.Entries {
tags = append(tags, tag)
for _, ite := range ifd.Entries {
tags = append(tags, ite)
if tag.ChildIfdPath != "" {
childIfdPath := ite.ChildIfdPath()
if childIfdPath != "" {
ifdsFoundCount++
childIfd, found := ifd.ChildIfdIndex[tag.ChildIfdPath]
childIfd, found := ifd.ChildIfdIndex[childIfdPath]
if found != true {
log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdPath)
log.Panicf("alien child IFD referenced by a tag: [%s]", childIfdPath)
}
tags = childIfd.dumpTags(tags)
@ -603,6 +542,50 @@ func (ifd *Ifd) DumpTags() []*IfdTagEntry {
return ifd.dumpTags(nil)
}
// FormatValue returns a stringified value for any well-defined tag value as
// well as supported undefined-tag values.
func (ifd *Ifd) FormatValue(ite *IfdTagEntry) (valuePhrase string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
value, err := ite.Value()
if err != nil {
if err == exifcommon.ErrUnhandledUnknownTypedTag {
return exifundefined.UnparseableUnknownTagValuePlaceholder, nil
}
log.Panic(err)
}
valuePhrase, err = exifcommon.FormatFromType(value, false)
log.PanicIf(err)
return valuePhrase, nil
}
// Value returns the value for the given tag.
func (ifd *Ifd) Value(ite *IfdTagEntry) (value interface{}, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
value, err = ite.Value()
if err != nil {
if err == exifcommon.ErrUnhandledUnknownTypedTag {
return nil, err
}
log.Panic(err)
}
return value, nil
}
func (ifd *Ifd) printTagTree(populateValues bool, index, level int, nextLink bool) {
indent := strings.Repeat(" ", level*2)
@ -617,40 +600,37 @@ func (ifd *Ifd) printTagTree(populateValues bool, index, level int, nextLink boo
ifdsFoundCount := 0
for _, tag := range ifd.Entries {
if tag.ChildIfdPath != "" {
fmt.Printf("%s - TAG: %s\n", indent, tag)
for _, ite := range ifd.Entries {
if ite.ChildIfdPath() != "" {
fmt.Printf("%s - TAG: %s\n", indent, ite)
} else {
it, err := ifd.tagIndex.Get(ifd.IfdPath, tag.TagId)
it, err := ifd.tagIndex.Get(ifd.IfdPath, ite.TagId())
tagName := ""
if err == nil {
tagName = it.Name
}
var value interface{}
var valuePhrase string
if populateValues == true {
var err error
value, err = ifd.TagValue(tag)
if err != nil {
if err == exifcommon.ErrUnhandledUnknownTypedTag {
value = exifundefined.UnparseableUnknownTagValuePlaceholder
} else {
log.Panic(err)
}
}
valuePhrase, err = ifd.FormatValue(ite)
log.PanicIf(err)
} else {
valuePhrase = "!UNRESOLVED"
}
fmt.Printf("%s - TAG: %s NAME=[%s] VALUE=[%v]\n", indent, tag, tagName, value)
fmt.Printf("%s - TAG: %s NAME=[%s] VALUE=[%v]\n", indent, ite, tagName, valuePhrase)
}
if tag.ChildIfdPath != "" {
childIfdPath := ite.ChildIfdPath()
if childIfdPath != "" {
ifdsFoundCount++
childIfd, found := ifd.ChildIfdIndex[tag.ChildIfdPath]
childIfd, found := ifd.ChildIfdIndex[childIfdPath]
if found != true {
log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdPath)
log.Panicf("alien child IFD referenced by a tag: [%s]", childIfdPath)
}
childIfd.printTagTree(populateValues, 0, level+1, false)
@ -685,13 +665,14 @@ func (ifd *Ifd) printIfdTree(level int, nextLink bool) {
ifdsFoundCount := 0
for _, tag := range ifd.Entries {
if tag.ChildIfdPath != "" {
for _, ite := range ifd.Entries {
childIfdPath := ite.ChildIfdPath()
if childIfdPath != "" {
ifdsFoundCount++
childIfd, found := ifd.ChildIfdIndex[tag.ChildIfdPath]
childIfd, found := ifd.ChildIfdIndex[childIfdPath]
if found != true {
log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdPath)
log.Panicf("alien child IFD referenced by a tag: [%s]", childIfdPath)
}
childIfd.printIfdTree(level+1, false)
@ -730,15 +711,16 @@ func (ifd *Ifd) dumpTree(tagsDump []string, level int) []string {
tagsDump = append(tagsDump, startBlurb)
ifdsFoundCount := 0
for _, tag := range ifd.Entries {
tagsDump = append(tagsDump, fmt.Sprintf("%s - (0x%04x)", indent, tag.TagId))
for _, ite := range ifd.Entries {
tagsDump = append(tagsDump, fmt.Sprintf("%s - (0x%04x)", indent, ite.TagId()))
if tag.ChildIfdPath != "" {
childIfdPath := ite.ChildIfdPath()
if childIfdPath != "" {
ifdsFoundCount++
childIfd, found := ifd.ChildIfdIndex[tag.ChildIfdPath]
childIfd, found := ifd.ChildIfdIndex[childIfdPath]
if found != true {
log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdPath)
log.Panicf("alien child IFD referenced by a tag: [%s]", childIfdPath)
}
tagsDump = childIfd.dumpTree(tagsDump, level+1)
@ -784,21 +766,24 @@ func (ifd *Ifd) GpsInfo() (gi *GpsInfo, err error) {
log.Panicf("GPS can only be read on GPS IFD: [%s] != [%s]", ifd.IfdPath, exifcommon.IfdPathStandardGps)
}
if tags, found := ifd.EntriesByTagId[TagVersionId]; found == false {
if tags, found := ifd.EntriesByTagId[TagGpsVersionId]; found == false {
// We've seen this. We'll just have to default to assuming we're in a
// 2.2.0.0 format.
ifdEnumerateLogger.Warningf(nil, "No GPS version tag (0x%04x) found.", TagVersionId)
ifdEnumerateLogger.Warningf(nil, "No GPS version tag (0x%04x) found.", TagGpsVersionId)
} else {
versionBytes, err := tags[0].RawBytes()
log.PanicIf(err)
hit := false
for _, acceptedGpsVersion := range ValidGpsVersions {
if bytes.Compare(tags[0].value, acceptedGpsVersion[:]) == 0 {
if bytes.Compare(versionBytes, acceptedGpsVersion[:]) == 0 {
hit = true
break
}
}
if hit != true {
ifdEnumerateLogger.Warningf(nil, "GPS version not supported: %v", tags[0].value)
ifdEnumerateLogger.Warningf(nil, "GPS version not supported: %v", versionBytes)
log.Panic(ErrNoGpsTags)
}
}
@ -809,7 +794,7 @@ func (ifd *Ifd) GpsInfo() (gi *GpsInfo, err error) {
log.Panic(ErrNoGpsTags)
}
latitudeValue, err := ifd.TagValue(tags[0])
latitudeValue, err := ifd.Value(tags[0])
log.PanicIf(err)
// Look for whether North or South.
@ -819,7 +804,7 @@ func (ifd *Ifd) GpsInfo() (gi *GpsInfo, err error) {
log.Panic(ErrNoGpsTags)
}
latitudeRefValue, err := ifd.TagValue(tags[0])
latitudeRefValue, err := ifd.Value(tags[0])
log.PanicIf(err)
tags, found = ifd.EntriesByTagId[TagLongitudeId]
@ -828,7 +813,7 @@ func (ifd *Ifd) GpsInfo() (gi *GpsInfo, err error) {
log.Panic(ErrNoGpsTags)
}
longitudeValue, err := ifd.TagValue(tags[0])
longitudeValue, err := ifd.Value(tags[0])
log.PanicIf(err)
// Look for whether West or East.
@ -838,7 +823,7 @@ func (ifd *Ifd) GpsInfo() (gi *GpsInfo, err error) {
log.Panic(ErrNoGpsTags)
}
longitudeRefValue, err := ifd.TagValue(tags[0])
longitudeRefValue, err := ifd.Value(tags[0])
log.PanicIf(err)
// Parse location.
@ -867,10 +852,10 @@ func (ifd *Ifd) GpsInfo() (gi *GpsInfo, err error) {
altitudeRefTags, foundAltitudeRef := ifd.EntriesByTagId[TagAltitudeRefId]
if foundAltitude == true && foundAltitudeRef == true {
altitudeValue, err := ifd.TagValue(altitudeTags[0])
altitudeValue, err := ifd.Value(altitudeTags[0])
log.PanicIf(err)
altitudeRefValue, err := ifd.TagValue(altitudeRefTags[0])
altitudeRefValue, err := ifd.Value(altitudeRefTags[0])
log.PanicIf(err)
altitudeRaw := altitudeValue.([]exifcommon.Rational)
@ -888,7 +873,7 @@ func (ifd *Ifd) GpsInfo() (gi *GpsInfo, err error) {
datestampTags, foundDatestamp := ifd.EntriesByTagId[TagDatestampId]
if foundTimestamp == true && foundDatestamp == true {
datestampValue, err := ifd.TagValue(datestampTags[0])
datestampValue, err := ifd.Value(datestampTags[0])
log.PanicIf(err)
dateParts := strings.Split(datestampValue.(string), ":")
@ -898,7 +883,7 @@ func (ifd *Ifd) GpsInfo() (gi *GpsInfo, err error) {
day, err3 := strconv.ParseUint(dateParts[2], 10, 8)
if err1 == nil && err2 == nil && err3 == nil {
timestampValue, err := ifd.TagValue(timestampTags[0])
timestampValue, err := ifd.Value(timestampTags[0])
log.PanicIf(err)
timestampRaw := timestampValue.([]exifcommon.Rational)
@ -925,8 +910,9 @@ func (ifd *Ifd) EnumerateTagsRecursively(visitor ParsedTagVisitor) (err error) {
for ptr := ifd; ptr != nil; ptr = ptr.NextIfd {
for _, ite := range ifd.Entries {
if ite.ChildIfdPath != "" {
childIfd := ifd.ChildIfdIndex[ite.ChildIfdPath]
childIfdPath := ite.ChildIfdPath()
if childIfdPath != "" {
childIfd := ifd.ChildIfdIndex[childIfdPath]
err := childIfd.EnumerateTagsRecursively(visitor)
log.PanicIf(err)
@ -940,13 +926,6 @@ func (ifd *Ifd) EnumerateTagsRecursively(visitor ParsedTagVisitor) (err error) {
return nil
}
func (ifd *Ifd) GetValueContext(ite *IfdTagEntry) *exifcommon.ValueContext {
return newValueContextFromTag(
ite,
ifd.addressableData,
ifd.ByteOrder)
}
type QueuedIfd struct {
Name string
IfdPath string
@ -972,7 +951,7 @@ type IfdIndex struct {
}
// Scan enumerates the different EXIF blocks (called IFDs).
func (ie *IfdEnumerate) Collect(rootIfdOffset uint32, resolveValues bool) (index IfdIndex, err error) {
func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
@ -1016,21 +995,23 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32, resolveValues bool) (index
queue = queue[1:]
ifdEnumerateLogger.Debugf(nil, "Parsing IFD [%s] (%d) at offset (%04x).", ifdPath, index, offset)
ite := ie.getTagEnumerator(offset)
enumerator := ie.getTagEnumerator(offset)
nextIfdOffset, entries, thumbnailData, err := ie.ParseIfd(fqIfdPath, index, ite, nil, false, resolveValues)
nextIfdOffset, entries, thumbnailData, err := ie.ParseIfd(fqIfdPath, index, enumerator, nil, false)
log.PanicIf(err)
id := len(ifds)
entriesByTagId := make(map[uint16][]*IfdTagEntry)
for _, tag := range entries {
tags, found := entriesByTagId[tag.TagId]
for _, ite := range entries {
tagId := ite.TagId()
tags, found := entriesByTagId[tagId]
if found == false {
tags = make([]*IfdTagEntry, 0)
}
entriesByTagId[tag.TagId] = append(tags, tag)
entriesByTagId[tagId] = append(tags, ite)
}
ifd := &Ifd{
@ -1093,19 +1074,19 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32, resolveValues bool) (index
}
// Determine if any of our entries is a child IFD and queue it.
for i, entry := range entries {
if entry.ChildIfdPath == "" {
for i, ite := range entries {
if ite.ChildIfdPath() == "" {
continue
}
qi := QueuedIfd{
Name: entry.ChildIfdName,
IfdPath: entry.ChildIfdPath,
FqIfdPath: entry.ChildFqIfdPath,
TagId: entry.TagId,
Name: ite.ChildIfdName(),
IfdPath: ite.ChildIfdPath(),
FqIfdPath: ite.ChildFqIfdPath(),
TagId: ite.TagId(),
Index: 0,
Offset: entry.ValueOffset,
Offset: ite.valueOffset_(),
Parent: ifd,
ParentTagIndex: i,
}
@ -1175,7 +1156,7 @@ func (ie *IfdEnumerate) setChildrenIndex(ifd *Ifd) (err error) {
// ParseOneIfd is a hack to use an IE to parse a raw IFD block. Can be used for
// testing.
func ParseOneIfd(ifdMapping *IfdMapping, tagIndex *TagIndex, fqIfdPath, ifdPath string, byteOrder binary.ByteOrder, ifdBlock []byte, visitor RawTagWalk, resolveValues bool) (nextIfdOffset uint32, entries []*IfdTagEntry, err error) {
func ParseOneIfd(ifdMapping *IfdMapping, tagIndex *TagIndex, fqIfdPath, ifdPath string, byteOrder binary.ByteOrder, ifdBlock []byte, visitor RawTagWalk) (nextIfdOffset uint32, entries []*IfdTagEntry, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
@ -1183,16 +1164,16 @@ func ParseOneIfd(ifdMapping *IfdMapping, tagIndex *TagIndex, fqIfdPath, ifdPath
}()
ie := NewIfdEnumerate(ifdMapping, tagIndex, make([]byte, 0), byteOrder)
ite := NewIfdTagEnumerator(ifdBlock, byteOrder, 0)
enumerator := NewIfdTagEnumerator(ifdBlock, byteOrder, 0)
nextIfdOffset, entries, _, err = ie.ParseIfd(fqIfdPath, 0, ite, visitor, true, resolveValues)
nextIfdOffset, entries, _, err = ie.ParseIfd(fqIfdPath, 0, enumerator, visitor, true)
log.PanicIf(err)
return nextIfdOffset, entries, nil
}
// ParseOneTag is a hack to use an IE to parse a raw tag block.
func ParseOneTag(ifdMapping *IfdMapping, tagIndex *TagIndex, fqIfdPath, ifdPath string, byteOrder binary.ByteOrder, tagBlock []byte, resolveValue bool) (tag *IfdTagEntry, err error) {
func ParseOneTag(ifdMapping *IfdMapping, tagIndex *TagIndex, fqIfdPath, ifdPath string, byteOrder binary.ByteOrder, tagBlock []byte) (tag *IfdTagEntry, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
@ -1200,9 +1181,9 @@ func ParseOneTag(ifdMapping *IfdMapping, tagIndex *TagIndex, fqIfdPath, ifdPath
}()
ie := NewIfdEnumerate(ifdMapping, tagIndex, make([]byte, 0), byteOrder)
ite := NewIfdTagEnumerator(tagBlock, byteOrder, 0)
enumerator := NewIfdTagEnumerator(tagBlock, byteOrder, 0)
tag, err = ie.parseTag(fqIfdPath, 0, ite, resolveValue)
tag, err = ie.parseTag(fqIfdPath, 0, enumerator)
log.PanicIf(err)
return tag, nil

View File

@ -7,7 +7,6 @@ import (
"reflect"
"testing"
"encoding/binary"
"io/ioutil"
"github.com/dsoprea/go-logging"
@ -15,34 +14,7 @@ import (
"github.com/dsoprea/go-exif/v2/common"
)
func TestIfdTagEntry_ValueBytes(t *testing.T) {
byteOrder := binary.BigEndian
ve := exifcommon.NewValueEncoder(byteOrder)
original := []byte("original text")
ed, err := ve.Encode(original)
log.PanicIf(err)
// Now, pass the raw encoded value as if it was the entire addressable area
// and provide an offset of 0 as if it was a real block of data and this
// value happened to be recorded at the beginning.
ite := IfdTagEntry{
TagType: exifcommon.TypeByte,
UnitCount: uint32(len(original)),
ValueOffset: 0,
}
decodedBytes, err := ite.ValueBytes(ed.Encoded, byteOrder)
log.PanicIf(err)
if bytes.Compare(decodedBytes, original) != 0 {
t.Fatalf("Bytes not decoded correctly.")
}
}
func TestIfdTagEntry_ValueBytes_RealData(t *testing.T) {
func TestIfdTagEntry_RawBytes_RealData(t *testing.T) {
defer func() {
if state := recover(); state != nil {
err := log.Wrap(state.(error))
@ -60,12 +32,12 @@ func TestIfdTagEntry_ValueBytes_RealData(t *testing.T) {
ti := NewTagIndex()
eh, index, err := Collect(im, ti, rawExif)
_, index, err := Collect(im, ti, rawExif)
log.PanicIf(err)
var ite *IfdTagEntry
for _, thisIte := range index.RootIfd.Entries {
if thisIte.TagId == 0x0110 {
if thisIte.TagId() == 0x0110 {
ite = thisIte
break
}
@ -75,14 +47,13 @@ func TestIfdTagEntry_ValueBytes_RealData(t *testing.T) {
t.Fatalf("Tag not found.")
}
addressableData := rawExif[ExifAddressableAreaStart:]
decodedBytes, err := ite.ValueBytes(addressableData, eh.ByteOrder)
decodedBytes, err := ite.RawBytes()
log.PanicIf(err)
expected := []byte("Canon EOS 5D Mark III")
expected = append(expected, 0)
if len(decodedBytes) != int(ite.UnitCount) {
if len(decodedBytes) != int(ite.UnitCount()) {
t.Fatalf("Decoded bytes not the right count.")
} else if bytes.Compare(decodedBytes, expected) != 0 {
t.Fatalf("Decoded bytes not correct.")
@ -108,7 +79,7 @@ func TestIfd_FindTagWithId_Hit(t *testing.T) {
if len(results) != 1 {
t.Fatalf("Exactly one result was not found: (%d)", len(results))
} else if results[0].TagId != 0x011b {
} else if results[0].TagId() != 0x011b {
t.Fatalf("The result was not expected: %v", results[0])
}
}
@ -156,7 +127,7 @@ func TestIfd_FindTagWithName_Hit(t *testing.T) {
if len(results) != 1 {
t.Fatalf("Exactly one result was not found: (%d)", len(results))
} else if results[0].TagId != 0x011b {
} else if results[0].TagId() != 0x011b {
t.Fatalf("The result was not expected: %v", results[0])
}
}
@ -304,7 +275,7 @@ func TestIfd_EnumerateTagsRecursively(t *testing.T) {
cb := func(ifd *Ifd, ite *IfdTagEntry) error {
item := [2]interface{}{
ifd.IfdPath,
int(ite.TagId),
int(ite.TagId()),
}
collected = append(collected, item)
@ -512,8 +483,10 @@ func ExampleIfd_FindTagWithName() {
tagName := "Model"
rootIfd := index.RootIfd
// We know the tag we want is on IFD0 (the first/root IFD).
results, err := index.RootIfd.FindTagWithName(tagName)
results, err := rootIfd.FindTagWithName(tagName)
log.PanicIf(err)
// This should never happen.
@ -522,8 +495,9 @@ func ExampleIfd_FindTagWithName() {
}
ite := results[0]
valueContext := ite.GetValueContext()
valueRaw, err := index.RootIfd.TagValue(ite)
valueRaw, err := valueContext.Values()
log.PanicIf(err)
value := valueRaw.(string)

View File

@ -15,141 +15,144 @@ var (
iteLogger = log.NewLogger("exif.ifd_tag_entry")
)
// IfdTagEntry refers to a tag in the loaded EXIF block.
type IfdTagEntry struct {
TagId uint16
TagIndex int
TagType exifcommon.TagTypePrimitive
UnitCount uint32
ValueOffset uint32
RawValueOffset []byte
tagId uint16
tagIndex int
tagType exifcommon.TagTypePrimitive
unitCount uint32
valueOffset uint32
rawValueOffset []byte
// ChildIfdName is the right most atom in the IFD-path. We need this to
// childIfdName is the right most atom in the IFD-path. We need this to
// construct the fully-qualified IFD-path.
ChildIfdName string
childIfdName string
// ChildIfdPath is the IFD-path of the child if this tag represents a child
// childIfdPath is the IFD-path of the child if this tag represents a child
// IFD.
ChildIfdPath string
childIfdPath string
// ChildFqIfdPath is the IFD-path of the child if this tag represents a
// childFqIfdPath is the IFD-path of the child if this tag represents a
// child IFD. Includes indices.
ChildFqIfdPath string
childFqIfdPath string
// TODO(dustin): !! IB's host the child-IBs directly in the tag, but that's not the case here. Refactor to accomodate it for a consistent experience.
// IfdPath is the IFD that this tag belongs to.
IfdPath string
// ifdPath is the IFD that this tag belongs to.
ifdPath string
// TODO(dustin): !! We now parse and read the value immediately. Update the rest of the logic to use this and get rid of all of the staggered and different resolution mechanisms.
value []byte
isUnhandledUnknown bool
addressableData []byte
byteOrder binary.ByteOrder
}
func (ite *IfdTagEntry) String() string {
return fmt.Sprintf("IfdTagEntry<TAG-IFD-PATH=[%s] TAG-ID=(0x%04x) TAG-TYPE=[%s] UNIT-COUNT=(%d)>", ite.IfdPath, ite.TagId, ite.TagType.String(), ite.UnitCount)
}
// TODO(dustin): TODO(dustin): Stop exporting IfdPath and TagId.
//
// func (ite *IfdTagEntry) IfdPath() string {
// return ite.IfdPath
// }
// TODO(dustin): TODO(dustin): Stop exporting IfdPath and TagId.
//
// func (ite *IfdTagEntry) TagId() uint16 {
// return ite.TagId
// }
// ValueString renders a string from whatever the value in this tag is.
func (ite *IfdTagEntry) ValueString(addressableData []byte, byteOrder binary.ByteOrder) (phrase string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
valueContext :=
newValueContextFromTag(
ite,
addressableData,
byteOrder)
if ite.TagType == exifcommon.TypeUndefined {
var err error
value, err := exifundefined.Decode(valueContext)
log.PanicIf(err)
s := value.(fmt.Stringer)
phrase = s.String()
} else {
var err error
phrase, err = valueContext.Format()
log.PanicIf(err)
func newIfdTagEntry(ifdPath string, tagId uint16, tagIndex int, tagType exifcommon.TagTypePrimitive, unitCount uint32, valueOffset uint32, rawValueOffset []byte, addressableData []byte, byteOrder binary.ByteOrder) *IfdTagEntry {
return &IfdTagEntry{
ifdPath: ifdPath,
tagId: tagId,
tagIndex: tagIndex,
tagType: tagType,
unitCount: unitCount,
valueOffset: valueOffset,
rawValueOffset: rawValueOffset,
addressableData: addressableData,
byteOrder: byteOrder,
}
return phrase, nil
}
// ValueBytes renders a specific list of bytes from the value in this tag.
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))
}
}()
// String returns a stringified representation of the struct.
func (ite *IfdTagEntry) String() string {
return fmt.Sprintf("IfdTagEntry<TAG-IFD-PATH=[%s] TAG-ID=(0x%04x) TAG-TYPE=[%s] UNIT-COUNT=(%d)>", ite.ifdPath, ite.tagId, ite.tagType.String(), ite.unitCount)
}
valueContext :=
newValueContextFromTag(
ite,
addressableData,
byteOrder)
// IfdPath returns the path of the IFD that owns this tag.
func (ite *IfdTagEntry) IfdPath() string {
return ite.ifdPath
}
// Return the exact bytes of the unknown-type value. Returning a string
// (`ValueString`) is easy because we can just pass everything to
// `Sprintf()`. Returning the raw, typed value (`Value`) is easy
// (obviously). However, here, in order to produce the list of bytes, we
// need to coerce whatever `Undefined()` returns.
if ite.TagType == exifcommon.TypeUndefined {
// TagId returns the ID of the tag that we represent. The combination of
// (IfdPath(), TagId()) is unique.
func (ite *IfdTagEntry) TagId() uint16 {
return ite.tagId
}
// TagType is the type of value for this tag.
func (ite *IfdTagEntry) TagType() exifcommon.TagTypePrimitive {
return ite.tagType
}
// updateTagType sets an alternatively interpreted tag-type.
func (ite *IfdTagEntry) updateTagType(tagType exifcommon.TagTypePrimitive) {
ite.tagType = tagType
}
// UnitCount returns the unit-count of the tag's value.
func (ite *IfdTagEntry) UnitCount() uint32 {
return ite.unitCount
}
// updateUnitCount sets an alternatively interpreted unit-count.
func (ite *IfdTagEntry) updateUnitCount(unitCount uint32) {
ite.unitCount = unitCount
}
// valueOffset_ is the four-byte offset converted to an integer to point to the
// location of its value in the EXIF block.
func (ite *IfdTagEntry) valueOffset_() uint32 {
return ite.valueOffset
}
// RawBytes renders a specific list of bytes from the value in this tag.
func (ite *IfdTagEntry) RawBytes() (rawBytes []byte, err error) {
valueContext := ite.GetValueContext()
if ite.TagType() == exifcommon.TypeUndefined {
value, err := exifundefined.Decode(valueContext)
log.PanicIf(err)
if err != nil {
if err == exifcommon.ErrUnhandledUnknownTypedTag {
ite.setIsUnhandledUnknown(true)
} else {
log.Panic(err)
}
}
ve := exifcommon.NewValueEncoder(byteOrder)
// Encode it back, in order to get the raw bytes. This is the best,
// general way to do it with an undefined tag.
ed, err := ve.Encode(value)
log.PanicIf(err)
return ed.Encoded, nil
} else {
rawBytes, err := valueContext.ReadRawEncoded()
rawBytes, _, err := exifundefined.Encode(value, ite.byteOrder)
log.PanicIf(err)
return rawBytes, nil
}
rawBytes, err = valueContext.ReadRawEncoded()
log.PanicIf(err)
return rawBytes, nil
}
// Value returns the specific, parsed, typed value from the tag.
func (ite *IfdTagEntry) Value(addressableData []byte, byteOrder binary.ByteOrder) (value interface{}, err error) {
func (ite *IfdTagEntry) Value() (value interface{}, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
valueContext :=
newValueContextFromTag(
ite,
addressableData,
byteOrder)
valueContext := ite.GetValueContext()
if ite.TagType == exifcommon.TypeUndefined {
if ite.tagType == exifcommon.TypeUndefined {
var err error
value, err = exifundefined.Decode(valueContext)
log.PanicIf(err)
if err != nil {
if err == exifcommon.ErrUnhandledUnknownTypedTag {
return nil, err
}
log.Panic(err)
}
} else {
var err error
@ -159,3 +162,38 @@ func (ite *IfdTagEntry) Value(addressableData []byte, byteOrder binary.ByteOrder
return value, nil
}
func (ite *IfdTagEntry) setIsUnhandledUnknown(isUnhandledUnknown bool) {
ite.isUnhandledUnknown = isUnhandledUnknown
}
// SetChildIfd sets child-IFD information (if we represent a child IFD).
func (ite *IfdTagEntry) SetChildIfd(childFqIfdPath, childIfdPath, childIfdName string) {
ite.childFqIfdPath = childFqIfdPath
ite.childIfdPath = childIfdPath
ite.childIfdName = childIfdName
}
func (ite *IfdTagEntry) ChildIfdName() string {
return ite.childIfdName
}
func (ite *IfdTagEntry) ChildIfdPath() string {
return ite.childIfdPath
}
func (ite *IfdTagEntry) ChildFqIfdPath() string {
return ite.childFqIfdPath
}
func (ite *IfdTagEntry) GetValueContext() *exifcommon.ValueContext {
return exifcommon.NewValueContext(
ite.ifdPath,
ite.tagId,
ite.unitCount,
ite.valueOffset,
ite.rawValueOffset,
ite.addressableData,
ite.tagType,
ite.byteOrder)
}

View File

@ -2,7 +2,6 @@ package exif
import (
"bytes"
"fmt"
"testing"
"github.com/dsoprea/go-logging"
@ -10,51 +9,31 @@ import (
"github.com/dsoprea/go-exif/v2/common"
)
func TestIfdTagEntry_ValueString_Allocated(t *testing.T) {
ite := IfdTagEntry{
TagId: 0x1,
TagIndex: 0,
TagType: exifcommon.TypeByte,
UnitCount: 6,
ValueOffset: 0x0,
RawValueOffset: []byte{0x0, 0x0, 0x0, 0x0},
IfdPath: exifcommon.IfdPathStandard,
}
func TestIfdTagEntry_RawBytes_Allocated(t *testing.T) {
data := []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}
value, err := ite.ValueString(data, exifcommon.TestDefaultByteOrder)
addressableBytes := data
ite := newIfdTagEntry(
exifcommon.IfdPathStandard,
0x1,
0,
exifcommon.TypeByte,
6,
0,
nil,
addressableBytes,
exifcommon.TestDefaultByteOrder)
value, err := ite.RawBytes()
log.PanicIf(err)
expected := "11 22 33 44 55 66"
if value != expected {
t.Fatalf("Value not expected: [%s] != [%s]", value, expected)
if bytes.Compare(value, data) != 0 {
t.Fatalf("Value not expected: [%s] != [%s]", value, data)
}
}
func TestIfdTagEntry_ValueString_Embedded(t *testing.T) {
data := []byte{0x11, 0x22, 0x33, 0x44}
ite := IfdTagEntry{
TagId: 0x1,
TagIndex: 0,
TagType: exifcommon.TypeByte,
UnitCount: 4,
ValueOffset: 0,
RawValueOffset: data,
IfdPath: exifcommon.IfdPathStandard,
}
value, err := ite.ValueString(nil, exifcommon.TestDefaultByteOrder)
log.PanicIf(err)
expected := "11 22 33 44"
if value != expected {
t.Fatalf("Value not expected: [%s] != [%s]", value, expected)
}
}
func TestIfdTagEntry_ValueString_Undefined(t *testing.T) {
func TestIfdTagEntry_RawBytes_Embedded(t *testing.T) {
defer func() {
if state := recover(); state != nil {
err := log.Wrap(state.(error))
@ -64,124 +43,38 @@ func TestIfdTagEntry_ValueString_Undefined(t *testing.T) {
}
}()
data := []uint8{'0', '2', '3', '0'}
data := []byte{0x11, 0x22, 0x33, 0x44}
ite := IfdTagEntry{
TagId: 0x9000,
TagIndex: 0,
TagType: exifcommon.TypeUndefined,
UnitCount: 4,
ValueOffset: 0x0,
RawValueOffset: data,
IfdPath: exifcommon.IfdPathStandardExif,
}
ite := newIfdTagEntry(
exifcommon.IfdPathStandard,
0x1,
0,
exifcommon.TypeByte,
4,
0,
data,
nil,
exifcommon.TestDefaultByteOrder)
value, err := ite.ValueString(nil, exifcommon.TestDefaultByteOrder)
log.PanicIf(err)
expected := "0230"
if value != expected {
t.Fatalf("Value not expected: [%s] != [%s]", value, expected)
}
}
func TestIfdTagEntry_ValueBytes_Allocated(t *testing.T) {
ite := IfdTagEntry{
TagId: 0x1,
TagIndex: 0,
TagType: exifcommon.TypeByte,
UnitCount: 6,
ValueOffset: 0x0,
RawValueOffset: []byte{0x0, 0x0, 0x0, 0x0},
IfdPath: exifcommon.IfdPathStandard,
}
data := []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}
value, err := ite.ValueBytes(data, exifcommon.TestDefaultByteOrder)
value, err := ite.RawBytes()
log.PanicIf(err)
if bytes.Compare(value, data) != 0 {
t.Fatalf("Value not expected: [%s] != [%s]", value, data)
}
}
func TestIfdTagEntry_ValueBytes_Embedded(t *testing.T) {
data := []byte{0x11, 0x22, 0x33, 0x44}
ite := IfdTagEntry{
TagId: 0x1,
TagIndex: 0,
TagType: exifcommon.TypeByte,
UnitCount: 4,
ValueOffset: 0x0,
RawValueOffset: data,
IfdPath: exifcommon.IfdPathStandard,
}
value, err := ite.ValueBytes(nil, exifcommon.TestDefaultByteOrder)
log.PanicIf(err)
if bytes.Compare(value, data) != 0 {
t.Fatalf("Value not expected: [%s] != [%s]", value, data)
}
}
func TestIfdTagEntry_Value_Normal(t *testing.T) {
data := []byte{0x11, 0x22, 0x33, 0x44}
ite := IfdTagEntry{
TagId: 0x1,
TagIndex: 0,
TagType: exifcommon.TypeByte,
UnitCount: 4,
ValueOffset: 0x0,
RawValueOffset: data,
IfdPath: exifcommon.IfdPathStandard,
}
value, err := ite.Value(nil, exifcommon.TestDefaultByteOrder)
log.PanicIf(err)
if bytes.Compare(value.([]byte), data) != 0 {
t.Fatalf("Value not expected: [%s] != [%s]", value, data)
}
}
func TestIfdTagEntry_Value_Undefined(t *testing.T) {
data := []uint8{'0', '2', '3', '0'}
ite := IfdTagEntry{
TagId: 0x9000,
TagIndex: 0,
TagType: exifcommon.TypeUndefined,
UnitCount: 4,
ValueOffset: 0x0,
RawValueOffset: data,
IfdPath: exifcommon.IfdPathStandardExif,
}
value, err := ite.Value(nil, exifcommon.TestDefaultByteOrder)
log.PanicIf(err)
s := value.(fmt.Stringer)
recovered := []byte(s.String())
if bytes.Compare(recovered, data) != 0 {
t.Fatalf("Value not expected: [%s] != [%s]", recovered, data)
t.Fatalf("Value not expected: %v != %v", value, data)
}
}
func TestIfdTagEntry_String(t *testing.T) {
ite := IfdTagEntry{
TagId: 0x1,
TagIndex: 0,
TagType: exifcommon.TypeByte,
UnitCount: 6,
ValueOffset: 0x0,
RawValueOffset: []byte{0x0, 0x0, 0x0, 0x0},
IfdPath: exifcommon.IfdPathStandard,
}
ite := newIfdTagEntry(
exifcommon.IfdPathStandard,
0x1,
0,
exifcommon.TypeByte,
6,
0,
nil,
nil,
exifcommon.TestDefaultByteOrder)
expected := "IfdTagEntry<TAG-IFD-PATH=[IFD] TAG-ID=(0x0001) TAG-TYPE=[BYTE] UNIT-COUNT=(6)>"
if ite.String() != expected {

View File

@ -17,7 +17,7 @@ const (
// Exif
TagVersionId = 0x0000
TagGpsVersionId = 0x0000
TagLatitudeId = 0x0002
TagLatitudeRefId = 0x0001

View File

@ -135,8 +135,6 @@ func validateExifSimpleTestIb(exifData []byte, t *testing.T) {
// Verify the values by using the actual, orginal types (this is awesome).
addressableData := exifData[ExifAddressableAreaStart:]
expected := []struct {
tagId uint16
value interface{}
@ -147,12 +145,12 @@ func validateExifSimpleTestIb(exifData []byte, t *testing.T) {
{tagId: 0x013e, value: []exifcommon.Rational{{Numerator: 0x11112222, Denominator: 0x33334444}}},
}
for i, e := range ifd.Entries {
if e.TagId != expected[i].tagId {
t.Fatalf("Tag-ID for entry (%d) not correct: (0x%02x) != (0x%02x)", i, e.TagId, expected[i].tagId)
for i, ite := range ifd.Entries {
if ite.TagId() != expected[i].tagId {
t.Fatalf("Tag-ID for entry (%d) not correct: (0x%02x) != (0x%02x)", i, ite.TagId(), expected[i].tagId)
}
value, err := e.Value(addressableData, exifcommon.TestDefaultByteOrder)
value, err := ite.Value()
log.PanicIf(err)
if reflect.DeepEqual(value, expected[i].value) != true {

View File

@ -116,7 +116,7 @@ func GetFlatExifData(exifData []byte) (exifTags []ExifTag, err error) {
for _, ite := range ifd.Entries {
tagName := ""
it, err := ti.Get(ifd.IfdPath, ite.TagId)
it, err := ti.Get(ifd.IfdPath, ite.TagId())
if err != nil {
// If it's a non-standard tag, just leave the name blank.
if log.Is(err, ErrTagNotFound) != true {
@ -126,7 +126,12 @@ func GetFlatExifData(exifData []byte) (exifTags []ExifTag, err error) {
tagName = it.Name
}
value, err := ifd.TagValue(ite)
valueContext := ite.GetValueContext()
valueBytes, err := valueContext.ReadRawEncoded()
log.PanicIf(err)
value, err := ite.Value()
if err != nil {
if err == exifcommon.ErrUnhandledUnknownTypedTag {
value = exifundefined.UnparseableUnknownTagValuePlaceholder
@ -135,20 +140,17 @@ func GetFlatExifData(exifData []byte) (exifTags []ExifTag, err error) {
}
}
valueBytes, err := ifd.TagValueBytes(ite)
if err != nil && err != exifcommon.ErrUnhandledUnknownTypedTag {
log.Panic(err)
}
tagType := ite.TagType()
et := ExifTag{
IfdPath: ifd.IfdPath,
TagId: ite.TagId,
TagId: ite.TagId(),
TagName: tagName,
TagTypeId: ite.TagType,
TagTypeName: ite.TagType.String(),
TagTypeId: tagType,
TagTypeName: tagType.String(),
Value: value,
ValueBytes: valueBytes,
ChildIfdPath: ite.ChildIfdPath,
ChildIfdPath: ite.ChildIfdPath(),
}
exifTags = append(exifTags, et)

View File

@ -1,19 +0,0 @@
package exif
import (
"encoding/binary"
"github.com/dsoprea/go-exif/v2/common"
)
func newValueContextFromTag(ite *IfdTagEntry, addressableData []byte, byteOrder binary.ByteOrder) *exifcommon.ValueContext {
return exifcommon.NewValueContext(
ite.IfdPath,
ite.TagId,
ite.UnitCount,
ite.ValueOffset,
ite.RawValueOffset,
addressableData,
ite.TagType,
byteOrder)
}