Skip to content

Commit

Permalink
Implement MediaParser interface
Browse files Browse the repository at this point in the history
  • Loading branch information
dsoprea committed May 5, 2021
1 parent b5d347a commit f9047d2
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 9 deletions.
Binary file added assets/webp_container_specification.pdf
Binary file not shown.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ go 1.16
require (
github.com/dsoprea/go-exif/v3 v3.0.0-20210428042052-dca55bf8ca15
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd
github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e
)
144 changes: 144 additions & 0 deletions media_parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package webp

import (
"bytes"
"io"
"os"

"github.com/dsoprea/go-exif/v3"
"github.com/dsoprea/go-exif/v3/common"
"github.com/dsoprea/go-logging/v2"
"github.com/dsoprea/go-utility/v2/image"
)

// WebpMediaParser is a `riimage.MediaParser` that knows how to parse JPEG
// images.
type WebpMediaParser struct {
}

// NewWebpMediaParser returns a new WebpMediaParser.
func NewWebpMediaParser() *WebpMediaParser {

// TODO(dustin): Add test

return new(WebpMediaParser)
}

type RawExifData []byte

// Exif returns the EXIF's root IFD.
func (ref RawExifData) Exif() (rootIfd *exif.Ifd, data []byte, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()

im, err := exifcommon.NewIfdMappingWithStandard()
log.PanicIf(err)

ti := exif.NewTagIndex()

_, index, err := exif.Collect(im, ti, ref)
log.PanicIf(err)

return index.RootIfd, ref, nil
}

// Parse parses a JPEG uses an `io.ReadSeeker`. Even if it fails, it will return
// the list of segments encountered prior to the failure.
func (wmp *WebpMediaParser) Parse(rs io.ReadSeeker, size int) (mc riimage.MediaContext, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()

wp := NewWebpParser(rs)

visitorCb := func(fourCc [4]byte, dataGetter DataGetterFunc) (err error) {
if fourCc != exifFourCc {
return nil
}

exifData, err := dataGetter()
log.PanicIf(err)

mc = RawExifData(exifData)

return nil
}

err = wp.enumerateChunks(visitorCb)
log.PanicIf(err)

if mc != nil {
return mc, nil
}

return nil, exif.ErrNoExif
}

// ParseFile parses a JPEG file. Even if it fails, it will return the list of
// segments encountered prior to the failure.
func (wmp *WebpMediaParser) ParseFile(filepath string) (mc riimage.MediaContext, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()

// TODO(dustin): Add test

f, err := os.Open(filepath)
log.PanicIf(err)

defer f.Close()

stat, err := f.Stat()
log.PanicIf(err)

size := stat.Size()

mc, err = wmp.Parse(f, int(size))
log.PanicIf(err)

return mc, nil
}

// ParseBytes parses a JPEG byte-slice. Even if it fails, it will return the
// list of segments encountered prior to the failure.
func (wmp *WebpMediaParser) ParseBytes(data []byte) (mc riimage.MediaContext, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()

br := bytes.NewReader(data)

mc, err = wmp.Parse(br, len(data))
log.PanicIf(err)

return mc, nil
}

// LooksLikeFormat indicates whether the data looks like a JPEG image.
func (wmp *WebpMediaParser) LooksLikeFormat(data []byte) bool {
defer func() {
if errRaw := recover(); errRaw != nil {
log.Panic(errRaw.(error))
}
}()

br := bytes.NewReader(data)
wp := NewWebpParser(br)

_, err := wp.readHeader()
return err == nil
}

var (
// Enforce interface conformance.
_ riimage.MediaParser = new(WebpMediaParser)
)
80 changes: 80 additions & 0 deletions media_parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package webp

import (
"os"
"testing"

"io/ioutil"

"github.com/dsoprea/go-exif/v3/common"
"github.com/dsoprea/go-logging/v2"
)

func TestWebpMediaParser_ParseBytes(t *testing.T) {
wmp := NewWebpMediaParser()

filepath := GetTestImageFilepath()

// f, err := os.Open(filepath)
// log.PanicIf(err)

// defer f.Close()

data, err := ioutil.ReadFile(filepath)
log.PanicIf(err)

// f, err := os.Open(filepath)
// log.PanicIf(err)

// defer f.Close()

mc, err := wmp.ParseBytes(data)
log.PanicIf(err)

rootIfd, _, err := mc.Exif()
log.PanicIf(err)

ii := rootIfd.IfdIdentity()
if ii != exifcommon.IfdStandardIfdIdentity {
t.Fatalf("Root IFD is not the correct type: [%s]", ii)
}
}

func TestWebpMediaParser_ParseFile(t *testing.T) {
wmp := NewWebpMediaParser()

filepath := GetTestImageFilepath()

mc, err := wmp.ParseFile(filepath)
log.PanicIf(err)

rootIfd, _, err := mc.Exif()
log.PanicIf(err)

ii := rootIfd.IfdIdentity()
if ii != exifcommon.IfdStandardIfdIdentity {
t.Fatalf("Root IFD is not the correct type: [%s]", ii)
}
}

func TestWebpMediaParser_Parse(t *testing.T) {
wmp := NewWebpMediaParser()

filepath := GetTestImageFilepath()

f, err := os.Open(filepath)
log.PanicIf(err)

defer f.Close()

mc, err := wmp.Parse(f, 0)
log.PanicIf(err)

rootIfd, _, err := mc.Exif()
log.PanicIf(err)

ii := rootIfd.IfdIdentity()
if ii != exifcommon.IfdStandardIfdIdentity {
t.Fatalf("Root IFD is not the correct type: [%s]", ii)
}
}
11 changes: 6 additions & 5 deletions webp.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ var (
DefaultEndianness = binary.LittleEndian
)

var (
riffFileHeaderBytes = [4]byte{'R', 'I', 'F', 'F'}
webpBytes = [4]byte{'W', 'E', 'B', 'P'}
exifFourCc = [4]byte{'E', 'X', 'I', 'F'}
)

// WebpParser parses WEBP RIFF streams.
type WebpParser struct {
r io.Reader
Expand All @@ -35,11 +41,6 @@ func NewWebpParser(r io.Reader) *WebpParser {
}
}

var (
riffFileHeaderBytes = [4]byte{'R', 'I', 'F', 'F'}
webpBytes = [4]byte{'W', 'E', 'B', 'P'}
)

// readHeader returns the file-header if we're sitting on the first byte.
func (wp *WebpParser) readHeader() (fileSize int64, err error) {
defer func() {
Expand Down
4 changes: 0 additions & 4 deletions webp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ import (
"github.com/dsoprea/go-logging/v2"
)

var (
exifFourCc = [4]byte{'E', 'X', 'I', 'F'}
)

func TestWebpParser_readHeader(t *testing.T) {
filepath := GetTestImageFilepath()

Expand Down

0 comments on commit f9047d2

Please sign in to comment.