...

Source file src/github.com/dsoprea/go-exif/v3/tags.go

Documentation: github.com/dsoprea/go-exif/v3

     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  	// IFD1
    15  
    16  	// ThumbnailFqIfdPath is the fully-qualified IFD path that the thumbnail
    17  	// must be found in.
    18  	ThumbnailFqIfdPath = "IFD1"
    19  
    20  	// ThumbnailOffsetTagId returns the tag-ID of the thumbnail offset.
    21  	ThumbnailOffsetTagId = 0x0201
    22  
    23  	// ThumbnailSizeTagId returns the tag-ID of the thumbnail size.
    24  	ThumbnailSizeTagId = 0x0202
    25  )
    26  
    27  const (
    28  	// GPS
    29  
    30  	// TagGpsVersionId is the ID of the GPS version tag.
    31  	TagGpsVersionId = 0x0000
    32  
    33  	// TagLatitudeId is the ID of the GPS latitude tag.
    34  	TagLatitudeId = 0x0002
    35  
    36  	// TagLatitudeRefId is the ID of the GPS latitude orientation tag.
    37  	TagLatitudeRefId = 0x0001
    38  
    39  	// TagLongitudeId is the ID of the GPS longitude tag.
    40  	TagLongitudeId = 0x0004
    41  
    42  	// TagLongitudeRefId is the ID of the GPS longitude-orientation tag.
    43  	TagLongitudeRefId = 0x0003
    44  
    45  	// TagTimestampId is the ID of the GPS time tag.
    46  	TagTimestampId = 0x0007
    47  
    48  	// TagDatestampId is the ID of the GPS date tag.
    49  	TagDatestampId = 0x001d
    50  
    51  	// TagAltitudeId is the ID of the GPS altitude tag.
    52  	TagAltitudeId = 0x0006
    53  
    54  	// TagAltitudeRefId is the ID of the GPS altitude-orientation tag.
    55  	TagAltitudeRefId = 0x0005
    56  )
    57  
    58  var (
    59  	// tagsWithoutAlignment is a tag-lookup for tags whose value size won't
    60  	// necessarily be a multiple of its tag-type.
    61  	tagsWithoutAlignment = map[uint16]struct{}{
    62  		// The thumbnail offset is stored as a long, but its data is a binary
    63  		// blob (not a slice of longs).
    64  		ThumbnailOffsetTagId: {},
    65  	}
    66  )
    67  
    68  var (
    69  	tagsLogger = log.NewLogger("exif.tags")
    70  )
    71  
    72  // File structures.
    73  
    74  type encodedTag struct {
    75  	// id is signed, here, because YAML doesn't have enough information to
    76  	// support unsigned.
    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  // Indexing structures.
    84  
    85  // IndexedTag describes one index lookup result.
    86  type IndexedTag struct {
    87  	// Id is the tag-ID.
    88  	Id uint16
    89  
    90  	// Name is the tag name.
    91  	Name string
    92  
    93  	// IfdPath is the proper IFD path of this tag. This is not fully-qualified.
    94  	IfdPath string
    95  
    96  	// SupportedTypes is an unsorted list of allowed tag-types.
    97  	SupportedTypes []exifcommon.TagTypePrimitive
    98  }
    99  
   100  // String returns a descriptive string.
   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  // IsName returns true if this tag matches the given tag name.
   106  func (it *IndexedTag) IsName(ifdPath, name string) bool {
   107  	return it.Name == name && it.IfdPath == ifdPath
   108  }
   109  
   110  // Is returns true if this tag matched the given tag ID.
   111  func (it *IndexedTag) Is(ifdPath string, id uint16) bool {
   112  	return it.Id == id && it.IfdPath == ifdPath
   113  }
   114  
   115  // GetEncodingType returns the largest type that this tag's value can occupy.
   116  func (it *IndexedTag) GetEncodingType(value interface{}) exifcommon.TagTypePrimitive {
   117  	// For convenience, we handle encoding a `time.Time` directly.
   118  	if exifcommon.IsTime(value) == true {
   119  		// Timestamps are encoded as ASCII.
   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  	// We specifically check for the cases that we know to expect.
   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  // DoesSupportType returns true if this tag can be found/decoded with this type.
   166  func (it *IndexedTag) DoesSupportType(tagType exifcommon.TagTypePrimitive) bool {
   167  	// This is always a very small collection. So, we keep it unsorted.
   168  	for _, thisTagType := range it.SupportedTypes {
   169  		if thisTagType == tagType {
   170  			return true
   171  		}
   172  	}
   173  
   174  	return false
   175  }
   176  
   177  // TagIndex is a tag-lookup facility.
   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  // NewTagIndex returns a new TagIndex struct.
   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  // SetUniversalSearch enables a fallback to matching tags under *any* IFD.
   198  func (ti *TagIndex) SetUniversalSearch(flag bool) {
   199  	ti.doUniversalSearch = flag
   200  }
   201  
   202  // UniversalSearch enables a fallback to matching tags under *any* IFD.
   203  func (ti *TagIndex) UniversalSearch() bool {
   204  	return ti.doUniversalSearch
   205  }
   206  
   207  // Add registers a new tag to be recognized during the parse.
   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  	// Store by ID.
   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  	// Store by name.
   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  // Get returns information about the non-IFD tag given a tag ID. `ifdPath` must
   278  // not be fully-qualified.
   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  	// We've been told to fallback to look for the tag in other IFDs.
   300  
   301  	skipIfdPath := ii.UnindexedString()
   302  
   303  	for currentIfdPath, _ := range ti.tagsByIfd {
   304  		if currentIfdPath == skipIfdPath {
   305  			// Skip the primary IFD, which has already been checked.
   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  	// tagGuessDefaultIfdIdentities describes which IFDs we'll look for a given
   326  	// tag-ID in, if it's not found where it's supposed to be. We suppose that
   327  	// Exif-IFD tags might be found in IFD0 or IFD1, or IFD0/IFD1 tags might be
   328  	// found in the Exif IFD. This is the only thing we've seen so far. So, this
   329  	// is the limit of our guessing.
   330  	tagGuessDefaultIfdIdentities = []*exifcommon.IfdIdentity{
   331  		exifcommon.IfdExifStandardIfdIdentity,
   332  		exifcommon.IfdStandardIfdIdentity,
   333  	}
   334  )
   335  
   336  // FindFirst looks for the given tag-ID in each of the given IFDs in the given
   337  // order. If `fqIfdPaths` is `nil` then use a default search order. This defies
   338  // the standard, which requires each tag to exist in certain IFDs. This is a
   339  // contingency to make recommendations for malformed data.
   340  //
   341  // Things *can* end badly here, in that the same tag-ID in different IFDs might
   342  // describe different data and different ata-types, and our decode might then
   343  // produce binary and non-printable data.
   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  		// Even though the tag might be mislocated, the type should still be the
   366  		// same. Check this so we don't accidentally end-up on a complete
   367  		// irrelevant tag with a totally different data type. This attempts to
   368  		// mitigate producing garbage.
   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  // GetWithName returns information about the non-IFD tag given a tag name.
   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  // LoadStandardTags registers the tags that all devices/applications should
   403  // support.
   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  	// Read static data.
   412  
   413  	encodedIfds := make(map[string][]encodedTag)
   414  
   415  	err = yaml.Unmarshal([]byte(tagsYaml), encodedIfds)
   416  	log.PanicIf(err)
   417  
   418  	// Load structure.
   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  				// TODO(dustin): Discard unsupported types. This helps us with non-standard types that have actually been found in real data, that we ignore for right now. e.g. SSHORT, FLOAT, DOUBLE
   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