init
This commit is contained in:
Binary file not shown.
+651
@@ -0,0 +1,651 @@
|
||||
package exifcommon
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
)
|
||||
|
||||
var (
|
||||
ifdLogger = log.NewLogger("exifcommon.ifd")
|
||||
)
|
||||
|
||||
var (
|
||||
ErrChildIfdNotMapped = errors.New("no child-IFD for that tag-ID under parent")
|
||||
)
|
||||
|
||||
// MappedIfd is one node in the IFD-mapping.
|
||||
type MappedIfd struct {
|
||||
ParentTagId uint16
|
||||
Placement []uint16
|
||||
Path []string
|
||||
|
||||
Name string
|
||||
TagId uint16
|
||||
Children map[uint16]*MappedIfd
|
||||
}
|
||||
|
||||
// String returns a descriptive string.
|
||||
func (mi *MappedIfd) String() string {
|
||||
pathPhrase := mi.PathPhrase()
|
||||
return fmt.Sprintf("MappedIfd<(0x%04X) [%s] PATH=[%s]>", mi.TagId, mi.Name, pathPhrase)
|
||||
}
|
||||
|
||||
// PathPhrase returns a non-fully-qualified IFD path.
|
||||
func (mi *MappedIfd) PathPhrase() string {
|
||||
return strings.Join(mi.Path, "/")
|
||||
}
|
||||
|
||||
// TODO(dustin): Refactor this to use IfdIdentity structs.
|
||||
|
||||
// IfdMapping describes all of the IFDs that we currently recognize.
|
||||
type IfdMapping struct {
|
||||
rootNode *MappedIfd
|
||||
}
|
||||
|
||||
// NewIfdMapping returns a new IfdMapping struct.
|
||||
func NewIfdMapping() (ifdMapping *IfdMapping) {
|
||||
rootNode := &MappedIfd{
|
||||
Path: make([]string, 0),
|
||||
Children: make(map[uint16]*MappedIfd),
|
||||
}
|
||||
|
||||
return &IfdMapping{
|
||||
rootNode: rootNode,
|
||||
}
|
||||
}
|
||||
|
||||
// NewIfdMappingWithStandard retruns a new IfdMapping struct preloaded with the
|
||||
// standard IFDs.
|
||||
func NewIfdMappingWithStandard() (ifdMapping *IfdMapping, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
im := NewIfdMapping()
|
||||
|
||||
err = LoadStandardIfds(im)
|
||||
log.PanicIf(err)
|
||||
|
||||
return im, nil
|
||||
}
|
||||
|
||||
// Get returns the node given the path slice.
|
||||
func (im *IfdMapping) Get(parentPlacement []uint16) (childIfd *MappedIfd, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
ptr := im.rootNode
|
||||
for _, tagId := range parentPlacement {
|
||||
if descendantPtr, found := ptr.Children[tagId]; found == false {
|
||||
log.Panicf("ifd child with tag-ID (%04x) not registered: [%s]", tagId, ptr.PathPhrase())
|
||||
} else {
|
||||
ptr = descendantPtr
|
||||
}
|
||||
}
|
||||
|
||||
return ptr, nil
|
||||
}
|
||||
|
||||
// GetWithPath returns the node given the path string.
|
||||
func (im *IfdMapping) GetWithPath(pathPhrase string) (mi *MappedIfd, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
if pathPhrase == "" {
|
||||
log.Panicf("path-phrase is empty")
|
||||
}
|
||||
|
||||
path := strings.Split(pathPhrase, "/")
|
||||
ptr := im.rootNode
|
||||
|
||||
for _, name := range path {
|
||||
var hit *MappedIfd
|
||||
for _, mi := range ptr.Children {
|
||||
if mi.Name == name {
|
||||
hit = mi
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if hit == nil {
|
||||
log.Panicf("ifd child with name [%s] not registered: [%s]", name, ptr.PathPhrase())
|
||||
}
|
||||
|
||||
ptr = hit
|
||||
}
|
||||
|
||||
return ptr, nil
|
||||
}
|
||||
|
||||
// GetChild is a convenience function to get the child path for a given parent
|
||||
// placement and child tag-ID.
|
||||
func (im *IfdMapping) GetChild(parentPathPhrase string, tagId uint16) (mi *MappedIfd, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
mi, err = im.GetWithPath(parentPathPhrase)
|
||||
log.PanicIf(err)
|
||||
|
||||
for _, childMi := range mi.Children {
|
||||
if childMi.TagId == tagId {
|
||||
return childMi, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Whether or not an IFD is defined in data, such an IFD is not registered
|
||||
// and would be unknown.
|
||||
log.Panic(ErrChildIfdNotMapped)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// IfdTagIdAndIndex represents a specific part of the IFD path.
|
||||
//
|
||||
// This is a legacy type.
|
||||
type IfdTagIdAndIndex struct {
|
||||
Name string
|
||||
TagId uint16
|
||||
Index int
|
||||
}
|
||||
|
||||
// String returns a descriptive string.
|
||||
func (itii IfdTagIdAndIndex) String() string {
|
||||
return fmt.Sprintf("IfdTagIdAndIndex<NAME=[%s] ID=(%04x) INDEX=(%d)>", itii.Name, itii.TagId, itii.Index)
|
||||
}
|
||||
|
||||
// ResolvePath takes a list of names, which can also be suffixed with indices
|
||||
// (to identify the second, third, etc.. sibling IFD) and returns a list of
|
||||
// tag-IDs and those indices.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// - IFD/Exif/Iop
|
||||
// - IFD0/Exif/Iop
|
||||
//
|
||||
// This is the only call that supports adding the numeric indices.
|
||||
func (im *IfdMapping) ResolvePath(pathPhrase string) (lineage []IfdTagIdAndIndex, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
pathPhrase = strings.TrimSpace(pathPhrase)
|
||||
|
||||
if pathPhrase == "" {
|
||||
log.Panicf("can not resolve empty path-phrase")
|
||||
}
|
||||
|
||||
path := strings.Split(pathPhrase, "/")
|
||||
lineage = make([]IfdTagIdAndIndex, len(path))
|
||||
|
||||
ptr := im.rootNode
|
||||
empty := IfdTagIdAndIndex{}
|
||||
for i, name := range path {
|
||||
indexByte := name[len(name)-1]
|
||||
index := 0
|
||||
if indexByte >= '0' && indexByte <= '9' {
|
||||
index = int(indexByte - '0')
|
||||
name = name[:len(name)-1]
|
||||
}
|
||||
|
||||
itii := IfdTagIdAndIndex{}
|
||||
for _, mi := range ptr.Children {
|
||||
if mi.Name != name {
|
||||
continue
|
||||
}
|
||||
|
||||
itii.Name = name
|
||||
itii.TagId = mi.TagId
|
||||
itii.Index = index
|
||||
|
||||
ptr = mi
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if itii == empty {
|
||||
log.Panicf("ifd child with name [%s] not registered: [%s]", name, pathPhrase)
|
||||
}
|
||||
|
||||
lineage[i] = itii
|
||||
}
|
||||
|
||||
return lineage, nil
|
||||
}
|
||||
|
||||
// FqPathPhraseFromLineage returns the fully-qualified IFD path from the slice.
|
||||
func (im *IfdMapping) FqPathPhraseFromLineage(lineage []IfdTagIdAndIndex) (fqPathPhrase string) {
|
||||
fqPathParts := make([]string, len(lineage))
|
||||
for i, itii := range lineage {
|
||||
if itii.Index > 0 {
|
||||
fqPathParts[i] = fmt.Sprintf("%s%d", itii.Name, itii.Index)
|
||||
} else {
|
||||
fqPathParts[i] = itii.Name
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(fqPathParts, "/")
|
||||
}
|
||||
|
||||
// PathPhraseFromLineage returns the non-fully-qualified IFD path from the
|
||||
// slice.
|
||||
func (im *IfdMapping) PathPhraseFromLineage(lineage []IfdTagIdAndIndex) (pathPhrase string) {
|
||||
pathParts := make([]string, len(lineage))
|
||||
for i, itii := range lineage {
|
||||
pathParts[i] = itii.Name
|
||||
}
|
||||
|
||||
return strings.Join(pathParts, "/")
|
||||
}
|
||||
|
||||
// StripPathPhraseIndices returns a non-fully-qualified path-phrase (no
|
||||
// indices).
|
||||
func (im *IfdMapping) StripPathPhraseIndices(pathPhrase string) (strippedPathPhrase string, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
lineage, err := im.ResolvePath(pathPhrase)
|
||||
log.PanicIf(err)
|
||||
|
||||
strippedPathPhrase = im.PathPhraseFromLineage(lineage)
|
||||
return strippedPathPhrase, nil
|
||||
}
|
||||
|
||||
// Add puts the given IFD at the given position of the tree. The position of the
|
||||
// tree is referred to as the placement and is represented by a set of tag-IDs,
|
||||
// where the leftmost is the root tag and the tags going to the right are
|
||||
// progressive descendants.
|
||||
func (im *IfdMapping) Add(parentPlacement []uint16, tagId uint16, name string) (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
// TODO(dustin): !! It would be nicer to provide a list of names in the placement rather than tag-IDs.
|
||||
|
||||
ptr, err := im.Get(parentPlacement)
|
||||
log.PanicIf(err)
|
||||
|
||||
path := make([]string, len(parentPlacement)+1)
|
||||
if len(parentPlacement) > 0 {
|
||||
copy(path, ptr.Path)
|
||||
}
|
||||
|
||||
path[len(path)-1] = name
|
||||
|
||||
placement := make([]uint16, len(parentPlacement)+1)
|
||||
if len(placement) > 0 {
|
||||
copy(placement, ptr.Placement)
|
||||
}
|
||||
|
||||
placement[len(placement)-1] = tagId
|
||||
|
||||
childIfd := &MappedIfd{
|
||||
ParentTagId: ptr.TagId,
|
||||
Path: path,
|
||||
Placement: placement,
|
||||
Name: name,
|
||||
TagId: tagId,
|
||||
Children: make(map[uint16]*MappedIfd),
|
||||
}
|
||||
|
||||
if _, found := ptr.Children[tagId]; found == true {
|
||||
log.Panicf("child IFD with tag-ID (%04x) already registered under IFD [%s] with tag-ID (%04x)", tagId, ptr.Name, ptr.TagId)
|
||||
}
|
||||
|
||||
ptr.Children[tagId] = childIfd
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (im *IfdMapping) dumpLineages(stack []*MappedIfd, input []string) (output []string, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
currentIfd := stack[len(stack)-1]
|
||||
|
||||
output = input
|
||||
for _, childIfd := range currentIfd.Children {
|
||||
stackCopy := make([]*MappedIfd, len(stack)+1)
|
||||
|
||||
copy(stackCopy, stack)
|
||||
stackCopy[len(stack)] = childIfd
|
||||
|
||||
// Add to output, but don't include the obligatory root node.
|
||||
parts := make([]string, len(stackCopy)-1)
|
||||
for i, mi := range stackCopy[1:] {
|
||||
parts[i] = mi.Name
|
||||
}
|
||||
|
||||
output = append(output, strings.Join(parts, "/"))
|
||||
|
||||
output, err = im.dumpLineages(stackCopy, output)
|
||||
log.PanicIf(err)
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// DumpLineages returns a slice of strings representing all mappings.
|
||||
func (im *IfdMapping) DumpLineages() (output []string, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
stack := []*MappedIfd{im.rootNode}
|
||||
output = make([]string, 0)
|
||||
|
||||
output, err = im.dumpLineages(stack, output)
|
||||
log.PanicIf(err)
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// LoadStandardIfds loads the standard IFDs into the mapping.
|
||||
func LoadStandardIfds(im *IfdMapping) (err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
err = im.Add(
|
||||
[]uint16{},
|
||||
IfdStandardIfdIdentity.TagId(), IfdStandardIfdIdentity.Name())
|
||||
|
||||
log.PanicIf(err)
|
||||
|
||||
err = im.Add(
|
||||
[]uint16{IfdStandardIfdIdentity.TagId()},
|
||||
IfdExifStandardIfdIdentity.TagId(), IfdExifStandardIfdIdentity.Name())
|
||||
|
||||
log.PanicIf(err)
|
||||
|
||||
err = im.Add(
|
||||
[]uint16{IfdStandardIfdIdentity.TagId(), IfdExifStandardIfdIdentity.TagId()},
|
||||
IfdExifIopStandardIfdIdentity.TagId(), IfdExifIopStandardIfdIdentity.Name())
|
||||
|
||||
log.PanicIf(err)
|
||||
|
||||
err = im.Add(
|
||||
[]uint16{IfdStandardIfdIdentity.TagId()},
|
||||
IfdGpsInfoStandardIfdIdentity.TagId(), IfdGpsInfoStandardIfdIdentity.Name())
|
||||
|
||||
log.PanicIf(err)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IfdTag describes a single IFD tag and its parent (if any).
|
||||
type IfdTag struct {
|
||||
parentIfdTag *IfdTag
|
||||
tagId uint16
|
||||
name string
|
||||
}
|
||||
|
||||
func NewIfdTag(parentIfdTag *IfdTag, tagId uint16, name string) IfdTag {
|
||||
return IfdTag{
|
||||
parentIfdTag: parentIfdTag,
|
||||
tagId: tagId,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
// ParentIfd returns the IfdTag of this IFD's parent.
|
||||
func (it IfdTag) ParentIfd() *IfdTag {
|
||||
return it.parentIfdTag
|
||||
}
|
||||
|
||||
// TagId returns the tag-ID of this IFD.
|
||||
func (it IfdTag) TagId() uint16 {
|
||||
return it.tagId
|
||||
}
|
||||
|
||||
// Name returns the simple name of this IFD.
|
||||
func (it IfdTag) Name() string {
|
||||
return it.name
|
||||
}
|
||||
|
||||
// String returns a descriptive string.
|
||||
func (it IfdTag) String() string {
|
||||
parentIfdPhrase := ""
|
||||
if it.parentIfdTag != nil {
|
||||
parentIfdPhrase = fmt.Sprintf(" PARENT=(0x%04x)[%s]", it.parentIfdTag.tagId, it.parentIfdTag.name)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("IfdTag<TAG-ID=(0x%04x) NAME=[%s]%s>", it.tagId, it.name, parentIfdPhrase)
|
||||
}
|
||||
|
||||
var (
|
||||
// rootStandardIfd is the standard root IFD.
|
||||
rootStandardIfd = NewIfdTag(nil, 0x0000, "IFD") // IFD
|
||||
|
||||
// exifStandardIfd is the standard "Exif" IFD.
|
||||
exifStandardIfd = NewIfdTag(&rootStandardIfd, 0x8769, "Exif") // IFD/Exif
|
||||
|
||||
// iopStandardIfd is the standard "Iop" IFD.
|
||||
iopStandardIfd = NewIfdTag(&exifStandardIfd, 0xA005, "Iop") // IFD/Exif/Iop
|
||||
|
||||
// gpsInfoStandardIfd is the standard "GPS" IFD.
|
||||
gpsInfoStandardIfd = NewIfdTag(&rootStandardIfd, 0x8825, "GPSInfo") // IFD/GPSInfo
|
||||
)
|
||||
|
||||
// IfdIdentityPart represents one component in an IFD path.
|
||||
type IfdIdentityPart struct {
|
||||
Name string
|
||||
Index int
|
||||
}
|
||||
|
||||
// String returns a fully-qualified IFD path.
|
||||
func (iip IfdIdentityPart) String() string {
|
||||
if iip.Index > 0 {
|
||||
return fmt.Sprintf("%s%d", iip.Name, iip.Index)
|
||||
} else {
|
||||
return iip.Name
|
||||
}
|
||||
}
|
||||
|
||||
// UnindexedString returned a non-fully-qualified IFD path.
|
||||
func (iip IfdIdentityPart) UnindexedString() string {
|
||||
return iip.Name
|
||||
}
|
||||
|
||||
// IfdIdentity represents a single IFD path and provides access to various
|
||||
// information and representations.
|
||||
//
|
||||
// Only global instances can be used for equality checks.
|
||||
type IfdIdentity struct {
|
||||
ifdTag IfdTag
|
||||
parts []IfdIdentityPart
|
||||
ifdPath string
|
||||
fqIfdPath string
|
||||
}
|
||||
|
||||
// NewIfdIdentity returns a new IfdIdentity struct.
|
||||
func NewIfdIdentity(ifdTag IfdTag, parts ...IfdIdentityPart) (ii *IfdIdentity) {
|
||||
ii = &IfdIdentity{
|
||||
ifdTag: ifdTag,
|
||||
parts: parts,
|
||||
}
|
||||
|
||||
ii.ifdPath = ii.getIfdPath()
|
||||
ii.fqIfdPath = ii.getFqIfdPath()
|
||||
|
||||
return ii
|
||||
}
|
||||
|
||||
// NewIfdIdentityFromString parses a string like "IFD/Exif" or "IFD1" or
|
||||
// something more exotic with custom IFDs ("SomeIFD4/SomeChildIFD6"). Note that
|
||||
// this will valid the unindexed IFD structure (because the standard tags from
|
||||
// the specification are unindexed), but not, obviously, any indices (e.g.
|
||||
// the numbers in "IFD0", "IFD1", "SomeIFD4/SomeChildIFD6"). It is
|
||||
// required for the caller to check whether these specific instances
|
||||
// were actually parsed out of the stream.
|
||||
func NewIfdIdentityFromString(im *IfdMapping, fqIfdPath string) (ii *IfdIdentity, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
lineage, err := im.ResolvePath(fqIfdPath)
|
||||
log.PanicIf(err)
|
||||
|
||||
var lastIt *IfdTag
|
||||
identityParts := make([]IfdIdentityPart, len(lineage))
|
||||
for i, itii := range lineage {
|
||||
// Build out the tag that will eventually point to the IFD represented
|
||||
// by the right-most part in the IFD path.
|
||||
|
||||
it := &IfdTag{
|
||||
parentIfdTag: lastIt,
|
||||
tagId: itii.TagId,
|
||||
name: itii.Name,
|
||||
}
|
||||
|
||||
lastIt = it
|
||||
|
||||
// Create the next IfdIdentity part.
|
||||
|
||||
iip := IfdIdentityPart{
|
||||
Name: itii.Name,
|
||||
Index: itii.Index,
|
||||
}
|
||||
|
||||
identityParts[i] = iip
|
||||
}
|
||||
|
||||
ii = NewIfdIdentity(*lastIt, identityParts...)
|
||||
return ii, nil
|
||||
}
|
||||
|
||||
func (ii *IfdIdentity) getFqIfdPath() string {
|
||||
partPhrases := make([]string, len(ii.parts))
|
||||
for i, iip := range ii.parts {
|
||||
partPhrases[i] = iip.String()
|
||||
}
|
||||
|
||||
return strings.Join(partPhrases, "/")
|
||||
}
|
||||
|
||||
func (ii *IfdIdentity) getIfdPath() string {
|
||||
partPhrases := make([]string, len(ii.parts))
|
||||
for i, iip := range ii.parts {
|
||||
partPhrases[i] = iip.UnindexedString()
|
||||
}
|
||||
|
||||
return strings.Join(partPhrases, "/")
|
||||
}
|
||||
|
||||
// String returns a fully-qualified IFD path.
|
||||
func (ii *IfdIdentity) String() string {
|
||||
return ii.fqIfdPath
|
||||
}
|
||||
|
||||
// UnindexedString returns a non-fully-qualified IFD path.
|
||||
func (ii *IfdIdentity) UnindexedString() string {
|
||||
return ii.ifdPath
|
||||
}
|
||||
|
||||
// IfdTag returns the tag struct behind this IFD.
|
||||
func (ii *IfdIdentity) IfdTag() IfdTag {
|
||||
return ii.ifdTag
|
||||
}
|
||||
|
||||
// TagId returns the tag-ID of the IFD.
|
||||
func (ii *IfdIdentity) TagId() uint16 {
|
||||
return ii.ifdTag.TagId()
|
||||
}
|
||||
|
||||
// LeafPathPart returns the last right-most path-part, which represents the
|
||||
// current IFD.
|
||||
func (ii *IfdIdentity) LeafPathPart() IfdIdentityPart {
|
||||
return ii.parts[len(ii.parts)-1]
|
||||
}
|
||||
|
||||
// Name returns the simple name of this IFD.
|
||||
func (ii *IfdIdentity) Name() string {
|
||||
return ii.LeafPathPart().Name
|
||||
}
|
||||
|
||||
// Index returns the index of this IFD (more then one IFD under a parent IFD
|
||||
// will be numbered [0..n]).
|
||||
func (ii *IfdIdentity) Index() int {
|
||||
return ii.LeafPathPart().Index
|
||||
}
|
||||
|
||||
// Equals returns true if the two IfdIdentity instances are effectively
|
||||
// identical.
|
||||
//
|
||||
// Since there's no way to get a specific fully-qualified IFD path without a
|
||||
// certain slice of parts and all other fields are also derived from this,
|
||||
// checking that the fully-qualified IFD path is equals is sufficient.
|
||||
func (ii *IfdIdentity) Equals(ii2 *IfdIdentity) bool {
|
||||
return ii.String() == ii2.String()
|
||||
}
|
||||
|
||||
// NewChild creates an IfdIdentity for an IFD that is a child of the current
|
||||
// IFD.
|
||||
func (ii *IfdIdentity) NewChild(childIfdTag IfdTag, index int) (iiChild *IfdIdentity) {
|
||||
if *childIfdTag.parentIfdTag != ii.ifdTag {
|
||||
log.Panicf("can not add child; we are not the parent:\nUS=%v\nCHILD=%v", ii.ifdTag, childIfdTag)
|
||||
}
|
||||
|
||||
childPart := IfdIdentityPart{childIfdTag.name, index}
|
||||
childParts := append(ii.parts, childPart)
|
||||
|
||||
iiChild = NewIfdIdentity(childIfdTag, childParts...)
|
||||
return iiChild
|
||||
}
|
||||
|
||||
// NewSibling creates an IfdIdentity for an IFD that is a sibling to the current
|
||||
// one.
|
||||
func (ii *IfdIdentity) NewSibling(index int) (iiSibling *IfdIdentity) {
|
||||
parts := make([]IfdIdentityPart, len(ii.parts))
|
||||
|
||||
copy(parts, ii.parts)
|
||||
parts[len(parts)-1].Index = index
|
||||
|
||||
iiSibling = NewIfdIdentity(ii.ifdTag, parts...)
|
||||
return iiSibling
|
||||
}
|
||||
|
||||
var (
|
||||
// IfdStandardIfdIdentity represents the IFD path for IFD0.
|
||||
IfdStandardIfdIdentity = NewIfdIdentity(rootStandardIfd, IfdIdentityPart{"IFD", 0})
|
||||
|
||||
// IfdExifStandardIfdIdentity represents the IFD path for IFD0/Exif0.
|
||||
IfdExifStandardIfdIdentity = IfdStandardIfdIdentity.NewChild(exifStandardIfd, 0)
|
||||
|
||||
// IfdExifIopStandardIfdIdentity represents the IFD path for IFD0/Exif0/Iop0.
|
||||
IfdExifIopStandardIfdIdentity = IfdExifStandardIfdIdentity.NewChild(iopStandardIfd, 0)
|
||||
|
||||
// IfdGPSInfoStandardIfdIdentity represents the IFD path for IFD0/GPSInfo0.
|
||||
IfdGpsInfoStandardIfdIdentity = IfdStandardIfdIdentity.NewChild(gpsInfoStandardIfd, 0)
|
||||
|
||||
// Ifd1StandardIfdIdentity represents the IFD path for IFD1.
|
||||
Ifd1StandardIfdIdentity = NewIfdIdentity(rootStandardIfd, IfdIdentityPart{"IFD", 1})
|
||||
)
|
||||
@@ -0,0 +1,336 @@
|
||||
package exifcommon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
)
|
||||
|
||||
func TestIfdMapping_Add(t *testing.T) {
|
||||
im := NewIfdMapping()
|
||||
|
||||
err := im.Add([]uint16{}, 0x1111, "ifd0")
|
||||
log.PanicIf(err)
|
||||
|
||||
err = im.Add([]uint16{0x1111}, 0x4444, "ifd00")
|
||||
log.PanicIf(err)
|
||||
|
||||
err = im.Add([]uint16{0x1111, 0x4444}, 0x5555, "ifd000")
|
||||
log.PanicIf(err)
|
||||
|
||||
err = im.Add([]uint16{}, 0x2222, "ifd1")
|
||||
log.PanicIf(err)
|
||||
|
||||
err = im.Add([]uint16{}, 0x3333, "ifd2")
|
||||
log.PanicIf(err)
|
||||
|
||||
lineages, err := im.DumpLineages()
|
||||
log.PanicIf(err)
|
||||
|
||||
sort.Strings(lineages)
|
||||
|
||||
expected := []string{
|
||||
"ifd0",
|
||||
"ifd0/ifd00",
|
||||
"ifd0/ifd00/ifd000",
|
||||
"ifd1",
|
||||
"ifd2",
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(lineages, expected) != true {
|
||||
fmt.Printf("Actual:\n")
|
||||
fmt.Printf("\n")
|
||||
|
||||
for i, line := range lineages {
|
||||
fmt.Printf("(%d) %s\n", i, line)
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
|
||||
fmt.Printf("Expected:\n")
|
||||
fmt.Printf("\n")
|
||||
|
||||
for i, line := range expected {
|
||||
fmt.Printf("(%d) %s\n", i, line)
|
||||
}
|
||||
|
||||
t.Fatalf("IFD-mapping dump not correct.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIfdMapping_LoadStandardIfds(t *testing.T) {
|
||||
im := NewIfdMapping()
|
||||
|
||||
err := LoadStandardIfds(im)
|
||||
log.PanicIf(err)
|
||||
|
||||
lineages, err := im.DumpLineages()
|
||||
log.PanicIf(err)
|
||||
|
||||
sort.Strings(lineages)
|
||||
|
||||
expected := []string{
|
||||
"IFD",
|
||||
"IFD/Exif",
|
||||
"IFD/Exif/Iop",
|
||||
"IFD/GPSInfo",
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(lineages, expected) != true {
|
||||
fmt.Printf("Actual:\n")
|
||||
fmt.Printf("\n")
|
||||
|
||||
for i, line := range lineages {
|
||||
fmt.Printf("(%d) %s\n", i, line)
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
|
||||
fmt.Printf("Expected:\n")
|
||||
fmt.Printf("\n")
|
||||
|
||||
for i, line := range expected {
|
||||
fmt.Printf("(%d) %s\n", i, line)
|
||||
}
|
||||
|
||||
t.Fatalf("IFD-mapping dump not correct.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIfdMapping_Get(t *testing.T) {
|
||||
im := NewIfdMapping()
|
||||
|
||||
err := LoadStandardIfds(im)
|
||||
log.PanicIf(err)
|
||||
|
||||
mi, err := im.Get([]uint16{
|
||||
IfdStandardIfdIdentity.TagId(),
|
||||
IfdExifStandardIfdIdentity.TagId(),
|
||||
IfdExifIopStandardIfdIdentity.TagId(),
|
||||
})
|
||||
|
||||
log.PanicIf(err)
|
||||
|
||||
if mi.ParentTagId != IfdExifStandardIfdIdentity.TagId() {
|
||||
t.Fatalf("Parent tag-ID not correct")
|
||||
} else if mi.TagId != IfdExifIopStandardIfdIdentity.TagId() {
|
||||
t.Fatalf("Tag-ID not correct")
|
||||
} else if mi.Name != "Iop" {
|
||||
t.Fatalf("name not correct")
|
||||
} else if mi.PathPhrase() != "IFD/Exif/Iop" {
|
||||
t.Fatalf("path not correct")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIfdMapping_GetWithPath(t *testing.T) {
|
||||
im := NewIfdMapping()
|
||||
|
||||
err := LoadStandardIfds(im)
|
||||
log.PanicIf(err)
|
||||
|
||||
mi, err := im.GetWithPath("IFD/Exif/Iop")
|
||||
log.PanicIf(err)
|
||||
|
||||
if mi.ParentTagId != IfdExifStandardIfdIdentity.TagId() {
|
||||
t.Fatalf("Parent tag-ID not correct")
|
||||
} else if mi.TagId != IfdExifIopStandardIfdIdentity.TagId() {
|
||||
t.Fatalf("Tag-ID not correct")
|
||||
} else if mi.Name != "Iop" {
|
||||
t.Fatalf("name not correct")
|
||||
} else if mi.PathPhrase() != "IFD/Exif/Iop" {
|
||||
t.Fatalf("path not correct")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIfdMapping_ResolvePath__Regular(t *testing.T) {
|
||||
im := NewIfdMapping()
|
||||
|
||||
err := LoadStandardIfds(im)
|
||||
log.PanicIf(err)
|
||||
|
||||
lineage, err := im.ResolvePath("IFD/Exif/Iop")
|
||||
log.PanicIf(err)
|
||||
|
||||
expected := []IfdTagIdAndIndex{
|
||||
{Name: "IFD", TagId: 0, Index: 0},
|
||||
{Name: "Exif", TagId: 0x8769, Index: 0},
|
||||
{Name: "Iop", TagId: 0xa005, Index: 0},
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(lineage, expected) != true {
|
||||
t.Fatalf("Lineage not correct.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIfdMapping_ResolvePath__WithIndices(t *testing.T) {
|
||||
im := NewIfdMapping()
|
||||
|
||||
err := LoadStandardIfds(im)
|
||||
log.PanicIf(err)
|
||||
|
||||
lineage, err := im.ResolvePath("IFD/Exif1/Iop")
|
||||
log.PanicIf(err)
|
||||
|
||||
expected := []IfdTagIdAndIndex{
|
||||
{Name: "IFD", TagId: 0, Index: 0},
|
||||
{Name: "Exif", TagId: 0x8769, Index: 1},
|
||||
{Name: "Iop", TagId: 0xa005, Index: 0},
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(lineage, expected) != true {
|
||||
t.Fatalf("Lineage not correct.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIfdMapping_ResolvePath__Miss(t *testing.T) {
|
||||
im := NewIfdMapping()
|
||||
|
||||
err := LoadStandardIfds(im)
|
||||
log.PanicIf(err)
|
||||
|
||||
_, err = im.ResolvePath("IFD/Exif/Invalid")
|
||||
if err == nil {
|
||||
t.Fatalf("Expected failure for invalid IFD path.")
|
||||
} else if err.Error() != "ifd child with name [Invalid] not registered: [IFD/Exif/Invalid]" {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIfdMapping_FqPathPhraseFromLineage(t *testing.T) {
|
||||
lineage := []IfdTagIdAndIndex{
|
||||
{Name: "IFD", Index: 0},
|
||||
{Name: "Exif", Index: 1},
|
||||
{Name: "Iop", Index: 0},
|
||||
}
|
||||
|
||||
im := NewIfdMapping()
|
||||
|
||||
fqPathPhrase := im.FqPathPhraseFromLineage(lineage)
|
||||
if fqPathPhrase != "IFD/Exif1/Iop" {
|
||||
t.Fatalf("path-phrase not correct: [%s]", fqPathPhrase)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIfdMapping_PathPhraseFromLineage(t *testing.T) {
|
||||
lineage := []IfdTagIdAndIndex{
|
||||
{Name: "IFD", Index: 0},
|
||||
{Name: "Exif", Index: 1},
|
||||
{Name: "Iop", Index: 0},
|
||||
}
|
||||
|
||||
im := NewIfdMapping()
|
||||
|
||||
fqPathPhrase := im.PathPhraseFromLineage(lineage)
|
||||
if fqPathPhrase != "IFD/Exif/Iop" {
|
||||
t.Fatalf("path-phrase not correct: [%s]", fqPathPhrase)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIfdMapping_NewIfdMappingWithStandard(t *testing.T) {
|
||||
imWith, err := NewIfdMappingWithStandard()
|
||||
log.PanicIf(err)
|
||||
|
||||
imWithout := NewIfdMapping()
|
||||
|
||||
err = LoadStandardIfds(imWithout)
|
||||
log.PanicIf(err)
|
||||
|
||||
outputWith, err := imWith.DumpLineages()
|
||||
log.PanicIf(err)
|
||||
|
||||
sort.Strings(outputWith)
|
||||
|
||||
outputWithout, err := imWithout.DumpLineages()
|
||||
log.PanicIf(err)
|
||||
|
||||
sort.Strings(outputWithout)
|
||||
|
||||
if reflect.DeepEqual(outputWith, outputWithout) != true {
|
||||
fmt.Printf("WITH:\n")
|
||||
fmt.Printf("\n")
|
||||
|
||||
for _, line := range outputWith {
|
||||
fmt.Printf("%s\n", line)
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
|
||||
fmt.Printf("WITHOUT:\n")
|
||||
fmt.Printf("\n")
|
||||
|
||||
for _, line := range outputWithout {
|
||||
fmt.Printf("%s\n", line)
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
|
||||
t.Fatalf("Standard IFDs not loaded correctly.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewIfdIdentityFromString_Valid_WithoutIndexes(t *testing.T) {
|
||||
im := NewIfdMapping()
|
||||
|
||||
err := LoadStandardIfds(im)
|
||||
log.PanicIf(err)
|
||||
|
||||
fqIfdPath := "IFD/Exif"
|
||||
|
||||
ii, err := NewIfdIdentityFromString(im, fqIfdPath)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ii.String() != fqIfdPath {
|
||||
t.Fatalf("'%s' IFD-path was not parsed correctly: [%s]", fqIfdPath, ii.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewIfdIdentityFromString_Valid_WithIndexes(t *testing.T) {
|
||||
im := NewIfdMapping()
|
||||
|
||||
err := LoadStandardIfds(im)
|
||||
log.PanicIf(err)
|
||||
|
||||
fqIfdPath := "IFD2/Exif4"
|
||||
|
||||
ii, err := NewIfdIdentityFromString(im, fqIfdPath)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ii.String() != fqIfdPath {
|
||||
t.Fatalf("'%s' IFD-path was not parsed correctly: [%s]", fqIfdPath, ii.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewIfdIdentityFromString_Invalid_IfdPathJustRoot(t *testing.T) {
|
||||
im := NewIfdMapping()
|
||||
|
||||
err := LoadStandardIfds(im)
|
||||
log.PanicIf(err)
|
||||
|
||||
fqIfdPath := "XYZ"
|
||||
|
||||
_, err = NewIfdIdentityFromString(im, fqIfdPath)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error from invalid path.")
|
||||
} else if err.Error() != "ifd child with name [XYZ] not registered: [XYZ]" {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewIfdIdentityFromString_Invalid_IfdPathWithSubdirectory(t *testing.T) {
|
||||
im := NewIfdMapping()
|
||||
|
||||
err := LoadStandardIfds(im)
|
||||
log.PanicIf(err)
|
||||
|
||||
fqIfdPath := "IFD/XYZ"
|
||||
|
||||
_, err = NewIfdIdentityFromString(im, fqIfdPath)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error from invalid path.")
|
||||
} else if err.Error() != "ifd child with name [XYZ] not registered: [IFD/XYZ]" {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
package exifcommon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"math"
|
||||
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
)
|
||||
|
||||
var (
|
||||
parserLogger = log.NewLogger("exifcommon.parser")
|
||||
)
|
||||
|
||||
var (
|
||||
ErrParseFail = errors.New("parse failure")
|
||||
)
|
||||
|
||||
// Parser knows how to parse all well-defined, encoded EXIF types.
|
||||
type Parser struct {
|
||||
}
|
||||
|
||||
// ParseBytesknows how to parse a byte-type value.
|
||||
func (p *Parser) ParseBytes(data []byte, unitCount uint32) (value []uint8, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
// TODO(dustin): Add test
|
||||
|
||||
count := int(unitCount)
|
||||
|
||||
if len(data) < (TypeByte.Size() * count) {
|
||||
log.Panic(ErrNotEnoughData)
|
||||
}
|
||||
|
||||
value = []uint8(data[:count])
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// ParseAscii returns a string and auto-strips the trailing NUL character that
|
||||
// should be at the end of the encoding.
|
||||
func (p *Parser) ParseAscii(data []byte, unitCount uint32) (value string, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
// TODO(dustin): Add test
|
||||
|
||||
count := int(unitCount)
|
||||
|
||||
if len(data) < (TypeAscii.Size() * count) {
|
||||
log.Panic(ErrNotEnoughData)
|
||||
}
|
||||
|
||||
if len(data) == 0 || data[count-1] != 0 {
|
||||
s := string(data[:count])
|
||||
parserLogger.Warningf(nil, "ASCII not terminated with NUL as expected: [%v]", s)
|
||||
|
||||
for i, c := range s {
|
||||
if c > 127 {
|
||||
// Binary
|
||||
|
||||
t := s[:i]
|
||||
parserLogger.Warningf(nil, "ASCII also had binary characters. Truncating: [%v]->[%s]", s, t)
|
||||
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Auto-strip the NUL from the end. It serves no purpose outside of
|
||||
// encoding semantics.
|
||||
|
||||
return string(data[:count-1]), nil
|
||||
}
|
||||
|
||||
// ParseAsciiNoNul returns a string without any consideration for a trailing NUL
|
||||
// character.
|
||||
func (p *Parser) ParseAsciiNoNul(data []byte, unitCount uint32) (value string, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
// TODO(dustin): Add test
|
||||
|
||||
count := int(unitCount)
|
||||
|
||||
if len(data) < (TypeAscii.Size() * count) {
|
||||
log.Panic(ErrNotEnoughData)
|
||||
}
|
||||
|
||||
return string(data[:count]), nil
|
||||
}
|
||||
|
||||
// ParseShorts knows how to parse an encoded list of shorts.
|
||||
func (p *Parser) ParseShorts(data []byte, unitCount uint32, byteOrder binary.ByteOrder) (value []uint16, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
// TODO(dustin): Add test
|
||||
|
||||
count := int(unitCount)
|
||||
|
||||
if len(data) < (TypeShort.Size() * count) {
|
||||
log.Panic(ErrNotEnoughData)
|
||||
}
|
||||
|
||||
value = make([]uint16, count)
|
||||
for i := 0; i < count; i++ {
|
||||
value[i] = byteOrder.Uint16(data[i*2:])
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// ParseLongs knows how to encode an encoded list of unsigned longs.
|
||||
func (p *Parser) ParseLongs(data []byte, unitCount uint32, byteOrder binary.ByteOrder) (value []uint32, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
// TODO(dustin): Add test
|
||||
|
||||
count := int(unitCount)
|
||||
|
||||
if len(data) < (TypeLong.Size() * count) {
|
||||
log.Panic(ErrNotEnoughData)
|
||||
}
|
||||
|
||||
value = make([]uint32, count)
|
||||
for i := 0; i < count; i++ {
|
||||
value[i] = byteOrder.Uint32(data[i*4:])
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// ParseFloats knows how to encode an encoded list of floats.
|
||||
func (p *Parser) ParseFloats(data []byte, unitCount uint32, byteOrder binary.ByteOrder) (value []float32, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
count := int(unitCount)
|
||||
|
||||
if len(data) != (TypeFloat.Size() * count) {
|
||||
log.Panic(ErrNotEnoughData)
|
||||
}
|
||||
|
||||
value = make([]float32, count)
|
||||
for i := 0; i < count; i++ {
|
||||
value[i] = math.Float32frombits(byteOrder.Uint32(data[i*4 : (i+1)*4]))
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// ParseDoubles knows how to encode an encoded list of doubles.
|
||||
func (p *Parser) ParseDoubles(data []byte, unitCount uint32, byteOrder binary.ByteOrder) (value []float64, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
count := int(unitCount)
|
||||
|
||||
if len(data) != (TypeDouble.Size() * count) {
|
||||
log.Panic(ErrNotEnoughData)
|
||||
}
|
||||
|
||||
value = make([]float64, count)
|
||||
for i := 0; i < count; i++ {
|
||||
value[i] = math.Float64frombits(byteOrder.Uint64(data[i*8 : (i+1)*8]))
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// ParseRationals knows how to parse an encoded list of unsigned rationals.
|
||||
func (p *Parser) ParseRationals(data []byte, unitCount uint32, byteOrder binary.ByteOrder) (value []Rational, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
// TODO(dustin): Add test
|
||||
|
||||
count := int(unitCount)
|
||||
|
||||
if len(data) < (TypeRational.Size() * count) {
|
||||
log.Panic(ErrNotEnoughData)
|
||||
}
|
||||
|
||||
value = make([]Rational, count)
|
||||
for i := 0; i < count; i++ {
|
||||
value[i].Numerator = byteOrder.Uint32(data[i*8:])
|
||||
value[i].Denominator = byteOrder.Uint32(data[i*8+4:])
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// ParseSignedLongs knows how to parse an encoded list of signed longs.
|
||||
func (p *Parser) ParseSignedLongs(data []byte, unitCount uint32, byteOrder binary.ByteOrder) (value []int32, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
// TODO(dustin): Add test
|
||||
|
||||
count := int(unitCount)
|
||||
|
||||
if len(data) < (TypeSignedLong.Size() * count) {
|
||||
log.Panic(ErrNotEnoughData)
|
||||
}
|
||||
|
||||
b := bytes.NewBuffer(data)
|
||||
|
||||
value = make([]int32, count)
|
||||
for i := 0; i < count; i++ {
|
||||
err := binary.Read(b, byteOrder, &value[i])
|
||||
log.PanicIf(err)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// ParseSignedRationals knows how to parse an encoded list of signed
|
||||
// rationals.
|
||||
func (p *Parser) ParseSignedRationals(data []byte, unitCount uint32, byteOrder binary.ByteOrder) (value []SignedRational, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
// TODO(dustin): Add test
|
||||
|
||||
count := int(unitCount)
|
||||
|
||||
if len(data) < (TypeSignedRational.Size() * count) {
|
||||
log.Panic(ErrNotEnoughData)
|
||||
}
|
||||
|
||||
b := bytes.NewBuffer(data)
|
||||
|
||||
value = make([]SignedRational, count)
|
||||
for i := 0; i < count; i++ {
|
||||
err = binary.Read(b, byteOrder, &value[i].Numerator)
|
||||
log.PanicIf(err)
|
||||
|
||||
err = binary.Read(b, byteOrder, &value[i].Denominator)
|
||||
log.PanicIf(err)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
package exifcommon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
)
|
||||
|
||||
func TestParser_ParseBytes(t *testing.T) {
|
||||
p := new(Parser)
|
||||
|
||||
encoded := []byte("abcdefg")
|
||||
|
||||
value, err := p.ParseBytes(encoded, 1)
|
||||
log.PanicIf(err)
|
||||
|
||||
if bytes.Equal(value, encoded[:1]) != true {
|
||||
t.Fatalf("Encoding not correct (1): %v", value)
|
||||
}
|
||||
|
||||
value, err = p.ParseBytes(encoded, 4)
|
||||
log.PanicIf(err)
|
||||
|
||||
if bytes.Equal(value, encoded[:4]) != true {
|
||||
t.Fatalf("Encoding not correct (2): %v", value)
|
||||
}
|
||||
|
||||
value, err = p.ParseBytes(encoded, uint32(len(encoded)))
|
||||
log.PanicIf(err)
|
||||
|
||||
if bytes.Equal(value, encoded) != true {
|
||||
t.Fatalf("Encoding not correct (3): %v", value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_ParseAscii(t *testing.T) {
|
||||
p := new(Parser)
|
||||
|
||||
original := "abcdefg"
|
||||
|
||||
encoded := []byte(original[:1])
|
||||
encoded = append(encoded, 0)
|
||||
|
||||
value, err := p.ParseAscii(encoded, uint32(len(encoded)))
|
||||
log.PanicIf(err)
|
||||
|
||||
if value != original[:1] {
|
||||
t.Fatalf("Encoding not correct (1): %s", value)
|
||||
}
|
||||
|
||||
encoded = []byte(original[:4])
|
||||
encoded = append(encoded, 0)
|
||||
|
||||
value, err = p.ParseAscii(encoded, uint32(len(encoded)))
|
||||
log.PanicIf(err)
|
||||
|
||||
if value != original[:4] {
|
||||
t.Fatalf("Encoding not correct (2): %v", value)
|
||||
}
|
||||
|
||||
encoded = []byte(original)
|
||||
encoded = append(encoded, 0)
|
||||
|
||||
value, err = p.ParseAscii(encoded, uint32(len(encoded)))
|
||||
log.PanicIf(err)
|
||||
|
||||
if value != original {
|
||||
t.Fatalf("Encoding not correct (3): %v", value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_ParseAsciiNoNul(t *testing.T) {
|
||||
p := new(Parser)
|
||||
|
||||
original := "abcdefg"
|
||||
|
||||
encoded := []byte(original)
|
||||
|
||||
value, err := p.ParseAsciiNoNul(encoded, 1)
|
||||
log.PanicIf(err)
|
||||
|
||||
if value != original[:1] {
|
||||
t.Fatalf("Encoding not correct (1): %s", value)
|
||||
}
|
||||
|
||||
value, err = p.ParseAsciiNoNul(encoded, 4)
|
||||
log.PanicIf(err)
|
||||
|
||||
if value != original[:4] {
|
||||
t.Fatalf("Encoding not correct (2): %v", value)
|
||||
}
|
||||
|
||||
value, err = p.ParseAsciiNoNul(encoded, uint32(len(encoded)))
|
||||
log.PanicIf(err)
|
||||
|
||||
if value != original {
|
||||
t.Fatalf("Encoding not correct (3): (%d) %v", len(value), value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_ParseShorts__Single(t *testing.T) {
|
||||
p := new(Parser)
|
||||
|
||||
encoded := []byte{0x00, 0x01}
|
||||
|
||||
value, err := p.ParseShorts(encoded, 1, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
if reflect.DeepEqual(value, []uint16{1}) != true {
|
||||
t.Fatalf("Encoding not correct (1): %v", value)
|
||||
}
|
||||
|
||||
encoded = []byte{0x00, 0x01, 0x00, 0x02}
|
||||
|
||||
value, err = p.ParseShorts(encoded, 1, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
if reflect.DeepEqual(value, []uint16{1}) != true {
|
||||
t.Fatalf("Encoding not correct (2): %v", value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_ParseShorts__Multiple(t *testing.T) {
|
||||
p := new(Parser)
|
||||
|
||||
encoded := []byte{0x00, 0x01, 0x00, 0x02}
|
||||
|
||||
value, err := p.ParseShorts(encoded, 2, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
if reflect.DeepEqual(value, []uint16{1, 2}) != true {
|
||||
t.Fatalf("Encoding not correct: %v", value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_ParseLongs__Single(t *testing.T) {
|
||||
p := new(Parser)
|
||||
|
||||
encoded := []byte{0x00, 0x00, 0x00, 0x01}
|
||||
|
||||
value, err := p.ParseLongs(encoded, 1, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
if reflect.DeepEqual(value, []uint32{1}) != true {
|
||||
t.Fatalf("Encoding not correct (1): %v", value)
|
||||
}
|
||||
|
||||
encoded = []byte{0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02}
|
||||
|
||||
value, err = p.ParseLongs(encoded, 1, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
if reflect.DeepEqual(value, []uint32{1}) != true {
|
||||
t.Fatalf("Encoding not correct (2): %v", value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_ParseLongs__Multiple(t *testing.T) {
|
||||
p := new(Parser)
|
||||
|
||||
encoded := []byte{0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02}
|
||||
|
||||
value, err := p.ParseLongs(encoded, 2, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
if reflect.DeepEqual(value, []uint32{1, 2}) != true {
|
||||
t.Fatalf("Encoding not correct: %v", value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_ParseFloats__Single(t *testing.T) {
|
||||
p := new(Parser)
|
||||
|
||||
encoded := []byte{0x40, 0x49, 0x0f, 0xdb}
|
||||
|
||||
value, err := p.ParseFloats(encoded, 1, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
expectedResult := []float32{3.14159265}
|
||||
|
||||
for i, v := range value {
|
||||
if v < expectedResult[i] ||
|
||||
v >= math.Nextafter32(expectedResult[i], expectedResult[i]+1) {
|
||||
t.Fatalf("Encoding not correct (1): %v", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_ParseFloats__Multiple(t *testing.T) {
|
||||
p := new(Parser)
|
||||
|
||||
encoded := []byte{0x40, 0x49, 0x0f, 0xdb, 0x40, 0x2d, 0xf8, 0x54}
|
||||
|
||||
value, err := p.ParseFloats(encoded, 2, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
expectedResult := []float32{3.14159265, 2.71828182}
|
||||
|
||||
for i, v := range value {
|
||||
if v < expectedResult[i] ||
|
||||
v >= math.Nextafter32(expectedResult[i], expectedResult[i]+1) {
|
||||
t.Fatalf("Encoding not correct (1): %v", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_ParseDoubles__Single(t *testing.T) {
|
||||
p := new(Parser)
|
||||
|
||||
encoded := []byte{0x40, 0x09, 0x21, 0xfb, 0x53, 0xc8, 0xd4, 0xf1}
|
||||
|
||||
value, err := p.ParseDoubles(encoded, 1, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
expectedResult := []float64{3.14159265}
|
||||
for i, v := range value {
|
||||
if v < expectedResult[i] ||
|
||||
v >= math.Nextafter(expectedResult[i], expectedResult[i]+1) {
|
||||
t.Fatalf("Encoding not correct (1): %v", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_ParseDoubles__Multiple(t *testing.T) {
|
||||
p := new(Parser)
|
||||
|
||||
encoded := []byte{0x40, 0x09, 0x21, 0xfb, 0x53, 0xc8, 0xd4, 0xf1,
|
||||
0x40, 0x05, 0xbf, 0x0a, 0x89, 0xf1, 0xb0, 0xdd}
|
||||
|
||||
value, err := p.ParseDoubles(encoded, 2, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
expectedResult := []float64{3.14159265, 2.71828182}
|
||||
|
||||
for i, v := range value {
|
||||
if v < expectedResult[i] ||
|
||||
v >= math.Nextafter(expectedResult[i], expectedResult[i]+1) {
|
||||
t.Fatalf("Encoding not correct: %v", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_ParseRationals__Single(t *testing.T) {
|
||||
p := new(Parser)
|
||||
|
||||
encoded := []byte{
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04,
|
||||
}
|
||||
|
||||
value, err := p.ParseRationals(encoded, 1, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
expected := []Rational{
|
||||
{Numerator: 1, Denominator: 2},
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(value, expected) != true {
|
||||
t.Fatalf("Encoding not correct (1): %v", value)
|
||||
}
|
||||
|
||||
encoded = []byte{
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04,
|
||||
}
|
||||
|
||||
value, err = p.ParseRationals(encoded, 1, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
expected = []Rational{
|
||||
{Numerator: 1, Denominator: 2},
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(value, expected) != true {
|
||||
t.Fatalf("Encoding not correct (2): %v", value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_ParseRationals__Multiple(t *testing.T) {
|
||||
p := new(Parser)
|
||||
|
||||
encoded := []byte{
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04,
|
||||
}
|
||||
|
||||
value, err := p.ParseRationals(encoded, 2, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
expected := []Rational{
|
||||
{Numerator: 1, Denominator: 2},
|
||||
{Numerator: 3, Denominator: 4},
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(value, expected) != true {
|
||||
t.Fatalf("Encoding not correct (2): %v", value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_ParseSignedLongs__Single(t *testing.T) {
|
||||
p := new(Parser)
|
||||
|
||||
encoded := []byte{0x00, 0x00, 0x00, 0x01}
|
||||
|
||||
value, err := p.ParseSignedLongs(encoded, 1, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
if reflect.DeepEqual(value, []int32{1}) != true {
|
||||
t.Fatalf("Encoding not correct (1): %v", value)
|
||||
}
|
||||
|
||||
encoded = []byte{0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02}
|
||||
|
||||
value, err = p.ParseSignedLongs(encoded, 1, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
if reflect.DeepEqual(value, []int32{1}) != true {
|
||||
t.Fatalf("Encoding not correct (2): %v", value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_ParseSignedLongs__Multiple(t *testing.T) {
|
||||
p := new(Parser)
|
||||
|
||||
encoded := []byte{0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02}
|
||||
|
||||
value, err := p.ParseSignedLongs(encoded, 2, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
if reflect.DeepEqual(value, []int32{1, 2}) != true {
|
||||
t.Fatalf("Encoding not correct: %v", value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_ParseSignedRationals__Single(t *testing.T) {
|
||||
p := new(Parser)
|
||||
|
||||
encoded := []byte{
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04,
|
||||
}
|
||||
|
||||
value, err := p.ParseSignedRationals(encoded, 1, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
expected := []SignedRational{
|
||||
{Numerator: 1, Denominator: 2},
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(value, expected) != true {
|
||||
t.Fatalf("Encoding not correct (1): %v", value)
|
||||
}
|
||||
|
||||
encoded = []byte{
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04,
|
||||
}
|
||||
|
||||
value, err = p.ParseSignedRationals(encoded, 1, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
expected = []SignedRational{
|
||||
{Numerator: 1, Denominator: 2},
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(value, expected) != true {
|
||||
t.Fatalf("Encoding not correct (2): %v", value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_ParseSignedRationals__Multiple(t *testing.T) {
|
||||
p := new(Parser)
|
||||
|
||||
encoded := []byte{
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04,
|
||||
}
|
||||
|
||||
value, err := p.ParseSignedRationals(encoded, 2, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
expected := []SignedRational{
|
||||
{Numerator: 1, Denominator: 2},
|
||||
{Numerator: 3, Denominator: 4},
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(value, expected) != true {
|
||||
t.Fatalf("Encoding not correct (2): %v", value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package exifcommon
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"encoding/binary"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
)
|
||||
|
||||
var (
|
||||
moduleRootPath = ""
|
||||
|
||||
testExifData []byte = nil
|
||||
|
||||
// EncodeDefaultByteOrder is the default byte-order for encoding operations.
|
||||
EncodeDefaultByteOrder = binary.BigEndian
|
||||
|
||||
// Default byte order for tests.
|
||||
TestDefaultByteOrder = binary.BigEndian
|
||||
)
|
||||
|
||||
func GetModuleRootPath() string {
|
||||
if moduleRootPath == "" {
|
||||
moduleRootPath = os.Getenv("EXIF_MODULE_ROOT_PATH")
|
||||
if moduleRootPath != "" {
|
||||
return moduleRootPath
|
||||
}
|
||||
|
||||
currentWd, err := os.Getwd()
|
||||
log.PanicIf(err)
|
||||
|
||||
currentPath := currentWd
|
||||
|
||||
visited := make([]string, 0)
|
||||
|
||||
for {
|
||||
tryStampFilepath := path.Join(currentPath, ".MODULE_ROOT")
|
||||
|
||||
_, err := os.Stat(tryStampFilepath)
|
||||
if err != nil && os.IsNotExist(err) != true {
|
||||
log.Panic(err)
|
||||
} else if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
visited = append(visited, tryStampFilepath)
|
||||
|
||||
currentPath = path.Dir(currentPath)
|
||||
if currentPath == "/" {
|
||||
log.Panicf("could not find module-root: %v", visited)
|
||||
}
|
||||
}
|
||||
|
||||
moduleRootPath = currentPath
|
||||
}
|
||||
|
||||
return moduleRootPath
|
||||
}
|
||||
|
||||
func GetTestAssetsPath() string {
|
||||
moduleRootPath := GetModuleRootPath()
|
||||
assetsPath := path.Join(moduleRootPath, "assets")
|
||||
|
||||
return assetsPath
|
||||
}
|
||||
|
||||
func getTestImageFilepath() string {
|
||||
assetsPath := GetTestAssetsPath()
|
||||
testImageFilepath := path.Join(assetsPath, "NDM_8901.jpg")
|
||||
return testImageFilepath
|
||||
}
|
||||
|
||||
func getTestExifData() []byte {
|
||||
if testExifData == nil {
|
||||
assetsPath := GetTestAssetsPath()
|
||||
filepath := path.Join(assetsPath, "NDM_8901.jpg.exif")
|
||||
|
||||
var err error
|
||||
|
||||
testExifData, err = ioutil.ReadFile(filepath)
|
||||
log.PanicIf(err)
|
||||
}
|
||||
|
||||
return testExifData
|
||||
}
|
||||
+482
@@ -0,0 +1,482 @@
|
||||
package exifcommon
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
)
|
||||
|
||||
var (
|
||||
typeLogger = log.NewLogger("exif.type")
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotEnoughData is used when there isn't enough data to accommodate what
|
||||
// we're trying to parse (sizeof(type) * unit_count).
|
||||
ErrNotEnoughData = errors.New("not enough data for type")
|
||||
|
||||
// ErrWrongType is used when we try to parse anything other than the
|
||||
// current type.
|
||||
ErrWrongType = errors.New("wrong type, can not parse")
|
||||
|
||||
// ErrUnhandledUndefinedTypedTag is used when we try to parse a tag that's
|
||||
// recorded as an "unknown" type but not a documented tag (therefore
|
||||
// leaving us not knowning how to read it).
|
||||
ErrUnhandledUndefinedTypedTag = errors.New("not a standard unknown-typed tag")
|
||||
)
|
||||
|
||||
// TagTypePrimitive is a type-alias that let's us easily lookup type properties.
|
||||
type TagTypePrimitive uint16
|
||||
|
||||
const (
|
||||
// TypeByte describes an encoded list of bytes.
|
||||
TypeByte TagTypePrimitive = 1
|
||||
|
||||
// TypeAscii describes an encoded list of characters that is terminated
|
||||
// with a NUL in its encoded form.
|
||||
TypeAscii TagTypePrimitive = 2
|
||||
|
||||
// TypeShort describes an encoded list of shorts.
|
||||
TypeShort TagTypePrimitive = 3
|
||||
|
||||
// TypeLong describes an encoded list of longs.
|
||||
TypeLong TagTypePrimitive = 4
|
||||
|
||||
// TypeRational describes an encoded list of rationals.
|
||||
TypeRational TagTypePrimitive = 5
|
||||
|
||||
// TypeUndefined describes an encoded value that has a complex/non-clearcut
|
||||
// interpretation.
|
||||
TypeUndefined TagTypePrimitive = 7
|
||||
|
||||
// We've seen type-8, but have no documentation on it.
|
||||
|
||||
// TypeSignedLong describes an encoded list of signed longs.
|
||||
TypeSignedLong TagTypePrimitive = 9
|
||||
|
||||
// TypeSignedRational describes an encoded list of signed rationals.
|
||||
TypeSignedRational TagTypePrimitive = 10
|
||||
|
||||
// TypeFloat describes an encoded list of floats
|
||||
TypeFloat TagTypePrimitive = 11
|
||||
|
||||
// TypeDouble describes an encoded list of doubles.
|
||||
TypeDouble TagTypePrimitive = 12
|
||||
|
||||
// TypeAsciiNoNul is just a pseudo-type, for our own purposes.
|
||||
TypeAsciiNoNul TagTypePrimitive = 0xf0
|
||||
)
|
||||
|
||||
// String returns the name of the type
|
||||
func (typeType TagTypePrimitive) String() string {
|
||||
return TypeNames[typeType]
|
||||
}
|
||||
|
||||
// Size returns the size of one atomic unit of the type.
|
||||
func (tagType TagTypePrimitive) Size() int {
|
||||
switch tagType {
|
||||
case TypeByte, TypeAscii, TypeAsciiNoNul:
|
||||
return 1
|
||||
case TypeShort:
|
||||
return 2
|
||||
case TypeLong, TypeSignedLong, TypeFloat:
|
||||
return 4
|
||||
case TypeRational, TypeSignedRational, TypeDouble:
|
||||
return 8
|
||||
default:
|
||||
log.Panicf("can not determine tag-value size for type (%d): [%s]",
|
||||
tagType,
|
||||
TypeNames[tagType])
|
||||
// Never called.
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// IsValid returns true if tagType is a valid type.
|
||||
func (tagType TagTypePrimitive) IsValid() bool {
|
||||
|
||||
// TODO(dustin): Add test
|
||||
|
||||
return tagType == TypeByte ||
|
||||
tagType == TypeAscii ||
|
||||
tagType == TypeAsciiNoNul ||
|
||||
tagType == TypeShort ||
|
||||
tagType == TypeLong ||
|
||||
tagType == TypeRational ||
|
||||
tagType == TypeSignedLong ||
|
||||
tagType == TypeSignedRational ||
|
||||
tagType == TypeFloat ||
|
||||
tagType == TypeDouble ||
|
||||
tagType == TypeUndefined
|
||||
}
|
||||
|
||||
var (
|
||||
// TODO(dustin): Rename TypeNames() to typeNames() and add getter.
|
||||
TypeNames = map[TagTypePrimitive]string{
|
||||
TypeByte: "BYTE",
|
||||
TypeAscii: "ASCII",
|
||||
TypeShort: "SHORT",
|
||||
TypeLong: "LONG",
|
||||
TypeRational: "RATIONAL",
|
||||
TypeUndefined: "UNDEFINED",
|
||||
TypeSignedLong: "SLONG",
|
||||
TypeSignedRational: "SRATIONAL",
|
||||
TypeFloat: "FLOAT",
|
||||
TypeDouble: "DOUBLE",
|
||||
|
||||
TypeAsciiNoNul: "_ASCII_NO_NUL",
|
||||
}
|
||||
|
||||
typeNamesR = map[string]TagTypePrimitive{}
|
||||
)
|
||||
|
||||
// Rational describes an unsigned rational value.
|
||||
type Rational struct {
|
||||
// Numerator is the numerator of the rational value.
|
||||
Numerator uint32
|
||||
|
||||
// Denominator is the numerator of the rational value.
|
||||
Denominator uint32
|
||||
}
|
||||
|
||||
// SignedRational describes a signed rational value.
|
||||
type SignedRational struct {
|
||||
// Numerator is the numerator of the rational value.
|
||||
Numerator int32
|
||||
|
||||
// Denominator is the numerator of the rational value.
|
||||
Denominator int32
|
||||
}
|
||||
|
||||
func isPrintableText(s string) bool {
|
||||
for _, c := range s {
|
||||
// unicode.IsPrint() returns false for newline characters.
|
||||
if c == 0x0d || c == 0x0a {
|
||||
continue
|
||||
} else if unicode.IsPrint(rune(c)) == false {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Format returns a stringified value for the given encoding. Automatically
|
||||
// parses. Automatically calculates count based on type size. This function
|
||||
// also supports undefined-type values (the ones that we support, anyway) by
|
||||
// way of the String() method that they all require. We can't be more specific
|
||||
// because we're a base package and we can't refer to it.
|
||||
func FormatFromType(value interface{}, justFirst bool) (phrase string, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
// TODO(dustin): !! Add test
|
||||
|
||||
switch t := value.(type) {
|
||||
case []byte:
|
||||
return DumpBytesToString(t), nil
|
||||
case string:
|
||||
for i, c := range t {
|
||||
if c == 0 {
|
||||
t = t[:i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if isPrintableText(t) == false {
|
||||
phrase = fmt.Sprintf("string with binary data (%d bytes)", len(t))
|
||||
return phrase, nil
|
||||
}
|
||||
|
||||
return t, nil
|
||||
case []uint16, []uint32, []int32, []float64, []float32:
|
||||
val := reflect.ValueOf(t)
|
||||
|
||||
if val.Len() == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if justFirst == true {
|
||||
var valueSuffix string
|
||||
if val.Len() > 1 {
|
||||
valueSuffix = "..."
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%v%s", val.Index(0), valueSuffix), nil
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%v", val), nil
|
||||
case []Rational:
|
||||
if len(t) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
parts := make([]string, len(t))
|
||||
for i, r := range t {
|
||||
parts[i] = fmt.Sprintf("%d/%d", r.Numerator, r.Denominator)
|
||||
|
||||
if justFirst == true {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if justFirst == true {
|
||||
var valueSuffix string
|
||||
if len(t) > 1 {
|
||||
valueSuffix = "..."
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%v%s", parts[0], valueSuffix), nil
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%v", parts), nil
|
||||
case []SignedRational:
|
||||
if len(t) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
parts := make([]string, len(t))
|
||||
for i, r := range t {
|
||||
parts[i] = fmt.Sprintf("%d/%d", r.Numerator, r.Denominator)
|
||||
|
||||
if justFirst == true {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if justFirst == true {
|
||||
var valueSuffix string
|
||||
if len(t) > 1 {
|
||||
valueSuffix = "..."
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%v%s", parts[0], valueSuffix), nil
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%v", parts), nil
|
||||
case fmt.Stringer:
|
||||
s := t.String()
|
||||
if isPrintableText(s) == false {
|
||||
phrase = fmt.Sprintf("stringable with binary data (%d bytes)", len(s))
|
||||
return phrase, nil
|
||||
}
|
||||
|
||||
// An undefined value that is documented (or that we otherwise support).
|
||||
return s, nil
|
||||
default:
|
||||
// Affects only "unknown" values, in general.
|
||||
log.Panicf("type can not be formatted into string: %v", reflect.TypeOf(value).Name())
|
||||
|
||||
// Never called.
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
// Format returns a stringified value for the given encoding. Automatically
|
||||
// parses. Automatically calculates count based on type size.
|
||||
func FormatFromBytes(rawBytes []byte, tagType TagTypePrimitive, justFirst bool, byteOrder binary.ByteOrder) (phrase string, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
// TODO(dustin): !! Add test
|
||||
|
||||
typeSize := tagType.Size()
|
||||
|
||||
if len(rawBytes)%typeSize != 0 {
|
||||
log.Panicf("byte-count (%d) does not align for [%s] type with a size of (%d) bytes", len(rawBytes), TypeNames[tagType], typeSize)
|
||||
}
|
||||
|
||||
// unitCount is the calculated unit-count. This should equal the original
|
||||
// value from the tag (pre-resolution).
|
||||
unitCount := uint32(len(rawBytes) / typeSize)
|
||||
|
||||
// Truncate the items if it's not bytes or a string and we just want the first.
|
||||
|
||||
var value interface{}
|
||||
|
||||
switch tagType {
|
||||
case TypeByte:
|
||||
var err error
|
||||
|
||||
value, err = parser.ParseBytes(rawBytes, unitCount)
|
||||
log.PanicIf(err)
|
||||
case TypeAscii:
|
||||
var err error
|
||||
|
||||
value, err = parser.ParseAscii(rawBytes, unitCount)
|
||||
log.PanicIf(err)
|
||||
case TypeAsciiNoNul:
|
||||
var err error
|
||||
|
||||
value, err = parser.ParseAsciiNoNul(rawBytes, unitCount)
|
||||
log.PanicIf(err)
|
||||
case TypeShort:
|
||||
var err error
|
||||
|
||||
value, err = parser.ParseShorts(rawBytes, unitCount, byteOrder)
|
||||
log.PanicIf(err)
|
||||
case TypeLong:
|
||||
var err error
|
||||
|
||||
value, err = parser.ParseLongs(rawBytes, unitCount, byteOrder)
|
||||
log.PanicIf(err)
|
||||
case TypeFloat:
|
||||
var err error
|
||||
|
||||
value, err = parser.ParseFloats(rawBytes, unitCount, byteOrder)
|
||||
log.PanicIf(err)
|
||||
case TypeDouble:
|
||||
var err error
|
||||
|
||||
value, err = parser.ParseDoubles(rawBytes, unitCount, byteOrder)
|
||||
log.PanicIf(err)
|
||||
case TypeRational:
|
||||
var err error
|
||||
|
||||
value, err = parser.ParseRationals(rawBytes, unitCount, byteOrder)
|
||||
log.PanicIf(err)
|
||||
case TypeSignedLong:
|
||||
var err error
|
||||
|
||||
value, err = parser.ParseSignedLongs(rawBytes, unitCount, byteOrder)
|
||||
log.PanicIf(err)
|
||||
case TypeSignedRational:
|
||||
var err error
|
||||
|
||||
value, err = parser.ParseSignedRationals(rawBytes, unitCount, byteOrder)
|
||||
log.PanicIf(err)
|
||||
default:
|
||||
// Affects only "unknown" values, in general.
|
||||
log.Panicf("value of type [%s] can not be formatted into string", tagType.String())
|
||||
|
||||
// Never called.
|
||||
return "", nil
|
||||
}
|
||||
|
||||
phrase, err = FormatFromType(value, justFirst)
|
||||
log.PanicIf(err)
|
||||
|
||||
return phrase, nil
|
||||
}
|
||||
|
||||
// TranslateStringToType converts user-provided strings to properly-typed
|
||||
// values. If a string, returns a string. Else, assumes that it's a single
|
||||
// number. If a list needs to be processed, it is the caller's responsibility to
|
||||
// split it (according to whichever convention has been established).
|
||||
func TranslateStringToType(tagType TagTypePrimitive, valueString string) (value interface{}, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
if tagType == TypeUndefined {
|
||||
// The caller should just call String() on the decoded type.
|
||||
log.Panicf("undefined-type values are not supported")
|
||||
}
|
||||
|
||||
if tagType == TypeByte {
|
||||
wide, err := strconv.ParseInt(valueString, 16, 8)
|
||||
log.PanicIf(err)
|
||||
|
||||
return byte(wide), nil
|
||||
} else if tagType == TypeAscii || tagType == TypeAsciiNoNul {
|
||||
// Whether or not we're putting an NUL on the end is only relevant for
|
||||
// byte-level encoding. This function really just supports a user
|
||||
// interface.
|
||||
|
||||
return valueString, nil
|
||||
} else if tagType == TypeShort {
|
||||
n, err := strconv.ParseUint(valueString, 10, 16)
|
||||
log.PanicIf(err)
|
||||
|
||||
return uint16(n), nil
|
||||
} else if tagType == TypeLong {
|
||||
n, err := strconv.ParseUint(valueString, 10, 32)
|
||||
log.PanicIf(err)
|
||||
|
||||
return uint32(n), nil
|
||||
} else if tagType == TypeRational {
|
||||
parts := strings.SplitN(valueString, "/", 2)
|
||||
|
||||
numerator, err := strconv.ParseUint(parts[0], 10, 32)
|
||||
log.PanicIf(err)
|
||||
|
||||
denominator, err := strconv.ParseUint(parts[1], 10, 32)
|
||||
log.PanicIf(err)
|
||||
|
||||
return Rational{
|
||||
Numerator: uint32(numerator),
|
||||
Denominator: uint32(denominator),
|
||||
}, nil
|
||||
} else if tagType == TypeSignedLong {
|
||||
n, err := strconv.ParseInt(valueString, 10, 32)
|
||||
log.PanicIf(err)
|
||||
|
||||
return int32(n), nil
|
||||
} else if tagType == TypeFloat {
|
||||
n, err := strconv.ParseFloat(valueString, 32)
|
||||
log.PanicIf(err)
|
||||
|
||||
return float32(n), nil
|
||||
} else if tagType == TypeDouble {
|
||||
n, err := strconv.ParseFloat(valueString, 64)
|
||||
log.PanicIf(err)
|
||||
|
||||
return float64(n), nil
|
||||
} else if tagType == TypeSignedRational {
|
||||
parts := strings.SplitN(valueString, "/", 2)
|
||||
|
||||
numerator, err := strconv.ParseInt(parts[0], 10, 32)
|
||||
log.PanicIf(err)
|
||||
|
||||
denominator, err := strconv.ParseInt(parts[1], 10, 32)
|
||||
log.PanicIf(err)
|
||||
|
||||
return SignedRational{
|
||||
Numerator: int32(numerator),
|
||||
Denominator: int32(denominator),
|
||||
}, nil
|
||||
}
|
||||
|
||||
log.Panicf("from-string encoding for type not supported; this shouldn't happen: [%s]", tagType.String())
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetTypeByName returns the `TagTypePrimitive` for the given type name.
|
||||
// Returns (0) if not valid.
|
||||
func GetTypeByName(typeName string) (tagType TagTypePrimitive, found bool) {
|
||||
tagType, found = typeNamesR[typeName]
|
||||
return tagType, found
|
||||
}
|
||||
|
||||
// BasicTag describes a single tag for any purpose.
|
||||
type BasicTag struct {
|
||||
// FqIfdPath is the fully-qualified IFD-path.
|
||||
FqIfdPath string
|
||||
|
||||
// IfdPath is the unindexed IFD-path.
|
||||
IfdPath string
|
||||
|
||||
// TagId is the tag-ID.
|
||||
TagId uint16
|
||||
}
|
||||
|
||||
func init() {
|
||||
for typeId, typeName := range TypeNames {
|
||||
typeNamesR[typeName] = typeId
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,437 @@
|
||||
package exifcommon
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
)
|
||||
|
||||
func TestTypeByte_String(t *testing.T) {
|
||||
if TypeByte.String() != "BYTE" {
|
||||
t.Fatalf("Type name not correct (byte): [%s]", TypeByte.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeAscii_String(t *testing.T) {
|
||||
if TypeAscii.String() != "ASCII" {
|
||||
t.Fatalf("Type name not correct (ASCII): [%s]", TypeAscii.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeAsciiNoNul_String(t *testing.T) {
|
||||
if TypeAsciiNoNul.String() != "_ASCII_NO_NUL" {
|
||||
t.Fatalf("Type name not correct (ASCII no-NUL): [%s]", TypeAsciiNoNul.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeShort_String(t *testing.T) {
|
||||
if TypeShort.String() != "SHORT" {
|
||||
t.Fatalf("Type name not correct (short): [%s]", TypeShort.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeLong_String(t *testing.T) {
|
||||
if TypeLong.String() != "LONG" {
|
||||
t.Fatalf("Type name not correct (long): [%s]", TypeLong.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeRational_String(t *testing.T) {
|
||||
if TypeRational.String() != "RATIONAL" {
|
||||
t.Fatalf("Type name not correct (rational): [%s]", TypeRational.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeSignedLong_String(t *testing.T) {
|
||||
if TypeSignedLong.String() != "SLONG" {
|
||||
t.Fatalf("Type name not correct (signed long): [%s]", TypeSignedLong.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeSignedRational_String(t *testing.T) {
|
||||
if TypeSignedRational.String() != "SRATIONAL" {
|
||||
t.Fatalf("Type name not correct (signed rational): [%s]", TypeSignedRational.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeFloat_String(t *testing.T) {
|
||||
if TypeFloat.String() != "FLOAT" {
|
||||
t.Fatalf("Type name not correct (float): [%s]", TypeFloat.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeDouble_String(t *testing.T) {
|
||||
if TypeDouble.String() != "DOUBLE" {
|
||||
t.Fatalf("Type name not correct (double): [%s]", TypeDouble.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeByte_Size(t *testing.T) {
|
||||
if TypeByte.Size() != 1 {
|
||||
t.Fatalf("Type size not correct (byte): (%d)", TypeByte.Size())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeAscii_Size(t *testing.T) {
|
||||
if TypeAscii.Size() != 1 {
|
||||
t.Fatalf("Type size not correct (ASCII): (%d)", TypeAscii.Size())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeAsciiNoNul_Size(t *testing.T) {
|
||||
if TypeAsciiNoNul.Size() != 1 {
|
||||
t.Fatalf("Type size not correct (ASCII no-NUL): (%d)", TypeAsciiNoNul.Size())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeShort_Size(t *testing.T) {
|
||||
if TypeShort.Size() != 2 {
|
||||
t.Fatalf("Type size not correct (short): (%d)", TypeShort.Size())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeLong_Size(t *testing.T) {
|
||||
if TypeLong.Size() != 4 {
|
||||
t.Fatalf("Type size not correct (long): (%d)", TypeLong.Size())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeRational_Size(t *testing.T) {
|
||||
if TypeRational.Size() != 8 {
|
||||
t.Fatalf("Type size not correct (rational): (%d)", TypeRational.Size())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeSignedLong_Size(t *testing.T) {
|
||||
if TypeSignedLong.Size() != 4 {
|
||||
t.Fatalf("Type size not correct (signed long): (%d)", TypeSignedLong.Size())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeSignedRational_Size(t *testing.T) {
|
||||
if TypeSignedRational.Size() != 8 {
|
||||
t.Fatalf("Type size not correct (signed rational): (%d)", TypeSignedRational.Size())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeFloat_Size(t *testing.T) {
|
||||
if TypeFloat.Size() != 4 {
|
||||
t.Fatalf("Type size not correct (float): (%d)", TypeFloat.Size())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeDouble_Size(t *testing.T) {
|
||||
if TypeDouble.Size() != 8 {
|
||||
t.Fatalf("Type size not correct (double): (%d)", TypeDouble.Size())
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormat__Byte(t *testing.T) {
|
||||
r := []byte{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
|
||||
s, err := FormatFromBytes(r, TypeByte, false, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
if s != "01 02 03 04 05 06 07 08" {
|
||||
t.Fatalf("Format output not correct (bytes): [%s]", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormat__Ascii(t *testing.T) {
|
||||
r := []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 0}
|
||||
|
||||
s, err := FormatFromBytes(r, TypeAscii, false, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
if s != "abcdefg" {
|
||||
t.Fatalf("Format output not correct (ASCII): [%s]", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormat__AsciiNoNul(t *testing.T) {
|
||||
r := []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}
|
||||
|
||||
s, err := FormatFromBytes(r, TypeAsciiNoNul, false, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
if s != "abcdefgh" {
|
||||
t.Fatalf("Format output not correct (ASCII no-NUL): [%s]", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormat__Short(t *testing.T) {
|
||||
r := []byte{0, 1, 0, 2}
|
||||
|
||||
s, err := FormatFromBytes(r, TypeShort, false, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
if s != "[1 2]" {
|
||||
t.Fatalf("Format output not correct (shorts): [%s]", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormat__Long(t *testing.T) {
|
||||
r := []byte{0, 0, 0, 1, 0, 0, 0, 2}
|
||||
|
||||
s, err := FormatFromBytes(r, TypeLong, false, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
if s != "[1 2]" {
|
||||
t.Fatalf("Format output not correct (longs): [%s]", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormat__Float(t *testing.T) {
|
||||
r := []byte{0x3f, 0x80, 0x00, 0x00,
|
||||
0x40, 0x00, 0x00, 0x00}
|
||||
|
||||
s, err := FormatFromBytes(r, TypeFloat, false, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
if s != "[1 2]" {
|
||||
t.Fatalf("Format output not correct (floats): [%s]", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormat__Double(t *testing.T) {
|
||||
r := []byte{0x40, 0x09, 0x21, 0xfb, 0x53, 0xc8, 0xd4, 0xf1,
|
||||
0x40, 0x05, 0xbf, 0x0a, 0x89, 0xf1, 0xb0, 0xdd}
|
||||
|
||||
s, err := FormatFromBytes(r, TypeDouble, false, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
if s != "[3.14159265 2.71828182]" {
|
||||
t.Fatalf("Format output not correct (doubles): [%s]", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormat__Rational(t *testing.T) {
|
||||
r := []byte{
|
||||
0, 0, 0, 1, 0, 0, 0, 2,
|
||||
0, 0, 0, 3, 0, 0, 0, 4,
|
||||
}
|
||||
|
||||
s, err := FormatFromBytes(r, TypeRational, false, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
if s != "[1/2 3/4]" {
|
||||
t.Fatalf("Format output not correct (rationals): [%s]", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormat__SignedLong(t *testing.T) {
|
||||
r := []byte{0, 0, 0, 1, 0, 0, 0, 2}
|
||||
|
||||
s, err := FormatFromBytes(r, TypeSignedLong, false, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
if s != "[1 2]" {
|
||||
t.Fatalf("Format output not correct (signed longs): [%s]", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormat__SignedRational(t *testing.T) {
|
||||
r := []byte{
|
||||
0, 0, 0, 1, 0, 0, 0, 2,
|
||||
0, 0, 0, 3, 0, 0, 0, 4,
|
||||
}
|
||||
|
||||
s, err := FormatFromBytes(r, TypeSignedRational, false, TestDefaultByteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
if s != "[1/2 3/4]" {
|
||||
t.Fatalf("Format output not correct (signed rationals): [%s]", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormat__Undefined(t *testing.T) {
|
||||
r := []byte{'a', 'b'}
|
||||
|
||||
_, err := FormatFromBytes(r, TypeUndefined, false, TestDefaultByteOrder)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error.")
|
||||
} else if err.Error() != "can not determine tag-value size for type (7): [UNDEFINED]" {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslateStringToType__TypeUndefined(t *testing.T) {
|
||||
_, err := TranslateStringToType(TypeUndefined, "")
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error.")
|
||||
} else if err.Error() != "undefined-type values are not supported" {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslateStringToType__TypeByte(t *testing.T) {
|
||||
v, err := TranslateStringToType(TypeByte, "02")
|
||||
log.PanicIf(err)
|
||||
|
||||
if v != byte(2) {
|
||||
t.Fatalf("Translation of string to type not correct (bytes): %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslateStringToType__TypeAscii(t *testing.T) {
|
||||
v, err := TranslateStringToType(TypeAscii, "abcdefgh")
|
||||
log.PanicIf(err)
|
||||
|
||||
if v != "abcdefgh" {
|
||||
t.Fatalf("Translation of string to type not correct (ascii): %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslateStringToType__TypeAsciiNoNul(t *testing.T) {
|
||||
v, err := TranslateStringToType(TypeAsciiNoNul, "abcdefgh")
|
||||
log.PanicIf(err)
|
||||
|
||||
if v != "abcdefgh" {
|
||||
t.Fatalf("Translation of string to type not correct (ascii no-NUL): %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslateStringToType__TypeShort(t *testing.T) {
|
||||
v, err := TranslateStringToType(TypeShort, "11")
|
||||
log.PanicIf(err)
|
||||
|
||||
if v != uint16(11) {
|
||||
t.Fatalf("Translation of string to type not correct (short): %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslateStringToType__TypeLong(t *testing.T) {
|
||||
v, err := TranslateStringToType(TypeLong, "11")
|
||||
log.PanicIf(err)
|
||||
|
||||
if v != uint32(11) {
|
||||
t.Fatalf("Translation of string to type not correct (long): %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslateStringToType__TypeFloat(t *testing.T) {
|
||||
v, err := TranslateStringToType(TypeFloat, "3.14159265")
|
||||
log.PanicIf(err)
|
||||
|
||||
expected := float32(3.14159265)
|
||||
if v.(float32) < expected || v.(float32) >= math.Nextafter32(expected, expected+1) {
|
||||
t.Fatalf("Translation of string to type not correct (float32): %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslateStringToType__TypeDouble(t *testing.T) {
|
||||
v, err := TranslateStringToType(TypeDouble, "3.14159265")
|
||||
log.PanicIf(err)
|
||||
|
||||
expected := float64(3.14159265)
|
||||
if v.(float64) < expected || v.(float64) >= math.Nextafter(expected, expected+1) {
|
||||
t.Fatalf("Translation of string to type not correct (double): %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslateStringToType__TypeRational(t *testing.T) {
|
||||
v, err := TranslateStringToType(TypeRational, "11/22")
|
||||
log.PanicIf(err)
|
||||
|
||||
r := v.(Rational)
|
||||
|
||||
if r.Numerator != 11 || r.Denominator != 22 {
|
||||
t.Fatalf("Translation of string to type not correct (rational): %v", r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslateStringToType__TypeSignedLong(t *testing.T) {
|
||||
v, err := TranslateStringToType(TypeSignedLong, "11")
|
||||
log.PanicIf(err)
|
||||
|
||||
if v != int32(11) {
|
||||
t.Fatalf("Translation of string to type not correct (signed long): %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslateStringToType__TypeSignedRational(t *testing.T) {
|
||||
v, err := TranslateStringToType(TypeSignedRational, "11/22")
|
||||
log.PanicIf(err)
|
||||
|
||||
r := v.(SignedRational)
|
||||
|
||||
if r.Numerator != 11 || r.Denominator != 22 {
|
||||
t.Fatalf("Translation of string to type not correct (signed rational): %v", r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslateStringToType__InvalidType(t *testing.T) {
|
||||
_, err := TranslateStringToType(99, "11/22")
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error for invalid type.")
|
||||
} else if err.Error() != "from-string encoding for type not supported; this shouldn't happen: []" {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// } else if tagType == TypeLong {
|
||||
// n, err := strconv.ParseUint(valueString, 10, 32)
|
||||
// log.PanicIf(err)
|
||||
|
||||
// return uint32(n), nil
|
||||
// } else if tagType == TypeRational {
|
||||
// parts := strings.SplitN(valueString, "/", 2)
|
||||
|
||||
// numerator, err := strconv.ParseUint(parts[0], 10, 32)
|
||||
// log.PanicIf(err)
|
||||
|
||||
// denominator, err := strconv.ParseUint(parts[1], 10, 32)
|
||||
// log.PanicIf(err)
|
||||
|
||||
// return Rational{
|
||||
// Numerator: uint32(numerator),
|
||||
// Denominator: uint32(denominator),
|
||||
// }, nil
|
||||
// } else if tagType == TypeSignedLong {
|
||||
// n, err := strconv.ParseInt(valueString, 10, 32)
|
||||
// log.PanicIf(err)
|
||||
|
||||
// return int32(n), nil
|
||||
// } else if tagType == TypeSignedRational {
|
||||
// parts := strings.SplitN(valueString, "/", 2)
|
||||
|
||||
// numerator, err := strconv.ParseInt(parts[0], 10, 32)
|
||||
// log.PanicIf(err)
|
||||
|
||||
// denominator, err := strconv.ParseInt(parts[1], 10, 32)
|
||||
// log.PanicIf(err)
|
||||
|
||||
// return SignedRational{
|
||||
// Numerator: int32(numerator),
|
||||
// Denominator: int32(denominator),
|
||||
// }, nil
|
||||
// }
|
||||
|
||||
// log.Panicf("from-string encoding for type not supported; this shouldn't happen: [%s]", tagType.String())
|
||||
// return nil, nil
|
||||
// }
|
||||
|
||||
func TestIsPrintableText_letters(t *testing.T) {
|
||||
if isPrintableText("abc") != true {
|
||||
t.Fatalf("Printable text interpreted as nonprintable.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsPrintableText_space(t *testing.T) {
|
||||
if isPrintableText(" ") != true {
|
||||
t.Fatalf("Printable text interpreted as nonprintable.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsPrintableText_newlines(t *testing.T) {
|
||||
if isPrintableText("\r\n") != true {
|
||||
t.Fatalf("Printable text interpreted as nonprintable.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsPrintableText_punctuationAndSymbols(t *testing.T) {
|
||||
if isPrintableText(",:-/$©") != true {
|
||||
t.Fatalf("Printable text interpreted as nonprintable.")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package exifcommon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
)
|
||||
|
||||
var (
|
||||
timeType = reflect.TypeOf(time.Time{})
|
||||
)
|
||||
|
||||
// DumpBytes prints a list of hex-encoded bytes.
|
||||
func DumpBytes(data []byte) {
|
||||
fmt.Printf("DUMP: ")
|
||||
for _, x := range data {
|
||||
fmt.Printf("%02x ", x)
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
|
||||
// DumpBytesClause prints a list like DumpBytes(), but encapsulated in
|
||||
// "[]byte { ... }".
|
||||
func DumpBytesClause(data []byte) {
|
||||
fmt.Printf("DUMP: ")
|
||||
|
||||
fmt.Printf("[]byte { ")
|
||||
|
||||
for i, x := range data {
|
||||
fmt.Printf("0x%02x", x)
|
||||
|
||||
if i < len(data)-1 {
|
||||
fmt.Printf(", ")
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf(" }\n")
|
||||
}
|
||||
|
||||
// DumpBytesToString returns a stringified list of hex-encoded bytes.
|
||||
func DumpBytesToString(data []byte) string {
|
||||
b := new(bytes.Buffer)
|
||||
|
||||
for i, x := range data {
|
||||
_, err := b.WriteString(fmt.Sprintf("%02x", x))
|
||||
log.PanicIf(err)
|
||||
|
||||
if i < len(data)-1 {
|
||||
_, err := b.WriteRune(' ')
|
||||
log.PanicIf(err)
|
||||
}
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// DumpBytesClauseToString returns a comma-separated list of hex-encoded bytes.
|
||||
func DumpBytesClauseToString(data []byte) string {
|
||||
b := new(bytes.Buffer)
|
||||
|
||||
for i, x := range data {
|
||||
_, err := b.WriteString(fmt.Sprintf("0x%02x", x))
|
||||
log.PanicIf(err)
|
||||
|
||||
if i < len(data)-1 {
|
||||
_, err := b.WriteString(", ")
|
||||
log.PanicIf(err)
|
||||
}
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// ExifFullTimestampString produces a string like "2018:11:30 13:01:49" from a
|
||||
// `time.Time` struct. It will attempt to convert to UTC first.
|
||||
func ExifFullTimestampString(t time.Time) (fullTimestampPhrase string) {
|
||||
t = t.UTC()
|
||||
|
||||
return fmt.Sprintf("%04d:%02d:%02d %02d:%02d:%02d", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second())
|
||||
}
|
||||
|
||||
// ParseExifFullTimestamp parses dates like "2018:11:30 13:01:49" into a UTC
|
||||
// `time.Time` struct.
|
||||
func ParseExifFullTimestamp(fullTimestampPhrase string) (timestamp time.Time, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
parts := strings.Split(fullTimestampPhrase, " ")
|
||||
datestampValue, timestampValue := parts[0], parts[1]
|
||||
|
||||
// Normalize the separators.
|
||||
datestampValue = strings.ReplaceAll(datestampValue, "-", ":")
|
||||
timestampValue = strings.ReplaceAll(timestampValue, "-", ":")
|
||||
|
||||
dateParts := strings.Split(datestampValue, ":")
|
||||
|
||||
year, err := strconv.ParseUint(dateParts[0], 10, 16)
|
||||
if err != nil {
|
||||
log.Panicf("could not parse year")
|
||||
}
|
||||
|
||||
month, err := strconv.ParseUint(dateParts[1], 10, 8)
|
||||
if err != nil {
|
||||
log.Panicf("could not parse month")
|
||||
}
|
||||
|
||||
day, err := strconv.ParseUint(dateParts[2], 10, 8)
|
||||
if err != nil {
|
||||
log.Panicf("could not parse day")
|
||||
}
|
||||
|
||||
timeParts := strings.Split(timestampValue, ":")
|
||||
|
||||
hour, err := strconv.ParseUint(timeParts[0], 10, 8)
|
||||
if err != nil {
|
||||
log.Panicf("could not parse hour")
|
||||
}
|
||||
|
||||
minute, err := strconv.ParseUint(timeParts[1], 10, 8)
|
||||
if err != nil {
|
||||
log.Panicf("could not parse minute")
|
||||
}
|
||||
|
||||
second, err := strconv.ParseUint(timeParts[2], 10, 8)
|
||||
if err != nil {
|
||||
log.Panicf("could not parse second")
|
||||
}
|
||||
|
||||
timestamp = time.Date(int(year), time.Month(month), int(day), int(hour), int(minute), int(second), 0, time.UTC)
|
||||
return timestamp, nil
|
||||
}
|
||||
|
||||
// IsTime returns true if the value is a `time.Time`.
|
||||
func IsTime(v interface{}) bool {
|
||||
|
||||
// TODO(dustin): Add test
|
||||
|
||||
return reflect.TypeOf(v) == timeType
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package exifcommon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
)
|
||||
|
||||
func TestDumpBytes(t *testing.T) {
|
||||
DumpBytes([]byte{1, 2, 3, 4})
|
||||
}
|
||||
|
||||
func TestDumpBytesClause(t *testing.T) {
|
||||
DumpBytesClause([]byte{1, 2, 3, 4})
|
||||
}
|
||||
|
||||
func TestDumpBytesToString(t *testing.T) {
|
||||
s := DumpBytesToString([]byte{1, 2, 3, 4})
|
||||
if s != "01 02 03 04" {
|
||||
t.Fatalf("String not correct: [%s]", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDumpBytesClauseToString(t *testing.T) {
|
||||
s := DumpBytesClauseToString([]byte{1, 2, 3, 4})
|
||||
if s != "0x01, 0x02, 0x03, 0x04" {
|
||||
t.Fatalf("Stringified clause is not correct: [%s]", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExifFullTimestampString(t *testing.T) {
|
||||
originalPhrase := "2018:11:30 13:01:49"
|
||||
|
||||
timestamp, err := ParseExifFullTimestamp(originalPhrase)
|
||||
log.PanicIf(err)
|
||||
|
||||
restoredPhrase := ExifFullTimestampString(timestamp)
|
||||
if restoredPhrase != originalPhrase {
|
||||
t.Fatalf("Final phrase [%s] does not equal original phrase [%s]", restoredPhrase, originalPhrase)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleExifFullTimestampString() {
|
||||
originalPhrase := "2018:11:30 13:01:49"
|
||||
|
||||
timestamp, err := ParseExifFullTimestamp(originalPhrase)
|
||||
log.PanicIf(err)
|
||||
|
||||
restoredPhrase := ExifFullTimestampString(timestamp)
|
||||
fmt.Printf("To EXIF timestamp: [%s]\n", restoredPhrase)
|
||||
|
||||
// Output:
|
||||
// To EXIF timestamp: [2018:11:30 13:01:49]
|
||||
}
|
||||
|
||||
func TestParseExifFullTimestamp(t *testing.T) {
|
||||
timestamp, err := ParseExifFullTimestamp("2018:11:30 13:01:49")
|
||||
log.PanicIf(err)
|
||||
|
||||
actual := timestamp.Format(time.RFC3339)
|
||||
expected := "2018-11-30T13:01:49Z"
|
||||
|
||||
if actual != expected {
|
||||
t.Fatalf("time not formatted correctly: [%s] != [%s]", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleParseExifFullTimestamp() {
|
||||
originalPhrase := "2018:11:30 13:01:49"
|
||||
|
||||
timestamp, err := ParseExifFullTimestamp(originalPhrase)
|
||||
log.PanicIf(err)
|
||||
|
||||
fmt.Printf("To Go timestamp: [%s]\n", timestamp.Format(time.RFC3339))
|
||||
|
||||
// Output:
|
||||
// To Go timestamp: [2018-11-30T13:01:49Z]
|
||||
}
|
||||
@@ -0,0 +1,464 @@
|
||||
package exifcommon
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
)
|
||||
|
||||
var (
|
||||
parser *Parser
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotFarValue indicates that an offset-based lookup was attempted for a
|
||||
// non-offset-based (embedded) value.
|
||||
ErrNotFarValue = errors.New("not a far value")
|
||||
)
|
||||
|
||||
// ValueContext embeds all of the parameters required to find and extract the
|
||||
// actual tag value.
|
||||
type ValueContext struct {
|
||||
unitCount uint32
|
||||
valueOffset uint32
|
||||
rawValueOffset []byte
|
||||
rs io.ReadSeeker
|
||||
|
||||
tagType TagTypePrimitive
|
||||
byteOrder binary.ByteOrder
|
||||
|
||||
// undefinedValueTagType is the effective type to use if this is an
|
||||
// "undefined" value.
|
||||
undefinedValueTagType TagTypePrimitive
|
||||
|
||||
ifdPath string
|
||||
tagId uint16
|
||||
}
|
||||
|
||||
// TODO(dustin): We can update newValueContext() to derive `valueOffset` itself (from `rawValueOffset`).
|
||||
|
||||
// NewValueContext returns a new ValueContext struct.
|
||||
func NewValueContext(ifdPath string, tagId uint16, unitCount, valueOffset uint32, rawValueOffset []byte, rs io.ReadSeeker, tagType TagTypePrimitive, byteOrder binary.ByteOrder) *ValueContext {
|
||||
return &ValueContext{
|
||||
unitCount: unitCount,
|
||||
valueOffset: valueOffset,
|
||||
rawValueOffset: rawValueOffset,
|
||||
rs: rs,
|
||||
|
||||
tagType: tagType,
|
||||
byteOrder: byteOrder,
|
||||
|
||||
ifdPath: ifdPath,
|
||||
tagId: tagId,
|
||||
}
|
||||
}
|
||||
|
||||
// SetUndefinedValueType sets the effective type if this is an unknown-type tag.
|
||||
func (vc *ValueContext) SetUndefinedValueType(tagType TagTypePrimitive) {
|
||||
if vc.tagType != TypeUndefined {
|
||||
log.Panicf("can not set effective type for unknown-type tag because this is *not* an unknown-type tag")
|
||||
}
|
||||
|
||||
vc.undefinedValueTagType = tagType
|
||||
}
|
||||
|
||||
// UnitCount returns the embedded unit-count.
|
||||
func (vc *ValueContext) UnitCount() uint32 {
|
||||
return vc.unitCount
|
||||
}
|
||||
|
||||
// ValueOffset returns the value-offset decoded as a `uint32`.
|
||||
func (vc *ValueContext) ValueOffset() uint32 {
|
||||
return vc.valueOffset
|
||||
}
|
||||
|
||||
// RawValueOffset returns the uninterpreted value-offset. This is used for
|
||||
// embedded values (values small enough to fit within the offset bytes rather
|
||||
// than needing to be stored elsewhere and referred to by an actual offset).
|
||||
func (vc *ValueContext) RawValueOffset() []byte {
|
||||
return vc.rawValueOffset
|
||||
}
|
||||
|
||||
// AddressableData returns the block of data that we can dereference into.
|
||||
func (vc *ValueContext) AddressableData() io.ReadSeeker {
|
||||
|
||||
// RELEASE)dustin): Rename from AddressableData() to ReadSeeker()
|
||||
|
||||
return vc.rs
|
||||
}
|
||||
|
||||
// ByteOrder returns the byte-order of numbers.
|
||||
func (vc *ValueContext) ByteOrder() binary.ByteOrder {
|
||||
return vc.byteOrder
|
||||
}
|
||||
|
||||
// IfdPath returns the path of the IFD containing this tag.
|
||||
func (vc *ValueContext) IfdPath() string {
|
||||
return vc.ifdPath
|
||||
}
|
||||
|
||||
// TagId returns the ID of the tag that we represent.
|
||||
func (vc *ValueContext) TagId() uint16 {
|
||||
return vc.tagId
|
||||
}
|
||||
|
||||
// isEmbedded returns whether the value is embedded or a reference. This can't
|
||||
// be precalculated since the size is not defined for all types (namely the
|
||||
// "undefined" types).
|
||||
func (vc *ValueContext) isEmbedded() bool {
|
||||
tagType := vc.effectiveValueType()
|
||||
|
||||
return (tagType.Size() * int(vc.unitCount)) <= 4
|
||||
}
|
||||
|
||||
// SizeInBytes returns the number of bytes that this value requires. The
|
||||
// underlying call will panic if the type is UNDEFINED. It is the
|
||||
// responsibility of the caller to preemptively check that.
|
||||
func (vc *ValueContext) SizeInBytes() int {
|
||||
tagType := vc.effectiveValueType()
|
||||
|
||||
return tagType.Size() * int(vc.unitCount)
|
||||
}
|
||||
|
||||
// effectiveValueType returns the effective type of the unknown-type tag or, if
|
||||
// not unknown, the actual type.
|
||||
func (vc *ValueContext) effectiveValueType() (tagType TagTypePrimitive) {
|
||||
if vc.tagType == TypeUndefined {
|
||||
tagType = vc.undefinedValueTagType
|
||||
|
||||
if tagType == 0 {
|
||||
log.Panicf("undefined-value type not set")
|
||||
}
|
||||
} else {
|
||||
tagType = vc.tagType
|
||||
}
|
||||
|
||||
return tagType
|
||||
}
|
||||
|
||||
// readRawEncoded returns the encoded bytes for the value that we represent.
|
||||
func (vc *ValueContext) readRawEncoded() (rawBytes []byte, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
tagType := vc.effectiveValueType()
|
||||
|
||||
unitSizeRaw := uint32(tagType.Size())
|
||||
|
||||
if vc.isEmbedded() == true {
|
||||
byteLength := unitSizeRaw * vc.unitCount
|
||||
return vc.rawValueOffset[:byteLength], nil
|
||||
}
|
||||
|
||||
_, err = vc.rs.Seek(int64(vc.valueOffset), io.SeekStart)
|
||||
log.PanicIf(err)
|
||||
|
||||
rawBytes = make([]byte, vc.unitCount*unitSizeRaw)
|
||||
|
||||
_, err = io.ReadFull(vc.rs, rawBytes)
|
||||
log.PanicIf(err)
|
||||
|
||||
return rawBytes, nil
|
||||
}
|
||||
|
||||
// GetFarOffset returns the offset if the value is not embedded [within the
|
||||
// pointer itself] or an error if an embedded value.
|
||||
func (vc *ValueContext) GetFarOffset() (offset uint32, err error) {
|
||||
if vc.isEmbedded() == true {
|
||||
return 0, ErrNotFarValue
|
||||
}
|
||||
|
||||
return vc.valueOffset, nil
|
||||
}
|
||||
|
||||
// ReadRawEncoded returns the encoded bytes for the value that we represent.
|
||||
func (vc *ValueContext) ReadRawEncoded() (rawBytes []byte, err error) {
|
||||
|
||||
// TODO(dustin): Remove this method and rename readRawEncoded in its place.
|
||||
|
||||
return vc.readRawEncoded()
|
||||
}
|
||||
|
||||
// Format returns a string representation for the value.
|
||||
//
|
||||
// Where the type is not ASCII, `justFirst` indicates whether to just stringify
|
||||
// the first item in the slice (or return an empty string if the slice is
|
||||
// empty).
|
||||
//
|
||||
// Since this method lacks the information to process undefined-type tags (e.g.
|
||||
// byte-order, tag-ID, IFD type), it will return an error if attempted. See
|
||||
// `Undefined()`.
|
||||
func (vc *ValueContext) Format() (value string, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
rawBytes, err := vc.readRawEncoded()
|
||||
log.PanicIf(err)
|
||||
|
||||
phrase, err := FormatFromBytes(rawBytes, vc.effectiveValueType(), false, vc.byteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
return phrase, nil
|
||||
}
|
||||
|
||||
// FormatFirst is similar to `Format` but only gets and stringifies the first
|
||||
// item.
|
||||
func (vc *ValueContext) FormatFirst() (value string, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
rawBytes, err := vc.readRawEncoded()
|
||||
log.PanicIf(err)
|
||||
|
||||
phrase, err := FormatFromBytes(rawBytes, vc.tagType, true, vc.byteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
return phrase, nil
|
||||
}
|
||||
|
||||
// ReadBytes parses the encoded byte-array from the value-context.
|
||||
func (vc *ValueContext) ReadBytes() (value []byte, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
rawValue, err := vc.readRawEncoded()
|
||||
log.PanicIf(err)
|
||||
|
||||
value, err = parser.ParseBytes(rawValue, vc.unitCount)
|
||||
log.PanicIf(err)
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// ReadAscii parses the encoded NUL-terminated ASCII string from the value-
|
||||
// context.
|
||||
func (vc *ValueContext) ReadAscii() (value string, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
rawValue, err := vc.readRawEncoded()
|
||||
log.PanicIf(err)
|
||||
|
||||
value, err = parser.ParseAscii(rawValue, vc.unitCount)
|
||||
log.PanicIf(err)
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// ReadAsciiNoNul parses the non-NUL-terminated encoded ASCII string from the
|
||||
// value-context.
|
||||
func (vc *ValueContext) ReadAsciiNoNul() (value string, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
rawValue, err := vc.readRawEncoded()
|
||||
log.PanicIf(err)
|
||||
|
||||
value, err = parser.ParseAsciiNoNul(rawValue, vc.unitCount)
|
||||
log.PanicIf(err)
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// ReadShorts parses the list of encoded shorts from the value-context.
|
||||
func (vc *ValueContext) ReadShorts() (value []uint16, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
rawValue, err := vc.readRawEncoded()
|
||||
log.PanicIf(err)
|
||||
|
||||
value, err = parser.ParseShorts(rawValue, vc.unitCount, vc.byteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// ReadLongs parses the list of encoded, unsigned longs from the value-context.
|
||||
func (vc *ValueContext) ReadLongs() (value []uint32, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
rawValue, err := vc.readRawEncoded()
|
||||
log.PanicIf(err)
|
||||
|
||||
value, err = parser.ParseLongs(rawValue, vc.unitCount, vc.byteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// ReadFloats parses the list of encoded, floats from the value-context.
|
||||
func (vc *ValueContext) ReadFloats() (value []float32, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
rawValue, err := vc.readRawEncoded()
|
||||
log.PanicIf(err)
|
||||
|
||||
value, err = parser.ParseFloats(rawValue, vc.unitCount, vc.byteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// ReadDoubles parses the list of encoded, doubles from the value-context.
|
||||
func (vc *ValueContext) ReadDoubles() (value []float64, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
rawValue, err := vc.readRawEncoded()
|
||||
log.PanicIf(err)
|
||||
|
||||
value, err = parser.ParseDoubles(rawValue, vc.unitCount, vc.byteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// ReadRationals parses the list of encoded, unsigned rationals from the value-
|
||||
// context.
|
||||
func (vc *ValueContext) ReadRationals() (value []Rational, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
rawValue, err := vc.readRawEncoded()
|
||||
log.PanicIf(err)
|
||||
|
||||
value, err = parser.ParseRationals(rawValue, vc.unitCount, vc.byteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// ReadSignedLongs parses the list of encoded, signed longs from the value-context.
|
||||
func (vc *ValueContext) ReadSignedLongs() (value []int32, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
rawValue, err := vc.readRawEncoded()
|
||||
log.PanicIf(err)
|
||||
|
||||
value, err = parser.ParseSignedLongs(rawValue, vc.unitCount, vc.byteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// ReadSignedRationals parses the list of encoded, signed rationals from the
|
||||
// value-context.
|
||||
func (vc *ValueContext) ReadSignedRationals() (value []SignedRational, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
rawValue, err := vc.readRawEncoded()
|
||||
log.PanicIf(err)
|
||||
|
||||
value, err = parser.ParseSignedRationals(rawValue, vc.unitCount, vc.byteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// Values knows how to resolve the given value. This value is always a list
|
||||
// (undefined-values aside), so we're named accordingly.
|
||||
//
|
||||
// Since this method lacks the information to process unknown-type tags (e.g.
|
||||
// byte-order, tag-ID, IFD type), it will return an error if attempted. See
|
||||
// `Undefined()`.
|
||||
func (vc *ValueContext) Values() (values interface{}, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
if vc.tagType == TypeByte {
|
||||
values, err = vc.ReadBytes()
|
||||
log.PanicIf(err)
|
||||
} else if vc.tagType == TypeAscii {
|
||||
values, err = vc.ReadAscii()
|
||||
log.PanicIf(err)
|
||||
} else if vc.tagType == TypeAsciiNoNul {
|
||||
values, err = vc.ReadAsciiNoNul()
|
||||
log.PanicIf(err)
|
||||
} else if vc.tagType == TypeShort {
|
||||
values, err = vc.ReadShorts()
|
||||
log.PanicIf(err)
|
||||
} else if vc.tagType == TypeLong {
|
||||
values, err = vc.ReadLongs()
|
||||
log.PanicIf(err)
|
||||
} else if vc.tagType == TypeRational {
|
||||
values, err = vc.ReadRationals()
|
||||
log.PanicIf(err)
|
||||
} else if vc.tagType == TypeSignedLong {
|
||||
values, err = vc.ReadSignedLongs()
|
||||
log.PanicIf(err)
|
||||
} else if vc.tagType == TypeSignedRational {
|
||||
values, err = vc.ReadSignedRationals()
|
||||
log.PanicIf(err)
|
||||
} else if vc.tagType == TypeFloat {
|
||||
values, err = vc.ReadFloats()
|
||||
log.PanicIf(err)
|
||||
} else if vc.tagType == TypeDouble {
|
||||
values, err = vc.ReadDoubles()
|
||||
log.PanicIf(err)
|
||||
} else if vc.tagType == TypeUndefined {
|
||||
log.Panicf("will not parse undefined-type value")
|
||||
|
||||
// Never called.
|
||||
return nil, nil
|
||||
} else {
|
||||
log.Panicf("value of type [%s] is unparseable", vc.tagType)
|
||||
// Never called.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
parser = new(Parser)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,273 @@
|
||||
package exifcommon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
)
|
||||
|
||||
var (
|
||||
typeEncodeLogger = log.NewLogger("exif.type_encode")
|
||||
)
|
||||
|
||||
// EncodedData encapsulates the compound output of an encoding operation.
|
||||
type EncodedData struct {
|
||||
Type TagTypePrimitive
|
||||
Encoded []byte
|
||||
|
||||
// TODO(dustin): Is this really necessary? We might have this just to correlate to the incoming stream format (raw bytes and a unit-count both for incoming and outgoing).
|
||||
UnitCount uint32
|
||||
}
|
||||
|
||||
// ValueEncoder knows how to encode values of every type to bytes.
|
||||
type ValueEncoder struct {
|
||||
byteOrder binary.ByteOrder
|
||||
}
|
||||
|
||||
// NewValueEncoder returns a new ValueEncoder.
|
||||
func NewValueEncoder(byteOrder binary.ByteOrder) *ValueEncoder {
|
||||
return &ValueEncoder{
|
||||
byteOrder: byteOrder,
|
||||
}
|
||||
}
|
||||
|
||||
func (ve *ValueEncoder) encodeBytes(value []uint8) (ed EncodedData, err error) {
|
||||
ed.Type = TypeByte
|
||||
ed.Encoded = []byte(value)
|
||||
ed.UnitCount = uint32(len(value))
|
||||
|
||||
return ed, nil
|
||||
}
|
||||
|
||||
func (ve *ValueEncoder) encodeAscii(value string) (ed EncodedData, err error) {
|
||||
ed.Type = TypeAscii
|
||||
|
||||
ed.Encoded = []byte(value)
|
||||
ed.Encoded = append(ed.Encoded, 0)
|
||||
|
||||
ed.UnitCount = uint32(len(ed.Encoded))
|
||||
|
||||
return ed, nil
|
||||
}
|
||||
|
||||
// encodeAsciiNoNul returns a string encoded as a byte-string without a trailing
|
||||
// NUL byte.
|
||||
//
|
||||
// Note that:
|
||||
//
|
||||
// 1. This type can not be automatically encoded using `Encode()`. The default
|
||||
// mode is to encode *with* a trailing NUL byte using `encodeAscii`. Only
|
||||
// certain undefined-type tags using an unterminated ASCII string and these
|
||||
// are exceptional in nature.
|
||||
//
|
||||
// 2. The presence of this method allows us to completely test the complimentary
|
||||
// no-nul parser.
|
||||
//
|
||||
func (ve *ValueEncoder) encodeAsciiNoNul(value string) (ed EncodedData, err error) {
|
||||
ed.Type = TypeAsciiNoNul
|
||||
ed.Encoded = []byte(value)
|
||||
ed.UnitCount = uint32(len(ed.Encoded))
|
||||
|
||||
return ed, nil
|
||||
}
|
||||
|
||||
func (ve *ValueEncoder) encodeShorts(value []uint16) (ed EncodedData, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
ed.UnitCount = uint32(len(value))
|
||||
ed.Encoded = make([]byte, ed.UnitCount*2)
|
||||
|
||||
for i := uint32(0); i < ed.UnitCount; i++ {
|
||||
ve.byteOrder.PutUint16(ed.Encoded[i*2:(i+1)*2], value[i])
|
||||
}
|
||||
|
||||
ed.Type = TypeShort
|
||||
|
||||
return ed, nil
|
||||
}
|
||||
|
||||
func (ve *ValueEncoder) encodeLongs(value []uint32) (ed EncodedData, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
ed.UnitCount = uint32(len(value))
|
||||
ed.Encoded = make([]byte, ed.UnitCount*4)
|
||||
|
||||
for i := uint32(0); i < ed.UnitCount; i++ {
|
||||
ve.byteOrder.PutUint32(ed.Encoded[i*4:(i+1)*4], value[i])
|
||||
}
|
||||
|
||||
ed.Type = TypeLong
|
||||
|
||||
return ed, nil
|
||||
}
|
||||
|
||||
func (ve *ValueEncoder) encodeFloats(value []float32) (ed EncodedData, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
ed.UnitCount = uint32(len(value))
|
||||
ed.Encoded = make([]byte, ed.UnitCount*4)
|
||||
|
||||
for i := uint32(0); i < ed.UnitCount; i++ {
|
||||
ve.byteOrder.PutUint32(ed.Encoded[i*4:(i+1)*4], math.Float32bits(value[i]))
|
||||
}
|
||||
|
||||
ed.Type = TypeFloat
|
||||
|
||||
return ed, nil
|
||||
}
|
||||
|
||||
func (ve *ValueEncoder) encodeDoubles(value []float64) (ed EncodedData, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
ed.UnitCount = uint32(len(value))
|
||||
ed.Encoded = make([]byte, ed.UnitCount*8)
|
||||
|
||||
for i := uint32(0); i < ed.UnitCount; i++ {
|
||||
ve.byteOrder.PutUint64(ed.Encoded[i*8:(i+1)*8], math.Float64bits(value[i]))
|
||||
}
|
||||
|
||||
ed.Type = TypeDouble
|
||||
|
||||
return ed, nil
|
||||
}
|
||||
|
||||
func (ve *ValueEncoder) encodeRationals(value []Rational) (ed EncodedData, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
ed.UnitCount = uint32(len(value))
|
||||
ed.Encoded = make([]byte, ed.UnitCount*8)
|
||||
|
||||
for i := uint32(0); i < ed.UnitCount; i++ {
|
||||
ve.byteOrder.PutUint32(ed.Encoded[i*8+0:i*8+4], value[i].Numerator)
|
||||
ve.byteOrder.PutUint32(ed.Encoded[i*8+4:i*8+8], value[i].Denominator)
|
||||
}
|
||||
|
||||
ed.Type = TypeRational
|
||||
|
||||
return ed, nil
|
||||
}
|
||||
|
||||
func (ve *ValueEncoder) encodeSignedLongs(value []int32) (ed EncodedData, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
ed.UnitCount = uint32(len(value))
|
||||
|
||||
b := bytes.NewBuffer(make([]byte, 0, 8*ed.UnitCount))
|
||||
|
||||
for i := uint32(0); i < ed.UnitCount; i++ {
|
||||
err := binary.Write(b, ve.byteOrder, value[i])
|
||||
log.PanicIf(err)
|
||||
}
|
||||
|
||||
ed.Type = TypeSignedLong
|
||||
ed.Encoded = b.Bytes()
|
||||
|
||||
return ed, nil
|
||||
}
|
||||
|
||||
func (ve *ValueEncoder) encodeSignedRationals(value []SignedRational) (ed EncodedData, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
ed.UnitCount = uint32(len(value))
|
||||
|
||||
b := bytes.NewBuffer(make([]byte, 0, 8*ed.UnitCount))
|
||||
|
||||
for i := uint32(0); i < ed.UnitCount; i++ {
|
||||
err := binary.Write(b, ve.byteOrder, value[i].Numerator)
|
||||
log.PanicIf(err)
|
||||
|
||||
err = binary.Write(b, ve.byteOrder, value[i].Denominator)
|
||||
log.PanicIf(err)
|
||||
}
|
||||
|
||||
ed.Type = TypeSignedRational
|
||||
ed.Encoded = b.Bytes()
|
||||
|
||||
return ed, nil
|
||||
}
|
||||
|
||||
// Encode returns bytes for the given value, infering type from the actual
|
||||
// value. This does not support `TypeAsciiNoNull` (all strings are encoded as
|
||||
// `TypeAscii`).
|
||||
func (ve *ValueEncoder) Encode(value interface{}) (ed EncodedData, err error) {
|
||||
defer func() {
|
||||
if state := recover(); state != nil {
|
||||
err = log.Wrap(state.(error))
|
||||
}
|
||||
}()
|
||||
|
||||
switch t := value.(type) {
|
||||
case []byte:
|
||||
ed, err = ve.encodeBytes(t)
|
||||
log.PanicIf(err)
|
||||
case string:
|
||||
ed, err = ve.encodeAscii(t)
|
||||
log.PanicIf(err)
|
||||
case []uint16:
|
||||
ed, err = ve.encodeShorts(t)
|
||||
log.PanicIf(err)
|
||||
case []uint32:
|
||||
ed, err = ve.encodeLongs(t)
|
||||
log.PanicIf(err)
|
||||
case []float32:
|
||||
ed, err = ve.encodeFloats(t)
|
||||
log.PanicIf(err)
|
||||
case []float64:
|
||||
ed, err = ve.encodeDoubles(t)
|
||||
log.PanicIf(err)
|
||||
case []Rational:
|
||||
ed, err = ve.encodeRationals(t)
|
||||
log.PanicIf(err)
|
||||
case []int32:
|
||||
ed, err = ve.encodeSignedLongs(t)
|
||||
log.PanicIf(err)
|
||||
case []SignedRational:
|
||||
ed, err = ve.encodeSignedRationals(t)
|
||||
log.PanicIf(err)
|
||||
case time.Time:
|
||||
// For convenience, if the user doesn't want to deal with translation
|
||||
// semantics with timestamps.
|
||||
|
||||
s := ExifFullTimestampString(t)
|
||||
|
||||
ed, err = ve.encodeAscii(s)
|
||||
log.PanicIf(err)
|
||||
default:
|
||||
log.Panicf("value not encodable: [%s] [%v]", reflect.TypeOf(value), value)
|
||||
}
|
||||
|
||||
return ed, nil
|
||||
}
|
||||
@@ -0,0 +1,725 @@
|
||||
package exifcommon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dsoprea/go-logging"
|
||||
)
|
||||
|
||||
func TestValueEncoder_encodeBytes__Cycle(t *testing.T) {
|
||||
byteOrder := TestDefaultByteOrder
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []byte("original text")
|
||||
|
||||
ed, err := ve.encodeBytes(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeByte {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte(original)
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 13 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
|
||||
recovered, err := parser.ParseBytes(ed.Encoded, ed.UnitCount)
|
||||
log.PanicIf(err)
|
||||
|
||||
if reflect.DeepEqual(recovered, original) != true {
|
||||
t.Fatalf("Value not recovered correctly.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueEncoder_encodeAscii__Cycle(t *testing.T) {
|
||||
byteOrder := TestDefaultByteOrder
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := "original text"
|
||||
|
||||
ed, err := ve.encodeAscii(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeAscii {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte(original)
|
||||
expected = append(expected, 0)
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 14 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
|
||||
// Check that the string was recovered correctly and with the trailing NUL
|
||||
// character autostripped.
|
||||
|
||||
recovered, err := parser.ParseAscii(ed.Encoded, ed.UnitCount)
|
||||
log.PanicIf(err)
|
||||
|
||||
if reflect.DeepEqual(recovered, original) != true {
|
||||
t.Fatalf("Value not recovered correctly.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueEncoder_encodeAsciiNoNul__Cycle(t *testing.T) {
|
||||
byteOrder := TestDefaultByteOrder
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := "original text"
|
||||
|
||||
ed, err := ve.encodeAsciiNoNul(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeAsciiNoNul {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte(original)
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 13 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
|
||||
// Check that the string was recovered correctly and with the trailing NUL
|
||||
// character ignored (because not expected in the context of that type).
|
||||
|
||||
recovered, err := parser.ParseAsciiNoNul(ed.Encoded, ed.UnitCount)
|
||||
log.PanicIf(err)
|
||||
|
||||
if reflect.DeepEqual(recovered, string(expected)) != true {
|
||||
t.Fatalf("Value not recovered correctly.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueEncoder_encodeShorts__Cycle(t *testing.T) {
|
||||
byteOrder := TestDefaultByteOrder
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []uint16{0x11, 0x22, 0x33, 0x44, 0x55}
|
||||
|
||||
ed, err := ve.encodeShorts(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeShort {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte{
|
||||
0x00, 0x11,
|
||||
0x00, 0x22,
|
||||
0x00, 0x33,
|
||||
0x00, 0x44,
|
||||
0x00, 0x55,
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 5 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
|
||||
recovered, err := parser.ParseShorts(ed.Encoded, ed.UnitCount, byteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
if reflect.DeepEqual(recovered, original) != true {
|
||||
t.Fatalf("Value not recovered correctly.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueEncoder_encodeLongs__Cycle(t *testing.T) {
|
||||
byteOrder := TestDefaultByteOrder
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []uint32{0x11, 0x22, 0x33, 0x44, 0x55}
|
||||
|
||||
ed, err := ve.encodeLongs(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeLong {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte{
|
||||
0x00, 0x00, 0x00, 0x11,
|
||||
0x00, 0x00, 0x00, 0x22,
|
||||
0x00, 0x00, 0x00, 0x33,
|
||||
0x00, 0x00, 0x00, 0x44,
|
||||
0x00, 0x00, 0x00, 0x55,
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 5 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
|
||||
recovered, err := parser.ParseLongs(ed.Encoded, ed.UnitCount, byteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
if reflect.DeepEqual(recovered, original) != true {
|
||||
t.Fatalf("Value not recovered correctly.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueEncoder_encodeFloats__Cycle(t *testing.T) {
|
||||
byteOrder := TestDefaultByteOrder
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []float32{3.14159265, 2.71828182, 51.0, 68.0, 85.0}
|
||||
|
||||
ed, err := ve.encodeFloats(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeFloat {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte{
|
||||
0x40, 0x49, 0x0f, 0xdb,
|
||||
0x40, 0x2d, 0xf8, 0x54,
|
||||
0x42, 0x4c, 0x00, 0x00,
|
||||
0x42, 0x88, 0x00, 0x00,
|
||||
0x42, 0xaa, 0x00, 0x00,
|
||||
}
|
||||
|
||||
if bytes.Equal(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 5 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
|
||||
recovered, err := parser.ParseFloats(ed.Encoded, ed.UnitCount, byteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
for i, v := range recovered {
|
||||
if v < original[i] || v >= math.Nextafter32(original[i], original[i]+1) {
|
||||
t.Fatalf("ReadFloats expecting %v, received %v", original[i], v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueEncoder_encodeDoubles__Cycle(t *testing.T) {
|
||||
byteOrder := TestDefaultByteOrder
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []float64{3.14159265, 2.71828182, 954877.1230695, 68.0, 85.0}
|
||||
|
||||
ed, err := ve.encodeDoubles(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeDouble {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte{
|
||||
0x40, 0x09, 0x21, 0xfb, 0x53, 0xc8, 0xd4, 0xf1,
|
||||
0x40, 0x05, 0xbf, 0x0a, 0x89, 0xf1, 0xb0, 0xdd,
|
||||
0x41, 0x2d, 0x23, 0xfa, 0x3f, 0x02, 0xf7, 0x2b,
|
||||
0x40, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x40, 0x55, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 5 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
|
||||
recovered, err := parser.ParseDoubles(ed.Encoded, ed.UnitCount, byteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
for i, v := range recovered {
|
||||
if v < original[i] || v >= math.Nextafter(original[i], original[i]+1) {
|
||||
t.Fatalf("ReadDoubles expecting %v, received %v", original[i], v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueEncoder_encodeRationals__Cycle(t *testing.T) {
|
||||
byteOrder := TestDefaultByteOrder
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []Rational{
|
||||
{
|
||||
Numerator: 0x11,
|
||||
Denominator: 0x22,
|
||||
},
|
||||
{
|
||||
Numerator: 0x33,
|
||||
Denominator: 0x44,
|
||||
},
|
||||
{
|
||||
Numerator: 0x55,
|
||||
Denominator: 0x66,
|
||||
},
|
||||
{
|
||||
Numerator: 0x77,
|
||||
Denominator: 0x88,
|
||||
},
|
||||
{
|
||||
Numerator: 0x99,
|
||||
Denominator: 0x00,
|
||||
},
|
||||
}
|
||||
|
||||
ed, err := ve.encodeRationals(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeRational {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte{
|
||||
0x00, 0x00, 0x00, 0x11,
|
||||
0x00, 0x00, 0x00, 0x22,
|
||||
0x00, 0x00, 0x00, 0x33,
|
||||
0x00, 0x00, 0x00, 0x44,
|
||||
0x00, 0x00, 0x00, 0x55,
|
||||
0x00, 0x00, 0x00, 0x66,
|
||||
0x00, 0x00, 0x00, 0x77,
|
||||
0x00, 0x00, 0x00, 0x88,
|
||||
0x00, 0x00, 0x00, 0x99,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 5 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
|
||||
recovered, err := parser.ParseRationals(ed.Encoded, ed.UnitCount, byteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
if reflect.DeepEqual(recovered, original) != true {
|
||||
t.Fatalf("Value not recovered correctly.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueEncoder_encodeSignedLongs__Cycle(t *testing.T) {
|
||||
byteOrder := TestDefaultByteOrder
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []int32{0x11, 0x22, 0x33, 0x44, 0x55}
|
||||
|
||||
ed, err := ve.encodeSignedLongs(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeSignedLong {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte{
|
||||
0x00, 0x00, 0x00, 0x11,
|
||||
0x00, 0x00, 0x00, 0x22,
|
||||
0x00, 0x00, 0x00, 0x33,
|
||||
0x00, 0x00, 0x00, 0x44,
|
||||
0x00, 0x00, 0x00, 0x55,
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 5 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
|
||||
recovered, err := parser.ParseSignedLongs(ed.Encoded, ed.UnitCount, byteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
if reflect.DeepEqual(recovered, original) != true {
|
||||
t.Fatalf("Value not recovered correctly.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueEncoder_encodeSignedRationals__Cycle(t *testing.T) {
|
||||
byteOrder := TestDefaultByteOrder
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []SignedRational{
|
||||
{
|
||||
Numerator: 0x11,
|
||||
Denominator: 0x22,
|
||||
},
|
||||
{
|
||||
Numerator: 0x33,
|
||||
Denominator: 0x44,
|
||||
},
|
||||
{
|
||||
Numerator: 0x55,
|
||||
Denominator: 0x66,
|
||||
},
|
||||
{
|
||||
Numerator: 0x77,
|
||||
Denominator: 0x88,
|
||||
},
|
||||
{
|
||||
Numerator: 0x99,
|
||||
Denominator: 0x00,
|
||||
},
|
||||
}
|
||||
|
||||
ed, err := ve.encodeSignedRationals(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeSignedRational {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte{
|
||||
0x00, 0x00, 0x00, 0x11,
|
||||
0x00, 0x00, 0x00, 0x22,
|
||||
0x00, 0x00, 0x00, 0x33,
|
||||
0x00, 0x00, 0x00, 0x44,
|
||||
0x00, 0x00, 0x00, 0x55,
|
||||
0x00, 0x00, 0x00, 0x66,
|
||||
0x00, 0x00, 0x00, 0x77,
|
||||
0x00, 0x00, 0x00, 0x88,
|
||||
0x00, 0x00, 0x00, 0x99,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 5 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
|
||||
recovered, err := parser.ParseSignedRationals(ed.Encoded, ed.UnitCount, byteOrder)
|
||||
log.PanicIf(err)
|
||||
|
||||
if reflect.DeepEqual(recovered, original) != true {
|
||||
t.Fatalf("Value not recovered correctly.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueEncoder_Encode__Byte(t *testing.T) {
|
||||
byteOrder := TestDefaultByteOrder
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []byte("original text")
|
||||
|
||||
ed, err := ve.Encode(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeByte {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte(original)
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 13 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueEncoder_Encode__Ascii(t *testing.T) {
|
||||
byteOrder := TestDefaultByteOrder
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := "original text"
|
||||
|
||||
ed, err := ve.Encode(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeAscii {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte(original)
|
||||
expected = append(expected, 0)
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 14 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueEncoder_Encode__Short(t *testing.T) {
|
||||
byteOrder := TestDefaultByteOrder
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []uint16{0x11, 0x22, 0x33, 0x44, 0x55}
|
||||
|
||||
ed, err := ve.Encode(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeShort {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte{
|
||||
0x00, 0x11,
|
||||
0x00, 0x22,
|
||||
0x00, 0x33,
|
||||
0x00, 0x44,
|
||||
0x00, 0x55,
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 5 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueEncoder_Encode__Long(t *testing.T) {
|
||||
byteOrder := TestDefaultByteOrder
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []uint32{0x11, 0x22, 0x33, 0x44, 0x55}
|
||||
|
||||
ed, err := ve.Encode(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeLong {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte{
|
||||
0x00, 0x00, 0x00, 0x11,
|
||||
0x00, 0x00, 0x00, 0x22,
|
||||
0x00, 0x00, 0x00, 0x33,
|
||||
0x00, 0x00, 0x00, 0x44,
|
||||
0x00, 0x00, 0x00, 0x55,
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 5 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueEncoder_Encode__Float(t *testing.T) {
|
||||
byteOrder := TestDefaultByteOrder
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []float32{3.14159265, 2.71828182, 51.0, 68.0, 85.0}
|
||||
|
||||
ed, err := ve.Encode(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeFloat {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte{
|
||||
0x40, 0x49, 0x0f, 0xdb,
|
||||
0x40, 0x2d, 0xf8, 0x54,
|
||||
0x42, 0x4c, 0x00, 0x00,
|
||||
0x42, 0x88, 0x00, 0x00,
|
||||
0x42, 0xaa, 0x00, 0x00,
|
||||
}
|
||||
|
||||
if bytes.Equal(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 5 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestValueEncoder_Encode__Double(t *testing.T) {
|
||||
byteOrder := TestDefaultByteOrder
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []float64{3.14159265, 2.71828182, 954877.1230695, 68.0, 85.0}
|
||||
|
||||
ed, err := ve.Encode(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeDouble {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte{
|
||||
0x40, 0x09, 0x21, 0xfb, 0x53, 0xc8, 0xd4, 0xf1,
|
||||
0x40, 0x05, 0xbf, 0x0a, 0x89, 0xf1, 0xb0, 0xdd,
|
||||
0x41, 0x2d, 0x23, 0xfa, 0x3f, 0x02, 0xf7, 0x2b,
|
||||
0x40, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x40, 0x55, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
if bytes.Equal(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 5 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestValueEncoder_Encode__Rational(t *testing.T) {
|
||||
byteOrder := TestDefaultByteOrder
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []Rational{
|
||||
{
|
||||
Numerator: 0x11,
|
||||
Denominator: 0x22,
|
||||
},
|
||||
{
|
||||
Numerator: 0x33,
|
||||
Denominator: 0x44,
|
||||
},
|
||||
{
|
||||
Numerator: 0x55,
|
||||
Denominator: 0x66,
|
||||
},
|
||||
{
|
||||
Numerator: 0x77,
|
||||
Denominator: 0x88,
|
||||
},
|
||||
{
|
||||
Numerator: 0x99,
|
||||
Denominator: 0x00,
|
||||
},
|
||||
}
|
||||
|
||||
ed, err := ve.Encode(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeRational {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte{
|
||||
0x00, 0x00, 0x00, 0x11,
|
||||
0x00, 0x00, 0x00, 0x22,
|
||||
0x00, 0x00, 0x00, 0x33,
|
||||
0x00, 0x00, 0x00, 0x44,
|
||||
0x00, 0x00, 0x00, 0x55,
|
||||
0x00, 0x00, 0x00, 0x66,
|
||||
0x00, 0x00, 0x00, 0x77,
|
||||
0x00, 0x00, 0x00, 0x88,
|
||||
0x00, 0x00, 0x00, 0x99,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 5 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueEncoder_Encode__SignedLong(t *testing.T) {
|
||||
byteOrder := TestDefaultByteOrder
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []int32{0x11, 0x22, 0x33, 0x44, 0x55}
|
||||
|
||||
ed, err := ve.Encode(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeSignedLong {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte{
|
||||
0x00, 0x00, 0x00, 0x11,
|
||||
0x00, 0x00, 0x00, 0x22,
|
||||
0x00, 0x00, 0x00, 0x33,
|
||||
0x00, 0x00, 0x00, 0x44,
|
||||
0x00, 0x00, 0x00, 0x55,
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 5 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueEncoder_Encode__SignedRational(t *testing.T) {
|
||||
byteOrder := TestDefaultByteOrder
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
original := []SignedRational{
|
||||
{
|
||||
Numerator: 0x11,
|
||||
Denominator: 0x22,
|
||||
},
|
||||
{
|
||||
Numerator: 0x33,
|
||||
Denominator: 0x44,
|
||||
},
|
||||
{
|
||||
Numerator: 0x55,
|
||||
Denominator: 0x66,
|
||||
},
|
||||
{
|
||||
Numerator: 0x77,
|
||||
Denominator: 0x88,
|
||||
},
|
||||
{
|
||||
Numerator: 0x99,
|
||||
Denominator: 0x00,
|
||||
},
|
||||
}
|
||||
|
||||
ed, err := ve.Encode(original)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeSignedRational {
|
||||
t.Fatalf("IFD type not expected.")
|
||||
}
|
||||
|
||||
expected := []byte{
|
||||
0x00, 0x00, 0x00, 0x11,
|
||||
0x00, 0x00, 0x00, 0x22,
|
||||
0x00, 0x00, 0x00, 0x33,
|
||||
0x00, 0x00, 0x00, 0x44,
|
||||
0x00, 0x00, 0x00, 0x55,
|
||||
0x00, 0x00, 0x00, 0x66,
|
||||
0x00, 0x00, 0x00, 0x77,
|
||||
0x00, 0x00, 0x00, 0x88,
|
||||
0x00, 0x00, 0x00, 0x99,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Data not encoded correctly.")
|
||||
} else if ed.UnitCount != 5 {
|
||||
t.Fatalf("Unit-count not correct.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueEncoder_Encode__Timestamp(t *testing.T) {
|
||||
byteOrder := TestDefaultByteOrder
|
||||
ve := NewValueEncoder(byteOrder)
|
||||
|
||||
now := time.Now()
|
||||
|
||||
ed, err := ve.Encode(now)
|
||||
log.PanicIf(err)
|
||||
|
||||
if ed.Type != TypeAscii {
|
||||
t.Fatalf("Timestamp not encoded as ASCII.")
|
||||
}
|
||||
|
||||
expectedTimestampBytes := ExifFullTimestampString(now)
|
||||
|
||||
// Leave an extra byte for the NUL.
|
||||
expected := make([]byte, len(expectedTimestampBytes)+1)
|
||||
copy(expected, expectedTimestampBytes)
|
||||
|
||||
if bytes.Equal(ed.Encoded, expected) != true {
|
||||
t.Fatalf("Timestamp not encoded correctly: [%s] != [%s]", string(ed.Encoded), string(expected))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user