diff --git a/exif-read-tool/main.go b/exif-read-tool/main.go index 68a3e00..469071d 100644 --- a/exif-read-tool/main.go +++ b/exif-read-tool/main.go @@ -32,16 +32,16 @@ var ( ) type IfdEntry struct { - IfdPath string `json:"ifd_path"` - FqIfdPath string `json:"fq_ifd_path"` - IfdIndex int `json:"ifd_index"` - TagId uint16 `json:"tag_id"` - TagName string `json:"tag_name"` - TagTypeId uint16 `json:"tag_type_id"` - TagTypeName string `json:"tag_type_name"` - UnitCount uint32 `json:"unit_count"` - Value interface{} `json:"value"` - ValueString string `json:"value_string"` + IfdPath string `json:"ifd_path"` + FqIfdPath string `json:"fq_ifd_path"` + IfdIndex int `json:"ifd_index"` + TagId uint16 `json:"tag_id"` + TagName string `json:"tag_name"` + TagTypeId exif.TagTypePrimitive `json:"tag_type_id"` + TagTypeName string `json:"tag_type_name"` + UnitCount uint32 `json:"unit_count"` + Value interface{} `json:"value"` + ValueString string `json:"value_string"` } func main() { diff --git a/ifd_builder.go b/ifd_builder.go index d7e0f21..327766f 100644 --- a/ifd_builder.go +++ b/ifd_builder.go @@ -90,7 +90,7 @@ type BuilderTag struct { ifdPath string tagId uint16 - typeId uint16 + typeId TagTypePrimitive // value is either a value that can be encoded, an IfdBuilder instance (for // child IFDs), or an IfdTagEntry instance representing an existing, @@ -102,7 +102,7 @@ type BuilderTag struct { byteOrder binary.ByteOrder } -func NewBuilderTag(ifdPath string, tagId uint16, typeId uint16, value *IfdBuilderTagValue, byteOrder binary.ByteOrder) *BuilderTag { +func NewBuilderTag(ifdPath string, tagId uint16, typeId TagTypePrimitive, value *IfdBuilderTagValue, byteOrder binary.ByteOrder) *BuilderTag { return &BuilderTag{ ifdPath: ifdPath, tagId: tagId, diff --git a/ifd_builder_encode.go b/ifd_builder_encode.go index 548eff8..90fb2dd 100644 --- a/ifd_builder_encode.go +++ b/ifd_builder_encode.go @@ -212,7 +212,7 @@ func (ibe *IfdByteEncoder) encodeTagToBytes(ib *IfdBuilder, bt *BuilderTag, bw * // Works for both values and child IFDs (which have an official size of // LONG). - err = bw.WriteUint16(bt.typeId) + err = bw.WriteUint16(uint16(bt.typeId)) log.PanicIf(err) // Write unit-count. @@ -226,7 +226,7 @@ func (ibe *IfdByteEncoder) encodeTagToBytes(ib *IfdBuilder, bt *BuilderTag, bw * // It's a non-unknown value.Calculate the count of values of // the type that we're writing and the raw bytes for the whole list. - typeSize := uint32(TagTypeSize(effectiveType)) + typeSize := uint32(effectiveType.Size()) valueBytes := bt.value.Bytes() diff --git a/ifd_enumerate.go b/ifd_enumerate.go index 5b072e4..e3f82f9 100644 --- a/ifd_enumerate.go +++ b/ifd_enumerate.go @@ -145,15 +145,6 @@ func NewIfdEnumerate(ifdMapping *IfdMapping, tagIndex *TagIndex, exifData []byte } } -// 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 -} - func (ie *IfdEnumerate) getTagEnumerator(ifdOffset uint32) (ite *IfdTagEnumerator) { ite = NewIfdTagEnumerator( ie.exifData[ExifAddressableAreaStart:], @@ -173,9 +164,11 @@ func (ie *IfdEnumerate) parseTag(fqIfdPath string, tagPosition int, ite *IfdTagE tagId, _, err := ite.getUint16() log.PanicIf(err) - tagType, _, err := ite.getUint16() + tagTypeRaw, _, err := ite.getUint16() log.PanicIf(err) + tagType := TagTypePrimitive(tagTypeRaw) + unitCount, _, err := ite.getUint32() log.PanicIf(err) @@ -276,7 +269,7 @@ func (ie *IfdEnumerate) resolveTagValue(ite *IfdTagEntry) (valueBytes []byte, is } } else { originalType := NewTagType(ite.TagType, ie.byteOrder) - byteCount := uint32(originalType.Size()) * ite.UnitCount + byteCount := uint32(originalType.Type().Size()) * ite.UnitCount tt := NewTagType(TypeByte, ie.byteOrder) diff --git a/ifd_tag_entry.go b/ifd_tag_entry.go index 207b53e..feec23e 100644 --- a/ifd_tag_entry.go +++ b/ifd_tag_entry.go @@ -16,7 +16,7 @@ var ( type IfdTagEntry struct { TagId uint16 TagIndex int - TagType uint16 + TagType TagTypePrimitive UnitCount uint32 ValueOffset uint32 RawValueOffset []byte @@ -121,7 +121,7 @@ func (ite IfdTagEntry) ValueBytes(addressableData []byte, byteOrder binary.ByteO } originalType := NewTagType(ite.TagType, byteOrder) - byteCount := uint32(originalType.Size()) * ite.UnitCount + byteCount := uint32(originalType.Type().Size()) * ite.UnitCount tt := NewTagType(TypeByte, byteOrder) diff --git a/tags.go b/tags.go index b23a44e..ec333ec 100644 --- a/tags.go +++ b/tags.go @@ -59,7 +59,7 @@ type IndexedTag struct { Id uint16 Name string IfdPath string - Type uint16 + Type TagTypePrimitive } func (it *IndexedTag) String() string { diff --git a/type.go b/type.go index c01e73b..a39c0f3 100644 --- a/type.go +++ b/type.go @@ -12,18 +12,43 @@ import ( "github.com/dsoprea/go-logging" ) -const ( - TypeByte = uint16(1) - TypeAscii = uint16(2) - TypeShort = uint16(3) - TypeLong = uint16(4) - TypeRational = uint16(5) - TypeUndefined = uint16(7) - TypeSignedLong = uint16(9) - TypeSignedRational = uint16(10) +type TagTypePrimitive uint16 - // Custom, for our purposes. - TypeAsciiNoNul = uint16(0xf0) +func (tagType TagTypePrimitive) Size() int { + if tagType == TypeByte { + return 1 + } else if tagType == TypeAscii || tagType == TypeAsciiNoNul { + return 1 + } else if tagType == TypeShort { + return 2 + } else if tagType == TypeLong { + return 4 + } else if tagType == TypeRational { + return 8 + } else if tagType == TypeSignedLong { + return 4 + } else if tagType == TypeSignedRational { + return 8 + } else { + log.Panicf("can not determine tag-value size for type (%d): [%s]", tagType, TypeNames[tagType]) + + // Never called. + return 0 + } +} + +const ( + TypeByte TagTypePrimitive = 1 + TypeAscii = 2 + TypeShort = 3 + TypeLong = 4 + TypeRational = 5 + TypeUndefined = 7 + TypeSignedLong = 9 + TypeSignedRational = 10 + + // TypeAsciiNoNul is just a pseudo-type, for our own purposes. + TypeAsciiNoNul = 0xf0 ) var ( @@ -32,7 +57,7 @@ var ( var ( // TODO(dustin): Rename TypeNames() to typeNames() and add getter. - TypeNames = map[uint16]string{ + TypeNames = map[TagTypePrimitive]string{ TypeByte: "BYTE", TypeAscii: "ASCII", TypeShort: "SHORT", @@ -45,7 +70,7 @@ var ( TypeAsciiNoNul: "_ASCII_NO_NUL", } - TypeNamesR = map[string]uint16{} + TypeNamesR = map[string]TagTypePrimitive{} ) var ( @@ -74,12 +99,12 @@ type SignedRational struct { } type TagType struct { - tagType uint16 + tagType TagTypePrimitive name string byteOrder binary.ByteOrder } -func NewTagType(tagType uint16, byteOrder binary.ByteOrder) TagType { +func NewTagType(tagType TagTypePrimitive, byteOrder binary.ByteOrder) TagType { name, found := TypeNames[tagType] if found == false { log.Panicf("tag-type not valid: 0x%04x", tagType) @@ -100,7 +125,7 @@ func (tt TagType) Name() string { return tt.name } -func (tt TagType) Type() uint16 { +func (tt TagType) Type() TagTypePrimitive { return tt.tagType } @@ -108,37 +133,20 @@ func (tt TagType) ByteOrder() binary.ByteOrder { return tt.byteOrder } +// DEPRECATED(dustin): `(TagTypePrimitive).Size()` should be used, directly. func (tt TagType) Size() int { - return TagTypeSize(tt.Type()) + return tt.Type().Size() } -func TagTypeSize(tagType uint16) int { - if tagType == TypeByte { - return 1 - } else if tagType == TypeAscii || tagType == TypeAsciiNoNul { - return 1 - } else if tagType == TypeShort { - return 2 - } else if tagType == TypeLong { - return 4 - } else if tagType == TypeRational { - return 8 - } else if tagType == TypeSignedLong { - return 4 - } else if tagType == TypeSignedRational { - return 8 - } else { - log.Panicf("can not determine tag-value size for type (%d): [%s]", tagType, TypeNames[tagType]) - - // Never called. - return 0 - } +// DEPRECATED(dustin): `(TagTypePrimitive).Size()` should be used, directly. +func TagTypeSize(tagType TagTypePrimitive) int { + return tagType.Size() } // valueIsEmbedded will return a boolean indicating whether the value should be // found directly within the IFD entry or an offset to somewhere else. func (tt TagType) valueIsEmbedded(unitCount uint32) bool { - return (tt.Size() * int(unitCount)) <= 4 + return (tt.tagType.Size() * int(unitCount)) <= 4 } func (tt TagType) readRawEncoded(valueContext ValueContext) (rawBytes []byte, err error) { @@ -148,11 +156,13 @@ func (tt TagType) readRawEncoded(valueContext ValueContext) (rawBytes []byte, er } }() + unitSizeRaw := uint32(tt.tagType.Size()) + if tt.valueIsEmbedded(valueContext.UnitCount) == true { - byteLength := uint32(tt.Size()) * valueContext.UnitCount + byteLength := unitSizeRaw * valueContext.UnitCount return valueContext.RawValueOffset[:byteLength], nil } else { - return valueContext.AddressableData[valueContext.ValueOffset : valueContext.ValueOffset+valueContext.UnitCount*uint32(tt.Size())], nil + return valueContext.AddressableData[valueContext.ValueOffset : valueContext.ValueOffset+valueContext.UnitCount*unitSizeRaw], nil } } @@ -169,7 +179,7 @@ func (tt TagType) ParseBytes(data []byte, unitCount uint32) (value []uint8, err count := int(unitCount) - if len(data) < (tt.Size() * count) { + if len(data) < (tt.tagType.Size() * count) { log.Panic(ErrNotEnoughData) } @@ -192,7 +202,7 @@ func (tt TagType) ParseAscii(data []byte, unitCount uint32) (value string, err e count := int(unitCount) - if len(data) < (tt.Size() * count) { + if len(data) < (tt.tagType.Size() * count) { log.Panic(ErrNotEnoughData) } @@ -224,7 +234,7 @@ func (tt TagType) ParseAsciiNoNul(data []byte, unitCount uint32) (value string, count := int(unitCount) - if len(data) < (tt.Size() * count) { + if len(data) < (tt.tagType.Size() * count) { log.Panic(ErrNotEnoughData) } @@ -244,7 +254,7 @@ func (tt TagType) ParseShorts(data []byte, unitCount uint32) (value []uint16, er count := int(unitCount) - if len(data) < (tt.Size() * count) { + if len(data) < (tt.tagType.Size() * count) { log.Panic(ErrNotEnoughData) } @@ -273,7 +283,7 @@ func (tt TagType) ParseLongs(data []byte, unitCount uint32) (value []uint32, err count := int(unitCount) - if len(data) < (tt.Size() * count) { + if len(data) < (tt.tagType.Size() * count) { log.Panic(ErrNotEnoughData) } @@ -302,7 +312,7 @@ func (tt TagType) ParseRationals(data []byte, unitCount uint32) (value []Rationa count := int(unitCount) - if len(data) < (tt.Size() * count) { + if len(data) < (tt.tagType.Size() * count) { log.Panic(ErrNotEnoughData) } @@ -333,7 +343,7 @@ func (tt TagType) ParseSignedLongs(data []byte, unitCount uint32) (value []int32 count := int(unitCount) - if len(data) < (tt.Size() * count) { + if len(data) < (tt.tagType.Size() * count) { log.Panic(ErrNotEnoughData) } @@ -366,7 +376,7 @@ func (tt TagType) ParseSignedRationals(data []byte, unitCount uint32) (value []S count := int(unitCount) - if len(data) < (tt.Size() * count) { + if len(data) < (tt.tagType.Size() * count) { log.Panic(ErrNotEnoughData) } @@ -405,7 +415,7 @@ func (tt TagType) ReadByteValues(valueContext ValueContext) (value []byte, err e // In this case, the bytes normally used for the offset are actually // data. - byteLength := uint32(tt.Size()) * valueContext.UnitCount + byteLength := uint32(tt.tagType.Size()) * valueContext.UnitCount rawValue := valueContext.RawValueOffset[:byteLength] value, err = tt.ParseBytes(rawValue, valueContext.UnitCount) @@ -430,7 +440,7 @@ func (tt TagType) ReadAsciiValue(valueContext ValueContext) (value string, err e if tt.valueIsEmbedded(valueContext.UnitCount) == true { typeLogger.Debugf(nil, "Reading ASCII value (embedded).") - byteLength := uint32(tt.Size()) * valueContext.UnitCount + byteLength := uint32(tt.tagType.Size()) * valueContext.UnitCount rawValue := valueContext.RawValueOffset[:byteLength] value, err = tt.ParseAscii(rawValue, valueContext.UnitCount) @@ -455,7 +465,7 @@ func (tt TagType) ReadAsciiNoNulValue(valueContext ValueContext) (value string, if tt.valueIsEmbedded(valueContext.UnitCount) == true { typeLogger.Debugf(nil, "Reading ASCII value (no-nul; embedded).") - byteLength := uint32(tt.Size()) * valueContext.UnitCount + byteLength := uint32(tt.tagType.Size()) * valueContext.UnitCount rawValue := valueContext.RawValueOffset[:byteLength] value, err = tt.ParseAsciiNoNul(rawValue, valueContext.UnitCount) @@ -480,7 +490,7 @@ func (tt TagType) ReadShortValues(valueContext ValueContext) (value []uint16, er if tt.valueIsEmbedded(valueContext.UnitCount) == true { typeLogger.Debugf(nil, "Reading SHORT value (embedded).") - byteLength := uint32(tt.Size()) * valueContext.UnitCount + byteLength := uint32(tt.tagType.Size()) * valueContext.UnitCount rawValue := valueContext.RawValueOffset[:byteLength] value, err = tt.ParseShorts(rawValue, valueContext.UnitCount) @@ -505,7 +515,7 @@ func (tt TagType) ReadLongValues(valueContext ValueContext) (value []uint32, err if tt.valueIsEmbedded(valueContext.UnitCount) == true { typeLogger.Debugf(nil, "Reading LONG value (embedded).") - byteLength := uint32(tt.Size()) * valueContext.UnitCount + byteLength := uint32(tt.tagType.Size()) * valueContext.UnitCount rawValue := valueContext.RawValueOffset[:byteLength] value, err = tt.ParseLongs(rawValue, valueContext.UnitCount) @@ -530,7 +540,7 @@ func (tt TagType) ReadRationalValues(valueContext ValueContext) (value []Rationa if tt.valueIsEmbedded(valueContext.UnitCount) == true { typeLogger.Debugf(nil, "Reading RATIONAL value (embedded).") - byteLength := uint32(tt.Size()) * valueContext.UnitCount + byteLength := uint32(tt.tagType.Size()) * valueContext.UnitCount rawValue := valueContext.RawValueOffset[:byteLength] value, err = tt.ParseRationals(rawValue, valueContext.UnitCount) @@ -555,7 +565,7 @@ func (tt TagType) ReadSignedLongValues(valueContext ValueContext) (value []int32 if tt.valueIsEmbedded(valueContext.UnitCount) == true { typeLogger.Debugf(nil, "Reading SLONG value (embedded).") - byteLength := uint32(tt.Size()) * valueContext.UnitCount + byteLength := uint32(tt.tagType.Size()) * valueContext.UnitCount rawValue := valueContext.RawValueOffset[:byteLength] value, err = tt.ParseSignedLongs(rawValue, valueContext.UnitCount) @@ -580,7 +590,7 @@ func (tt TagType) ReadSignedRationalValues(valueContext ValueContext) (value []S if tt.valueIsEmbedded(valueContext.UnitCount) == true { typeLogger.Debugf(nil, "Reading SRATIONAL value (embedded).") - byteLength := uint32(tt.Size()) * valueContext.UnitCount + byteLength := uint32(tt.tagType.Size()) * valueContext.UnitCount rawValue := valueContext.RawValueOffset[:byteLength] value, err = tt.ParseSignedRationals(rawValue, valueContext.UnitCount) @@ -632,7 +642,7 @@ func (tt TagType) Format(rawBytes []byte, justFirst bool) (value string, err err // TODO(dustin): !! Add tests typeId := tt.Type() - typeSize := TagTypeSize(typeId) + typeSize := typeId.Size() if len(rawBytes)%typeSize != 0 { log.Panicf("byte-count (%d) does not align for [%s] type with a size of (%d) bytes", len(rawBytes), TypeNames[typeId], typeSize) diff --git a/type_encode.go b/type_encode.go index cd95f81..af55ee9 100644 --- a/type_encode.go +++ b/type_encode.go @@ -1,8 +1,8 @@ package exif import ( - "reflect" "bytes" + "reflect" "encoding/binary" @@ -13,15 +13,13 @@ var ( typeEncodeLogger = log.NewLogger("exif.type_encode") ) - // EncodedData encapsulates the compound output of an encoding operation. type EncodedData struct { - Type uint16 - Encoded []byte + Type TagTypePrimitive + Encoded []byte UnitCount uint32 } - type ValueEncoder struct { byteOrder binary.ByteOrder } @@ -32,7 +30,6 @@ func NewValueEncoder(byteOrder binary.ByteOrder) *ValueEncoder { } } - func (ve *ValueEncoder) encodeBytes(value []uint8) (ed EncodedData, err error) { ed.Type = TypeByte ed.Encoded = []byte(value) @@ -81,10 +78,10 @@ func (ve *ValueEncoder) encodeShorts(value []uint16) (ed EncodedData, err error) }() ed.UnitCount = uint32(len(value)) - ed.Encoded = make([]byte, ed.UnitCount * 2) + ed.Encoded = make([]byte, ed.UnitCount*2) for i := uint32(0); i < ed.UnitCount; i++ { -// TODO(dustin): We have a ton of duplication in how we handle the byte-orders and the inherent risk of accidentally doing something inconsistently. Move this to reusable code. + // TODO(dustin): We have a ton of duplication in how we handle the byte-orders and the inherent risk of accidentally doing something inconsistently. Move this to reusable code. if ve.byteOrder == binary.BigEndian { binary.BigEndian.PutUint16(ed.Encoded[i*2:(i+1)*2], value[i]) } else { @@ -105,7 +102,7 @@ func (ve *ValueEncoder) encodeLongs(value []uint32) (ed EncodedData, err error) }() ed.UnitCount = uint32(len(value)) - ed.Encoded = make([]byte, ed.UnitCount * 4) + ed.Encoded = make([]byte, ed.UnitCount*4) for i := uint32(0); i < ed.UnitCount; i++ { if ve.byteOrder == binary.BigEndian { @@ -128,7 +125,7 @@ func (ve *ValueEncoder) encodeRationals(value []Rational) (ed EncodedData, err e }() ed.UnitCount = uint32(len(value)) - ed.Encoded = make([]byte, ed.UnitCount * 8) + ed.Encoded = make([]byte, ed.UnitCount*8) for i := uint32(0); i < ed.UnitCount; i++ { if ve.byteOrder == binary.BigEndian { @@ -154,7 +151,7 @@ func (ve *ValueEncoder) encodeSignedLongs(value []int32) (ed EncodedData, err er ed.UnitCount = uint32(len(value)) - b := bytes.NewBuffer(make([]byte, 0, 8 * ed.UnitCount)) + b := bytes.NewBuffer(make([]byte, 0, 8*ed.UnitCount)) for i := uint32(0); i < ed.UnitCount; i++ { if ve.byteOrder == binary.BigEndian { @@ -181,7 +178,7 @@ func (ve *ValueEncoder) encodeSignedRationals(value []SignedRational) (ed Encode ed.UnitCount = uint32(len(value)) - b := bytes.NewBuffer(make([]byte, 0, 8 * ed.UnitCount)) + b := bytes.NewBuffer(make([]byte, 0, 8*ed.UnitCount)) for i := uint32(0); i < ed.UnitCount; i++ { if ve.byteOrder == binary.BigEndian { diff --git a/utility.go b/utility.go index d9f885d..1df1162 100644 --- a/utility.go +++ b/utility.go @@ -132,10 +132,10 @@ type ExifTag struct { TagId uint16 `json:"id"` TagName string `json:"name"` - TagTypeId uint16 `json:"type_id"` - TagTypeName string `json:"type_name"` - Value interface{} `json:"value"` - ValueBytes []byte `json:"value_bytes"` + TagTypeId TagTypePrimitive `json:"type_id"` + TagTypeName string `json:"type_name"` + Value interface{} `json:"value"` + ValueBytes []byte `json:"value_bytes"` ChildIfdPath string `json:"child_ifd_path"` } diff --git a/value_context.go b/value_context.go new file mode 100644 index 0000000..10c6534 --- /dev/null +++ b/value_context.go @@ -0,0 +1,19 @@ +package exif + +// 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 +} + +func newValueContext(unitCount, valueOffset uint32, rawValueOffset, addressableData []byte) ValueContext { + return ValueContext{ + UnitCount: unitCount, + ValueOffset: valueOffset, + RawValueOffset: rawValueOffset, + AddressableData: addressableData, + } +}