From 46ae5115bbe29e3287c9b9c81b40dbca9db19d1e Mon Sep 17 00:00:00 2001 From: viktorstrate Date: Tue, 19 Jan 2021 16:39:57 +0100 Subject: [PATCH] Refactor EXIF parsing --- api/scanner/exif/exif.go | 51 ++++++++++++++++ .../{exif.go => exif/exif_parser_internal.go} | 58 ++++++------------- api/scanner/scanner_media.go | 5 +- 3 files changed, 71 insertions(+), 43 deletions(-) create mode 100644 api/scanner/exif/exif.go rename api/scanner/{exif.go => exif/exif_parser_internal.go} (72%) diff --git a/api/scanner/exif/exif.go b/api/scanner/exif/exif.go new file mode 100644 index 00000000..a0449c86 --- /dev/null +++ b/api/scanner/exif/exif.go @@ -0,0 +1,51 @@ +package exif + +import ( + "log" + + "github.com/pkg/errors" + "gorm.io/gorm" + + "github.com/photoview/photoview/api/graphql/models" +) + +type exifParser interface { + ParseExif(media *models.Media) (*models.MediaEXIF, error) +} + +// SaveEXIF scans the media file for exif metadata and saves it in the database if found +func SaveEXIF(tx *gorm.DB, media *models.Media) (*models.MediaEXIF, error) { + + log.Printf("Scanning for EXIF: %s", media.Path) + + { + // Check if EXIF data already exists + if media.ExifID != nil { + + var exif models.MediaEXIF + if err := tx.First(&exif, media.ExifID).Error; err != nil { + return nil, errors.Wrap(err, "get EXIF for media from database") + } + + return &exif, nil + } + } + + var parser exifParser = &internalExifParser{} + + exif, err := parser.ParseExif(media) + if err != nil { + return nil, errors.Wrap(err, "failed to parse exif data") + } + + if exif == nil { + return nil, nil + } + + // Add EXIF to database and link to media + if err := tx.Model(&media).Association("Exif").Replace(exif); err != nil { + return nil, errors.Wrap(err, "save media exif to database") + } + + return exif, nil +} diff --git a/api/scanner/exif.go b/api/scanner/exif/exif_parser_internal.go similarity index 72% rename from api/scanner/exif.go rename to api/scanner/exif/exif_parser_internal.go index 750e9eb7..e253a577 100644 --- a/api/scanner/exif.go +++ b/api/scanner/exif/exif_parser_internal.go @@ -1,4 +1,4 @@ -package scanner +package exif import ( "fmt" @@ -6,31 +6,16 @@ import ( "math/big" "os" - "github.com/pkg/errors" - "gorm.io/gorm" - "github.com/photoview/photoview/api/graphql/models" + "github.com/pkg/errors" "github.com/xor-gate/goexif2/exif" "github.com/xor-gate/goexif2/mknote" ) -func ScanEXIF(tx *gorm.DB, media *models.Media) (returnExif *models.MediaEXIF, returnErr error) { - - log.Printf("Scanning for EXIF") - - { - // Check if EXIF data already exists - if media.ExifID != nil { - - var exif models.MediaEXIF - if err := tx.First(&exif, media.ExifID).Error; err != nil { - return nil, errors.Wrap(err, "get EXIF for media from database") - } - - return &exif, nil - } - } +// internalExifParser is an exif parser that parses the media without the use of external tools +type internalExifParser struct{} +func (p *internalExifParser) ParseExif(media *models.Media) (returnExif *models.MediaEXIF, returnErr error) { photoFile, err := os.Open(media.Path) if err != nil { return nil, err @@ -53,17 +38,17 @@ func ScanEXIF(tx *gorm.DB, media *models.Media) (returnExif *models.MediaEXIF, r newExif := models.MediaEXIF{} - model, err := readStringTag(exifTags, exif.Model, media) + model, err := p.readStringTag(exifTags, exif.Model, media) if err == nil { newExif.Camera = model } - maker, err := readStringTag(exifTags, exif.Make, media) + maker, err := p.readStringTag(exifTags, exif.Make, media) if err == nil { newExif.Maker = maker } - lens, err := readStringTag(exifTags, exif.LensModel, media) + lens, err := p.readStringTag(exifTags, exif.LensModel, media) if err == nil { newExif.Lens = lens } @@ -73,13 +58,13 @@ func ScanEXIF(tx *gorm.DB, media *models.Media) (returnExif *models.MediaEXIF, r newExif.DateShot = &date } - exposure, err := readRationalTag(exifTags, exif.ExposureTime, media) + exposure, err := p.readRationalTag(exifTags, exif.ExposureTime, media) if err == nil { exposureStr := exposure.RatString() newExif.Exposure = &exposureStr } - apertureRat, err := readRationalTag(exifTags, exif.FNumber, media) + apertureRat, err := p.readRationalTag(exifTags, exif.FNumber, media) if err == nil { aperture, _ := apertureRat.Float64() newExif.Aperture = &aperture @@ -125,12 +110,12 @@ func ScanEXIF(tx *gorm.DB, media *models.Media) (returnExif *models.MediaEXIF, r newExif.Flash = &flash } - orientation, err := readIntegerTag(exifTags, exif.Orientation, media) + orientation, err := p.readIntegerTag(exifTags, exif.Orientation, media) if err == nil { newExif.Orientation = orientation } - exposureProgram, err := readIntegerTag(exifTags, exif.ExposureProgram, media) + exposureProgram, err := p.readIntegerTag(exifTags, exif.ExposureProgram, media) if err == nil { newExif.ExposureProgram = exposureProgram } @@ -141,20 +126,11 @@ func ScanEXIF(tx *gorm.DB, media *models.Media) (returnExif *models.MediaEXIF, r newExif.GPSLonitude = &long } - // If exif is empty - if newExif == (models.MediaEXIF{}) { - return nil, nil - } - - // Add EXIF to database and link to media - if err := tx.Model(&media).Association("Exif").Replace(newExif); err != nil { - return nil, errors.Wrap(err, "save media exif to database") - } - - return &newExif, nil + returnExif = &newExif + return } -func readStringTag(tags *exif.Exif, name exif.FieldName, media *models.Media) (*string, error) { +func (p *internalExifParser) readStringTag(tags *exif.Exif, name exif.FieldName, media *models.Media) (*string, error) { tag, err := tags.Get(name) if err != nil { return nil, errors.Wrapf(err, "could not read %s from EXIF: %s", name, media.Title) @@ -173,7 +149,7 @@ func readStringTag(tags *exif.Exif, name exif.FieldName, media *models.Media) (* return nil, errors.New("exif tag returned null") } -func readRationalTag(tags *exif.Exif, name exif.FieldName, media *models.Media) (*big.Rat, error) { +func (p *internalExifParser) readRationalTag(tags *exif.Exif, name exif.FieldName, media *models.Media) (*big.Rat, error) { tag, err := tags.Get(name) if err != nil { return nil, errors.Wrapf(err, "could not read %s from EXIF: %s", name, media.Title) @@ -192,7 +168,7 @@ func readRationalTag(tags *exif.Exif, name exif.FieldName, media *models.Media) return nil, errors.New("exif tag returned null") } -func readIntegerTag(tags *exif.Exif, name exif.FieldName, media *models.Media) (*int, error) { +func (p *internalExifParser) readIntegerTag(tags *exif.Exif, name exif.FieldName, media *models.Media) (*int, error) { tag, err := tags.Get(name) if err != nil { return nil, errors.Wrapf(err, "could not read %s from EXIF: %s", name, media.Title) diff --git a/api/scanner/scanner_media.go b/api/scanner/scanner_media.go index cc272726..7027a425 100644 --- a/api/scanner/scanner_media.go +++ b/api/scanner/scanner_media.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/photoview/photoview/api/graphql/models" + "github.com/photoview/photoview/api/scanner/exif" "github.com/pkg/errors" "gorm.io/gorm" ) @@ -165,9 +166,9 @@ func ScanMedia(tx *gorm.DB, mediaPath string, albumId int, cache *AlbumScannerCa return nil, false, errors.Wrap(err, "could not insert media into database") } - _, err = ScanEXIF(tx, &media) + _, err = exif.SaveEXIF(tx, &media) if err != nil { - log.Printf("WARN: ScanEXIF for %s failed: %s\n", mediaName, err) + log.Printf("WARN: SaveEXIF for %s failed: %s\n", mediaName, err) } if media.Type == models.MediaTypeVideo {