mirror of https://github.com/dsoprea/go-exif.git
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
parent
7fb09bbf9f
commit
69821c417f
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
684
type.go
|
@ -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
|
||||
}
|
||||
|
||||
|
|
326
value_context.go
326
value_context.go
|
@ -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{}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue