mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-30 18:42:26 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			237 lines
		
	
	
	
		
			6.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			237 lines
		
	
	
	
		
			6.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package exif
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"math"
 | |
| 
 | |
| 	"github.com/dsoprea/go-logging"
 | |
| 	"github.com/dsoprea/go-utility/v2/filesystem"
 | |
| 
 | |
| 	"github.com/dsoprea/go-exif/v3/common"
 | |
| 	"github.com/dsoprea/go-exif/v3/undefined"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	utilityLogger = log.NewLogger("exif.utility")
 | |
| )
 | |
| 
 | |
| // ExifTag is one simple representation of a tag in a flat list of all of them.
 | |
| type ExifTag struct {
 | |
| 	// IfdPath is the fully-qualified IFD path (even though it is not named as
 | |
| 	// such).
 | |
| 	IfdPath string `json:"ifd_path"`
 | |
| 
 | |
| 	// TagId is the tag-ID.
 | |
| 	TagId uint16 `json:"id"`
 | |
| 
 | |
| 	// TagName is the tag-name. This is never empty.
 | |
| 	TagName string `json:"name"`
 | |
| 
 | |
| 	// UnitCount is the recorded number of units constution of the value.
 | |
| 	UnitCount uint32 `json:"unit_count"`
 | |
| 
 | |
| 	// TagTypeId is the type-ID.
 | |
| 	TagTypeId exifcommon.TagTypePrimitive `json:"type_id"`
 | |
| 
 | |
| 	// TagTypeName is the type name.
 | |
| 	TagTypeName string `json:"type_name"`
 | |
| 
 | |
| 	// Value is the decoded value.
 | |
| 	Value interface{} `json:"value"`
 | |
| 
 | |
| 	// ValueBytes is the raw, encoded value.
 | |
| 	ValueBytes []byte `json:"value_bytes"`
 | |
| 
 | |
| 	// Formatted is the human representation of the first value (tag values are
 | |
| 	// always an array).
 | |
| 	FormattedFirst string `json:"formatted_first"`
 | |
| 
 | |
| 	// Formatted is the human representation of the complete value.
 | |
| 	Formatted string `json:"formatted"`
 | |
| 
 | |
| 	// ChildIfdPath is the name of the child IFD this tag represents (if it
 | |
| 	// represents any). Otherwise, this is empty.
 | |
| 	ChildIfdPath string `json:"child_ifd_path"`
 | |
| }
 | |
| 
 | |
| // String returns a string representation.
 | |
| func (et ExifTag) String() string {
 | |
| 	return fmt.Sprintf(
 | |
| 		"ExifTag<"+
 | |
| 			"IFD-PATH=[%s] "+
 | |
| 			"TAG-ID=(0x%02x) "+
 | |
| 			"TAG-NAME=[%s] "+
 | |
| 			"TAG-TYPE=[%s] "+
 | |
| 			"VALUE=[%v] "+
 | |
| 			"VALUE-BYTES=(%d) "+
 | |
| 			"CHILD-IFD-PATH=[%s]",
 | |
| 		et.IfdPath, et.TagId, et.TagName, et.TagTypeName, et.FormattedFirst,
 | |
| 		len(et.ValueBytes), et.ChildIfdPath)
 | |
| }
 | |
| 
 | |
| // GetFlatExifData returns a simple, flat representation of all tags.
 | |
| func GetFlatExifData(exifData []byte, so *ScanOptions) (exifTags []ExifTag, med *MiscellaneousExifData, err error) {
 | |
| 	defer func() {
 | |
| 		if state := recover(); state != nil {
 | |
| 			err = log.Wrap(state.(error))
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	sb := rifs.NewSeekableBufferWithBytes(exifData)
 | |
| 
 | |
| 	exifTags, med, err = getFlatExifDataUniversalSearchWithReadSeeker(sb, so, false)
 | |
| 	log.PanicIf(err)
 | |
| 
 | |
| 	return exifTags, med, nil
 | |
| }
 | |
| 
 | |
| // 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.
 | |
| 
 | |
| // GetFlatExifDataUniversalSearch returns a simple, flat representation of all
 | |
| // tags.
 | |
| func GetFlatExifDataUniversalSearch(exifData []byte, so *ScanOptions, doUniversalSearch bool) (exifTags []ExifTag, med *MiscellaneousExifData, err error) {
 | |
| 	defer func() {
 | |
| 		if state := recover(); state != nil {
 | |
| 			err = log.Wrap(state.(error))
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	sb := rifs.NewSeekableBufferWithBytes(exifData)
 | |
| 
 | |
| 	exifTags, med, err = getFlatExifDataUniversalSearchWithReadSeeker(sb, so, doUniversalSearch)
 | |
| 	log.PanicIf(err)
 | |
| 
 | |
| 	return exifTags, med, nil
 | |
| }
 | |
| 
 | |
| // 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.
 | |
| 
 | |
| // GetFlatExifDataUniversalSearchWithReadSeeker returns a simple, flat
 | |
| // representation of all tags given a ReadSeeker.
 | |
| func GetFlatExifDataUniversalSearchWithReadSeeker(rs io.ReadSeeker, so *ScanOptions, doUniversalSearch bool) (exifTags []ExifTag, med *MiscellaneousExifData, err error) {
 | |
| 	defer func() {
 | |
| 		if state := recover(); state != nil {
 | |
| 			err = log.Wrap(state.(error))
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	exifTags, med, err = getFlatExifDataUniversalSearchWithReadSeeker(rs, so, doUniversalSearch)
 | |
| 	log.PanicIf(err)
 | |
| 
 | |
| 	return exifTags, med, nil
 | |
| }
 | |
| 
 | |
| // getFlatExifDataUniversalSearchWithReadSeeker returns a simple, flat
 | |
| // representation of all tags given a ReadSeeker.
 | |
| func getFlatExifDataUniversalSearchWithReadSeeker(rs io.ReadSeeker, so *ScanOptions, doUniversalSearch bool) (exifTags []ExifTag, med *MiscellaneousExifData, err error) {
 | |
| 	defer func() {
 | |
| 		if state := recover(); state != nil {
 | |
| 			err = log.Wrap(state.(error))
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	headerData := make([]byte, ExifSignatureLength)
 | |
| 	if _, err = io.ReadFull(rs, headerData); err != nil {
 | |
| 		if err == io.EOF {
 | |
| 			return nil, nil, err
 | |
| 		}
 | |
| 
 | |
| 		log.Panic(err)
 | |
| 	}
 | |
| 
 | |
| 	eh, err := ParseExifHeader(headerData)
 | |
| 	log.PanicIf(err)
 | |
| 
 | |
| 	im, err := exifcommon.NewIfdMappingWithStandard()
 | |
| 	log.PanicIf(err)
 | |
| 
 | |
| 	ti := NewTagIndex()
 | |
| 
 | |
| 	if doUniversalSearch == true {
 | |
| 		ti.SetUniversalSearch(true)
 | |
| 	}
 | |
| 
 | |
| 	ebs := NewExifReadSeeker(rs)
 | |
| 	ie := NewIfdEnumerate(im, ti, ebs, eh.ByteOrder)
 | |
| 
 | |
| 	exifTags = make([]ExifTag, 0)
 | |
| 
 | |
| 	visitor := func(ite *IfdTagEntry) (err error) {
 | |
| 		// This encodes down to base64. Since this an example tool and we do not
 | |
| 		// expect to ever decode the output, we are not worried about
 | |
| 		// specifically base64-encoding it in order to have a measure of
 | |
| 		// control.
 | |
| 		valueBytes, err := ite.GetRawBytes()
 | |
| 		if err != nil {
 | |
| 			if err == exifundefined.ErrUnparseableValue {
 | |
| 				return nil
 | |
| 			}
 | |
| 
 | |
| 			log.Panic(err)
 | |
| 		}
 | |
| 
 | |
| 		value, err := ite.Value()
 | |
| 		if err != nil {
 | |
| 			if err == exifcommon.ErrUnhandledUndefinedTypedTag {
 | |
| 				value = exifundefined.UnparseableUnknownTagValuePlaceholder
 | |
| 			} else if log.Is(err, exifcommon.ErrParseFail) == true {
 | |
| 				utilityLogger.Warningf(nil,
 | |
| 					"Could not parse value for tag [%s] (%04x) [%s].",
 | |
| 					ite.IfdPath(), ite.TagId(), ite.TagName())
 | |
| 
 | |
| 				return nil
 | |
| 			} else {
 | |
| 				log.Panic(err)
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		et := ExifTag{
 | |
| 			IfdPath:      ite.IfdPath(),
 | |
| 			TagId:        ite.TagId(),
 | |
| 			TagName:      ite.TagName(),
 | |
| 			UnitCount:    ite.UnitCount(),
 | |
| 			TagTypeId:    ite.TagType(),
 | |
| 			TagTypeName:  ite.TagType().String(),
 | |
| 			Value:        value,
 | |
| 			ValueBytes:   valueBytes,
 | |
| 			ChildIfdPath: ite.ChildIfdPath(),
 | |
| 		}
 | |
| 
 | |
| 		et.Formatted, err = ite.Format()
 | |
| 		log.PanicIf(err)
 | |
| 
 | |
| 		et.FormattedFirst, err = ite.FormatFirst()
 | |
| 		log.PanicIf(err)
 | |
| 
 | |
| 		exifTags = append(exifTags, et)
 | |
| 
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	med, err = ie.Scan(exifcommon.IfdStandardIfdIdentity, eh.FirstIfdOffset, visitor, nil)
 | |
| 	log.PanicIf(err)
 | |
| 
 | |
| 	return exifTags, med, nil
 | |
| }
 | |
| 
 | |
| // GpsDegreesEquals returns true if the two `GpsDegrees` are identical.
 | |
| func GpsDegreesEquals(gi1, gi2 GpsDegrees) bool {
 | |
| 	if gi2.Orientation != gi1.Orientation {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	degreesRightBound := math.Nextafter(gi1.Degrees, gi1.Degrees+1)
 | |
| 	minutesRightBound := math.Nextafter(gi1.Minutes, gi1.Minutes+1)
 | |
| 	secondsRightBound := math.Nextafter(gi1.Seconds, gi1.Seconds+1)
 | |
| 
 | |
| 	if gi2.Degrees < gi1.Degrees || gi2.Degrees >= degreesRightBound {
 | |
| 		return false
 | |
| 	} else if gi2.Minutes < gi1.Minutes || gi2.Minutes >= minutesRightBound {
 | |
| 		return false
 | |
| 	} else if gi2.Seconds < gi1.Seconds || gi2.Seconds >= secondsRightBound {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	return true
 | |
| }
 |