Reorganized to not require GOPATH unless testing.

This commit is contained in:
Dustin Oprea 2018-06-16 09:49:14 -04:00
parent 7a8e5b005e
commit dfbe003c0e
4 changed files with 358 additions and 380 deletions

32
common_test.go Normal file
View File

@ -0,0 +1,32 @@
package exif
import (
"os"
"path"
"io/ioutil"
"github.com/dsoprea/go-logging"
)
var (
assetsPath = ""
testExifData = make([]byte, 0)
)
func init() {
goPath := os.Getenv("GOPATH")
if goPath == "" {
log.Panicf("GOPATH is empty")
}
assetsPath = path.Join(goPath, "src", "github.com", "dsoprea", "go-exif", "assets")
// Load test EXIF data.
filepath := path.Join(assetsPath, "NDM_8901.jpg.exif")
var err error
testExifData, err = ioutil.ReadFile(filepath)
log.PanicIf(err)
}

285
exif.go
View File

@ -1,128 +1,129 @@
package exif package exif
import ( import (
"os" "bytes"
"errors" "errors"
"bytes" "fmt"
"fmt" "os"
"io/ioutil" "encoding/binary"
"encoding/binary" "io/ioutil"
"github.com/dsoprea/go-logging" "github.com/dsoprea/go-logging"
) )
const ( const (
// ExifAddressableAreaStart is the absolute offset in the file that all // ExifAddressableAreaStart is the absolute offset in the file that all
// offsets are relative to. // offsets are relative to.
ExifAddressableAreaStart = uint32(0x0) ExifAddressableAreaStart = uint32(0x0)
// ExifDefaultFirstIfdOffset is essentially the number of bytes in addition // ExifDefaultFirstIfdOffset is essentially the number of bytes in addition
// to `ExifAddressableAreaStart` that you have to move in order to escape // to `ExifAddressableAreaStart` that you have to move in order to escape
// the rest of the header and get to the earliest point where we can put // the rest of the header and get to the earliest point where we can put
// stuff (which has to be the first IFD). This is the size of the header // stuff (which has to be the first IFD). This is the size of the header
// sequence containing the two-character byte-order, two-character fixed- // sequence containing the two-character byte-order, two-character fixed-
// bytes, and the four bytes describing the first-IFD offset. // bytes, and the four bytes describing the first-IFD offset.
ExifDefaultFirstIfdOffset = uint32(2 + 2 + 4) ExifDefaultFirstIfdOffset = uint32(2 + 2 + 4)
) )
var ( var (
exifLogger = log.NewLogger("exif.exif") exifLogger = log.NewLogger("exif.exif")
// EncodeDefaultByteOrder is the default byte-order for encoding operations. // EncodeDefaultByteOrder is the default byte-order for encoding operations.
EncodeDefaultByteOrder = binary.BigEndian EncodeDefaultByteOrder = binary.BigEndian
BigEndianBoBytes = [2]byte { 'M', 'M' } // Default byte order for tests.
LittleEndianBoBytes = [2]byte { 'I', 'I' } TestDefaultByteOrder = binary.BigEndian
ByteOrderLookup = map[[2]byte]binary.ByteOrder { BigEndianBoBytes = [2]byte{'M', 'M'}
BigEndianBoBytes: binary.BigEndian, LittleEndianBoBytes = [2]byte{'I', 'I'}
LittleEndianBoBytes: binary.LittleEndian,
}
ByteOrderLookupR = map[binary.ByteOrder][2]byte { ByteOrderLookup = map[[2]byte]binary.ByteOrder{
binary.BigEndian: BigEndianBoBytes, BigEndianBoBytes: binary.BigEndian,
binary.LittleEndian: LittleEndianBoBytes, LittleEndianBoBytes: binary.LittleEndian,
} }
ExifFixedBytesLookup = map[binary.ByteOrder][2]byte { ByteOrderLookupR = map[binary.ByteOrder][2]byte{
binary.LittleEndian: [2]byte { 0x2a, 0x00 }, binary.BigEndian: BigEndianBoBytes,
binary.BigEndian: [2]byte { 0x00, 0x2a }, binary.LittleEndian: LittleEndianBoBytes,
} }
ExifFixedBytesLookup = map[binary.ByteOrder][2]byte{
binary.LittleEndian: [2]byte{0x2a, 0x00},
binary.BigEndian: [2]byte{0x00, 0x2a},
}
) )
var ( var (
ErrNoExif = errors.New("no exif data") ErrNoExif = errors.New("no exif data")
ErrExifHeaderError = errors.New("exif header error") ErrExifHeaderError = errors.New("exif header error")
) )
// SearchAndExtractExif returns a slice from the beginning of the EXIF data the // SearchAndExtractExif returns a slice from the beginning of the EXIF data the
// end of the file (it's not practical to try and calculate where the data // end of the file (it's not practical to try and calculate where the data
// actually ends). // actually ends).
func SearchAndExtractExif(data []byte) (rawExif []byte, err error) { func SearchAndExtractExif(data []byte) (rawExif []byte, err error) {
defer func() { defer func() {
if state := recover(); state != nil { if state := recover(); state != nil {
err := log.Wrap(state.(error)) err := log.Wrap(state.(error))
log.Panic(err) log.Panic(err)
} }
}() }()
// Search for the beginning of the EXIF information. The EXIF is near the // Search for the beginning of the EXIF information. The EXIF is near the
// beginning of our/most JPEGs, so this has a very low cost. // beginning of our/most JPEGs, so this has a very low cost.
foundAt := -1 foundAt := -1
for i := 0; i < len(data); i++ { for i := 0; i < len(data); i++ {
if _, err := ParseExifHeader(data[i:]); err == nil { if _, err := ParseExifHeader(data[i:]); err == nil {
foundAt = i foundAt = i
break break
} else if log.Is(err, ErrNoExif) == false { } else if log.Is(err, ErrNoExif) == false {
log.Panic(err) log.Panic(err)
} }
} }
if foundAt == -1 { if foundAt == -1 {
log.Panic(ErrNoExif) log.Panic(ErrNoExif)
} }
return data[foundAt:], nil return data[foundAt:], nil
} }
// SearchFileAndExtractExif returns a slice from the beginning of the EXIF data // SearchFileAndExtractExif returns a slice from the beginning of the EXIF data
// to the end of the file (it's not practical to try and calculate where the // to the end of the file (it's not practical to try and calculate where the
// data actually ends). // data actually ends).
func SearchFileAndExtractExif(filepath string) (rawExif []byte, err error) { func SearchFileAndExtractExif(filepath string) (rawExif []byte, err error) {
defer func() { defer func() {
if state := recover(); state != nil { if state := recover(); state != nil {
err := log.Wrap(state.(error)) err := log.Wrap(state.(error))
log.Panic(err) log.Panic(err)
} }
}() }()
// Open the file. // Open the file.
f, err := os.Open(filepath) f, err := os.Open(filepath)
log.PanicIf(err) log.PanicIf(err)
defer f.Close() defer f.Close()
data, err := ioutil.ReadAll(f) data, err := ioutil.ReadAll(f)
log.PanicIf(err) log.PanicIf(err)
rawExif, err = SearchAndExtractExif(data) rawExif, err = SearchAndExtractExif(data)
log.PanicIf(err) log.PanicIf(err)
return rawExif, nil return rawExif, nil
} }
type ExifHeader struct { type ExifHeader struct {
ByteOrder binary.ByteOrder ByteOrder binary.ByteOrder
FirstIfdOffset uint32 FirstIfdOffset uint32
} }
func (eh ExifHeader) String() string { func (eh ExifHeader) String() string {
return fmt.Sprintf("ExifHeader<BYTE-ORDER=[%v] FIRST-IFD-OFFSET=(0x%02x)>", eh.ByteOrder, eh.FirstIfdOffset) return fmt.Sprintf("ExifHeader<BYTE-ORDER=[%v] FIRST-IFD-OFFSET=(0x%02x)>", eh.ByteOrder, eh.FirstIfdOffset)
} }
// ParseExifHeader parses the bytes at the very top of the header. // ParseExifHeader parses the bytes at the very top of the header.
@ -130,102 +131,102 @@ func (eh ExifHeader) String() string {
// This will panic with ErrNoExif on any data errors so that we can double as // This will panic with ErrNoExif on any data errors so that we can double as
// an EXIF-detection routine. // an EXIF-detection routine.
func ParseExifHeader(data []byte) (eh ExifHeader, err error) { func ParseExifHeader(data []byte) (eh ExifHeader, err error) {
defer func() { defer func() {
if state := recover(); state != nil { if state := recover(); state != nil {
err = log.Wrap(state.(error)) err = log.Wrap(state.(error))
} }
}() }()
// Good reference: // Good reference:
// //
// CIPA DC-008-2016; JEITA CP-3451D // CIPA DC-008-2016; JEITA CP-3451D
// -> http://www.cipa.jp/std/documents/e/DC-008-Translation-2016-E.pdf // -> http://www.cipa.jp/std/documents/e/DC-008-Translation-2016-E.pdf
byteOrderBytes := [2]byte { data[0], data[1] } byteOrderBytes := [2]byte{data[0], data[1]}
byteOrder, found := ByteOrderLookup[byteOrderBytes] byteOrder, found := ByteOrderLookup[byteOrderBytes]
if found == false { if found == false {
exifLogger.Warningf(nil, "EXIF byte-order not recognized: [%v]", byteOrderBytes) exifLogger.Warningf(nil, "EXIF byte-order not recognized: [%v]", byteOrderBytes)
log.Panic(ErrNoExif) log.Panic(ErrNoExif)
} }
fixedBytes := [2]byte { data[2], data[3] } fixedBytes := [2]byte{data[2], data[3]}
expectedFixedBytes := ExifFixedBytesLookup[byteOrder] expectedFixedBytes := ExifFixedBytesLookup[byteOrder]
if fixedBytes != expectedFixedBytes { if fixedBytes != expectedFixedBytes {
exifLogger.Warningf(nil, "EXIF header fixed-bytes should be [%v] but are: [%v]", expectedFixedBytes, fixedBytes) exifLogger.Warningf(nil, "EXIF header fixed-bytes should be [%v] but are: [%v]", expectedFixedBytes, fixedBytes)
log.Panic(ErrNoExif) log.Panic(ErrNoExif)
} }
firstIfdOffset := byteOrder.Uint32(data[4:8]) firstIfdOffset := byteOrder.Uint32(data[4:8])
eh = ExifHeader{ eh = ExifHeader{
ByteOrder: byteOrder, ByteOrder: byteOrder,
FirstIfdOffset: firstIfdOffset, FirstIfdOffset: firstIfdOffset,
} }
return eh, nil return eh, nil
} }
// Visit recursively invokes a callback for every tag. // Visit recursively invokes a callback for every tag.
func Visit(exifData []byte, visitor RawTagVisitor) (eh ExifHeader, err error) { func Visit(exifData []byte, visitor RawTagVisitor) (eh ExifHeader, err error) {
defer func() { defer func() {
if state := recover(); state != nil { if state := recover(); state != nil {
err = log.Wrap(state.(error)) err = log.Wrap(state.(error))
} }
}() }()
eh, err = ParseExifHeader(exifData) eh, err = ParseExifHeader(exifData)
log.PanicIf(err) log.PanicIf(err)
ie := NewIfdEnumerate(exifData, eh.ByteOrder) ie := NewIfdEnumerate(exifData, eh.ByteOrder)
err = ie.Scan(eh.FirstIfdOffset, visitor, true) err = ie.Scan(eh.FirstIfdOffset, visitor, true)
log.PanicIf(err) log.PanicIf(err)
return eh, nil return eh, nil
} }
// Collect recursively builds a static structure of all IFDs and tags. // Collect recursively builds a static structure of all IFDs and tags.
func Collect(exifData []byte) (eh ExifHeader, index IfdIndex, err error) { func Collect(exifData []byte) (eh ExifHeader, index IfdIndex, err error) {
defer func() { defer func() {
if state := recover(); state != nil { if state := recover(); state != nil {
err = log.Wrap(state.(error)) err = log.Wrap(state.(error))
} }
}() }()
eh, err = ParseExifHeader(exifData) eh, err = ParseExifHeader(exifData)
log.PanicIf(err) log.PanicIf(err)
ie := NewIfdEnumerate(exifData, eh.ByteOrder) ie := NewIfdEnumerate(exifData, eh.ByteOrder)
index, err = ie.Collect(eh.FirstIfdOffset, true) index, err = ie.Collect(eh.FirstIfdOffset, true)
log.PanicIf(err) log.PanicIf(err)
return eh, index, nil return eh, index, nil
} }
// BuildExifHeader constructs the bytes that go in the very beginning. // BuildExifHeader constructs the bytes that go in the very beginning.
func BuildExifHeader(byteOrder binary.ByteOrder, firstIfdOffset uint32) (headerBytes []byte, err error) { func BuildExifHeader(byteOrder binary.ByteOrder, firstIfdOffset uint32) (headerBytes []byte, err error) {
defer func() { defer func() {
if state := recover(); state != nil { if state := recover(); state != nil {
err = log.Wrap(state.(error)) err = log.Wrap(state.(error))
} }
}() }()
b := new(bytes.Buffer) b := new(bytes.Buffer)
// This is the point in the data that all offsets are relative to. // This is the point in the data that all offsets are relative to.
boBytes := ByteOrderLookupR[byteOrder] boBytes := ByteOrderLookupR[byteOrder]
_, err = b.WriteString(string(boBytes[:])) _, err = b.WriteString(string(boBytes[:]))
log.PanicIf(err) log.PanicIf(err)
fixedBytes := ExifFixedBytesLookup[byteOrder] fixedBytes := ExifFixedBytesLookup[byteOrder]
_, err = b.Write(fixedBytes[:]) _, err = b.Write(fixedBytes[:])
log.PanicIf(err) log.PanicIf(err)
err = binary.Write(b, byteOrder, firstIfdOffset) err = binary.Write(b, byteOrder, firstIfdOffset)
log.PanicIf(err) log.PanicIf(err)
return b.Bytes(), nil return b.Bytes(), nil
} }

385
tags.go
View File

@ -1,242 +1,231 @@
package exif package exif
import ( import (
"os" "fmt"
"path"
"fmt"
"gopkg.in/yaml.v2" "github.com/dsoprea/go-logging"
"github.com/dsoprea/go-logging" "gopkg.in/yaml.v2"
) )
const ( const (
// IFD1 // IFD1
ThumbnailOffsetTagId = 0x0201 ThumbnailOffsetTagId = 0x0201
ThumbnailSizeTagId = 0x0202 ThumbnailSizeTagId = 0x0202
// Exif // Exif
TagVersionId = 0x0000 TagVersionId = 0x0000
TagLatitudeId = 0x0002 TagLatitudeId = 0x0002
TagLatitudeRefId = 0x0001 TagLatitudeRefId = 0x0001
TagLongitudeId = 0x0004 TagLongitudeId = 0x0004
TagLongitudeRefId = 0x0003 TagLongitudeRefId = 0x0003
TagTimestampId = 0x0007 TagTimestampId = 0x0007
TagDatestampId = 0x001d TagDatestampId = 0x001d
TagAltitudeId = 0x0006 TagAltitudeId = 0x0006
TagAltitudeRefId = 0x0005 TagAltitudeRefId = 0x0005
) )
var ( var (
tagDataFilepath = "" // tagsWithoutAlignment is a tag-lookup for tags whose value size won't
// necessarily be a multiple of its tag-type.
tagsWithoutAlignment = map[uint16]struct{}{
// The thumbnail offset is stored as a long, but its data is a binary
// blob (not a slice of longs).
ThumbnailOffsetTagId: struct{}{},
}
// tagsWithoutAlignment is a tag-lookup for tags whose value size won't tagIndex *TagIndex
// necessarily be a multiple of its tag-type.
tagsWithoutAlignment = map[uint16]struct{} {
// The thumbnail offset is stored as a long, but its data is a binary
// blob (not a slice of longs).
ThumbnailOffsetTagId: struct{}{},
}
tagIndex *TagIndex
) )
var ( var (
tagsLogger = log.NewLogger("exif.tags") tagsLogger = log.NewLogger("exif.tags")
) )
// File structures. // File structures.
type encodedTag struct { type encodedTag struct {
// id is signed, here, because YAML doesn't have enough information to // id is signed, here, because YAML doesn't have enough information to
// support unsigned. // support unsigned.
Id int `yaml:"id"` Id int `yaml:"id"`
Name string `yaml:"name"` Name string `yaml:"name"`
TypeName string `yaml:"type_name"` TypeName string `yaml:"type_name"`
} }
// Indexing structures. // Indexing structures.
type IndexedTag struct { type IndexedTag struct {
Id uint16 Id uint16
Name string Name string
Ifd string Ifd string
Type uint16 Type uint16
} }
func (it IndexedTag) String() string { func (it IndexedTag) String() string {
return fmt.Sprintf("TAG<ID=(0x%04x) NAME=[%s] IFD=[%s]>", it.Id, it.Name, it.Ifd) return fmt.Sprintf("TAG<ID=(0x%04x) NAME=[%s] IFD=[%s]>", it.Id, it.Name, it.Ifd)
} }
func (it IndexedTag) IsName(ifd, name string) bool { func (it IndexedTag) IsName(ifd, name string) bool {
return it.Name == name && it.Ifd == ifd return it.Name == name && it.Ifd == ifd
} }
func (it IndexedTag) Is(ifd string, id uint16) bool { func (it IndexedTag) Is(ifd string, id uint16) bool {
return it.Id == id && it.Ifd == ifd return it.Id == id && it.Ifd == ifd
} }
type TagIndex struct { type TagIndex struct {
tagsByIfd map[string]map[uint16]*IndexedTag tagsByIfd map[string]map[uint16]*IndexedTag
tagsByIfdR map[string]map[string]*IndexedTag tagsByIfdR map[string]map[string]*IndexedTag
} }
func NewTagIndex() *TagIndex { func NewTagIndex() *TagIndex {
ti := new(TagIndex) ti := new(TagIndex)
return ti return ti
} }
func (ti *TagIndex) load() (err error) { func (ti *TagIndex) load() (err error) {
defer func() { defer func() {
if state := recover(); state != nil { if state := recover(); state != nil {
err = log.Wrap(state.(error)) err = log.Wrap(state.(error))
} }
}() }()
// Already loaded. // Already loaded.
if ti.tagsByIfd != nil { if ti.tagsByIfd != nil {
return nil return nil
} }
// Read static data.
// Read static data. encodedIfds := make(map[string][]encodedTag)
encodedIfds := make(map[string][]encodedTag) err = yaml.Unmarshal([]byte(tagsYaml), encodedIfds)
log.PanicIf(err)
err = yaml.Unmarshal([]byte(tagsYaml), encodedIfds) // Load structure.
log.PanicIf(err)
tagsByIfd := make(map[string]map[uint16]*IndexedTag)
tagsByIfdR := make(map[string]map[string]*IndexedTag)
// Load structure. count := 0
for ifdName, tags := range encodedIfds {
for _, tagInfo := range tags {
tagId := uint16(tagInfo.Id)
tagName := tagInfo.Name
tagTypeName := tagInfo.TypeName
tagsByIfd := make(map[string]map[uint16]*IndexedTag) // TODO(dustin): !! Non-standard types, but found in real data. Ignore for right now.
tagsByIfdR := make(map[string]map[string]*IndexedTag) if tagTypeName == "SSHORT" || tagTypeName == "FLOAT" || tagTypeName == "DOUBLE" {
continue
}
count := 0 tagTypeId, found := TypeNamesR[tagTypeName]
for ifdName, tags := range encodedIfds { if found == false {
for _, tagInfo := range tags { log.Panicf("type [%s] for [%s] not valid", tagTypeName, tagName)
tagId := uint16(tagInfo.Id) continue
tagName := tagInfo.Name }
tagTypeName := tagInfo.TypeName
// TODO(dustin): !! Non-standard types, but found in real data. Ignore for right now. tag := &IndexedTag{
if tagTypeName == "SSHORT" || tagTypeName == "FLOAT" || tagTypeName == "DOUBLE" { Ifd: ifdName,
continue Id: tagId,
} Name: tagName,
Type: tagTypeId,
}
tagTypeId, found := TypeNamesR[tagTypeName] // Store by ID.
if found == false {
log.Panicf("type [%s] for [%s] not valid", tagTypeName, tagName)
continue
}
tag := &IndexedTag{ family, found := tagsByIfd[ifdName]
Ifd: ifdName, if found == false {
Id: tagId, family = make(map[uint16]*IndexedTag)
Name: tagName, tagsByIfd[ifdName] = family
Type: tagTypeId, }
}
if _, found := family[tagId]; found == true {
log.Panicf("tag-ID defined more than once for IFD [%s]: (%02x)", ifdName, tagId)
}
// Store by ID. family[tagId] = tag
family, found := tagsByIfd[ifdName] // Store by name.
if found == false {
family = make(map[uint16]*IndexedTag)
tagsByIfd[ifdName] = family
}
if _, found := family[tagId]; found == true { familyR, found := tagsByIfdR[ifdName]
log.Panicf("tag-ID defined more than once for IFD [%s]: (%02x)", ifdName, tagId) if found == false {
} familyR = make(map[string]*IndexedTag)
tagsByIfdR[ifdName] = familyR
}
family[tagId] = tag if _, found := familyR[tagName]; found == true {
log.Panicf("tag-name defined more than once for IFD [%s]: (%s)", ifdName, tagName)
}
familyR[tagName] = tag
// Store by name. count++
}
}
familyR, found := tagsByIfdR[ifdName] ti.tagsByIfd = tagsByIfd
if found == false { ti.tagsByIfdR = tagsByIfdR
familyR = make(map[string]*IndexedTag)
tagsByIfdR[ifdName] = familyR
}
if _, found := familyR[tagName]; found == true { tagsLogger.Debugf(nil, "(%d) tags loaded.", count)
log.Panicf("tag-name defined more than once for IFD [%s]: (%s)", ifdName, tagName)
}
familyR[tagName] = tag return nil
count++
}
}
ti.tagsByIfd = tagsByIfd
ti.tagsByIfdR = tagsByIfdR
tagsLogger.Debugf(nil, "(%d) tags loaded.", count)
return nil
} }
// Get returns information about the non-IFD tag. // Get returns information about the non-IFD tag.
func (ti *TagIndex) Get(ii IfdIdentity, id uint16) (it *IndexedTag, err error) { func (ti *TagIndex) Get(ii IfdIdentity, id uint16) (it *IndexedTag, err error) {
defer func() { defer func() {
if state := recover(); state != nil { if state := recover(); state != nil {
err = log.Wrap(state.(error)) err = log.Wrap(state.(error))
} }
}() }()
err = ti.load() err = ti.load()
log.PanicIf(err) log.PanicIf(err)
family, found := ti.tagsByIfd[ii.IfdName] family, found := ti.tagsByIfd[ii.IfdName]
if found == false { if found == false {
log.Panic(ErrTagNotFound) log.Panic(ErrTagNotFound)
} }
it, found = family[id] it, found = family[id]
if found == false { if found == false {
log.Panic(ErrTagNotFound) log.Panic(ErrTagNotFound)
} }
return it, nil return it, nil
} }
// Get returns information about the non-IFD tag. // Get returns information about the non-IFD tag.
func (ti *TagIndex) GetWithName(ii IfdIdentity, name string) (it *IndexedTag, err error) { func (ti *TagIndex) GetWithName(ii IfdIdentity, name string) (it *IndexedTag, err error) {
defer func() { defer func() {
if state := recover(); state != nil { if state := recover(); state != nil {
err = log.Wrap(state.(error)) err = log.Wrap(state.(error))
} }
}() }()
err = ti.load() err = ti.load()
log.PanicIf(err) log.PanicIf(err)
it, found := ti.tagsByIfdR[ii.IfdName][name] it, found := ti.tagsByIfdR[ii.IfdName][name]
if found != true { if found != true {
log.Panic(ErrTagNotFound) log.Panic(ErrTagNotFound)
} }
return it, nil return it, nil
} }
// IfdTagWithId returns true if the given tag points to a child IFD block. // IfdTagWithId returns true if the given tag points to a child IFD block.
func IfdTagNameWithIdOrFail(parentIfdName string, tagId uint16) string { func IfdTagNameWithIdOrFail(parentIfdName string, tagId uint16) string {
name, found := IfdTagNameWithId(parentIfdName, tagId) name, found := IfdTagNameWithId(parentIfdName, tagId)
if found == false { if found == false {
log.Panicf("tag-ID (0x%02x) under parent IFD [%s] not associated with a child IFD", tagId, parentIfdName) log.Panicf("tag-ID (0x%02x) under parent IFD [%s] not associated with a child IFD", tagId, parentIfdName)
} }
return name return name
} }
// IfdTagWithId returns true if the given tag points to a child IFD block. // IfdTagWithId returns true if the given tag points to a child IFD block.
@ -244,88 +233,80 @@ func IfdTagNameWithIdOrFail(parentIfdName string, tagId uint16) string {
// TODO(dustin): !! Rewrite to take an IfdIdentity, instead. We shouldn't expect that IFD names are globally unique. // TODO(dustin): !! Rewrite to take an IfdIdentity, instead. We shouldn't expect that IFD names are globally unique.
func IfdTagNameWithId(parentIfdName string, tagId uint16) (name string, found bool) { func IfdTagNameWithId(parentIfdName string, tagId uint16) (name string, found bool) {
if tags, found := IfdTagNames[parentIfdName]; found == true { if tags, found := IfdTagNames[parentIfdName]; found == true {
if name, found = tags[tagId]; found == true { if name, found = tags[tagId]; found == true {
return name, true return name, true
} }
} }
return "", false return "", false
} }
// IfdTagIdWithIdentity returns true if the given tag points to a child IFD // IfdTagIdWithIdentity returns true if the given tag points to a child IFD
// block. // block.
func IfdTagIdWithIdentity(ii IfdIdentity) (tagId uint16, found bool) { func IfdTagIdWithIdentity(ii IfdIdentity) (tagId uint16, found bool) {
if tags, found := IfdTagIds[ii.ParentIfdName]; found == true { if tags, found := IfdTagIds[ii.ParentIfdName]; found == true {
if tagId, found = tags[ii.IfdName]; found == true { if tagId, found = tags[ii.IfdName]; found == true {
return tagId, true return tagId, true
} }
} }
return 0, false return 0, false
} }
func IfdTagIdWithIdentityOrFail(ii IfdIdentity) (tagId uint16) { func IfdTagIdWithIdentityOrFail(ii IfdIdentity) (tagId uint16) {
if tags, found := IfdTagIds[ii.ParentIfdName]; found == true { if tags, found := IfdTagIds[ii.ParentIfdName]; found == true {
if tagId, found = tags[ii.IfdName]; found == true { if tagId, found = tags[ii.IfdName]; found == true {
if tagId == 0 { if tagId == 0 {
// This IFD is not the type that can be linked to from a tag. // This IFD is not the type that can be linked to from a tag.
log.Panicf("not a child IFD: [%s]", ii.IfdName) log.Panicf("not a child IFD: [%s]", ii.IfdName)
} }
return tagId return tagId
} }
} }
log.Panicf("no tag for invalid IFD identity: %v", ii) log.Panicf("no tag for invalid IFD identity: %v", ii)
return 0 return 0
} }
func IfdIdWithIdentity(ii IfdIdentity) int { func IfdIdWithIdentity(ii IfdIdentity) int {
id, _ := IfdIds[ii] id, _ := IfdIds[ii]
return id return id
} }
func IfdIdWithIdentityOrFail(ii IfdIdentity) int { func IfdIdWithIdentityOrFail(ii IfdIdentity) int {
id, _ := IfdIds[ii] id, _ := IfdIds[ii]
if id == 0 { if id == 0 {
log.Panicf("IFD not valid: %v", ii) log.Panicf("IFD not valid: %v", ii)
} }
return id return id
} }
func IfdIdOrFail(parentIfdName, ifdName string) (ii IfdIdentity, id int) { func IfdIdOrFail(parentIfdName, ifdName string) (ii IfdIdentity, id int) {
ii, id = IfdId(parentIfdName, ifdName) ii, id = IfdId(parentIfdName, ifdName)
if id == 0 { if id == 0 {
log.Panicf("IFD is not valid: [%s] [%s]", parentIfdName, ifdName) log.Panicf("IFD is not valid: [%s] [%s]", parentIfdName, ifdName)
} }
return ii, id return ii, id
} }
func IfdId(parentIfdName, ifdName string) (ii IfdIdentity, id int) { func IfdId(parentIfdName, ifdName string) (ii IfdIdentity, id int) {
ii = IfdIdentity{ ii = IfdIdentity{
ParentIfdName: parentIfdName, ParentIfdName: parentIfdName,
IfdName: ifdName, IfdName: ifdName,
} }
id, found := IfdIds[ii] id, found := IfdIds[ii]
if found != true { if found != true {
return IfdIdentity{}, 0 return IfdIdentity{}, 0
} }
return ii, id return ii, id
} }
func init() { func init() {
goPath := os.Getenv("GOPATH") tagIndex = NewTagIndex()
if goPath == "" {
log.Panicf("GOPATH is empty")
}
assetsPath := path.Join(goPath, "src", "github.com", "dsoprea", "go-exif", "assets")
tagDataFilepath = path.Join(assetsPath, "tags.yaml")
tagIndex = NewTagIndex()
} }

View File

@ -1,36 +0,0 @@
package exif
import (
"os"
"path"
"encoding/binary"
"io/ioutil"
"github.com/dsoprea/go-logging"
)
var (
TestDefaultByteOrder = binary.BigEndian
assetsPath = ""
testExifData = make([]byte, 0)
)
func init() {
goPath := os.Getenv("GOPATH")
if goPath == "" {
log.Panicf("GOPATH is empty")
}
assetsPath = path.Join(goPath, "src", "github.com", "dsoprea", "go-exif", "assets")
// Load test EXIF data.
filepath := path.Join(assetsPath, "NDM_8901.jpg.exif")
var err error
testExifData, err = ioutil.ReadFile(filepath)
log.PanicIf(err)
}