gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

utility.go (6843B)


      1 package exif
      2 
      3 import (
      4 	"fmt"
      5 	"io"
      6 	"math"
      7 
      8 	"github.com/dsoprea/go-logging"
      9 	"github.com/dsoprea/go-utility/v2/filesystem"
     10 
     11 	"github.com/dsoprea/go-exif/v3/common"
     12 	"github.com/dsoprea/go-exif/v3/undefined"
     13 )
     14 
     15 var (
     16 	utilityLogger = log.NewLogger("exif.utility")
     17 )
     18 
     19 // ExifTag is one simple representation of a tag in a flat list of all of them.
     20 type ExifTag struct {
     21 	// IfdPath is the fully-qualified IFD path (even though it is not named as
     22 	// such).
     23 	IfdPath string `json:"ifd_path"`
     24 
     25 	// TagId is the tag-ID.
     26 	TagId uint16 `json:"id"`
     27 
     28 	// TagName is the tag-name. This is never empty.
     29 	TagName string `json:"name"`
     30 
     31 	// UnitCount is the recorded number of units constution of the value.
     32 	UnitCount uint32 `json:"unit_count"`
     33 
     34 	// TagTypeId is the type-ID.
     35 	TagTypeId exifcommon.TagTypePrimitive `json:"type_id"`
     36 
     37 	// TagTypeName is the type name.
     38 	TagTypeName string `json:"type_name"`
     39 
     40 	// Value is the decoded value.
     41 	Value interface{} `json:"value"`
     42 
     43 	// ValueBytes is the raw, encoded value.
     44 	ValueBytes []byte `json:"value_bytes"`
     45 
     46 	// Formatted is the human representation of the first value (tag values are
     47 	// always an array).
     48 	FormattedFirst string `json:"formatted_first"`
     49 
     50 	// Formatted is the human representation of the complete value.
     51 	Formatted string `json:"formatted"`
     52 
     53 	// ChildIfdPath is the name of the child IFD this tag represents (if it
     54 	// represents any). Otherwise, this is empty.
     55 	ChildIfdPath string `json:"child_ifd_path"`
     56 }
     57 
     58 // String returns a string representation.
     59 func (et ExifTag) String() string {
     60 	return fmt.Sprintf(
     61 		"ExifTag<"+
     62 			"IFD-PATH=[%s] "+
     63 			"TAG-ID=(0x%02x) "+
     64 			"TAG-NAME=[%s] "+
     65 			"TAG-TYPE=[%s] "+
     66 			"VALUE=[%v] "+
     67 			"VALUE-BYTES=(%d) "+
     68 			"CHILD-IFD-PATH=[%s]",
     69 		et.IfdPath, et.TagId, et.TagName, et.TagTypeName, et.FormattedFirst,
     70 		len(et.ValueBytes), et.ChildIfdPath)
     71 }
     72 
     73 // GetFlatExifData returns a simple, flat representation of all tags.
     74 func GetFlatExifData(exifData []byte, so *ScanOptions) (exifTags []ExifTag, med *MiscellaneousExifData, err error) {
     75 	defer func() {
     76 		if state := recover(); state != nil {
     77 			err = log.Wrap(state.(error))
     78 		}
     79 	}()
     80 
     81 	sb := rifs.NewSeekableBufferWithBytes(exifData)
     82 
     83 	exifTags, med, err = getFlatExifDataUniversalSearchWithReadSeeker(sb, so, false)
     84 	log.PanicIf(err)
     85 
     86 	return exifTags, med, nil
     87 }
     88 
     89 // 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.
     90 
     91 // GetFlatExifDataUniversalSearch returns a simple, flat representation of all
     92 // tags.
     93 func GetFlatExifDataUniversalSearch(exifData []byte, so *ScanOptions, doUniversalSearch bool) (exifTags []ExifTag, med *MiscellaneousExifData, err error) {
     94 	defer func() {
     95 		if state := recover(); state != nil {
     96 			err = log.Wrap(state.(error))
     97 		}
     98 	}()
     99 
    100 	sb := rifs.NewSeekableBufferWithBytes(exifData)
    101 
    102 	exifTags, med, err = getFlatExifDataUniversalSearchWithReadSeeker(sb, so, doUniversalSearch)
    103 	log.PanicIf(err)
    104 
    105 	return exifTags, med, nil
    106 }
    107 
    108 // 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.
    109 
    110 // GetFlatExifDataUniversalSearchWithReadSeeker returns a simple, flat
    111 // representation of all tags given a ReadSeeker.
    112 func GetFlatExifDataUniversalSearchWithReadSeeker(rs io.ReadSeeker, so *ScanOptions, doUniversalSearch bool) (exifTags []ExifTag, med *MiscellaneousExifData, err error) {
    113 	defer func() {
    114 		if state := recover(); state != nil {
    115 			err = log.Wrap(state.(error))
    116 		}
    117 	}()
    118 
    119 	exifTags, med, err = getFlatExifDataUniversalSearchWithReadSeeker(rs, so, doUniversalSearch)
    120 	log.PanicIf(err)
    121 
    122 	return exifTags, med, nil
    123 }
    124 
    125 // getFlatExifDataUniversalSearchWithReadSeeker returns a simple, flat
    126 // representation of all tags given a ReadSeeker.
    127 func getFlatExifDataUniversalSearchWithReadSeeker(rs io.ReadSeeker, so *ScanOptions, doUniversalSearch bool) (exifTags []ExifTag, med *MiscellaneousExifData, err error) {
    128 	defer func() {
    129 		if state := recover(); state != nil {
    130 			err = log.Wrap(state.(error))
    131 		}
    132 	}()
    133 
    134 	headerData := make([]byte, ExifSignatureLength)
    135 	if _, err = io.ReadFull(rs, headerData); err != nil {
    136 		if err == io.EOF {
    137 			return nil, nil, err
    138 		}
    139 
    140 		log.Panic(err)
    141 	}
    142 
    143 	eh, err := ParseExifHeader(headerData)
    144 	log.PanicIf(err)
    145 
    146 	im, err := exifcommon.NewIfdMappingWithStandard()
    147 	log.PanicIf(err)
    148 
    149 	ti := NewTagIndex()
    150 
    151 	if doUniversalSearch == true {
    152 		ti.SetUniversalSearch(true)
    153 	}
    154 
    155 	ebs := NewExifReadSeeker(rs)
    156 	ie := NewIfdEnumerate(im, ti, ebs, eh.ByteOrder)
    157 
    158 	exifTags = make([]ExifTag, 0)
    159 
    160 	visitor := func(ite *IfdTagEntry) (err error) {
    161 		// This encodes down to base64. Since this an example tool and we do not
    162 		// expect to ever decode the output, we are not worried about
    163 		// specifically base64-encoding it in order to have a measure of
    164 		// control.
    165 		valueBytes, err := ite.GetRawBytes()
    166 		if err != nil {
    167 			if err == exifundefined.ErrUnparseableValue {
    168 				return nil
    169 			}
    170 
    171 			log.Panic(err)
    172 		}
    173 
    174 		value, err := ite.Value()
    175 		if err != nil {
    176 			if err == exifcommon.ErrUnhandledUndefinedTypedTag {
    177 				value = exifundefined.UnparseableUnknownTagValuePlaceholder
    178 			} else if log.Is(err, exifcommon.ErrParseFail) == true {
    179 				utilityLogger.Warningf(nil,
    180 					"Could not parse value for tag [%s] (%04x) [%s].",
    181 					ite.IfdPath(), ite.TagId(), ite.TagName())
    182 
    183 				return nil
    184 			} else {
    185 				log.Panic(err)
    186 			}
    187 		}
    188 
    189 		et := ExifTag{
    190 			IfdPath:      ite.IfdPath(),
    191 			TagId:        ite.TagId(),
    192 			TagName:      ite.TagName(),
    193 			UnitCount:    ite.UnitCount(),
    194 			TagTypeId:    ite.TagType(),
    195 			TagTypeName:  ite.TagType().String(),
    196 			Value:        value,
    197 			ValueBytes:   valueBytes,
    198 			ChildIfdPath: ite.ChildIfdPath(),
    199 		}
    200 
    201 		et.Formatted, err = ite.Format()
    202 		log.PanicIf(err)
    203 
    204 		et.FormattedFirst, err = ite.FormatFirst()
    205 		log.PanicIf(err)
    206 
    207 		exifTags = append(exifTags, et)
    208 
    209 		return nil
    210 	}
    211 
    212 	med, err = ie.Scan(exifcommon.IfdStandardIfdIdentity, eh.FirstIfdOffset, visitor, nil)
    213 	log.PanicIf(err)
    214 
    215 	return exifTags, med, nil
    216 }
    217 
    218 // GpsDegreesEquals returns true if the two `GpsDegrees` are identical.
    219 func GpsDegreesEquals(gi1, gi2 GpsDegrees) bool {
    220 	if gi2.Orientation != gi1.Orientation {
    221 		return false
    222 	}
    223 
    224 	degreesRightBound := math.Nextafter(gi1.Degrees, gi1.Degrees+1)
    225 	minutesRightBound := math.Nextafter(gi1.Minutes, gi1.Minutes+1)
    226 	secondsRightBound := math.Nextafter(gi1.Seconds, gi1.Seconds+1)
    227 
    228 	if gi2.Degrees < gi1.Degrees || gi2.Degrees >= degreesRightBound {
    229 		return false
    230 	} else if gi2.Minutes < gi1.Minutes || gi2.Minutes >= minutesRightBound {
    231 		return false
    232 	} else if gi2.Seconds < gi1.Seconds || gi2.Seconds >= secondsRightBound {
    233 		return false
    234 	}
    235 
    236 	return true
    237 }