From bf3bc4e542b6a4aa86f4ce3a9d0981e760258894 Mon Sep 17 00:00:00 2001 From: Dustin Oprea Date: Thu, 2 Jan 2020 06:00:07 -0500 Subject: [PATCH] Refactor undefined-type tag processing --- exif_test.go | 8 +- v2/common/common_test.go | 13 + v2/common/exif.go | 29 ++ v2/{ => common}/parser.go | 18 +- v2/{ => common}/type.go | 2 +- v2/{ => common}/type_encode.go | 45 +- v2/{ => common}/type_encode_test.go | 84 ++-- v2/common/utility.go | 65 +++ v2/common/value_context.go | 361 +++++++++++++++ v2/{ => common}/value_context_test.go | 2 +- v2/exif.go | 6 - v2/go.mod | 3 +- v2/go.sum | 3 + v2/ifd.go | 28 -- v2/{type_test.go => tag_type_test.go} | 40 +- v2/tags_undefined.go | 417 ------------------ v2/tags_undefined_test.go | 88 ---- v2/undefined/README.md | 4 + v2/undefined/accessor.go | 61 +++ v2/undefined/exif_8828_oecf.go | 94 ++++ v2/undefined/exif_9000_exif_version.go | 32 ++ .../exif_9101_components_configuration.go | 122 +++++ v2/undefined/exif_927C_maker_note.go | 102 +++++ v2/undefined/exif_9286_user_comment.go | 130 ++++++ v2/undefined/exif_A000_flashpix_version.go | 32 ++ .../exif_A20C_spatial_frequency_response.go | 151 +++++++ v2/undefined/exif_A300_file_source.go | 69 +++ v2/undefined/exif_A301_scene_type.go | 66 +++ v2/undefined/exif_A302_cfa_pattern.go | 88 ++++ v2/undefined/exif_iop_0002_interop_version.go | 32 ++ .../gps_001B_gps_processing_method.go | 32 ++ v2/undefined/gps_001C_gps_area_information.go | 32 ++ v2/undefined/registration.go | 42 ++ v2/undefined/type.go | 58 +++ v2/utility.go | 57 --- v2/value_context.go | 344 --------------- 36 files changed, 1707 insertions(+), 1053 deletions(-) create mode 100644 v2/common/common_test.go create mode 100644 v2/common/exif.go rename v2/{ => common}/parser.go (94%) rename v2/{ => common}/type.go (99%) rename v2/{ => common}/type_encode.go (80%) rename v2/{ => common}/type_encode_test.go (90%) create mode 100644 v2/common/utility.go create mode 100644 v2/common/value_context.go rename v2/{ => common}/value_context_test.go (99%) rename v2/{type_test.go => tag_type_test.go} (84%) delete mode 100644 v2/tags_undefined.go delete mode 100644 v2/tags_undefined_test.go create mode 100644 v2/undefined/README.md create mode 100644 v2/undefined/accessor.go create mode 100644 v2/undefined/exif_8828_oecf.go create mode 100644 v2/undefined/exif_9000_exif_version.go create mode 100644 v2/undefined/exif_9101_components_configuration.go create mode 100644 v2/undefined/exif_927C_maker_note.go create mode 100644 v2/undefined/exif_9286_user_comment.go create mode 100644 v2/undefined/exif_A000_flashpix_version.go create mode 100644 v2/undefined/exif_A20C_spatial_frequency_response.go create mode 100644 v2/undefined/exif_A300_file_source.go create mode 100644 v2/undefined/exif_A301_scene_type.go create mode 100644 v2/undefined/exif_A302_cfa_pattern.go create mode 100644 v2/undefined/exif_iop_0002_interop_version.go create mode 100644 v2/undefined/gps_001B_gps_processing_method.go create mode 100644 v2/undefined/gps_001C_gps_area_information.go create mode 100644 v2/undefined/registration.go create mode 100644 v2/undefined/type.go diff --git a/exif_test.go b/exif_test.go index a67d472..2f1de56 100644 --- a/exif_test.go +++ b/exif_test.go @@ -17,7 +17,9 @@ func TestVisit(t *testing.T) { defer func() { if state := recover(); state != nil { err := log.Wrap(state.(error)) - log.PrintErrorf(err, "Exif failure.") + log.PrintError(err) + + t.Fatalf("Test failure.") } }() @@ -213,7 +215,9 @@ func TestCollect(t *testing.T) { defer func() { if state := recover(); state != nil { err := log.Wrap(state.(error)) - log.PrintErrorf(err, "Exif failure.") + log.PrintError(err) + + t.Fatalf("Test failure.") } }() diff --git a/v2/common/common_test.go b/v2/common/common_test.go new file mode 100644 index 0000000..4e68ec0 --- /dev/null +++ b/v2/common/common_test.go @@ -0,0 +1,13 @@ +package exifcommon + +import ( + "encoding/binary" +) + +var ( + // EncodeDefaultByteOrder is the default byte-order for encoding operations. + EncodeDefaultByteOrder = binary.BigEndian + + // Default byte order for tests. + TestDefaultByteOrder = binary.BigEndian +) diff --git a/v2/common/exif.go b/v2/common/exif.go new file mode 100644 index 0000000..cfe9e59 --- /dev/null +++ b/v2/common/exif.go @@ -0,0 +1,29 @@ +package exifcommon + +const ( + // IFD names. The paths that we referred to the IFDs with are comprised of + // these. + + IfdStandard = "IFD" + IfdExif = "Exif" + IfdGps = "GPSInfo" + IfdIop = "Iop" + + // Tag IDs for child IFDs. + + IfdExifId = 0x8769 + IfdGpsId = 0x8825 + IfdIopId = 0xA005 + + // Just a placeholder. + + IfdRootId = 0x0000 + + // The paths of the standard IFDs expressed in the standard IFD-mappings + // and as the group-names in the tag data. + + IfdPathStandard = "IFD" + IfdPathStandardExif = "IFD/Exif" + IfdPathStandardExifIop = "IFD/Exif/Iop" + IfdPathStandardGps = "IFD/GPSInfo" +) diff --git a/v2/parser.go b/v2/common/parser.go similarity index 94% rename from v2/parser.go rename to v2/common/parser.go index 4702db2..faa2fb3 100644 --- a/v2/parser.go +++ b/v2/common/parser.go @@ -1,4 +1,4 @@ -package exif +package exifcommon import ( "bytes" @@ -18,6 +18,8 @@ func (p *Parser) ParseBytes(data []byte, unitCount uint32) (value []uint8, err e } }() + // TODO(dustin): Add test + count := int(unitCount) if len(data) < (TypeByte.Size() * count) { @@ -37,6 +39,8 @@ func (p *Parser) ParseAscii(data []byte, unitCount uint32) (value string, err er } }() + // TODO(dustin): Add test + count := int(unitCount) if len(data) < (TypeAscii.Size() * count) { @@ -65,6 +69,8 @@ func (p *Parser) ParseAsciiNoNul(data []byte, unitCount uint32) (value string, e } }() + // TODO(dustin): Add test + count := int(unitCount) if len(data) < (TypeAscii.Size() * count) { @@ -81,6 +87,8 @@ func (p *Parser) ParseShorts(data []byte, unitCount uint32, byteOrder binary.Byt } }() + // TODO(dustin): Add test + count := int(unitCount) if len(data) < (TypeShort.Size() * count) { @@ -102,6 +110,8 @@ func (p *Parser) ParseLongs(data []byte, unitCount uint32, byteOrder binary.Byte } }() + // TODO(dustin): Add test + count := int(unitCount) if len(data) < (TypeLong.Size() * count) { @@ -123,6 +133,8 @@ func (p *Parser) ParseRationals(data []byte, unitCount uint32, byteOrder binary. } }() + // TODO(dustin): Add test + count := int(unitCount) if len(data) < (TypeRational.Size() * count) { @@ -145,6 +157,8 @@ func (p *Parser) ParseSignedLongs(data []byte, unitCount uint32, byteOrder binar } }() + // TODO(dustin): Add test + count := int(unitCount) if len(data) < (TypeSignedLong.Size() * count) { @@ -169,6 +183,8 @@ func (p *Parser) ParseSignedRationals(data []byte, unitCount uint32, byteOrder b } }() + // TODO(dustin): Add test + count := int(unitCount) if len(data) < (TypeSignedRational.Size() * count) { diff --git a/v2/type.go b/v2/common/type.go similarity index 99% rename from v2/type.go rename to v2/common/type.go index 0d635b7..8fe5a64 100644 --- a/v2/type.go +++ b/v2/common/type.go @@ -1,4 +1,4 @@ -package exif +package exifcommon import ( "errors" diff --git a/v2/type_encode.go b/v2/common/type_encode.go similarity index 80% rename from v2/type_encode.go rename to v2/common/type_encode.go index f2c2e91..158176d 100644 --- a/v2/type_encode.go +++ b/v2/common/type_encode.go @@ -1,4 +1,4 @@ -package exif +package exifcommon import ( "bytes" @@ -217,46 +217,3 @@ 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 { - err = log.Wrap(state.(error)) - } - }() - - // TODO(dustin): This is redundant with Encode. Refactor one to use the other. - - switch tt.Type() { - case TypeByte: - ed, err = ve.encodeBytes(value.([]byte)) - log.PanicIf(err) - 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) - case TypeLong: - ed, err = ve.encodeLongs(value.([]uint32)) - log.PanicIf(err) - case TypeRational: - ed, err = ve.encodeRationals(value.([]Rational)) - log.PanicIf(err) - case TypeSignedLong: - ed, err = ve.encodeSignedLongs(value.([]int32)) - log.PanicIf(err) - case TypeSignedRational: - ed, err = ve.encodeSignedRationals(value.([]SignedRational)) - log.PanicIf(err) - default: - log.Panicf("value not encodable (with type): %v [%v]", tt, value) - } - - return ed, nil -} diff --git a/v2/type_encode_test.go b/v2/common/type_encode_test.go similarity index 90% rename from v2/type_encode_test.go rename to v2/common/type_encode_test.go index 99aed48..b2e1ad8 100644 --- a/v2/type_encode_test.go +++ b/v2/common/type_encode_test.go @@ -1,8 +1,8 @@ -package exif +package exifcommon import ( - "testing" "reflect" + "testing" "github.com/dsoprea/go-logging" ) @@ -105,7 +105,7 @@ func TestShortCycle(t *testing.T) { byteOrder := TestDefaultByteOrder ve := NewValueEncoder(byteOrder) - original := []uint16 { 0x11, 0x22, 0x33, 0x44, 0x55 } + original := []uint16{0x11, 0x22, 0x33, 0x44, 0x55} ed, err := ve.encodeShorts(original) log.PanicIf(err) @@ -114,7 +114,7 @@ func TestShortCycle(t *testing.T) { t.Fatalf("IFD type not expected.") } - expected := []byte { + expected := []byte{ 0x00, 0x11, 0x00, 0x22, 0x00, 0x33, @@ -140,7 +140,7 @@ func TestLongCycle(t *testing.T) { byteOrder := TestDefaultByteOrder ve := NewValueEncoder(byteOrder) - original := []uint32 { 0x11, 0x22, 0x33, 0x44, 0x55 } + original := []uint32{0x11, 0x22, 0x33, 0x44, 0x55} ed, err := ve.encodeLongs(original) log.PanicIf(err) @@ -149,7 +149,7 @@ func TestLongCycle(t *testing.T) { t.Fatalf("IFD type not expected.") } - expected := []byte { + expected := []byte{ 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x33, @@ -175,25 +175,25 @@ func TestRationalCycle(t *testing.T) { byteOrder := TestDefaultByteOrder ve := NewValueEncoder(byteOrder) - original := []Rational { + original := []Rational{ Rational{ - Numerator: 0x11, + Numerator: 0x11, Denominator: 0x22, }, Rational{ - Numerator: 0x33, + Numerator: 0x33, Denominator: 0x44, }, Rational{ - Numerator: 0x55, + Numerator: 0x55, Denominator: 0x66, }, Rational{ - Numerator: 0x77, + Numerator: 0x77, Denominator: 0x88, }, Rational{ - Numerator: 0x99, + Numerator: 0x99, Denominator: 0x00, }, } @@ -205,7 +205,7 @@ func TestRationalCycle(t *testing.T) { t.Fatalf("IFD type not expected.") } - expected := []byte { + expected := []byte{ 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x33, @@ -236,7 +236,7 @@ func TestSignedLongCycle(t *testing.T) { byteOrder := TestDefaultByteOrder ve := NewValueEncoder(byteOrder) - original := []int32 { 0x11, 0x22, 0x33, 0x44, 0x55 } + original := []int32{0x11, 0x22, 0x33, 0x44, 0x55} ed, err := ve.encodeSignedLongs(original) log.PanicIf(err) @@ -245,7 +245,7 @@ func TestSignedLongCycle(t *testing.T) { t.Fatalf("IFD type not expected.") } - expected := []byte { + expected := []byte{ 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x33, @@ -271,25 +271,25 @@ func TestSignedRationalCycle(t *testing.T) { byteOrder := TestDefaultByteOrder ve := NewValueEncoder(byteOrder) - original := []SignedRational { + original := []SignedRational{ SignedRational{ - Numerator: 0x11, + Numerator: 0x11, Denominator: 0x22, }, SignedRational{ - Numerator: 0x33, + Numerator: 0x33, Denominator: 0x44, }, SignedRational{ - Numerator: 0x55, + Numerator: 0x55, Denominator: 0x66, }, SignedRational{ - Numerator: 0x77, + Numerator: 0x77, Denominator: 0x88, }, SignedRational{ - Numerator: 0x99, + Numerator: 0x99, Denominator: 0x00, }, } @@ -301,7 +301,7 @@ func TestSignedRationalCycle(t *testing.T) { t.Fatalf("IFD type not expected.") } - expected := []byte { + expected := []byte{ 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x33, @@ -377,7 +377,7 @@ func TestEncode_Short(t *testing.T) { byteOrder := TestDefaultByteOrder ve := NewValueEncoder(byteOrder) - original := []uint16 { 0x11, 0x22, 0x33, 0x44, 0x55 } + original := []uint16{0x11, 0x22, 0x33, 0x44, 0x55} ed, err := ve.Encode(original) log.PanicIf(err) @@ -386,7 +386,7 @@ func TestEncode_Short(t *testing.T) { t.Fatalf("IFD type not expected.") } - expected := []byte { + expected := []byte{ 0x00, 0x11, 0x00, 0x22, 0x00, 0x33, @@ -405,7 +405,7 @@ func TestEncode_Long(t *testing.T) { byteOrder := TestDefaultByteOrder ve := NewValueEncoder(byteOrder) - original := []uint32 { 0x11, 0x22, 0x33, 0x44, 0x55 } + original := []uint32{0x11, 0x22, 0x33, 0x44, 0x55} ed, err := ve.Encode(original) log.PanicIf(err) @@ -414,7 +414,7 @@ func TestEncode_Long(t *testing.T) { t.Fatalf("IFD type not expected.") } - expected := []byte { + expected := []byte{ 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x33, @@ -433,25 +433,25 @@ func TestEncode_Rational(t *testing.T) { byteOrder := TestDefaultByteOrder ve := NewValueEncoder(byteOrder) - original := []Rational { + original := []Rational{ Rational{ - Numerator: 0x11, + Numerator: 0x11, Denominator: 0x22, }, Rational{ - Numerator: 0x33, + Numerator: 0x33, Denominator: 0x44, }, Rational{ - Numerator: 0x55, + Numerator: 0x55, Denominator: 0x66, }, Rational{ - Numerator: 0x77, + Numerator: 0x77, Denominator: 0x88, }, Rational{ - Numerator: 0x99, + Numerator: 0x99, Denominator: 0x00, }, } @@ -463,7 +463,7 @@ func TestEncode_Rational(t *testing.T) { t.Fatalf("IFD type not expected.") } - expected := []byte { + expected := []byte{ 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x33, @@ -487,7 +487,7 @@ func TestEncode_SignedLong(t *testing.T) { byteOrder := TestDefaultByteOrder ve := NewValueEncoder(byteOrder) - original := []int32 { 0x11, 0x22, 0x33, 0x44, 0x55 } + original := []int32{0x11, 0x22, 0x33, 0x44, 0x55} ed, err := ve.Encode(original) log.PanicIf(err) @@ -496,7 +496,7 @@ func TestEncode_SignedLong(t *testing.T) { t.Fatalf("IFD type not expected.") } - expected := []byte { + expected := []byte{ 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x33, @@ -515,25 +515,25 @@ func TestEncode_SignedRational(t *testing.T) { byteOrder := TestDefaultByteOrder ve := NewValueEncoder(byteOrder) - original := []SignedRational { + original := []SignedRational{ SignedRational{ - Numerator: 0x11, + Numerator: 0x11, Denominator: 0x22, }, SignedRational{ - Numerator: 0x33, + Numerator: 0x33, Denominator: 0x44, }, SignedRational{ - Numerator: 0x55, + Numerator: 0x55, Denominator: 0x66, }, SignedRational{ - Numerator: 0x77, + Numerator: 0x77, Denominator: 0x88, }, SignedRational{ - Numerator: 0x99, + Numerator: 0x99, Denominator: 0x00, }, } @@ -545,7 +545,7 @@ func TestEncode_SignedRational(t *testing.T) { t.Fatalf("IFD type not expected.") } - expected := []byte { + expected := []byte{ 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x33, diff --git a/v2/common/utility.go b/v2/common/utility.go new file mode 100644 index 0000000..4b5b58c --- /dev/null +++ b/v2/common/utility.go @@ -0,0 +1,65 @@ +package exifcommon + +import ( + "bytes" + "fmt" + + "github.com/dsoprea/go-logging" +) + +func DumpBytes(data []byte) { + fmt.Printf("DUMP: ") + for _, x := range data { + fmt.Printf("%02x ", x) + } + + fmt.Printf("\n") +} + +func DumpBytesClause(data []byte) { + fmt.Printf("DUMP: ") + + fmt.Printf("[]byte { ") + + for i, x := range data { + fmt.Printf("0x%02x", x) + + if i < len(data)-1 { + fmt.Printf(", ") + } + } + + fmt.Printf(" }\n") +} + +func DumpBytesToString(data []byte) string { + b := new(bytes.Buffer) + + for i, x := range data { + _, err := b.WriteString(fmt.Sprintf("%02x", x)) + log.PanicIf(err) + + if i < len(data)-1 { + _, err := b.WriteRune(' ') + log.PanicIf(err) + } + } + + return b.String() +} + +func DumpBytesClauseToString(data []byte) string { + b := new(bytes.Buffer) + + for i, x := range data { + _, err := b.WriteString(fmt.Sprintf("0x%02x", x)) + log.PanicIf(err) + + if i < len(data)-1 { + _, err := b.WriteString(", ") + log.PanicIf(err) + } + } + + return b.String() +} diff --git a/v2/common/value_context.go b/v2/common/value_context.go new file mode 100644 index 0000000..b91ab89 --- /dev/null +++ b/v2/common/value_context.go @@ -0,0 +1,361 @@ +package exifcommon + +import ( + "encoding/binary" + + "github.com/dsoprea/go-logging" +) + +var ( + parser *Parser +) + +// ValueContext describes all of the parameters required to find and extract +// the actual tag value. +type ValueContext struct { + unitCount uint32 + valueOffset uint32 + rawValueOffset []byte + addressableData []byte + + tagType TagTypePrimitive + byteOrder binary.ByteOrder + + // undefinedValueTagType is the effective type to use if this is an + // "undefined" value. + undefinedValueTagType TagTypePrimitive + + ifdPath string + tagId uint16 +} + +func newValueContext(ifdPath string, tagId uint16, unitCount, valueOffset uint32, rawValueOffset, addressableData []byte, tagType TagTypePrimitive, byteOrder binary.ByteOrder) *ValueContext { + return &ValueContext{ + unitCount: unitCount, + valueOffset: valueOffset, + rawValueOffset: rawValueOffset, + addressableData: addressableData, + + tagType: tagType, + byteOrder: byteOrder, + + ifdPath: ifdPath, + tagId: tagId, + } +} + +func (vc *ValueContext) SetUnknownValueType(tagType TagTypePrimitive) { + vc.undefinedValueTagType = tagType +} + +func (vc *ValueContext) UnitCount() uint32 { + return vc.unitCount +} + +func (vc *ValueContext) ValueOffset() uint32 { + return vc.valueOffset +} + +func (vc *ValueContext) RawValueOffset() []byte { + return vc.rawValueOffset +} + +func (vc *ValueContext) AddressableData() []byte { + return vc.addressableData +} + +func (vc *ValueContext) ByteOrder() binary.ByteOrder { + return vc.byteOrder +} + +func (vc *ValueContext) IfdPath() string { + return vc.ifdPath +} + +func (vc *ValueContext) TagId() uint16 { + return vc.tagId +} + +// isEmbedded returns whether the value is embedded or a reference. This can't +// be precalculated since the size is not defined for all types (namely the +// "undefined" types). +func (vc *ValueContext) isEmbedded() bool { + tagType := vc.effectiveValueType() + + return (tagType.Size() * int(vc.unitCount)) <= 4 +} + +func (vc *ValueContext) effectiveValueType() (tagType TagTypePrimitive) { + if vc.tagType == TypeUndefined { + tagType = vc.undefinedValueTagType + + if tagType == 0 { + log.Panicf("undefined-value type not set") + } + } else { + tagType = vc.tagType + } + + return tagType +} + +func (vc *ValueContext) readRawEncoded() (rawBytes []byte, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + tagType := vc.effectiveValueType() + + unitSizeRaw := uint32(tagType.Size()) + + if vc.isEmbedded() == true { + byteLength := unitSizeRaw * vc.unitCount + return vc.rawValueOffset[:byteLength], nil + } else { + return vc.addressableData[vc.valueOffset : vc.valueOffset+vc.unitCount*unitSizeRaw], nil + } +} + +// Format returns a string representation for the value. +// +// Where the type is not ASCII, `justFirst` indicates whether to just stringify +// the first item in the slice (or return an empty string if the slice is +// empty). +// +// Since this method lacks the information to process undefined-type tags (e.g. +// byte-order, tag-ID, IFD type), it will return an error if attempted. See +// `Undefined()`. +func (vc *ValueContext) Format() (value string, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + rawBytes, err := vc.readRawEncoded() + log.PanicIf(err) + + phrase, err := Format(rawBytes, vc.tagType, false, vc.byteOrder) + log.PanicIf(err) + + return phrase, nil +} + +// FormatOne is similar to `Format` but only gets and stringifies the first +// item. +func (vc *ValueContext) FormatFirst() (value string, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + rawBytes, err := vc.readRawEncoded() + log.PanicIf(err) + + phrase, err := Format(rawBytes, vc.tagType, true, vc.byteOrder) + log.PanicIf(err) + + return phrase, nil +} + +func (vc *ValueContext) ReadBytes() (value []byte, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + rawValue, err := vc.readRawEncoded() + log.PanicIf(err) + + value, err = parser.ParseBytes(rawValue, vc.unitCount) + log.PanicIf(err) + + return value, nil +} + +func (vc *ValueContext) ReadAscii() (value string, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + rawValue, err := vc.readRawEncoded() + log.PanicIf(err) + + value, err = parser.ParseAscii(rawValue, vc.unitCount) + log.PanicIf(err) + + return value, nil +} + +func (vc *ValueContext) ReadAsciiNoNul() (value string, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + rawValue, err := vc.readRawEncoded() + log.PanicIf(err) + + value, err = parser.ParseAsciiNoNul(rawValue, vc.unitCount) + log.PanicIf(err) + + return value, nil +} + +func (vc *ValueContext) ReadShorts() (value []uint16, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + rawValue, err := vc.readRawEncoded() + log.PanicIf(err) + + value, err = parser.ParseShorts(rawValue, vc.unitCount, vc.byteOrder) + log.PanicIf(err) + + return value, nil +} + +func (vc *ValueContext) ReadLongs() (value []uint32, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + rawValue, err := vc.readRawEncoded() + log.PanicIf(err) + + value, err = parser.ParseLongs(rawValue, vc.unitCount, vc.byteOrder) + log.PanicIf(err) + + return value, nil +} + +func (vc *ValueContext) ReadRationals() (value []Rational, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + rawValue, err := vc.readRawEncoded() + log.PanicIf(err) + + value, err = parser.ParseRationals(rawValue, vc.unitCount, vc.byteOrder) + log.PanicIf(err) + + return value, nil +} + +func (vc *ValueContext) ReadSignedLongs() (value []int32, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + rawValue, err := vc.readRawEncoded() + log.PanicIf(err) + + value, err = parser.ParseSignedLongs(rawValue, vc.unitCount, vc.byteOrder) + log.PanicIf(err) + + return value, nil +} + +func (vc *ValueContext) ReadSignedRationals() (value []SignedRational, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + rawValue, err := vc.readRawEncoded() + log.PanicIf(err) + + value, err = parser.ParseSignedRationals(rawValue, vc.unitCount, vc.byteOrder) + log.PanicIf(err) + + return value, nil +} + +// Values knows how to resolve the given value. This value is always a list +// (undefined-values aside), so we're named accordingly. +// +// Since this method lacks the information to process unknown-type tags (e.g. +// byte-order, tag-ID, IFD type), it will return an error if attempted. See +// `Undefined()`. +func (vc *ValueContext) Values() (values interface{}, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + if vc.tagType == TypeByte { + values, err = vc.ReadBytes() + log.PanicIf(err) + } else if vc.tagType == TypeAscii { + values, err = vc.ReadAscii() + log.PanicIf(err) + } else if vc.tagType == TypeAsciiNoNul { + values, err = vc.ReadAsciiNoNul() + log.PanicIf(err) + } else if vc.tagType == TypeShort { + values, err = vc.ReadShorts() + log.PanicIf(err) + } else if vc.tagType == TypeLong { + values, err = vc.ReadLongs() + log.PanicIf(err) + } else if vc.tagType == TypeRational { + values, err = vc.ReadRationals() + log.PanicIf(err) + } else if vc.tagType == TypeSignedLong { + values, err = vc.ReadSignedLongs() + log.PanicIf(err) + } else if vc.tagType == TypeSignedRational { + values, err = vc.ReadSignedRationals() + log.PanicIf(err) + } else if vc.tagType == TypeUndefined { + log.Panicf("will not parse undefined-type value") + + // Never called. + return nil, nil + } else { + log.Panicf("value of type [%s] is unparseable", vc.tagType) + + // Never called. + return nil, nil + } + + return values, nil +} + +// // Undefined attempts to identify and decode supported undefined-type fields. +// // This is the primary, preferred interface to reading undefined values. +// func (vc *ValueContext) Undefined() (value interface{}, err error) { +// defer func() { +// if state := recover(); state != nil { +// err = log.Wrap(state.(error)) +// } +// }() + +// value, err = UndefinedValue(vc.ifdPath, vc.tagId, vc, vc.byteOrder) +// log.PanicIf(err) + +// return value, nil +// } + +func init() { + parser = new(Parser) +} diff --git a/v2/value_context_test.go b/v2/common/value_context_test.go similarity index 99% rename from v2/value_context_test.go rename to v2/common/value_context_test.go index 3d4df86..d070691 100644 --- a/v2/value_context_test.go +++ b/v2/common/value_context_test.go @@ -1,4 +1,4 @@ -package exif +package exifcommon import ( "bytes" diff --git a/v2/exif.go b/v2/exif.go index ae40f01..64e556d 100644 --- a/v2/exif.go +++ b/v2/exif.go @@ -29,12 +29,6 @@ const ( var ( exifLogger = log.NewLogger("exif.exif") - // EncodeDefaultByteOrder is the default byte-order for encoding operations. - EncodeDefaultByteOrder = binary.BigEndian - - // Default byte order for tests. - TestDefaultByteOrder = binary.BigEndian - BigEndianBoBytes = [2]byte{'M', 'M'} LittleEndianBoBytes = [2]byte{'I', 'I'} diff --git a/v2/go.mod b/v2/go.mod index e19afe6..ea2b46f 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -3,9 +3,8 @@ module github.com/dsoprea/go-exif/v2 go 1.13 require ( + github.com/dsoprea/go-exif v0.0.0-20200102050106-254a6b902a75 github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696 - github.com/go-errors/errors v1.0.1 // indirect github.com/golang/geo v0.0.0-20190916061304-5b978397cfec - golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect gopkg.in/yaml.v2 v2.2.7 ) diff --git a/v2/go.sum b/v2/go.sum index a36fc59..5acc9b6 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -1,3 +1,6 @@ +github.com/dsoprea/go-exif v0.0.0-20200102050106-254a6b902a75 h1:WeCZb0AeChtLIgHhVtGOuJmomEYgEzckxkzSnfuubzc= +github.com/dsoprea/go-exif v0.0.0-20200102050106-254a6b902a75/go.mod h1:lOaOt7+UEppOgyvRy749v3do836U/hw0YVJNjoyPaEs= +github.com/dsoprea/go-exif v0.0.0-20200102120628-74567945ac68 h1:A7DoYvhnDGMhePdmSGyoCWhH5pW0VHgT2E6dEJRlzlo= github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696 h1:VGFnZAcLwPpt1sHlAxml+pGLZz9A2s+K/s1YNhPC91Y= github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= diff --git a/v2/ifd.go b/v2/ifd.go index e75404d..78b1daa 100644 --- a/v2/ifd.go +++ b/v2/ifd.go @@ -8,34 +8,6 @@ import ( "github.com/dsoprea/go-logging" ) -const ( - // IFD names. The paths that we referred to the IFDs with are comprised of - // these. - - IfdStandard = "IFD" - IfdExif = "Exif" - IfdGps = "GPSInfo" - IfdIop = "Iop" - - // Tag IDs for child IFDs. - - IfdExifId = 0x8769 - IfdGpsId = 0x8825 - IfdIopId = 0xA005 - - // Just a placeholder. - - IfdRootId = 0x0000 - - // The paths of the standard IFDs expressed in the standard IFD-mappings - // and as the group-names in the tag data. - - IfdPathStandard = "IFD" - IfdPathStandardExif = "IFD/Exif" - IfdPathStandardExifIop = "IFD/Exif/Iop" - IfdPathStandardGps = "IFD/GPSInfo" -) - var ( ifdLogger = log.NewLogger("exif.ifd") ) diff --git a/v2/type_test.go b/v2/tag_type_test.go similarity index 84% rename from v2/type_test.go rename to v2/tag_type_test.go index 2327064..fef79b3 100644 --- a/v2/type_test.go +++ b/v2/tag_type_test.go @@ -1,10 +1,10 @@ -package exif +package exifcommon import ( - "testing" "bytes" "fmt" "reflect" + "testing" "github.com/dsoprea/go-logging" ) @@ -12,7 +12,7 @@ import ( func TestTagType_EncodeDecode_Byte(t *testing.T) { tt := NewTagType(TypeByte, TestDefaultByteOrder) - data := []byte { 0x11, 0x22, 0x33, 0x44, 0x55 } + data := []byte{0x11, 0x22, 0x33, 0x44, 0x55} encoded, err := tt.Encode(data) log.PanicIf(err) @@ -52,12 +52,12 @@ func TestTagType_EncodeDecode_Ascii(t *testing.T) { func TestTagType_EncodeDecode_Shorts(t *testing.T) { tt := NewTagType(TypeShort, TestDefaultByteOrder) - data := []uint16 { 0x11, 0x22, 0x33 } + data := []uint16{0x11, 0x22, 0x33} encoded, err := tt.Encode(data) log.PanicIf(err) - if bytes.Compare(encoded, []byte { 0x00, 0x11, 0x00, 0x22, 0x00, 0x33 }) != 0 { + if bytes.Compare(encoded, []byte{0x00, 0x11, 0x00, 0x22, 0x00, 0x33}) != 0 { t.Fatalf("Data not encoded correctly.") } @@ -72,12 +72,12 @@ func TestTagType_EncodeDecode_Shorts(t *testing.T) { func TestTagType_EncodeDecode_Long(t *testing.T) { tt := NewTagType(TypeLong, TestDefaultByteOrder) - data := []uint32 { 0x11, 0x22, 0x33 } + data := []uint32{0x11, 0x22, 0x33} encoded, err := tt.Encode(data) log.PanicIf(err) - if bytes.Compare(encoded, []byte { 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x33 }) != 0 { + if bytes.Compare(encoded, []byte{0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x33}) != 0 { t.Fatalf("Data not encoded correctly.") } @@ -92,15 +92,15 @@ func TestTagType_EncodeDecode_Long(t *testing.T) { func TestTagType_EncodeDecode_Rational(t *testing.T) { tt := NewTagType(TypeRational, TestDefaultByteOrder) - data := []Rational { - Rational{ Numerator: 0x11, Denominator: 0x22 }, - Rational{ Numerator: 0x33, Denominator: 0x44 }, + data := []Rational{ + Rational{Numerator: 0x11, Denominator: 0x22}, + Rational{Numerator: 0x33, Denominator: 0x44}, } encoded, err := tt.Encode(data) log.PanicIf(err) - if bytes.Compare(encoded, []byte { 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x44 }) != 0 { + if bytes.Compare(encoded, []byte{0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x44}) != 0 { t.Fatalf("Data not encoded correctly.") } @@ -115,12 +115,12 @@ func TestTagType_EncodeDecode_Rational(t *testing.T) { func TestTagType_EncodeDecode_SignedLong(t *testing.T) { tt := NewTagType(TypeSignedLong, TestDefaultByteOrder) - data := []int32 { 0x11, 0x22, 0x33 } + data := []int32{0x11, 0x22, 0x33} encoded, err := tt.Encode(data) log.PanicIf(err) - if bytes.Compare(encoded, []byte { 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x33 }) != 0 { + if bytes.Compare(encoded, []byte{0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x33}) != 0 { t.Fatalf("Data not encoded correctly.") } @@ -135,15 +135,15 @@ func TestTagType_EncodeDecode_SignedLong(t *testing.T) { func TestTagType_EncodeDecode_SignedRational(t *testing.T) { tt := NewTagType(TypeSignedRational, TestDefaultByteOrder) - data := []SignedRational { - SignedRational{ Numerator: 0x11, Denominator: 0x22 }, - SignedRational{ Numerator: 0x33, Denominator: 0x44 }, + data := []SignedRational{ + SignedRational{Numerator: 0x11, Denominator: 0x22}, + SignedRational{Numerator: 0x33, Denominator: 0x44}, } encoded, err := tt.Encode(data) log.PanicIf(err) - if bytes.Compare(encoded, []byte { 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x44 }) != 0 { + if bytes.Compare(encoded, []byte{0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x44}) != 0 { t.Fatalf("Data not encoded correctly.") } @@ -204,7 +204,7 @@ func TestTagType_FromString_Byte(t *testing.T) { value, err := tt.FromString("abc") log.PanicIf(err) - if reflect.DeepEqual(value, []byte { 'a', 'b', 'c' }) != true { + if reflect.DeepEqual(value, []byte{'a', 'b', 'c'}) != true { t.Fatalf("byte value not correct") } } @@ -249,7 +249,7 @@ func TestTagType_FromString_Rational(t *testing.T) { log.PanicIf(err) expected := Rational{ - Numerator: 12, + Numerator: 12, Denominator: 34, } @@ -276,7 +276,7 @@ func TestTagType_FromString_SignedRational(t *testing.T) { log.PanicIf(err) expected := SignedRational{ - Numerator: -12, + Numerator: -12, Denominator: 34, } diff --git a/v2/tags_undefined.go b/v2/tags_undefined.go deleted file mode 100644 index 3e752b6..0000000 --- a/v2/tags_undefined.go +++ /dev/null @@ -1,417 +0,0 @@ -package exif - -import ( - "bytes" - "fmt" - "strings" - - "crypto/sha1" - "encoding/binary" - - "github.com/dsoprea/go-logging" -) - -const ( - UnparseableUnknownTagValuePlaceholder = "!UNKNOWN" -) - -// TODO(dustin): Rename "unknown" in symbol names to "undefined" in the next release. -// -// See https://github.com/dsoprea/go-exif/issues/27 . - -const ( - TagUnknownType_9298_UserComment_Encoding_ASCII = iota - TagUnknownType_9298_UserComment_Encoding_JIS = iota - TagUnknownType_9298_UserComment_Encoding_UNICODE = iota - TagUnknownType_9298_UserComment_Encoding_UNDEFINED = iota -) - -const ( - TagUnknownType_9101_ComponentsConfiguration_Channel_Y = 0x1 - TagUnknownType_9101_ComponentsConfiguration_Channel_Cb = 0x2 - TagUnknownType_9101_ComponentsConfiguration_Channel_Cr = 0x3 - TagUnknownType_9101_ComponentsConfiguration_Channel_R = 0x4 - TagUnknownType_9101_ComponentsConfiguration_Channel_G = 0x5 - TagUnknownType_9101_ComponentsConfiguration_Channel_B = 0x6 -) - -const ( - TagUnknownType_9101_ComponentsConfiguration_OTHER = iota - TagUnknownType_9101_ComponentsConfiguration_RGB = iota - TagUnknownType_9101_ComponentsConfiguration_YCBCR = iota -) - -var ( - TagUnknownType_9298_UserComment_Encoding_Names = map[int]string{ - TagUnknownType_9298_UserComment_Encoding_ASCII: "ASCII", - TagUnknownType_9298_UserComment_Encoding_JIS: "JIS", - TagUnknownType_9298_UserComment_Encoding_UNICODE: "UNICODE", - TagUnknownType_9298_UserComment_Encoding_UNDEFINED: "UNDEFINED", - } - - TagUnknownType_9298_UserComment_Encodings = map[int][]byte{ - TagUnknownType_9298_UserComment_Encoding_ASCII: []byte{'A', 'S', 'C', 'I', 'I', 0, 0, 0}, - TagUnknownType_9298_UserComment_Encoding_JIS: []byte{'J', 'I', 'S', 0, 0, 0, 0, 0}, - TagUnknownType_9298_UserComment_Encoding_UNICODE: []byte{'U', 'n', 'i', 'c', 'o', 'd', 'e', 0}, - TagUnknownType_9298_UserComment_Encoding_UNDEFINED: []byte{0, 0, 0, 0, 0, 0, 0, 0}, - } - - TagUnknownType_9101_ComponentsConfiguration_Names = map[int]string{ - TagUnknownType_9101_ComponentsConfiguration_OTHER: "OTHER", - TagUnknownType_9101_ComponentsConfiguration_RGB: "RGB", - TagUnknownType_9101_ComponentsConfiguration_YCBCR: "YCBCR", - } - - TagUnknownType_9101_ComponentsConfiguration_Configurations = map[int][]byte{ - TagUnknownType_9101_ComponentsConfiguration_RGB: []byte{ - TagUnknownType_9101_ComponentsConfiguration_Channel_R, - TagUnknownType_9101_ComponentsConfiguration_Channel_G, - TagUnknownType_9101_ComponentsConfiguration_Channel_B, - 0, - }, - - TagUnknownType_9101_ComponentsConfiguration_YCBCR: []byte{ - TagUnknownType_9101_ComponentsConfiguration_Channel_Y, - TagUnknownType_9101_ComponentsConfiguration_Channel_Cb, - TagUnknownType_9101_ComponentsConfiguration_Channel_Cr, - 0, - }, - } -) - -// TODO(dustin): Rename `UnknownTagValue` to `UndefinedTagValue`. - -type UnknownTagValue interface { - ValueBytes() ([]byte, error) -} - -// TODO(dustin): Rename `TagUnknownType_GeneralString` to `TagUnknownType_GeneralString`. - -type TagUnknownType_GeneralString string - -func (gs TagUnknownType_GeneralString) ValueBytes() (value []byte, err error) { - return []byte(gs), nil -} - -// TODO(dustin): Rename `TagUnknownType_9298_UserComment` to `TagUndefinedType_9298_UserComment`. - -type TagUnknownType_9298_UserComment struct { - EncodingType int - EncodingBytes []byte -} - -func (uc TagUnknownType_9298_UserComment) String() string { - var valuePhrase string - - if len(uc.EncodingBytes) <= 8 { - valuePhrase = fmt.Sprintf("%v", uc.EncodingBytes) - } else { - valuePhrase = fmt.Sprintf("%v...", uc.EncodingBytes[:8]) - } - - return fmt.Sprintf("UserComment", len(uc.EncodingBytes), TagUnknownType_9298_UserComment_Encoding_Names[uc.EncodingType], valuePhrase, len(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) - copy(value[8:], uc.EncodingBytes) - - return value, nil -} - -// TODO(dustin): Rename `TagUnknownType_927C_MakerNote` to `TagUndefinedType_927C_MakerNote`. - -type TagUnknownType_927C_MakerNote struct { - MakerNoteType []byte - MakerNoteBytes []byte -} - -func (mn TagUnknownType_927C_MakerNote) String() string { - parts := make([]string, 20) - for i, c := range mn.MakerNoteType { - parts[i] = fmt.Sprintf("%02x", c) - } - - h := sha1.New() - - _, err := h.Write(mn.MakerNoteBytes) - log.PanicIf(err) - - digest := h.Sum(nil) - - return fmt.Sprintf("MakerNote", strings.Join(parts, " "), len(mn.MakerNoteBytes), digest) -} - -func (uc TagUnknownType_927C_MakerNote) ValueBytes() (value []byte, err error) { - return uc.MakerNoteBytes, nil -} - -// TODO(dustin): Rename `TagUnknownType_9101_ComponentsConfiguration` to `TagUndefinedType_9101_ComponentsConfiguration`. - -type TagUnknownType_9101_ComponentsConfiguration struct { - ConfigurationId int - ConfigurationBytes []byte -} - -func (cc TagUnknownType_9101_ComponentsConfiguration) String() string { - return fmt.Sprintf("ComponentsConfiguration", TagUnknownType_9101_ComponentsConfiguration_Names[cc.ConfigurationId], cc.ConfigurationBytes) -} - -func (uc TagUnknownType_9101_ComponentsConfiguration) ValueBytes() (value []byte, err error) { - return uc.ConfigurationBytes, nil -} - -// TODO(dustin): Rename `EncodeUnknown_9286` to `EncodeUndefined_9286`. - -func EncodeUnknown_9286(uc TagUnknownType_9298_UserComment) (encoded []byte, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - b := new(bytes.Buffer) - - encodingTypeBytes := TagUnknownType_9298_UserComment_Encodings[uc.EncodingType] - - _, err = b.Write(encodingTypeBytes) - log.PanicIf(err) - - _, err = b.Write(uc.EncodingBytes) - log.PanicIf(err) - - return b.Bytes(), nil -} - -type EncodeableUndefinedValue struct { - IfdPath string - TagId uint16 - Parameters interface{} -} - -func EncodeUndefined(ifdPath string, tagId uint16, value interface{}) (ed EncodedData, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - // TODO(dustin): !! Finish implementing these. - if ifdPath == IfdPathStandardExif { - if tagId == 0x9286 { - encoded, err := EncodeUnknown_9286(value.(TagUnknownType_9298_UserComment)) - log.PanicIf(err) - - ed.Type = TypeUndefined - ed.Encoded = encoded - ed.UnitCount = uint32(len(encoded)) - - return ed, nil - } - } - - log.Panicf("undefined value not encodable: %s (0x%02x)", ifdPath, tagId) - - // Never called. - return EncodedData{}, nil -} - -// TODO(dustin): Rename `TagUnknownType_UnknownValue` to `TagUndefinedType_UnknownValue`. - -type TagUnknownType_UnknownValue []byte - -func (tutuv TagUnknownType_UnknownValue) String() string { - parts := make([]string, len(tutuv)) - for i, c := range tutuv { - parts[i] = fmt.Sprintf("%02x", c) - } - - h := sha1.New() - - _, err := h.Write(tutuv) - log.PanicIf(err) - - digest := h.Sum(nil) - - return fmt.Sprintf("Unknown", strings.Join(parts, " "), len(tutuv), digest) -} - -// UndefinedValue knows how to resolve the value for most unknown-type tags. -func UndefinedValue(ifdPath string, tagId uint16, valueContext interface{}, byteOrder binary.ByteOrder) (value interface{}, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - // TODO(dustin): Stop exporting this. Use `(*ValueContext).Undefined()`. - - var valueContextPtr *ValueContext - - if vc, ok := valueContext.(*ValueContext); ok == true { - // Legacy usage. - - valueContextPtr = vc - } else { - // Standard usage. - - valueContextValue := valueContext.(ValueContext) - valueContextPtr = &valueContextValue - } - - typeLogger.Debugf(nil, "UndefinedValue: IFD-PATH=[%s] TAG-ID=(0x%02x)", ifdPath, tagId) - - if ifdPath == IfdPathStandardExif { - if tagId == 0x9000 { - // ExifVersion - - valueContextPtr.SetUnknownValueType(TypeAsciiNoNul) - - valueString, err := valueContextPtr.ReadAsciiNoNul() - log.PanicIf(err) - - return TagUnknownType_GeneralString(valueString), nil - } else if tagId == 0xa000 { - // FlashpixVersion - - valueContextPtr.SetUnknownValueType(TypeAsciiNoNul) - - valueString, err := valueContextPtr.ReadAsciiNoNul() - log.PanicIf(err) - - return TagUnknownType_GeneralString(valueString), nil - } else if tagId == 0x9286 { - // UserComment - - valueContextPtr.SetUnknownValueType(TypeByte) - - valueBytes, err := valueContextPtr.ReadBytes() - log.PanicIf(err) - - unknownUc := TagUnknownType_9298_UserComment{ - EncodingType: TagUnknownType_9298_UserComment_Encoding_UNDEFINED, - EncodingBytes: []byte{}, - } - - encoding := valueBytes[:8] - for encodingIndex, encodingBytes := range TagUnknownType_9298_UserComment_Encodings { - if bytes.Compare(encoding, encodingBytes) == 0 { - uc := TagUnknownType_9298_UserComment{ - EncodingType: encodingIndex, - EncodingBytes: valueBytes[8:], - } - - return uc, nil - } - } - - typeLogger.Warningf(nil, "User-comment encoding not valid. Returning 'unknown' type (the default).") - return unknownUc, nil - } else if tagId == 0x927c { - // MakerNote - // TODO(dustin): !! This is the Wild Wild West. This very well might be a child IFD, but any and all OEM's define their own formats. If we're going to be writing changes and this is complete EXIF (which may not have the first eight bytes), it might be fine. However, if these are just IFDs they'll be relative to the main EXIF, this will invalidate the MakerNote data for IFDs and any other implementations that use offsets unless we can interpret them all. It be best to return to this later and just exclude this from being written for now, though means a loss of a wealth of image metadata. - // -> We can also just blindly try to interpret as an IFD and just validate that it's looks good (maybe it will even have a 'next ifd' pointer that we can validate is 0x0). - - valueContextPtr.SetUnknownValueType(TypeByte) - - valueBytes, err := valueContextPtr.ReadBytes() - log.PanicIf(err) - - // TODO(dustin): Doesn't work, but here as an example. - // ie := NewIfdEnumerate(valueBytes, byteOrder) - - // // TODO(dustin): !! Validate types (might have proprietary types, but it might be worth splitting the list between valid and not valid; maybe fail if a certain proportion are invalid, or maybe aren't less then a certain small integer)? - // ii, err := ie.Collect(0x0) - - // for _, entry := range ii.RootIfd.Entries { - // fmt.Printf("ENTRY: 0x%02x %d\n", entry.TagId, entry.TagType) - // } - - mn := TagUnknownType_927C_MakerNote{ - MakerNoteType: valueBytes[:20], - - // MakerNoteBytes has the whole length of bytes. There's always - // the chance that the first 20 bytes includes actual data. - MakerNoteBytes: valueBytes, - } - - return mn, nil - } else if tagId == 0x9101 { - // ComponentsConfiguration - - valueContextPtr.SetUnknownValueType(TypeByte) - - valueBytes, err := valueContextPtr.ReadBytes() - log.PanicIf(err) - - for configurationId, configurationBytes := range TagUnknownType_9101_ComponentsConfiguration_Configurations { - if bytes.Compare(valueBytes, configurationBytes) == 0 { - cc := TagUnknownType_9101_ComponentsConfiguration{ - ConfigurationId: configurationId, - ConfigurationBytes: valueBytes, - } - - return cc, nil - } - } - - cc := TagUnknownType_9101_ComponentsConfiguration{ - ConfigurationId: TagUnknownType_9101_ComponentsConfiguration_OTHER, - ConfigurationBytes: valueBytes, - } - - return cc, nil - } - } else if ifdPath == IfdPathStandardGps { - if tagId == 0x001c { - // GPSAreaInformation - - valueContextPtr.SetUnknownValueType(TypeAsciiNoNul) - - valueString, err := valueContextPtr.ReadAsciiNoNul() - log.PanicIf(err) - - return TagUnknownType_GeneralString(valueString), nil - } else if tagId == 0x001b { - // GPSProcessingMethod - - valueContextPtr.SetUnknownValueType(TypeAsciiNoNul) - - valueString, err := valueContextPtr.ReadAsciiNoNul() - log.PanicIf(err) - - return TagUnknownType_GeneralString(valueString), nil - } - } else if ifdPath == IfdPathStandardExifIop { - if tagId == 0x0002 { - // InteropVersion - - valueContextPtr.SetUnknownValueType(TypeAsciiNoNul) - - valueString, err := valueContextPtr.ReadAsciiNoNul() - log.PanicIf(err) - - return TagUnknownType_GeneralString(valueString), nil - } - } - - // TODO(dustin): !! Still need to do: - // - // complex: 0xa302, 0xa20c, 0x8828 - // long: 0xa301, 0xa300 - // - // 0xa40b is device-specific and unhandled. - // - // See https://github.com/dsoprea/go-exif/issues/26. - - // We have no choice but to return the error. We have no way of knowing how - // much data there is without already knowing what data-type this tag is. - return nil, ErrUnhandledUnknownTypedTag -} diff --git a/v2/tags_undefined_test.go b/v2/tags_undefined_test.go deleted file mode 100644 index 0403178..0000000 --- a/v2/tags_undefined_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package exif - -import ( - "bytes" - "testing" - - "github.com/dsoprea/go-logging" -) - -func TestUndefinedValue_ExifVersion(t *testing.T) { - byteOrder := TestDefaultByteOrder - fqIfdPath := "IFD0/Exif0" - ifdPath := "IFD/Exif" - - // 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 := &BuilderTag{ - ifdPath: ifdPath, - tagId: 0x9000, - typeId: TypeUndefined, - value: encodedValue, - } - - // Stage the build. - - im := NewIfdMapping() - - err = LoadStandardIfds(im) - log.PanicIf(err) - - ti := NewTagIndex() - - ibe := NewIfdByteEncoder() - ib := NewIfdBuilder(im, ti, ifdPath, 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(im, ti, fqIfdPath, ifdPath, byteOrder, tagBytes, false) - 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.ChildIfdPath != "" { - t.Fatalf("Tag's child IFD-path should be empty: [%s]", ite.ChildIfdPath) - } else if ite.IfdPath != ifdPath { - t.Fatalf("Tag's parent IFD is not correct: %v", ite.IfdPath) - } -} - -// TODO(dustin): !! Add tests for remaining, well-defined unknown -// 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/v2/undefined/README.md b/v2/undefined/README.md new file mode 100644 index 0000000..afa6b15 --- /dev/null +++ b/v2/undefined/README.md @@ -0,0 +1,4 @@ + +## 0xa40b + +The specification is not specific/clear enough to be handled. We're deferring until some point in the future when either we or someone else have a better understanding. diff --git a/v2/undefined/accessor.go b/v2/undefined/accessor.go new file mode 100644 index 0000000..ab26529 --- /dev/null +++ b/v2/undefined/accessor.go @@ -0,0 +1,61 @@ +package exifundefined + +import ( + "reflect" + + "encoding/binary" + + "github.com/dsoprea/go-logging" + + "github.com/dsoprea/go-exif/v2/common" +) + +func Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + encoderName := reflect.TypeOf(value).Name() + + encoder, found := encoders[encoderName] + if found == false { + log.Panicf("no encoder registered for type [%s]", encoderName) + } + + encoded, unitCount, err = encoder.Encode(value, byteOrder) + log.PanicIf(err) + + return encoded, unitCount, nil +} + +// UndefinedValue knows how to resolve the value for most unknown-type tags. +func Decode(ifdPath string, tagId uint16, valueContext *exifcommon.ValueContext, byteOrder binary.ByteOrder) (value interface{}, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + uth := UndefinedTagHandle{ + IfdPath: ifdPath, + TagId: tagId, + } + + decoder, found := decoders[uth] + if found == false { + // We have no choice but to return the error. We have no way of knowing how + // much data there is without already knowing what data-type this tag is. + return nil, exifcommon.ErrUnhandledUnknownTypedTag + } + + if valueContext.IfdPath() != ifdPath || valueContext.TagId() != tagId { + log.Panicf("IFD-path for codec does not match value-context: [%s] (0x%04x) != [%s] (0x%04x)", ifdPath, tagId, valueContext.IfdPath(), valueContext.TagId()) + } + + value, err = decoder.Decode(valueContext) + log.PanicIf(err) + + return value, nil +} diff --git a/v2/undefined/exif_8828_oecf.go b/v2/undefined/exif_8828_oecf.go new file mode 100644 index 0000000..716fe20 --- /dev/null +++ b/v2/undefined/exif_8828_oecf.go @@ -0,0 +1,94 @@ +package exifundefined + +import ( + "github.com/dsoprea/go-logging" + + "github.com/dsoprea/go-exif/v2/common" +) + +type Tag8828Oecf struct { + Columns uint16 + Rows uint16 + ColumnNames []string + Values []exifcommon.SignedRational +} + +type Codec8828Oecf struct { +} + +func (Codec8828Oecf) Decode(valueContext *exifcommon.ValueContext) (value interface{}, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + // TODO(dustin): Add test using known good data. + + valueContext.SetUnknownValueType(exifcommon.TypeByte) + + valueBytes, err := valueContext.ReadBytes() + log.PanicIf(err) + + oecf := Tag8828Oecf{} + + oecf.Columns = valueContext.ByteOrder().Uint16(valueBytes[0:2]) + oecf.Rows = valueContext.ByteOrder().Uint16(valueBytes[2:4]) + + columnNames := make([]string, oecf.Columns) + + // startAt is where the current column name starts. + startAt := 4 + + // offset is our current position. + offset := 4 + + currentColumnNumber := uint16(0) + + for currentColumnNumber < oecf.Columns { + if valueBytes[offset] == 0 { + columnName := string(valueBytes[startAt:offset]) + if len(columnName) == 0 { + log.Panicf("SFR column (%d) has zero length", currentColumnNumber) + } + + columnNames[currentColumnNumber] = columnName + currentColumnNumber++ + + offset++ + startAt = offset + continue + } + + offset++ + } + + oecf.ColumnNames = columnNames + + rawRationalBytes := valueBytes[offset:] + + rationalSize := exifcommon.TypeSignedRational.Size() + if len(rawRationalBytes)%rationalSize > 0 { + log.Panicf("OECF signed-rationals not aligned: (%d) %% (%d) > 0", len(rawRationalBytes), rationalSize) + } + + rationalCount := len(rawRationalBytes) / rationalSize + + parser := new(exifcommon.Parser) + + byteOrder := valueContext.ByteOrder() + + items, err := parser.ParseSignedRationals(rawRationalBytes, uint32(rationalCount), byteOrder) + log.PanicIf(err) + + oecf.Values = items + + return oecf, nil +} + +func init() { + registerDecoder( + exifcommon.IfdPathStandardExif, + 0x8828, + Codec8828Oecf{}) +} diff --git a/v2/undefined/exif_9000_exif_version.go b/v2/undefined/exif_9000_exif_version.go new file mode 100644 index 0000000..07996a8 --- /dev/null +++ b/v2/undefined/exif_9000_exif_version.go @@ -0,0 +1,32 @@ +package exifundefined + +import ( + "github.com/dsoprea/go-logging" + + "github.com/dsoprea/go-exif/v2/common" +) + +type Codec9000ExifVersion struct { +} + +func (Codec9000ExifVersion) Decode(valueContext *exifcommon.ValueContext) (value interface{}, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + valueContext.SetUnknownValueType(exifcommon.TypeAsciiNoNul) + + valueString, err := valueContext.ReadAsciiNoNul() + log.PanicIf(err) + + return TagUndefinedGeneralString(valueString), nil +} + +func init() { + registerDecoder( + exifcommon.IfdPathStandardExif, + 0x9000, + Codec9000ExifVersion{}) +} diff --git a/v2/undefined/exif_9101_components_configuration.go b/v2/undefined/exif_9101_components_configuration.go new file mode 100644 index 0000000..a59c7ad --- /dev/null +++ b/v2/undefined/exif_9101_components_configuration.go @@ -0,0 +1,122 @@ +package exifundefined + +import ( + "bytes" + "fmt" + + "encoding/binary" + + "github.com/dsoprea/go-logging" + + "github.com/dsoprea/go-exif/v2/common" +) + +const ( + TagUndefinedType_9101_ComponentsConfiguration_Channel_Y = 0x1 + TagUndefinedType_9101_ComponentsConfiguration_Channel_Cb = 0x2 + TagUndefinedType_9101_ComponentsConfiguration_Channel_Cr = 0x3 + TagUndefinedType_9101_ComponentsConfiguration_Channel_R = 0x4 + TagUndefinedType_9101_ComponentsConfiguration_Channel_G = 0x5 + TagUndefinedType_9101_ComponentsConfiguration_Channel_B = 0x6 +) + +const ( + TagUndefinedType_9101_ComponentsConfiguration_OTHER = iota + TagUndefinedType_9101_ComponentsConfiguration_RGB = iota + TagUndefinedType_9101_ComponentsConfiguration_YCBCR = iota +) + +var ( + TagUndefinedType_9101_ComponentsConfiguration_Names = map[int]string{ + TagUndefinedType_9101_ComponentsConfiguration_OTHER: "OTHER", + TagUndefinedType_9101_ComponentsConfiguration_RGB: "RGB", + TagUndefinedType_9101_ComponentsConfiguration_YCBCR: "YCBCR", + } + + TagUndefinedType_9101_ComponentsConfiguration_Configurations = map[int][]byte{ + TagUndefinedType_9101_ComponentsConfiguration_RGB: []byte{ + TagUndefinedType_9101_ComponentsConfiguration_Channel_R, + TagUndefinedType_9101_ComponentsConfiguration_Channel_G, + TagUndefinedType_9101_ComponentsConfiguration_Channel_B, + 0, + }, + + TagUndefinedType_9101_ComponentsConfiguration_YCBCR: []byte{ + TagUndefinedType_9101_ComponentsConfiguration_Channel_Y, + TagUndefinedType_9101_ComponentsConfiguration_Channel_Cb, + TagUndefinedType_9101_ComponentsConfiguration_Channel_Cr, + 0, + }, + } +) + +type TagExif9101ComponentsConfiguration struct { + ConfigurationId int + ConfigurationBytes []byte +} + +func (cc TagExif9101ComponentsConfiguration) String() string { + return fmt.Sprintf("Exif9101ComponentsConfiguration", TagUndefinedType_9101_ComponentsConfiguration_Names[cc.ConfigurationId], cc.ConfigurationBytes) +} + +type CodecExif9101ComponentsConfiguration struct { +} + +func (CodecExif9101ComponentsConfiguration) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + cc, ok := value.(TagExif9101ComponentsConfiguration) + if ok == false { + log.Panicf("can only encode a TagExif9101ComponentsConfiguration") + } + + // TODO(dustin): Confirm this size against the specification. + + return cc.ConfigurationBytes, uint32(len(cc.ConfigurationBytes)), nil +} + +func (CodecExif9101ComponentsConfiguration) Decode(valueContext *exifcommon.ValueContext) (value interface{}, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + valueContext.SetUnknownValueType(exifcommon.TypeByte) + + valueBytes, err := valueContext.ReadBytes() + log.PanicIf(err) + + for configurationId, configurationBytes := range TagUndefinedType_9101_ComponentsConfiguration_Configurations { + if bytes.Compare(valueBytes, configurationBytes) == 0 { + cc := TagExif9101ComponentsConfiguration{ + ConfigurationId: configurationId, + ConfigurationBytes: valueBytes, + } + + return cc, nil + } + } + + cc := TagExif9101ComponentsConfiguration{ + ConfigurationId: TagUndefinedType_9101_ComponentsConfiguration_OTHER, + ConfigurationBytes: valueBytes, + } + + return cc, nil +} + +func init() { + registerEncoder( + TagExif9101ComponentsConfiguration{}, + CodecExif9101ComponentsConfiguration{}) + + registerDecoder( + exifcommon.IfdPathStandardExif, + 0x9101, + CodecExif9101ComponentsConfiguration{}) +} diff --git a/v2/undefined/exif_927C_maker_note.go b/v2/undefined/exif_927C_maker_note.go new file mode 100644 index 0000000..f84673f --- /dev/null +++ b/v2/undefined/exif_927C_maker_note.go @@ -0,0 +1,102 @@ +package exifundefined + +import ( + "fmt" + "strings" + + "crypto/sha1" + "encoding/binary" + + "github.com/dsoprea/go-logging" + + "github.com/dsoprea/go-exif/v2/common" +) + +type Tag927CMakerNote struct { + MakerNoteType []byte + MakerNoteBytes []byte +} + +func (mn Tag927CMakerNote) String() string { + parts := make([]string, 20) + for i, c := range mn.MakerNoteType { + parts[i] = fmt.Sprintf("%02x", c) + } + + h := sha1.New() + + _, err := h.Write(mn.MakerNoteBytes) + log.PanicIf(err) + + digest := h.Sum(nil) + + return fmt.Sprintf("MakerNote", strings.Join(parts, " "), len(mn.MakerNoteBytes), digest) +} + +type Codec927CMakerNote struct { +} + +func (Codec927CMakerNote) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + mn, ok := value.(Tag927CMakerNote) + if ok == false { + log.Panicf("can only encode a Tag927CMakerNote") + } + + // TODO(dustin): Confirm this size against the specification. + + return mn.MakerNoteBytes, uint32(len(mn.MakerNoteBytes)), nil +} + +func (Codec927CMakerNote) Decode(valueContext *exifcommon.ValueContext) (value interface{}, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + // MakerNote + // TODO(dustin): !! This is the Wild Wild West. This very well might be a child IFD, but any and all OEM's define their own formats. If we're going to be writing changes and this is complete EXIF (which may not have the first eight bytes), it might be fine. However, if these are just IFDs they'll be relative to the main EXIF, this will invalidate the MakerNote data for IFDs and any other implementations that use offsets unless we can interpret them all. It be best to return to this later and just exclude this from being written for now, though means a loss of a wealth of image metadata. + // -> We can also just blindly try to interpret as an IFD and just validate that it's looks good (maybe it will even have a 'next ifd' pointer that we can validate is 0x0). + + valueContext.SetUnknownValueType(exifcommon.TypeByte) + + valueBytes, err := valueContext.ReadBytes() + log.PanicIf(err) + + // TODO(dustin): Doesn't work, but here as an example. + // ie := NewIfdEnumerate(valueBytes, byteOrder) + + // // TODO(dustin): !! Validate types (might have proprietary types, but it might be worth splitting the list between valid and not valid; maybe fail if a certain proportion are invalid, or maybe aren't less then a certain small integer)? + // ii, err := ie.Collect(0x0) + + // for _, entry := range ii.RootIfd.Entries { + // fmt.Printf("ENTRY: 0x%02x %d\n", entry.TagId, entry.TagType) + // } + + mn := Tag927CMakerNote{ + MakerNoteType: valueBytes[:20], + + // MakerNoteBytes has the whole length of bytes. There's always + // the chance that the first 20 bytes includes actual data. + MakerNoteBytes: valueBytes, + } + + return mn, nil +} + +func init() { + registerEncoder( + Tag927CMakerNote{}, + Codec927CMakerNote{}) + + registerDecoder( + exifcommon.IfdPathStandardExif, + 0x927c, + Codec927CMakerNote{}) +} diff --git a/v2/undefined/exif_9286_user_comment.go b/v2/undefined/exif_9286_user_comment.go new file mode 100644 index 0000000..753f936 --- /dev/null +++ b/v2/undefined/exif_9286_user_comment.go @@ -0,0 +1,130 @@ +package exifundefined + +import ( + "bytes" + "fmt" + + "encoding/binary" + + "github.com/dsoprea/go-logging" + + "github.com/dsoprea/go-exif/v2/common" +) + +var ( + exif9286Logger = log.NewLogger("exifundefined.exif_9286_user_comment") +) + +const ( + TagUndefinedType_9286_UserComment_Encoding_ASCII = iota + TagUndefinedType_9286_UserComment_Encoding_JIS = iota + TagUndefinedType_9286_UserComment_Encoding_UNICODE = iota + TagUndefinedType_9286_UserComment_Encoding_UNDEFINED = iota +) + +var ( + TagUndefinedType_9286_UserComment_Encoding_Names = map[int]string{ + TagUndefinedType_9286_UserComment_Encoding_ASCII: "ASCII", + TagUndefinedType_9286_UserComment_Encoding_JIS: "JIS", + TagUndefinedType_9286_UserComment_Encoding_UNICODE: "UNICODE", + TagUndefinedType_9286_UserComment_Encoding_UNDEFINED: "UNDEFINED", + } + + TagUndefinedType_9286_UserComment_Encodings = map[int][]byte{ + TagUndefinedType_9286_UserComment_Encoding_ASCII: []byte{'A', 'S', 'C', 'I', 'I', 0, 0, 0}, + TagUndefinedType_9286_UserComment_Encoding_JIS: []byte{'J', 'I', 'S', 0, 0, 0, 0, 0}, + TagUndefinedType_9286_UserComment_Encoding_UNICODE: []byte{'U', 'n', 'i', 'c', 'o', 'd', 'e', 0}, + TagUndefinedType_9286_UserComment_Encoding_UNDEFINED: []byte{0, 0, 0, 0, 0, 0, 0, 0}, + } +) + +type Tag9286UserComment struct { + EncodingType int + EncodingBytes []byte +} + +func (uc Tag9286UserComment) String() string { + var valuePhrase string + + if len(uc.EncodingBytes) <= 8 { + valuePhrase = fmt.Sprintf("%v", uc.EncodingBytes) + } else { + valuePhrase = fmt.Sprintf("%v...", uc.EncodingBytes[:8]) + } + + return fmt.Sprintf("UserComment", len(uc.EncodingBytes), TagUndefinedType_9286_UserComment_Encoding_Names[uc.EncodingType], valuePhrase, len(uc.EncodingBytes)) +} + +type Codec9286UserComment struct { +} + +func (Codec9286UserComment) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + uc, ok := value.(Tag9286UserComment) + if ok == false { + log.Panicf("can only encode a Tag9286UserComment") + } + + encodingTypeBytes, found := TagUndefinedType_9286_UserComment_Encodings[uc.EncodingType] + if found == false { + log.Panicf("encoding-type not valid for unknown-type tag 9286 (UserComment): (%d)", uc.EncodingType) + } + + encoded = make([]byte, len(uc.EncodingBytes)+8) + + copy(encoded[:8], encodingTypeBytes) + copy(encoded[8:], uc.EncodingBytes) + + // TODO(dustin): Confirm this size against the specification. + + return encoded, uint32(len(encoded)), nil +} + +func (Codec9286UserComment) Decode(valueContext *exifcommon.ValueContext) (value interface{}, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + valueContext.SetUnknownValueType(exifcommon.TypeByte) + + valueBytes, err := valueContext.ReadBytes() + log.PanicIf(err) + + unknownUc := Tag9286UserComment{ + EncodingType: TagUndefinedType_9286_UserComment_Encoding_UNDEFINED, + EncodingBytes: []byte{}, + } + + encoding := valueBytes[:8] + for encodingIndex, encodingBytes := range TagUndefinedType_9286_UserComment_Encodings { + if bytes.Compare(encoding, encodingBytes) == 0 { + uc := Tag9286UserComment{ + EncodingType: encodingIndex, + EncodingBytes: valueBytes[8:], + } + + return uc, nil + } + } + + exif9286Logger.Warningf(nil, "User-comment encoding not valid. Returning 'unknown' type (the default).") + return unknownUc, nil +} + +func init() { + registerEncoder( + Tag9286UserComment{}, + Codec9286UserComment{}) + + registerDecoder( + exifcommon.IfdPathStandardExif, + 0x9286, + Codec9286UserComment{}) +} diff --git a/v2/undefined/exif_A000_flashpix_version.go b/v2/undefined/exif_A000_flashpix_version.go new file mode 100644 index 0000000..bfa8a0d --- /dev/null +++ b/v2/undefined/exif_A000_flashpix_version.go @@ -0,0 +1,32 @@ +package exifundefined + +import ( + "github.com/dsoprea/go-logging" + + "github.com/dsoprea/go-exif/v2/common" +) + +type CodecA000FlashpixVersion struct { +} + +func (CodecA000FlashpixVersion) Decode(valueContext *exifcommon.ValueContext) (value interface{}, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + valueContext.SetUnknownValueType(exifcommon.TypeAsciiNoNul) + + valueString, err := valueContext.ReadAsciiNoNul() + log.PanicIf(err) + + return TagUndefinedGeneralString(valueString), nil +} + +func init() { + registerDecoder( + exifcommon.IfdPathStandardExif, + 0xa000, + CodecA000FlashpixVersion{}) +} diff --git a/v2/undefined/exif_A20C_spatial_frequency_response.go b/v2/undefined/exif_A20C_spatial_frequency_response.go new file mode 100644 index 0000000..97f4771 --- /dev/null +++ b/v2/undefined/exif_A20C_spatial_frequency_response.go @@ -0,0 +1,151 @@ +package exifundefined + +import ( + "bytes" + + "encoding/binary" + + "github.com/dsoprea/go-logging" + + "github.com/dsoprea/go-exif/v2/common" +) + +type TagA20CSpatialFrequencyResponse struct { + Columns uint16 + Rows uint16 + ColumnNames []string + Values []exifcommon.Rational +} + +type CodecA20CSpatialFrequencyResponse struct { +} + +func (CodecA20CSpatialFrequencyResponse) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + // TODO(dustin): Add test. + + sfr, ok := value.(TagA20CSpatialFrequencyResponse) + if ok == false { + log.Panicf("can only encode a TagA20CSpatialFrequencyResponse") + } + + b := new(bytes.Buffer) + + err = binary.Write(b, byteOrder, sfr.Columns) + log.PanicIf(err) + + err = binary.Write(b, byteOrder, sfr.Rows) + log.PanicIf(err) + + // Write columns. + + for _, name := range sfr.ColumnNames { + _, err := b.WriteString(name) + log.PanicIf(err) + + err = b.WriteByte(0) + log.PanicIf(err) + } + + // Write values. + + ve := exifcommon.NewValueEncoder(byteOrder) + + ed, err := ve.Encode(sfr.Values) + log.PanicIf(err) + + _, err = b.Write(ed.Encoded) + log.PanicIf(err) + + encoded = b.Bytes() + + // TODO(dustin): Confirm this size against the specification. + + return encoded, uint32(len(encoded)), nil +} + +func (CodecA20CSpatialFrequencyResponse) Decode(valueContext *exifcommon.ValueContext) (value interface{}, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + // TODO(dustin): Add test using known good data. + + byteOrder := valueContext.ByteOrder() + + valueContext.SetUnknownValueType(exifcommon.TypeByte) + + valueBytes, err := valueContext.ReadBytes() + log.PanicIf(err) + + sfr := TagA20CSpatialFrequencyResponse{} + + sfr.Columns = byteOrder.Uint16(valueBytes[0:2]) + sfr.Rows = byteOrder.Uint16(valueBytes[2:4]) + + columnNames := make([]string, sfr.Columns) + + // startAt is where the current column name starts. + startAt := 4 + + // offset is our current position. + offset := 4 + + currentColumnNumber := uint16(0) + + for currentColumnNumber < sfr.Columns { + if valueBytes[offset] == 0 { + columnName := string(valueBytes[startAt:offset]) + if len(columnName) == 0 { + log.Panicf("SFR column (%d) has zero length", currentColumnNumber) + } + + columnNames[currentColumnNumber] = columnName + currentColumnNumber++ + + offset++ + startAt = offset + continue + } + + offset++ + } + + sfr.ColumnNames = columnNames + + rawRationalBytes := valueBytes[offset:] + + rationalSize := exifcommon.TypeRational.Size() + if len(rawRationalBytes)%rationalSize > 0 { + log.Panicf("SFR rationals not aligned: (%d) %% (%d) > 0", len(rawRationalBytes), rationalSize) + } + + rationalCount := len(rawRationalBytes) / rationalSize + + parser := new(exifcommon.Parser) + + items, err := parser.ParseRationals(rawRationalBytes, uint32(rationalCount), byteOrder) + log.PanicIf(err) + + sfr.Values = items + + return sfr, nil +} + +func init() { + registerEncoder( + TagA302CfaPattern{}, + CodecA302CfaPattern{}) + + registerDecoder( + exifcommon.IfdPathStandardExif, + 0xa302, + CodecA302CfaPattern{}) +} diff --git a/v2/undefined/exif_A300_file_source.go b/v2/undefined/exif_A300_file_source.go new file mode 100644 index 0000000..814bd25 --- /dev/null +++ b/v2/undefined/exif_A300_file_source.go @@ -0,0 +1,69 @@ +package exifundefined + +import ( + "encoding/binary" + + "github.com/dsoprea/go-logging" + + "github.com/dsoprea/go-exif/v2/common" +) + +type TagExifA300FileSource uint32 + +const ( + TagUndefinedType_A300_SceneType_Others TagExifA300FileSource = 0 + TagUndefinedType_A300_SceneType_ScannerOfTransparentType TagExifA300FileSource = 1 + TagUndefinedType_A300_SceneType_ScannerOfReflexType TagExifA300FileSource = 2 + TagUndefinedType_A300_SceneType_Dsc TagExifA300FileSource = 3 +) + +type CodecExifA300FileSource struct { +} + +func (CodecExifA300FileSource) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + st, ok := value.(TagExifA300FileSource) + if ok == false { + log.Panicf("can only encode a TagExifA300FileSource") + } + + ve := exifcommon.NewValueEncoder(byteOrder) + + ed, err := ve.Encode([]uint32{uint32(st)}) + log.PanicIf(err) + + // TODO(dustin): Confirm this size against the specification. It's non-specific about what type it is, but it looks to be no more than a single integer scalar. So, we're assuming it's a LONG. + + return ed.Encoded, uint32(int(ed.UnitCount)), nil +} + +func (CodecExifA300FileSource) Decode(valueContext *exifcommon.ValueContext) (value interface{}, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + valueContext.SetUnknownValueType(exifcommon.TypeLong) + + valueLongs, err := valueContext.ReadLongs() + log.PanicIf(err) + + return TagExifA300FileSource(valueLongs[0]), nil +} + +func init() { + registerEncoder( + TagExifA300FileSource(0), + CodecExifA300FileSource{}) + + registerDecoder( + exifcommon.IfdPathStandardExif, + 0xa300, + CodecExifA300FileSource{}) +} diff --git a/v2/undefined/exif_A301_scene_type.go b/v2/undefined/exif_A301_scene_type.go new file mode 100644 index 0000000..1ff7706 --- /dev/null +++ b/v2/undefined/exif_A301_scene_type.go @@ -0,0 +1,66 @@ +package exifundefined + +import ( + "encoding/binary" + + "github.com/dsoprea/go-logging" + + "github.com/dsoprea/go-exif/v2/common" +) + +type TagExifA301SceneType uint32 + +const ( + TagUndefinedType_A301_SceneType_DirectlyPhotographedImage TagExifA301SceneType = 1 +) + +type CodecExifA301SceneType struct { +} + +func (CodecExifA301SceneType) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + st, ok := value.(TagExifA301SceneType) + if ok == false { + log.Panicf("can only encode a TagExif9101ComponentsConfiguration") + } + + ve := exifcommon.NewValueEncoder(byteOrder) + + ed, err := ve.Encode([]uint32{uint32(st)}) + log.PanicIf(err) + + // TODO(dustin): Confirm this size against the specification. It's non-specific about what type it is, but it looks to be no more than a single integer scalar. So, we're assuming it's a LONG. + + return ed.Encoded, uint32(int(ed.UnitCount)), nil +} + +func (CodecExifA301SceneType) Decode(valueContext *exifcommon.ValueContext) (value interface{}, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + valueContext.SetUnknownValueType(exifcommon.TypeLong) + + valueLongs, err := valueContext.ReadLongs() + log.PanicIf(err) + + return TagExifA301SceneType(valueLongs[0]), nil +} + +func init() { + registerEncoder( + TagExifA301SceneType(0), + CodecExifA301SceneType{}) + + registerDecoder( + exifcommon.IfdPathStandardExif, + 0xa301, + CodecExifA301SceneType{}) +} diff --git a/v2/undefined/exif_A302_cfa_pattern.go b/v2/undefined/exif_A302_cfa_pattern.go new file mode 100644 index 0000000..bb0fc69 --- /dev/null +++ b/v2/undefined/exif_A302_cfa_pattern.go @@ -0,0 +1,88 @@ +package exifundefined + +import ( + "bytes" + + "encoding/binary" + + "github.com/dsoprea/go-logging" + + "github.com/dsoprea/go-exif/v2/common" +) + +type TagA302CfaPattern struct { + HorizontalRepeat uint16 + VerticalRepeat uint16 + CfaValue []byte +} + +type CodecA302CfaPattern struct { +} + +func (CodecA302CfaPattern) Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + // TODO(dustin): Add test. + + cp, ok := value.(TagA302CfaPattern) + if ok == false { + log.Panicf("can only encode a TagA302CfaPattern") + } + + b := new(bytes.Buffer) + + err = binary.Write(b, byteOrder, cp.HorizontalRepeat) + log.PanicIf(err) + + err = binary.Write(b, byteOrder, cp.VerticalRepeat) + log.PanicIf(err) + + _, err = b.Write(cp.CfaValue) + log.PanicIf(err) + + encoded = b.Bytes() + + // TODO(dustin): Confirm this size against the specification. + + return encoded, uint32(len(encoded)), nil +} + +func (CodecA302CfaPattern) Decode(valueContext *exifcommon.ValueContext) (value interface{}, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + // TODO(dustin): Add test using known good data. + + valueContext.SetUnknownValueType(exifcommon.TypeByte) + + valueBytes, err := valueContext.ReadBytes() + log.PanicIf(err) + + cp := TagA302CfaPattern{} + + cp.HorizontalRepeat = valueContext.ByteOrder().Uint16(valueBytes[0:2]) + cp.VerticalRepeat = valueContext.ByteOrder().Uint16(valueBytes[2:4]) + + expectedLength := int(cp.HorizontalRepeat * cp.VerticalRepeat) + cp.CfaValue = valueBytes[4 : 4+expectedLength] + + return cp, nil +} + +func init() { + registerEncoder( + TagA302CfaPattern{}, + CodecA302CfaPattern{}) + + registerDecoder( + exifcommon.IfdPathStandardExif, + 0xa302, + CodecA302CfaPattern{}) +} diff --git a/v2/undefined/exif_iop_0002_interop_version.go b/v2/undefined/exif_iop_0002_interop_version.go new file mode 100644 index 0000000..d1e17f9 --- /dev/null +++ b/v2/undefined/exif_iop_0002_interop_version.go @@ -0,0 +1,32 @@ +package exifundefined + +import ( + "github.com/dsoprea/go-logging" + + "github.com/dsoprea/go-exif/v2/common" +) + +type Codec0002InteropVersion struct { +} + +func (Codec0002InteropVersion) Decode(valueContext *exifcommon.ValueContext) (value interface{}, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + valueContext.SetUnknownValueType(exifcommon.TypeAsciiNoNul) + + valueString, err := valueContext.ReadAsciiNoNul() + log.PanicIf(err) + + return TagUndefinedGeneralString(valueString), nil +} + +func init() { + registerDecoder( + exifcommon.IfdPathStandardExifIop, + 0x0002, + Codec0002InteropVersion{}) +} diff --git a/v2/undefined/gps_001B_gps_processing_method.go b/v2/undefined/gps_001B_gps_processing_method.go new file mode 100644 index 0000000..8dd0605 --- /dev/null +++ b/v2/undefined/gps_001B_gps_processing_method.go @@ -0,0 +1,32 @@ +package exifundefined + +import ( + "github.com/dsoprea/go-logging" + + "github.com/dsoprea/go-exif/v2/common" +) + +type Codec001BGPSProcessingMethod struct { +} + +func (Codec001BGPSProcessingMethod) Decode(valueContext *exifcommon.ValueContext) (value interface{}, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + valueContext.SetUnknownValueType(exifcommon.TypeAsciiNoNul) + + valueString, err := valueContext.ReadAsciiNoNul() + log.PanicIf(err) + + return TagUndefinedGeneralString(valueString), nil +} + +func init() { + registerDecoder( + exifcommon.IfdPathStandardGps, + 0x001b, + Codec001BGPSProcessingMethod{}) +} diff --git a/v2/undefined/gps_001C_gps_area_information.go b/v2/undefined/gps_001C_gps_area_information.go new file mode 100644 index 0000000..0fa1119 --- /dev/null +++ b/v2/undefined/gps_001C_gps_area_information.go @@ -0,0 +1,32 @@ +package exifundefined + +import ( + "github.com/dsoprea/go-logging" + + "github.com/dsoprea/go-exif/v2/common" +) + +type Codec001CGPSAreaInformation struct { +} + +func (Codec001CGPSAreaInformation) Decode(valueContext *exifcommon.ValueContext) (value interface{}, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + valueContext.SetUnknownValueType(exifcommon.TypeAsciiNoNul) + + valueString, err := valueContext.ReadAsciiNoNul() + log.PanicIf(err) + + return TagUndefinedGeneralString(valueString), nil +} + +func init() { + registerDecoder( + exifcommon.IfdPathStandardGps, + 0x001c, + Codec001CGPSAreaInformation{}) +} diff --git a/v2/undefined/registration.go b/v2/undefined/registration.go new file mode 100644 index 0000000..983595a --- /dev/null +++ b/v2/undefined/registration.go @@ -0,0 +1,42 @@ +package exifundefined + +import ( + "reflect" + + "github.com/dsoprea/go-logging" +) + +type UndefinedTagHandle struct { + IfdPath string + TagId uint16 +} + +func registerEncoder(entity interface{}, encoder UndefinedValueEncoder) { + typeName := reflect.TypeOf(entity).Name() + + _, found := encoders[typeName] + if found != true { + log.Panicf("encoder already registered: %v", typeName) + } + + encoders[typeName] = encoder +} + +func registerDecoder(ifdPath string, tagId uint16, decoder UndefinedValueDecoder) { + uth := UndefinedTagHandle{ + IfdPath: ifdPath, + TagId: tagId, + } + + _, found := decoders[uth] + if found != true { + log.Panicf("decoder already registered: %v", uth) + } + + decoders[uth] = decoder +} + +var ( + encoders = make(map[string]UndefinedValueEncoder) + decoders = make(map[UndefinedTagHandle]UndefinedValueDecoder) +) diff --git a/v2/undefined/type.go b/v2/undefined/type.go new file mode 100644 index 0000000..fe0559f --- /dev/null +++ b/v2/undefined/type.go @@ -0,0 +1,58 @@ +package exifundefined + +import ( + "fmt" + "strings" + + "crypto/sha1" + "encoding/binary" + + "github.com/dsoprea/go-logging" + + "github.com/dsoprea/go-exif/v2/common" +) + +const ( + UnparseableUnknownTagValuePlaceholder = "!UNKNOWN" +) + +// UndefinedValueEncoder knows how to encode an undefined-type tag's value to +// bytes. +type UndefinedValueEncoder interface { + Encode(value interface{}, byteOrder binary.ByteOrder) (encoded []byte, unitCount uint32, err error) +} + +// UndefinedValueEncoder knows how to decode an undefined-type tag's value from +// bytes. +type UndefinedValueDecoder interface { + Decode(valueContext *exifcommon.ValueContext) (value interface{}, err error) +} + +// TODO(dustin): Rename `UnknownTagValue` to `UndefinedTagValue`. +// OBSOLETE(dustin): Use a `UndefinedValueEncoder` instead of an `UnknownTagValue`. + +type UnknownTagValue interface { + ValueBytes() ([]byte, error) +} + +// TODO(dustin): Rename `TagUnknownType_UnknownValue` to `TagUndefinedType_UnknownValue`. + +type TagUnknownType_UnknownValue []byte + +func (tutuv TagUnknownType_UnknownValue) String() string { + parts := make([]string, len(tutuv)) + for i, c := range tutuv { + parts[i] = fmt.Sprintf("%02x", c) + } + + h := sha1.New() + + _, err := h.Write(tutuv) + log.PanicIf(err) + + digest := h.Sum(nil) + + return fmt.Sprintf("Unknown", strings.Join(parts, " "), len(tutuv), digest) +} + +type TagUndefinedGeneralString string diff --git a/v2/utility.go b/v2/utility.go index d7f3006..46bb79f 100644 --- a/v2/utility.go +++ b/v2/utility.go @@ -10,63 +10,6 @@ import ( "github.com/dsoprea/go-logging" ) -func DumpBytes(data []byte) { - fmt.Printf("DUMP: ") - for _, x := range data { - fmt.Printf("%02x ", x) - } - - fmt.Printf("\n") -} - -func DumpBytesClause(data []byte) { - fmt.Printf("DUMP: ") - - fmt.Printf("[]byte { ") - - for i, x := range data { - fmt.Printf("0x%02x", x) - - if i < len(data)-1 { - fmt.Printf(", ") - } - } - - fmt.Printf(" }\n") -} - -func DumpBytesToString(data []byte) string { - b := new(bytes.Buffer) - - for i, x := range data { - _, err := b.WriteString(fmt.Sprintf("%02x", x)) - log.PanicIf(err) - - if i < len(data)-1 { - _, err := b.WriteRune(' ') - log.PanicIf(err) - } - } - - return b.String() -} - -func DumpBytesClauseToString(data []byte) string { - b := new(bytes.Buffer) - - for i, x := range data { - _, err := b.WriteString(fmt.Sprintf("0x%02x", x)) - log.PanicIf(err) - - if i < len(data)-1 { - _, err := b.WriteString(", ") - log.PanicIf(err) - } - } - - return b.String() -} - // ParseExifFullTimestamp parses dates like "2018:11:30 13:01:49" into a UTC // `time.Time` struct. func ParseExifFullTimestamp(fullTimestampPhrase string) (timestamp time.Time, err error) { diff --git a/v2/value_context.go b/v2/value_context.go index c5adff2..e89e4b4 100644 --- a/v2/value_context.go +++ b/v2/value_context.go @@ -2,48 +2,8 @@ package exif import ( "encoding/binary" - - "github.com/dsoprea/go-logging" ) -var ( - parser *Parser -) - -// ValueContext describes all of the parameters required to find and extract -// the actual tag value. -type ValueContext struct { - unitCount uint32 - valueOffset uint32 - rawValueOffset []byte - addressableData []byte - - tagType TagTypePrimitive - byteOrder binary.ByteOrder - - // undefinedValueTagType is the effective type to use if this is an - // "undefined" value. - undefinedValueTagType TagTypePrimitive - - ifdPath string - tagId uint16 -} - -func newValueContext(ifdPath string, tagId uint16, unitCount, valueOffset uint32, rawValueOffset, addressableData []byte, tagType TagTypePrimitive, byteOrder binary.ByteOrder) *ValueContext { - return &ValueContext{ - unitCount: unitCount, - valueOffset: valueOffset, - rawValueOffset: rawValueOffset, - addressableData: addressableData, - - tagType: tagType, - byteOrder: byteOrder, - - ifdPath: ifdPath, - tagId: tagId, - } -} - func newValueContextFromTag(ite *IfdTagEntry, addressableData []byte, byteOrder binary.ByteOrder) *ValueContext { return newValueContext( ite.IfdPath, @@ -55,307 +15,3 @@ func newValueContextFromTag(ite *IfdTagEntry, addressableData []byte, byteOrder ite.TagType, byteOrder) } - -func (vc *ValueContext) SetUnknownValueType(tagType TagTypePrimitive) { - vc.undefinedValueTagType = tagType -} - -func (vc *ValueContext) UnitCount() uint32 { - return vc.unitCount -} - -func (vc *ValueContext) ValueOffset() uint32 { - return vc.valueOffset -} - -func (vc *ValueContext) RawValueOffset() []byte { - return vc.rawValueOffset -} - -func (vc *ValueContext) AddressableData() []byte { - return vc.addressableData -} - -// isEmbedded returns whether the value is embedded or a reference. This can't -// be precalculated since the size is not defined for all types (namely the -// "undefined" types). -func (vc *ValueContext) isEmbedded() bool { - tagType := vc.effectiveValueType() - - return (tagType.Size() * int(vc.unitCount)) <= 4 -} - -func (vc *ValueContext) effectiveValueType() (tagType TagTypePrimitive) { - if vc.tagType == TypeUndefined { - tagType = vc.undefinedValueTagType - - if tagType == 0 { - log.Panicf("undefined-value type not set") - } - } else { - tagType = vc.tagType - } - - return tagType -} - -func (vc *ValueContext) readRawEncoded() (rawBytes []byte, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - tagType := vc.effectiveValueType() - - unitSizeRaw := uint32(tagType.Size()) - - if vc.isEmbedded() == true { - byteLength := unitSizeRaw * vc.unitCount - return vc.rawValueOffset[:byteLength], nil - } else { - return vc.addressableData[vc.valueOffset : vc.valueOffset+vc.unitCount*unitSizeRaw], nil - } -} - -// Format returns a string representation for the value. -// -// Where the type is not ASCII, `justFirst` indicates whether to just stringify -// the first item in the slice (or return an empty string if the slice is -// empty). -// -// Since this method lacks the information to process undefined-type tags (e.g. -// byte-order, tag-ID, IFD type), it will return an error if attempted. See -// `Undefined()`. -func (vc *ValueContext) Format() (value string, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - rawBytes, err := vc.readRawEncoded() - log.PanicIf(err) - - phrase, err := Format(rawBytes, vc.tagType, false, vc.byteOrder) - log.PanicIf(err) - - return phrase, nil -} - -// FormatOne is similar to `Format` but only gets and stringifies the first -// item. -func (vc *ValueContext) FormatFirst() (value string, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - rawBytes, err := vc.readRawEncoded() - log.PanicIf(err) - - phrase, err := Format(rawBytes, vc.tagType, true, vc.byteOrder) - log.PanicIf(err) - - return phrase, nil -} - -func (vc *ValueContext) ReadBytes() (value []byte, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - rawValue, err := vc.readRawEncoded() - log.PanicIf(err) - - value, err = parser.ParseBytes(rawValue, vc.unitCount) - log.PanicIf(err) - - return value, nil -} - -func (vc *ValueContext) ReadAscii() (value string, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - rawValue, err := vc.readRawEncoded() - log.PanicIf(err) - - value, err = parser.ParseAscii(rawValue, vc.unitCount) - log.PanicIf(err) - - return value, nil -} - -func (vc *ValueContext) ReadAsciiNoNul() (value string, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - rawValue, err := vc.readRawEncoded() - log.PanicIf(err) - - value, err = parser.ParseAsciiNoNul(rawValue, vc.unitCount) - log.PanicIf(err) - - return value, nil -} - -func (vc *ValueContext) ReadShorts() (value []uint16, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - rawValue, err := vc.readRawEncoded() - log.PanicIf(err) - - value, err = parser.ParseShorts(rawValue, vc.unitCount, vc.byteOrder) - log.PanicIf(err) - - return value, nil -} - -func (vc *ValueContext) ReadLongs() (value []uint32, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - rawValue, err := vc.readRawEncoded() - log.PanicIf(err) - - value, err = parser.ParseLongs(rawValue, vc.unitCount, vc.byteOrder) - log.PanicIf(err) - - return value, nil -} - -func (vc *ValueContext) ReadRationals() (value []Rational, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - rawValue, err := vc.readRawEncoded() - log.PanicIf(err) - - value, err = parser.ParseRationals(rawValue, vc.unitCount, vc.byteOrder) - log.PanicIf(err) - - return value, nil -} - -func (vc *ValueContext) ReadSignedLongs() (value []int32, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - rawValue, err := vc.readRawEncoded() - log.PanicIf(err) - - value, err = parser.ParseSignedLongs(rawValue, vc.unitCount, vc.byteOrder) - log.PanicIf(err) - - return value, nil -} - -func (vc *ValueContext) ReadSignedRationals() (value []SignedRational, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - rawValue, err := vc.readRawEncoded() - log.PanicIf(err) - - value, err = parser.ParseSignedRationals(rawValue, vc.unitCount, vc.byteOrder) - log.PanicIf(err) - - return value, nil -} - -// Values knows how to resolve the given value. This value is always a list -// (undefined-values aside), so we're named accordingly. -// -// Since this method lacks the information to process unknown-type tags (e.g. -// byte-order, tag-ID, IFD type), it will return an error if attempted. See -// `Undefined()`. -func (vc *ValueContext) Values() (values interface{}, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - if vc.tagType == TypeByte { - values, err = vc.ReadBytes() - log.PanicIf(err) - } else if vc.tagType == TypeAscii { - values, err = vc.ReadAscii() - log.PanicIf(err) - } else if vc.tagType == TypeAsciiNoNul { - values, err = vc.ReadAsciiNoNul() - log.PanicIf(err) - } else if vc.tagType == TypeShort { - values, err = vc.ReadShorts() - log.PanicIf(err) - } else if vc.tagType == TypeLong { - values, err = vc.ReadLongs() - log.PanicIf(err) - } else if vc.tagType == TypeRational { - values, err = vc.ReadRationals() - log.PanicIf(err) - } else if vc.tagType == TypeSignedLong { - values, err = vc.ReadSignedLongs() - log.PanicIf(err) - } else if vc.tagType == TypeSignedRational { - values, err = vc.ReadSignedRationals() - log.PanicIf(err) - } else if vc.tagType == TypeUndefined { - log.Panicf("will not parse undefined-type value") - - // Never called. - return nil, nil - } else { - log.Panicf("value of type [%s] is unparseable", vc.tagType) - - // Never called. - return nil, nil - } - - return values, nil -} - -// Undefined attempts to identify and decode supported undefined-type fields. -// This is the primary, preferred interface to reading undefined values. -func (vc *ValueContext) Undefined() (value interface{}, err error) { - defer func() { - if state := recover(); state != nil { - err = log.Wrap(state.(error)) - } - }() - - value, err = UndefinedValue(vc.ifdPath, vc.tagId, vc, vc.byteOrder) - log.PanicIf(err) - - return value, nil -} - -func init() { - parser = &Parser{} -}