Skip to content

Commit

Permalink
Add HandleXMP option
Browse files Browse the repository at this point in the history
  • Loading branch information
bep committed Aug 15, 2023
1 parent bd85aee commit 80f37b9
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 20 deletions.
2 changes: 1 addition & 1 deletion imagedecoder_jpg.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func (e *imageDecoderJPEG) decode() error {
e.skip(int64(xmpIDLen))
length -= xmpIDLen
r := io.LimitReader(e.r, int64(length))
if err := decodeXMP(r, e.opts.HandleTag); err != nil {
if err := decodeXMP(r, e.opts); err != nil {
return err
}
continue
Expand Down
2 changes: 1 addition & 1 deletion imagedecoder_tif.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func (e *imageDecoderTIF) decode() error {
pos := e.pos()
e.seek(int(valueOffset))
r := io.LimitReader(e.r, int64(count))
if err := decodeXMP(r, e.opts.HandleTag); err != nil {
if err := decodeXMP(r, e.opts); err != nil {
return err
}
sourceSet = sourceSet.Remove(TagSourceXMP)
Expand Down
18 changes: 5 additions & 13 deletions imagedecoder_webp.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,8 @@ func (e *decoderWebP) decode() error {

chunkLen := e.read4()

switch chunkID {

case fccVP8X:
switch {
case chunkID == fccVP8X:
if chunkLen != 10 {
return ErrInvalidFormat
}
Expand All @@ -103,24 +102,17 @@ func (e *decoderWebP) decode() error {
if !hasEXIF && !hasXMP {
return nil
}
case fccEXIF:
if !sourceSet.Has(TagSourceEXIF) {
continue
}
case chunkID == fccEXIF && sourceSet.Has(TagSourceEXIF):
r := io.LimitReader(e.r, int64(chunkLen))
dec := newMetaDecoderEXIF(r, e.opts.HandleTag)

if err := dec.decode(); err != nil {
return err
}
sourceSet = sourceSet.Remove(TagSourceEXIF)
case fccXMP:
if !sourceSet.Has(TagSourceXMP) {
continue
}
case chunkID == fccXMP && sourceSet.Has(TagSourceXMP):
sourceSet = sourceSet.Remove(TagSourceXMP)
r := io.LimitReader(e.r, int64(chunkLen))
if err := decodeXMP(r, e.opts.HandleTag); err != nil {
if err := decodeXMP(r, e.opts); err != nil {
return err
}
default:
Expand Down
12 changes: 9 additions & 3 deletions imagemeta.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ func Decode(opts Options) (err error) {
if opts.R == nil {
return fmt.Errorf("need a reader")
}
if opts.HandleTag == nil {
return fmt.Errorf("need a HandleTag function")
}
if opts.ImageFormat == ImageFormatAuto {
return fmt.Errorf("need an image format; format detection not implemented yet")
}
if opts.HandleTag == nil {
opts.HandleTag = func(TagInfo) error { return nil }
}
if opts.Sources == 0 {
opts.Sources = TagSourceEXIF | TagSourceIPTC | TagSourceXMP
}
Expand Down Expand Up @@ -118,6 +118,12 @@ type Options struct {
// The function to call for each tag.
HandleTag HandleTagFunc

// The default XMP handler is currently very simple:
// It decodes the RDF.Description.Attrs using Go's xml package and passes each tag to HandleTag.
// If HandleXMP is set, the decoder will call this function for each XMP packet instead.
// Note that r must be read completely.
HandleXMP func(r io.Reader) error

// If set, the decoder will only read the given tag sources.
// Note that this is a bitmask and you may send multiple sources at once.
Sources TagSource
Expand Down
48 changes: 48 additions & 0 deletions imagemeta_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package imagemeta_test

import (
"fmt"
"io"
"math"
"math/big"
"os"
Expand Down Expand Up @@ -117,6 +118,53 @@ func TestDecodeOrientationOnly(t *testing.T) {

}

func TestDecodeCustomXMPHandler(t *testing.T) {
c := qt.New(t)

img, close := getSunrise(c, imagemeta.ImageFormatWebP)
c.Cleanup(close)

var xml string
err := imagemeta.Decode(
imagemeta.Options{
R: img,
ImageFormat: imagemeta.ImageFormatWebP,
HandleXMP: func(r io.Reader) error {
b, err := io.ReadAll(r)
xml = string(b)
return err
},
Sources: imagemeta.TagSourceXMP,
},
)

c.Assert(err, qt.IsNil)
c.Assert(xml, qt.Contains, "Sunrise in Spain")

}

func TestDecodeCustomXMPHandlerShortRead(t *testing.T) {
c := qt.New(t)

img, close := getSunrise(c, imagemeta.ImageFormatWebP)
c.Cleanup(close)

err := imagemeta.Decode(
imagemeta.Options{
R: img,
ImageFormat: imagemeta.ImageFormatWebP,
HandleXMP: func(r io.Reader) error {
return nil
},
Sources: imagemeta.TagSourceXMP,
},
)

c.Assert(err, qt.IsNotNil)
c.Assert(err.Error(), qt.Contains, "expected EOF after XMP")

}

func TestSmoke(t *testing.T) {
c := qt.New(t)

Expand Down
17 changes: 15 additions & 2 deletions metadecoder_xmp.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package imagemeta

import (
"encoding/xml"
"errors"
"io"
)

Expand All @@ -17,7 +18,19 @@ type xmpmeta struct {
RDF rdf `xml:"RDF"`
}

func decodeXMP(r io.Reader, handleTag HandleTagFunc) error {
func decodeXMP(r io.Reader, opts Options) error {
if opts.HandleXMP != nil {
if err := opts.HandleXMP(r); err != nil {
return err
}
// Read one more byte to make sure we're at EOF.
var b [1]byte
if _, err := r.Read(b[:]); err != io.EOF {
return errors.New("expected EOF after XMP")
}
return nil
}

var meta xmpmeta
if err := xml.NewDecoder(r).Decode(&meta); err != nil {
return err
Expand All @@ -34,7 +47,7 @@ func decodeXMP(r io.Reader, handleTag HandleTagFunc) error {
Value: attr.Value,
}

if err := handleTag(tagInfo); err != nil {
if err := opts.HandleTag(tagInfo); err != nil {
return err
}
}
Expand Down

0 comments on commit 80f37b9

Please sign in to comment.