mirror of https://github.com/dsoprea/go-exif.git
612 lines
16 KiB
Go
612 lines
16 KiB
Go
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(7)
|
|
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() == 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
|
|
}
|
|
}
|