diff --git a/ifd_enumerate.go b/ifd_enumerate.go index 8fbd28f..09b42d6 100644 --- a/ifd_enumerate.go +++ b/ifd_enumerate.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" "errors" + "reflect" "encoding/binary" @@ -153,6 +154,12 @@ func (ie *IfdEnumerate) parseTag(ii IfdIdentity, tagIndex int, ite *IfdTagEnumer RawValueOffset: rawValueOffset, } + value, isUnhandledUnknown, err := ie.resolveTagValue(tag) + log.PanicIf(err) + + tag.value = value + tag.isUnhandledUnknown = isUnhandledUnknown + // If it's an IFD but not a standard one, it'll just be seen as a LONG // (the standard IFD tag type), later, unless we skip it because it's // [likely] not even in the standard list of known tags. @@ -164,6 +171,72 @@ func (ie *IfdEnumerate) parseTag(ii IfdIdentity, tagIndex int, ite *IfdTagEnumer return tag, nil } +func (ie *IfdEnumerate) resolveTagValue(ite *IfdTagEntry) (valueBytes []byte, isUnhandledUnknown bool, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + addressableData := ie.exifData[ExifAddressableAreaStart:] + + // Return the exact bytes of the unknown-type value. Returning a string + // (`ValueString`) is easy because we can just pass everything to + // `Sprintf()`. Returning the raw, typed value (`Value`) is easy + // (obviously). However, here, in order to produce the list of bytes, we + // need to coerce whatever `UndefinedValue()` returns. + if ite.TagType == TypeUndefined { + valueContext := ValueContext{ + UnitCount: ite.UnitCount, + ValueOffset: ite.ValueOffset, + RawValueOffset: ite.RawValueOffset, + AddressableData: addressableData, + } + + value, err := UndefinedValue(ite.Ii, ite.TagId, valueContext, ie.byteOrder) + log.PanicIf(err) + + switch value.(type) { + case []byte: + return value.([]byte), false, nil + case string: + return []byte(value.(string)), false, nil + case UnknownTagValue: + valueBytes, err := value.(UnknownTagValue).ValueBytes() + +// TODO(dustin): Is this always bytes? What about the tag-specific structures that are built? Handle unhandled unknown. Set isUnhandledUnknown. + + log.PanicIf(err) + + return valueBytes, false, nil + default: +// TODO(dustin): !! Finish translating the rest of the types (make reusable and replace into other similar implementations?) + log.Panicf("can not produce bytes for unknown-type tag (0x%04x): [%s]", ite.TagId, reflect.TypeOf(value)) + } + } + + originalType := NewTagType(ite.TagType, ie.byteOrder) + byteCount := uint32(originalType.Size()) * ite.UnitCount + + tt := NewTagType(TypeByte, ie.byteOrder) + + if tt.ValueIsEmbedded(byteCount) == true { + iteLogger.Debugf(nil, "Reading BYTE value (ITE; embedded).") + + // In this case, the bytes normally used for the offset are actually + // data. + valueBytes, err = tt.ParseBytes(ite.RawValueOffset, byteCount) + log.PanicIf(err) + } else { + iteLogger.Debugf(nil, "Reading BYTE value (ITE; at offset).") + + valueBytes, err = tt.ParseBytes(addressableData[ite.ValueOffset:], byteCount) + log.PanicIf(err) + } + + return valueBytes, false, nil +} + // TagVisitor is an optional callback that can get hit for every tag we parse // through. `addressableData` is the byte array startign after the EXIF header // (where the offsets of all IFDs and values are calculated from). diff --git a/ifd_tag_entry.go b/ifd_tag_entry.go index 75073d3..28bceca 100644 --- a/ifd_tag_entry.go +++ b/ifd_tag_entry.go @@ -28,6 +28,10 @@ type IfdTagEntry struct { // IfdName is the IFD that this tag belongs to. Ii IfdIdentity + +// TODO(dustin): !! We now parse and read the value immediately. Update the rest of the logic to use this and get rid of all ofthe staggered and different resolution mechanisms. + value []byte + isUnhandledUnknown bool } func (ite IfdTagEntry) String() string { diff --git a/tags.go b/tags.go index edf275b..e608552 100644 --- a/tags.go +++ b/tags.go @@ -24,6 +24,11 @@ const ( ThumbnailSizeTagId = 0x0202 ) +type IfdNameAndIndex struct { + Ii IfdIdentity + Index int +} + var ( tagDataFilepath = "" @@ -89,12 +94,30 @@ var ( } tagIndex *TagIndex + + IfdDesignations = map[string]IfdNameAndIndex { + "ifd0": { RootIi, 0 }, + "ifd1": { RootIi, 1 }, + "exif": { ExifIi, 0 }, + "gps": { GpsIi, 0 }, + "iop": { ExifIopIi, 0 }, + } + + IfdDesignationsR = make(map[IfdNameAndIndex]string) ) var ( tagsLogger = log.NewLogger("exif.tags") ) +func IfdDesignation(ii IfdIdentity, index int) string { + if ii == RootIi { + return fmt.Sprintf("%s%d", ii.IfdName, index) + } else { + return ii.IfdName + } +} + type IfdIdentity struct { ParentIfdName string @@ -397,5 +420,9 @@ func init() { IfdTagNames[ifdName] = tagsR } + for designation, ni := range IfdDesignations { + IfdDesignationsR[ni] = designation + } + tagIndex = NewTagIndex() }