mirror of https://github.com/dsoprea/go-exif.git
thumbnails: Reimplemented to use normal decode flow.
- We added a trick to overcome the fact that the tag is a slice of longs rather than bytes (and would usually cause check errors and alignment problems), but this allows us to use the normal allocation and decoding flows that are used by normal tags. - Made things simpler, but we're still mis-encoding the thumbnail, somehow/somewhere.pull/3/head
parent
c93f37a85d
commit
75ff008365
116
ifd_builder.go
116
ifd_builder.go
|
@ -1,5 +1,10 @@
|
|||
package exif
|
||||
|
||||
// NOTES:
|
||||
//
|
||||
// The thumbnail offset and length tags shouldn't be set directly. Use the
|
||||
// (*IfdBuilder).SetThumbnail() method instead.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -246,6 +251,10 @@ type IfdBuilder struct {
|
|||
|
||||
// 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) {
|
||||
|
@ -339,6 +348,66 @@ 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)
|
||||
|
||||
|
@ -583,6 +652,27 @@ func (ib *IfdBuilder) Replace(tagId uint16, bt builderTag) (err error) {
|
|||
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 {
|
||||
|
@ -689,10 +779,32 @@ func (ib *IfdBuilder) AddTagsFromExisting(ifd *Ifd, itevr *IfdTagEntryValueResol
|
|||
}
|
||||
}()
|
||||
|
||||
thumbnailData, err := ifd.Thumbnail()
|
||||
if err == nil {
|
||||
|
||||
// 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 _, 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 != "" {
|
||||
// If we want to add an IFD tag, we'll have to build it first and
|
||||
// *then* add it via a different method.
|
||||
|
||||
continue
|
||||
} else if ite.TagId == ThumbnailOffsetTagId || ite.TagId == ThumbnailSizeTagId {
|
||||
// These will be added on-the-fly when we encode.
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
@ -234,10 +234,13 @@ func (ibe *IfdByteEncoder) encodeTagToBytes(ib *IfdBuilder, bt *builderTag, bw *
|
|||
|
||||
len_ := len(valueBytes)
|
||||
unitCount := uint32(len_) / typeSize
|
||||
remainder := uint32(len_) % typeSize
|
||||
|
||||
if remainder > 0 {
|
||||
log.Panicf("tag value of (%d) bytes not evenly divisible by type-size (%d)", len_, typeSize)
|
||||
if _, found := tagsWithoutAlignment[bt.tagId]; found == false {
|
||||
remainder := uint32(len_) % typeSize
|
||||
|
||||
if remainder > 0 {
|
||||
log.Panicf("tag (0x%02x) value of (%d) bytes not evenly divisible by type-size (%d)", bt.tagId, len_, typeSize)
|
||||
}
|
||||
}
|
||||
|
||||
err = bw.WriteUint32(unitCount)
|
||||
|
@ -337,6 +340,8 @@ func (ibe *IfdByteEncoder) encodeIfdToBytes(ib *IfdBuilder, ifdAddressableOffset
|
|||
log.PanicIf(err)
|
||||
|
||||
if childIfdBlock != nil {
|
||||
// We aren't allowed to have non-nil child IFDs if we're just
|
||||
// sizing things up.
|
||||
if nextIfdOffsetToWrite == 0 {
|
||||
log.Panicf("no IFD offset provided for child-IFDs; no new child-IFDs permitted")
|
||||
}
|
||||
|
@ -384,6 +389,23 @@ func (ibe *IfdByteEncoder) encodeIfdToBytes(ib *IfdBuilder, ifdAddressableOffset
|
|||
_, err = b.Write(dataBytes)
|
||||
log.PanicIf(err)
|
||||
|
||||
// TODO(dustin): !! Debugging.
|
||||
// if thumbnailOffset != uint32(0) {
|
||||
// currentRelativeOffset := thumbnailOffset - ifdAddressableOffset
|
||||
// currentBuffer := b.Bytes()
|
||||
|
||||
// len_ := len(thumbnailData)
|
||||
// extractedThumbnailData := currentBuffer[int(currentRelativeOffset):int(currentRelativeOffset) + len_]
|
||||
|
||||
// // We didn't have enough data available, which would be queer.
|
||||
// if len(extractedThumbnailData) != len_ {
|
||||
// log.Panicf("extracted thumbnail data was truncated; not enough data")
|
||||
// }
|
||||
|
||||
// // fmt.Printf("Re-extracted (%d) bytes of thumbnail data.\n", len(extractedThumbnailData))
|
||||
// // DumpBytes(extractedThumbnailData[:50])
|
||||
// }
|
||||
|
||||
// Append any child IFD blocks after our table and data blocks. These IFDs
|
||||
// were equipped with the appropriate offset information so it's expected
|
||||
// that all offsets referred to by these will be correct.
|
||||
|
@ -400,6 +422,13 @@ func (ibe *IfdByteEncoder) encodeIfdToBytes(ib *IfdBuilder, ifdAddressableOffset
|
|||
|
||||
ibe.pushToJournal("encodeIfdToBytes", "<", "%s", ib)
|
||||
|
||||
// if nextIfdOffsetToWrite > uint32(0) {
|
||||
// fmt.Printf("Encoded IB: %s ADDRESSABLE-OFFSET=(0x%08x; %d):\n", ib.ii, ifdAddressableOffset, ifdAddressableOffset)
|
||||
// fmt.Printf("\n")
|
||||
// DumpBytes(b.Bytes())
|
||||
// fmt.Printf("\n")
|
||||
// }
|
||||
|
||||
return b.Bytes(), tableSize, dataSize, childIfdSizes, nil
|
||||
}
|
||||
|
||||
|
|
123
ifd_enumerate.go
123
ifd_enumerate.go
|
@ -176,7 +176,7 @@ type TagVisitor func(ii IfdIdentity, ifdIndex int, tagId uint16, tagType TagType
|
|||
|
||||
// ParseIfd decodes the IFD block that we're currently sitting on the first
|
||||
// byte of.
|
||||
func (ie *IfdEnumerate) ParseIfd(ii IfdIdentity, ifdIndex int, ite *IfdTagEnumerator, visitor TagVisitor, doDescend bool) (nextIfdOffset uint32, entries []*IfdTagEntry, err error) {
|
||||
func (ie *IfdEnumerate) ParseIfd(ii IfdIdentity, ifdIndex int, ite *IfdTagEnumerator, visitor TagVisitor, doDescend bool) (nextIfdOffset uint32, entries []*IfdTagEntry, thumbnailData []byte, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
|
@ -188,12 +188,24 @@ func (ie *IfdEnumerate) ParseIfd(ii IfdIdentity, ifdIndex int, ite *IfdTagEnumer
|
|||
|
||||
ifdEnumerateLogger.Debugf(nil, "Current IFD tag-count: (%d)", tagCount)
|
||||
|
||||
entries = make([]*IfdTagEntry, tagCount)
|
||||
entries = make([]*IfdTagEntry, 0)
|
||||
|
||||
var iteThumbnailOffset *IfdTagEntry
|
||||
var iteThumbnailSize *IfdTagEntry
|
||||
|
||||
for i := 0; i < int(tagCount); i++ {
|
||||
tag, err := ie.parseTag(ii, i, ite)
|
||||
log.PanicIf(err)
|
||||
|
||||
if tag.TagId == ThumbnailOffsetTagId {
|
||||
iteThumbnailOffset = tag
|
||||
|
||||
continue
|
||||
} else if tag.TagId == ThumbnailSizeTagId {
|
||||
iteThumbnailSize = tag
|
||||
continue
|
||||
}
|
||||
|
||||
if visitor != nil {
|
||||
tt := NewTagType(tag.TagType, ie.byteOrder)
|
||||
|
||||
|
@ -220,7 +232,12 @@ func (ie *IfdEnumerate) ParseIfd(ii IfdIdentity, ifdIndex int, ite *IfdTagEnumer
|
|||
log.PanicIf(err)
|
||||
}
|
||||
|
||||
entries[i] = tag
|
||||
entries = append(entries, tag)
|
||||
}
|
||||
|
||||
if iteThumbnailOffset != nil && iteThumbnailSize != nil {
|
||||
thumbnailData, err = ie.parseThumbnail(iteThumbnailOffset, iteThumbnailSize)
|
||||
log.PanicIf(err)
|
||||
}
|
||||
|
||||
nextIfdOffset, _, err = ite.getUint32()
|
||||
|
@ -228,7 +245,37 @@ func (ie *IfdEnumerate) ParseIfd(ii IfdIdentity, ifdIndex int, ite *IfdTagEnumer
|
|||
|
||||
ifdEnumerateLogger.Debugf(nil, "Next IFD at offset: (%08x)", nextIfdOffset)
|
||||
|
||||
return nextIfdOffset, entries, nil
|
||||
return nextIfdOffset, entries, thumbnailData, nil
|
||||
}
|
||||
|
||||
func (ie *IfdEnumerate) parseThumbnail(offsetIte, lengthIte *IfdTagEntry) (thumbnailData []byte, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
addressableData := ie.exifData[ExifAddressableAreaStart:]
|
||||
|
||||
|
||||
vRaw, err := lengthIte.Value(addressableData, ie.byteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
vList := vRaw.([]uint32)
|
||||
if len(vList) != 1 {
|
||||
log.Panicf("not exactly one long: (%d)", len(vList))
|
||||
}
|
||||
|
||||
length := vList[0]
|
||||
|
||||
// The tag is official a LONG type, but it's actually an offset to a blob of bytes.
|
||||
offsetIte.TagType = TypeByte
|
||||
offsetIte.UnitCount = length
|
||||
|
||||
thumbnailData, err = offsetIte.ValueBytes(addressableData, ie.byteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
return thumbnailData, nil
|
||||
}
|
||||
|
||||
// Scan enumerates the different EXIF's IFD blocks.
|
||||
|
@ -243,7 +290,7 @@ func (ie *IfdEnumerate) scan(ii IfdIdentity, ifdOffset uint32, visitor TagVisito
|
|||
ifdEnumerateLogger.Debugf(nil, "Parsing IFD [%s] (%d) at offset (%04x).", ii.IfdName, ifdIndex, ifdOffset)
|
||||
ite := ie.getTagEnumerator(ifdOffset)
|
||||
|
||||
nextIfdOffset, _, err := ie.ParseIfd(ii, ifdIndex, ite, visitor, true)
|
||||
nextIfdOffset, _, _, err := ie.ParseIfd(ii, ifdIndex, ite, visitor, true)
|
||||
log.PanicIf(err)
|
||||
|
||||
if nextIfdOffset == 0 {
|
||||
|
@ -385,64 +432,6 @@ func (ifd Ifd) String() string {
|
|||
return fmt.Sprintf("Ifd<ID=(%d) PARENT-IFD=[%s] IFD=[%s] IDX=(%d) COUNT=(%d) OFF=(0x%04x) CHILDREN=(%d) PARENT=(0x%04x) NEXT-IFD=(0x%04x)", ifd.Id, ifd.Ii.ParentIfdName, ifd.Ii.IfdName, ifd.Index, len(ifd.Entries), ifd.Offset, len(ifd.Children), parentOffset, ifd.NextIfdOffset)
|
||||
}
|
||||
|
||||
func (ifd *Ifd) parseThumbnail() (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
results, err := ifd.FindTagWithId(ThumbnailOffsetTagId)
|
||||
if err != nil {
|
||||
if log.Is(err, ErrTagNotFound) == true {
|
||||
return nil
|
||||
} else {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
offsetIte := results[0]
|
||||
|
||||
vRaw, err := ifd.TagValue(offsetIte)
|
||||
log.PanicIf(err)
|
||||
|
||||
vList := vRaw.([]uint32)
|
||||
if len(vList) != 1 {
|
||||
log.Panicf("not exactly one long: (%d)", len(vList))
|
||||
}
|
||||
|
||||
offset := vList[0]
|
||||
|
||||
results, err = ifd.FindTagWithId(ThumbnailSizeTagId)
|
||||
if err != nil {
|
||||
if log.Is(err, ErrTagNotFound) == true {
|
||||
log.Panic(ErrNoThumbnail)
|
||||
} else {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
lengthIte := results[0]
|
||||
|
||||
vRaw, err = ifd.TagValue(lengthIte)
|
||||
log.PanicIf(err)
|
||||
|
||||
vList = vRaw.([]uint32)
|
||||
if len(vList) != 1 {
|
||||
log.Panicf("not exactly one long: (%d)", len(vList))
|
||||
}
|
||||
|
||||
length := vList[0]
|
||||
|
||||
if len(ifd.addressableData) < int(offset) + int(length) {
|
||||
log.Panicf("thumbnail size not valid: (%d) > (%d)", length, len(ifd.addressableData) - int(offset))
|
||||
}
|
||||
|
||||
ifd.thumbnailData = ifd.addressableData[offset:offset + length]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ifd *Ifd) Thumbnail() (data []byte, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
|
@ -737,7 +726,7 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
|
|||
ifdEnumerateLogger.Debugf(nil, "Parsing IFD [%s] (%d) at offset (%04x).", ii.IfdName, index, offset)
|
||||
ite := ie.getTagEnumerator(offset)
|
||||
|
||||
nextIfdOffset, entries, err := ie.ParseIfd(ii, index, ite, nil, false)
|
||||
nextIfdOffset, entries, thumbnailData, err := ie.ParseIfd(ii, index, ite, nil, false)
|
||||
log.PanicIf(err)
|
||||
|
||||
id := len(ifds)
|
||||
|
@ -766,11 +755,9 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
|
|||
EntriesByTagId: entriesByTagId,
|
||||
Children: make([]*Ifd, 0),
|
||||
NextIfdOffset: nextIfdOffset,
|
||||
thumbnailData: thumbnailData,
|
||||
}
|
||||
|
||||
err = ifd.parseThumbnail()
|
||||
log.PanicIf(err)
|
||||
|
||||
// Add ourselves to a big list of IFDs.
|
||||
ifds = append(ifds, &ifd)
|
||||
|
||||
|
@ -858,7 +845,7 @@ func ParseOneIfd(ii IfdIdentity, byteOrder binary.ByteOrder, ifdBlock []byte, vi
|
|||
|
||||
ite := NewIfdTagEnumerator(ifdBlock, byteOrder, 0)
|
||||
|
||||
nextIfdOffset, entries, err = ie.ParseIfd(ii, 0, ite, visitor, true)
|
||||
nextIfdOffset, entries, _, err = ie.ParseIfd(ii, 0, ite, visitor, true)
|
||||
log.PanicIf(err)
|
||||
|
||||
return nextIfdOffset, entries, nil
|
||||
|
|
8
tags.go
8
tags.go
|
@ -79,6 +79,14 @@ var (
|
|||
GpsIi: 3,
|
||||
ExifIopIi: 4,
|
||||
}
|
||||
|
||||
// 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{}{},
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
Loading…
Reference in New Issue