mirror of https://github.com/dsoprea/go-exif.git
ifd: Implemented several layers of value processing. Done.
- It's going to be laborious to write unit-tests for, though.pull/3/head
parent
315ca60f03
commit
1511788a4e
2
exif.go
2
exif.go
|
@ -38,7 +38,7 @@ func (e *Exif) Parse(data []byte, visitor TagVisitor) (err error) {
|
|||
}()
|
||||
|
||||
if e.IsExif(data) == false {
|
||||
return ErrNotExif
|
||||
log.Panic(ErrNotExif)
|
||||
}
|
||||
|
||||
// Good reference:
|
||||
|
|
48
exif_test.go
48
exif_test.go
|
@ -5,6 +5,7 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"io/ioutil"
|
||||
|
||||
|
@ -64,8 +65,16 @@ func TestParse(t *testing.T) {
|
|||
// Run the parse.
|
||||
|
||||
ti := NewTagIndex()
|
||||
tags := make([]string, 0)
|
||||
|
||||
visitor := func(tagId uint16, tagType TagType, valueContext ValueContext) (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
log.PrintErrorf(err, "The visitor encountered an error.")
|
||||
}
|
||||
}()
|
||||
|
||||
visitor := func(tagId, tagType uint16, tagCount, valueOffset uint32) (err error) {
|
||||
it, err := ti.GetWithTagId(tagId)
|
||||
if err != nil {
|
||||
if err == ErrTagNotFound {
|
||||
|
@ -75,21 +84,42 @@ func TestParse(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Tag: ID=(0x%04x) NAME=[%s] IFD=[%s] TYPE=(%d) COUNT=(%d) VALUE-OFFSET=(%d)\n", tagId, it.Name, it.Ifd, tagType, tagCount, valueOffset)
|
||||
valueString, err := tagType.ValueString(valueContext, true)
|
||||
log.PanicIf(err)
|
||||
|
||||
// Notes on the tag-value's value (we'll have to use this as a pointer if the type potentially requires more than four bytes):
|
||||
//
|
||||
// This tag records the offset from the start of the TIFF header to the position where the value itself is
|
||||
// recorded. In cases where the value fits in 4 Bytes, the value itself is recorded. If the value is smaller
|
||||
// than 4 Bytes, the value is stored in the 4-Byte area starting from the left, i.e., from the lower end of
|
||||
// the byte offset area. For example, in big endian format, if the type is SHORT and the value is 1, it is
|
||||
// recorded as 00010000.H
|
||||
description := fmt.Sprintf("ID=(0x%04x) NAME=[%s] IFD=[%s] COUNT=(%d) TYPE=[%s] VALUE=[%s]", tagId, it.Name, it.Ifd, valueContext.UnitCount, tagType.Name(), valueString)
|
||||
tags = append(tags, description)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err = e.Parse(data[foundAt:], visitor)
|
||||
log.PanicIf(err)
|
||||
|
||||
expected := []string {
|
||||
"ID=(0x010f) NAME=[Make] IFD=[Image] COUNT=(6) TYPE=[ASCII] VALUE=[Canon]",
|
||||
"ID=(0x0110) NAME=[Model] IFD=[Image] COUNT=(22) TYPE=[ASCII] VALUE=[Canon EOS 5D Mark III]",
|
||||
"ID=(0x0112) NAME=[Orientation] IFD=[Image] COUNT=(1) TYPE=[SHORT] VALUE=[1]",
|
||||
"ID=(0x011a) NAME=[XResolution] IFD=[Image] COUNT=(1) TYPE=[RATIONAL] VALUE=[72/1]",
|
||||
"ID=(0x011b) NAME=[YResolution] IFD=[Image] COUNT=(1) TYPE=[RATIONAL] VALUE=[72/1]",
|
||||
"ID=(0x0128) NAME=[ResolutionUnit] IFD=[Image] COUNT=(1) TYPE=[SHORT] VALUE=[2]",
|
||||
"ID=(0x0132) NAME=[DateTime] IFD=[Image] COUNT=(20) TYPE=[ASCII] VALUE=[2017:12:02 08:18:50]",
|
||||
"ID=(0x013b) NAME=[Artist] IFD=[Image] COUNT=(1) TYPE=[ASCII] VALUE=[]",
|
||||
"ID=(0x0213) NAME=[YCbCrPositioning] IFD=[Image] COUNT=(1) TYPE=[SHORT] VALUE=[2]",
|
||||
"ID=(0x8298) NAME=[Copyright] IFD=[Image] COUNT=(1) TYPE=[ASCII] VALUE=[]",
|
||||
"ID=(0x8769) NAME=[ExifTag] IFD=[Image] COUNT=(1) TYPE=[LONG] VALUE=[360]",
|
||||
"ID=(0x8825) NAME=[GPSTag] IFD=[Image] COUNT=(1) TYPE=[LONG] VALUE=[9554]",
|
||||
"ID=(0x0103) NAME=[Compression] IFD=[Image] COUNT=(1) TYPE=[SHORT] VALUE=[6]",
|
||||
"ID=(0x011a) NAME=[XResolution] IFD=[Image] COUNT=(1) TYPE=[RATIONAL] VALUE=[72/1]",
|
||||
"ID=(0x011b) NAME=[YResolution] IFD=[Image] COUNT=(1) TYPE=[RATIONAL] VALUE=[72/1]",
|
||||
"ID=(0x0128) NAME=[ResolutionUnit] IFD=[Image] COUNT=(1) TYPE=[SHORT] VALUE=[2]",
|
||||
"ID=(0x0201) NAME=[JPEGInterchangeFormat] IFD=[Image] COUNT=(1) TYPE=[LONG] VALUE=[11444]",
|
||||
"ID=(0x0202) NAME=[JPEGInterchangeFormatLength] IFD=[Image] COUNT=(1) TYPE=[LONG] VALUE=[21491]",
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(tags, expected) == false {
|
||||
t.Fatalf("tags not correct:\n%v", tags)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
59
ifd.go
59
ifd.go
|
@ -9,24 +9,10 @@ import (
|
|||
"github.com/dsoprea/go-logging"
|
||||
)
|
||||
|
||||
const (
|
||||
BigEndianByteOrder = iota
|
||||
LittleEndianByteOrder = iota
|
||||
)
|
||||
|
||||
var (
|
||||
ifdLogger = log.NewLogger("exifjpeg.ifd")
|
||||
)
|
||||
|
||||
type IfdByteOrder int
|
||||
|
||||
func (ibo IfdByteOrder) IsBigEndian() bool {
|
||||
return ibo == BigEndianByteOrder
|
||||
}
|
||||
|
||||
func (ibo IfdByteOrder) IsLittleEndian() bool {
|
||||
return ibo == LittleEndianByteOrder
|
||||
}
|
||||
|
||||
type Ifd struct {
|
||||
data []byte
|
||||
|
@ -86,23 +72,43 @@ func (ifd *Ifd) getUint16() (value uint16, err error) {
|
|||
// getUint32 reads a uint32 and advances both our current and our current
|
||||
// accumulator (which allows us to know how far to seek to the beginning of the
|
||||
// next IFD when it's time to jump).
|
||||
func (ifd *Ifd) getUint32() (value uint32, err error) {
|
||||
func (ifd *Ifd) getUint32() (value uint32, raw []byte, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
err = ifd.read(ifd.buffer, &value)
|
||||
raw = make([]byte, 4)
|
||||
|
||||
_, err = ifd.buffer.Read(raw)
|
||||
log.PanicIf(err)
|
||||
|
||||
ifd.currentOffset += 4
|
||||
|
||||
return value, nil
|
||||
if ifd.byteOrder.IsBigEndian() {
|
||||
value = binary.BigEndian.Uint32(raw)
|
||||
} else {
|
||||
value = binary.LittleEndian.Uint32(raw)
|
||||
}
|
||||
|
||||
return value, raw, nil
|
||||
}
|
||||
|
||||
// ValueContext describes all of the parameters required to find and extract
|
||||
// the actual tag value.
|
||||
type ValueContext struct {
|
||||
UnitCount uint32
|
||||
ValueOffset uint32
|
||||
RawValueOffset []byte
|
||||
RawExif []byte
|
||||
}
|
||||
|
||||
|
||||
type TagVisitor func(tagId, tagType uint16, tagCount, valueOffset uint32) (err error)
|
||||
// TagVisitor is an optional callback that can get hit for every tag we parse
|
||||
// through. `rawExif` is the byte array startign after the EXIF header (where
|
||||
// the offsets of all IFDs and values are calculated from).
|
||||
type TagVisitor func(tagId uint16, tagType TagType, valueContext ValueContext) (err error)
|
||||
|
||||
// parseCurrentIfd decodes the IFD block that we're currently sitting on the
|
||||
// first byte of.
|
||||
|
@ -129,19 +135,28 @@ func (ifd *Ifd) parseCurrentIfd(visitor TagVisitor) (nextIfdOffset uint32, err e
|
|||
tagType, err := ifd.getUint16()
|
||||
log.PanicIf(err)
|
||||
|
||||
tagCount, err := ifd.getUint32()
|
||||
unitCount, _, err := ifd.getUint32()
|
||||
log.PanicIf(err)
|
||||
|
||||
valueOffset, err := ifd.getUint32()
|
||||
valueOffset, rawValueOffset, err := ifd.getUint32()
|
||||
log.PanicIf(err)
|
||||
|
||||
if visitor != nil {
|
||||
err := visitor(tagId, tagType, tagCount, valueOffset)
|
||||
tt := NewTagType(tagType, ifd.byteOrder)
|
||||
|
||||
vc := ValueContext{
|
||||
UnitCount: unitCount,
|
||||
ValueOffset: valueOffset,
|
||||
RawValueOffset: rawValueOffset,
|
||||
RawExif: ifd.data[ifd.ifdTopOffset:],
|
||||
}
|
||||
|
||||
err := visitor(tagId, tt, vc)
|
||||
log.PanicIf(err)
|
||||
}
|
||||
}
|
||||
|
||||
nextIfdOffset, err = ifd.getUint32()
|
||||
nextIfdOffset, _, err = ifd.getUint32()
|
||||
log.PanicIf(err)
|
||||
|
||||
ifdLogger.Debugf(nil, "Next IFD at offset: (%08x)", nextIfdOffset)
|
||||
|
|
2
tags.go
2
tags.go
|
@ -140,7 +140,7 @@ func (ti *TagIndex) GetWithTagId(id uint16) (it *IndexedTag, err error) {
|
|||
|
||||
it, found := ti.tagsById[id]
|
||||
if found == false {
|
||||
return nil, ErrTagNotFound
|
||||
log.Panic(ErrTagNotFound)
|
||||
}
|
||||
|
||||
return it, nil
|
||||
|
|
|
@ -0,0 +1,611 @@
|
|||
package exif
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
)
|
||||
|
||||
const (
|
||||
TypeByte = uint16(1)
|
||||
TypeAscii = uint16(2)
|
||||
TypeShort = uint16(3)
|
||||
TypeLong = uint16(4)
|
||||
TypeRational = uint16(5)
|
||||
TypeUndefined = uint16(6)
|
||||
TypeSignedLong = uint16(9)
|
||||
TypeSignedRational = uint16(10)
|
||||
)
|
||||
|
||||
var (
|
||||
TypeNames = map[uint16]string {
|
||||
TypeByte: "BYTE",
|
||||
TypeAscii: "ASCII",
|
||||
TypeShort: "SHORT",
|
||||
TypeLong: "LONG",
|
||||
TypeRational: "RATIONAL",
|
||||
TypeUndefined: "UNDEFINED",
|
||||
TypeSignedLong: "SLONG",
|
||||
TypeSignedRational: "SRATIONAL",
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
typeLogger = log.NewLogger("exif.type")
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrCantDetermineTagValueSize is used when we're trying to determine a
|
||||
//size for a non-standard/undefined type.
|
||||
ErrCantDetermineTagValueSize = errors.New("can not determine tag-value size")
|
||||
|
||||
// ErrNotEnoughData is used when there isn't enough data to accomodate what
|
||||
// we're trying to parse (sizeof(type) * unit_count).
|
||||
ErrNotEnoughData = errors.New("not enough data for type")
|
||||
|
||||
// ErrWrongType is used when we try to parse anything other than the current type.
|
||||
ErrWrongType = errors.New("wrong type, can not parse")
|
||||
)
|
||||
|
||||
|
||||
const (
|
||||
BigEndianByteOrder = iota
|
||||
LittleEndianByteOrder = iota
|
||||
)
|
||||
|
||||
type IfdByteOrder int
|
||||
|
||||
func (ibo IfdByteOrder) IsBigEndian() bool {
|
||||
return ibo == BigEndianByteOrder
|
||||
}
|
||||
|
||||
func (ibo IfdByteOrder) IsLittleEndian() bool {
|
||||
return ibo == LittleEndianByteOrder
|
||||
}
|
||||
|
||||
|
||||
type Rational struct {
|
||||
Numerator uint32
|
||||
Denominator uint32
|
||||
}
|
||||
|
||||
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) Size() int {
|
||||
if tt.tagType == TypeByte {
|
||||
return 1
|
||||
} else if tt.tagType == TypeAscii {
|
||||
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 {
|
||||
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))
|
||||
}
|
||||
}()
|
||||
|
||||
if tt.ValueIsEmbedded(valueContext.UnitCount) == true {
|
||||
typeLogger.Debugf(nil, "Reading ASCII value (embedded).")
|
||||
|
||||
value, err = tt.ParseAscii(valueContext.RawValueOffset, valueContext.UnitCount)
|
||||
log.PanicIf(err)
|
||||
} else {
|
||||
typeLogger.Debugf(nil, "Reading ASCII value (at offset).")
|
||||
|
||||
value, err = tt.ParseAscii(valueContext.RawExif[valueContext.ValueOffset:], valueContext.UnitCount)
|
||||
log.PanicIf(err)
|
||||
}
|
||||
|
||||
len_ := len(value)
|
||||
if value[len_ - 1] != 0 {
|
||||
typeLogger.Warningf(nil, "ascii value not terminated with nul: [%s]", value)
|
||||
|
||||
// TODO(dustin): !! Debugging
|
||||
fmt.Printf("ascii value not terminated with nul: [%s]", value)
|
||||
|
||||
return value, nil
|
||||
} else {
|
||||
return value[:len_ - 1], 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() == 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() == TypeRational {
|
||||
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 [%s] is unparseable", tt)
|
||||
|
||||
// Never called.
|
||||
return "", nil
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue