photoprism-client-go/vendor/github.com/dsoprea/go-jpeg-image-structure/segment_list.go

396 lines
8.3 KiB
Go
Raw Normal View History

package jpegstructure
import (
"bytes"
"fmt"
"io"
"crypto/sha1"
"encoding/binary"
"github.com/dsoprea/go-exif/v2"
"github.com/dsoprea/go-iptc"
log "github.com/dsoprea/go-logging"
)
// SegmentList contains a slice of segments.
type SegmentList struct {
segments []*Segment
}
// NewSegmentList returns a new SegmentList struct.
func NewSegmentList(segments []*Segment) (sl *SegmentList) {
if segments == nil {
segments = make([]*Segment, 0)
}
return &SegmentList{
segments: segments,
}
}
// OffsetsEqual returns true is all segments have the same marker-IDs and were
// found at the same offsets.
func (sl *SegmentList) OffsetsEqual(o *SegmentList) bool {
if len(o.segments) != len(sl.segments) {
return false
}
for i, s := range o.segments {
if s.MarkerId != sl.segments[i].MarkerId || s.Offset != sl.segments[i].Offset {
return false
}
}
return true
}
// Segments returns the underlying slice of segments.
func (sl *SegmentList) Segments() []*Segment {
return sl.segments
}
// Add adds another segment.
func (sl *SegmentList) Add(s *Segment) {
sl.segments = append(sl.segments, s)
}
// Print prints segment info.
func (sl *SegmentList) Print() {
if len(sl.segments) == 0 {
fmt.Printf("No segments.\n")
} else {
exifIndex, _, err := sl.FindExif()
if err != nil {
if err == exif.ErrNoExif {
exifIndex = -1
} else {
log.Panic(err)
}
}
xmpIndex, _, err := sl.FindXmp()
if err != nil {
if err == ErrNoXmp {
xmpIndex = -1
} else {
log.Panic(err)
}
}
iptcIndex, _, err := sl.FindIptc()
if err != nil {
if err == ErrNoIptc {
iptcIndex = -1
} else {
log.Panic(err)
}
}
for i, s := range sl.segments {
fmt.Printf("%2d: %s", i, s.EmbeddedString())
if i == exifIndex {
fmt.Printf(" [EXIF]")
} else if i == xmpIndex {
fmt.Printf(" [XMP]")
} else if i == iptcIndex {
fmt.Printf(" [IPTC]")
}
fmt.Printf("\n")
}
}
}
// Validate checks that all of the markers are actually located at all of the
// recorded offsets.
func (sl *SegmentList) Validate(data []byte) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if len(sl.segments) < 2 {
log.Panicf("minimum segments not found")
}
if sl.segments[0].MarkerId != MARKER_SOI {
log.Panicf("first segment not SOI")
} else if sl.segments[len(sl.segments)-1].MarkerId != MARKER_EOI {
log.Panicf("last segment not EOI")
}
lastOffset := 0
for i, s := range sl.segments {
if lastOffset != 0 && s.Offset <= lastOffset {
log.Panicf("segment offset not greater than the last: SEGMENT=(%d) (0x%08x) <= (0x%08x)", i, s.Offset, lastOffset)
}
// The scan-data doesn't start with a marker.
if s.MarkerId == 0x0 {
continue
}
o := s.Offset
if bytes.Compare(data[o:o+2], []byte{0xff, s.MarkerId}) != 0 {
log.Panicf("segment offset does not point to the start of a segment: SEGMENT=(%d) (0x%08x)", i, s.Offset)
}
lastOffset = o
}
return nil
}
// FindExif returns the the segment that hosts the EXIF data (if present).
func (sl *SegmentList) FindExif() (index int, segment *Segment, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
for i, s := range sl.segments {
if s.IsExif() == true {
return i, s, nil
}
}
return -1, nil, exif.ErrNoExif
}
// FindXmp returns the the segment that hosts the XMP data (if present).
func (sl *SegmentList) FindXmp() (index int, segment *Segment, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
for i, s := range sl.segments {
if s.IsXmp() == true {
return i, s, nil
}
}
return -1, nil, ErrNoXmp
}
// FindIptc returns the the segment that hosts the IPTC data (if present).
func (sl *SegmentList) FindIptc() (index int, segment *Segment, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
for i, s := range sl.segments {
if s.IsIptc() == true {
return i, s, nil
}
}
return -1, nil, ErrNoIptc
}
// Exif returns an `exif.Ifd` instance for the EXIF data we currently have.
func (sl *SegmentList) Exif() (rootIfd *exif.Ifd, rawExif []byte, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
_, s, err := sl.FindExif()
log.PanicIf(err)
rootIfd, rawExif, err = s.Exif()
log.PanicIf(err)
return rootIfd, rawExif, nil
}
// Iptc returns embedded IPTC data if present.
func (sl *SegmentList) Iptc() (tags map[iptc.StreamTagKey][]iptc.TagData, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// TODO(dustin): Add comment and return data.
_, s, err := sl.FindIptc()
log.PanicIf(err)
tags, err = s.Iptc()
log.PanicIf(err)
return tags, nil
}
// ConstructExifBuilder returns an `exif.IfdBuilder` instance (needed for
// modifying) preloaded with all existing tags.
func (sl *SegmentList) ConstructExifBuilder() (rootIb *exif.IfdBuilder, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
rootIfd, _, err := sl.Exif()
log.PanicIf(err)
ib := exif.NewIfdBuilderFromExistingChain(rootIfd)
return ib, nil
}
// DumpExif returns an unstructured list of tags (useful when just reviewing).
func (sl *SegmentList) DumpExif() (segmentIndex int, segment *Segment, exifTags []exif.ExifTag, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
segmentIndex, s, err := sl.FindExif()
if err != nil {
if err == exif.ErrNoExif {
return 0, nil, nil, err
}
log.Panic(err)
}
exifTags, err = s.FlatExif()
log.PanicIf(err)
return segmentIndex, s, exifTags, nil
}
func makeEmptyExifSegment() (s *Segment) {
// TODO(dustin): Add test
return &Segment{
MarkerId: MARKER_APP1,
}
}
// SetExif encodes and sets EXIF data into the given segment. If `index` is -1,
// append a new segment.
func (sl *SegmentList) SetExif(ib *exif.IfdBuilder) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
_, s, err := sl.FindExif()
if err != nil {
if log.Is(err, exif.ErrNoExif) == false {
log.Panic(err)
}
s = makeEmptyExifSegment()
prefix := sl.segments[:1]
// Install it near the beginning where we know it's safe. We can't
// insert it after the EOI segment, and there might be more than one
// depending on implementation and/or lax adherence to the standard.
tail := append([]*Segment{s}, sl.segments[1:]...)
sl.segments = append(prefix, tail...)
}
err = s.SetExif(ib)
log.PanicIf(err)
return nil
}
// DropExif will drop the EXIF data if present.
func (sl *SegmentList) DropExif() (wasDropped bool, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
// TODO(dustin): Add test
i, _, err := sl.FindExif()
if err == nil {
// Found.
sl.segments = append(sl.segments[:i], sl.segments[i+1:]...)
return true, nil
} else if log.Is(err, exif.ErrNoExif) == false {
log.Panic(err)
}
// Not found.
return false, nil
}
// Write writes the segment data to the given `io.Writer`.
func (sl *SegmentList) Write(w io.Writer) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
offset := 0
for i, s := range sl.segments {
h := sha1.New()
h.Write(s.Data)
// The scan-data will have a marker-ID of (0) because it doesn't have a
// marker-ID or length.
if s.MarkerId != 0 {
_, err := w.Write([]byte{0xff})
log.PanicIf(err)
offset++
_, err = w.Write([]byte{s.MarkerId})
log.PanicIf(err)
offset++
sizeLen, found := markerLen[s.MarkerId]
if found == false || sizeLen == 2 {
sizeLen = 2
l := uint16(len(s.Data) + sizeLen)
err = binary.Write(w, binary.BigEndian, &l)
log.PanicIf(err)
offset += 2
} else if sizeLen == 4 {
l := uint32(len(s.Data) + sizeLen)
err = binary.Write(w, binary.BigEndian, &l)
log.PanicIf(err)
offset += 4
} else if sizeLen != 0 {
log.Panicf("not a supported marker-size: SEGMENT-INDEX=(%d) MARKER-ID=(0x%02x) MARKER-SIZE-LEN=(%d)", i, s.MarkerId, sizeLen)
}
}
_, err := w.Write(s.Data)
log.PanicIf(err)
offset += len(s.Data)
}
return nil
}