...

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

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

     1  package exif
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  
     7  	"encoding/binary"
     8  
     9  	"github.com/dsoprea/go-logging"
    10  
    11  	"github.com/dsoprea/go-exif/v3/common"
    12  	"github.com/dsoprea/go-exif/v3/undefined"
    13  )
    14  
    15  var (
    16  	iteLogger = log.NewLogger("exif.ifd_tag_entry")
    17  )
    18  
    19  // IfdTagEntry refers to a tag in the loaded EXIF block.
    20  type IfdTagEntry struct {
    21  	tagId          uint16
    22  	tagIndex       int
    23  	tagType        exifcommon.TagTypePrimitive
    24  	unitCount      uint32
    25  	valueOffset    uint32
    26  	rawValueOffset []byte
    27  
    28  	// childIfdName is the right most atom in the IFD-path. We need this to
    29  	// construct the fully-qualified IFD-path.
    30  	childIfdName string
    31  
    32  	// childIfdPath is the IFD-path of the child if this tag represents a child
    33  	// IFD.
    34  	childIfdPath string
    35  
    36  	// childFqIfdPath is the IFD-path of the child if this tag represents a
    37  	// child IFD. Includes indices.
    38  	childFqIfdPath string
    39  
    40  	// TODO(dustin): !! IB's host the child-IBs directly in the tag, but that's not the case here. Refactor to accommodate it for a consistent experience.
    41  
    42  	ifdIdentity *exifcommon.IfdIdentity
    43  
    44  	isUnhandledUnknown bool
    45  
    46  	rs        io.ReadSeeker
    47  	byteOrder binary.ByteOrder
    48  
    49  	tagName string
    50  }
    51  
    52  func newIfdTagEntry(ii *exifcommon.IfdIdentity, tagId uint16, tagIndex int, tagType exifcommon.TagTypePrimitive, unitCount uint32, valueOffset uint32, rawValueOffset []byte, rs io.ReadSeeker, byteOrder binary.ByteOrder) *IfdTagEntry {
    53  	return &IfdTagEntry{
    54  		ifdIdentity:    ii,
    55  		tagId:          tagId,
    56  		tagIndex:       tagIndex,
    57  		tagType:        tagType,
    58  		unitCount:      unitCount,
    59  		valueOffset:    valueOffset,
    60  		rawValueOffset: rawValueOffset,
    61  		rs:             rs,
    62  		byteOrder:      byteOrder,
    63  	}
    64  }
    65  
    66  // String returns a stringified representation of the struct.
    67  func (ite *IfdTagEntry) String() string {
    68  	return fmt.Sprintf("IfdTagEntry<TAG-IFD-PATH=[%s] TAG-ID=(0x%04x) TAG-TYPE=[%s] UNIT-COUNT=(%d)>", ite.ifdIdentity.String(), ite.tagId, ite.tagType.String(), ite.unitCount)
    69  }
    70  
    71  // TagName returns the name of the tag. This is determined else and set after
    72  // the parse (since it's not actually stored in the stream). If it's empty, it
    73  // is because it is an unknown tag (nonstandard or otherwise unavailable in the
    74  // tag-index).
    75  func (ite *IfdTagEntry) TagName() string {
    76  	return ite.tagName
    77  }
    78  
    79  // setTagName sets the tag-name. This provides the name for convenience and
    80  // efficiency by determining it when most efficient while we're parsing rather
    81  // than delegating it to the caller (or, worse, the user).
    82  func (ite *IfdTagEntry) setTagName(tagName string) {
    83  	ite.tagName = tagName
    84  }
    85  
    86  // IfdPath returns the fully-qualified path of the IFD that owns this tag.
    87  func (ite *IfdTagEntry) IfdPath() string {
    88  	return ite.ifdIdentity.String()
    89  }
    90  
    91  // TagId returns the ID of the tag that we represent. The combination of
    92  // (IfdPath(), TagId()) is unique.
    93  func (ite *IfdTagEntry) TagId() uint16 {
    94  	return ite.tagId
    95  }
    96  
    97  // IsThumbnailOffset returns true if the tag has the IFD and tag-ID of a
    98  // thumbnail offset.
    99  func (ite *IfdTagEntry) IsThumbnailOffset() bool {
   100  	return ite.tagId == ThumbnailOffsetTagId && ite.ifdIdentity.String() == ThumbnailFqIfdPath
   101  }
   102  
   103  // IsThumbnailSize returns true if the tag has the IFD and tag-ID of a thumbnail
   104  // size.
   105  func (ite *IfdTagEntry) IsThumbnailSize() bool {
   106  	return ite.tagId == ThumbnailSizeTagId && ite.ifdIdentity.String() == ThumbnailFqIfdPath
   107  }
   108  
   109  // TagType is the type of value for this tag.
   110  func (ite *IfdTagEntry) TagType() exifcommon.TagTypePrimitive {
   111  	return ite.tagType
   112  }
   113  
   114  // updateTagType sets an alternatively interpreted tag-type.
   115  func (ite *IfdTagEntry) updateTagType(tagType exifcommon.TagTypePrimitive) {
   116  	ite.tagType = tagType
   117  }
   118  
   119  // UnitCount returns the unit-count of the tag's value.
   120  func (ite *IfdTagEntry) UnitCount() uint32 {
   121  	return ite.unitCount
   122  }
   123  
   124  // updateUnitCount sets an alternatively interpreted unit-count.
   125  func (ite *IfdTagEntry) updateUnitCount(unitCount uint32) {
   126  	ite.unitCount = unitCount
   127  }
   128  
   129  // getValueOffset is the four-byte offset converted to an integer to point to
   130  // the location of its value in the EXIF block. The "get" parameter is obviously
   131  // used in order to differentiate the naming of the method from the field.
   132  func (ite *IfdTagEntry) getValueOffset() uint32 {
   133  	return ite.valueOffset
   134  }
   135  
   136  // GetRawBytes renders a specific list of bytes from the value in this tag.
   137  func (ite *IfdTagEntry) GetRawBytes() (rawBytes []byte, err error) {
   138  	defer func() {
   139  		if state := recover(); state != nil {
   140  			err = log.Wrap(state.(error))
   141  		}
   142  	}()
   143  
   144  	valueContext := ite.getValueContext()
   145  
   146  	if ite.tagType == exifcommon.TypeUndefined {
   147  		value, err := exifundefined.Decode(valueContext)
   148  		if err != nil {
   149  			if err == exifcommon.ErrUnhandledUndefinedTypedTag {
   150  				ite.setIsUnhandledUnknown(true)
   151  				return nil, exifundefined.ErrUnparseableValue
   152  			} else if err == exifundefined.ErrUnparseableValue {
   153  				return nil, err
   154  			} else {
   155  				log.Panic(err)
   156  			}
   157  		}
   158  
   159  		// Encode it back, in order to get the raw bytes. This is the best,
   160  		// general way to do it with an undefined tag.
   161  
   162  		rawBytes, _, err := exifundefined.Encode(value, ite.byteOrder)
   163  		log.PanicIf(err)
   164  
   165  		return rawBytes, nil
   166  	}
   167  
   168  	rawBytes, err = valueContext.ReadRawEncoded()
   169  	log.PanicIf(err)
   170  
   171  	return rawBytes, nil
   172  }
   173  
   174  // Value returns the specific, parsed, typed value from the tag.
   175  func (ite *IfdTagEntry) Value() (value interface{}, err error) {
   176  	defer func() {
   177  		if state := recover(); state != nil {
   178  			err = log.Wrap(state.(error))
   179  		}
   180  	}()
   181  
   182  	valueContext := ite.getValueContext()
   183  
   184  	if ite.tagType == exifcommon.TypeUndefined {
   185  		var err error
   186  
   187  		value, err = exifundefined.Decode(valueContext)
   188  		if err != nil {
   189  			if err == exifcommon.ErrUnhandledUndefinedTypedTag || err == exifundefined.ErrUnparseableValue {
   190  				return nil, err
   191  			}
   192  
   193  			log.Panic(err)
   194  		}
   195  	} else {
   196  		var err error
   197  
   198  		value, err = valueContext.Values()
   199  		log.PanicIf(err)
   200  	}
   201  
   202  	return value, nil
   203  }
   204  
   205  // Format returns the tag's value as a string.
   206  func (ite *IfdTagEntry) Format() (phrase string, err error) {
   207  	defer func() {
   208  		if state := recover(); state != nil {
   209  			err = log.Wrap(state.(error))
   210  		}
   211  	}()
   212  
   213  	value, err := ite.Value()
   214  	if err != nil {
   215  		if err == exifcommon.ErrUnhandledUndefinedTypedTag {
   216  			return exifundefined.UnparseableUnknownTagValuePlaceholder, nil
   217  		} else if err == exifundefined.ErrUnparseableValue {
   218  			return exifundefined.UnparseableHandledTagValuePlaceholder, nil
   219  		}
   220  
   221  		log.Panic(err)
   222  	}
   223  
   224  	phrase, err = exifcommon.FormatFromType(value, false)
   225  	log.PanicIf(err)
   226  
   227  	return phrase, nil
   228  }
   229  
   230  // FormatFirst returns the same as Format() but only the first item.
   231  func (ite *IfdTagEntry) FormatFirst() (phrase string, err error) {
   232  	defer func() {
   233  		if state := recover(); state != nil {
   234  			err = log.Wrap(state.(error))
   235  		}
   236  	}()
   237  
   238  	// TODO(dustin): We should add a convenience type "timestamp", to simplify translating to and from the physical ASCII and provide validation.
   239  
   240  	value, err := ite.Value()
   241  	if err != nil {
   242  		if err == exifcommon.ErrUnhandledUndefinedTypedTag {
   243  			return exifundefined.UnparseableUnknownTagValuePlaceholder, nil
   244  		}
   245  
   246  		log.Panic(err)
   247  	}
   248  
   249  	phrase, err = exifcommon.FormatFromType(value, true)
   250  	log.PanicIf(err)
   251  
   252  	return phrase, nil
   253  }
   254  
   255  func (ite *IfdTagEntry) setIsUnhandledUnknown(isUnhandledUnknown bool) {
   256  	ite.isUnhandledUnknown = isUnhandledUnknown
   257  }
   258  
   259  // SetChildIfd sets child-IFD information (if we represent a child IFD).
   260  func (ite *IfdTagEntry) SetChildIfd(ii *exifcommon.IfdIdentity) {
   261  	ite.childFqIfdPath = ii.String()
   262  	ite.childIfdPath = ii.UnindexedString()
   263  	ite.childIfdName = ii.Name()
   264  }
   265  
   266  // ChildIfdName returns the name of the child IFD
   267  func (ite *IfdTagEntry) ChildIfdName() string {
   268  	return ite.childIfdName
   269  }
   270  
   271  // ChildIfdPath returns the path of the child IFD.
   272  func (ite *IfdTagEntry) ChildIfdPath() string {
   273  	return ite.childIfdPath
   274  }
   275  
   276  // ChildFqIfdPath returns the complete path of the child IFD along with the
   277  // numeric suffixes differentiating sibling occurrences of the same type. "0"
   278  // indices are omitted.
   279  func (ite *IfdTagEntry) ChildFqIfdPath() string {
   280  	return ite.childFqIfdPath
   281  }
   282  
   283  // IfdIdentity returns the IfdIdentity associated with this tag.
   284  func (ite *IfdTagEntry) IfdIdentity() *exifcommon.IfdIdentity {
   285  	return ite.ifdIdentity
   286  }
   287  
   288  func (ite *IfdTagEntry) getValueContext() *exifcommon.ValueContext {
   289  	return exifcommon.NewValueContext(
   290  		ite.ifdIdentity.String(),
   291  		ite.tagId,
   292  		ite.unitCount,
   293  		ite.valueOffset,
   294  		ite.rawValueOffset,
   295  		ite.rs,
   296  		ite.tagType,
   297  		ite.byteOrder)
   298  }
   299  

View as plain text