go-exif/ifd_builder.go

1258 lines
30 KiB
Go

package exif
// NOTES:
//
// The thumbnail offset and length tags shouldn't be set directly. Use the
// (*IfdBuilder).SetThumbnail() method instead.
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")
ErrChildIbNotFound = errors.New("child IB not found")
)
type IfdBuilderTagValue struct {
valueBytes []byte
ib *IfdBuilder
}
func (ibtv IfdBuilderTagValue) String() string {
if ibtv.IsBytes() == true {
var valuePhrase string
if len(ibtv.valueBytes) <= 8 {
valuePhrase = fmt.Sprintf("%v", ibtv.valueBytes)
} else {
valuePhrase = fmt.Sprintf("%v...", ibtv.valueBytes[:8])
}
return fmt.Sprintf("IfdBuilderTagValue<BYTES=%v LEN=(%d)>", valuePhrase, len(ibtv.valueBytes))
} else if ibtv.IsIb() == true {
return fmt.Sprintf("IfdBuilderTagValue<IB=%s>", ibtv.ib)
} else {
log.Panicf("IBTV state undefined")
return ""
}
}
func NewIfdBuilderTagValueFromBytes(valueBytes []byte) *IfdBuilderTagValue {
return &IfdBuilderTagValue{
valueBytes: valueBytes,
}
}
func NewIfdBuilderTagValueFromIfdBuilder(ib *IfdBuilder) *IfdBuilderTagValue {
return &IfdBuilderTagValue{
ib: ib,
}
}
// IsBytes returns true if the bytes are populated. This is always the case
// when we're loaded from a tag in an existing IFD.
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 {
// ifdPath is the path of the IFD that hosts this tag.
ifdPath string
tagId uint16
typeId TagTypePrimitive
// 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
// byteOrder is the byte order. It's chiefly/originally here to support
// printing the value.
byteOrder binary.ByteOrder
}
func NewBuilderTag(ifdPath string, tagId uint16, typeId TagTypePrimitive, value *IfdBuilderTagValue, byteOrder binary.ByteOrder) *BuilderTag {
return &BuilderTag{
ifdPath: ifdPath,
tagId: tagId,
typeId: typeId,
value: value,
byteOrder: byteOrder,
}
}
func NewChildIfdBuilderTag(ifdPath string, tagId uint16, value *IfdBuilderTagValue) *BuilderTag {
return &BuilderTag{
ifdPath: ifdPath,
tagId: tagId,
typeId: TypeLong,
value: value,
}
}
func (bt *BuilderTag) Value() (value *IfdBuilderTagValue) {
return bt.value
}
func (bt *BuilderTag) String() string {
var valueString string
if bt.value.IsBytes() == true {
tt := NewTagType(bt.typeId, bt.byteOrder)
var err error
valueString, err = tt.Format(bt.value.Bytes(), false)
log.PanicIf(err)
} else {
valueString = fmt.Sprintf("%v", bt.value)
}
return fmt.Sprintf("BuilderTag<IFD-PATH=[%s] TAG-ID=(0x%04x) TAG-TYPE=[%s] VALUE=[%s]>", bt.ifdPath, bt.tagId, TypeNames[bt.typeId], valueString)
}
func (bt *BuilderTag) SetValue(byteOrder binary.ByteOrder, value interface{}) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// TODO(dustin): !! Add test.
tt := NewTagType(bt.typeId, byteOrder)
ve := NewValueEncoder(byteOrder)
var ed EncodedData
if bt.typeId == TypeUndefined {
var err error
ed, err = EncodeUndefined(bt.ifdPath, bt.tagId, value)
log.PanicIf(err)
} else {
var err error
ed, err = ve.EncodeWithType(tt, value)
log.PanicIf(err)
}
bt.value = NewIfdBuilderTagValueFromBytes(ed.Encoded)
return nil
}
// NewStandardBuilderTag constructs a `BuilderTag` instance. The type is looked
// up. `ii` is the type of IFD that owns this tag.
func NewStandardBuilderTag(ifdPath string, it *IndexedTag, byteOrder binary.ByteOrder, value interface{}) *BuilderTag {
typeId := it.Type
tt := NewTagType(typeId, byteOrder)
ve := NewValueEncoder(byteOrder)
var ed EncodedData
if it.Type == TypeUndefined {
var err error
ed, err = EncodeUndefined(ifdPath, it.Id, value)
log.PanicIf(err)
} else {
var err error
ed, err = ve.EncodeWithType(tt, value)
log.PanicIf(err)
}
tagValue := NewIfdBuilderTagValueFromBytes(ed.Encoded)
return NewBuilderTag(
ifdPath,
it.Id,
typeId,
tagValue,
byteOrder)
}
type IfdBuilder struct {
// ifdName is the name of the IFD represented by this instance.
name string
// ifdPath is the path of the IFD represented by this instance.
ifdPath string
// fqIfdPath is the fully-qualified path of the IFD represented by this
// instance.
fqIfdPath 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).
// TODO(dustin): Keep a separate list of children like with IFd.
// TODO(dustin): Either rename this or `Entries` in Ifd to be the same thing.
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
// thumbnailData is populated with thumbnail data if there was thumbnail
// data. Otherwise, it's nil.
thumbnailData []byte
ifdMapping *IfdMapping
tagIndex *TagIndex
}
func NewIfdBuilder(ifdMapping *IfdMapping, tagIndex *TagIndex, fqIfdPath string, byteOrder binary.ByteOrder) (ib *IfdBuilder) {
ifdPath, err := ifdMapping.StripPathPhraseIndices(fqIfdPath)
log.PanicIf(err)
var ifdTagId uint16
mi, err := ifdMapping.GetWithPath(ifdPath)
if err == nil {
ifdTagId = mi.TagId
} else if log.Is(err, ErrChildIfdNotMapped) == false {
log.Panic(err)
}
ib = &IfdBuilder{
// The right-most part of the IFD-path.
name: mi.Name,
// ifdPath describes the current IFD placement within the IFD tree.
ifdPath: ifdPath,
// fqIfdPath describes the current IFD placement within the IFD tree as
// well as being qualified with non-zero indices.
fqIfdPath: fqIfdPath,
// ifdTagId is empty unless it's a child-IFD.
ifdTagId: ifdTagId,
byteOrder: byteOrder,
tags: make([]*BuilderTag, 0),
ifdMapping: ifdMapping,
tagIndex: tagIndex,
}
return ib
}
// NewIfdBuilderWithExistingIfd creates a new IB using the same header type
// information as the given IFD.
func NewIfdBuilderWithExistingIfd(ifd *Ifd) (ib *IfdBuilder) {
name := ifd.Name
ifdPath := ifd.IfdPath
fqIfdPath := ifd.FqIfdPath
var ifdTagId uint16
// There is no tag-ID for the root IFD. It will never be a child IFD.
if ifdPath != IfdPathStandard {
mi, err := ifd.ifdMapping.GetWithPath(ifdPath)
log.PanicIf(err)
ifdTagId = mi.TagId
}
ib = &IfdBuilder{
name: name,
ifdPath: ifdPath,
fqIfdPath: fqIfdPath,
ifdTagId: ifdTagId,
byteOrder: ifd.ByteOrder,
existingOffset: ifd.Offset,
ifdMapping: ifd.ifdMapping,
tagIndex: ifd.tagIndex,
}
return ib
}
// NewIfdBuilderFromExistingChain creates a chain of IB instances from an
// IFD chain generated from real data.
func NewIfdBuilderFromExistingChain(rootIfd *Ifd, itevr *IfdTagEntryValueResolver) (firstIb *IfdBuilder) {
// 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 lastIb *IfdBuilder
i := 0
for thisExistingIfd := rootIfd; thisExistingIfd != nil; thisExistingIfd = thisExistingIfd.NextIfd {
newIb := NewIfdBuilder(rootIfd.ifdMapping, rootIfd.tagIndex, rootIfd.FqIfdPath, thisExistingIfd.ByteOrder)
if firstIb == nil {
firstIb = newIb
} else {
lastIb.SetNextIb(newIb)
}
err := newIb.AddTagsFromExisting(thisExistingIfd, itevr, nil, nil)
log.PanicIf(err)
lastIb = newIb
i++
}
return firstIb
}
func (ib *IfdBuilder) NextIb() (nextIb *IfdBuilder, err error) {
return ib.nextIb, nil
}
func (ib *IfdBuilder) ChildWithTagId(childIfdTagId uint16) (childIb *IfdBuilder, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
for _, bt := range ib.tags {
if bt.value.IsIb() == false {
continue
}
childIbThis := bt.value.Ib()
if childIbThis.ifdTagId == childIfdTagId {
return childIbThis, nil
}
}
log.Panic(ErrChildIbNotFound)
// Never reached.
return nil, nil
}
func getOrCreateIbFromRootIbInner(rootIb *IfdBuilder, parentIb *IfdBuilder, currentLineage []IfdTagIdAndIndex) (ib *IfdBuilder, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// TODO(dustin): !! Add test.
thisIb := rootIb
// Since we're calling ourselves recursively with incrementally different
// paths, the FQ IFD-path of the parent that called us needs to be passed
// in, in order for us to know it.
var parentLineage []IfdTagIdAndIndex
if parentIb != nil {
var err error
parentLineage, err = thisIb.ifdMapping.ResolvePath(parentIb.fqIfdPath)
log.PanicIf(err)
}
// Process the current path part.
currentItii := currentLineage[0]
// Make sure the leftmost part of the FQ IFD-path agrees with the IB we
// were given.
expectedFqRootIfdPath := ""
if parentLineage != nil {
expectedLineage := append(parentLineage, currentItii)
expectedFqRootIfdPath = thisIb.ifdMapping.PathPhraseFromLineage(expectedLineage)
} else {
expectedFqRootIfdPath = thisIb.ifdMapping.PathPhraseFromLineage(currentLineage[:1])
}
if expectedFqRootIfdPath != thisIb.fqIfdPath {
log.Panicf("the FQ IFD-path [%s] we were given does not match the builder's FQ IFD-path [%s]", expectedFqRootIfdPath, thisIb.fqIfdPath)
}
// If we actually wanted a sibling (currentItii.Index > 0) then seek to it,
// appending new siblings, as required, until we get there.
for i := 0; i < currentItii.Index; i++ {
if thisIb.nextIb == nil {
// Generate an FQ IFD-path for the sibling. It'll use the same
// non-FQ IFD-path as the current IB.
siblingFqIfdPath := ""
if parentLineage != nil {
siblingFqIfdPath = fmt.Sprintf("%s/%s%d", parentIb.fqIfdPath, currentItii.Name, i+1)
} else {
siblingFqIfdPath = fmt.Sprintf("%s%d", currentItii.Name, i+1)
}
thisIb.nextIb = NewIfdBuilder(thisIb.ifdMapping, thisIb.tagIndex, siblingFqIfdPath, thisIb.byteOrder)
}
thisIb = thisIb.nextIb
}
// There is no child IFD to process. We're done.
if len(currentLineage) == 1 {
return thisIb, nil
}
// Establish the next child to be processed.
childItii := currentLineage[1]
var foundChild *IfdBuilder
for _, bt := range thisIb.tags {
if bt.value.IsIb() == false {
continue
}
childIb := bt.value.Ib()
if childIb.ifdTagId == childItii.TagId {
foundChild = childIb
break
}
}
// If we didn't find the child, add it.
if foundChild == nil {
thisIbLineage, err := thisIb.ifdMapping.ResolvePath(thisIb.fqIfdPath)
log.PanicIf(err)
childLineage := make([]IfdTagIdAndIndex, len(thisIbLineage)+1)
copy(childLineage, thisIbLineage)
childLineage[len(childLineage)-1] = childItii
fqIfdChildPath := thisIb.ifdMapping.FqPathPhraseFromLineage(childLineage)
foundChild = NewIfdBuilder(thisIb.ifdMapping, thisIb.tagIndex, fqIfdChildPath, thisIb.byteOrder)
err = thisIb.AddChildIb(foundChild)
log.PanicIf(err)
}
finalIb, err := getOrCreateIbFromRootIbInner(foundChild, thisIb, currentLineage[1:])
log.PanicIf(err)
return finalIb, nil
}
// GetOrCreateIbFromRootIb returns an IB representing the requested IFD, even if
// an IB doesn't already exist for it. This function may call itself
// recursively.
func GetOrCreateIbFromRootIb(rootIb *IfdBuilder, fqIfdPath string) (ib *IfdBuilder, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// lineage is a necessity of our recursion process. It doesn't include any
// parent IFDs on its left-side; it starts with the current IB only.
lineage, err := rootIb.ifdMapping.ResolvePath(fqIfdPath)
log.PanicIf(err)
ib, err = getOrCreateIbFromRootIbInner(rootIb, nil, lineage)
log.PanicIf(err)
return ib, nil
}
func (ib *IfdBuilder) String() string {
nextIfdPhrase := ""
if ib.nextIb != nil {
// TODO(dustin): We were setting this to ii.String(), but we were getting hex-data when printing this after building from an existing chain.
nextIfdPhrase = ib.nextIb.ifdPath
}
return fmt.Sprintf("IfdBuilder<PATH=[%s] TAG-ID=(0x%04x) COUNT=(%d) OFF=(0x%04x) NEXT-IFD-PATH=[%s]>", ib.ifdPath, ib.ifdTagId, len(ib.tags), ib.existingOffset, nextIfdPhrase)
}
func (ib *IfdBuilder) Tags() (tags []*BuilderTag) {
return ib.tags
}
// SetThumbnail sets thumbnail data.
//
// NOTES:
//
// - We don't manage any facet of the thumbnail data. This is the
// responsibility of the user/developer.
// - This method will fail unless the thumbnail is set on a the root IFD.
// However, in order to be valid, it must be set on the second one, linked to
// by the first, as per the EXIF/TIFF specification.
// - We set the offset to (0) now but will allocate the data and properly assign
// the offset when the IB is encoded (later).
func (ib *IfdBuilder) SetThumbnail(data []byte) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if ib.ifdPath != IfdPathStandard {
log.Panicf("thumbnails can only go into a root Ifd (and only the second one)")
}
// TODO(dustin): !! Add a test for this function.
if data == nil || len(data) == 0 {
log.Panic("thumbnail is empty")
}
ib.thumbnailData = data
ibtvfb := NewIfdBuilderTagValueFromBytes(ib.thumbnailData)
offsetBt :=
NewBuilderTag(
ib.ifdPath,
ThumbnailOffsetTagId,
TypeLong,
ibtvfb,
ib.byteOrder)
err = ib.Set(offsetBt)
log.PanicIf(err)
thumbnailSizeIt, err := ib.tagIndex.Get(ib.ifdPath, ThumbnailSizeTagId)
log.PanicIf(err)
sizeBt := NewStandardBuilderTag(ib.ifdPath, thumbnailSizeIt, ib.byteOrder, []uint32{uint32(len(ib.thumbnailData))})
err = ib.Set(sizeBt)
log.PanicIf(err)
return nil
}
func (ib *IfdBuilder) Thumbnail() []byte {
return ib.thumbnailData
}
func (ib *IfdBuilder) printTagTree(levels int) {
indent := strings.Repeat(" ", levels*2)
i := 0
for currentIb := ib; currentIb != nil; currentIb = currentIb.nextIb {
prefix := " "
if i > 0 {
prefix = ">"
}
if levels == 0 {
fmt.Printf("%s%sIFD: %s INDEX=(%d)\n", indent, prefix, currentIb, i)
} else {
fmt.Printf("%s%sChild IFD: %s\n", indent, prefix, currentIb)
}
if len(currentIb.tags) > 0 {
fmt.Printf("\n")
for i, tag := range currentIb.tags {
isChildIb := false
_, err := ib.ifdMapping.GetChild(currentIb.ifdPath, tag.tagId)
if err == nil {
isChildIb = true
} else if log.Is(err, ErrChildIfdNotMapped) == false {
log.Panic(err)
}
tagName := ""
// If a normal tag (not a child IFD) get the name.
if isChildIb == true {
tagName = "<Child IFD>"
} else {
it, err := ib.tagIndex.Get(tag.ifdPath, 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%04x) is an IFD but the tag value is not an IB instance: %v", tag.tagId, tag)
}
fmt.Printf("\n")
childIb := tag.value.Ib()
childIb.printTagTree(levels + 1)
}
}
fmt.Printf("\n")
}
i++
}
}
func (ib *IfdBuilder) PrintTagTree() {
ib.printTagTree(0)
}
func (ib *IfdBuilder) printIfdTree(levels int) {
indent := strings.Repeat(" ", levels*2)
i := 0
for currentIb := ib; currentIb != nil; currentIb = currentIb.nextIb {
prefix := " "
if i > 0 {
prefix = ">"
}
fmt.Printf("%s%s%s\n", indent, prefix, currentIb)
if len(currentIb.tags) > 0 {
for _, tag := range currentIb.tags {
isChildIb := false
_, err := ib.ifdMapping.GetChild(currentIb.ifdPath, tag.tagId)
if err == nil {
isChildIb = true
} else if log.Is(err, ErrChildIfdNotMapped) == false {
log.Panic(err)
}
if isChildIb == true {
if tag.value.IsIb() == false {
log.Panicf("tag-ID (0x%04x) is an IFD but the tag value is not an IB instance: %v", tag.tagId, tag)
}
childIb := tag.value.Ib()
childIb.printIfdTree(levels + 1)
}
}
}
i++
}
}
func (ib *IfdBuilder) PrintIfdTree() {
ib.printIfdTree(0)
}
func (ib *IfdBuilder) dumpToStrings(thisIb *IfdBuilder, prefix string, tagId uint16, lines []string) (linesOutput []string) {
if lines == nil {
linesOutput = make([]string, 0)
} else {
linesOutput = lines
}
siblingIfdIndex := 0
for ; thisIb != nil; thisIb = thisIb.nextIb {
line := fmt.Sprintf("IFD<PARENTS=[%s] FQ-IFD-PATH=[%s] IFD-INDEX=(%d) IFD-TAG-ID=(0x%04x) TAG=[0x%04x]>", prefix, thisIb.fqIfdPath, siblingIfdIndex, thisIb.ifdTagId, tagId)
linesOutput = append(linesOutput, line)
for i, tag := range thisIb.tags {
var childIb *IfdBuilder
childIfdName := ""
if tag.value.IsIb() == true {
childIb = tag.value.Ib()
childIfdName = childIb.ifdPath
}
line := fmt.Sprintf("TAG<PARENTS=[%s] FQ-IFD-PATH=[%s] IFD-TAG-ID=(0x%04x) CHILD-IFD=[%s] TAG-INDEX=(%d) TAG=[0x%04x]>", prefix, thisIb.fqIfdPath, thisIb.ifdTagId, childIfdName, i, tag.tagId)
linesOutput = append(linesOutput, line)
if childIb == nil {
continue
}
childPrefix := ""
if prefix == "" {
childPrefix = fmt.Sprintf("%s", thisIb.ifdPath)
} else {
childPrefix = fmt.Sprintf("%s->%s", prefix, thisIb.ifdPath)
}
linesOutput = thisIb.dumpToStrings(childIb, childPrefix, tag.tagId, linesOutput)
}
siblingIfdIndex++
}
return linesOutput
}
func (ib *IfdBuilder) DumpToStrings() (lines []string) {
return ib.dumpToStrings(ib, "", 0, lines)
}
func (ib *IfdBuilder) SetNextIb(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
}
// Set will add a new entry or update an existing entry.
func (ib *IfdBuilder) Set(bt *BuilderTag) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
position, err := ib.Find(bt.tagId)
if err == nil {
ib.tags[position] = bt
} else if log.Is(err, ErrTagEntryNotFound) == true {
err = ib.add(bt)
log.PanicIf(err)
} else {
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) FindTag(tagId uint16) (bt *BuilderTag, 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)
}
position := found[0]
return ib.tags[position], nil
}
func (ib *IfdBuilder) FindTagWithName(tagName string) (bt *BuilderTag, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
it, err := ib.tagIndex.GetWithName(ib.ifdPath, tagName)
log.PanicIf(err)
found, err := ib.FindN(it.Id, 1)
log.PanicIf(err)
if len(found) == 0 {
log.Panic(ErrTagEntryNotFound)
}
position := found[0]
return ib.tags[position], nil
}
func (ib *IfdBuilder) add(bt *BuilderTag) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if bt.ifdPath == "" {
log.Panicf("BuilderTag ifdPath is not set: %s", bt)
} else if bt.typeId == 0x0 {
log.Panicf("BuilderTag type-ID is not set: %s", bt)
} else if bt.value == nil {
log.Panicf("BuilderTag value is not set: %s", bt)
}
ib.tags = append(ib.tags, bt)
return 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() or AddTagsFromExisting(), not Add()")
}
err = ib.add(bt)
log.PanicIf(err)
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.ifdPath)
}
}
bt := ib.NewBuilderTagFromBuilder(childIb)
ib.tags = append(ib.tags, bt)
return nil
}
func (ib *IfdBuilder) NewBuilderTagFromBuilder(childIb *IfdBuilder) (bt *BuilderTag) {
defer func() {
if state := recover(); state != nil {
err := log.Wrap(state.(error))
log.Panic(err)
}
}()
value := NewIfdBuilderTagValueFromIfdBuilder(childIb)
bt = NewChildIfdBuilderTag(
ib.ifdPath,
childIb.ifdTagId,
value)
return bt
}
// 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))
}
}()
thumbnailData, err := ifd.Thumbnail()
if err == nil {
err = ib.SetThumbnail(thumbnailData)
log.PanicIf(err)
} else if log.Is(err, ErrNoThumbnail) == false {
log.Panic(err)
}
for i, ite := range ifd.Entries {
if ite.TagId == ThumbnailOffsetTagId || ite.TagId == ThumbnailSizeTagId {
// These will be added on-the-fly when we encode.
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
}
}
var bt *BuilderTag
if ite.ChildIfdPath != "" {
// If we want to add an IFD tag, we'll have to build it first and
// *then* add it via a different method.
if itevr == nil {
// We don't have any ability to resolve the structure of the
// child-IFD. Just install it as a normal tag rather than a
// fully-structured child-IFD. We're going to blank the value,
// though, since its original offset will no longer be valid
// (nor does it matter since this is just a temporary
// placeholder, in this situation).
value := NewIfdBuilderTagValueFromBytes([]byte{0, 0, 0, 0})
bt = NewBuilderTag(
ite.IfdPath,
ite.TagId,
ite.TagType,
value,
ib.byteOrder)
} else {
// Figure out which of the child-IFDs that are associated with
// this IFD represents this specific child IFD.
var childIfd *Ifd
for _, thisChildIfd := range ifd.Children {
if thisChildIfd.ParentTagIndex != i {
continue
} else if thisChildIfd.TagId != 0xffff && thisChildIfd.TagId != ite.TagId {
log.Panicf("child-IFD tag is not correct: TAG-POSITION=(%d) ITE=%s CHILD-IFD=%s", thisChildIfd.ParentTagIndex, ite, thisChildIfd)
}
childIfd = thisChildIfd
break
}
if childIfd == nil {
childTagIds := make([]string, len(ifd.Children))
for j, childIfd := range ifd.Children {
childTagIds[j] = fmt.Sprintf("0x%04x (parent tag-position %d)", childIfd.TagId, childIfd.ParentTagIndex)
}
log.Panicf("could not find child IFD for child ITE: IFD-PATH=[%s] TAG-ID=(0x%04x) CURRENT-TAG-POSITION=(%d) CHILDREN=%v", ite.IfdPath, ite.TagId, i, childTagIds)
}
childIb := NewIfdBuilderFromExistingChain(childIfd, itevr)
bt = ib.NewBuilderTagFromBuilder(childIb)
}
} else {
var value *IfdBuilderTagValue
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)
value = NewIfdBuilderTagValueFromBytes(rawValueOffsetCopy)
} else {
var err error
// TODO(dustin): !! Not correct. If we're adding from existing and it's an unknown-type tag that we can't parse, we're just going to be seting the placeholder even though there's nothing stopping us from just taking the raw bytes (other than some design decisions that we'd have to make in order to do this).
valueBytes, err := itevr.ValueBytes(ite)
if err != nil {
if log.Is(err, ErrUnhandledUnknownTypedTag) == true {
valueBytes = []byte(UnparseableUnknownTagValuePlaceholder)
} else {
log.Panic(err)
}
}
value = NewIfdBuilderTagValueFromBytes(valueBytes)
}
bt = NewBuilderTag(
ifd.IfdPath,
ite.TagId,
ite.TagType,
value,
ib.byteOrder)
}
err := ib.add(bt)
log.PanicIf(err)
}
return nil
}
// AddStandard quickly and easily composes and adds the tag using the
// information already known about a tag. Only works with standard tags.
func (ib *IfdBuilder) AddStandard(tagId uint16, value interface{}) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
it, err := ib.tagIndex.Get(ib.ifdPath, tagId)
log.PanicIf(err)
bt := NewStandardBuilderTag(ib.ifdPath, it, ib.byteOrder, value)
err = ib.add(bt)
log.PanicIf(err)
return nil
}
// AddStandardWithName 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) AddStandardWithName(tagName string, value interface{}) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
it, err := ib.tagIndex.GetWithName(ib.ifdPath, tagName)
log.PanicIf(err)
bt := NewStandardBuilderTag(ib.ifdPath, it, ib.byteOrder, value)
err = ib.add(bt)
log.PanicIf(err)
return nil
}
// SetStandard quickly and easily composes and adds or replaces the tag using
// the information already known about a tag. Only works with standard tags.
func (ib *IfdBuilder) SetStandard(tagId uint16, value interface{}) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// TODO(dustin): !! Add test for this function.
it, err := ib.tagIndex.Get(ib.ifdPath, tagId)
log.PanicIf(err)
bt := NewStandardBuilderTag(ib.ifdPath, it, ib.byteOrder, value)
i, err := ib.Find(tagId)
if err != nil {
if log.Is(err, ErrTagEntryNotFound) == false {
log.Panic(err)
}
ib.tags = append(ib.tags, bt)
} else {
ib.tags[i] = bt
}
return nil
}
// SetStandardWithName quickly and easily composes and adds or replaces the
// tag using the information already known about a tag (using the name). Only
// works with standard tags.
func (ib *IfdBuilder) SetStandardWithName(tagName string, value interface{}) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// TODO(dustin): !! Add test for this function.
it, err := ib.tagIndex.GetWithName(ib.ifdPath, tagName)
log.PanicIf(err)
bt := NewStandardBuilderTag(ib.ifdPath, it, ib.byteOrder, value)
i, err := ib.Find(bt.tagId)
if err != nil {
if log.Is(err, ErrTagEntryNotFound) == false {
log.Panic(err)
}
ib.tags = append(ib.tags, bt)
} else {
ib.tags[i] = bt
}
return nil
}