From dc8325bd4abbe20760dae3a211b773cd7815919f Mon Sep 17 00:00:00 2001 From: viktorstrate Date: Thu, 22 Apr 2021 22:46:52 +0200 Subject: [PATCH] Extend exif tests --- api/scanner/exif/exif_parser_external.go | 18 +++++++ api/scanner/exif/exif_parser_internal.go | 8 +++- api/scanner/exif/exif_test.go | 57 +++++++++++++++++------ api/scanner/exif/test_data/stripped.jpg | Bin 0 -> 11132 bytes 4 files changed, 67 insertions(+), 16 deletions(-) create mode 100644 api/scanner/exif/test_data/stripped.jpg diff --git a/api/scanner/exif/exif_parser_external.go b/api/scanner/exif/exif_parser_external.go index b057d1c9..61d958ec 100644 --- a/api/scanner/exif/exif_parser_external.go +++ b/api/scanner/exif/exif_parser_external.go @@ -21,6 +21,7 @@ func (p *externalExifParser) ParseExif(media_path string) (returnExif *models.Me fileInfos := et.ExtractMetadata(media_path) newExif := models.MediaEXIF{} + found_exif := false for _, fileInfo := range fileInfos { if fileInfo.Err != nil { @@ -31,18 +32,21 @@ func (p *externalExifParser) ParseExif(media_path string) (returnExif *models.Me // Get camera model model, err := fileInfo.GetString("Model") if err == nil { + found_exif = true newExif.Camera = &model } // Get Camera make make, err := fileInfo.GetString("Make") if err == nil { + found_exif = true newExif.Maker = &make } // Get lens lens, err := fileInfo.GetString("LensModel") if err == nil { + found_exif = true newExif.Lens = &lens } @@ -52,6 +56,7 @@ func (p *externalExifParser) ParseExif(media_path string) (returnExif *models.Me layout := "2006:01:02 15:04:05" dateTime, err := time.Parse(layout, date) if err == nil { + found_exif = true newExif.DateShot = &dateTime } } @@ -59,58 +64,71 @@ func (p *externalExifParser) ParseExif(media_path string) (returnExif *models.Me // Get exposure time exposureTime, err := fileInfo.GetFloat("ExposureTime") if err == nil { + found_exif = true newExif.Exposure = &exposureTime } // Get aperture aperture, err := fileInfo.GetFloat("Aperture") if err == nil { + found_exif = true newExif.Aperture = &aperture } // Get ISO iso, err := fileInfo.GetInt("ISO") if err == nil { + found_exif = true newExif.Iso = &iso } // Get focal length focalLen, err := fileInfo.GetFloat("FocalLength") if err == nil { + found_exif = true newExif.FocalLength = &focalLen } // Get flash info flash, err := fileInfo.GetInt("Flash") if err == nil { + found_exif = true newExif.Flash = &flash } // Get orientation orientation, err := fileInfo.GetInt("Orientation") if err == nil { + found_exif = true newExif.Orientation = &orientation } // Get exposure program expProgram, err := fileInfo.GetInt("ExposureProgram") if err == nil { + found_exif = true newExif.ExposureProgram = &expProgram } // GPS coordinates - longitude longitudeRaw, err := fileInfo.GetFloat("GPSLongitude") if err == nil { + found_exif = true newExif.GPSLongitude = &longitudeRaw } // GPS coordinates - latitude latitudeRaw, err := fileInfo.GetFloat("GPSLatitude") if err == nil { + found_exif = true newExif.GPSLatitude = &latitudeRaw } } + if !found_exif { + return nil, nil + } + returnExif = &newExif return } diff --git a/api/scanner/exif/exif_parser_internal.go b/api/scanner/exif/exif_parser_internal.go index b1d30129..5897e6e5 100644 --- a/api/scanner/exif/exif_parser_internal.go +++ b/api/scanner/exif/exif_parser_internal.go @@ -5,6 +5,7 @@ import ( "log" "math/big" "os" + "time" "github.com/photoview/photoview/api/graphql/models" "github.com/pkg/errors" @@ -33,7 +34,8 @@ func (p *internalExifParser) ParseExif(media_path string) (returnExif *models.Me exifTags, err := exif.Decode(photoFile) if err != nil { - return nil, errors.Wrap(err, "Could not decode EXIF") + return nil, nil + // return nil, errors.Wrap(err, "Could not decode EXIF") } newExif := models.MediaEXIF{} @@ -55,7 +57,9 @@ func (p *internalExifParser) ParseExif(media_path string) (returnExif *models.Me date, err := exifTags.DateTime() if err == nil { - newExif.DateShot = &date + _, tz := date.Zone() + date_utc := date.Add(time.Duration(tz) * time.Second).UTC() + newExif.DateShot = &date_utc } exposure, err := exifTags.Get(exif.ExposureTime) diff --git a/api/scanner/exif/exif_test.go b/api/scanner/exif/exif_test.go index 049de693..f125be95 100644 --- a/api/scanner/exif/exif_test.go +++ b/api/scanner/exif/exif_test.go @@ -1,18 +1,20 @@ -// +build integration - package exif import ( + "fmt" + "path" "testing" "time" + "github.com/barasher/go-exiftool" + "github.com/photoview/photoview/api/graphql/models" "github.com/stretchr/testify/assert" ) -const bird_path = "./test_data/bird.jpg" - func TestExifParsers(t *testing.T) { - t.Parallel() + if testing.Short() { + t.Skip("skipping integration test") + } parsers := []struct { name string @@ -28,13 +30,14 @@ func TestExifParsers(t *testing.T) { }, } - 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) { + images := []struct { + path string + assert func(t *testing.T, exif *models.MediaEXIF) + }{ + { + path: "./test_data/bird.jpg", + assert: func(t *testing.T, exif *models.MediaEXIF) { + assert.WithinDuration(t, *exif.DateShot, time.Unix(1336318784, 0).UTC(), time.Minute) 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) @@ -45,8 +48,34 @@ func TestExifParsers(t *testing.T) { assert.EqualValues(t, *exif.Orientation, 1) assert.InDelta(t, *exif.GPSLatitude, 65.01681388888889, 0.0001) assert.InDelta(t, *exif.GPSLongitude, 25.466863888888888, 0.0001) - } - }) + }, + }, + { + path: "./test_data/stripped.jpg", + assert: func(t *testing.T, exif *models.MediaEXIF) { + assert.Nil(t, exif) + }, + }, + } + + for _, p := range parsers { + for _, img := range images { + t.Run(fmt.Sprintf("%s:%s", p.name, path.Base(img.path)), func(t *testing.T) { + + if p.name == "external" { + _, err := exiftool.NewExiftool() + if err != nil { + t.Skip("failed to get exiftool, skipping test") + } + } + + exif, err := p.parser.ParseExif(img.path) + + if assert.NoError(t, err) { + img.assert(t, exif) + } + }) + } } } diff --git a/api/scanner/exif/test_data/stripped.jpg b/api/scanner/exif/test_data/stripped.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4a1788e809c9a17658af8369054b22096a50c788 GIT binary patch literal 11132 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 POb%u^c4P&qKmYna*f$6s literal 0 HcmV?d00001