mirror of https://github.com/dsoprea/go-exif.git
1008 lines
27 KiB
Go
1008 lines
27 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")
|
|
)
|
|
|
|
|
|
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 the IfdIdentity of the IFD that hosts this tag.
|
|
ii IfdIdentity
|
|
|
|
tagId uint16
|
|
typeId 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 NewBuilderTag(ii IfdIdentity, tagId uint16, typeId uint16, value *IfdBuilderTagValue) builderTag {
|
|
return builderTag{
|
|
ii: ii,
|
|
tagId: tagId,
|
|
typeId: typeId,
|
|
value: value,
|
|
}
|
|
}
|
|
|
|
func NewStandardBuilderTag(ii IfdIdentity, tagId uint16, value *IfdBuilderTagValue) builderTag {
|
|
ti := NewTagIndex()
|
|
|
|
it, err := ti.Get(ii, tagId)
|
|
log.PanicIf(err)
|
|
|
|
return builderTag{
|
|
ii: ii,
|
|
tagId: tagId,
|
|
typeId: it.Type,
|
|
value: value,
|
|
}
|
|
}
|
|
|
|
func NewChildIfdBuilderTag(ii IfdIdentity, tagId uint16, value *IfdBuilderTagValue) builderTag {
|
|
return builderTag{
|
|
ii: ii,
|
|
tagId: tagId,
|
|
typeId: TypeLong,
|
|
value: value,
|
|
}
|
|
}
|
|
|
|
func (bt builderTag) Value() (value *IfdBuilderTagValue) {
|
|
return bt.value
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
// NewStandardBuilderTagFromConfig constructs a `builderTag` instance. The type
|
|
// is looked up. `ii` is the type of IFD that owns this tag.
|
|
func NewStandardBuilderTagFromConfig(ii IfdIdentity, tagId uint16, byteOrder binary.ByteOrder, value interface{}) builderTag {
|
|
ti := NewTagIndex()
|
|
|
|
it, err := ti.Get(ii, tagId)
|
|
log.PanicIf(err)
|
|
|
|
typeId := it.Type
|
|
tt := NewTagType(typeId, byteOrder)
|
|
|
|
ve := NewValueEncoder(byteOrder)
|
|
|
|
var ed EncodedData
|
|
if it.Type == TypeUndefined {
|
|
var err error
|
|
|
|
ed, err = EncodeUndefined(ii, tagId, value)
|
|
log.PanicIf(err)
|
|
} else {
|
|
var err error
|
|
|
|
ed, err = ve.EncodeWithType(tt, value)
|
|
log.PanicIf(err)
|
|
}
|
|
|
|
tagValue := NewIfdBuilderTagValueFromBytes(ed.Encoded)
|
|
|
|
return NewBuilderTag(
|
|
ii,
|
|
tagId,
|
|
typeId,
|
|
tagValue)
|
|
}
|
|
|
|
// NewStandardBuilderTagFromConfig constructs a `builderTag` instance. The type is
|
|
// explicitly provided. `ii` is the type of IFD that owns this tag.
|
|
func NewBuilderTagFromConfig(ii IfdIdentity, tagId uint16, typeId uint16, byteOrder binary.ByteOrder, value interface{}) builderTag {
|
|
tt := NewTagType(typeId, byteOrder)
|
|
|
|
ve := NewValueEncoder(byteOrder)
|
|
|
|
var ed EncodedData
|
|
if typeId == TypeUndefined {
|
|
var err error
|
|
|
|
ed, err = EncodeUndefined(ii, tagId, value)
|
|
log.PanicIf(err)
|
|
} else {
|
|
var err error
|
|
|
|
ed, err = ve.EncodeWithType(tt, value)
|
|
log.PanicIf(err)
|
|
}
|
|
|
|
tagValue := NewIfdBuilderTagValueFromBytes(ed.Encoded)
|
|
|
|
return NewBuilderTag(
|
|
ii,
|
|
tagId,
|
|
typeId,
|
|
tagValue)
|
|
}
|
|
|
|
// NewStandardBuilderTagFromConfigWithName allows us to easily generate solid, consistent tags
|
|
// for testing with. `ii` is the type of IFD that owns this tag. This can not be
|
|
// an IFD (IFDs are not associated with standardized, official names).
|
|
func NewStandardBuilderTagFromConfigWithName(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)
|
|
|
|
var ed EncodedData
|
|
if it.Type == TypeUndefined {
|
|
var err error
|
|
|
|
ed, err = EncodeUndefined(ii, 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(
|
|
ii,
|
|
it.Id,
|
|
it.Type,
|
|
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
|
|
|
|
// thumbnailData is populated with thumbnail data if there was thumbnail
|
|
// data. Otherwise, it's nil.
|
|
thumbnailData []byte
|
|
}
|
|
|
|
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()
|
|
|
|
var ifdTagId uint16
|
|
|
|
// There is no tag-ID for the root IFD. It will never be a child IFD.
|
|
if ii != RootIi {
|
|
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, 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 {
|
|
ii := thisExistingIfd.Identity()
|
|
|
|
newIb := NewIfdBuilder(ii, 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) 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.ii.IfdName
|
|
}
|
|
|
|
return fmt.Sprintf("IfdBuilder<PARENT-IFD=[%s] IFD=[%s] TAG-ID=(0x%02x) COUNT=(%d) OFF=(0x%04x) NEXT-IFD=(0x%04x)>", ib.ii.ParentIfdName, ib.ii.IfdName, 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.ii != RootIi {
|
|
log.Panicf("thumbnails can only go into a root Ifd (and only the second one)")
|
|
}
|
|
|
|
// TODO(dustin): !! Add a test for this.
|
|
|
|
if data == nil || len(data) == 0 {
|
|
|
|
// TODO(dustin): !! Debugging.
|
|
// fmt.Printf("Thumbnail empty.\n")
|
|
|
|
log.Panic("thumbnail is empty")
|
|
}
|
|
|
|
ib.thumbnailData = data
|
|
|
|
// fmt.Printf("SETTING THUMBNAIL.\n")
|
|
|
|
ibtvfb := NewIfdBuilderTagValueFromBytes(ib.thumbnailData)
|
|
offsetBt := NewBuilderTag(ib.ii, ThumbnailOffsetTagId, TypeLong, ibtvfb)
|
|
// offsetBt := NewStandardBuilderTagFromConfig(ib.ii, ThumbnailOffsetTagId, ib.byteOrder, []uint32 { 0 })
|
|
|
|
err = ib.Set(offsetBt)
|
|
log.PanicIf(err)
|
|
|
|
sizeBt := NewStandardBuilderTagFromConfig(ib.ii, ThumbnailSizeTagId, ib.byteOrder, []uint32 { uint32(len(ib.thumbnailData)) })
|
|
|
|
err = ib.Set(sizeBt)
|
|
log.PanicIf(err)
|
|
|
|
|
|
// TODO(dustin): !! Debugging.
|
|
// fmt.Printf("Set thumbnail into IB.\n")
|
|
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ib *IfdBuilder) Thumbnail() []byte {
|
|
return ib.thumbnailData
|
|
}
|
|
|
|
func (ib *IfdBuilder) printTagTree(levels int) {
|
|
indent := strings.Repeat(" ", levels * 2)
|
|
|
|
ti := NewTagIndex()
|
|
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 := IfdTagNameWithId(currentIb.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.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 := IfdTagNameWithId(currentIb.ii.IfdName, tag.tagId)
|
|
|
|
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)
|
|
}
|
|
|
|
childIb := tag.value.Ib()
|
|
childIb.printIfdTree(levels + 1)
|
|
}
|
|
}
|
|
}
|
|
|
|
i++
|
|
}
|
|
}
|
|
|
|
func (ib *IfdBuilder) PrintIfdTree() {
|
|
ib.printIfdTree(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 {
|
|
childIfdName := ""
|
|
if tag.value.IsIb() == true {
|
|
childIfdName = tag.value.Ib().ii.IfdName
|
|
}
|
|
|
|
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, childIfdName, i, tag.tagId)
|
|
linesOutput = append(linesOutput, line)
|
|
|
|
if tag.value.IsIb() == true {
|
|
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) 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) add(bt builderTag) (err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
if bt.ii == ZeroIi {
|
|
log.Panicf("builderTag IfdIdentity 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.ii)
|
|
}
|
|
}
|
|
|
|
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.ii,
|
|
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 {
|
|
|
|
// TODO(dustin): The thumbnail tags will be added out of order.
|
|
|
|
// TODO(dustin): !! Debugging.
|
|
// fmt.Printf("Importing thumbnail: %s\n", ifd.Identity())
|
|
|
|
err = ib.SetThumbnail(thumbnailData)
|
|
log.PanicIf(err)
|
|
} else if log.Is(err, ErrNoThumbnail) == false {
|
|
log.Panic(err)
|
|
} else {
|
|
|
|
// TODO(dustin): !! Debugging.
|
|
// fmt.Printf("NO THUMBNAIL FOUND: %s\n", ifd.Identity())
|
|
|
|
}
|
|
|
|
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.ChildIfdName != "" {
|
|
// 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.Ii, ite.TagId, ite.TagType, value)
|
|
} 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%02x (parent tag-position %d)", childIfd.TagId, childIfd.ParentTagIndex)
|
|
}
|
|
|
|
log.Panicf("could not find child IFD for child ITE: II=[%s] TAG-ID=(0x%02x) CURRENT-TAG-POSITION=(%d) CHILDREN=%v", ite.Ii, 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
|
|
|
|
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)
|
|
}
|
|
|
|
value = NewIfdBuilderTagValueFromBytes(valueBytes)
|
|
}
|
|
|
|
bt = NewBuilderTag(ifd.Ii, ite.TagId, ite.TagType, value)
|
|
}
|
|
|
|
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 := NewStandardBuilderTagFromConfig(ib.ii, tagId, ib.byteOrder, value)
|
|
|
|
err = ib.add(bt)
|
|
log.PanicIf(err)
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetFromConfig 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) SetFromConfig(tagId uint16, value interface{}) (err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
// TODO(dustin): !! Add test.
|
|
|
|
bt := NewStandardBuilderTagFromConfig(ib.ii, tagId, ib.byteOrder, value)
|
|
|
|
i, err := ib.Find(tagId)
|
|
log.PanicIf(err)
|
|
|
|
ib.tags[i] = bt
|
|
|
|
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 := NewStandardBuilderTagFromConfigWithName(ib.ii, tagName, ib.byteOrder, value)
|
|
|
|
err = ib.add(bt)
|
|
log.PanicIf(err)
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetFromConfigWithName 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) SetFromConfigWithName(tagName string, value interface{}) (err error) {
|
|
defer func() {
|
|
if state := recover(); state != nil {
|
|
err = log.Wrap(state.(error))
|
|
}
|
|
}()
|
|
|
|
// TODO(dustin): !! Add test.
|
|
|
|
bt := NewStandardBuilderTagFromConfigWithName(ib.ii, tagName, ib.byteOrder, value)
|
|
|
|
i, err := ib.Find(bt.tagId)
|
|
log.PanicIf(err)
|
|
|
|
ib.tags[i] = bt
|
|
|
|
return nil
|
|
}
|