mirror of
https://github.com/dsoprea/go-exif.git
synced 2025-05-01 21:20:02 +00:00
- 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.
230 lines
4.6 KiB
Go
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
|
|
}
|