mirror of
https://github.com/dsoprea/go-exif.git
synced 2025-05-31 11:41:57 +00:00
builder: Added full IFD-building functionality.
- There are still some placeholders for things that we need to figure out. - Still needs tests.
This commit is contained in:
parent
52dafd070a
commit
f99faad160
12
exif_test.go
12
exif_test.go
@ -264,16 +264,16 @@ func TestCollect(t *testing.T) {
|
||||
if ite.IfdName == IfdExif {
|
||||
foundExif++
|
||||
|
||||
if IfdTags[ite.TagId] != IfdExif {
|
||||
t.Fatalf("EXIF IFD tag-ID mismatch: (0x%02x) [%s] != [%s]", ite.TagId, IfdTags[ite.TagId], IfdExif)
|
||||
if IfdTagNames[ite.TagId] != IfdExif {
|
||||
t.Fatalf("EXIF IFD tag-ID mismatch: (0x%02x) [%s] != [%s]", ite.TagId, IfdTagNames[ite.TagId], IfdExif)
|
||||
}
|
||||
}
|
||||
|
||||
if ite.IfdName == IfdGps {
|
||||
foundGps++
|
||||
|
||||
if IfdTags[ite.TagId] != IfdGps {
|
||||
t.Fatalf("EXIF GPS tag-ID mismatch: (0x%02x) [%s] != [%s]", ite.TagId, IfdTags[ite.TagId] != IfdGps)
|
||||
if IfdTagNames[ite.TagId] != IfdGps {
|
||||
t.Fatalf("EXIF GPS tag-ID mismatch: (0x%02x) [%s] != [%s]", ite.TagId, IfdTagNames[ite.TagId] != IfdGps)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -289,8 +289,8 @@ func TestCollect(t *testing.T) {
|
||||
if ite.IfdName == IfdIop {
|
||||
foundIop++
|
||||
|
||||
if IfdTags[ite.TagId] != IfdIop {
|
||||
t.Fatalf("IOP IFD tag-ID mismatch: (0x%02x) [%s] != [%s]", ite.TagId, IfdTags[ite.TagId], IfdIop)
|
||||
if IfdTagNames[ite.TagId] != IfdIop {
|
||||
t.Fatalf("IOP IFD tag-ID mismatch: (0x%02x) [%s] != [%s]", ite.TagId, IfdTagNames[ite.TagId], IfdIop)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
332
ifd_builder.go
Normal file
332
ifd_builder.go
Normal file
@ -0,0 +1,332 @@
|
||||
package exif
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
)
|
||||
|
||||
var (
|
||||
ifdBuilderLogger = log.NewLogger("exif.ifd_builder")
|
||||
)
|
||||
|
||||
var (
|
||||
ErrTagEntryNotFound = errors.New("tag entry not found")
|
||||
)
|
||||
|
||||
|
||||
// TODO(dustin): !! Make sure we either replace existing IFDs or validate that the IFD doesn't already exist.
|
||||
|
||||
|
||||
type builderTag struct {
|
||||
ifdName string
|
||||
tagId uint16
|
||||
|
||||
// value is either a value that can be encoded, an IfdBuilder instance (for
|
||||
// child IFDs), or an IfdTagEntry instance representing an existing,
|
||||
// previously-stored tag.
|
||||
value interface{}
|
||||
}
|
||||
|
||||
type IfdBuilder struct {
|
||||
ifdName string
|
||||
|
||||
// ifdTagId will be non-zero if we're a child IFD.
|
||||
ifdTagId uint16
|
||||
|
||||
byteOrder binary.ByteOrder
|
||||
|
||||
// Includes both normal tags and IFD tags (which point to child IFDs).
|
||||
tags []builderTag
|
||||
|
||||
// existingOffset will be the offset that this IFD is currently found at if
|
||||
// it represents an IFD that has previously been stored (or 0 if not).
|
||||
existingOffset uint32
|
||||
|
||||
// nextIfd represents the next link if we're chaining to another.
|
||||
nextIfd *IfdBuilder
|
||||
}
|
||||
|
||||
func NewIfdBuilder(ifdName string, byteOrder binary.ByteOrder) (ib *IfdBuilder) {
|
||||
ib = &IfdBuilder{
|
||||
ifdName: ifdName,
|
||||
ifdTagId: IfdTagIds[ifdName],
|
||||
byteOrder: byteOrder,
|
||||
tags: make([]builderTag, 0),
|
||||
}
|
||||
|
||||
return ib
|
||||
}
|
||||
|
||||
func NewIfdBuilderWithExistingIfd(ifd *Ifd, byteOrder binary.ByteOrder) (ib *IfdBuilder) {
|
||||
ib = &IfdBuilder{
|
||||
ifdName: ifd.Name,
|
||||
byteOrder: byteOrder,
|
||||
existingOffset: ifd.Offset,
|
||||
}
|
||||
|
||||
return ib
|
||||
}
|
||||
|
||||
func (ib *IfdBuilder) SetNextIfd(nextIfd *IfdBuilder) (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
ib.nextIfd = nextIfd
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ib *IfdBuilder) DeleteN(tagId uint16, n int) (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
if n < 1 {
|
||||
log.Panicf("N must be at least 1: (%d)", n)
|
||||
}
|
||||
|
||||
for ; n > 0; {
|
||||
j := -1
|
||||
for i, bt := range ib.tags {
|
||||
if bt.tagId == tagId {
|
||||
j = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if j == -1 {
|
||||
log.Panic(ErrTagEntryNotFound)
|
||||
}
|
||||
|
||||
ib.tags = append(ib.tags[:j], ib.tags[j + 1:]...)
|
||||
n--
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ib *IfdBuilder) DeleteFirst(tagId uint16) (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
err = ib.DeleteN(tagId, 1)
|
||||
log.PanicIf(err)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ib *IfdBuilder) DeleteAll(tagId uint16) (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
err = ib.DeleteN(tagId, 1)
|
||||
if log.Is(err, ErrTagEntryNotFound) == true {
|
||||
break
|
||||
} else if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ib *IfdBuilder) FindN(tagId uint16, maxFound int) (found []int, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
found = make([]int, 0)
|
||||
|
||||
for i, bt := range ib.tags {
|
||||
if bt.tagId == tagId {
|
||||
found = append(found, i)
|
||||
if maxFound == 0 || len(found) >= maxFound {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found, nil
|
||||
}
|
||||
|
||||
func (ib *IfdBuilder) Find(tagId uint16) (position int, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
found, err := ib.FindN(tagId, 1)
|
||||
log.PanicIf(err)
|
||||
|
||||
if len(found) == 0 {
|
||||
log.Panic(ErrTagEntryNotFound)
|
||||
}
|
||||
|
||||
return found[0], nil
|
||||
}
|
||||
|
||||
func (ib *IfdBuilder) ReplaceAt(position int, bt builderTag) (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
if position < 0 {
|
||||
log.Panicf("replacement position must be 0 or greater")
|
||||
} else if position >= len(ib.tags) {
|
||||
log.Panicf("replacement position does not exist")
|
||||
}
|
||||
|
||||
ib.tags[position] = bt
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ib *IfdBuilder) Replace(tagId uint16, bt builderTag) (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
position, err := ib.Find(tagId)
|
||||
log.PanicIf(err)
|
||||
|
||||
ib.tags[position] = bt
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(dustin): !! Switch to producing bytes immediately so that they're validated.
|
||||
|
||||
func (ib *IfdBuilder) Add(bt builderTag) (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
ib.tags = append(ib.tags, bt)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ib *IfdBuilder) AddChildIfd(childIfd *IfdBuilder) (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
// TODO(dustin): !! We might not want to take an actual IfdBuilder instance, as
|
||||
// these are mutable in nature (unless we definitely want to
|
||||
// allow them to tbe chnaged right up until they're actually
|
||||
// written). We might be better with a final, immutable tag
|
||||
// container insted.
|
||||
|
||||
if childIfd.ifdTagId == 0 {
|
||||
log.Panicf("IFD [%s] can not be used as a child IFD (not associated with a tag-ID)")
|
||||
}
|
||||
|
||||
bt := builderTag{
|
||||
ifdName: childIfd.ifdName,
|
||||
tagId: childIfd.ifdTagId,
|
||||
value: childIfd,
|
||||
}
|
||||
|
||||
ib.Add(bt)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddTagsFromExisting does a verbatim copy of the entries in `ifd` to this
|
||||
// builder. It excludes child IFDs. This must be added explicitly via
|
||||
// `AddChildIfd()`.
|
||||
func (ib *IfdBuilder) AddTagsFromExisting(ifd *Ifd, includeTagIds []uint16, excludeTagIds []uint16) (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
// Notes: This is used to update existing IFDs (by constructing a new IFD with existing information).
|
||||
// - How to handle the existing allocation? Obviously this will be an update
|
||||
// operation, we should try and re-use the current space.
|
||||
// - Inevitably, there will be fragmentation as IFDs are changed. We might not
|
||||
// be able to avoid reallocation.
|
||||
// - !! We'll potentially have to update every recorded tag and IFD offset.
|
||||
// - We might just have to refuse to allow updates if we encountered any
|
||||
// unmanageable tags (we'll definitely have to finish adding support for
|
||||
// the well-known ones).
|
||||
//
|
||||
// - An IfdEnumerator might not be the right type of argument, here. It actively
|
||||
// reads from a file and is not just a static container.
|
||||
// - We might want to create a static-container type that can populate from
|
||||
// an IfdEnumerator and then be read and re-read (like an IEnumerable vs IList).
|
||||
|
||||
|
||||
|
||||
for _, tag := range ifd.Entries {
|
||||
// If we want to add an IFD tag, we'll have to build it first and *then*
|
||||
// add it via a different method.
|
||||
if tag.IfdName != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if excludeTagIds != nil && len(excludeTagIds) > 0 {
|
||||
for _, excludedTagId := range excludeTagIds {
|
||||
if excludedTagId == tag.TagId {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if includeTagIds != nil && len(includeTagIds) > 0 {
|
||||
// Whether or not there was a list of excludes, if there is a list
|
||||
// of includes than the current tag has to be in it.
|
||||
|
||||
found := false
|
||||
for _, includedTagId := range includeTagIds {
|
||||
if includedTagId == tag.TagId {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if found == false {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
bt := builderTag{
|
||||
tagId: tag.TagId,
|
||||
|
||||
// TODO(dustin): !! For right now, a IfdTagEntry instance will mean that the value will have to be inherited/copied from an existing offset.
|
||||
value: tag,
|
||||
}
|
||||
|
||||
err := ib.Add(bt)
|
||||
log.PanicIf(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ifdLogger = log.NewLogger("exifjpeg.ifd")
|
||||
ifdEnumerateLogger = log.NewLogger("exifjpeg.ifd")
|
||||
)
|
||||
|
||||
|
||||
@ -147,7 +147,7 @@ func (ie *IfdEnumerate) ParseIfd(ifdName string, ifdIndex int, ifdOffset uint32,
|
||||
}
|
||||
}()
|
||||
|
||||
ifdLogger.Debugf(nil, "Parsing IFD [%s] (%d) at offset (%04x).", ifdName, ifdIndex, ifdOffset)
|
||||
ifdEnumerateLogger.Debugf(nil, "Parsing IFD [%s] (%d) at offset (%04x).", ifdName, ifdIndex, ifdOffset)
|
||||
|
||||
// Return the name of the IFD as its known in our tag-index. We should skip
|
||||
// over the current IFD if this is empty (which means we don't recognize/
|
||||
@ -159,7 +159,7 @@ func (ie *IfdEnumerate) ParseIfd(ifdName string, ifdIndex int, ifdOffset uint32,
|
||||
// they unwittingly write something that breaks in that situation.
|
||||
indexedIfdName := IfdName(ifdName, ifdIndex)
|
||||
if indexedIfdName == "" {
|
||||
ifdLogger.Debugf(nil, "IFD not known and will not be visited: [%s] (%d)", ifdName, ifdIndex)
|
||||
ifdEnumerateLogger.Debugf(nil, "IFD not known and will not be visited: [%s] (%d)", ifdName, ifdIndex)
|
||||
}
|
||||
|
||||
ite := ie.getTagEnumerator(ifdOffset)
|
||||
@ -167,7 +167,7 @@ func (ie *IfdEnumerate) ParseIfd(ifdName string, ifdIndex int, ifdOffset uint32,
|
||||
tagCount, _, err := ite.getUint16()
|
||||
log.PanicIf(err)
|
||||
|
||||
ifdLogger.Debugf(nil, "Current IFD tag-count: (%d)", tagCount)
|
||||
ifdEnumerateLogger.Debugf(nil, "Current IFD tag-count: (%d)", tagCount)
|
||||
|
||||
entries = make([]IfdTagEntry, tagCount)
|
||||
|
||||
@ -212,7 +212,7 @@ func (ie *IfdEnumerate) ParseIfd(ifdName string, ifdIndex int, ifdOffset uint32,
|
||||
tag.IfdName = childIfdName
|
||||
|
||||
if doDescend == true {
|
||||
ifdLogger.Debugf(nil, "Descending to IFD [%s].", childIfdName)
|
||||
ifdEnumerateLogger.Debugf(nil, "Descending to IFD [%s].", childIfdName)
|
||||
|
||||
err := ie.Scan(childIfdName, valueOffset, visitor)
|
||||
log.PanicIf(err)
|
||||
@ -225,7 +225,7 @@ func (ie *IfdEnumerate) ParseIfd(ifdName string, ifdIndex int, ifdOffset uint32,
|
||||
nextIfdOffset, _, err = ite.getUint32()
|
||||
log.PanicIf(err)
|
||||
|
||||
ifdLogger.Debugf(nil, "Next IFD at offset: (%08x)", nextIfdOffset)
|
||||
ifdEnumerateLogger.Debugf(nil, "Next IFD at offset: (%08x)", nextIfdOffset)
|
||||
|
||||
return nextIfdOffset, entries, nil
|
||||
}
|
||||
|
17
tags.go
17
tags.go
@ -20,11 +20,14 @@ const (
|
||||
var (
|
||||
tagDataFilepath = ""
|
||||
|
||||
IfdTags = map[uint16]string {
|
||||
0x8769: IfdExif,
|
||||
0x8825: IfdGps,
|
||||
0xA005: IfdIop,
|
||||
IfdTagIds = map[string]uint16 {
|
||||
IfdExif: 0x8769,
|
||||
IfdGps: 0x8825,
|
||||
IfdIop: 0xA005,
|
||||
}
|
||||
|
||||
// IfdTagNames is populated in the init(), below.
|
||||
IfdTagNames = map[uint16]string {}
|
||||
)
|
||||
|
||||
var (
|
||||
@ -174,7 +177,7 @@ func IfdName(ifdName string, ifdIndex int) string {
|
||||
|
||||
// IsIfdTag returns true if the given tag points to a child IFD block.
|
||||
func IsIfdTag(tagId uint16) (name string, found bool) {
|
||||
name, found = IfdTags[tagId]
|
||||
name, found = IfdTagNames[tagId]
|
||||
return name, found
|
||||
}
|
||||
|
||||
@ -186,4 +189,8 @@ func init() {
|
||||
|
||||
assetsPath := path.Join(goPath, "src", "github.com", "dsoprea", "go-exif", "assets")
|
||||
tagDataFilepath = path.Join(assetsPath, "tags.yaml")
|
||||
|
||||
for name, tagId := range IfdTagIds {
|
||||
IfdTagNames[tagId] = name
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user