Add integration tests for exif parsers

This commit is contained in:
viktorstrate
2021-04-22 21:37:59 +02:00
parent cb9e57637f
commit 88a9d705a9
7 changed files with 98 additions and 28 deletions

View File

@@ -21,6 +21,8 @@ require (
github.com/mitchellh/mapstructure v1.4.1 // indirect github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f github.com/sabhiram/go-gitignore v0.0.0-20201211210132-54b8a0bf510f
github.com/stretchr/objx v0.3.0 // indirect
github.com/stretchr/testify v1.7.0 // indirect
github.com/strukturag/libheif v1.11.0 github.com/strukturag/libheif v1.11.0
github.com/vektah/gqlparser/v2 v2.1.0 github.com/vektah/gqlparser/v2 v2.1.0
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0
@@ -30,6 +32,7 @@ require (
golang.org/x/text v0.3.6 // indirect golang.org/x/text v0.3.6 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/vansante/go-ffprobe.v2 v2.0.2 gopkg.in/vansante/go-ffprobe.v2 v2.0.2
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
gorm.io/driver/mysql v1.0.5 gorm.io/driver/mysql v1.0.5
gorm.io/driver/postgres v1.0.8 gorm.io/driver/postgres v1.0.8
gorm.io/driver/sqlite v1.1.4 gorm.io/driver/sqlite v1.1.4

View File

@@ -539,6 +539,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@@ -546,6 +548,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/strukturag/libheif v1.11.0 h1:HaWu5re98INSXNq7C8o5AwLcv2qD8+U7a+jVCpGWemI= github.com/strukturag/libheif v1.11.0 h1:HaWu5re98INSXNq7C8o5AwLcv2qD8+U7a+jVCpGWemI=
github.com/strukturag/libheif v1.11.0/go.mod h1:E/PNRlmVtrtj9j2AvBZlrO4dsBDu6KfwDZn7X1Ce8Ks= github.com/strukturag/libheif v1.11.0/go.mod h1:E/PNRlmVtrtj9j2AvBZlrO4dsBDu6KfwDZn7X1Ce8Ks=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8 h1:ndzgwNDnKIqyCvHTXaCqh9KlOWKvBry6nuXMJmonVsE= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8 h1:ndzgwNDnKIqyCvHTXaCqh9KlOWKvBry6nuXMJmonVsE=
@@ -756,6 +760,8 @@ gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.0.5 h1:WAAmvLK2rG0tCOqrf5XcLi2QUwugd4rcVJ/W3aoon9o= gorm.io/driver/mysql v1.0.5 h1:WAAmvLK2rG0tCOqrf5XcLi2QUwugd4rcVJ/W3aoon9o=
gorm.io/driver/mysql v1.0.5/go.mod h1:N1OIhHAIhx5SunkMGqWbGFVeh4yTNWKmMo1GOAsohLI= gorm.io/driver/mysql v1.0.5/go.mod h1:N1OIhHAIhx5SunkMGqWbGFVeh4yTNWKmMo1GOAsohLI=
gorm.io/driver/postgres v1.0.8 h1:PAgM+PaHOSAeroTjHkCHCBIHHoBIf9RgPWGo8dF2DA8= gorm.io/driver/postgres v1.0.8 h1:PAgM+PaHOSAeroTjHkCHCBIHHoBIf9RgPWGo8dF2DA8=

View File

@@ -11,7 +11,7 @@ import (
) )
type exifParser interface { type exifParser interface {
ParseExif(media *models.Media) (*models.MediaEXIF, error) ParseExif(media_path string) (*models.MediaEXIF, error)
} }
var use_exiftool bool = false var use_exiftool bool = false
@@ -55,7 +55,7 @@ func SaveEXIF(tx *gorm.DB, media *models.Media) (*models.MediaEXIF, error) {
parser = &internalExifParser{} parser = &internalExifParser{}
} }
exif, err := parser.ParseExif(media) exif, err := parser.ParseExif(media.Path)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to parse exif data") return nil, errors.Wrap(err, "failed to parse exif data")
} }

View File

