gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

segment.go (7531B)


      1 package jpegstructure
      2 
      3 import (
      4 	"bytes"
      5 	"errors"
      6 	"fmt"
      7 
      8 	"crypto/sha1"
      9 	"encoding/hex"
     10 
     11 	"github.com/dsoprea/go-exif/v3"
     12 	"github.com/dsoprea/go-exif/v3/common"
     13 	"github.com/dsoprea/go-iptc"
     14 	"github.com/dsoprea/go-logging"
     15 	"github.com/dsoprea/go-photoshop-info-format"
     16 	"github.com/dsoprea/go-utility/v2/image"
     17 )
     18 
     19 const (
     20 	pirIptcImageResourceId = uint16(0x0404)
     21 )
     22 
     23 var (
     24 	// exifPrefix is the prefix found at the top of an EXIF slice. This is JPEG-
     25 	// specific.
     26 	exifPrefix = []byte{'E', 'x', 'i', 'f', 0, 0}
     27 
     28 	xmpPrefix = []byte("http://ns.adobe.com/xap/1.0/\000")
     29 
     30 	ps30Prefix = []byte("Photoshop 3.0\000")
     31 )
     32 
     33 var (
     34 	// ErrNoXmp is returned if XMP data was requested but not found.
     35 	ErrNoXmp = errors.New("no XMP data")
     36 
     37 	// ErrNoIptc is returned if IPTC data was requested but not found.
     38 	ErrNoIptc = errors.New("no IPTC data")
     39 
     40 	// ErrNoPhotoshopData is returned if Photoshop info was requested but not
     41 	// found.
     42 	ErrNoPhotoshopData = errors.New("no photoshop data")
     43 )
     44 
     45 // SofSegment has info read from a SOF segment.
     46 type SofSegment struct {
     47 	// BitsPerSample is the bits-per-sample.
     48 	BitsPerSample byte
     49 
     50 	// Width is the image width.
     51 	Width uint16
     52 
     53 	// Height is the image height.
     54 	Height uint16
     55 
     56 	// ComponentCount is the number of color components.
     57 	ComponentCount byte
     58 }
     59 
     60 // String returns a string representation of the SOF segment.
     61 func (ss SofSegment) String() string {
     62 
     63 	// TODO(dustin): Add test
     64 
     65 	return fmt.Sprintf("SOF<BitsPerSample=(%d) Width=(%d) Height=(%d) ComponentCount=(%d)>", ss.BitsPerSample, ss.Width, ss.Height, ss.ComponentCount)
     66 }
     67 
     68 // SegmentVisitor describes a segment-visitor struct.
     69 type SegmentVisitor interface {
     70 	// HandleSegment is triggered for each segment encountered as well as the
     71 	// scan-data.
     72 	HandleSegment(markerId byte, markerName string, counter int, lastIsScanData bool) error
     73 }
     74 
     75 // SofSegmentVisitor describes a visitor that is only called for each SOF
     76 // segment.
     77 type SofSegmentVisitor interface {
     78 	// HandleSof is called for each encountered SOF segment.
     79 	HandleSof(sof *SofSegment) error
     80 }
     81 
     82 // Segment describes a single segment.
     83 type Segment struct {
     84 	MarkerId   byte
     85 	MarkerName string
     86 	Offset     int
     87 	Data       []byte
     88 
     89 	photoshopInfo map[uint16]photoshopinfo.Photoshop30InfoRecord
     90 	iptcTags      map[iptc.StreamTagKey][]iptc.TagData
     91 }
     92 
     93 // SetExif encodes and sets EXIF data into this segment.
     94 func (s *Segment) SetExif(ib *exif.IfdBuilder) (err error) {
     95 	defer func() {
     96 		if state := recover(); state != nil {
     97 			err = log.Wrap(state.(error))
     98 		}
     99 	}()
    100 
    101 	ibe := exif.NewIfdByteEncoder()
    102 
    103 	exifData, err := ibe.EncodeToExif(ib)
    104 	log.PanicIf(err)
    105 
    106 	l := len(exifPrefix)
    107 
    108 	s.Data = make([]byte, l+len(exifData))
    109 	copy(s.Data[0:], exifPrefix)
    110 	copy(s.Data[l:], exifData)
    111 
    112 	return nil
    113 }
    114 
    115 // Exif returns an `exif.Ifd` instance for the EXIF data we currently have.
    116 func (s *Segment) Exif() (rootIfd *exif.Ifd, data []byte, err error) {
    117 	defer func() {
    118 		if state := recover(); state != nil {
    119 			err = log.Wrap(state.(error))
    120 		}
    121 	}()
    122 
    123 	l := len(exifPrefix)
    124 
    125 	rawExif := s.Data[l:]
    126 
    127 	jpegLogger.Debugf(nil, "Attempting to parse (%d) byte EXIF blob (Exif).", len(rawExif))
    128 
    129 	im, err := exifcommon.NewIfdMappingWithStandard()
    130 	log.PanicIf(err)
    131 
    132 	ti := exif.NewTagIndex()
    133 
    134 	_, index, err := exif.Collect(im, ti, rawExif)
    135 	log.PanicIf(err)
    136 
    137 	return index.RootIfd, rawExif, nil
    138 }
    139 
    140 // FlatExif parses the EXIF data and just returns a list of tags.
    141 func (s *Segment) FlatExif() (exifTags []exif.ExifTag, err error) {
    142 	defer func() {
    143 		if state := recover(); state != nil {
    144 			err = log.Wrap(state.(error))
    145 		}
    146 	}()
    147 
    148 	// TODO(dustin): Add test
    149 
    150 	l := len(exifPrefix)
    151 
    152 	rawExif := s.Data[l:]
    153 
    154 	jpegLogger.Debugf(nil, "Attempting to parse (%d) byte EXIF blob (FlatExif).", len(rawExif))
    155 
    156 	exifTags, _, err = exif.GetFlatExifData(rawExif, nil)
    157 	log.PanicIf(err)
    158 
    159 	return exifTags, nil
    160 }
    161 
    162 // EmbeddedString returns a string of properties that can be embedded into an
    163 // longer string of properties.
    164 func (s *Segment) EmbeddedString() string {
    165 	h := sha1.New()
    166 	h.Write(s.Data)
    167 
    168 	// TODO(dustin): Add test
    169 
    170 	digestString := hex.EncodeToString(h.Sum(nil))
    171 
    172 	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)
    173 }
    174 
    175 // String returns a descriptive string.
    176 func (s *Segment) String() string {
    177 
    178 	// TODO(dustin): Add test
    179 
    180 	return fmt.Sprintf("Segment<%s>", s.EmbeddedString())
    181 }
    182 
    183 // IsExif returns true if EXIF data.
    184 func (s *Segment) IsExif() bool {
    185 	if s.MarkerId != MARKER_APP1 {
    186 		return false
    187 	}
    188 
    189 	// TODO(dustin): Add test
    190 
    191 	l := len(exifPrefix)
    192 
    193 	if len(s.Data) < l {
    194 		return false
    195 	}
    196 
    197 	if bytes.Equal(s.Data[:l], exifPrefix) == false {
    198 		return false
    199 	}
    200 
    201 	return true
    202 }
    203 
    204 // IsXmp returns true if XMP data.
    205 func (s *Segment) IsXmp() bool {
    206 	if s.MarkerId != MARKER_APP1 {
    207 		return false
    208 	}
    209 
    210 	// TODO(dustin): Add test
    211 
    212 	l := len(xmpPrefix)
    213 
    214 	if len(s.Data) < l {
    215 		return false
    216 	}
    217 
    218 	if bytes.Equal(s.Data[:l], xmpPrefix) == false {
    219 		return false
    220 	}
    221 
    222 	return true
    223 }
    224 
    225 // FormattedXmp returns a formatted XML string. This only makes sense for a
    226 // segment comprised of XML data (like XMP).
    227 func (s *Segment) FormattedXmp() (formatted string, err error) {
    228 	defer func() {
    229 		if state := recover(); state != nil {
    230 			err = log.Wrap(state.(error))
    231 		}
    232 	}()
    233 
    234 	// TODO(dustin): Add test
    235 
    236 	if s.IsXmp() != true {
    237 		log.Panicf("not an XMP segment")
    238 	}
    239 
    240 	l := len(xmpPrefix)
    241 
    242 	raw := string(s.Data[l:])
    243 
    244 	formatted, err = FormatXml(raw)
    245 	log.PanicIf(err)
    246 
    247 	return formatted, nil
    248 }
    249 
    250 func (s *Segment) parsePhotoshopInfo() (photoshopInfo map[uint16]photoshopinfo.Photoshop30InfoRecord, err error) {
    251 	defer func() {
    252 		if state := recover(); state != nil {
    253 			err = log.Wrap(state.(error))
    254 		}
    255 	}()
    256 
    257 	if s.photoshopInfo != nil {
    258 		return s.photoshopInfo, nil
    259 	}
    260 
    261 	if s.MarkerId != MARKER_APP13 {
    262 		return nil, ErrNoPhotoshopData
    263 	}
    264 
    265 	l := len(ps30Prefix)
    266 
    267 	if len(s.Data) < l {
    268 		return nil, ErrNoPhotoshopData
    269 	}
    270 
    271 	if bytes.Equal(s.Data[:l], ps30Prefix) == false {
    272 		return nil, ErrNoPhotoshopData
    273 	}
    274 
    275 	data := s.Data[l:]
    276 	b := bytes.NewBuffer(data)
    277 
    278 	// Parse it.
    279 
    280 	pirIndex, err := photoshopinfo.ReadPhotoshop30Info(b)
    281 	log.PanicIf(err)
    282 
    283 	s.photoshopInfo = pirIndex
    284 
    285 	return s.photoshopInfo, nil
    286 }
    287 
    288 // IsIptc returns true if XMP data.
    289 func (s *Segment) IsIptc() bool {
    290 	// TODO(dustin): Add test
    291 
    292 	// There's a cost to determining if there's IPTC data, so we won't do it
    293 	// more than once.
    294 	if s.iptcTags != nil {
    295 		return true
    296 	}
    297 
    298 	photoshopInfo, err := s.parsePhotoshopInfo()
    299 	if err != nil {
    300 		if err == ErrNoPhotoshopData {
    301 			return false
    302 		}
    303 
    304 		log.Panic(err)
    305 	}
    306 
    307 	// Bail if the Photoshop info doesn't have IPTC data.
    308 
    309 	_, found := photoshopInfo[pirIptcImageResourceId]
    310 	if found == false {
    311 		return false
    312 	}
    313 
    314 	return true
    315 }
    316 
    317 // Iptc parses Photoshop info (if present) and then parses the IPTC info inside
    318 // it (if present).
    319 func (s *Segment) Iptc() (tags map[iptc.StreamTagKey][]iptc.TagData, err error) {
    320 	defer func() {
    321 		if state := recover(); state != nil {
    322 			err = log.Wrap(state.(error))
    323 		}
    324 	}()
    325 
    326 	// Cache the parse.
    327 	if s.iptcTags != nil {
    328 		return s.iptcTags, nil
    329 	}
    330 
    331 	photoshopInfo, err := s.parsePhotoshopInfo()
    332 	log.PanicIf(err)
    333 
    334 	iptcPir, found := photoshopInfo[pirIptcImageResourceId]
    335 	if found == false {
    336 		return nil, ErrNoIptc
    337 	}
    338 
    339 	b := bytes.NewBuffer(iptcPir.Data)
    340 
    341 	tags, err = iptc.ParseStream(b)
    342 	log.PanicIf(err)
    343 
    344 	s.iptcTags = tags
    345 
    346 	return tags, nil
    347 }
    348 
    349 var (
    350 	// Enforce interface conformance.
    351 	_ riimage.MediaContext = new(Segment)
    352 )