mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
use exif-terminator
This commit is contained in:
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)
|
||||
)
|
Reference in New Issue
Block a user