@@ -10,7 +10,7 @@ import (
type externalExifParser struct{} type externalExifParser struct{}
func (p *externalExifParser) ParseExif(media *models.Media) (returnExif *models.MediaEXIF, returnErr error) { func (p *externalExifParser) ParseExif(media_path string) (returnExif *models.MediaEXIF, returnErr error) {
// ExifTool - No print conversion mode // ExifTool - No print conversion mode
et, err := exiftool.NewExiftool(exiftool.NoPrintConversion()) et, err := exiftool.NewExiftool(exiftool.NoPrintConversion())
if err != nil { if err != nil {
@@ -19,7 +19,7 @@ func (p *externalExifParser) ParseExif(media *models.Media) (returnExif *models.
} }
defer et.Close() defer et.Close()
fileInfos := et.ExtractMetadata(media.Path) fileInfos := et.ExtractMetadata(media_path)
newExif := models.MediaEXIF{} newExif := models.MediaEXIF{}
for _, fileInfo := range fileInfos { for _, fileInfo := range fileInfos {

View File

@@ -15,8 +15,8 @@ import (
// internalExifParser is an exif parser that parses the media without the use of external tools // internalExifParser is an exif parser that parses the media without the use of external tools
type internalExifParser struct{} type internalExifParser struct{}
func (p *internalExifParser) ParseExif(media *models.Media) (returnExif *models.MediaEXIF, returnErr error) { func (p *internalExifParser) ParseExif(media_path string) (returnExif *models.MediaEXIF, returnErr error) {
photoFile, err := os.Open(media.Path) photoFile, err := os.Open(media_path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -38,17 +38,17 @@ func (p *internalExifParser) ParseExif(media *models.Media) (returnExif *models.
newExif := models.MediaEXIF{} newExif := models.MediaEXIF{}
model, err := p.readStringTag(exifTags, exif.Model, media) model, err := p.readStringTag(exifTags, exif.Model, media_path)
if err == nil { if err == nil {
newExif.Camera = model newExif.Camera = model
} }
maker, err := p.readStringTag(exifTags, exif.Make, media) maker, err := p.readStringTag(exifTags, exif.Make, media_path)
if err == nil { if err == nil {
newExif.Maker = maker newExif.Maker = maker
} }
lens, err := p.readStringTag(exifTags, exif.LensModel, media) lens, err := p.readStringTag(exifTags, exif.LensModel, media_path)
if err == nil { if err == nil {
newExif.Lens = lens newExif.Lens = lens
} }
@@ -66,7 +66,7 @@ func (p *internalExifParser) ParseExif(media *models.Media) (returnExif *models.
} }
} }
apertureRat, err := p.readRationalTag(exifTags, exif.FNumber, media) apertureRat, err := p.readRationalTag(exifTags, exif.FNumber, media_path)
if err == nil { if err == nil {
aperture, _ := apertureRat.Float64() aperture, _ := apertureRat.Float64()
newExif.Aperture = &aperture newExif.Aperture = &aperture
@@ -74,11 +74,11 @@ func (p *internalExifParser) ParseExif(media *models.Media) (returnExif *models.
isoTag, err := exifTags.Get(exif.ISOSpeedRatings) isoTag, err := exifTags.Get(exif.ISOSpeedRatings)
if err != nil { if err != nil {
log.Printf("WARN: Could not read ISOSpeedRatings from EXIF: %s\n", media.Title) log.Printf("WARN: Could not read ISOSpeedRatings from EXIF: %v\n", media_path)
} else { } else {
iso, err := isoTag.Int(0) iso, err := isoTag.Int(0)
if err != nil { if err != nil {
log.Printf("WARN: Could not parse EXIF ISOSpeedRatings as integer: %s\n", media.Title) log.Printf("WARN: Could not parse EXIF ISOSpeedRatings as integer: %v\n", media_path)
} else { } else {
iso64 := int64(iso) iso64 := int64(iso)
newExif.Iso = &iso64 newExif.Iso = &iso64
@@ -99,7 +99,7 @@ func (p *internalExifParser) ParseExif(media *models.Media) (returnExif *models.
if err == nil { if err == nil {
focalLength, err := focalLengthTag.Int(1) focalLength, err := focalLengthTag.Int(1)
if err != nil { if err != nil {
log.Printf("WARN: Could not parse EXIF FocalLength as rational or integer: %s\n%s\n", media.Title, err) log.Printf("WARN: Could not parse EXIF FocalLength as rational or integer: %v\n%s\n", media_path, err)
} else { } else {
focalLenFloat := float64(focalLength) focalLenFloat := float64(focalLength)
newExif.FocalLength = &focalLenFloat newExif.FocalLength = &focalLenFloat
@@ -108,19 +108,19 @@ func (p *internalExifParser) ParseExif(media *models.Media) (returnExif *models.
} }
} }
flash, err := p.readIntegerTag(exifTags, exif.Flash, media) flash, err := p.readIntegerTag(exifTags, exif.Flash, media_path)
if err == nil { if err == nil {
flash64 := int64(*flash) flash64 := int64(*flash)
newExif.Flash = &flash64 newExif.Flash = &flash64
} }
orientation, err := p.readIntegerTag(exifTags, exif.Orientation, media) orientation, err := p.readIntegerTag(exifTags, exif.Orientation, media_path)
if err == nil { if err == nil {
orientation64 := int64(*orientation) orientation64 := int64(*orientation)
newExif.Orientation = &orientation64 newExif.Orientation = &orientation64
} }
exposureProgram, err := p.readIntegerTag(exifTags, exif.ExposureProgram, media) exposureProgram, err := p.readIntegerTag(exifTags, exif.ExposureProgram, media_path)
if err == nil { if err == nil {
exposureProgram64 := int64(*exposureProgram) exposureProgram64 := int64(*exposureProgram)
newExif.ExposureProgram = &exposureProgram64 newExif.ExposureProgram = &exposureProgram64
@@ -136,59 +136,59 @@ func (p *internalExifParser) ParseExif(media *models.Media) (returnExif *models.
return return
} }
func (p *internalExifParser) readStringTag(tags *exif.Exif, name exif.FieldName, media *models.Media) (*string, error) { func (p *internalExifParser) readStringTag(tags *exif.Exif, name exif.FieldName, media_path string) (*string, error) {
tag, err := tags.Get(name) tag, err := tags.Get(name)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "could not read %s from EXIF: %s", name, media.Title) return nil, errors.Wrapf(err, "could not read %s from EXIF: %s", name, media_path)
} }
if tag != nil { if tag != nil {
value, err := tag.StringVal() value, err := tag.StringVal()
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "could not parse %s from EXIF as string: %s", name, media.Title) return nil, errors.Wrapf(err, "could not parse %s from EXIF as string: %s", name, media_path)
} }
return &value, nil return &value, nil
} }
log.Printf("WARN: EXIF tag %s returned null: %s\n", name, media.Title) log.Printf("WARN: EXIF tag %s returned null: %s\n", name, media_path)
return nil, errors.New("exif tag returned null") return nil, errors.New("exif tag returned null")
} }
func (p *internalExifParser) readRationalTag(tags *exif.Exif, name exif.FieldName, media *models.Media) (*big.Rat, error) { func (p *internalExifParser) readRationalTag(tags *exif.Exif, name exif.FieldName, media_path string) (*big.Rat, error) {
tag, err := tags.Get(name) tag, err := tags.Get(name)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "could not read %s from EXIF: %s", name, media.Title) return nil, errors.Wrapf(err, "could not read %s from EXIF: %s", name, media_path)
} }
if tag != nil { if tag != nil {
value, err := tag.Rat(0) value, err := tag.Rat(0)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "could not parse %s from EXIF as rational: %s", name, media.Title) return nil, errors.Wrapf(err, "could not parse %s from EXIF as rational: %s", name, media_path)
} }
return value, nil return value, nil
} }
log.Printf("WARN: EXIF tag %s returned null: %s\n", name, media.Title) log.Printf("WARN: EXIF tag %s returned null: %s\n", name, media_path)
return nil, errors.New("exif tag returned null") return nil, errors.New("exif tag returned null")
} }
func (p *internalExifParser) readIntegerTag(tags *exif.Exif, name exif.FieldName, media *models.Media) (*int, error) { func (p *internalExifParser) readIntegerTag(tags *exif.Exif, name exif.FieldName, media_path string) (*int, error) {
tag, err := tags.Get(name) tag, err := tags.Get(name)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "could not read %s from EXIF: %s", name, media.Title) return nil, errors.Wrapf(err, "could not read %s from EXIF: %s", name, media_path)
} }
if tag != nil { if tag != nil {
value, err := tag.Int(0) value, err := tag.Int(0)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "Could not parse %s from EXIF as integer: %s", name, media.Title) return nil, errors.Wrapf(err, "Could not parse %s from EXIF as integer: %s", name, media_path)
} }
return &value, nil return &value, nil
} }
log.Printf("WARN: EXIF tag %s returned null: %s\n", name, media.Title) log.Printf("WARN: EXIF tag %s returned null: %s\n", name, media_path)
return nil, errors.New("exif tag returned null") return nil, errors.New("exif tag returned null")
} }

