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
20 type ExifTag struct {
21
22
23 IfdPath string `json:"ifd_path"`
24
25
26 TagId uint16 `json:"id"`
27
28
29 TagName string `json:"name"`
30
31
32 UnitCount uint32 `json:"unit_count"`
33
34
35 TagTypeId exifcommon.TagTypePrimitive `json:"type_id"`
36
37
38 TagTypeName string `json:"type_name"`
39
40
41 Value interface{} `json:"value"`
42
43
44 ValueBytes []byte `json:"value_bytes"`
45
46
47
48 FormattedFirst string `json:"formatted_first"`
49
50
51 Formatted string `json:"formatted"`
52
53
54
55 ChildIfdPath string `json:"child_ifd_path"`
56 }
57
58
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
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
90
91
92
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
109
110
111
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
126
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
162
163
164
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
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 }
238
View as plain text