ifd.go (16652B)
1 package exifcommon 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 8 "github.com/dsoprea/go-logging" 9 ) 10 11 var ( 12 ifdLogger = log.NewLogger("exifcommon.ifd") 13 ) 14 15 var ( 16 ErrChildIfdNotMapped = errors.New("no child-IFD for that tag-ID under parent") 17 ) 18 19 // MappedIfd is one node in the IFD-mapping. 20 type MappedIfd struct { 21 ParentTagId uint16 22 Placement []uint16 23 Path []string 24 25 Name string 26 TagId uint16 27 Children map[uint16]*MappedIfd 28 } 29 30 // String returns a descriptive string. 31 func (mi *MappedIfd) String() string { 32 pathPhrase := mi.PathPhrase() 33 return fmt.Sprintf("MappedIfd<(0x%04X) [%s] PATH=[%s]>", mi.TagId, mi.Name, pathPhrase) 34 } 35 36 // PathPhrase returns a non-fully-qualified IFD path. 37 func (mi *MappedIfd) PathPhrase() string { 38 return strings.Join(mi.Path, "/") 39 } 40 41 // TODO(dustin): Refactor this to use IfdIdentity structs. 42 43 // IfdMapping describes all of the IFDs that we currently recognize. 44 type IfdMapping struct { 45 rootNode *MappedIfd 46 } 47 48 // NewIfdMapping returns a new IfdMapping struct. 49 func NewIfdMapping() (ifdMapping *IfdMapping) { 50 rootNode := &MappedIfd{ 51 Path: make([]string, 0), 52 Children: make(map[uint16]*MappedIfd), 53 } 54 55 return &IfdMapping{ 56 rootNode: rootNode, 57 } 58 } 59 60 // NewIfdMappingWithStandard retruns a new IfdMapping struct preloaded with the 61 // standard IFDs. 62 func NewIfdMappingWithStandard() (ifdMapping *IfdMapping, err error) { 63 defer func() { 64 if state := recover(); state != nil { 65 err = log.Wrap(state.(error)) 66 } 67 }() 68 69 im := NewIfdMapping() 70 71 err = LoadStandardIfds(im) 72 log.PanicIf(err) 73 74 return im, nil 75 } 76 77 // Get returns the node given the path slice. 78 func (im *IfdMapping) Get(parentPlacement []uint16) (childIfd *MappedIfd, err error) { 79 defer func() { 80 if state := recover(); state != nil { 81 err = log.Wrap(state.(error)) 82 } 83 }() 84 85 ptr := im.rootNode 86 for _, tagId := range parentPlacement { 87 if descendantPtr, found := ptr.Children[tagId]; found == false { 88 log.Panicf("ifd child with tag-ID (%04x) not registered: [%s]", tagId, ptr.PathPhrase()) 89 } else { 90 ptr = descendantPtr 91 } 92 } 93 94 return ptr, nil 95 } 96 97 // GetWithPath returns the node given the path string. 98 func (im *IfdMapping) GetWithPath(pathPhrase string) (mi *MappedIfd, err error) { 99 defer func() { 100 if state := recover(); state != nil { 101 err = log.Wrap(state.(error)) 102 } 103 }() 104 105 if pathPhrase == "" { 106 log.Panicf("path-phrase is empty") 107 } 108 109 path := strings.Split(pathPhrase, "/") 110 ptr := im.rootNode 111 112 for _, name := range path { 113 var hit *MappedIfd 114 for _, mi := range ptr.Children { 115 if mi.Name == name { 116 hit = mi 117 break 118 } 119 } 120 121 if hit == nil { 122 log.Panicf("ifd child with name [%s] not registered: [%s]", name, ptr.PathPhrase()) 123 } 124 125 ptr = hit 126 } 127 128 return ptr, nil 129 } 130 131 // GetChild is a convenience function to get the child path for a given parent 132 // placement and child tag-ID. 133 func (im *IfdMapping) GetChild(parentPathPhrase string, tagId uint16) (mi *MappedIfd, err error) { 134 defer func() { 135 if state := recover(); state != nil { 136 err = log.Wrap(state.(error)) 137 } 138 }() 139 140 mi, err = im.GetWithPath(parentPathPhrase) 141 log.PanicIf(err) 142 143 for _, childMi := range mi.Children { 144 if childMi.TagId == tagId { 145 return childMi, nil 146 } 147 } 148 149 // Whether or not an IFD is defined in data, such an IFD is not registered 150 // and would be unknown. 151 log.Panic(ErrChildIfdNotMapped) 152 return nil, nil 153 } 154 155 // IfdTagIdAndIndex represents a specific part of the IFD path. 156 // 157 // This is a legacy type. 158 type IfdTagIdAndIndex struct { 159 Name string 160 TagId uint16 161 Index int 162 } 163 164 // String returns a descriptive string. 165 func (itii IfdTagIdAndIndex) String() string { 166 return fmt.Sprintf("IfdTagIdAndIndex<NAME=[%s] ID=(%04x) INDEX=(%d)>", itii.Name, itii.TagId, itii.Index) 167 } 168 169 // ResolvePath takes a list of names, which can also be suffixed with indices 170 // (to identify the second, third, etc.. sibling IFD) and returns a list of 171 // tag-IDs and those indices. 172 // 173 // Example: 174 // 175 // - IFD/Exif/Iop 176 // - IFD0/Exif/Iop 177 // 178 // This is the only call that supports adding the numeric indices. 179 func (im *IfdMapping) ResolvePath(pathPhrase string) (lineage []IfdTagIdAndIndex, err error) { 180 defer func() { 181 if state := recover(); state != nil { 182 err = log.Wrap(state.(error)) 183 } 184 }() 185 186 pathPhrase = strings.TrimSpace(pathPhrase) 187 188 if pathPhrase == "" { 189 log.Panicf("can not resolve empty path-phrase") 190 } 191 192 path := strings.Split(pathPhrase, "/") 193 lineage = make([]IfdTagIdAndIndex, len(path)) 194 195 ptr := im.rootNode 196 empty := IfdTagIdAndIndex{} 197 for i, name := range path { 198 indexByte := name[len(name)-1] 199 index := 0 200 if indexByte >= '0' && indexByte <= '9' { 201 index = int(indexByte - '0') 202 name = name[:len(name)-1] 203 } 204 205 itii := IfdTagIdAndIndex{} 206 for _, mi := range ptr.Children { 207 if mi.Name != name { 208 continue 209 } 210 211 itii.Name = name 212 itii.TagId = mi.TagId 213 itii.Index = index 214 215 ptr = mi 216 217 break 218 } 219 220 if itii == empty { 221 log.Panicf("ifd child with name [%s] not registered: [%s]", name, pathPhrase) 222 } 223 224 lineage[i] = itii 225 } 226 227 return lineage, nil 228 } 229 230 // FqPathPhraseFromLineage returns the fully-qualified IFD path from the slice. 231 func (im *IfdMapping) FqPathPhraseFromLineage(lineage []IfdTagIdAndIndex) (fqPathPhrase string) { 232 fqPathParts := make([]string, len(lineage)) 233 for i, itii := range lineage { 234 if itii.Index > 0 { 235 fqPathParts[i] = fmt.Sprintf("%s%d", itii.Name, itii.Index) 236 } else { 237 fqPathParts[i] = itii.Name 238 } 239 } 240 241 return strings.Join(fqPathParts, "/") 242 } 243 244 // PathPhraseFromLineage returns the non-fully-qualified IFD path from the 245 // slice. 246 func (im *IfdMapping) PathPhraseFromLineage(lineage []IfdTagIdAndIndex) (pathPhrase string) { 247 pathParts := make([]string, len(lineage)) 248 for i, itii := range lineage { 249 pathParts[i] = itii.Name 250 } 251 252 return strings.Join(pathParts, "/") 253 } 254 255 // StripPathPhraseIndices returns a non-fully-qualified path-phrase (no 256 // indices). 257 func (im *IfdMapping) StripPathPhraseIndices(pathPhrase string) (strippedPathPhrase string, err error) { 258 defer func() { 259 if state := recover(); state != nil { 260 err = log.Wrap(state.(error)) 261 } 262 }() 263 264 lineage, err := im.ResolvePath(pathPhrase) 265 log.PanicIf(err) 266 267 strippedPathPhrase = im.PathPhraseFromLineage(lineage) 268 return strippedPathPhrase, nil 269 } 270 271 // Add puts the given IFD at the given position of the tree. The position of the 272 // tree is referred to as the placement and is represented by a set of tag-IDs, 273 // where the leftmost is the root tag and the tags going to the right are 274 // progressive descendants. 275 func (im *IfdMapping) Add(parentPlacement []uint16, tagId uint16, name string) (err error) { 276 defer func() { 277 if state := recover(); state != nil { 278 err = log.Wrap(state.(error)) 279 } 280 }() 281 282 // TODO(dustin): !! It would be nicer to provide a list of names in the placement rather than tag-IDs. 283 284 ptr, err := im.Get(parentPlacement) 285 log.PanicIf(err) 286 287 path := make([]string, len(parentPlacement)+1) 288 if len(parentPlacement) > 0 { 289 copy(path, ptr.Path) 290 } 291 292 path[len(path)-1] = name 293 294 placement := make([]uint16, len(parentPlacement)+1) 295 if len(placement) > 0 { 296 copy(placement, ptr.Placement) 297 } 298 299 placement[len(placement)-1] = tagId 300 301 childIfd := &MappedIfd{ 302 ParentTagId: ptr.TagId, 303 Path: path, 304 Placement: placement, 305 Name: name, 306 TagId: tagId, 307 Children: make(map[uint16]*MappedIfd), 308 } 309 310 if _, found := ptr.Children[tagId]; found == true { 311 log.Panicf("child IFD with tag-ID (%04x) already registered under IFD [%s] with tag-ID (%04x)", tagId, ptr.Name, ptr.TagId) 312 } 313 314 ptr.Children[tagId] = childIfd 315 316 return nil 317 } 318 319 func (im *IfdMapping) dumpLineages(stack []*MappedIfd, input []string) (output []string, err error) { 320 defer func() { 321 if state := recover(); state != nil { 322 err = log.Wrap(state.(error)) 323 } 324 }() 325 326 currentIfd := stack[len(stack)-1] 327 328 output = input 329 for _, childIfd := range currentIfd.Children { 330 stackCopy := make([]*MappedIfd, len(stack)+1) 331 332 copy(stackCopy, stack) 333 stackCopy[len(stack)] = childIfd 334 335 // Add to output, but don't include the obligatory root node. 336 parts := make([]string, len(stackCopy)-1) 337 for i, mi := range stackCopy[1:] { 338 parts[i] = mi.Name 339 } 340 341 output = append(output, strings.Join(parts, "/")) 342 343 output, err = im.dumpLineages(stackCopy, output) 344 log.PanicIf(err) 345 } 346 347 return output, nil 348 } 349 350 // DumpLineages returns a slice of strings representing all mappings. 351 func (im *IfdMapping) DumpLineages() (output []string, err error) { 352 defer func() { 353 if state := recover(); state != nil { 354 err = log.Wrap(state.(error)) 355 } 356 }() 357 358 stack := []*MappedIfd{im.rootNode} 359 output = make([]string, 0) 360 361 output, err = im.dumpLineages(stack, output) 362 log.PanicIf(err) 363 364 return output, nil 365 } 366 367 // LoadStandardIfds loads the standard IFDs into the mapping. 368 func LoadStandardIfds(im *IfdMapping) (err error) { 369 defer func() { 370 if state := recover(); state != nil { 371 err = log.Wrap(state.(error)) 372 } 373 }() 374 375 err = im.Add( 376 []uint16{}, 377 IfdStandardIfdIdentity.TagId(), IfdStandardIfdIdentity.Name()) 378 379 log.PanicIf(err) 380 381 err = im.Add( 382 []uint16{IfdStandardIfdIdentity.TagId()}, 383 IfdExifStandardIfdIdentity.TagId(), IfdExifStandardIfdIdentity.Name()) 384 385 log.PanicIf(err) 386 387 err = im.Add( 388 []uint16{IfdStandardIfdIdentity.TagId(), IfdExifStandardIfdIdentity.TagId()}, 389 IfdExifIopStandardIfdIdentity.TagId(), IfdExifIopStandardIfdIdentity.Name()) 390 391 log.PanicIf(err) 392 393 err = im.Add( 394 []uint16{IfdStandardIfdIdentity.TagId()}, 395 IfdGpsInfoStandardIfdIdentity.TagId(), IfdGpsInfoStandardIfdIdentity.Name()) 396 397 log.PanicIf(err) 398 399 return nil 400 } 401 402 // IfdTag describes a single IFD tag and its parent (if any). 403 type IfdTag struct { 404 parentIfdTag *IfdTag 405 tagId uint16 406 name string 407 } 408 409 func NewIfdTag(parentIfdTag *IfdTag, tagId uint16, name string) IfdTag { 410 return IfdTag{ 411 parentIfdTag: parentIfdTag, 412 tagId: tagId, 413 name: name, 414 } 415 } 416 417 // ParentIfd returns the IfdTag of this IFD's parent. 418 func (it IfdTag) ParentIfd() *IfdTag { 419 return it.parentIfdTag 420 } 421 422 // TagId returns the tag-ID of this IFD. 423 func (it IfdTag) TagId() uint16 { 424 return it.tagId 425 } 426 427 // Name returns the simple name of this IFD. 428 func (it IfdTag) Name() string { 429 return it.name 430 } 431 432 // String returns a descriptive string. 433 func (it IfdTag) String() string { 434 parentIfdPhrase := "" 435 if it.parentIfdTag != nil { 436 parentIfdPhrase = fmt.Sprintf(" PARENT=(0x%04x)[%s]", it.parentIfdTag.tagId, it.parentIfdTag.name) 437 } 438 439 return fmt.Sprintf("IfdTag<TAG-ID=(0x%04x) NAME=[%s]%s>", it.tagId, it.name, parentIfdPhrase) 440 } 441 442 var ( 443 // rootStandardIfd is the standard root IFD. 444 rootStandardIfd = NewIfdTag(nil, 0x0000, "IFD") // IFD 445 446 // exifStandardIfd is the standard "Exif" IFD. 447 exifStandardIfd = NewIfdTag(&rootStandardIfd, 0x8769, "Exif") // IFD/Exif 448 449 // iopStandardIfd is the standard "Iop" IFD. 450 iopStandardIfd = NewIfdTag(&exifStandardIfd, 0xA005, "Iop") // IFD/Exif/Iop 451 452 // gpsInfoStandardIfd is the standard "GPS" IFD. 453 gpsInfoStandardIfd = NewIfdTag(&rootStandardIfd, 0x8825, "GPSInfo") // IFD/GPSInfo 454 ) 455 456 // IfdIdentityPart represents one component in an IFD path. 457 type IfdIdentityPart struct { 458 Name string 459 Index int 460 } 461 462 // String returns a fully-qualified IFD path. 463 func (iip IfdIdentityPart) String() string { 464 if iip.Index > 0 { 465 return fmt.Sprintf("%s%d", iip.Name, iip.Index) 466 } else { 467 return iip.Name 468 } 469 } 470 471 // UnindexedString returned a non-fully-qualified IFD path. 472 func (iip IfdIdentityPart) UnindexedString() string { 473 return iip.Name 474 } 475 476 // IfdIdentity represents a single IFD path and provides access to various 477 // information and representations. 478 // 479 // Only global instances can be used for equality checks. 480 type IfdIdentity struct { 481 ifdTag IfdTag 482 parts []IfdIdentityPart 483 ifdPath string 484 fqIfdPath string 485 } 486 487 // NewIfdIdentity returns a new IfdIdentity struct. 488 func NewIfdIdentity(ifdTag IfdTag, parts ...IfdIdentityPart) (ii *IfdIdentity) { 489 ii = &IfdIdentity{ 490 ifdTag: ifdTag, 491 parts: parts, 492 } 493 494 ii.ifdPath = ii.getIfdPath() 495 ii.fqIfdPath = ii.getFqIfdPath() 496 497 return ii 498 } 499 500 // NewIfdIdentityFromString parses a string like "IFD/Exif" or "IFD1" or 501 // something more exotic with custom IFDs ("SomeIFD4/SomeChildIFD6"). Note that 502 // this will valid the unindexed IFD structure (because the standard tags from 503 // the specification are unindexed), but not, obviously, any indices (e.g. 504 // the numbers in "IFD0", "IFD1", "SomeIFD4/SomeChildIFD6"). It is 505 // required for the caller to check whether these specific instances 506 // were actually parsed out of the stream. 507 func NewIfdIdentityFromString(im *IfdMapping, fqIfdPath string) (ii *IfdIdentity, err error) { 508 defer func() { 509 if state := recover(); state != nil { 510 err = log.Wrap(state.(error)) 511 } 512 }() 513 514 lineage, err := im.ResolvePath(fqIfdPath) 515 log.PanicIf(err) 516 517 var lastIt *IfdTag 518 identityParts := make([]IfdIdentityPart, len(lineage)) 519 for i, itii := range lineage { 520 // Build out the tag that will eventually point to the IFD represented 521 // by the right-most part in the IFD path. 522 523 it := &IfdTag{ 524 parentIfdTag: lastIt, 525 tagId: itii.TagId, 526 name: itii.Name, 527 } 528 529 lastIt = it 530 531 // Create the next IfdIdentity part. 532 533 iip := IfdIdentityPart{ 534 Name: itii.Name, 535 Index: itii.Index, 536 } 537 538 identityParts[i] = iip 539 } 540 541 ii = NewIfdIdentity(*lastIt, identityParts...) 542 return ii, nil 543 } 544 545 func (ii *IfdIdentity) getFqIfdPath() string { 546 partPhrases := make([]string, len(ii.parts)) 547 for i, iip := range ii.parts { 548 partPhrases[i] = iip.String() 549 } 550 551 return strings.Join(partPhrases, "/") 552 } 553 554 func (ii *IfdIdentity) getIfdPath() string { 555 partPhrases := make([]string, len(ii.parts)) 556 for i, iip := range ii.parts { 557 partPhrases[i] = iip.UnindexedString() 558 } 559 560 return strings.Join(partPhrases, "/") 561 } 562 563 // String returns a fully-qualified IFD path. 564 func (ii *IfdIdentity) String() string { 565 return ii.fqIfdPath 566 } 567 568 // UnindexedString returns a non-fully-qualified IFD path. 569 func (ii *IfdIdentity) UnindexedString() string { 570 return ii.ifdPath 571 } 572 573 // IfdTag returns the tag struct behind this IFD. 574 func (ii *IfdIdentity) IfdTag() IfdTag { 575 return ii.ifdTag 576 } 577 578 // TagId returns the tag-ID of the IFD. 579 func (ii *IfdIdentity) TagId() uint16 { 580 return ii.ifdTag.TagId() 581 } 582 583 // LeafPathPart returns the last right-most path-part, which represents the 584 // current IFD. 585 func (ii *IfdIdentity) LeafPathPart() IfdIdentityPart { 586 return ii.parts[len(ii.parts)-1] 587 } 588 589 // Name returns the simple name of this IFD. 590 func (ii *IfdIdentity) Name() string { 591 return ii.LeafPathPart().Name 592 } 593 594 // Index returns the index of this IFD (more then one IFD under a parent IFD 595 // will be numbered [0..n]). 596 func (ii *IfdIdentity) Index() int { 597 return ii.LeafPathPart().Index 598 } 599 600 // Equals returns true if the two IfdIdentity instances are effectively 601 // identical. 602 // 603 // Since there's no way to get a specific fully-qualified IFD path without a 604 // certain slice of parts and all other fields are also derived from this, 605 // checking that the fully-qualified IFD path is equals is sufficient. 606 func (ii *IfdIdentity) Equals(ii2 *IfdIdentity) bool { 607 return ii.String() == ii2.String() 608 } 609 610 // NewChild creates an IfdIdentity for an IFD that is a child of the current 611 // IFD. 612 func (ii *IfdIdentity) NewChild(childIfdTag IfdTag, index int) (iiChild *IfdIdentity) { 613 if *childIfdTag.parentIfdTag != ii.ifdTag { 614 log.Panicf("can not add child; we are not the parent:\nUS=%v\nCHILD=%v", ii.ifdTag, childIfdTag) 615 } 616 617 childPart := IfdIdentityPart{childIfdTag.name, index} 618 childParts := append(ii.parts, childPart) 619 620 iiChild = NewIfdIdentity(childIfdTag, childParts...) 621 return iiChild 622 } 623 624 // NewSibling creates an IfdIdentity for an IFD that is a sibling to the current 625 // one. 626 func (ii *IfdIdentity) NewSibling(index int) (iiSibling *IfdIdentity) { 627 parts := make([]IfdIdentityPart, len(ii.parts)) 628 629 copy(parts, ii.parts) 630 parts[len(parts)-1].Index = index 631 632 iiSibling = NewIfdIdentity(ii.ifdTag, parts...) 633 return iiSibling 634 } 635 636 var ( 637 // IfdStandardIfdIdentity represents the IFD path for IFD0. 638 IfdStandardIfdIdentity = NewIfdIdentity(rootStandardIfd, IfdIdentityPart{"IFD", 0}) 639 640 // IfdExifStandardIfdIdentity represents the IFD path for IFD0/Exif0. 641 IfdExifStandardIfdIdentity = IfdStandardIfdIdentity.NewChild(exifStandardIfd, 0) 642 643 // IfdExifIopStandardIfdIdentity represents the IFD path for IFD0/Exif0/Iop0. 644 IfdExifIopStandardIfdIdentity = IfdExifStandardIfdIdentity.NewChild(iopStandardIfd, 0) 645 646 // IfdGPSInfoStandardIfdIdentity represents the IFD path for IFD0/GPSInfo0. 647 IfdGpsInfoStandardIfdIdentity = IfdStandardIfdIdentity.NewChild(gpsInfoStandardIfd, 0) 648 649 // Ifd1StandardIfdIdentity represents the IFD path for IFD1. 650 Ifd1StandardIfdIdentity = NewIfdIdentity(rootStandardIfd, IfdIdentityPart{"IFD", 1}) 651 )