From b748843a75760b1bf46bc3775239c251ce6bfeec Mon Sep 17 00:00:00 2001 From: Dustin Oprea Date: Wed, 2 May 2018 06:04:14 -0400 Subject: [PATCH] tags_unknown: Seeded a first test for encode/decode. - type_encode: `EncodeWithType` can now encode TypeAsciiNoNul. - Resolved/removed a bunch of to-do's. --- TODO | 33 +++++++++++++++++ ifd_builder.go | 8 ++++ ifd_builder_encode.go | 5 +-- ifd_builder_encode_test.go | 14 +------ tags_unknown_test.go | 75 ++++++++++++++++++++++++++++++++++++++ type_encode.go | 8 ++++ 6 files changed, 127 insertions(+), 16 deletions(-) create mode 100644 TODO create mode 100644 tags_unknown_test.go diff --git a/TODO b/TODO new file mode 100644 index 0000000..384a84b --- /dev/null +++ b/TODO @@ -0,0 +1,33 @@ +run the example exif reader tool against all of my images. + + +updating tags: + +- if the data fits in the previous structure or allocation, stick with it and just update the length. + - Note that, if the size decreases to <=4 bytes, we might need to deallocate and embed the value directly in the offset LONG (per the spec). + +adding tags: + +- It would be safest to identify where the next IFD would start (or, (len(raw_exif_data) + 1) if we're in the last IFD), and just append the data, shift the offset of the next IFD, and update the IFD offset (either the pointer in a tag or the offset following the last IFD if we're past the first in a chain). + - We might not otherwise be able to derive the total size of the current tags given 1) those tags having an unknown type with a size that is not easily calculated, and 2) non-standard tags that we simply don't know how to parse. + - Note that we need to build a table of IFD offsets as well as keep track where the offset of each was recorded. + +- allow to delete tags +- allow to delete whole IFDs (and implicitly deallocate tags). + - Only possible if we can parse them and determine the offset and length for the value. + +- allow to create IFDs. + + +Other +== + +- allow to import/export tags from structured data. +- consider writing a couple of quick tools for GPS + - parsing GeJSON and GPX data and installing GPS tags to the EXIF + - writing GeJSON and GPX data *from* the EXIF + + +Notes +== +We might be discretionary in allowing people to *easily* update certain undefined-type tags if they're too complex. We might provide a method to allow them to pass a raw bye-array and count and take responsibility for that values structure (so we don't prevent sufficiently-advanced users from doing from they need to do. diff --git a/ifd_builder.go b/ifd_builder.go index a295af8..d256574 100644 --- a/ifd_builder.go +++ b/ifd_builder.go @@ -73,6 +73,14 @@ type builderTag struct { value *IfdBuilderTagValue } +func NewBuilderTag(ii IfdIdentity, tagId uint16, value *IfdBuilderTagValue) builderTag { + return builderTag{ + ii: ii, + tagId: tagId, + value: value, + } +} + func (bt builderTag) String() string { valuePhrase := "" diff --git a/ifd_builder_encode.go b/ifd_builder_encode.go index f048469..798c0ee 100644 --- a/ifd_builder_encode.go +++ b/ifd_builder_encode.go @@ -216,8 +216,6 @@ func (ibe *IfdByteEncoder) encodeTagToBytes(ib *IfdBuilder, bt *builderTag, bw * if nextIfdOffsetToWrite > 0 { var err error -// TODO(dustin): Create a tool to print/dump the structure and allocation of the output data. - // Create the block of IFD data and everything it requires. childIfdBlock, err = ibe.encodeAndAttachIfd(bt.value.Ib(), nextIfdOffsetToWrite) log.PanicIf(err) @@ -341,7 +339,6 @@ func (ibe *IfdByteEncoder) encodeAndAttachIfd(ib *IfdBuilder, ifdAddressableOffs log.Panicf("trying to encode an IfdBuilder that doesn't have any tags") } -// TODO(dustin): !! There's a lot of numbers-agreement required in our current implementation (where offsets are independently calculated in multiple areas, such as in the first and second runs of encodeIfdToBytes). Refactor this to share the offsets rather than repeatedly calculating them (which has a risk of fallign out of aligning and there being cosnsitency problems). b := new(bytes.Buffer) nextIfdOffsetToWrite := uint32(0) @@ -358,7 +355,7 @@ func (ibe *IfdByteEncoder) encodeAndAttachIfd(ib *IfdBuilder, ifdAddressableOffs // where new IFDs and their data will be allocated). setNextIb := thisIb.nextIb != nil -// TODO(dustin): !! Test the output sizes. + tableAndAllocated, tableSize, allocatedDataSize, _, err := ibe.encodeIfdToBytes(ib, addressableOffset, nextIfdOffsetToWrite, setNextIb) log.PanicIf(err) diff --git a/ifd_builder_encode_test.go b/ifd_builder_encode_test.go index 1a51e4a..0b4c72d 100644 --- a/ifd_builder_encode_test.go +++ b/ifd_builder_encode_test.go @@ -192,7 +192,6 @@ func Test_IfdByteEncoder_encodeTagToBytes_bytes_embedded1(t *testing.T) { addressableOffset := uint32(0x1234) ida := newIfdDataAllocator(addressableOffset) -// TODO(dustin): !! Test with and without nextIfdOffsetToWrite. childIfdBlock, err := ibe.encodeTagToBytes(ib, &bt, bw, ida, uint32(0)) log.PanicIf(err) @@ -218,7 +217,6 @@ func Test_IfdByteEncoder_encodeTagToBytes_bytes_embedded2(t *testing.T) { addressableOffset := uint32(0x1234) ida := newIfdDataAllocator(addressableOffset) -// TODO(dustin): !! Test with and without nextIfdOffsetToWrite. childIfdBlock, err := ibe.encodeTagToBytes(ib, &bt, bw, ida, uint32(0)) log.PanicIf(err) @@ -244,7 +242,6 @@ func Test_IfdByteEncoder_encodeTagToBytes_bytes_allocated(t *testing.T) { bt := NewBuilderTagFromConfig(GpsIi, 0x0000, TestDefaultByteOrder, []uint8 { uint8(0x12), uint8(0x34), uint8(0x56), uint8(0x78), uint8(0x9a) }) -// TODO(dustin): !! Test with and without nextIfdOffsetToWrite. childIfdBlock, err := ibe.encodeTagToBytes(ib, &bt, bw, ida, uint32(0)) log.PanicIf(err) @@ -262,7 +259,6 @@ func Test_IfdByteEncoder_encodeTagToBytes_bytes_allocated(t *testing.T) { bt = NewBuilderTagFromConfig(GpsIi, 0x0000, TestDefaultByteOrder, []uint8 { uint8(0xbc), uint8(0xde), uint8(0xf0), uint8(0x12), uint8(0x34) }) -// TODO(dustin): !! Test with and without nextIfdOffsetToWrite. childIfdBlock, err = ibe.encodeTagToBytes(ib, &bt, bw, ida, uint32(0)) log.PanicIf(err) @@ -389,11 +385,6 @@ func Test_IfdByteEncoder_encodeTagToBytes_childIfd__withAllocate(t *testing.T) { t.Fatalf("IFD first tag parent IFD not correct: %v", iteV.Ii) } - -// TODO(dustin): Test writing some tags that require allocation. -// TODO(dustin): Do an child-IFD allocation in addition to some tag allocations, and vice-verse. - - // Validate the child's raw IFD bytes. childNextIfdOffset, childEntries, err := ParseOneIfd(ExifIi, TestDefaultByteOrder, childIfdBlock, nil) @@ -468,8 +459,6 @@ func Test_IfdByteEncoder_encodeTagToBytes_simpleTag_allocate(t *testing.T) { ite, err := ParseOneTag(RootIi, TestDefaultByteOrder, tagBytes) log.PanicIf(err) -// TODO(dustin): !! When we eventually start allocating values and child-IFDs, be careful that the offsets are calculated correctly. - if ite.TagId != 0x000b { t.Fatalf("Tag-ID not correct: (0x%02x)", ite.TagId) } else if ite.TagIndex != 0 { @@ -735,6 +724,8 @@ func Test_IfdByteEncoder_EncodeToExifPayload(t *testing.T) { func Test_IfdByteEncoder_EncodeToExif(t *testing.T) { ib := getExifSimpleTestIb() +// TODO(dustin): Do a child-IFD allocation in addition to the tag allocations. + ibe := NewIfdByteEncoder() exifData, err := ibe.EncodeToExif(ib) @@ -805,6 +796,5 @@ func ExampleIfdByteEncoder_EncodeToExif() { // TODO(dustin): !! Write test with both chained and child IFDs -// TODO(dustin): !! Test all types. // TODO(dustin): !! Test specific unknown-type tags. // TODO(dustin): !! Test what happens with unhandled unknown-type tags (though it should never get to this point in the normal workflow). diff --git a/tags_unknown_test.go b/tags_unknown_test.go new file mode 100644 index 0000000..2c22b21 --- /dev/null +++ b/tags_unknown_test.go @@ -0,0 +1,75 @@ +package exif + +import ( + "testing" + "bytes" + + "github.com/dsoprea/go-logging" +) + +func TestUndefinedValue_ExifVersion(t *testing.T) { + byteOrder := TestDefaultByteOrder + ii := ExifIi + + + // Create our unknown-type tag's value using the fact that we know it's a + // non-null-terminated string. + + ve := NewValueEncoder(byteOrder) + + tt := NewTagType(TypeAsciiNoNul, byteOrder) + valueString := "0230" + + ed, err := ve.EncodeWithType(tt, valueString) + log.PanicIf(err) + + + // Create the tag using the official "unknown" type now that we already + // have the bytes. + + encodedValue := NewIfdBuilderTagValueFromBytes(ed.Encoded) + bt := NewBuilderTag(ii, 0x9000, encodedValue) + + + // Stage the build. + + ibe := NewIfdByteEncoder() + ib := NewIfdBuilder(ii, byteOrder) + + b := new(bytes.Buffer) + bw := NewByteWriter(b, byteOrder) + + addressableOffset := uint32(0x1234) + ida := newIfdDataAllocator(addressableOffset) + + + // Encode. + + _, err = ibe.encodeTagToBytes(ib, &bt, bw, ida, uint32(0)) + log.PanicIf(err) + + tagBytes := b.Bytes() + + if len(tagBytes) != 12 { + t.Fatalf("Tag not encoded to the right number of bytes: (%d)", len(tagBytes)) + } + + ite, err := ParseOneTag(ii, byteOrder, tagBytes) + log.PanicIf(err) + + if ite.TagId != 0x9000 { + 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 != TypeUndefined { + t.Fatalf("Tag type not correct: (%d)", ite.TagType) + } else if ite.UnitCount != (uint32(len(valueString))) { + t.Fatalf("Tag unit-count not correct: (%d)", ite.UnitCount) + } else if bytes.Compare(ite.RawValueOffset, []byte { '0', '2', '3', '0' }) != 0 { + t.Fatalf("Tag's value (as raw bytes) is not correct: [%x]", ite.RawValueOffset) + } else if ite.ChildIfdName != "" { + t.Fatalf("Tag's IFD-name should be empty: [%s]", ite.ChildIfdName) + } else if ite.Ii != ii { + t.Fatalf("Tag's parent IFD is not correct: %v", ite.Ii) + } +} diff --git a/type_encode.go b/type_encode.go index f348a4f..d6674ee 100644 --- a/type_encode.go +++ b/type_encode.go @@ -204,6 +204,9 @@ func (ve *ValueEncoder) encodeSignedRationals(value []SignedRational) (ed Encode return ed, nil } +// Encode returns bytes for the given value, infering type from the actual +// value. This does not support `TypeAsciiNoNull` (all strings are encoded as +// `TypeAscii`). func (ve *ValueEncoder) Encode(value interface{}) (ed EncodedData, err error) { defer func() { if state := recover(); state != nil { @@ -240,6 +243,8 @@ func (ve *ValueEncoder) Encode(value interface{}) (ed EncodedData, err error) { return ed, nil } +// EncodeWithType returns bytes for the given value, using the given `TagType` +// value to determine how to encode. This supports `TypeAsciiNoNul`. func (ve *ValueEncoder) EncodeWithType(tt TagType, value interface{}) (ed EncodedData, err error) { defer func() { if state := recover(); state != nil { @@ -254,6 +259,9 @@ func (ve *ValueEncoder) EncodeWithType(tt TagType, value interface{}) (ed Encode case TypeAscii: ed, err = ve.encodeAscii(value.(string)) log.PanicIf(err) + case TypeAsciiNoNul: + ed, err = ve.encodeAsciiNoNul(value.(string)) + log.PanicIf(err) case TypeShort: ed, err = ve.encodeShorts(value.([]uint16)) log.PanicIf(err)