go-exif/ifd_builder.go

675 lines
18 KiB
Go

package exif
import (
"errors"
"fmt"
"strings"
"encoding/binary"
"github.com/dsoprea/go-logging"
)
var (
ifdBuilderLogger = log.NewLogger("exif.ifd_builder")
)
var (
ErrTagEntryNotFound = errors.New("tag entry not found")
)
type IfdBuilderTagValue struct {
valueBytes []byte
ib *IfdBuilder
}
func NewIfdBuilderTagValueFromBytes(valueBytes []byte) *IfdBuilderTagValue {
return &IfdBuilderTagValue{
valueBytes: valueBytes,
}
}
func NewIfdBuilderTagValueFromIfdBuilder(ib *IfdBuilder) *IfdBuilderTagValue {
return &IfdBuilderTagValue{
ib: ib,
}
}
func (ibtv IfdBuilderTagValue) IsBytes() bool {
return ibtv.valueBytes != nil
}
func (ibtv IfdBuilderTagValue) Bytes() []byte {
if ibtv.IsBytes() == false {
log.Panicf("this tag is not a byte-slice value")
}
return ibtv.valueBytes
}
func (ibtv IfdBuilderTagValue) IsIb() bool {
return ibtv.ib != nil
}
func (ibtv IfdBuilderTagValue) Ib() *IfdBuilder {
if ibtv.IsIb() == false {
log.Panicf("this tag is not an IFD-builder value")
}
return ibtv.ib
}
type builderTag struct {
// ii is non-empty if represents a child-IFD.
ii IfdIdentity
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 *IfdBuilderTagValue
}
func (bt builderTag) String() string {
valuePhrase := ""
if bt.value.IsBytes() == true {
valueBytes := bt.value.Bytes()
if len(valueBytes) <= 8 {
valuePhrase = fmt.Sprintf("%v", valueBytes)
} else {
valuePhrase = fmt.Sprintf("%v...", valueBytes[:8])
}
} else {
valuePhrase = fmt.Sprintf("%v", bt.value.Ib())
}
return fmt.Sprintf("BuilderTag<TAG-ID=(0x%02x) IFD=[%s] VALUE=[%v]>", bt.tagId, bt.ii, valuePhrase)
}
// NewBuilderTagFromConfig allows us to easily generate solid, consistent tags
// for testing with. `ii` is the tpye of IFD that owns this tag.
func NewBuilderTagFromConfig(ii IfdIdentity, tagId uint16, byteOrder binary.ByteOrder, value interface{}) builderTag {
var tagValue *IfdBuilderTagValue
switch value.(type) {
case *IfdBuilder:
tagValue = NewIfdBuilderTagValueFromIfdBuilder(value.(*IfdBuilder))
default:
ti := NewTagIndex()
it, err := ti.Get(ii, tagId)
log.PanicIf(err)
tt := NewTagType(it.Type, byteOrder)
ve := NewValueEncoder(byteOrder)
ed, err := ve.EncodeWithType(tt, value)
log.PanicIf(err)
tagValue = NewIfdBuilderTagValueFromBytes(ed.Encoded)
}
return builderTag{
ii: ii,
tagId: tagId,
value: tagValue,
}
}
// NewBuilderTagFromConfigWithName allows us to easily generate solid, consistent tags
// for testing with. `ii` is the tpye of IFD that owns this tag. This can not be
// an IFD (IFDs are not associated with standardized, official names).
func NewBuilderTagFromConfigWithName(ii IfdIdentity, tagName string, byteOrder binary.ByteOrder, value interface{}) builderTag {
ti := NewTagIndex()
it, err := ti.GetWithName(ii, tagName)
log.PanicIf(err)
tt := NewTagType(it.Type, byteOrder)
ve := NewValueEncoder(byteOrder)
ed, err := ve.EncodeWithType(tt, value)
log.PanicIf(err)
tagValue := NewIfdBuilderTagValueFromBytes(ed.Encoded)
return builderTag{
ii: ii,
tagId: it.Id,
value: tagValue,
}
}
type IfdBuilder struct {
// ifd is the IfdIdentity instance of the IFD that owns the current tag.
ii IfdIdentity
// 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
// nextIb represents the next link if we're chaining to another.
nextIb *IfdBuilder
}
func NewIfdBuilder(ii IfdIdentity, byteOrder binary.ByteOrder) (ib *IfdBuilder) {
ifdTagId, _ := IfdTagIdWithIdentity(ii)
ib = &IfdBuilder{
// ii describes the current IFD and its parent.
ii: ii,
// ifdTagId is empty unless it's a child-IFD.
ifdTagId: ifdTagId,
byteOrder: byteOrder,
tags: make([]builderTag, 0),
}
return ib
}
// NewIfdBuilderWithExistingIfd creates a new IB using the same header type
// information as the given IFD.
func NewIfdBuilderWithExistingIfd(ifd *Ifd) (ib *IfdBuilder) {
ii := ifd.Identity()
ifdTagId := IfdTagIdWithIdentityOrFail(ii)
ib = &IfdBuilder{
ii: ii,
ifdTagId: ifdTagId,
byteOrder: ifd.ByteOrder,
existingOffset: ifd.Offset,
}
return ib
}
// NewIfdBuilderFromExistingChain creates a chain of IB instances from an
// IFD chain generated from real data.
func NewIfdBuilderFromExistingChain(rootIfd *Ifd, exifData []byte) (rootIb *IfdBuilder) {
itevr := NewIfdTagEntryValueResolver(exifData, rootIfd.ByteOrder)
// TODO(dustin): !! When we actually write the code to flatten the IB to bytes, make sure to skip the tags that have a nil value (which will happen when we add-from-exsting without a resolver instance).
var newIb *IfdBuilder
for thisExistingIfd := rootIfd; thisExistingIfd != nil; thisExistingIfd = thisExistingIfd.NextIfd {
lastIb := newIb
ii := thisExistingIfd.Identity()
newIb = NewIfdBuilder(ii, binary.BigEndian)
if lastIb != nil {
lastIb.SetNextIfd(newIb)
}
if rootIb == nil {
rootIb = newIb
}
err := newIb.AddTagsFromExisting(thisExistingIfd, itevr, nil, nil)
log.PanicIf(err)
// Any child IFDs will still not be copied. Do that now.
for _, childIfd := range thisExistingIfd.Children {
childIb := NewIfdBuilderFromExistingChain(childIfd, exifData)
err = newIb.AddChildIb(childIb)
log.PanicIf(err)
}
}
return rootIb
}
func (ib *IfdBuilder) String() string {
nextIfdPhrase := ""
if ib.nextIb != nil {
nextIfdPhrase = ib.nextIb.ii.String()
}
return fmt.Sprintf("IfdBuilder<NAME=[%s] TAG-ID=(0x%02x) BO=[%s] COUNT=(%d) OFFSET=(0x%04x) NEXT-IFD=(0x%04x)>", ib.ii, ib.ifdTagId, ib.byteOrder, len(ib.tags), ib.existingOffset, nextIfdPhrase)
}
// // ifdOffsetIterator keeps track of where the next IFD should be written
// // (relative to the end of the EXIF header bytes; all addresses are relative to
// // this).
// type ifdOffsetIterator struct {
// offset uint32
// }
// func (ioi *ifdOffsetIterator) Step(size uint32) {
// ioi.offset += size
// }
// func (ioi *ifdOffsetIterator) Offset() uint32 {
// return ioi.offset
// }
func (ib *IfdBuilder) Tags() (tags []builderTag) {
return ib.tags
}
func (ib *IfdBuilder) dump(levels int) {
indent := strings.Repeat(" ", levels * 4)
if levels == 0 {
fmt.Printf("%sIFD: %s\n", indent, ib)
} else {
fmt.Printf("%sChild IFD: %s\n", indent, ib)
}
ti := NewTagIndex()
if len(ib.tags) > 0 {
fmt.Printf("\n")
for i, tag := range ib.tags {
_, isChildIb := IfdTagNameWithId(ib.ii.IfdName, tag.tagId)
tagName := ""
// If a normal tag (not a child IFD) get the name.
if isChildIb == true {
tagName = "<Child IFD>"
} else {
it, err := ti.Get(tag.ii, tag.tagId)
if log.Is(err, ErrTagNotFound) == true {
tagName = "<UNKNOWN>"
} else if err != nil {
log.Panic(err)
} else {
tagName = it.Name
}
}
fmt.Printf("%s (%d): [%s] %s\n", indent, i, tagName, tag)
if isChildIb == true {
if tag.value.IsIb() == false {
log.Panicf("tag-ID (0x%02x) is an IFD but the tag value is not an IB instance: %v", tag.tagId, tag)
}
fmt.Printf("\n")
childIb := tag.value.Ib()
childIb.dump(levels + 1)
}
}
fmt.Printf("\n")
}
}
func (ib *IfdBuilder) Dump() {
ib.dump(0)
}
func (ib *IfdBuilder) dumpToStrings(thisIb *IfdBuilder, prefix string, lines []string) (linesOutput []string) {
if lines == nil {
linesOutput = make([]string, 0)
} else {
linesOutput = lines
}
for i, tag := range thisIb.tags {
line := fmt.Sprintf("<PARENTS=[%s] IFD-NAME=[%s]> IFD-TAG-ID=(0x%02x) CHILD-IFD=[%s] INDEX=(%d) TAG=[0x%02x]", prefix, thisIb.ii.IfdName, thisIb.ifdTagId, tag.ii.IfdName, i, tag.tagId)
linesOutput = append(linesOutput, line)
if tag.ii.IfdName != "" {
if tag.value.IsIb() == false {
log.Panicf("tag has IFD tag-ID (0x%02x) and is acting like a child IB but does not *look* like a child IB: %v", tag.tagId, tag)
}
childPrefix := ""
if prefix == "" {
childPrefix = fmt.Sprintf("%s", thisIb.ii.IfdName)
} else {
childPrefix = fmt.Sprintf("%s->%s", prefix, thisIb.ii.IfdName)
}
linesOutput = thisIb.dumpToStrings(tag.value.Ib(), childPrefix, linesOutput)
}
}
return linesOutput
}
func (ib *IfdBuilder) DumpToStrings() (lines []string) {
return ib.dumpToStrings(ib, "", lines)
}
func (ib *IfdBuilder) SetNextIfd(nextIb *IfdBuilder) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
ib.nextIb = nextIb
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) (n int, 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)
}
n++
}
return n, 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
}
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) Add(bt builderTag) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if bt.value.IsIb() == true {
log.Panicf("child IfdBuilders must be added via AddChildIb() not Add()")
}
ib.tags = append(ib.tags, bt)
return nil
}
// AddChildIb adds a tag that branches to a new IFD.
func (ib *IfdBuilder) AddChildIb(childIb *IfdBuilder) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if childIb.ifdTagId == 0 {
log.Panicf("IFD can not be used as a child IFD (not associated with a tag-ID): %v", childIb)
} else if childIb.byteOrder != ib.byteOrder {
log.Panicf("Child IFD does not have the same byte-order: [%s] != [%s]", childIb.byteOrder, ib.byteOrder)
}
// Since no standard IFDs supports occuring more than once, check that a
// tag of this type has not been previously added. Note that we just search
// the current IFD and *not every* IFD.
for _, bt := range childIb.tags {
if bt.tagId == childIb.ifdTagId {
log.Panicf("child-IFD already added: %v", childIb.ii)
}
}
value := NewIfdBuilderTagValueFromIfdBuilder(childIb)
bt := builderTag{
ii: childIb.ii,
tagId: childIb.ifdTagId,
value: value,
}
ib.tags = append(ib.tags, bt)
return nil
}
// AddTagsFromExisting does a verbatim copy of the entries in `ifd` to this
// builder. It excludes child IFDs. These must be added explicitly via
// `AddChildIb()`.
func (ib *IfdBuilder) AddTagsFromExisting(ifd *Ifd, itevr *IfdTagEntryValueResolver, includeTagIds []uint16, excludeTagIds []uint16) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
for _, ite := 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 ite.ChildIfdName != "" {
continue
}
if excludeTagIds != nil && len(excludeTagIds) > 0 {
found := false
for _, excludedTagId := range excludeTagIds {
if excludedTagId == ite.TagId {
found = true
}
}
if found == true {
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 == ite.TagId {
found = true
break
}
}
if found == false {
continue
}
}
bt := builderTag{
tagId: ite.TagId,
}
if itevr == nil {
// rawValueOffsetCopy is our own private copy of the original data.
// It should always be four-bytes, but just copy whatever there is.
rawValueOffsetCopy := make([]byte, len(ite.RawValueOffset))
copy(rawValueOffsetCopy, ite.RawValueOffset)
bt.value = NewIfdBuilderTagValueFromBytes(rawValueOffsetCopy)
} else {
var err error
valueBytes, err := itevr.ValueBytes(&ite)
if err != nil {
if log.Is(err, ErrUnhandledUnknownTypedTag) == true {
ifdBuilderLogger.Warningf(nil, "Unknown-type tag can't be parsed so it can't be copied to the new IFD.")
continue
}
log.Panic(err)
}
bt.value = NewIfdBuilderTagValueFromBytes(valueBytes)
}
err := ib.Add(bt)
log.PanicIf(err)
}
return nil
}
// AddFromConfig quickly and easily composes and adds the tag using the
// information already known about a tag. Only works with standard tags.
func (ib *IfdBuilder) AddFromConfig(tagId uint16, value interface{}) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
bt := NewBuilderTagFromConfig(ib.ii, tagId, ib.byteOrder, value)
err = ib.Add(bt)
log.PanicIf(err)
return nil
}
// AddFromConfigWithName quickly and easily composes and adds the tag using the
// information already known about a tag (using the name). Only works with
// standard tags.
func (ib *IfdBuilder) AddFromConfigWithName(tagName string, value interface{}) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
bt := NewBuilderTagFromConfigWithName(ib.ii, tagName, ib.byteOrder, value)
err = ib.Add(bt)
log.PanicIf(err)
return nil
}