go-exif/tags.go
Dustin Oprea 9068786204 Rewired to use IFD-path strings instead of IfdIdentities.
- These are absolute representations of where an IFD is positioned with
  respect to the other IFDs. There is a more-specific, "fully-qualified"
  form of the IFD-path that allows you to express indices in order to
  refer to specific siblings.

- Eliminates issues with IFDs at different levels potentially having the
  same name (if that's what is required with a certain dataset/
  datasource).

- There is a specific IFD registry that controls the heirarchy of IFDs
  that we recognize and the tags associated with the child IFDs. This
  allows custom hierarchies replacing even the TIFF specification for
  which are expected in an image (so we can read other types of images
  or any potential IFD hierarchy, even non-image ones).

- IFD and IB instances embed the IFD-path and FQ IFD-path that they were
  found or built for.
2018-08-01 08:35:21 -04:00

230 lines
4.6 KiB
Go

package exif
import (
"fmt"
"github.com/dsoprea/go-logging"
"gopkg.in/yaml.v2"
)
const (
// IFD1
ThumbnailOffsetTagId = 0x0201
ThumbnailSizeTagId = 0x0202
// Exif
TagVersionId = 0x0000
TagLatitudeId = 0x0002
TagLatitudeRefId = 0x0001
TagLongitudeId = 0x0004
TagLongitudeRefId = 0x0003
TagTimestampId = 0x0007
TagDatestampId = 0x001d
TagAltitudeId = 0x0006
TagAltitudeRefId = 0x0005
)
var (
// 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{}{},
}
)
var (
tagsLogger = log.NewLogger("exif.tags")
)
// 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
IfdPath string
Type uint16
}
func (it *IndexedTag) String() string {
return fmt.Sprintf("TAG<ID=(0x%04x) NAME=[%s] IFD=[%s]>", it.Id, it.Name, it.IfdPath)
}
func (it *IndexedTag) IsName(ifdPath, name string) bool {
return it.Name == name && it.IfdPath == ifdPath
}
func (it *IndexedTag) Is(ifdPath string, id uint16) bool {
return it.Id == id && it.IfdPath == ifdPath
}
type TagIndex struct {
tagsByIfd map[string]map[uint16]*IndexedTag
tagsByIfdR map[string]map[string]*IndexedTag
}
func NewTagIndex() *TagIndex {
ti := new(TagIndex)
ti.tagsByIfd = make(map[string]map[uint16]*IndexedTag)
ti.tagsByIfdR = make(map[string]map[string]*IndexedTag)
return ti
}
func (ti *TagIndex) Add(it *IndexedTag) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// Store by ID.
family, found := ti.tagsByIfd[it.IfdPath]
if found == false {
family = make(map[uint16]*IndexedTag)
ti.tagsByIfd[it.IfdPath] = family
}
if _, found := family[it.Id]; found == true {
log.Panicf("tag-ID defined more than once for IFD [%s]: (%02x)", it.IfdPath, it.Id)
}
family[it.Id] = it
// Store by name.
familyR, found := ti.tagsByIfdR[it.IfdPath]
if found == false {
familyR = make(map[string]*IndexedTag)
ti.tagsByIfdR[it.IfdPath] = familyR
}
if _, found := familyR[it.Name]; found == true {
log.Panicf("tag-name defined more than once for IFD [%s]: (%s)", it.IfdPath, it.Name)
}
familyR[it.Name] = it
return nil
}
// Get returns information about the non-IFD tag.
func (ti *TagIndex) Get(ifdPath string, id uint16) (it *IndexedTag, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if len(ti.tagsByIfd) == 0 {
err := LoadStandardTags(ti)
log.PanicIf(err)
}
family, found := ti.tagsByIfd[ifdPath]
if found == false {
log.Panic(ErrTagNotFound)
}
it, found = family[id]
if found == false {
log.Panic(ErrTagNotFound)
}
return it, nil
}
// Get returns information about the non-IFD tag.
func (ti *TagIndex) GetWithName(ifdPath string, name string) (it *IndexedTag, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if len(ti.tagsByIfdR) == 0 {
err := LoadStandardTags(ti)
log.PanicIf(err)
}
it, found := ti.tagsByIfdR[ifdPath][name]
if found != true {
log.Panic(ErrTagNotFound)
}
return it, nil
}
// LoadStandardTags registers the tags that all devices/applications should
// support.
func LoadStandardTags(ti *TagIndex) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// Read static data.
encodedIfds := make(map[string][]encodedTag)
err = yaml.Unmarshal([]byte(tagsYaml), encodedIfds)
log.PanicIf(err)
// Load structure.
count := 0
for ifdPath, tags := range encodedIfds {
for _, tagInfo := range tags {
tagId := uint16(tagInfo.Id)
tagName := tagInfo.Name
tagTypeName := tagInfo.TypeName
// TODO(dustin): !! Non-standard types, but found in real data. 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
}
it := &IndexedTag{
IfdPath: ifdPath,
Id: tagId,
Name: tagName,
Type: tagTypeId,
}
err = ti.Add(it)
log.PanicIf(err)
count++
}
}
tagsLogger.Debugf(nil, "(%d) tags loaded.", count)
return nil
}