diff --git a/exif_test.go b/exif_test.go index e1696c5..2b68a95 100644 --- a/exif_test.go +++ b/exif_test.go @@ -92,10 +92,16 @@ func TestParse(t *testing.T) { } } -// TODO(dustin): Finish case-specific parsing of known undefined values. valueString := "" if tagType.Type() == TypeUndefined { - valueString = "!UNDEFINED!" + value, err := UndefinedValue(indexedIfdName, tagId, valueContext, tagType.ByteOrder()) + if log.Is(err, ErrUnhandledUnknownTypedTag) { + valueString = "!UNDEFINED!" + } else if err != nil { + log.Panic(err) + } else { + valueString = value.(string) + } } else { valueString, err = tagType.ValueString(valueContext, true) log.PanicIf(err) @@ -110,6 +116,10 @@ func TestParse(t *testing.T) { err = e.Parse(data[foundAt:], visitor) log.PanicIf(err) + // for _, line := range tags { + // fmt.Printf("TAGS: %s\n", line) + // } + expected := []string { "IFD=[IFD] ID=(0x010f) NAME=[Make] COUNT=(6) TYPE=[ASCII] VALUE=[Canon]", "IFD=[IFD] ID=(0x0110) NAME=[Model] COUNT=(22) TYPE=[ASCII] VALUE=[Canon EOS 5D Mark III]", @@ -128,7 +138,7 @@ func TestParse(t *testing.T) { "IFD=[Exif] ID=(0x8827) NAME=[ISOSpeedRatings] COUNT=(1) TYPE=[SHORT] VALUE=[1600]", "IFD=[Exif] ID=(0x8830) NAME=[SensitivityType] COUNT=(1) TYPE=[SHORT] VALUE=[2]", "IFD=[Exif] ID=(0x8832) NAME=[RecommendedExposureIndex] COUNT=(1) TYPE=[LONG] VALUE=[1600]", - "IFD=[Exif] ID=(0x9000) NAME=[ExifVersion] COUNT=(4) TYPE=[UNDEFINED] VALUE=[!UNDEFINED!]", + "IFD=[Exif] ID=(0x9000) NAME=[ExifVersion] COUNT=(4) TYPE=[UNDEFINED] VALUE=[0230]", "IFD=[Exif] ID=(0x9003) NAME=[DateTimeOriginal] COUNT=(20) TYPE=[ASCII] VALUE=[2017:12:02 08:18:50]", "IFD=[Exif] ID=(0x9004) NAME=[DateTimeDigitized] COUNT=(20) TYPE=[ASCII] VALUE=[2017:12:02 08:18:50]", "IFD=[Exif] ID=(0x9101) NAME=[ComponentsConfiguration] COUNT=(4) TYPE=[UNDEFINED] VALUE=[!UNDEFINED!]", @@ -143,7 +153,7 @@ func TestParse(t *testing.T) { "IFD=[Exif] ID=(0x9290) NAME=[SubSecTime] COUNT=(3) TYPE=[ASCII] VALUE=[00]", "IFD=[Exif] ID=(0x9291) NAME=[SubSecTimeOriginal] COUNT=(3) TYPE=[ASCII] VALUE=[00]", "IFD=[Exif] ID=(0x9292) NAME=[SubSecTimeDigitized] COUNT=(3) TYPE=[ASCII] VALUE=[00]", - "IFD=[Exif] ID=(0xa000) NAME=[FlashpixVersion] COUNT=(4) TYPE=[UNDEFINED] VALUE=[!UNDEFINED!]", + "IFD=[Exif] ID=(0xa000) NAME=[FlashpixVersion] COUNT=(4) TYPE=[UNDEFINED] VALUE=[0100]", "IFD=[Exif] ID=(0xa001) NAME=[ColorSpace] COUNT=(1) TYPE=[SHORT] VALUE=[1]", "IFD=[Exif] ID=(0xa002) NAME=[PixelXDimension] COUNT=(1) TYPE=[SHORT] VALUE=[3840]", "IFD=[Exif] ID=(0xa003) NAME=[PixelYDimension] COUNT=(1) TYPE=[SHORT] VALUE=[2560]", diff --git a/type.go b/type.go index 38999a6..1aa9d6f 100644 --- a/type.go +++ b/type.go @@ -19,6 +19,9 @@ const ( TypeUndefined = uint16(7) TypeSignedLong = uint16(9) TypeSignedRational = uint16(10) + + // Custom, for our purposes. + TypeAsciiNoNul = uint16(0xf0) ) var ( @@ -31,6 +34,8 @@ var ( TypeUndefined: "UNDEFINED", TypeSignedLong: "SLONG", TypeSignedRational: "SRATIONAL", + + TypeAsciiNoNul: "_ASCII_NO_NUL", } ) @@ -47,8 +52,14 @@ var ( // 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 is used when we try to parse anything other than the + // current type. ErrWrongType = errors.New("wrong type, can not parse") + + // ErrUnhandledUnknownTag 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). + ErrUnhandledUnknownTypedTag = errors.New("not a standard unknown-typed tag") ) @@ -110,11 +121,15 @@ func (tt TagType) Type() uint16 { return tt.tagType } +func (tt TagType) ByteOrder() IfdByteOrder { + return tt.byteOrder +} + func (tt TagType) Size() int { if tt.tagType == TypeByte { return 1 - } else if tt.tagType == TypeAscii { + } else if tt.tagType == TypeAscii || tt.tagType == TypeAsciiNoNul { return 1 } else if tt.tagType == TypeShort { return 2 @@ -172,7 +187,7 @@ func (tt TagType) ParseAscii(data []byte, rawCount uint32) (value string, err er } }() - if tt.tagType != TypeAscii { + if tt.tagType != TypeAscii && tt.tagType != TypeAsciiNoNul { log.Panic(ErrWrongType) } @@ -375,29 +390,38 @@ func (tt TagType) ReadAsciiValue(valueContext ValueContext) (value string, err e } }() + value, err = tt.ReadAsciiNoNulValue(valueContext) + log.PanicIf(err) + + len_ := len(value) + if len_ == 0 || value[len_ - 1] != 0 { + typeLogger.Warningf(nil, "ascii value not terminated with nul: [%s]", value) + return value, nil + } else { + return value[:len_ - 1], nil + } +} + +func (tt TagType) ReadAsciiNoNulValue(valueContext ValueContext) (value string, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + if tt.ValueIsEmbedded(valueContext.UnitCount) == true { - typeLogger.Debugf(nil, "Reading ASCII value (embedded).") + typeLogger.Debugf(nil, "Reading ASCII value (no-nul; embedded).") value, err = tt.ParseAscii(valueContext.RawValueOffset, valueContext.UnitCount) log.PanicIf(err) } else { - typeLogger.Debugf(nil, "Reading ASCII value (at offset).") + typeLogger.Debugf(nil, "Reading ASCII value (no-nul; at offset).") value, err = tt.ParseAscii(valueContext.RawExif[valueContext.ValueOffset:], valueContext.UnitCount) log.PanicIf(err) } - len_ := len(value) - if value[len_ - 1] != 0 { - typeLogger.Warningf(nil, "ascii value not terminated with nul: [%s]", value) - -// TODO(dustin): !! Debugging - fmt.Printf("ascii value not terminated with nul: [%s]", value) - - return value, nil - } else { - return value[:len_ - 1], nil - } + return value, nil } func (tt TagType) ReadShortValues(valueContext ValueContext) (value []uint16, err error) { @@ -536,6 +560,11 @@ func (tt TagType) ValueString(valueContext ValueContext, justFirst bool) (value raw, err := tt.ReadAsciiValue(valueContext) log.PanicIf(err) + return fmt.Sprintf("%s", raw), nil + } else if tt.Type() == TypeAsciiNoNul { + raw, err := tt.ReadAsciiNoNulValue(valueContext) + log.PanicIf(err) + return fmt.Sprintf("%s", raw), nil } else if tt.Type() == TypeShort { raw, err := tt.ReadShortValues(valueContext) @@ -609,3 +638,63 @@ func (tt TagType) ValueString(valueContext ValueContext, justFirst bool) (value return "", nil } } + +// UndefinedValue returns the value for a tag of "undefined" type. +func UndefinedValue(indexedIfdName string, tagId uint16, valueContext ValueContext, byteOrder IfdByteOrder) (value interface{}, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + if indexedIfdName == IfdName(IfdExif, 0) { + if tagId == 0x9000 { + tt := NewTagType(TypeAsciiNoNul, byteOrder) + + value, err = tt.ReadAsciiValue(valueContext) + log.PanicIf(err) + + return value, nil + } else if tagId == 0xa000 { + tt := NewTagType(TypeAsciiNoNul, byteOrder) + + value, err = tt.ReadAsciiValue(valueContext) + log.PanicIf(err) + + return value, nil + } + } else if indexedIfdName == IfdName(IfdGps, 0) { + if tagId == 0x001c { + // GPSAreaInformation + + tt := NewTagType(TypeAsciiNoNul, byteOrder) + + value, err = tt.ReadAsciiValue(valueContext) + log.PanicIf(err) + + return value, nil + } else if tagId == 0x001b { + // GPSProcessingMethod + + tt := NewTagType(TypeAsciiNoNul, byteOrder) + + value, err = tt.ReadAsciiValue(valueContext) + log.PanicIf(err) + + return value, nil + } + } + +// TODO(dustin): !! Still need to do: +// +// complex: 0xa302, 0xa20c, 0x8828 +// long: 0xa301, 0xa300 +// bytes: 0x927c, 0x9101 (probably, but not certain) +// other: 0x9286 (simple, but needs some processing) + + // 0xa40b is device-specific and unhandled. + + + log.Panic(ErrUnhandledUnknownTypedTag) + return nil, nil +}