mirror of
https://github.com/dsoprea/go-exif.git
synced 2025-05-31 11:41:57 +00:00
- Updated IfdByteEncoder tests to use it instead of hacking-together their own BT's (makes for more standardized, consistent testing). - Universally refactored all core IFD knowledge implemented upon a single IFD name to instead work with IfdIdentity instances, instead, in order to validate that we only recognize the IFDs only in the context of the correct parents in the hierarchy. - Implemented standard testing byte-order (assigned to TestDefaultByteOrder).
344 lines
8.1 KiB
Go
344 lines
8.1 KiB
Go
package exif
|
|
|
|
import (
|
|
"os"
|
|
"path"
|
|
"fmt"
|
|
"errors"
|
|
|
|
"gopkg.in/yaml.v2"
|
|
"github.com/dsoprea/go-logging"
|
|
)
|
|
|
|
const (
|
|
IfdStandard = "IFD"
|
|
|
|
IfdExif = "Exif"
|
|
IfdGps = "GPSInfo"
|
|
IfdIop = "Iop"
|
|
|
|
IfdExifId = 0x8769
|
|
IfdGpsId = 0x8825
|
|
IfdIopId = 0xA005
|
|
)
|
|
|
|
var (
|
|
tagDataFilepath = ""
|
|
|
|
// TODO(dustin): !! Get rid of this in favor of one of the two lookups, just below.
|
|
validIfds = []string {
|
|
IfdStandard,
|
|
IfdExif,
|
|
IfdGps,
|
|
IfdIop,
|
|
}
|
|
|
|
// A lookup for IFDs by their parents.
|
|
// TODO(dustin): !! We should switch to indexing by their unique integer IDs (defined below) rather than exposing ourselves to non-unique IFD names (even if we *do* manage the naming ourselves).
|
|
IfdTagIds = map[string]map[string]uint16 {
|
|
"": map[string]uint16 {
|
|
// A root IFD type. Not allowed to be a child (tag-based) IFD.
|
|
IfdStandard: 0x0,
|
|
},
|
|
|
|
IfdStandard: map[string]uint16 {
|
|
IfdExif: IfdExifId,
|
|
IfdGps: IfdGpsId,
|
|
},
|
|
|
|
IfdExif: map[string]uint16 {
|
|
IfdIop: IfdIopId,
|
|
},
|
|
}
|
|
|
|
// IfdTagNames is populated in the init(), below.
|
|
IfdTagNames = map[string]map[uint16]string {}
|
|
|
|
// IFD Identities. These are often how we refer to IFDs, from call to call.
|
|
|
|
// The NULL-type instance for search misses and empty responses.
|
|
ZeroIi = IfdIdentity{}
|
|
|
|
RootIi = IfdIdentity{ IfdName: IfdStandard }
|
|
ExifIi = IfdIdentity{ ParentIfdName: IfdStandard, IfdName: IfdExif }
|
|
GpsIi = IfdIdentity{ ParentIfdName: IfdStandard, IfdName: IfdGps }
|
|
ExifIopIi = IfdIdentity{ ParentIfdName: IfdExif, IfdName: IfdIop }
|
|
|
|
// Produce a list of unique IDs for each IFD that we can pass around (so we
|
|
// don't always have to be comparing parent and child names).
|
|
//
|
|
// For lack of need, this is just static.
|
|
//
|
|
// (0) is reserved for not-found/miss responses.
|
|
IfdIds = map[IfdIdentity]int {
|
|
RootIi: 1,
|
|
ExifIi: 2,
|
|
GpsIi: 3,
|
|
ExifIopIi: 4,
|
|
}
|
|
)
|
|
|
|
var (
|
|
tagsLogger = log.NewLogger("exif.tags")
|
|
ErrTagNotFound = errors.New("tag not found")
|
|
)
|
|
|
|
|
|
type IfdIdentity struct {
|
|
ParentIfdName string
|
|
IfdName string
|
|
}
|
|
|
|
func (ii IfdIdentity) String() string {
|
|
return fmt.Sprintf("IfdIdentity<PARENT-NAME=[%s] NAME=[%s]>", ii.ParentIfdName, ii.IfdName)
|
|
}
|
|
|
|
func (ii IfdIdentity) Id() int {
|
|
return IfdIdWithIdentityOrFail(ii)
|
|
}
|
|
|
|
|
|
// File structures.
|
|
|
|
type encodedTag struct {
|
|
// id is signed, here, because YAML doesn't have enough information to
|
|
// support unsigned.
|
|
Id int `yaml:"id"`
|
|
Name string `yaml:"name"`
|
|
TypeName string `yaml:"type_name"`
|
|
}
|
|
|
|
|
|
// Indexing structures.
|
|
|
|
type IndexedTag struct {
|
|
Id uint16
|
|
Name string
|
|
Ifd string
|
|
Type uint16
|
|
}
|
|
|
|
func (it IndexedTag) String() string {
|
|
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 {
|
|
return it.Name == name && it.Ifd == ifd
|
|
}
|
|
|
|
func (it IndexedTag) Is(id uint16) bool {
|
|
return it.Id == id
|
|
}
|
|
|
|
type TagIndex struct {
|
|
tagsByIfd map[string]map[uint16]*IndexedTag
|
|
}
|
|
|
|
func NewTagIndex() *TagIndex {
|
|
ti := new(TagIndex)
|
|
|
|
err := ti.load()
|
|
log.PanicIf(err)
|
|
|
|
return ti
|
|
}
|
|
|
|
func (ti *TagIndex) load() (err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
|
|
// Read static data.
|
|
|
|
f, err := os.Open(tagDataFilepath)
|
|
log.PanicIf(err)
|
|
|
|
d := yaml.NewDecoder(f)
|
|
|
|
encodedIfds := make(map[string][]encodedTag)
|
|
|
|
err = d.Decode(encodedIfds)
|
|
log.PanicIf(err)
|
|
|
|
|
|
// Load structure.
|
|
|
|
tagsByIfd := make(map[string]map[uint16]*IndexedTag)
|
|
|
|
count := 0
|
|
for ifdName, tags := range encodedIfds {
|
|
for _, tagInfo := range tags {
|
|
tagId := uint16(tagInfo.Id)
|
|
tagName := tagInfo.Name
|
|
tagTypeName := tagInfo.TypeName
|
|
|
|
// TODO(dustin): !! Non-standard types but present types. Ignore for right now.
|
|
if tagTypeName == "SSHORT" || tagTypeName == "FLOAT" || tagTypeName == "DOUBLE" {
|
|
continue
|
|
}
|
|
|
|
tagTypeId, found := TypeNamesR[tagTypeName]
|
|
if found == false {
|
|
log.Panicf("type [%s] for [%s] not valid", tagTypeName, tagName)
|
|
continue
|
|
}
|
|
|
|
tag := &IndexedTag{
|
|
Ifd: ifdName,
|
|
Id: tagId,
|
|
Name: tagName,
|
|
Type: tagTypeId,
|
|
}
|
|
|
|
family, found := tagsByIfd[ifdName]
|
|
if found == false {
|
|
family = make(map[uint16]*IndexedTag)
|
|
tagsByIfd[ifdName] = family
|
|
}
|
|
|
|
if _, found := family[tagId]; found == true {
|
|
log.Panicf("tag-ID defined more than once for IFD [%s]: (%02x)", ifdName, tagId)
|
|
}
|
|
|
|
family[tagId] = tag
|
|
|
|
count++
|
|
}
|
|
}
|
|
|
|
ti.tagsByIfd = tagsByIfd
|
|
|
|
tagsLogger.Debugf(nil, "(%d) tags loaded.", count)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ti *TagIndex) Get(ii IfdIdentity, id uint16) (it *IndexedTag, err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
family, found := ti.tagsByIfd[ii.IfdName]
|
|
if found == false {
|
|
log.Panic(ErrTagNotFound)
|
|
}
|
|
|
|
it, found = family[id]
|
|
if found == false {
|
|
log.Panic(ErrTagNotFound)
|
|
}
|
|
|
|
return it, nil
|
|
}
|
|
|
|
// IfdTagWithId returns true if the given tag points to a child IFD block.
|
|
func IfdTagNameWithIdOrFail(parentIfdName string, tagId uint16) string {
|
|
name, found := IfdTagNameWithId(parentIfdName, tagId)
|
|
if found == false {
|
|
log.Panicf("tag-ID (0x%02x) under parent IFD [%s] not associated with a child IFD", tagId, parentIfdName)
|
|
}
|
|
|
|
return name
|
|
}
|
|
|
|
// IfdTagWithId returns true if the given tag points to a child IFD block.
|
|
func IfdTagNameWithId(parentIfdName string, tagId uint16) (name string, found bool) {
|
|
if tags, found := IfdTagNames[parentIfdName]; found == true {
|
|
if name, found = tags[tagId]; found == true {
|
|
return name, true
|
|
}
|
|
}
|
|
|
|
return "", false
|
|
}
|
|
|
|
// IfdTagIdWithIdentity returns true if the given tag points to a child IFD
|
|
// block.
|
|
func IfdTagIdWithIdentity(ii IfdIdentity) (tagId uint16, found bool) {
|
|
if tags, found := IfdTagIds[ii.ParentIfdName]; found == true {
|
|
if tagId, found = tags[ii.IfdName]; found == true {
|
|
return tagId, true
|
|
}
|
|
}
|
|
|
|
return 0, false
|
|
}
|
|
|
|
func IfdTagIdWithIdentityOrFail(ii IfdIdentity) (tagId uint16) {
|
|
if tags, found := IfdTagIds[ii.ParentIfdName]; found == true {
|
|
if tagId, found = tags[ii.IfdName]; found == true {
|
|
if tagId == 0 {
|
|
// This IFD is not the type that can be linked to from a tag.
|
|
log.Panicf("not a child IFD")
|
|
}
|
|
|
|
return tagId
|
|
}
|
|
}
|
|
|
|
log.Panicf("no tag for invalid IFD identity")
|
|
return 0
|
|
}
|
|
|
|
func IfdIdWithIdentity(ii IfdIdentity) int {
|
|
id, _ := IfdIds[ii]
|
|
return id
|
|
}
|
|
|
|
func IfdIdWithIdentityOrFail(ii IfdIdentity) int {
|
|
id, _ := IfdIds[ii]
|
|
if id == 0 {
|
|
log.Panicf("IFD not valid: %v", ii)
|
|
}
|
|
|
|
return id
|
|
}
|
|
|
|
func IfdIdOrFail(parentIfdName, ifdName string) (ii IfdIdentity, id int) {
|
|
ii, id = IfdId(parentIfdName, ifdName)
|
|
if id == 0 {
|
|
log.Panicf("IFD is not valid: [%s] [%s]", parentIfdName, ifdName)
|
|
}
|
|
|
|
return ii, id
|
|
}
|
|
|
|
func IfdId(parentIfdName, ifdName string) (ii IfdIdentity, id int) {
|
|
ii = IfdIdentity{
|
|
ParentIfdName: parentIfdName,
|
|
IfdName: ifdName,
|
|
}
|
|
|
|
id, found := IfdIds[ii]
|
|
if found != true {
|
|
return IfdIdentity{}, 0
|
|
}
|
|
|
|
return ii, id
|
|
}
|
|
|
|
func init() {
|
|
goPath := os.Getenv("GOPATH")
|
|
if goPath == "" {
|
|
log.Panicf("GOPATH is empty")
|
|
}
|
|
|
|
assetsPath := path.Join(goPath, "src", "github.com", "dsoprea", "go-exif", "assets")
|
|
tagDataFilepath = path.Join(assetsPath, "tags.yaml")
|
|
|
|
for ifdName, tags := range IfdTagIds {
|
|
tagsR := make(map[uint16]string)
|
|
|
|
for tagName, tagId := range tags {
|
|
tagsR[tagId] = tagName
|
|
}
|
|
|
|
IfdTagNames[ifdName] = tagsR
|
|
}
|
|
}
|