mirror of
https://github.com/dsoprea/go-exif.git
synced 2025-05-31 11:41:57 +00:00
Reorganized to not require GOPATH unless testing.
This commit is contained in:
parent
7a8e5b005e
commit
dfbe003c0e
32
common_test.go
Normal file
32
common_test.go
Normal 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
285
exif.go
@ -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
385
tags.go
@ -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()
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user