1 package exif
2
3 import (
4 "fmt"
5 "sync"
6
7 "github.com/dsoprea/go-logging"
8 "gopkg.in/yaml.v2"
9
10 "github.com/dsoprea/go-exif/v3/common"
11 )
12
13 const (
14
15
16
17
18 ThumbnailFqIfdPath = "IFD1"
19
20
21 ThumbnailOffsetTagId = 0x0201
22
23
24 ThumbnailSizeTagId = 0x0202
25 )
26
27 const (
28
29
30
31 TagGpsVersionId = 0x0000
32
33
34 TagLatitudeId = 0x0002
35
36
37 TagLatitudeRefId = 0x0001
38
39
40 TagLongitudeId = 0x0004
41
42
43 TagLongitudeRefId = 0x0003
44
45
46 TagTimestampId = 0x0007
47
48
49 TagDatestampId = 0x001d
50
51
52 TagAltitudeId = 0x0006
53
54
55 TagAltitudeRefId = 0x0005
56 )
57
58 var (
59
60
61 tagsWithoutAlignment = map[uint16]struct{}{
62
63
64 ThumbnailOffsetTagId: {},
65 }
66 )
67
68 var (
69 tagsLogger = log.NewLogger("exif.tags")
70 )
71
72
73
74 type encodedTag struct {
75
76
77 Id int `yaml:"id"`
78 Name string `yaml:"name"`
79 TypeName string `yaml:"type_name"`
80 TypeNames []string `yaml:"type_names"`
81 }
82
83
84
85
86 type IndexedTag struct {
87
88 Id uint16
89
90
91 Name string
92
93
94 IfdPath string
95
96
97 SupportedTypes []exifcommon.TagTypePrimitive
98 }
99
100
101 func (it *IndexedTag) String() string {
102 return fmt.Sprintf("TAG<ID=(0x%04x) NAME=[%s] IFD=[%s]>", it.Id, it.Name, it.IfdPath)
103 }
104
105
106 func (it *IndexedTag) IsName(ifdPath, name string) bool {
107 return it.Name == name && it.IfdPath == ifdPath
108 }
109
110
111 func (it *IndexedTag) Is(ifdPath string, id uint16) bool {
112 return it.Id == id && it.IfdPath == ifdPath
113 }
114
115
116 func (it *IndexedTag) GetEncodingType(value interface{}) exifcommon.TagTypePrimitive {
117
118 if exifcommon.IsTime(value) == true {
119
120 value = ""
121 }
122
123 if len(it.SupportedTypes) == 0 {
124 log.Panicf("IndexedTag [%s] (%d) has no supported types.", it.IfdPath, it.Id)
125 } else if len(it.SupportedTypes) == 1 {
126 return it.SupportedTypes[0]
127 }
128
129 supportsLong := false
130 supportsShort := false
131 supportsRational := false
132 supportsSignedRational := false
133 for _, supportedType := range it.SupportedTypes {
134 if supportedType == exifcommon.TypeLong {
135 supportsLong = true
136 } else if supportedType == exifcommon.TypeShort {
137 supportsShort = true
138 } else if supportedType == exifcommon.TypeRational {
139 supportsRational = true
140 } else if supportedType == exifcommon.TypeSignedRational {
141 supportsSignedRational = true
142 }
143 }
144
145
146
147 if supportsLong == true && supportsShort == true {
148 return exifcommon.TypeLong
149 } else if supportsRational == true && supportsSignedRational == true {
150 if value == nil {
151 log.Panicf("GetEncodingType: require value to be given")
152 }
153
154 if _, ok := value.(exifcommon.SignedRational); ok == true {
155 return exifcommon.TypeSignedRational
156 }
157
158 return exifcommon.TypeRational
159 }
160
161 log.Panicf("WidestSupportedType() case is not handled for tag [%s] (0x%04x): %v", it.IfdPath, it.Id, it.SupportedTypes)
162 return 0
163 }
164
165
166 func (it *IndexedTag) DoesSupportType(tagType exifcommon.TagTypePrimitive) bool {
167
168 for _, thisTagType := range it.SupportedTypes {
169 if thisTagType == tagType {
170 return true
171 }
172 }
173
174 return false
175 }
176
177
178 type TagIndex struct {
179 tagsByIfd map[string]map[uint16]*IndexedTag
180 tagsByIfdR map[string]map[string]*IndexedTag
181
182 mutex sync.Mutex
183
184 doUniversalSearch bool
185 }
186
187
188 func NewTagIndex() *TagIndex {
189 ti := new(TagIndex)
190
191 ti.tagsByIfd = make(map[string]map[uint16]*IndexedTag)
192 ti.tagsByIfdR = make(map[string]map[string]*IndexedTag)
193
194 return ti
195 }
196
197
198 func (ti *TagIndex) SetUniversalSearch(flag bool) {
199 ti.doUniversalSearch = flag
200 }
201
202
203 func (ti *TagIndex) UniversalSearch() bool {
204 return ti.doUniversalSearch
205 }
206
207
208 func (ti *TagIndex) Add(it *IndexedTag) (err error) {
209 defer func() {
210 if state := recover(); state != nil {
211 err = log.Wrap(state.(error))
212 }
213 }()
214
215 ti.mutex.Lock()
216 defer ti.mutex.Unlock()
217
218
219
220 family, found := ti.tagsByIfd[it.IfdPath]
221 if found == false {
222 family = make(map[uint16]*IndexedTag)
223 ti.tagsByIfd[it.IfdPath] = family
224 }
225
226 if _, found := family[it.Id]; found == true {
227 log.Panicf("tag-ID defined more than once for IFD [%s]: (%02x)", it.IfdPath, it.Id)
228 }
229
230 family[it.Id] = it
231
232
233
234 familyR, found := ti.tagsByIfdR[it.IfdPath]
235 if found == false {
236 familyR = make(map[string]*IndexedTag)
237 ti.tagsByIfdR[it.IfdPath] = familyR
238 }
239
240 if _, found := familyR[it.Name]; found == true {
241 log.Panicf("tag-name defined more than once for IFD [%s]: (%s)", it.IfdPath, it.Name)
242 }
243
244 familyR[it.Name] = it
245
246 return nil
247 }
248
249 func (ti *TagIndex) getOne(ifdPath string, id uint16) (it *IndexedTag, err error) {
250 defer func() {
251 if state := recover(); state != nil {
252 err = log.Wrap(state.(error))
253 }
254 }()
255
256 if len(ti.tagsByIfd) == 0 {
257 err := LoadStandardTags(ti)
258 log.PanicIf(err)
259 }
260
261 ti.mutex.Lock()
262 defer ti.mutex.Unlock()
263
264 family, found := ti.tagsByIfd[ifdPath]
265 if found == false {
266 return nil, ErrTagNotFound
267 }
268
269 it, found = family[id]
270 if found == false {
271 return nil, ErrTagNotFound
272 }
273
274 return it, nil
275 }
276
277
278
279 func (ti *TagIndex) Get(ii *exifcommon.IfdIdentity, id uint16) (it *IndexedTag, err error) {
280 defer func() {
281 if state := recover(); state != nil {
282 err = log.Wrap(state.(error))
283 }
284 }()
285
286 ifdPath := ii.UnindexedString()
287
288 it, err = ti.getOne(ifdPath, id)
289 if err == nil {
290 return it, nil
291 } else if err != ErrTagNotFound {
292 log.Panic(err)
293 }
294
295 if ti.doUniversalSearch == false {
296 return nil, ErrTagNotFound
297 }
298
299
300
301 skipIfdPath := ii.UnindexedString()
302
303 for currentIfdPath, _ := range ti.tagsByIfd {
304 if currentIfdPath == skipIfdPath {
305
306 continue
307 }
308
309 it, err = ti.getOne(currentIfdPath, id)
310 if err == nil {
311 tagsLogger.Warningf(nil,
312 "Found tag (0x%02x) in the wrong IFD: [%s] != [%s]",
313 id, currentIfdPath, ifdPath)
314
315 return it, nil
316 } else if err != ErrTagNotFound {
317 log.Panic(err)
318 }
319 }
320
321 return nil, ErrTagNotFound
322 }
323
324 var (
325
326
327
328
329
330 tagGuessDefaultIfdIdentities = []*exifcommon.IfdIdentity{
331 exifcommon.IfdExifStandardIfdIdentity,
332 exifcommon.IfdStandardIfdIdentity,
333 }
334 )
335
336
337
338
339
340
341
342
343
344 func (ti *TagIndex) FindFirst(id uint16, typeId exifcommon.TagTypePrimitive, ifdIdentities []*exifcommon.IfdIdentity) (it *IndexedTag, err error) {
345 defer func() {
346 if state := recover(); state != nil {
347 err = log.Wrap(state.(error))
348 }
349 }()
350
351 if ifdIdentities == nil {
352 ifdIdentities = tagGuessDefaultIfdIdentities
353 }
354
355 for _, ii := range ifdIdentities {
356 it, err := ti.Get(ii, id)
357 if err != nil {
358 if err == ErrTagNotFound {
359 continue
360 }
361
362 log.Panic(err)
363 }
364
365
366
367
368
369 for _, supportedType := range it.SupportedTypes {
370 if supportedType == typeId {
371 return it, nil
372 }
373 }
374 }
375
376 return nil, ErrTagNotFound
377 }
378
379
380 func (ti *TagIndex) GetWithName(ii *exifcommon.IfdIdentity, name string) (it *IndexedTag, err error) {
381 defer func() {
382 if state := recover(); state != nil {
383 err = log.Wrap(state.(error))
384 }
385 }()
386
387 if len(ti.tagsByIfdR) == 0 {
388 err := LoadStandardTags(ti)
389 log.PanicIf(err)
390 }
391
392 ifdPath := ii.UnindexedString()
393
394 it, found := ti.tagsByIfdR[ifdPath][name]
395 if found != true {
396 log.Panic(ErrTagNotFound)
397 }
398
399 return it, nil
400 }
401
402
403
404 func LoadStandardTags(ti *TagIndex) (err error) {
405 defer func() {
406 if state := recover(); state != nil {
407 err = log.Wrap(state.(error))
408 }
409 }()
410
411
412
413 encodedIfds := make(map[string][]encodedTag)
414
415 err = yaml.Unmarshal([]byte(tagsYaml), encodedIfds)
416 log.PanicIf(err)
417
418
419
420 count := 0
421 for ifdPath, tags := range encodedIfds {
422 for _, tagInfo := range tags {
423 tagId := uint16(tagInfo.Id)
424 tagName := tagInfo.Name
425 tagTypeName := tagInfo.TypeName
426 tagTypeNames := tagInfo.TypeNames
427
428 if tagTypeNames == nil {
429 if tagTypeName == "" {
430 log.Panicf("no tag-types were given when registering standard tag [%s] (0x%04x) [%s]", ifdPath, tagId, tagName)
431 }
432
433 tagTypeNames = []string{
434 tagTypeName,
435 }
436 } else if tagTypeName != "" {
437 log.Panicf("both 'type_names' and 'type_name' were given when registering standard tag [%s] (0x%04x) [%s]", ifdPath, tagId, tagName)
438 }
439
440 tagTypes := make([]exifcommon.TagTypePrimitive, 0)
441 for _, tagTypeName := range tagTypeNames {
442
443
444 tagTypeId, found := exifcommon.GetTypeByName(tagTypeName)
445 if found == false {
446 tagsLogger.Warningf(nil, "Type [%s] for tag [%s] being loaded is not valid and is being ignored.", tagTypeName, tagName)
447 continue
448 }
449
450 tagTypes = append(tagTypes, tagTypeId)
451 }
452
453 if len(tagTypes) == 0 {
454 tagsLogger.Warningf(nil, "Tag [%s] (0x%04x) [%s] being loaded does not have any supported types and will not be registered.", ifdPath, tagId, tagName)
455 continue
456 }
457
458 it := &IndexedTag{
459 IfdPath: ifdPath,
460 Id: tagId,
461 Name: tagName,
462 SupportedTypes: tagTypes,
463 }
464
465 err = ti.Add(it)
466 log.PanicIf(err)
467
468 count++
469 }
470 }
471
472 tagsLogger.Debugf(nil, "(%d) tags loaded.", count)
473
474 return nil
475 }
476
View as plain text