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 }