mirror of https://github.com/dsoprea/go-exif.git
ifd_enumerate: Now parse and expose thumbnail.
parent
d7ff9ccbbe
commit
c93f37a85d
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
102
ifd_enumerate.go
102
ifd_enumerate.go
|
@ -4,6 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
|
||||||
|
@ -12,6 +13,8 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ifdEnumerateLogger = log.NewLogger("exifjpeg.ifd")
|
ifdEnumerateLogger = log.NewLogger("exifjpeg.ifd")
|
||||||
|
|
||||||
|
ErrNoThumbnail = errors.New("no thumbnail")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -293,6 +296,8 @@ type Ifd struct {
|
||||||
Children []*Ifd
|
Children []*Ifd
|
||||||
NextIfdOffset uint32
|
NextIfdOffset uint32
|
||||||
NextIfd *Ifd
|
NextIfd *Ifd
|
||||||
|
|
||||||
|
thumbnailData []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ifd *Ifd) TagValue(ite *IfdTagEntry) (value interface{}, err error) {
|
func (ifd *Ifd) TagValue(ite *IfdTagEntry) (value interface{}, err error) {
|
||||||
|
@ -323,7 +328,7 @@ func (ifd *Ifd) TagValueBytes(ite *IfdTagEntry) (value []byte, err error) {
|
||||||
|
|
||||||
// FindTagWithId returns a list of tags (usually just zero or one) that match
|
// FindTagWithId returns a list of tags (usually just zero or one) that match
|
||||||
// the given tag ID. This is efficient.
|
// the given tag ID. This is efficient.
|
||||||
func (ifd Ifd) FindTagWithId(tagId uint16) (results []*IfdTagEntry, err error) {
|
func (ifd *Ifd) FindTagWithId(tagId uint16) (results []*IfdTagEntry, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if state := recover(); state != nil {
|
if state := recover(); state != nil {
|
||||||
err = log.Wrap(state.(error))
|
err = log.Wrap(state.(error))
|
||||||
|
@ -340,7 +345,7 @@ func (ifd Ifd) FindTagWithId(tagId uint16) (results []*IfdTagEntry, err error) {
|
||||||
|
|
||||||
// FindTagWithName returns a list of tags (usually just zero or one) that match
|
// FindTagWithName returns a list of tags (usually just zero or one) that match
|
||||||
// the given tag name. This is not efficient (though the labor is trivial).
|
// the given tag name. This is not efficient (though the labor is trivial).
|
||||||
func (ifd Ifd) FindTagWithName(tagName string) (results []*IfdTagEntry, err error) {
|
func (ifd *Ifd) FindTagWithName(tagName string) (results []*IfdTagEntry, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if state := recover(); state != nil {
|
if state := recover(); state != nil {
|
||||||
err = log.Wrap(state.(error))
|
err = log.Wrap(state.(error))
|
||||||
|
@ -380,11 +385,83 @@ 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)
|
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) Identity() IfdIdentity {
|
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 {
|
||||||
|
err = log.Wrap(state.(error))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if ifd.thumbnailData == nil {
|
||||||
|
log.Panic(ErrNoThumbnail)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ifd.thumbnailData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ifd *Ifd) Identity() IfdIdentity {
|
||||||
return ifd.Ii
|
return ifd.Ii
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ifd Ifd) dumpTags(tags []*IfdTagEntry) []*IfdTagEntry {
|
func (ifd *Ifd) dumpTags(tags []*IfdTagEntry) []*IfdTagEntry {
|
||||||
if tags == nil {
|
if tags == nil {
|
||||||
tags = make([]*IfdTagEntry, 0)
|
tags = make([]*IfdTagEntry, 0)
|
||||||
}
|
}
|
||||||
|
@ -427,11 +504,11 @@ func (ifd Ifd) dumpTags(tags []*IfdTagEntry) []*IfdTagEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DumpTags prints the IFD hierarchy.
|
// DumpTags prints the IFD hierarchy.
|
||||||
func (ifd Ifd) DumpTags() []*IfdTagEntry {
|
func (ifd *Ifd) DumpTags() []*IfdTagEntry {
|
||||||
return ifd.dumpTags(nil)
|
return ifd.dumpTags(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ifd Ifd) printTagTree(index, level int, nextLink bool) {
|
func (ifd *Ifd) printTagTree(index, level int, nextLink bool) {
|
||||||
indent := strings.Repeat(" ", level * 2)
|
indent := strings.Repeat(" ", level * 2)
|
||||||
|
|
||||||
prefix := " "
|
prefix := " "
|
||||||
|
@ -489,11 +566,11 @@ func (ifd Ifd) printTagTree(index, level int, nextLink bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrintTagTree prints the IFD hierarchy.
|
// PrintTagTree prints the IFD hierarchy.
|
||||||
func (ifd Ifd) PrintTagTree() {
|
func (ifd *Ifd) PrintTagTree() {
|
||||||
ifd.printTagTree(0, 0, false)
|
ifd.printTagTree(0, 0, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ifd Ifd) printIfdTree(level int, nextLink bool) {
|
func (ifd *Ifd) printIfdTree(level int, nextLink bool) {
|
||||||
indent := strings.Repeat(" ", level * 2)
|
indent := strings.Repeat(" ", level * 2)
|
||||||
|
|
||||||
prefix := " "
|
prefix := " "
|
||||||
|
@ -537,11 +614,11 @@ func (ifd Ifd) printIfdTree(level int, nextLink bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrintIfdTree prints the IFD hierarchy.
|
// PrintIfdTree prints the IFD hierarchy.
|
||||||
func (ifd Ifd) PrintIfdTree() {
|
func (ifd *Ifd) PrintIfdTree() {
|
||||||
ifd.printIfdTree(0, false)
|
ifd.printIfdTree(0, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ifd Ifd) dumpTree(tagsDump []string, level int) []string {
|
func (ifd *Ifd) dumpTree(tagsDump []string, level int) []string {
|
||||||
if tagsDump == nil {
|
if tagsDump == nil {
|
||||||
tagsDump = make([]string, 0)
|
tagsDump = make([]string, 0)
|
||||||
}
|
}
|
||||||
|
@ -599,7 +676,7 @@ func (ifd Ifd) dumpTree(tagsDump []string, level int) []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DumpTree returns a list of strings describing the IFD hierarchy.
|
// DumpTree returns a list of strings describing the IFD hierarchy.
|
||||||
func (ifd Ifd) DumpTree() []string {
|
func (ifd *Ifd) DumpTree() []string {
|
||||||
return ifd.dumpTree(nil, 0)
|
return ifd.dumpTree(nil, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -691,6 +768,9 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
|
||||||
NextIfdOffset: nextIfdOffset,
|
NextIfdOffset: nextIfdOffset,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = ifd.parseThumbnail()
|
||||||
|
log.PanicIf(err)
|
||||||
|
|
||||||
// Add ourselves to a big list of IFDs.
|
// Add ourselves to a big list of IFDs.
|
||||||
ifds = append(ifds, &ifd)
|
ifds = append(ifds, &ifd)
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
"github.com/dsoprea/go-logging"
|
"github.com/dsoprea/go-logging"
|
||||||
)
|
)
|
||||||
|
@ -277,3 +278,34 @@ func Test_Ifd_FindTagWithName_NonStandard(t *testing.T) {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_Ifd_Thumbnail(t *testing.T) {
|
||||||
|
filepath := path.Join(assetsPath, "NDM_8901.jpg")
|
||||||
|
|
||||||
|
e := NewExif()
|
||||||
|
|
||||||
|
rawExif, err := e.SearchAndExtractExif(filepath)
|
||||||
|
log.PanicIf(err)
|
||||||
|
|
||||||
|
_, index, err := e.Collect(rawExif)
|
||||||
|
log.PanicIf(err)
|
||||||
|
|
||||||
|
ifd := index.RootIfd
|
||||||
|
|
||||||
|
if ifd.NextIfd == nil {
|
||||||
|
t.Fatalf("There is no IFD1.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The thumbnail is in IFD1 (The second root IFD).
|
||||||
|
actual, err := ifd.NextIfd.Thumbnail()
|
||||||
|
log.PanicIf(err)
|
||||||
|
|
||||||
|
expectedFilepath := path.Join(assetsPath, "NDM_8901.jpg.thumbnail")
|
||||||
|
|
||||||
|
expected, err := ioutil.ReadFile(expectedFilepath)
|
||||||
|
log.PanicIf(err)
|
||||||
|
|
||||||
|
if bytes.Compare(actual, expected) != 0 {
|
||||||
|
t.Fatalf("thumbnail not correct")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue