mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 06:42:25 -05:00 
			
		
		
		
	use exif-terminator
This commit is contained in:
		
					parent
					
						
							
								589bb9df02
							
						
					
				
			
			
				commit
				
					
						7d024ce74d
					
				
			
		
					 117 changed files with 3873 additions and 8725 deletions
				
			
		
							
								
								
									
										352
									
								
								vendor/github.com/dsoprea/go-jpeg-image-structure/v2/segment.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										352
									
								
								vendor/github.com/dsoprea/go-jpeg-image-structure/v2/segment.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,352 @@ | |||
| package jpegstructure | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	"crypto/sha1" | ||||
| 	"encoding/hex" | ||||
| 
 | ||||
| 	"github.com/dsoprea/go-exif/v3" | ||||
| 	"github.com/dsoprea/go-exif/v3/common" | ||||
| 	"github.com/dsoprea/go-iptc" | ||||
| 	"github.com/dsoprea/go-logging" | ||||
| 	"github.com/dsoprea/go-photoshop-info-format" | ||||
| 	"github.com/dsoprea/go-utility/v2/image" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	pirIptcImageResourceId = uint16(0x0404) | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	// exifPrefix is the prefix found at the top of an EXIF slice. This is JPEG- | ||||
| 	// specific. | ||||
| 	exifPrefix = []byte{'E', 'x', 'i', 'f', 0, 0} | ||||
| 
 | ||||
| 	xmpPrefix = []byte("http://ns.adobe.com/xap/1.0/\000") | ||||
| 
 | ||||
| 	ps30Prefix = []byte("Photoshop 3.0\000") | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	// ErrNoXmp is returned if XMP data was requested but not found. | ||||
| 	ErrNoXmp = errors.New("no XMP data") | ||||
| 
 | ||||
| 	// ErrNoIptc is returned if IPTC data was requested but not found. | ||||
| 	ErrNoIptc = errors.New("no IPTC data") | ||||
| 
 | ||||
| 	// ErrNoPhotoshopData is returned if Photoshop info was requested but not | ||||
| 	// found. | ||||
| 	ErrNoPhotoshopData = errors.New("no photoshop data") | ||||
| ) | ||||
| 
 | ||||
| // SofSegment has info read from a SOF segment. | ||||
| type SofSegment struct { | ||||
| 	// BitsPerSample is the bits-per-sample. | ||||
| 	BitsPerSample byte | ||||
| 
 | ||||
| 	// Width is the image width. | ||||
| 	Width uint16 | ||||
| 
 | ||||
| 	// Height is the image height. | ||||
| 	Height uint16 | ||||
| 
 | ||||
| 	// ComponentCount is the number of color components. | ||||
| 	ComponentCount byte | ||||
| } | ||||
| 
 | ||||
| // String returns a string representation of the SOF segment. | ||||
| func (ss SofSegment) String() string { | ||||
| 
 | ||||
| 	// TODO(dustin): Add test | ||||
| 
 | ||||
| 	return fmt.Sprintf("SOF<BitsPerSample=(%d) Width=(%d) Height=(%d) ComponentCount=(%d)>", ss.BitsPerSample, ss.Width, ss.Height, ss.ComponentCount) | ||||
| } | ||||
| 
 | ||||
| // SegmentVisitor describes a segment-visitor struct. | ||||
| type SegmentVisitor interface { | ||||
| 	// HandleSegment is triggered for each segment encountered as well as the | ||||
| 	// scan-data. | ||||
| 	HandleSegment(markerId byte, markerName string, counter int, lastIsScanData bool) error | ||||
| } | ||||
| 
 | ||||
| // SofSegmentVisitor describes a visitor that is only called for each SOF | ||||
| // segment. | ||||
| type SofSegmentVisitor interface { | ||||
| 	// HandleSof is called for each encountered SOF segment. | ||||
| 	HandleSof(sof *SofSegment) error | ||||
| } | ||||
| 
 | ||||
| // Segment describes a single segment. | ||||
| type Segment struct { | ||||
| 	MarkerId   byte | ||||
| 	MarkerName string | ||||
| 	Offset     int | ||||
| 	Data       []byte | ||||
| 
 | ||||
| 	photoshopInfo map[uint16]photoshopinfo.Photoshop30InfoRecord | ||||
| 	iptcTags      map[iptc.StreamTagKey][]iptc.TagData | ||||
| } | ||||
| 
 | ||||
| // SetExif encodes and sets EXIF data into this segment. | ||||
| func (s *Segment) SetExif(ib *exif.IfdBuilder) (err error) { | ||||
| 	defer func() { | ||||
| 		if state := recover(); state != nil { | ||||
| 			err = log.Wrap(state.(error)) | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	ibe := exif.NewIfdByteEncoder() | ||||
| 
 | ||||
| 	exifData, err := ibe.EncodeToExif(ib) | ||||
| 	log.PanicIf(err) | ||||
| 
 | ||||
| 	l := len(exifPrefix) | ||||
| 
 | ||||
| 	s.Data = make([]byte, l+len(exifData)) | ||||
| 	copy(s.Data[0:], exifPrefix) | ||||
| 	copy(s.Data[l:], exifData) | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Exif returns an `exif.Ifd` instance for the EXIF data we currently have. | ||||
| func (s *Segment) Exif() (rootIfd *exif.Ifd, data []byte, err error) { | ||||
| 	defer func() { | ||||
| 		if state := recover(); state != nil { | ||||
| 			err = log.Wrap(state.(error)) | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	l := len(exifPrefix) | ||||
| 
 | ||||
| 	rawExif := s.Data[l:] | ||||
| 
 | ||||
| 	jpegLogger.Debugf(nil, "Attempting to parse (%d) byte EXIF blob (Exif).", len(rawExif)) | ||||
| 
 | ||||
| 	im, err := exifcommon.NewIfdMappingWithStandard() | ||||
| 	log.PanicIf(err) | ||||
| 
 | ||||
| 	ti := exif.NewTagIndex() | ||||
| 
 | ||||
| 	_, index, err := exif.Collect(im, ti, rawExif) | ||||
| 	log.PanicIf(err) | ||||
| 
 | ||||
| 	return index.RootIfd, rawExif, nil | ||||
| } | ||||
| 
 | ||||
| // FlatExif parses the EXIF data and just returns a list of tags. | ||||
| func (s *Segment) FlatExif() (exifTags []exif.ExifTag, err error) { | ||||
| 	defer func() { | ||||
| 		if state := recover(); state != nil { | ||||
| 			err = log.Wrap(state.(error)) | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	// TODO(dustin): Add test | ||||
| 
 | ||||
| 	l := len(exifPrefix) | ||||
| 
 | ||||
| 	rawExif := s.Data[l:] | ||||
| 
 | ||||
| 	jpegLogger.Debugf(nil, "Attempting to parse (%d) byte EXIF blob (FlatExif).", len(rawExif)) | ||||
| 
 | ||||
| 	exifTags, _, err = exif.GetFlatExifData(rawExif, nil) | ||||
| 	log.PanicIf(err) | ||||
| 
 | ||||
| 	return exifTags, nil | ||||
| } | ||||
| 
 | ||||
| // EmbeddedString returns a string of properties that can be embedded into an | ||||
| // longer string of properties. | ||||
| func (s *Segment) EmbeddedString() string { | ||||
| 	h := sha1.New() | ||||
| 	h.Write(s.Data) | ||||
| 
 | ||||
| 	// TODO(dustin): Add test | ||||
| 
 | ||||
| 	digestString := hex.EncodeToString(h.Sum(nil)) | ||||
| 
 | ||||
| 	return fmt.Sprintf("OFFSET=(0x%08x %10d) ID=(0x%02x) NAME=[%-5s] SIZE=(%10d) SHA1=[%s]", s.Offset, s.Offset, s.MarkerId, markerNames[s.MarkerId], len(s.Data), digestString) | ||||
| } | ||||
| 
 | ||||
| // String returns a descriptive string. | ||||
| func (s *Segment) String() string { | ||||
| 
 | ||||
| 	// TODO(dustin): Add test | ||||
| 
 | ||||
| 	return fmt.Sprintf("Segment<%s>", s.EmbeddedString()) | ||||
| } | ||||
| 
 | ||||
| // IsExif returns true if EXIF data. | ||||
| func (s *Segment) IsExif() bool { | ||||
| 	if s.MarkerId != MARKER_APP1 { | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	// TODO(dustin): Add test | ||||
| 
 | ||||
| 	l := len(exifPrefix) | ||||
| 
 | ||||
| 	if len(s.Data) < l { | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	if bytes.Equal(s.Data[:l], exifPrefix) == false { | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| // IsXmp returns true if XMP data. | ||||
| func (s *Segment) IsXmp() bool { | ||||
| 	if s.MarkerId != MARKER_APP1 { | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	// TODO(dustin): Add test | ||||
| 
 | ||||
| 	l := len(xmpPrefix) | ||||
| 
 | ||||
| 	if len(s.Data) < l { | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	if bytes.Equal(s.Data[:l], xmpPrefix) == false { | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| // FormattedXmp returns a formatted XML string. This only makes sense for a | ||||
| // segment comprised of XML data (like XMP). | ||||
| func (s *Segment) FormattedXmp() (formatted string, err error) { | ||||
| 	defer func() { | ||||
| 		if state := recover(); state != nil { | ||||
| 			err = log.Wrap(state.(error)) | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	// TODO(dustin): Add test | ||||
| 
 | ||||
| 	if s.IsXmp() != true { | ||||
| 		log.Panicf("not an XMP segment") | ||||
| 	} | ||||
| 
 | ||||
| 	l := len(xmpPrefix) | ||||
| 
 | ||||
| 	raw := string(s.Data[l:]) | ||||
| 
 | ||||
| 	formatted, err = FormatXml(raw) | ||||
| 	log.PanicIf(err) | ||||
| 
 | ||||
| 	return formatted, nil | ||||
| } | ||||
| 
 | ||||
| func (s *Segment) parsePhotoshopInfo() (photoshopInfo map[uint16]photoshopinfo.Photoshop30InfoRecord, err error) { | ||||
| 	defer func() { | ||||
| 		if state := recover(); state != nil { | ||||
| 			err = log.Wrap(state.(error)) | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	if s.photoshopInfo != nil { | ||||
| 		return s.photoshopInfo, nil | ||||
| 	} | ||||
| 
 | ||||
| 	if s.MarkerId != MARKER_APP13 { | ||||
| 		return nil, ErrNoPhotoshopData | ||||
| 	} | ||||
| 
 | ||||
| 	l := len(ps30Prefix) | ||||
| 
 | ||||
| 	if len(s.Data) < l { | ||||
| 		return nil, ErrNoPhotoshopData | ||||
| 	} | ||||
| 
 | ||||
| 	if bytes.Equal(s.Data[:l], ps30Prefix) == false { | ||||
| 		return nil, ErrNoPhotoshopData | ||||
| 	} | ||||
| 
 | ||||
| 	data := s.Data[l:] | ||||
| 	b := bytes.NewBuffer(data) | ||||
| 
 | ||||
| 	// Parse it. | ||||
| 
 | ||||
| 	pirIndex, err := photoshopinfo.ReadPhotoshop30Info(b) | ||||
| 	log.PanicIf(err) | ||||
| 
 | ||||
| 	s.photoshopInfo = pirIndex | ||||
| 
 | ||||
| 	return s.photoshopInfo, nil | ||||
| } | ||||
| 
 | ||||
| // IsIptc returns true if XMP data. | ||||
| func (s *Segment) IsIptc() bool { | ||||
| 	// TODO(dustin): Add test | ||||
| 
 | ||||
| 	// There's a cost to determining if there's IPTC data, so we won't do it | ||||
| 	// more than once. | ||||
| 	if s.iptcTags != nil { | ||||
| 		return true | ||||
| 	} | ||||
| 
 | ||||
| 	photoshopInfo, err := s.parsePhotoshopInfo() | ||||
| 	if err != nil { | ||||
| 		if err == ErrNoPhotoshopData { | ||||
| 			return false | ||||
| 		} | ||||
| 
 | ||||
| 		log.Panic(err) | ||||
| 	} | ||||
| 
 | ||||
| 	// Bail if the Photoshop info doesn't have IPTC data. | ||||
| 
 | ||||
| 	_, found := photoshopInfo[pirIptcImageResourceId] | ||||
| 	if found == false { | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| // Iptc parses Photoshop info (if present) and then parses the IPTC info inside | ||||
| // it (if present). | ||||
| func (s *Segment) Iptc() (tags map[iptc.StreamTagKey][]iptc.TagData, err error) { | ||||
| 	defer func() { | ||||
| 		if state := recover(); state != nil { | ||||
| 			err = log.Wrap(state.(error)) | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	// Cache the parse. | ||||
| 	if s.iptcTags != nil { | ||||
| 		return s.iptcTags, nil | ||||
| 	} | ||||
| 
 | ||||
| 	photoshopInfo, err := s.parsePhotoshopInfo() | ||||
| 	log.PanicIf(err) | ||||
| 
 | ||||
| 	iptcPir, found := photoshopInfo[pirIptcImageResourceId] | ||||
| 	if found == false { | ||||
| 		return nil, ErrNoIptc | ||||
| 	} | ||||
| 
 | ||||
| 	b := bytes.NewBuffer(iptcPir.Data) | ||||
| 
 | ||||
| 	tags, err = iptc.ParseStream(b) | ||||
| 	log.PanicIf(err) | ||||
| 
 | ||||
| 	s.iptcTags = tags | ||||
| 
 | ||||
| 	return tags, nil | ||||
| } | ||||
| 
 | ||||
| var ( | ||||
| 	// Enforce interface conformance. | ||||
| 	_ riimage.MediaContext = new(Segment) | ||||
| ) | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue