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 )