value_context.go: ValueContext now embeds value processing

The upshot of this is that generating a list of type-correct slices or a
string from a tag's value is now stupidly easy.

- Broke parsing into an independent type (parser.go:Parser).

- Moved primary value-processing logic to `ValueContext` so that it
  coexists with the actual data (though as much basic functionality as
  possible is implemented independently and reused here). This
  eliminates extremely ridiculously obtuse usage procedure.

- Deprecated almost all existing TagType functionality (in order to
  distance us from this now-legacy usage pattern). Existing
  functionality maintained for now. We'll drop it when we do the next
  release major.

- `ValueContext` is now passed by reference.
pull/28/head
Dustin Oprea 2019-12-29 04:25:23 -05:00
parent 7fb09bbf9f
commit 69821c417f
10 changed files with 1020 additions and 687 deletions

View File

@ -117,7 +117,7 @@ func main() {
valueString = fmt.Sprintf("%v", value)
}
} else {
valueString, err = tagType.ResolveAsString(valueContext, true)
valueString, err = valueContext.FormatFirst()
log.PanicIf(err)
value = valueString

View File

@ -90,7 +90,7 @@ func TestVisit(t *testing.T) {
valueString = fmt.Sprintf("%v", value)
}
} else {
valueString, err = tagType.ResolveAsString(valueContext, true)
valueString, err = valueContext.FormatFirst()
log.PanicIf(err)
}

View File

@ -129,11 +129,9 @@ func (bt *BuilderTag) String() string {
var valueString string
if bt.value.IsBytes() == true {
tt := NewTagType(bt.typeId, bt.byteOrder)
var err error
valueString, err = tt.Format(bt.value.Bytes(), false)
valueString, err = Format(bt.value.Bytes(), bt.typeId, false, bt.byteOrder)
log.PanicIf(err)
} else {
valueString = fmt.Sprintf("%v", bt.value)

View File

@ -294,20 +294,59 @@ func (ie *IfdEnumerate) resolveTagValue(ite *IfdTagEntry) (valueBytes []byte, is
return valueBytes, false, nil
}
// RawTagVisitorPtr is an optional callback that can get hit for every tag we parse
// through. `addressableData` is the byte array startign after the EXIF header
// (where the offsets of all IFDs and values are calculated from).
//
// This was reimplemented as an interface to allow for simpler change management
// in the future.
type RawTagWalk interface {
Visit(fqIfdPath string, ifdIndex int, tagId uint16, tagType TagType, valueContext *ValueContext) (err error)
}
type RawTagWalkLegacyWrapper struct {
legacyVisitor RawTagVisitor
}
func (rtwlw RawTagWalkLegacyWrapper) Visit(fqIfdPath string, ifdIndex int, tagId uint16, tagType TagType, valueContext *ValueContext) (err error) {
return rtwlw.legacyVisitor(fqIfdPath, ifdIndex, tagId, tagType, *valueContext)
}
// RawTagVisitor is an optional callback that can get hit for every tag we parse
// through. `addressableData` is the byte array startign after the EXIF header
// (where the offsets of all IFDs and values are calculated from).
//
// DEPRECATED(dustin): Use a RawTagWalk instead.
type RawTagVisitor func(fqIfdPath string, ifdIndex int, tagId uint16, tagType TagType, valueContext ValueContext) (err error)
// ParseIfd decodes the IFD block that we're currently sitting on the first
// byte of.
func (ie *IfdEnumerate) ParseIfd(fqIfdPath string, ifdIndex int, ite *IfdTagEnumerator, visitor RawTagVisitor, doDescend bool, resolveValues bool) (nextIfdOffset uint32, entries []*IfdTagEntry, thumbnailData []byte, err error) {
func (ie *IfdEnumerate) ParseIfd(fqIfdPath string, ifdIndex int, ite *IfdTagEnumerator, visitor interface{}, doDescend bool, resolveValues bool) (nextIfdOffset uint32, entries []*IfdTagEntry, thumbnailData []byte, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
var visitorWrapper RawTagWalk
if visitor != nil {
var ok bool
visitorWrapper, ok = visitor.(RawTagWalk)
if ok == false {
// Legacy usage.
// `ok` can be `true` but `legacyVisitor` can still be `nil` (when
// passed as nil).
if legacyVisitor, ok := visitor.(RawTagVisitor); ok == true && legacyVisitor != nil {
visitorWrapper = RawTagWalkLegacyWrapper{
legacyVisitor: legacyVisitor,
}
}
}
}
tagCount, _, err := ite.getUint16()
log.PanicIf(err)
@ -338,7 +377,7 @@ func (ie *IfdEnumerate) ParseIfd(fqIfdPath string, ifdIndex int, ite *IfdTagEnum
continue
}
if visitor != nil {
if visitorWrapper != nil {
tt := NewTagType(tag.TagType, ie.byteOrder)
vc :=
@ -350,7 +389,7 @@ func (ie *IfdEnumerate) ParseIfd(fqIfdPath string, ifdIndex int, ite *IfdTagEnum
tag.TagType,
ie.byteOrder)
err := visitor(fqIfdPath, ifdIndex, tag.TagId, tt, vc)
err := visitorWrapper.Visit(fqIfdPath, ifdIndex, tag.TagId, tt, vc)
log.PanicIf(err)
}
@ -412,7 +451,7 @@ func (ie *IfdEnumerate) parseThumbnail(offsetIte, lengthIte *IfdTagEntry) (thumb
}
// Scan enumerates the different EXIF's IFD blocks.
func (ie *IfdEnumerate) scan(fqIfdName string, ifdOffset uint32, visitor RawTagVisitor, resolveValues bool) (err error) {
func (ie *IfdEnumerate) scan(fqIfdName string, ifdOffset uint32, visitor interface{}, resolveValues bool) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))

View File

@ -70,9 +70,7 @@ func (ite IfdTagEntry) ValueString(addressableData []byte, byteOrder binary.Byte
value = fmt.Sprintf("%v", valueRaw)
} else {
tt := NewTagType(ite.TagType, byteOrder)
value, err = tt.ResolveAsString(vc, false)
value, err = vc.Format()
log.PanicIf(err)
}

190
parser.go Normal file
View File

@ -0,0 +1,190 @@
package exif
import (
"bytes"
"encoding/binary"
"github.com/dsoprea/go-logging"
)
type Parser struct {
}
func (p *Parser) ParseBytes(data []byte, unitCount uint32) (value []uint8, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
count := int(unitCount)
if len(data) < (TypeByte.Size() * count) {
log.Panic(ErrNotEnoughData)
}
value = []uint8(data[:count])
return value, nil
}
// ParseAscii returns a string and auto-strips the trailing NUL character.
func (p *Parser) ParseAscii(data []byte, unitCount uint32) (value string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
count := int(unitCount)
if len(data) < (TypeAscii.Size() * count) {
log.Panic(ErrNotEnoughData)
}
if len(data) == 0 || data[count-1] != 0 {
s := string(data[:count])
typeLogger.Warningf(nil, "ascii not terminated with nul as expected: [%v]", s)
return s, nil
} else {
// Auto-strip the NUL from the end. It serves no purpose outside of
// encoding semantics.
return string(data[:count-1]), nil
}
}
// ParseAsciiNoNul returns a string without any consideration for a trailing NUL
// character.
func (p *Parser) ParseAsciiNoNul(data []byte, unitCount uint32) (value string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
count := int(unitCount)
if len(data) < (TypeAscii.Size() * count) {
log.Panic(ErrNotEnoughData)
}
return string(data[:count]), nil
}
func (p *Parser) ParseShorts(data []byte, unitCount uint32, byteOrder binary.ByteOrder) (value []uint16, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
count := int(unitCount)
if len(data) < (TypeShort.Size() * count) {
log.Panic(ErrNotEnoughData)
}
value = make([]uint16, count)
for i := 0; i < count; i++ {
value[i] = byteOrder.Uint16(data[i*2:])
}
return value, nil
}
func (p *Parser) ParseLongs(data []byte, unitCount uint32, byteOrder binary.ByteOrder) (value []uint32, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
count := int(unitCount)
if len(data) < (TypeLong.Size() * count) {
log.Panic(ErrNotEnoughData)
}
value = make([]uint32, count)
for i := 0; i < count; i++ {
value[i] = byteOrder.Uint32(data[i*4:])
}
return value, nil
}
func (p *Parser) ParseRationals(data []byte, unitCount uint32, byteOrder binary.ByteOrder) (value []Rational, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
count := int(unitCount)
if len(data) < (TypeRational.Size() * count) {
log.Panic(ErrNotEnoughData)
}
value = make([]Rational, count)
for i := 0; i < count; i++ {
value[i].Numerator = byteOrder.Uint32(data[i*8:])
value[i].Denominator = byteOrder.Uint32(data[i*8+4:])
}
return value, nil
}
func (p *Parser) ParseSignedLongs(data []byte, unitCount uint32, byteOrder binary.ByteOrder) (value []int32, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
count := int(unitCount)
if len(data) < (TypeSignedLong.Size() * count) {
log.Panic(ErrNotEnoughData)
}
b := bytes.NewBuffer(data)
value = make([]int32, count)
for i := 0; i < count; i++ {
err := binary.Read(b, byteOrder, &value[i])
log.PanicIf(err)
}
return value, nil
}
func (p *Parser) ParseSignedRationals(data []byte, unitCount uint32, byteOrder binary.ByteOrder) (value []SignedRational, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
count := int(unitCount)
if len(data) < (TypeSignedRational.Size() * count) {
log.Panic(ErrNotEnoughData)
}
b := bytes.NewBuffer(data)
value = make([]SignedRational, count)
for i := 0; i < count; i++ {
err = binary.Read(b, byteOrder, &value[i].Numerator)
log.PanicIf(err)
err = binary.Read(b, byteOrder, &value[i].Denominator)
log.PanicIf(err)
}
return value, nil
}

397
tag_type.go Normal file
View File

@ -0,0 +1,397 @@
package exif
// NOTE(dustin): Most of this file encapsulates deprecated functionality and awaits being dumped in a future release.
import (
"fmt"
"encoding/binary"
"github.com/dsoprea/go-logging"
)
type TagType struct {
tagType TagTypePrimitive
name string
byteOrder binary.ByteOrder
}
func NewTagType(tagType TagTypePrimitive, byteOrder binary.ByteOrder) TagType {
name, found := TypeNames[tagType]
if found == false {
log.Panicf("tag-type not valid: 0x%04x", tagType)
}
return TagType{
tagType: tagType,
name: name,
byteOrder: byteOrder,
}
}
func (tt TagType) String() string {
return fmt.Sprintf("TagType<NAME=[%s]>", tt.name)
}
func (tt TagType) Name() string {
return tt.name
}
func (tt TagType) Type() TagTypePrimitive {
return tt.tagType
}
func (tt TagType) ByteOrder() binary.ByteOrder {
return tt.byteOrder
}
func (tt TagType) Size() int {
// DEPRECATED(dustin): `(TagTypePrimitive).Size()` should be used, directly.
return tt.Type().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.tagType.Size() * int(unitCount)) <= 4
}
func (tt TagType) readRawEncoded(valueContext ValueContext) (rawBytes []byte, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
unitSizeRaw := uint32(tt.tagType.Size())
if tt.valueIsEmbedded(valueContext.UnitCount()) == true {
byteLength := unitSizeRaw * valueContext.UnitCount()
return valueContext.RawValueOffset()[:byteLength], nil
} else {
return valueContext.AddressableData()[valueContext.ValueOffset() : valueContext.ValueOffset()+valueContext.UnitCount()*unitSizeRaw], nil
}
}
func (tt TagType) ParseBytes(data []byte, unitCount uint32) (value []uint8, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// DEPRECATED(dustin): `(*Parser).ParseBytes()` should be used.
value, err = parser.ParseBytes(data, unitCount)
log.PanicIf(err)
return value, nil
}
// ParseAscii returns a string and auto-strips the trailing NUL character.
func (tt TagType) ParseAscii(data []byte, unitCount uint32) (value string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// DEPRECATED(dustin): `(*Parser).ParseAscii()` should be used.
value, err = parser.ParseAscii(data, unitCount)
log.PanicIf(err)
return value, nil
}
// ParseAsciiNoNul returns a string without any consideration for a trailing NUL
// character.
func (tt TagType) ParseAsciiNoNul(data []byte, unitCount uint32) (value string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// DEPRECATED(dustin): `(*Parser).ParseAsciiNoNul()` should be used.
value, err = parser.ParseAsciiNoNul(data, unitCount)
log.PanicIf(err)
return value, nil
}
func (tt TagType) ParseShorts(data []byte, unitCount uint32) (value []uint16, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// DEPRECATED(dustin): `(*Parser).ParseShorts()` should be used.
value, err = parser.ParseShorts(data, unitCount, tt.byteOrder)
log.PanicIf(err)
return value, nil
}
func (tt TagType) ParseLongs(data []byte, unitCount uint32) (value []uint32, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// DEPRECATED(dustin): `(*Parser).ParseLongs()` should be used.
value, err = parser.ParseLongs(data, unitCount, tt.byteOrder)
log.PanicIf(err)
return value, nil
}
func (tt TagType) ParseRationals(data []byte, unitCount uint32) (value []Rational, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// DEPRECATED(dustin): `(*Parser).ParseRationals()` should be used.
value, err = parser.ParseRationals(data, unitCount, tt.byteOrder)
log.PanicIf(err)
return value, nil
}
func (tt TagType) ParseSignedLongs(data []byte, unitCount uint32) (value []int32, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// DEPRECATED(dustin): `(*Parser).ParseSignedLongs()` should be used.
value, err = parser.ParseSignedLongs(data, unitCount, tt.byteOrder)
log.PanicIf(err)
return value, nil
}
func (tt TagType) ParseSignedRationals(data []byte, unitCount uint32) (value []SignedRational, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// DEPRECATED(dustin): `(*Parser).ParseSignedRationals()` should be used.
value, err = parser.ParseSignedRationals(data, unitCount, tt.byteOrder)
log.PanicIf(err)
return value, nil
}
func (tt TagType) ReadByteValues(valueContext ValueContext) (value []byte, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// DEPRECATED(dustin): `(ValueContext).ReadBytes()` should be used.
value, err = valueContext.ReadBytes()
log.PanicIf(err)
return value, nil
}
func (tt TagType) ReadAsciiValue(valueContext ValueContext) (value string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// DEPRECATED(dustin): `(ValueContext).ReadAscii()` should be used.
value, err = valueContext.ReadAscii()
log.PanicIf(err)
return value, nil
}
func (tt TagType) ReadAsciiNoNulValue(valueContext ValueContext) (value string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// DEPRECATED(dustin): `(ValueContext).ReadAsciiNoNul()` should be used.
value, err = valueContext.ReadAsciiNoNul()
log.PanicIf(err)
return value, nil
}
func (tt TagType) ReadShortValues(valueContext ValueContext) (value []uint16, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// DEPRECATED(dustin): `(ValueContext).ReadShorts()` should be used.
value, err = valueContext.ReadShorts()
log.PanicIf(err)
return value, nil
}
func (tt TagType) ReadLongValues(valueContext ValueContext) (value []uint32, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// DEPRECATED(dustin): `(ValueContext).ReadLongs()` should be used.
value, err = valueContext.ReadLongs()
log.PanicIf(err)
return value, nil
}
func (tt TagType) ReadRationalValues(valueContext ValueContext) (value []Rational, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// DEPRECATED(dustin): `(ValueContext).ReadRationals()` should be used.
value, err = valueContext.ReadRationals()
log.PanicIf(err)
return value, nil
}
func (tt TagType) ReadSignedLongValues(valueContext ValueContext) (value []int32, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// DEPRECATED(dustin): `(ValueContext).ReadSignedLongs()` should be used.
value, err = valueContext.ReadSignedLongs()
log.PanicIf(err)
return value, nil
}
func (tt TagType) ReadSignedRationalValues(valueContext ValueContext) (value []SignedRational, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// DEPRECATED(dustin): `(ValueContext).ReadSignedRationals()` should be used.
value, err = valueContext.ReadSignedRationals()
log.PanicIf(err)
return value, nil
}
// ResolveAsString resolves the given value and returns a flat string.
//
// 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 unknown-type tags (e.g.
// byte-order, tag-ID, IFD type), it will return an error if attempted. See
// `UndefinedValue()`.
func (tt TagType) ResolveAsString(valueContext ValueContext, justFirst bool) (value string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if justFirst == true {
value, err = valueContext.FormatFirst()
log.PanicIf(err)
} else {
value, err = valueContext.Format()
log.PanicIf(err)
}
return value, nil
}
// Resolve knows how to resolve the given value.
//
// 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
// `UndefinedValue()`.
func (tt TagType) Resolve(valueContext *ValueContext) (values interface{}, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// DEPRECATED(dustin): `(ValueContext).Values()` should be used.
values, err = valueContext.Values()
log.PanicIf(err)
return values, nil
}
// Encode knows how to encode the given value to a byte slice.
func (tt TagType) Encode(value interface{}) (encoded []byte, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
ve := NewValueEncoder(tt.byteOrder)
ed, err := ve.EncodeWithType(tt, value)
log.PanicIf(err)
return ed.Encoded, err
}
func (tt TagType) FromString(valueString string) (value interface{}, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// DEPRECATED(dustin): `EncodeStringToBytes()` should be used.
value, err = EncodeStringToBytes(tt.tagType, valueString)
log.PanicIf(err)
return value, nil
}

View File

@ -226,40 +226,53 @@ func (tutuv TagUnknownType_UnknownValue) String() string {
}
// UndefinedValue knows how to resolve the value for most unknown-type tags.
func UndefinedValue(ifdPath string, tagId uint16, valueContext ValueContext, byteOrder binary.ByteOrder) (value interface{}, err error) {
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))
}
}()
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
tt := NewTagType(TypeAsciiNoNul, byteOrder)
valueContextPtr.SetUnknownValueParameters(TypeAsciiNoNul, valueContextPtr.UnitCount())
valueString, err := tt.ReadAsciiNoNulValue(valueContext)
valueString, err := valueContextPtr.ReadAsciiNoNul()
log.PanicIf(err)
return TagUnknownType_GeneralString(valueString), nil
} else if tagId == 0xa000 {
// FlashpixVersion
tt := NewTagType(TypeAsciiNoNul, byteOrder)
valueContextPtr.SetUnknownValueParameters(TypeAsciiNoNul, valueContextPtr.UnitCount())
valueString, err := tt.ReadAsciiNoNulValue(valueContext)
valueString, err := valueContextPtr.ReadAsciiNoNul()
log.PanicIf(err)
return TagUnknownType_GeneralString(valueString), nil
} else if tagId == 0x9286 {
// UserComment
tt := NewTagType(TypeByte, byteOrder)
valueContextPtr.SetUnknownValueParameters(TypeByte, valueContextPtr.UnitCount())
valueBytes, err := tt.ReadByteValues(valueContext)
valueBytes, err := valueContextPtr.ReadBytes()
log.PanicIf(err)
unknownUc := TagUnknownType_9298_UserComment{
@ -286,9 +299,9 @@ func UndefinedValue(ifdPath string, tagId uint16, valueContext ValueContext, byt
// 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).
tt := NewTagType(TypeByte, byteOrder)
valueContextPtr.SetUnknownValueParameters(TypeByte, valueContextPtr.UnitCount())
valueBytes, err := tt.ReadByteValues(valueContext)
valueBytes, err := valueContextPtr.ReadBytes()
log.PanicIf(err)
// TODO(dustin): Doesn't work, but here as an example.
@ -313,9 +326,9 @@ func UndefinedValue(ifdPath string, tagId uint16, valueContext ValueContext, byt
} else if tagId == 0x9101 {
// ComponentsConfiguration
tt := NewTagType(TypeByte, byteOrder)
valueContextPtr.SetUnknownValueParameters(TypeByte, valueContextPtr.UnitCount())
valueBytes, err := tt.ReadByteValues(valueContext)
valueBytes, err := valueContextPtr.ReadBytes()
log.PanicIf(err)
for configurationId, configurationBytes := range TagUnknownType_9101_ComponentsConfiguration_Configurations {
@ -340,18 +353,18 @@ func UndefinedValue(ifdPath string, tagId uint16, valueContext ValueContext, byt
if tagId == 0x001c {
// GPSAreaInformation
tt := NewTagType(TypeAsciiNoNul, byteOrder)
valueContextPtr.SetUnknownValueParameters(TypeAsciiNoNul, valueContextPtr.UnitCount())
valueString, err := tt.ReadAsciiNoNulValue(valueContext)
valueString, err := valueContextPtr.ReadAsciiNoNul()
log.PanicIf(err)
return TagUnknownType_GeneralString(valueString), nil
} else if tagId == 0x001b {
// GPSProcessingMethod
tt := NewTagType(TypeAsciiNoNul, byteOrder)
valueContextPtr.SetUnknownValueParameters(TypeAsciiNoNul, valueContextPtr.UnitCount())
valueString, err := tt.ReadAsciiNoNulValue(valueContext)
valueString, err := valueContextPtr.ReadAsciiNoNul()
log.PanicIf(err)
return TagUnknownType_GeneralString(valueString), nil
@ -360,9 +373,9 @@ func UndefinedValue(ifdPath string, tagId uint16, valueContext ValueContext, byt
if tagId == 0x0002 {
// InteropVersion
tt := NewTagType(TypeAsciiNoNul, byteOrder)
valueContextPtr.SetUnknownValueParameters(TypeAsciiNoNul, valueContextPtr.UnitCount())
valueString, err := tt.ReadAsciiNoNulValue(valueContext)
valueString, err := valueContextPtr.ReadAsciiNoNul()
log.PanicIf(err)
return TagUnknownType_GeneralString(valueString), nil
@ -379,9 +392,9 @@ func UndefinedValue(ifdPath string, tagId uint16, valueContext ValueContext, byt
// Return encapsulated data rather than an error so that we can at least
// print/profile the opaque data.
tt := NewTagType(TypeByte, byteOrder)
valueContextPtr.SetUnknownValueParameters(TypeByte, valueContextPtr.UnitCount())
valueBytes, err := tt.ReadByteValues(valueContext)
valueBytes, err := valueContextPtr.ReadBytes()
log.PanicIf(err)
tutuv := TagUnknownType_UnknownValue(valueBytes)

684
type.go
View File

@ -1,7 +1,6 @@
package exif
import (
"bytes"
"errors"
"fmt"
"strconv"
@ -14,6 +13,10 @@ import (
type TagTypePrimitive uint16
func (typeType TagTypePrimitive) String() string {
return TypeNames[typeType]
}
func (tagType TagTypePrimitive) Size() int {
if tagType == TypeByte {
return 1
@ -39,16 +42,16 @@ func (tagType TagTypePrimitive) Size() int {
const (
TypeByte TagTypePrimitive = 1
TypeAscii = 2
TypeShort = 3
TypeLong = 4
TypeRational = 5
TypeUndefined = 7
TypeSignedLong = 9
TypeSignedRational = 10
TypeAscii TagTypePrimitive = 2
TypeShort TagTypePrimitive = 3
TypeLong TagTypePrimitive = 4
TypeRational TagTypePrimitive = 5
TypeUndefined TagTypePrimitive = 7
TypeSignedLong TagTypePrimitive = 9
TypeSignedRational TagTypePrimitive = 10
// TypeAsciiNoNul is just a pseudo-type, for our own purposes.
TypeAsciiNoNul = 0xf0
TypeAsciiNoNul TagTypePrimitive = 0xf0
)
var (
@ -98,541 +101,16 @@ type SignedRational struct {
Denominator int32
}
type TagType struct {
tagType TagTypePrimitive
name string
byteOrder binary.ByteOrder
}
func NewTagType(tagType TagTypePrimitive, byteOrder binary.ByteOrder) TagType {
name, found := TypeNames[tagType]
if found == false {
log.Panicf("tag-type not valid: 0x%04x", tagType)
}
return TagType{
tagType: tagType,
name: name,
byteOrder: byteOrder,
}
}
func (tt TagType) String() string {
return fmt.Sprintf("TagType<NAME=[%s]>", tt.name)
}
func (tt TagType) Name() string {
return tt.name
}
func (tt TagType) Type() TagTypePrimitive {
return tt.tagType
}
func (tt TagType) ByteOrder() binary.ByteOrder {
return tt.byteOrder
}
// DEPRECATED(dustin): `(TagTypePrimitive).Size()` should be used, directly.
func (tt TagType) Size() int {
return tt.Type().Size()
}
// DEPRECATED(dustin): `(TagTypePrimitive).Size()` should be used, directly.
func TagTypeSize(tagType TagTypePrimitive) int {
// DEPRECATED(dustin): `(TagTypePrimitive).Size()` should be used, directly.
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.tagType.Size() * int(unitCount)) <= 4
}
func (tt TagType) readRawEncoded(valueContext ValueContext) (rawBytes []byte, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
unitSizeRaw := uint32(tt.tagType.Size())
if tt.valueIsEmbedded(valueContext.UnitCount()) == true {
byteLength := unitSizeRaw * valueContext.UnitCount()
return valueContext.RawValueOffset()[:byteLength], nil
} else {
return valueContext.AddressableData()[valueContext.ValueOffset() : valueContext.ValueOffset()+valueContext.UnitCount()*unitSizeRaw], nil
}
}
func (tt TagType) ParseBytes(data []byte, unitCount uint32) (value []uint8, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.tagType != TypeByte {
log.Panic(ErrWrongType)
}
count := int(unitCount)
if len(data) < (tt.tagType.Size() * count) {
log.Panic(ErrNotEnoughData)
}
value = []uint8(data[:count])
return value, nil
}
// ParseAscii returns a string and auto-strips the trailing NUL character.
func (tt TagType) ParseAscii(data []byte, unitCount uint32) (value string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.tagType != TypeAscii && tt.tagType != TypeAsciiNoNul {
log.Panic(ErrWrongType)
}
count := int(unitCount)
if len(data) < (tt.tagType.Size() * count) {
log.Panic(ErrNotEnoughData)
}
if len(data) == 0 || data[count-1] != 0 {
s := string(data[:count])
typeLogger.Warningf(nil, "ascii not terminated with nul as expected: [%v]", s)
return s, nil
} else {
// Auto-strip the NUL from the end. It serves no purpose outside of
// encoding semantics.
return string(data[:count-1]), nil
}
}
// ParseAsciiNoNul returns a string without any consideration for a trailing NUL
// character.
func (tt TagType) ParseAsciiNoNul(data []byte, unitCount uint32) (value string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.tagType != TypeAscii && tt.tagType != TypeAsciiNoNul {
log.Panic(ErrWrongType)
}
count := int(unitCount)
if len(data) < (tt.tagType.Size() * count) {
log.Panic(ErrNotEnoughData)
}
return string(data[:count]), nil
}
func (tt TagType) ParseShorts(data []byte, unitCount uint32) (value []uint16, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.tagType != TypeShort {
log.Panic(ErrWrongType)
}
count := int(unitCount)
if len(data) < (tt.tagType.Size() * count) {
log.Panic(ErrNotEnoughData)
}
value = make([]uint16, count)
for i := 0; i < count; i++ {
if tt.byteOrder == binary.BigEndian {
value[i] = binary.BigEndian.Uint16(data[i*2:])
} else {
value[i] = binary.LittleEndian.Uint16(data[i*2:])
}
}
return value, nil
}
func (tt TagType) ParseLongs(data []byte, unitCount uint32) (value []uint32, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.tagType != TypeLong {
log.Panic(ErrWrongType)
}
count := int(unitCount)
if len(data) < (tt.tagType.Size() * count) {
log.Panic(ErrNotEnoughData)
}
value = make([]uint32, count)
for i := 0; i < count; i++ {
if tt.byteOrder == binary.BigEndian {
value[i] = binary.BigEndian.Uint32(data[i*4:])
} else {
value[i] = binary.LittleEndian.Uint32(data[i*4:])
}
}
return value, nil
}
func (tt TagType) ParseRationals(data []byte, unitCount uint32) (value []Rational, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.tagType != TypeRational {
log.Panic(ErrWrongType)
}
count := int(unitCount)
if len(data) < (tt.tagType.Size() * count) {
log.Panic(ErrNotEnoughData)
}
value = make([]Rational, count)
for i := 0; i < count; i++ {
if tt.byteOrder == binary.BigEndian {
value[i].Numerator = binary.BigEndian.Uint32(data[i*8:])
value[i].Denominator = binary.BigEndian.Uint32(data[i*8+4:])
} else {
value[i].Numerator = binary.LittleEndian.Uint32(data[i*8:])
value[i].Denominator = binary.LittleEndian.Uint32(data[i*8+4:])
}
}
return value, nil
}
func (tt TagType) ParseSignedLongs(data []byte, unitCount uint32) (value []int32, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.tagType != TypeSignedLong {
log.Panic(ErrWrongType)
}
count := int(unitCount)
if len(data) < (tt.tagType.Size() * count) {
log.Panic(ErrNotEnoughData)
}
b := bytes.NewBuffer(data)
value = make([]int32, count)
for i := 0; i < count; i++ {
if tt.byteOrder == binary.BigEndian {
err := binary.Read(b, binary.BigEndian, &value[i])
log.PanicIf(err)
} else {
err := binary.Read(b, binary.LittleEndian, &value[i])
log.PanicIf(err)
}
}
return value, nil
}
func (tt TagType) ParseSignedRationals(data []byte, unitCount uint32) (value []SignedRational, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.tagType != TypeSignedRational {
log.Panic(ErrWrongType)
}
count := int(unitCount)
if len(data) < (tt.tagType.Size() * count) {
log.Panic(ErrNotEnoughData)
}
b := bytes.NewBuffer(data)
value = make([]SignedRational, count)
for i := 0; i < count; i++ {
if tt.byteOrder == binary.BigEndian {
err = binary.Read(b, binary.BigEndian, &value[i].Numerator)
log.PanicIf(err)
err = binary.Read(b, binary.BigEndian, &value[i].Denominator)
log.PanicIf(err)
} else {
err = binary.Read(b, binary.LittleEndian, &value[i].Numerator)
log.PanicIf(err)
err = binary.Read(b, binary.LittleEndian, &value[i].Denominator)
log.PanicIf(err)
}
}
return value, nil
}
func (tt TagType) ReadByteValues(valueContext ValueContext) (value []byte, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.valueIsEmbedded(valueContext.UnitCount()) == true {
typeLogger.Debugf(nil, "Reading BYTE value (embedded).")
// In this case, the bytes normally used for the offset are actually
// data.
byteLength := uint32(tt.tagType.Size()) * valueContext.UnitCount()
rawValue := valueContext.RawValueOffset()[:byteLength]
value, err = tt.ParseBytes(rawValue, valueContext.UnitCount())
log.PanicIf(err)
} else {
typeLogger.Debugf(nil, "Reading BYTE value (at offset).")
value, err = tt.ParseBytes(valueContext.AddressableData()[valueContext.ValueOffset():], valueContext.UnitCount())
log.PanicIf(err)
}
return value, nil
}
func (tt TagType) ReadAsciiValue(valueContext ValueContext) (value string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.valueIsEmbedded(valueContext.UnitCount()) == true {
typeLogger.Debugf(nil, "Reading ASCII value (embedded).")
byteLength := uint32(tt.tagType.Size()) * valueContext.UnitCount()
rawValue := valueContext.RawValueOffset()[:byteLength]
value, err = tt.ParseAscii(rawValue, valueContext.UnitCount())
log.PanicIf(err)
} else {
typeLogger.Debugf(nil, "Reading ASCII value (at offset).")
value, err = tt.ParseAscii(valueContext.AddressableData()[valueContext.ValueOffset():], valueContext.UnitCount())
log.PanicIf(err)
}
return value, nil
}
func (tt TagType) ReadAsciiNoNulValue(valueContext ValueContext) (value string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.valueIsEmbedded(valueContext.UnitCount()) == true {
typeLogger.Debugf(nil, "Reading ASCII value (no-nul; embedded).")
byteLength := uint32(tt.tagType.Size()) * valueContext.UnitCount()
rawValue := valueContext.RawValueOffset()[:byteLength]
value, err = tt.ParseAsciiNoNul(rawValue, valueContext.UnitCount())
log.PanicIf(err)
} else {
typeLogger.Debugf(nil, "Reading ASCII value (no-nul; at offset).")
value, err = tt.ParseAsciiNoNul(valueContext.AddressableData()[valueContext.ValueOffset():], valueContext.UnitCount())
log.PanicIf(err)
}
return value, nil
}
func (tt TagType) ReadShortValues(valueContext ValueContext) (value []uint16, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.valueIsEmbedded(valueContext.UnitCount()) == true {
typeLogger.Debugf(nil, "Reading SHORT value (embedded).")
byteLength := uint32(tt.tagType.Size()) * valueContext.UnitCount()
rawValue := valueContext.RawValueOffset()[:byteLength]
value, err = tt.ParseShorts(rawValue, valueContext.UnitCount())
log.PanicIf(err)
} else {
typeLogger.Debugf(nil, "Reading SHORT value (at offset).")
value, err = tt.ParseShorts(valueContext.AddressableData()[valueContext.ValueOffset():], valueContext.UnitCount())
log.PanicIf(err)
}
return value, nil
}
func (tt TagType) ReadLongValues(valueContext ValueContext) (value []uint32, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.valueIsEmbedded(valueContext.UnitCount()) == true {
typeLogger.Debugf(nil, "Reading LONG value (embedded).")
byteLength := uint32(tt.tagType.Size()) * valueContext.UnitCount()
rawValue := valueContext.RawValueOffset()[:byteLength]
value, err = tt.ParseLongs(rawValue, valueContext.UnitCount())
log.PanicIf(err)
} else {
typeLogger.Debugf(nil, "Reading LONG value (at offset).")
value, err = tt.ParseLongs(valueContext.AddressableData()[valueContext.ValueOffset():], valueContext.UnitCount())
log.PanicIf(err)
}
return value, nil
}
func (tt TagType) ReadRationalValues(valueContext ValueContext) (value []Rational, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.valueIsEmbedded(valueContext.UnitCount()) == true {
typeLogger.Debugf(nil, "Reading RATIONAL value (embedded).")
byteLength := uint32(tt.tagType.Size()) * valueContext.UnitCount()
rawValue := valueContext.RawValueOffset()[:byteLength]
value, err = tt.ParseRationals(rawValue, valueContext.UnitCount())
log.PanicIf(err)
} else {
typeLogger.Debugf(nil, "Reading RATIONAL value (at offset).")
value, err = tt.ParseRationals(valueContext.AddressableData()[valueContext.ValueOffset():], valueContext.UnitCount())
log.PanicIf(err)
}
return value, nil
}
func (tt TagType) ReadSignedLongValues(valueContext ValueContext) (value []int32, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.valueIsEmbedded(valueContext.UnitCount()) == true {
typeLogger.Debugf(nil, "Reading SLONG value (embedded).")
byteLength := uint32(tt.tagType.Size()) * valueContext.UnitCount()
rawValue := valueContext.RawValueOffset()[:byteLength]
value, err = tt.ParseSignedLongs(rawValue, valueContext.UnitCount())
log.PanicIf(err)
} else {
typeLogger.Debugf(nil, "Reading SLONG value (at offset).")
value, err = tt.ParseSignedLongs(valueContext.AddressableData()[valueContext.ValueOffset():], valueContext.UnitCount())
log.PanicIf(err)
}
return value, nil
}
func (tt TagType) ReadSignedRationalValues(valueContext ValueContext) (value []SignedRational, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.valueIsEmbedded(valueContext.UnitCount()) == true {
typeLogger.Debugf(nil, "Reading SRATIONAL value (embedded).")
byteLength := uint32(tt.tagType.Size()) * valueContext.UnitCount()
rawValue := valueContext.RawValueOffset()[:byteLength]
value, err = tt.ParseSignedRationals(rawValue, valueContext.UnitCount())
log.PanicIf(err)
} else {
typeLogger.Debugf(nil, "Reading SRATIONAL value (at offset).")
value, err = tt.ParseSignedRationals(valueContext.AddressableData()[valueContext.ValueOffset():], valueContext.UnitCount())
log.PanicIf(err)
}
return value, nil
}
// ResolveAsString resolves the given value and returns a flat string.
//
// 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 unknown-type tags (e.g.
// byte-order, tag-ID, IFD type), it will return an error if attempted. See
// `UndefinedValue()`.
func (tt TagType) ResolveAsString(valueContext ValueContext, justFirst bool) (value string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
rawBytes, err := tt.readRawEncoded(valueContext)
log.PanicIf(err)
valueString, err := tt.Format(rawBytes, justFirst)
log.PanicIf(err)
return valueString, nil
}
// Format returns a stringified value for the given bytes. Automatically
// calculates count based on type size.
func (tt TagType) Format(rawBytes []byte, justFirst bool) (value string, err error) {
func Format(rawBytes []byte, tagType TagTypePrimitive, justFirst bool, byteOrder binary.ByteOrder) (value string, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
@ -641,11 +119,10 @@ func (tt TagType) Format(rawBytes []byte, justFirst bool) (value string, err err
// TODO(dustin): !! Add tests
typeId := tt.Type()
typeSize := typeId.Size()
typeSize := tagType.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)
log.Panicf("byte-count (%d) does not align for [%s] type with a size of (%d) bytes", len(rawBytes), TypeNames[tagType], typeSize)
}
// unitCount is the calculated unit-count. This should equal the original
@ -655,28 +132,28 @@ func (tt TagType) Format(rawBytes []byte, justFirst bool) (value string, err err
// Truncate the items if it's not bytes or a string and we just want the first.
valueSuffix := ""
if justFirst == true && unitCount > 1 && typeId != TypeByte && typeId != TypeAscii && typeId != TypeAsciiNoNul {
if justFirst == true && unitCount > 1 && tagType != TypeByte && tagType != TypeAscii && tagType != TypeAsciiNoNul {
unitCount = 1
valueSuffix = "..."
}
if typeId == TypeByte {
items, err := tt.ParseBytes(rawBytes, unitCount)
if tagType == TypeByte {
items, err := parser.ParseBytes(rawBytes, unitCount)
log.PanicIf(err)
return DumpBytesToString(items), nil
} else if typeId == TypeAscii {
phrase, err := tt.ParseAscii(rawBytes, unitCount)
} else if tagType == TypeAscii {
phrase, err := parser.ParseAscii(rawBytes, unitCount)
log.PanicIf(err)
return phrase, nil
} else if typeId == TypeAsciiNoNul {
phrase, err := tt.ParseAsciiNoNul(rawBytes, unitCount)
} else if tagType == TypeAsciiNoNul {
phrase, err := parser.ParseAsciiNoNul(rawBytes, unitCount)
log.PanicIf(err)
return phrase, nil
} else if typeId == TypeShort {
items, err := tt.ParseShorts(rawBytes, unitCount)
} else if tagType == TypeShort {
items, err := parser.ParseShorts(rawBytes, unitCount, byteOrder)
log.PanicIf(err)
if len(items) > 0 {
@ -688,8 +165,8 @@ func (tt TagType) Format(rawBytes []byte, justFirst bool) (value string, err err
} else {
return "", nil
}
} else if typeId == TypeLong {
items, err := tt.ParseLongs(rawBytes, unitCount)
} else if tagType == TypeLong {
items, err := parser.ParseLongs(rawBytes, unitCount, byteOrder)
log.PanicIf(err)
if len(items) > 0 {
@ -701,8 +178,8 @@ func (tt TagType) Format(rawBytes []byte, justFirst bool) (value string, err err
} else {
return "", nil
}
} else if typeId == TypeRational {
items, err := tt.ParseRationals(rawBytes, unitCount)
} else if tagType == TypeRational {
items, err := parser.ParseRationals(rawBytes, unitCount, byteOrder)
log.PanicIf(err)
if len(items) > 0 {
@ -719,8 +196,8 @@ func (tt TagType) Format(rawBytes []byte, justFirst bool) (value string, err err
} else {
return "", nil
}
} else if typeId == TypeSignedLong {
items, err := tt.ParseSignedLongs(rawBytes, unitCount)
} else if tagType == TypeSignedLong {
items, err := parser.ParseSignedLongs(rawBytes, unitCount, byteOrder)
log.PanicIf(err)
if len(items) > 0 {
@ -732,8 +209,8 @@ func (tt TagType) Format(rawBytes []byte, justFirst bool) (value string, err err
} else {
return "", nil
}
} else if typeId == TypeSignedRational {
items, err := tt.ParseSignedRationals(rawBytes, unitCount)
} else if tagType == TypeSignedRational {
items, err := parser.ParseSignedRationals(rawBytes, unitCount, byteOrder)
log.PanicIf(err)
parts := make([]string, len(items))
@ -752,113 +229,44 @@ func (tt TagType) Format(rawBytes []byte, justFirst bool) (value string, err err
}
} else {
// Affects only "unknown" values, in general.
log.Panicf("value of type (%d) [%s] can not be formatted into string", typeId, tt)
log.Panicf("value of type [%s] can not be formatted into string", tagType.String())
// Never called.
return "", nil
}
}
// Value knows how to resolve the given value.
//
// 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
// `UndefinedValue()`.
func (tt TagType) Resolve(valueContext ValueContext) (value interface{}, err error) {
func EncodeStringToBytes(tagType TagTypePrimitive, valueString string) (value interface{}, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
typeId := tt.Type()
if typeId == TypeByte {
value, err = tt.ReadByteValues(valueContext)
log.PanicIf(err)
} else if typeId == TypeAscii {
value, err = tt.ReadAsciiValue(valueContext)
log.PanicIf(err)
} else if typeId == TypeAsciiNoNul {
value, err = tt.ReadAsciiNoNulValue(valueContext)
log.PanicIf(err)
} else if typeId == TypeShort {
value, err = tt.ReadShortValues(valueContext)
log.PanicIf(err)
} else if typeId == TypeLong {
value, err = tt.ReadLongValues(valueContext)
log.PanicIf(err)
} else if typeId == TypeRational {
value, err = tt.ReadRationalValues(valueContext)
log.PanicIf(err)
} else if typeId == TypeSignedLong {
value, err = tt.ReadSignedLongValues(valueContext)
log.PanicIf(err)
} else if typeId == TypeSignedRational {
value, err = tt.ReadSignedRationalValues(valueContext)
log.PanicIf(err)
} else if typeId == TypeUndefined {
log.Panicf("will not parse unknown-type value: %v", tt)
// Never called.
return nil, nil
} else {
log.Panicf("value of type (%d) [%s] is unparseable", typeId, tt)
// Never called.
return nil, nil
}
return value, nil
}
// Encode knows how to encode the given value to a byte slice.
func (tt TagType) Encode(value interface{}) (encoded []byte, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
ve := NewValueEncoder(tt.byteOrder)
ed, err := ve.EncodeWithType(tt, value)
log.PanicIf(err)
return ed.Encoded, err
}
func (tt TagType) FromString(valueString string) (value interface{}, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if tt.tagType == TypeUndefined {
if tagType == TypeUndefined {
// TODO(dustin): Circle back to this.
log.Panicf("undefined-type values are not supported")
}
if tt.tagType == TypeByte {
if tagType == TypeByte {
return []byte(valueString), nil
} else if tt.tagType == TypeAscii || tt.tagType == TypeAsciiNoNul {
} else if tagType == TypeAscii || tagType == TypeAsciiNoNul {
// Whether or not we're putting an NUL on the end is only relevant for
// byte-level encoding. This function really just supports a user
// interface.
return valueString, nil
} else if tt.tagType == TypeShort {
} else if tagType == TypeShort {
n, err := strconv.ParseUint(valueString, 10, 16)
log.PanicIf(err)
return uint16(n), nil
} else if tt.tagType == TypeLong {
} else if tagType == TypeLong {
n, err := strconv.ParseUint(valueString, 10, 32)
log.PanicIf(err)
return uint32(n), nil
} else if tt.tagType == TypeRational {
} else if tagType == TypeRational {
parts := strings.SplitN(valueString, "/", 2)
numerator, err := strconv.ParseUint(parts[0], 10, 32)
@ -871,12 +279,12 @@ func (tt TagType) FromString(valueString string) (value interface{}, err error)
Numerator: uint32(numerator),
Denominator: uint32(denominator),
}, nil
} else if tt.tagType == TypeSignedLong {
} else if tagType == TypeSignedLong {
n, err := strconv.ParseInt(valueString, 10, 32)
log.PanicIf(err)
return int32(n), nil
} else if tt.tagType == TypeSignedRational {
} else if tagType == TypeSignedRational {
parts := strings.SplitN(valueString, "/", 2)
numerator, err := strconv.ParseInt(parts[0], 10, 32)
@ -891,7 +299,7 @@ func (tt TagType) FromString(valueString string) (value interface{}, err error)
}, nil
}
log.Panicf("from-string encoding for type not supported; this shouldn't happen: (%d)", tt.Type())
log.Panicf("from-string encoding for type not supported; this shouldn't happen: [%s]", tagType.String())
return nil, nil
}

View File

@ -2,6 +2,12 @@ package exif
import (
"encoding/binary"
"github.com/dsoprea/go-logging"
)
var (
parser *Parser
)
// ValueContext describes all of the parameters required to find and extract
@ -14,26 +20,18 @@ type ValueContext struct {
tagType TagTypePrimitive
byteOrder binary.ByteOrder
// undefinedValueTagType is the effective type to use if this is an "undefined"
// value.
undefinedValueTagType TagTypePrimitive
// undefinedValueUnitCount is the effective unit-count to use if this is an
// "undefined" value.
undefinedValueUnitCount uint32
}
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 newValueContext(unitCount, valueOffset uint32, rawValueOffset, addressableData []byte, tagType TagTypePrimitive, byteOrder binary.ByteOrder) ValueContext {
return ValueContext{
func newValueContext(unitCount, valueOffset uint32, rawValueOffset, addressableData []byte, tagType TagTypePrimitive, byteOrder binary.ByteOrder) *ValueContext {
return &ValueContext{
unitCount: unitCount,
valueOffset: valueOffset,
rawValueOffset: rawValueOffset,
@ -43,3 +41,295 @@ func newValueContext(unitCount, valueOffset uint32, rawValueOffset, addressableD
byteOrder: byteOrder,
}
}
func (vc *ValueContext) SetUnknownValueParameters(tagType TagTypePrimitive, unitCount uint32) {
vc.undefinedValueTagType = tagType
vc.undefinedValueUnitCount = unitCount
}
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, unitCount := vc.effectiveValueParameters()
return (tagType.Size() * int(unitCount)) <= 4
}
func (vc *ValueContext) effectiveValueParameters() (tagType TagTypePrimitive, unitCount uint32) {
if vc.tagType == TypeUndefined {
tagType = vc.undefinedValueTagType
unitCount = vc.undefinedValueUnitCount
if tagType == 0 {
log.Panicf("undefined-value type not set")
}
} else {
tagType = vc.tagType
unitCount = vc.unitCount
}
return tagType, unitCount
}
func (vc *ValueContext) readRawEncoded() (rawBytes []byte, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
tagType, unitCount := vc.effectiveValueParameters()
unitSizeRaw := uint32(tagType.Size())
if vc.isEmbedded() == true {
byteLength := unitSizeRaw * unitCount
return vc.rawValueOffset[:byteLength], nil
} else {
return vc.addressableData[vc.valueOffset : vc.valueOffset+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
// `UndefinedValue()`.
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
// `UndefinedValue()`.
func (vc *ValueContext) Values() (value interface{}, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if vc.tagType == TypeByte {
value, err = vc.ReadBytes()
log.PanicIf(err)
} else if vc.tagType == TypeAscii {
value, err = vc.ReadAscii()
log.PanicIf(err)
} else if vc.tagType == TypeAsciiNoNul {
value, err = vc.ReadAsciiNoNul()
log.PanicIf(err)
} else if vc.tagType == TypeShort {
value, err = vc.ReadShorts()
log.PanicIf(err)
} else if vc.tagType == TypeLong {
value, err = vc.ReadLongs()
log.PanicIf(err)
} else if vc.tagType == TypeRational {
value, err = vc.ReadRationals()
log.PanicIf(err)
} else if vc.tagType == TypeSignedLong {
value, err = vc.ReadSignedLongs()
log.PanicIf(err)
} else if vc.tagType == TypeSignedRational {
value, 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 value, nil
}
func init() {
parser = &Parser{}
}