diff options
Diffstat (limited to 'vendor/github.com/dsoprea/go-exif/v3/utility.go')
-rw-r--r-- | vendor/github.com/dsoprea/go-exif/v3/utility.go | 237 |
1 files changed, 237 insertions, 0 deletions
diff --git a/vendor/github.com/dsoprea/go-exif/v3/utility.go b/vendor/github.com/dsoprea/go-exif/v3/utility.go new file mode 100644 index 000000000..f0b5e6383 --- /dev/null +++ b/vendor/github.com/dsoprea/go-exif/v3/utility.go @@ -0,0 +1,237 @@ +package exif + +import ( + "fmt" + "io" + "math" + + "github.com/dsoprea/go-logging" + "github.com/dsoprea/go-utility/v2/filesystem" + + "github.com/dsoprea/go-exif/v3/common" + "github.com/dsoprea/go-exif/v3/undefined" +) + +var ( + utilityLogger = log.NewLogger("exif.utility") +) + +// ExifTag is one simple representation of a tag in a flat list of all of them. +type ExifTag struct { + // IfdPath is the fully-qualified IFD path (even though it is not named as + // such). + IfdPath string `json:"ifd_path"` + + // TagId is the tag-ID. + TagId uint16 `json:"id"` + + // TagName is the tag-name. This is never empty. + TagName string `json:"name"` + + // UnitCount is the recorded number of units constution of the value. + UnitCount uint32 `json:"unit_count"` + + // TagTypeId is the type-ID. + TagTypeId exifcommon.TagTypePrimitive `json:"type_id"` + + // TagTypeName is the type name. + TagTypeName string `json:"type_name"` + + // Value is the decoded value. + Value interface{} `json:"value"` + + // ValueBytes is the raw, encoded value. + ValueBytes []byte `json:"value_bytes"` + + // Formatted is the human representation of the first value (tag values are + // always an array). + FormattedFirst string `json:"formatted_first"` + + // Formatted is the human representation of the complete value. + Formatted string `json:"formatted"` + + // ChildIfdPath is the name of the child IFD this tag represents (if it + // represents any). Otherwise, this is empty. + ChildIfdPath string `json:"child_ifd_path"` +} + +// String returns a string representation. +func (et ExifTag) String() string { + return fmt.Sprintf( + "ExifTag<"+ + "IFD-PATH=[%s] "+ + "TAG-ID=(0x%02x) "+ + "TAG-NAME=[%s] "+ + "TAG-TYPE=[%s] "+ + "VALUE=[%v] "+ + "VALUE-BYTES=(%d) "+ + "CHILD-IFD-PATH=[%s]", + et.IfdPath, et.TagId, et.TagName, et.TagTypeName, et.FormattedFirst, + len(et.ValueBytes), et.ChildIfdPath) +} + +// GetFlatExifData returns a simple, flat representation of all tags. +func GetFlatExifData(exifData []byte, so *ScanOptions) (exifTags []ExifTag, med *MiscellaneousExifData, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + sb := rifs.NewSeekableBufferWithBytes(exifData) + + exifTags, med, err = getFlatExifDataUniversalSearchWithReadSeeker(sb, so, false) + log.PanicIf(err) + + return exifTags, med, nil +} + +// RELEASE(dustin): GetFlatExifDataUniversalSearch is a kludge to allow univeral tag searching in a backwards-compatible manner. For the next release, undo this and simply add the flag to GetFlatExifData. + +// GetFlatExifDataUniversalSearch returns a simple, flat representation of all +// tags. +func GetFlatExifDataUniversalSearch(exifData []byte, so *ScanOptions, doUniversalSearch bool) (exifTags []ExifTag, med *MiscellaneousExifData, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + sb := rifs.NewSeekableBufferWithBytes(exifData) + + exifTags, med, err = getFlatExifDataUniversalSearchWithReadSeeker(sb, so, doUniversalSearch) + log.PanicIf(err) + + return exifTags, med, nil +} + +// RELEASE(dustin): GetFlatExifDataUniversalSearchWithReadSeeker is a kludge to allow using a ReadSeeker in a backwards-compatible manner. For the next release, drop this and refactor GetFlatExifDataUniversalSearch to take a ReadSeeker. + +// GetFlatExifDataUniversalSearchWithReadSeeker returns a simple, flat +// representation of all tags given a ReadSeeker. +func GetFlatExifDataUniversalSearchWithReadSeeker(rs io.ReadSeeker, so *ScanOptions, doUniversalSearch bool) (exifTags []ExifTag, med *MiscellaneousExifData, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + exifTags, med, err = getFlatExifDataUniversalSearchWithReadSeeker(rs, so, doUniversalSearch) + log.PanicIf(err) + + return exifTags, med, nil +} + +// getFlatExifDataUniversalSearchWithReadSeeker returns a simple, flat +// representation of all tags given a ReadSeeker. +func getFlatExifDataUniversalSearchWithReadSeeker(rs io.ReadSeeker, so *ScanOptions, doUniversalSearch bool) (exifTags []ExifTag, med *MiscellaneousExifData, err error) { + defer func() { + if state := recover(); state != nil { + err = log.Wrap(state.(error)) + } + }() + + headerData := make([]byte, ExifSignatureLength) + if _, err = io.ReadFull(rs, headerData); err != nil { + if err == io.EOF { + return nil, nil, err + } + + log.Panic(err) + } + + eh, err := ParseExifHeader(headerData) + log.PanicIf(err) + + im, err := exifcommon.NewIfdMappingWithStandard() + log.PanicIf(err) + + ti := NewTagIndex() + + if doUniversalSearch == true { + ti.SetUniversalSearch(true) + } + + ebs := NewExifReadSeeker(rs) + ie := NewIfdEnumerate(im, ti, ebs, eh.ByteOrder) + + exifTags = make([]ExifTag, 0) + + visitor := func(ite *IfdTagEntry) (err error) { + // This encodes down to base64. Since this an example tool and we do not + // expect to ever decode the output, we are not worried about + // specifically base64-encoding it in order to have a measure of + // control. + valueBytes, err := ite.GetRawBytes() + if err != nil { + if err == exifundefined.ErrUnparseableValue { + return nil + } + + log.Panic(err) + } + + value, err := ite.Value() + if err != nil { + if err == exifcommon.ErrUnhandledUndefinedTypedTag { + value = exifundefined.UnparseableUnknownTagValuePlaceholder + } else if log.Is(err, exifcommon.ErrParseFail) == true { + utilityLogger.Warningf(nil, + "Could not parse value for tag [%s] (%04x) [%s].", + ite.IfdPath(), ite.TagId(), ite.TagName()) + + return nil + } else { + log.Panic(err) + } + } + + et := ExifTag{ + IfdPath: ite.IfdPath(), + TagId: ite.TagId(), + TagName: ite.TagName(), + UnitCount: ite.UnitCount(), + TagTypeId: ite.TagType(), + TagTypeName: ite.TagType().String(), + Value: value, + ValueBytes: valueBytes, + ChildIfdPath: ite.ChildIfdPath(), + } + + et.Formatted, err = ite.Format() + log.PanicIf(err) + + et.FormattedFirst, err = ite.FormatFirst() + log.PanicIf(err) + + exifTags = append(exifTags, et) + + return nil + } + + med, err = ie.Scan(exifcommon.IfdStandardIfdIdentity, eh.FirstIfdOffset, visitor, nil) + log.PanicIf(err) + + return exifTags, med, nil +} + +// GpsDegreesEquals returns true if the two `GpsDegrees` are identical. +func GpsDegreesEquals(gi1, gi2 GpsDegrees) bool { + if gi2.Orientation != gi1.Orientation { + return false + } + + degreesRightBound := math.Nextafter(gi1.Degrees, gi1.Degrees+1) + minutesRightBound := math.Nextafter(gi1.Minutes, gi1.Minutes+1) + secondsRightBound := math.Nextafter(gi1.Seconds, gi1.Seconds+1) + + if gi2.Degrees < gi1.Degrees || gi2.Degrees >= degreesRightBound { + return false + } else if gi2.Minutes < gi1.Minutes || gi2.Minutes >= minutesRightBound { + return false + } else if gi2.Seconds < gi1.Seconds || gi2.Seconds >= secondsRightBound { + return false + } + + return true +} |