mirror of https://github.com/dsoprea/go-exif.git
ifd_enumerate. The IFD tree and content can now be collected.
- Collected into a static structure in addition to scanned (which is - only a visitor pattern). - Test still has be finished.pull/3/head
parent
acbda6d1e1
commit
679ea19d6c
56
exif.go
56
exif.go
|
@ -1,9 +1,11 @@
|
|||
package exif
|
||||
|
||||
import (
|
||||
"os"
|
||||
"errors"
|
||||
"bytes"
|
||||
|
||||
"io/ioutil"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
|
@ -34,6 +36,42 @@ func (e *Exif) IsExif(data []byte) (ok bool) {
|
|||
return false
|
||||
}
|
||||
|
||||
func (e *Exif) SearchAndExtractExif(filepath string) (rawExif []byte, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err := log.Wrap(state.(error))
|
||||
log.Panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Open the file.
|
||||
|
||||
f, err := os.Open(filepath)
|
||||
log.PanicIf(err)
|
||||
|
||||
defer f.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(f)
|
||||
log.PanicIf(err)
|
||||
|
||||
// Search for the beginning of the EXIF information. The EXIF is near the
|
||||
// beginning of our/most JPEGs, so this has a very low cost.
|
||||
|
||||
foundAt := -1
|
||||
for i := 0; i < len(data); i++ {
|
||||
if e.IsExif(data[i:i + 6]) == true {
|
||||
foundAt = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if foundAt == -1 {
|
||||
log.Panicf("EXIF start not found")
|
||||
}
|
||||
|
||||
return data[foundAt:], nil
|
||||
}
|
||||
|
||||
|
||||
type ExifHeader struct {
|
||||
ByteOrder binary.ByteOrder
|
||||
|
@ -103,3 +141,21 @@ func (e *Exif) Visit(data []byte, visitor TagVisitor) (err error) {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Exif) Collect(data []byte) (rootIfd *Ifd, tree map[int]*Ifd, ifds []*Ifd, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
eh, err := e.ParseExifHeader(data)
|
||||
log.PanicIf(err)
|
||||
|
||||
ie := NewIfdEnumerate(data, eh.ByteOrder)
|
||||
|
||||
rootIfd, tree, ifds, err = ie.Collect(eh.FirstIfdOffset)
|
||||
log.PanicIf(err)
|
||||
|
||||
return rootIfd, tree, ifds, nil
|
||||
}
|
||||
|
|
36
exif_test.go
36
exif_test.go
|
@ -187,6 +187,42 @@ func TestVisit(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCollect(t *testing.T) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err := log.Wrap(state.(error))
|
||||
log.PrintErrorf(err, "Exif failure.")
|
||||
}
|
||||
}()
|
||||
|
||||
e := NewExif()
|
||||
|
||||
filepath := path.Join(assetsPath, "NDM_8901.jpg")
|
||||
|
||||
rawExif, err := e.SearchAndExtractExif(filepath)
|
||||
log.PanicIf(err)
|
||||
|
||||
rootIfd, tree, ifds, err := e.Collect(rawExif)
|
||||
log.PanicIf(err)
|
||||
|
||||
rootIfd = rootIfd
|
||||
tree = tree
|
||||
ifds = ifds
|
||||
|
||||
|
||||
// TODO(dustin): !! Finish test.
|
||||
|
||||
|
||||
// rootIfd.PrintTree()
|
||||
|
||||
// expected := []string {
|
||||
// }
|
||||
|
||||
// if reflect.DeepEqual(tags, expected) == false {
|
||||
// t.Fatalf("tags not correct:\n%v", tags)
|
||||
// }
|
||||
}
|
||||
|
||||
func init() {
|
||||
goPath := os.Getenv("GOPATH")
|
||||
if goPath == "" {
|
||||
|
|
159
ifd_enumerate.go
159
ifd_enumerate.go
|
@ -2,6 +2,8 @@ package exif
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"encoding/binary"
|
||||
|
||||
|
@ -132,7 +134,7 @@ type IfdTagEntry struct {
|
|||
UnitCount uint32
|
||||
ValueOffset uint32
|
||||
RawValueOffset []byte
|
||||
IsIfd bool
|
||||
IfdName string
|
||||
}
|
||||
|
||||
|
||||
|
@ -207,7 +209,7 @@ func (ie *IfdEnumerate) ParseIfd(ifdName string, ifdIndex int, ifdOffset uint32,
|
|||
|
||||
childIfdName, isIfd := IsIfdTag(tagId)
|
||||
if isIfd == true {
|
||||
tag.IsIfd = true
|
||||
tag.IfdName = childIfdName
|
||||
|
||||
if doDescend == true {
|
||||
ifdLogger.Debugf(nil, "Descending to IFD [%s].", childIfdName)
|
||||
|
@ -249,3 +251,156 @@ func (ie *IfdEnumerate) Scan(ifdName string, ifdOffset uint32, visitor TagVisito
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
type Ifd struct {
|
||||
Id int
|
||||
ParentIfd *Ifd
|
||||
Name string
|
||||
Index int
|
||||
Offset uint32
|
||||
Entries []IfdTagEntry
|
||||
Children []*Ifd
|
||||
NextIfdOffset uint32
|
||||
NextIfd *Ifd
|
||||
}
|
||||
|
||||
func (ifd Ifd) String() string {
|
||||
parentOffset := uint32(0)
|
||||
if ifd.ParentIfd != nil {
|
||||
parentOffset = ifd.ParentIfd.Offset
|
||||
}
|
||||
|
||||
return fmt.Sprintf("IFD<ID=(%d) N=[%s] IDX=(%d) OFF=(0x%04x) COUNT=(%d) CHILDREN=(%d) PARENT=(0x%04x) NEXT-IFD=(0x%04x)", ifd.Id, ifd.Name, ifd.Index, ifd.Offset, len(ifd.Entries), len(ifd.Children), parentOffset, ifd.NextIfdOffset)
|
||||
}
|
||||
|
||||
func (ifd Ifd) printNode(level int, nextLink bool) {
|
||||
indent := strings.Repeat(" ", level * 2)
|
||||
|
||||
prefix := " "
|
||||
if nextLink {
|
||||
prefix = ">"
|
||||
}
|
||||
|
||||
fmt.Printf("%s%s%s\n", indent, prefix, ifd)
|
||||
|
||||
for _, childIfd := range ifd.Children {
|
||||
childIfd.printNode(level + 1, false)
|
||||
}
|
||||
|
||||
if ifd.NextIfd != nil {
|
||||
ifd.NextIfd.printNode(level, true)
|
||||
}
|
||||
}
|
||||
|
||||
func (ifd Ifd) PrintTree() {
|
||||
ifd.printNode(0, false)
|
||||
}
|
||||
|
||||
|
||||
type QueuedIfd struct {
|
||||
Name string
|
||||
Index int
|
||||
Offset uint32
|
||||
Parent *Ifd
|
||||
}
|
||||
|
||||
// Scan enumerates the different EXIF blocks (called IFDs).
|
||||
func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (rootIfd *Ifd, tree map[int]*Ifd, ifds []*Ifd, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
tree = make(map[int]*Ifd)
|
||||
ifds = make([]*Ifd, 0)
|
||||
|
||||
queue := []QueuedIfd {
|
||||
{
|
||||
Name: IfdStandard,
|
||||
Index: 0,
|
||||
Offset: rootIfdOffset,
|
||||
},
|
||||
}
|
||||
|
||||
edges := make(map[uint32]*Ifd)
|
||||
|
||||
for {
|
||||
if len(queue) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
name := queue[0].Name
|
||||
index := queue[0].Index
|
||||
offset := queue[0].Offset
|
||||
parentIfd := queue[0].Parent
|
||||
|
||||
queue = queue[1:]
|
||||
|
||||
nextIfdOffset, entries, err := ie.ParseIfd(name, index, offset, nil, false)
|
||||
log.PanicIf(err)
|
||||
|
||||
id := len(ifds)
|
||||
|
||||
ifd := Ifd{
|
||||
Id: id,
|
||||
ParentIfd: parentIfd,
|
||||
Name: name,
|
||||
Index: index,
|
||||
Offset: offset,
|
||||
Entries: entries,
|
||||
Children: make([]*Ifd, 0),
|
||||
NextIfdOffset: nextIfdOffset,
|
||||
}
|
||||
|
||||
// Add ourselves to a big list of IFDs.
|
||||
ifds = append(ifds, &ifd)
|
||||
|
||||
// Install ourselves into a lookup table.
|
||||
tree[id] = &ifd
|
||||
|
||||
// Add a link from the previous IFD in the chain to us.
|
||||
if previousIfd, found := edges[offset]; found == true {
|
||||
previousIfd.NextIfd = &ifd
|
||||
}
|
||||
|
||||
// Attach as a child to our parent (where we appeared as a tag in
|
||||
// that IFD).
|
||||
if parentIfd != nil {
|
||||
parentIfd.Children = append(parentIfd.Children, &ifd)
|
||||
}
|
||||
|
||||
// Determine if any of our entries is a child IFD and queue it.
|
||||
for _, entry := range entries {
|
||||
if entry.IfdName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
qi := QueuedIfd {
|
||||
Name: entry.IfdName,
|
||||
Index: 0,
|
||||
Offset: entry.ValueOffset,
|
||||
Parent: &ifd,
|
||||
}
|
||||
|
||||
queue = append(queue, qi)
|
||||
}
|
||||
|
||||
// If there's another IFD in the chain.
|
||||
if nextIfdOffset != 0 {
|
||||
// Allow the next link to know what the previous link was.
|
||||
edges[nextIfdOffset] = &ifd
|
||||
|
||||
qi := QueuedIfd {
|
||||
Name: IfdStandard,
|
||||
Index: index + 1,
|
||||
Offset: nextIfdOffset,
|
||||
}
|
||||
|
||||
queue = append(queue, qi)
|
||||
}
|
||||
}
|
||||
|
||||
return tree[0], tree, ifds, nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue