...

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

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

     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  // ExifTag is one simple representation of a tag in a flat list of all of them.
    20  type ExifTag struct {
    21  	// IfdPath is the fully-qualified IFD path (even though it is not named as
    22  	// such).
    23  	IfdPath string `json:"ifd_path"`
    24  
    25  	// TagId is the tag-ID.
    26  	TagId uint16 `json:"id"`
    27  
    28  	// TagName is the tag-name. This is never empty.
    29  	TagName string `json:"name"`
    30  
    31  	// UnitCount is the recorded number of units constution of the value.
    32  	UnitCount uint32 `json:"unit_count"`
    33  
    34  	// TagTypeId is the type-ID.
    35  	TagTypeId exifcommon.TagTypePrimitive `json:"type_id"`
    36  
    37  	// TagTypeName is the type name.
    38  	TagTypeName string `json:"type_name"`
    39  
    40  	// Value is the decoded value.
    41  	Value interface{} `json:"value"`
    42  
    43  	// ValueBytes is the raw, encoded value.
    44  	ValueBytes []byte `json:"value_bytes"`
    45  
    46  	// Formatted is the human representation of the first value (tag values are
    47  	// always an array).
    48  	FormattedFirst string `json:"formatted_first"`
    49  
    50  	// Formatted is the human representation of the complete value.
    51  	Formatted string `json:"formatted"`
    52  
    53  	// ChildIfdPath is the name of the child IFD this tag represents (if it
    54  	// represents any). Otherwise, this is empty.
    55  	ChildIfdPath string `json:"child_ifd_path"`
    56  }
    57  
    58  // String returns a string representation.
    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  // GetFlatExifData returns a simple, flat representation of all tags.
    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  // RELEASE(dustin): GetFlatExifDataUniversalSearch is a kludge to allow univeral tag searching in a backwards-compatible manner. For the next release, undo this and simply add the flag to GetFlatExifData.
    90  
    91  // GetFlatExifDataUniversalSearch returns a simple, flat representation of all
    92  // tags.
    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  // RELEASE(dustin): GetFlatExifDataUniversalSearchWithReadSeeker is a kludge to allow using a ReadSeeker in a backwards-compatible manner. For the next release, drop this and refactor GetFlatExifDataUniversalSearch to take a ReadSeeker.
   109  
   110  // GetFlatExifDataUniversalSearchWithReadSeeker returns a simple, flat
   111  // representation of all tags given a ReadSeeker.
   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  // getFlatExifDataUniversalSearchWithReadSeeker returns a simple, flat
   126  // representation of all tags given a ReadSeeker.
   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  		// This encodes down to base64. Since this an example tool and we do not
   162  		// expect to ever decode the output, we are not worried about
   163  		// specifically base64-encoding it in order to have a measure of
   164  		// control.
   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  // GpsDegreesEquals returns true if the two `GpsDegrees` are identical.
   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