View File

@@ -0,0 +1,61 @@
// +build integration
package exif
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
const bird_path = "./test_data/bird.jpg"
func TestExifParsers(t *testing.T) {
t.Parallel()
parsers := []struct {
name string
parser exifParser
}{
{
name: "internal",
parser: &internalExifParser{},
},
{
name: "external",
parser: &externalExifParser{},
},
}
for _, p := range parsers {
t.Run(p.name, func(t *testing.T) {
t.Parallel()
exif, err := p.parser.ParseExif(bird_path)
if assert.NoError(t, err) {
assert.EqualValues(t, *exif.Camera, "Canon EOS 600D")
assert.EqualValues(t, *exif.Maker, "Canon")
assert.WithinDuration(t, *exif.DateShot, time.Unix(1336318784, 0).UTC(), time.Minute)
assert.EqualValues(t, *exif.Aperture, 6.3)
assert.EqualValues(t, *exif.Iso, 800)
assert.EqualValues(t, *exif.FocalLength, 300)
assert.EqualValues(t, *exif.Flash, 16)
assert.EqualValues(t, *exif.Orientation, 1)
assert.InDelta(t, *exif.GPSLatitude, 65.01681388888889, 0.0001)
assert.InDelta(t, *exif.GPSLongitude, 25.466863888888888, 0.0001)
}
})
}
}
// func TestExternalExifParser(t *testing.T) {
// parser := externalExifParser{}
// exif, err := parser.ParseExif((bird_path))
// if assert.NoError(t, err) {
// assert.Equal(t, exif, &bird_exif)
// }
// }

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB