mirror of https://github.com/dsoprea/go-exif.git
type: Added encoding and complete encode/parse tests.
parent
5864c10c03
commit
53f74d18a1
623
type.go
623
type.go
|
@ -1,13 +1,7 @@
|
|||
package exif
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -38,11 +32,6 @@ var (
|
|||
TypeAsciiNoNul: "_ASCII_NO_NUL",
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
typeLogger = log.NewLogger("exif.type")
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrCantDetermineTagValueSize is used when we're trying to determine a
|
||||
//size for a non-standard/undefined type.
|
||||
|
@ -63,6 +52,8 @@ var (
|
|||
)
|
||||
|
||||
|
||||
// TODO(dustin): !! Replace our implementation of an endian descriptor to just pass around binary.BigEndian and binary.LittleEndian
|
||||
|
||||
const (
|
||||
BigEndianByteOrder = iota
|
||||
LittleEndianByteOrder = iota
|
||||
|
@ -88,613 +79,3 @@ type SignedRational struct {
|
|||
Numerator int32
|
||||
Denominator int32
|
||||
}
|
||||
|
||||
|
||||
type TagType struct {
|
||||
tagType uint16
|
||||
name string
|
||||
byteOrder IfdByteOrder
|
||||
}
|
||||
|
||||
func NewTagType(tagType uint16, byteOrder IfdByteOrder) 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() uint16 {
|
||||
return tt.tagType
|
||||
}
|
||||
|
||||
func (tt TagType) ByteOrder() IfdByteOrder {
|
||||
return tt.byteOrder
|
||||
}
|
||||
|
||||
|
||||
func (tt TagType) Size() int {
|
||||
if tt.tagType == TypeByte {
|
||||
return 1
|
||||
} else if tt.tagType == TypeAscii || tt.tagType == TypeAsciiNoNul {
|
||||
return 1
|
||||
} else if tt.tagType == TypeShort {
|
||||
return 2
|
||||
} else if tt.tagType == TypeLong {
|
||||
return 4
|
||||
} else if tt.tagType == TypeRational {
|
||||
return 8
|
||||
} else if tt.tagType == TypeSignedLong {
|
||||
return 4
|
||||
} else if tt.tagType == TypeSignedRational {
|
||||
return 8
|
||||
} else {
|
||||
log.Panic(ErrCantDetermineTagValueSize)
|
||||
|
||||
// Never called.
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// ValueIsEmbedded will return a boolean indicating whether the value should be
|
||||
// found directly within the IFD entry or an offset to somewhere else.
|
||||
func (tt TagType) ValueIsEmbedded(unitCount uint32) bool {
|
||||
return (tt.Size() * int(unitCount)) <= 4
|
||||
}
|
||||
|
||||
func (tt TagType) ParseBytes(data []byte, rawCount 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(rawCount)
|
||||
|
||||
if len(data) < (tt.Size() * count) {
|
||||
log.Panic(ErrNotEnoughData)
|
||||
}
|
||||
|
||||
value = make([]uint8, count)
|
||||
for i := 0; i < count; i++ {
|
||||
value[i] = uint8(data[i])
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (tt TagType) ParseAscii(data []byte, rawCount 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(rawCount)
|
||||
|
||||
if len(data) < (tt.Size() * count) {
|
||||
log.Panic(ErrNotEnoughData)
|
||||
}
|
||||
|
||||
return string(data[:count]), nil
|
||||
}
|
||||
|
||||
func (tt TagType) ParseShorts(data []byte, rawCount 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(rawCount)
|
||||
|
||||
if len(data) < (tt.Size() * count) {
|
||||
log.Panic(ErrNotEnoughData)
|
||||
}
|
||||
|
||||
value = make([]uint16, count)
|
||||
for i := 0; i < count; i++ {
|
||||
if tt.byteOrder.IsBigEndian() {
|
||||
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, rawCount 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(rawCount)
|
||||
|
||||
if len(data) < (tt.Size() * count) {
|
||||
log.Panic(ErrNotEnoughData)
|
||||
}
|
||||
|
||||
value = make([]uint32, count)
|
||||
for i := 0; i < count; i++ {
|
||||
if tt.byteOrder.IsBigEndian() {
|
||||
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, rawCount 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(rawCount)
|
||||
|
||||
if len(data) < (tt.Size() * count) {
|
||||
log.Panic(ErrNotEnoughData)
|
||||
}
|
||||
|
||||
value = make([]Rational, count)
|
||||
for i := 0; i < count; i++ {
|
||||
if tt.byteOrder.IsBigEndian() {
|
||||
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, rawCount 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(rawCount)
|
||||
|
||||
if len(data) < (tt.Size() * count) {
|
||||
log.Panic(ErrNotEnoughData)
|
||||
}
|
||||
|
||||
b := bytes.NewBuffer(data)
|
||||
|
||||
value = make([]int32, count)
|
||||
for i := 0; i < count; i++ {
|
||||
if tt.byteOrder.IsBigEndian() {
|
||||
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, rawCount 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(rawCount)
|
||||
|
||||
if len(data) < (tt.Size() * count) {
|
||||
log.Panic(ErrNotEnoughData)
|
||||
}
|
||||
|
||||
b := bytes.NewBuffer(data)
|
||||
|
||||
value = make([]SignedRational, count)
|
||||
for i := 0; i < count; i++ {
|
||||
if tt.byteOrder.IsBigEndian() {
|
||||
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).")
|
||||
|
||||
value, err = tt.ParseBytes(valueContext.RawValueOffset, valueContext.UnitCount)
|
||||
log.PanicIf(err)
|
||||
} else {
|
||||
typeLogger.Debugf(nil, "Reading BYTE value (at offset).")
|
||||
|
||||
value, err = tt.ParseBytes(valueContext.RawExif[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))
|
||||
}
|
||||
}()
|
||||
|
||||
value, err = tt.ReadAsciiNoNulValue(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
len_ := len(value)
|
||||
if len_ == 0 || value[len_ - 1] != 0 {
|
||||
typeLogger.Warningf(nil, "ascii value not terminated with nul: [%s]", value)
|
||||
return value, nil
|
||||
} else {
|
||||
return value[:len_ - 1], 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).")
|
||||
|
||||
value, err = tt.ParseAscii(valueContext.RawValueOffset, valueContext.UnitCount)
|
||||
log.PanicIf(err)
|
||||
} else {
|
||||
typeLogger.Debugf(nil, "Reading ASCII value (no-nul; at offset).")
|
||||
|
||||
value, err = tt.ParseAscii(valueContext.RawExif[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).")
|
||||
|
||||
value, err = tt.ParseShorts(valueContext.RawValueOffset, valueContext.UnitCount)
|
||||
log.PanicIf(err)
|
||||
} else {
|
||||
typeLogger.Debugf(nil, "Reading SHORT value (at offset).")
|
||||
|
||||
value, err = tt.ParseShorts(valueContext.RawExif[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).")
|
||||
|
||||
value, err = tt.ParseLongs(valueContext.RawValueOffset, valueContext.UnitCount)
|
||||
log.PanicIf(err)
|
||||
} else {
|
||||
typeLogger.Debugf(nil, "Reading LONG value (at offset).")
|
||||
|
||||
value, err = tt.ParseLongs(valueContext.RawExif[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).")
|
||||
|
||||
value, err = tt.ParseRationals(valueContext.RawValueOffset, valueContext.UnitCount)
|
||||
log.PanicIf(err)
|
||||
} else {
|
||||
typeLogger.Debugf(nil, "Reading RATIONAL value (at offset).")
|
||||
|
||||
value, err = tt.ParseRationals(valueContext.RawExif[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).")
|
||||
|
||||
value, err = tt.ParseSignedLongs(valueContext.RawValueOffset, valueContext.UnitCount)
|
||||
log.PanicIf(err)
|
||||
} else {
|
||||
typeLogger.Debugf(nil, "Reading SLONG value (at offset).")
|
||||
|
||||
value, err = tt.ParseSignedLongs(valueContext.RawExif[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).")
|
||||
|
||||
value, err = tt.ParseSignedRationals(valueContext.RawValueOffset, valueContext.UnitCount)
|
||||
log.PanicIf(err)
|
||||
} else {
|
||||
typeLogger.Debugf(nil, "Reading SRATIONAL value (at offset).")
|
||||
|
||||
value, err = tt.ParseSignedRationals(valueContext.RawExif[valueContext.ValueOffset:], valueContext.UnitCount)
|
||||
log.PanicIf(err)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// ValueString extracts and parses 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).
|
||||
func (tt TagType) ValueString(valueContext ValueContext, justFirst bool) (value string, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
if tt.Type() == TypeByte {
|
||||
raw, err := tt.ReadByteValues(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
if justFirst == false {
|
||||
return fmt.Sprintf("%v", raw), nil
|
||||
} else if valueContext.UnitCount > 0 {
|
||||
return fmt.Sprintf("%v", raw[0]), nil
|
||||
} else {
|
||||
return "", nil
|
||||
}
|
||||
} else if tt.Type() == TypeAscii {
|
||||
raw, err := tt.ReadAsciiValue(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
return fmt.Sprintf("%s", raw), nil
|
||||
} else if tt.Type() == TypeAsciiNoNul {
|
||||
raw, err := tt.ReadAsciiNoNulValue(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
return fmt.Sprintf("%s", raw), nil
|
||||
} else if tt.Type() == TypeShort {
|
||||
raw, err := tt.ReadShortValues(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
if justFirst == false {
|
||||
return fmt.Sprintf("%v", raw), nil
|
||||
} else if valueContext.UnitCount > 0 {
|
||||
return fmt.Sprintf("%v", raw[0]), nil
|
||||
} else {
|
||||
return "", nil
|
||||
}
|
||||
} else if tt.Type() == TypeLong {
|
||||
raw, err := tt.ReadLongValues(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
if justFirst == false {
|
||||
return fmt.Sprintf("%v", raw), nil
|
||||
} else if valueContext.UnitCount > 0 {
|
||||
return fmt.Sprintf("%v", raw[0]), nil
|
||||
} else {
|
||||
return "", nil
|
||||
}
|
||||
} else if tt.Type() == TypeRational {
|
||||
raw, err := tt.ReadRationalValues(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
parts := make([]string, len(raw))
|
||||
for i, r := range raw {
|
||||
parts[i] = fmt.Sprintf("%d/%d", r.Numerator, r.Denominator)
|
||||
}
|
||||
|
||||
if justFirst == false {
|
||||
return fmt.Sprintf("%v", parts), nil
|
||||
} else if valueContext.UnitCount > 0 {
|
||||
return parts[0], nil
|
||||
} else {
|
||||
return "", nil
|
||||
}
|
||||
} else if tt.Type() == TypeSignedLong {
|
||||
raw, err := tt.ReadSignedLongValues(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
if justFirst == false {
|
||||
return fmt.Sprintf("%v", raw), nil
|
||||
} else if valueContext.UnitCount > 0 {
|
||||
return fmt.Sprintf("%v", raw[0]), nil
|
||||
} else {
|
||||
return "", nil
|
||||
}
|
||||
} else if tt.Type() == TypeSignedRational {
|
||||
raw, err := tt.ReadSignedRationalValues(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
parts := make([]string, len(raw))
|
||||
for i, r := range raw {
|
||||
parts[i] = fmt.Sprintf("%d/%d", r.Numerator, r.Denominator)
|
||||
}
|
||||
|
||||
if justFirst == false {
|
||||
return fmt.Sprintf("%v", raw), nil
|
||||
} else if valueContext.UnitCount > 0 {
|
||||
return parts[0], nil
|
||||
} else {
|
||||
return "", nil
|
||||
}
|
||||
} else {
|
||||
log.Panicf("value of type (%d) [%s] is unparseable", tt.Type(), tt)
|
||||
|
||||
// Never called.
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
// UndefinedValue returns the value for a tag of "undefined" type.
|
||||
func UndefinedValue(indexedIfdName string, tagId uint16, valueContext ValueContext, byteOrder IfdByteOrder) (value interface{}, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
if indexedIfdName == IfdName(IfdExif, 0) {
|
||||
if tagId == 0x9000 {
|
||||
tt := NewTagType(TypeAsciiNoNul, byteOrder)
|
||||
|
||||
value, err = tt.ReadAsciiValue(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
return value, nil
|
||||
} else if tagId == 0xa000 {
|
||||
tt := NewTagType(TypeAsciiNoNul, byteOrder)
|
||||
|
||||
value, err = tt.ReadAsciiValue(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
return value, nil
|
||||
}
|
||||
} else if indexedIfdName == IfdName(IfdGps, 0) {
|
||||
if tagId == 0x001c {
|
||||
// GPSAreaInformation
|
||||
|
||||
tt := NewTagType(TypeAsciiNoNul, byteOrder)
|
||||
|
||||
value, err = tt.ReadAsciiValue(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
return value, nil
|
||||
} else if tagId == 0x001b {
|
||||
// GPSProcessingMethod
|
||||
|
||||
tt := NewTagType(TypeAsciiNoNul, byteOrder)
|
||||
|
||||
value, err = tt.ReadAsciiValue(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(dustin): !! Still need to do:
|
||||
//
|
||||
// complex: 0xa302, 0xa20c, 0x8828
|
||||
// long: 0xa301, 0xa300
|
||||
// bytes: 0x927c, 0x9101 (probably, but not certain)
|
||||
// other: 0x9286 (simple, but needs some processing)
|
||||
|
||||
// 0xa40b is device-specific and unhandled.
|
||||
|
||||
|
||||
log.Panic(ErrUnhandledUnknownTypedTag)
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,652 @@
|
|||
package exif
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
)
|
||||
|
||||
var (
|
||||
typeDecodeLogger = log.NewLogger("exif.type_decode")
|
||||
)
|
||||
|
||||
|
||||
type TagType struct {
|
||||
tagType uint16
|
||||
name string
|
||||
byteOrder IfdByteOrder
|
||||
}
|
||||
|
||||
func NewTagType(tagType uint16, byteOrder IfdByteOrder) 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() uint16 {
|
||||
return tt.tagType
|
||||
}
|
||||
|
||||
func (tt TagType) ByteOrder() IfdByteOrder {
|
||||
return tt.byteOrder
|
||||
}
|
||||
|
||||
|
||||
func (tt TagType) Size() int {
|
||||
if tt.tagType == TypeByte {
|
||||
return 1
|
||||
} else if tt.tagType == TypeAscii || tt.tagType == TypeAsciiNoNul {
|
||||
return 1
|
||||
} else if tt.tagType == TypeShort {
|
||||
return 2
|
||||
} else if tt.tagType == TypeLong {
|
||||
return 4
|
||||
} else if tt.tagType == TypeRational {
|
||||
return 8
|
||||
} else if tt.tagType == TypeSignedLong {
|
||||
return 4
|
||||
} else if tt.tagType == TypeSignedRational {
|
||||
return 8
|
||||
} else {
|
||||
log.Panic(ErrCantDetermineTagValueSize)
|
||||
|
||||
// Never called.
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// ValueIsEmbedded will return a boolean indicating whether the value should be
|
||||
// found directly within the IFD entry or an offset to somewhere else.
|
||||
func (tt TagType) ValueIsEmbedded(unitCount uint32) bool {
|
||||
return (tt.Size() * int(unitCount)) <= 4
|
||||
}
|
||||
|
||||
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.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.Size() * count) {
|
||||
log.Panic(ErrNotEnoughData)
|
||||
}
|
||||
|
||||
if len(data) == 0 || data[count - 1] != 0 {
|
||||
typeDecodeLogger.Warningf(nil, "ascii not terminated with nul")
|
||||
return string(data[:count]), nil
|
||||
} else {
|
||||
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.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.Size() * count) {
|
||||
log.Panic(ErrNotEnoughData)
|
||||
}
|
||||
|
||||
value = make([]uint16, count)
|
||||
for i := 0; i < count; i++ {
|
||||
if tt.byteOrder.IsBigEndian() {
|
||||
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.Size() * count) {
|
||||
log.Panic(ErrNotEnoughData)
|
||||
}
|
||||
|
||||
value = make([]uint32, count)
|
||||
for i := 0; i < count; i++ {
|
||||
if tt.byteOrder.IsBigEndian() {
|
||||
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.Size() * count) {
|
||||
log.Panic(ErrNotEnoughData)
|
||||
}
|
||||
|
||||
value = make([]Rational, count)
|
||||
for i := 0; i < count; i++ {
|
||||
if tt.byteOrder.IsBigEndian() {
|
||||
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.Size() * count) {
|
||||
log.Panic(ErrNotEnoughData)
|
||||
}
|
||||
|
||||
b := bytes.NewBuffer(data)
|
||||
|
||||
value = make([]int32, count)
|
||||
for i := 0; i < count; i++ {
|
||||
if tt.byteOrder.IsBigEndian() {
|
||||
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.Size() * count) {
|
||||
log.Panic(ErrNotEnoughData)
|
||||
}
|
||||
|
||||
b := bytes.NewBuffer(data)
|
||||
|
||||
value = make([]SignedRational, count)
|
||||
for i := 0; i < count; i++ {
|
||||
if tt.byteOrder.IsBigEndian() {
|
||||
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 {
|
||||
typeDecodeLogger.Debugf(nil, "Reading BYTE value (embedded).")
|
||||
|
||||
value, err = tt.ParseBytes(valueContext.RawValueOffset, valueContext.UnitCount)
|
||||
log.PanicIf(err)
|
||||
} else {
|
||||
typeDecodeLogger.Debugf(nil, "Reading BYTE value (at offset).")
|
||||
|
||||
value, err = tt.ParseBytes(valueContext.RawExif[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 {
|
||||
typeDecodeLogger.Debugf(nil, "Reading ASCII value (no-nul; embedded).")
|
||||
|
||||
value, err = tt.ParseAscii(valueContext.RawValueOffset, valueContext.UnitCount)
|
||||
log.PanicIf(err)
|
||||
} else {
|
||||
typeDecodeLogger.Debugf(nil, "Reading ASCII value (no-nul; at offset).")
|
||||
|
||||
value, err = tt.ParseAscii(valueContext.RawExif[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 {
|
||||
typeDecodeLogger.Debugf(nil, "Reading ASCII value (no-nul; embedded).")
|
||||
|
||||
value, err = tt.ParseAsciiNoNul(valueContext.RawValueOffset, valueContext.UnitCount)
|
||||
log.PanicIf(err)
|
||||
} else {
|
||||
typeDecodeLogger.Debugf(nil, "Reading ASCII value (no-nul; at offset).")
|
||||
|
||||
value, err = tt.ParseAsciiNoNul(valueContext.RawExif[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 {
|
||||
typeDecodeLogger.Debugf(nil, "Reading SHORT value (embedded).")
|
||||
|
||||
value, err = tt.ParseShorts(valueContext.RawValueOffset, valueContext.UnitCount)
|
||||
log.PanicIf(err)
|
||||
} else {
|
||||
typeDecodeLogger.Debugf(nil, "Reading SHORT value (at offset).")
|
||||
|
||||
value, err = tt.ParseShorts(valueContext.RawExif[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 {
|
||||
typeDecodeLogger.Debugf(nil, "Reading LONG value (embedded).")
|
||||
|
||||
value, err = tt.ParseLongs(valueContext.RawValueOffset, valueContext.UnitCount)
|
||||
log.PanicIf(err)
|
||||
} else {
|
||||
typeDecodeLogger.Debugf(nil, "Reading LONG value (at offset).")
|
||||
|
||||
value, err = tt.ParseLongs(valueContext.RawExif[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 {
|
||||
typeDecodeLogger.Debugf(nil, "Reading RATIONAL value (embedded).")
|
||||
|
||||
value, err = tt.ParseRationals(valueContext.RawValueOffset, valueContext.UnitCount)
|
||||
log.PanicIf(err)
|
||||
} else {
|
||||
typeDecodeLogger.Debugf(nil, "Reading RATIONAL value (at offset).")
|
||||
|
||||
value, err = tt.ParseRationals(valueContext.RawExif[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 {
|
||||
typeDecodeLogger.Debugf(nil, "Reading SLONG value (embedded).")
|
||||
|
||||
value, err = tt.ParseSignedLongs(valueContext.RawValueOffset, valueContext.UnitCount)
|
||||
log.PanicIf(err)
|
||||
} else {
|
||||
typeDecodeLogger.Debugf(nil, "Reading SLONG value (at offset).")
|
||||
|
||||
value, err = tt.ParseSignedLongs(valueContext.RawExif[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 {
|
||||
typeDecodeLogger.Debugf(nil, "Reading SRATIONAL value (embedded).")
|
||||
|
||||
value, err = tt.ParseSignedRationals(valueContext.RawValueOffset, valueContext.UnitCount)
|
||||
log.PanicIf(err)
|
||||
} else {
|
||||
typeDecodeLogger.Debugf(nil, "Reading SRATIONAL value (at offset).")
|
||||
|
||||
value, err = tt.ParseSignedRationals(valueContext.RawExif[valueContext.ValueOffset:], valueContext.UnitCount)
|
||||
log.PanicIf(err)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// ValueString extracts and parses 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).
|
||||
func (tt TagType) ValueString(valueContext ValueContext, justFirst bool) (value string, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
if tt.Type() == TypeByte {
|
||||
raw, err := tt.ReadByteValues(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
if justFirst == false {
|
||||
return fmt.Sprintf("%v", raw), nil
|
||||
} else if valueContext.UnitCount > 0 {
|
||||
return fmt.Sprintf("%v", raw[0]), nil
|
||||
} else {
|
||||
return "", nil
|
||||
}
|
||||
} else if tt.Type() == TypeAscii {
|
||||
raw, err := tt.ReadAsciiValue(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
return fmt.Sprintf("%s", raw), nil
|
||||
} else if tt.Type() == TypeAsciiNoNul {
|
||||
raw, err := tt.ReadAsciiNoNulValue(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
return fmt.Sprintf("%s", raw), nil
|
||||
} else if tt.Type() == TypeShort {
|
||||
raw, err := tt.ReadShortValues(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
if justFirst == false {
|
||||
return fmt.Sprintf("%v", raw), nil
|
||||
} else if valueContext.UnitCount > 0 {
|
||||
return fmt.Sprintf("%v", raw[0]), nil
|
||||
} else {
|
||||
return "", nil
|
||||
}
|
||||
} else if tt.Type() == TypeLong {
|
||||
raw, err := tt.ReadLongValues(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
if justFirst == false {
|
||||
return fmt.Sprintf("%v", raw), nil
|
||||
} else if valueContext.UnitCount > 0 {
|
||||
return fmt.Sprintf("%v", raw[0]), nil
|
||||
} else {
|
||||
return "", nil
|
||||
}
|
||||
} else if tt.Type() == TypeRational {
|
||||
raw, err := tt.ReadRationalValues(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
parts := make([]string, len(raw))
|
||||
for i, r := range raw {
|
||||
parts[i] = fmt.Sprintf("%d/%d", r.Numerator, r.Denominator)
|
||||
}
|
||||
|
||||
if justFirst == false {
|
||||
return fmt.Sprintf("%v", parts), nil
|
||||
} else if valueContext.UnitCount > 0 {
|
||||
return parts[0], nil
|
||||
} else {
|
||||
return "", nil
|
||||
}
|
||||
} else if tt.Type() == TypeSignedLong {
|
||||
raw, err := tt.ReadSignedLongValues(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
if justFirst == false {
|
||||
return fmt.Sprintf("%v", raw), nil
|
||||
} else if valueContext.UnitCount > 0 {
|
||||
return fmt.Sprintf("%v", raw[0]), nil
|
||||
} else {
|
||||
return "", nil
|
||||
}
|
||||
} else if tt.Type() == TypeSignedRational {
|
||||
raw, err := tt.ReadSignedRationalValues(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
parts := make([]string, len(raw))
|
||||
for i, r := range raw {
|
||||
parts[i] = fmt.Sprintf("%d/%d", r.Numerator, r.Denominator)
|
||||
}
|
||||
|
||||
if justFirst == false {
|
||||
return fmt.Sprintf("%v", raw), nil
|
||||
} else if valueContext.UnitCount > 0 {
|
||||
return parts[0], nil
|
||||
} else {
|
||||
return "", nil
|
||||
}
|
||||
} else {
|
||||
log.Panicf("value of type (%d) [%s] is unparseable", tt.Type(), tt)
|
||||
|
||||
// Never called.
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
// UndefinedValue returns the value for a tag of "undefined" type.
|
||||
func UndefinedValue(indexedIfdName string, tagId uint16, valueContext ValueContext, byteOrder IfdByteOrder) (value interface{}, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
if indexedIfdName == IfdName(IfdExif, 0) {
|
||||
if tagId == 0x9000 {
|
||||
tt := NewTagType(TypeAsciiNoNul, byteOrder)
|
||||
|
||||
value, err = tt.ReadAsciiValue(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
return value, nil
|
||||
} else if tagId == 0xa000 {
|
||||
tt := NewTagType(TypeAsciiNoNul, byteOrder)
|
||||
|
||||
value, err = tt.ReadAsciiValue(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
return value, nil
|
||||
}
|
||||
} else if indexedIfdName == IfdName(IfdGps, 0) {
|
||||
if tagId == 0x001c {
|
||||
// GPSAreaInformation
|
||||
|
||||
tt := NewTagType(TypeAsciiNoNul, byteOrder)
|
||||
|
||||
value, err = tt.ReadAsciiValue(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
return value, nil
|
||||
} else if tagId == 0x001b {
|
||||
// GPSProcessingMethod
|
||||
|
||||
tt := NewTagType(TypeAsciiNoNul, byteOrder)
|
||||
|
||||
value, err = tt.ReadAsciiValue(valueContext)
|
||||
log.PanicIf(err)
|
||||
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(dustin): !! Still need to do:
|
||||
//
|
||||
// complex: 0xa302, 0xa20c, 0x8828
|
||||
// long: 0xa301, 0xa300
|
||||
// bytes: 0x927c, 0x9101 (probably, but not certain)
|
||||
// other: 0x9286 (simple, but needs some processing)
|
||||
|
||||
// 0xa40b is device-specific and unhandled.
|
||||
|
||||
|
||||
log.Panic(ErrUnhandledUnknownTypedTag)
|
||||
return nil, nil
|
||||
}
|
|
@ -0,0 +1,241 @@
|
|||
package exif
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"bytes"
|
||||
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
)
|
||||
|
||||
var (
|
||||
typeEncodeLogger = log.NewLogger("exif.type_encode")
|
||||
)
|
||||
|
||||
|
||||
// EncodedData encapsulates the compound output of an encoding operation.
|
||||
type EncodedData struct {
|
||||
Type uint16
|
||||
Encoded []byte
|
||||
UnitCount uint32
|
||||
}
|
||||
|
||||
|
||||
type ValueEncoder struct {
|
||||
byteOrder IfdByteOrder
|
||||
}
|
||||
|
||||
func NewValueEncoder(byteOrder IfdByteOrder) *ValueEncoder {
|
||||
return &ValueEncoder{
|
||||
byteOrder: byteOrder,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func (ve *ValueEncoder) encodeBytes(value []uint8) (ed EncodedData, err error) {
|
||||
ed.Type = TypeByte
|
||||
ed.Encoded = []byte(value)
|
||||
ed.UnitCount = uint32(len(value))
|
||||
|
||||
return ed, nil
|
||||
}
|
||||
|
||||
func (ve *ValueEncoder) encodeAscii(value string) (ed EncodedData, err error) {
|
||||
ed.Type = TypeAscii
|
||||
|
||||
ed.Encoded = []byte(value)
|
||||
ed.Encoded = append(ed.Encoded, 0)
|
||||
|
||||
ed.UnitCount = uint32(len(ed.Encoded))
|
||||
|
||||
return ed, nil
|
||||
}
|
||||
|
||||
// encodeAsciiNoNul returns a string encoded as a byte-string without a trailing
|
||||
// NUL byte.
|
||||
//
|
||||
// Note that:
|
||||
//
|
||||
// 1. This type can not be automatically encoded using `Encode()`. The default
|
||||
// mode is to encode *with* a trailing NUL byte using `encodeAscii`. Only
|
||||
// certain undefined-type tags using an unterminated ASCII string and these
|
||||
// are exceptional in nature.
|
||||
//
|
||||
// 2. The presence of this method allows us to completely test the complimentary
|
||||
// no-nul parser.
|
||||
//
|
||||
func (ve *ValueEncoder) encodeAsciiNoNul(value string) (ed EncodedData, err error) {
|
||||
ed.Type = TypeAsciiNoNul
|
||||
ed.Encoded = []byte(value)
|
||||
ed.UnitCount = uint32(len(ed.Encoded))
|
||||
|
||||
return ed, nil
|
||||
}
|
||||
|
||||
func (ve *ValueEncoder) encodeShorts(value []uint16) (ed EncodedData, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
ed.UnitCount = uint32(len(value))
|
||||
ed.Encoded = make([]byte, ed.UnitCount * 2)
|
||||
|
||||
for i := uint32(0); i < ed.UnitCount; i++ {
|
||||
if ve.byteOrder.IsBigEndian() {
|
||||
binary.BigEndian.PutUint16(ed.Encoded[i*2:(i+1)*2], value[i])
|
||||
} else {
|
||||
binary.LittleEndian.PutUint16(ed.Encoded[i*2:(i+1)*2], value[i])
|
||||
}
|
||||
}
|
||||
|
||||
ed.Type = TypeShort
|
||||
|
||||
return ed, nil
|
||||
}
|
||||
|
||||
func (ve *ValueEncoder) encodeLongs(value []uint32) (ed EncodedData, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
ed.UnitCount = uint32(len(value))
|
||||
ed.Encoded = make([]byte, ed.UnitCount * 4)
|
||||
|
||||
for i := uint32(0); i < ed.UnitCount; i++ {
|
||||
if ve.byteOrder.IsBigEndian() {
|
||||
binary.BigEndian.PutUint32(ed.Encoded[i*4:(i+1)*4], value[i])
|
||||
} else {
|
||||
binary.LittleEndian.PutUint32(ed.Encoded[i*4:(i+1)*4], value[i])
|
||||
}
|
||||
}
|
||||
|
||||
ed.Type = TypeLong
|
||||
|
||||
return ed, nil
|
||||
}
|
||||
|
||||
func (ve *ValueEncoder) encodeRationals(value []Rational) (ed EncodedData, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
ed.UnitCount = uint32(len(value))
|
||||
ed.Encoded = make([]byte, ed.UnitCount * 8)
|
||||
|
||||
for i := uint32(0); i < ed.UnitCount; i++ {
|
||||
if ve.byteOrder.IsBigEndian() {
|
||||
binary.BigEndian.PutUint32(ed.Encoded[i*8+0:i*8+4], value[i].Numerator)
|
||||
binary.BigEndian.PutUint32(ed.Encoded[i*8+4:i*8+8], value[i].Denominator)
|
||||
} else {
|
||||
binary.LittleEndian.PutUint32(ed.Encoded[i*8+0:i*8+4], value[i].Numerator)
|
||||
binary.LittleEndian.PutUint32(ed.Encoded[i*8+4:i*8+8], value[i].Denominator)
|
||||
}
|
||||
}
|
||||
|
||||
ed.Type = TypeRational
|
||||
|
||||
return ed, nil
|
||||
}
|
||||
|
||||
func (ve *ValueEncoder) encodeSignedLongs(value []int32) (ed EncodedData, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
ed.UnitCount = uint32(len(value))
|
||||
|
||||
b := bytes.NewBuffer(make([]byte, 0, 8 * ed.UnitCount))
|
||||
|
||||
for i := uint32(0); i < ed.UnitCount; i++ {
|
||||
if ve.byteOrder.IsBigEndian() {
|
||||
err := binary.Write(b, binary.BigEndian, value[i])
|
||||
log.PanicIf(err)
|
||||
} else {
|
||||
err := binary.Write(b, binary.LittleEndian, value[i])
|
||||
log.PanicIf(err)
|
||||
}
|
||||
}
|
||||
|
||||
ed.Type = TypeSignedLong
|
||||
ed.Encoded = b.Bytes()
|
||||
|
||||
return ed, nil
|
||||
}
|
||||
|
||||
func (ve *ValueEncoder) encodeSignedRationals(value []SignedRational) (ed EncodedData, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
ed.UnitCount = uint32(len(value))
|
||||
|
||||
b := bytes.NewBuffer(make([]byte, 0, 8 * ed.UnitCount))
|
||||
|
||||
for i := uint32(0); i < ed.UnitCount; i++ {
|
||||
if ve.byteOrder.IsBigEndian() {
|
||||
err := binary.Write(b, binary.BigEndian, value[i].Numerator)
|
||||
log.PanicIf(err)
|
||||
|
||||
err = binary.Write(b, binary.BigEndian, value[i].Denominator)
|
||||
log.PanicIf(err)
|
||||
} else {
|
||||
err := binary.Write(b, binary.LittleEndian, value[i].Numerator)
|
||||
log.PanicIf(err)
|
||||
|
||||
err = binary.Write(b, binary.LittleEndian, value[i].Denominator)
|
||||
log.PanicIf(err)
|
||||
}
|
||||
}
|
||||
|
||||
ed.Type = TypeSignedRational
|
||||
ed.Encoded = b.Bytes()
|
||||
|
||||
return ed, nil
|
||||
}
|
||||
|
||||
func (ve *ValueEncoder) Encode(value interface{}) (ed EncodedData, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
switch value.(type) {
|
||||
case []byte:
|
||||
ed, err = ve.encodeBytes(value.([]byte))
|
||||
log.PanicIf(err)
|
||||
case string:
|
||||
ed, err = ve.encodeAscii(value.(string))
|
||||
log.PanicIf(err)
|
||||
case []uint16:
|
||||
ed, err = ve.encodeShorts(value.([]uint16))
|
||||
log.PanicIf(err)
|
||||
case []uint32:
|
||||
ed, err = ve.encodeLongs(value.([]uint32))
|
||||
log.PanicIf(err)
|
||||
case []Rational:
|
||||
ed, err = ve.encodeRationals(value.([]Rational))
|
||||
log.PanicIf(err)
|
||||
case []int32:
|
||||
ed, err = ve.encodeSignedLongs(value.([]int32))
|
||||
log.PanicIf(err)
|
||||
case []SignedRational:
|
||||
ed, err = ve.encodeSignedRationals(value.([]SignedRational))
|
||||
log.PanicIf(err)
|
||||
default:
|
||||
log.Panicf("value not encodable: [%s] [%v]", reflect.TypeOf(value), value)
|
||||
}
|
||||
|
||||
return ed, nil
|
||||
}
|
|
@ -0,0 +1,578 @@
|
|||
package exif
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"reflect"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
)
|
||||
|
||||
func TestByteCycle(t *testing.T) {
|
||||
byteOrder := IfdByteOrder(BigEndianByteOrder)
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []byte("original text")
|
||||
|
||||
ed, err := ve.encodeBytes(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeByte {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte(original)
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 13 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
|
||||
tt := NewTagType(ed.Type, byteOrder)
|
||||
recovered, err := tt.ParseBytes(ed.Encoded, ed.UnitCount)
|
||||
|
||||
if reflect.DeepEqual(recovered, original) != true {
|
||||
t.Fatalf("Value not recovered correctly.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAsciiCycle(t *testing.T) {
|
||||
byteOrder := IfdByteOrder(BigEndianByteOrder)
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := "original text"
|
||||
|
||||
ed, err := ve.encodeAscii(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeAscii {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte(original)
|
||||
expected = append(expected, 0)
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 14 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
|
||||
// Check that the string was recovered correctly and with the trailing NUL
|
||||
// character autostripped.
|
||||
|
||||
tt := NewTagType(TypeAscii, byteOrder)
|
||||
recovered, err := tt.ParseAscii(ed.Encoded, ed.UnitCount)
|
||||
|
||||
if reflect.DeepEqual(recovered, original) != true {
|
||||
t.Fatalf("Value not recovered correctly.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAsciiNoNulCycle(t *testing.T) {
|
||||
byteOrder := IfdByteOrder(BigEndianByteOrder)
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := "original text"
|
||||
|
||||
ed, err := ve.encodeAsciiNoNul(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeAsciiNoNul {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte(original)
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 13 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
|
||||
// Check that the string was recovered correctly and with the trailing NUL
|
||||
// character ignored (because not expected in the context of that type).
|
||||
|
||||
tt := NewTagType(TypeAsciiNoNul, byteOrder)
|
||||
recovered, err := tt.ParseAsciiNoNul(ed.Encoded, ed.UnitCount)
|
||||
|
||||
if reflect.DeepEqual(recovered, string(expected)) != true {
|
||||
t.Fatalf("Value not recovered correctly.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShortCycle(t *testing.T) {
|
||||
byteOrder := IfdByteOrder(BigEndianByteOrder)
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []uint16 { 0x11, 0x22, 0x33, 0x44, 0x55 }
|
||||
|
||||
ed, err := ve.encodeShorts(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeShort {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte {
|
||||
0x00, 0x11,
|
||||
0x00, 0x22,
|
||||
0x00, 0x33,
|
||||
0x00, 0x44,
|
||||
0x00, 0x55,
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 5 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
|
||||
tt := NewTagType(ed.Type, byteOrder)
|
||||
recovered, err := tt.ParseShorts(ed.Encoded, ed.UnitCount)
|
||||
|
||||
if reflect.DeepEqual(recovered, original) != true {
|
||||
t.Fatalf("Value not recovered correctly.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLongCycle(t *testing.T) {
|
||||
byteOrder := IfdByteOrder(BigEndianByteOrder)
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []uint32 { 0x11, 0x22, 0x33, 0x44, 0x55 }
|
||||
|
||||
ed, err := ve.encodeLongs(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeLong {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte {
|
||||
0x00, 0x00, 0x00, 0x11,
|
||||
0x00, 0x00, 0x00, 0x22,
|
||||
0x00, 0x00, 0x00, 0x33,
|
||||
0x00, 0x00, 0x00, 0x44,
|
||||
0x00, 0x00, 0x00, 0x55,
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 5 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
|
||||
tt := NewTagType(ed.Type, byteOrder)
|
||||
recovered, err := tt.ParseLongs(ed.Encoded, ed.UnitCount)
|
||||
|
||||
if reflect.DeepEqual(recovered, original) != true {
|
||||
t.Fatalf("Value not recovered correctly.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRationalCycle(t *testing.T) {
|
||||
byteOrder := IfdByteOrder(BigEndianByteOrder)
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []Rational {
|
||||
Rational{
|
||||
Numerator: 0x11,
|
||||
Denominator: 0x22,
|
||||
},
|
||||
Rational{
|
||||
Numerator: 0x33,
|
||||
Denominator: 0x44,
|
||||
},
|
||||
Rational{
|
||||
Numerator: 0x55,
|
||||
Denominator: 0x66,
|
||||
},
|
||||
Rational{
|
||||
Numerator: 0x77,
|
||||
Denominator: 0x88,
|
||||
},
|
||||
Rational{
|
||||
Numerator: 0x99,
|
||||
Denominator: 0x00,
|
||||
},
|
||||
}
|
||||
|
||||
ed, err := ve.encodeRationals(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeRational {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte {
|
||||
0x00, 0x00, 0x00, 0x11,
|
||||
0x00, 0x00, 0x00, 0x22,
|
||||
0x00, 0x00, 0x00, 0x33,
|
||||
0x00, 0x00, 0x00, 0x44,
|
||||
0x00, 0x00, 0x00, 0x55,
|
||||
0x00, 0x00, 0x00, 0x66,
|
||||
0x00, 0x00, 0x00, 0x77,
|
||||
0x00, 0x00, 0x00, 0x88,
|
||||
0x00, 0x00, 0x00, 0x99,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 5 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
|
||||
tt := NewTagType(ed.Type, byteOrder)
|
||||
recovered, err := tt.ParseRationals(ed.Encoded, ed.UnitCount)
|
||||
|
||||
if reflect.DeepEqual(recovered, original) != true {
|
||||
t.Fatalf("Value not recovered correctly.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignedLongCycle(t *testing.T) {
|
||||
byteOrder := IfdByteOrder(BigEndianByteOrder)
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []int32 { 0x11, 0x22, 0x33, 0x44, 0x55 }
|
||||
|
||||
ed, err := ve.encodeSignedLongs(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeSignedLong {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte {
|
||||
0x00, 0x00, 0x00, 0x11,
|
||||
0x00, 0x00, 0x00, 0x22,
|
||||
0x00, 0x00, 0x00, 0x33,
|
||||
0x00, 0x00, 0x00, 0x44,
|
||||
0x00, 0x00, 0x00, 0x55,
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 5 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
|
||||
tt := NewTagType(ed.Type, byteOrder)
|
||||
recovered, err := tt.ParseSignedLongs(ed.Encoded, ed.UnitCount)
|
||||
|
||||
if reflect.DeepEqual(recovered, original) != true {
|
||||
t.Fatalf("Value not recovered correctly.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignedRationalCycle(t *testing.T) {
|
||||
byteOrder := IfdByteOrder(BigEndianByteOrder)
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []SignedRational {
|
||||
SignedRational{
|
||||
Numerator: 0x11,
|
||||
Denominator: 0x22,
|
||||
},
|
||||
SignedRational{
|
||||
Numerator: 0x33,
|
||||
Denominator: 0x44,
|
||||
},
|
||||
SignedRational{
|
||||
Numerator: 0x55,
|
||||
Denominator: 0x66,
|
||||
},
|
||||
SignedRational{
|
||||
Numerator: 0x77,
|
||||
Denominator: 0x88,
|
||||
},
|
||||
SignedRational{
|
||||
Numerator: 0x99,
|
||||
Denominator: 0x00,
|
||||
},
|
||||
}
|
||||
|
||||
ed, err := ve.encodeSignedRationals(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeSignedRational {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte {
|
||||
0x00, 0x00, 0x00, 0x11,
|
||||
0x00, 0x00, 0x00, 0x22,
|
||||
0x00, 0x00, 0x00, 0x33,
|
||||
0x00, 0x00, 0x00, 0x44,
|
||||
0x00, 0x00, 0x00, 0x55,
|
||||
0x00, 0x00, 0x00, 0x66,
|
||||
0x00, 0x00, 0x00, 0x77,
|
||||
0x00, 0x00, 0x00, 0x88,
|
||||
0x00, 0x00, 0x00, 0x99,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 5 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
|
||||
tt := NewTagType(ed.Type, byteOrder)
|
||||
recovered, err := tt.ParseSignedRationals(ed.Encoded, ed.UnitCount)
|
||||
|
||||
if reflect.DeepEqual(recovered, original) != true {
|
||||
t.Fatalf("Value not recovered correctly.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
func TestEncode_Byte(t *testing.T) {
|
||||
byteOrder := IfdByteOrder(BigEndianByteOrder)
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []byte("original text")
|
||||
|
||||
ed, err := ve.Encode(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeByte {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte(original)
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 13 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncode_Ascii(t *testing.T) {
|
||||
byteOrder := IfdByteOrder(BigEndianByteOrder)
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := "original text"
|
||||
|
||||
ed, err := ve.Encode(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeAscii {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte(original)
|
||||
expected = append(expected, 0)
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 14 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncode_Short(t *testing.T) {
|
||||
byteOrder := IfdByteOrder(BigEndianByteOrder)
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []uint16 { 0x11, 0x22, 0x33, 0x44, 0x55 }
|
||||
|
||||
ed, err := ve.Encode(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeShort {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte {
|
||||
0x00, 0x11,
|
||||
0x00, 0x22,
|
||||
0x00, 0x33,
|
||||
0x00, 0x44,
|
||||
0x00, 0x55,
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 5 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncode_Long(t *testing.T) {
|
||||
byteOrder := IfdByteOrder(BigEndianByteOrder)
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []uint32 { 0x11, 0x22, 0x33, 0x44, 0x55 }
|
||||
|
||||
ed, err := ve.Encode(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeLong {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte {
|
||||
0x00, 0x00, 0x00, 0x11,
|
||||
0x00, 0x00, 0x00, 0x22,
|
||||
0x00, 0x00, 0x00, 0x33,
|
||||
0x00, 0x00, 0x00, 0x44,
|
||||
0x00, 0x00, 0x00, 0x55,
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 5 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncode_Rational(t *testing.T) {
|
||||
byteOrder := IfdByteOrder(BigEndianByteOrder)
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []Rational {
|
||||
Rational{
|
||||
Numerator: 0x11,
|
||||
Denominator: 0x22,
|
||||
},
|
||||
Rational{
|
||||
Numerator: 0x33,
|
||||
Denominator: 0x44,
|
||||
},
|
||||
Rational{
|
||||
Numerator: 0x55,
|
||||
Denominator: 0x66,
|
||||
},
|
||||
Rational{
|
||||
Numerator: 0x77,
|
||||
Denominator: 0x88,
|
||||
},
|
||||
Rational{
|
||||
Numerator: 0x99,
|
||||
Denominator: 0x00,
|
||||
},
|
||||
}
|
||||
|
||||
ed, err := ve.Encode(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeRational {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte {
|
||||
0x00, 0x00, 0x00, 0x11,
|
||||
0x00, 0x00, 0x00, 0x22,
|
||||
0x00, 0x00, 0x00, 0x33,
|
||||
0x00, 0x00, 0x00, 0x44,
|
||||
0x00, 0x00, 0x00, 0x55,
|
||||
0x00, 0x00, 0x00, 0x66,
|
||||
0x00, 0x00, 0x00, 0x77,
|
||||
0x00, 0x00, 0x00, 0x88,
|
||||
0x00, 0x00, 0x00, 0x99,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 5 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncode_SignedLong(t *testing.T) {
|
||||
byteOrder := IfdByteOrder(BigEndianByteOrder)
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []int32 { 0x11, 0x22, 0x33, 0x44, 0x55 }
|
||||
|
||||
ed, err := ve.Encode(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeSignedLong {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte {
|
||||
0x00, 0x00, 0x00, 0x11,
|
||||
0x00, 0x00, 0x00, 0x22,
|
||||
0x00, 0x00, 0x00, 0x33,
|
||||
0x00, 0x00, 0x00, 0x44,
|
||||
0x00, 0x00, 0x00, 0x55,
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 5 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncode_SignedRational(t *testing.T) {
|
||||
byteOrder := IfdByteOrder(BigEndianByteOrder)
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []SignedRational {
|
||||
SignedRational{
|
||||
Numerator: 0x11,
|
||||
Denominator: 0x22,
|
||||
},
|
||||
SignedRational{
|
||||
Numerator: 0x33,
|
||||
Denominator: 0x44,
|
||||
},
|
||||
SignedRational{
|
||||
Numerator: 0x55,
|
||||
Denominator: 0x66,
|
||||
},
|
||||
SignedRational{
|
||||
Numerator: 0x77,
|
||||
Denominator: 0x88,
|
||||
},
|
||||
SignedRational{
|
||||
Numerator: 0x99,
|
||||
Denominator: 0x00,
|
||||
},
|
||||
}
|
||||
|
||||
ed, err := ve.Encode(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeSignedRational {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte {
|
||||
0x00, 0x00, 0x00, 0x11,
|
||||
0x00, 0x00, 0x00, 0x22,
|
||||
0x00, 0x00, 0x00, 0x33,
|
||||
0x00, 0x00, 0x00, 0x44,
|
||||
0x00, 0x00, 0x00, 0x55,
|
||||
0x00, 0x00, 0x00, 0x66,
|
||||
0x00, 0x00, 0x00, 0x77,
|
||||
0x00, 0x00, 0x00, 0x88,
|
||||
0x00, 0x00, 0x00, 0x99,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 5 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue