From 39b053031311cf8bc8ac1e189aeefdf6f1393d58 Mon Sep 17 00:00:00 2001 From: Michael Mayer Date: Mon, 28 Mar 2022 15:57:29 +0200 Subject: [PATCH] Metadata: Skip brute-force search if no Exif headers were found #2196 --- Makefile | 9 +++- internal/commands/config.go | 15 ++++--- internal/config/config_test.go | 7 ---- internal/config/flags.go | 35 +++++++++------- internal/config/fs.go | 15 ------- internal/config/fs_test.go | 24 ----------- internal/config/metadata.go | 21 ++++++++++ internal/config/metadata_test.go | 43 +++++++++++++++++++ internal/config/options.go | 7 ++-- internal/config/test.go | 1 + internal/meta/data.go | 2 +- internal/meta/exif.go | 12 +++--- internal/meta/exif_parser.go | 12 ++++-- internal/meta/exif_test.go | 60 +++++++++++++++++---------- internal/photoprism/mediafile_meta.go | 2 +- 15 files changed, 161 insertions(+), 104 deletions(-) create mode 100644 internal/config/metadata.go create mode 100644 internal/config/metadata_test.go diff --git a/Makefile b/Makefile index 6d76ae2a9..bc3d128c5 100644 --- a/Makefile +++ b/Makefile @@ -50,6 +50,9 @@ fmt: fmt-js fmt-go clean-local: clean-local-config clean-local-cache upgrade: dep-upgrade-js dep-upgrade devtools: install-go dep-npm +.SILENT: help; +help: + @echo "For build instructions, visit ." fix-permissions: $(info Updating filesystem permissions...) @if [ $(UID) != 0 ]; then\ @@ -405,7 +408,11 @@ fmt-go: goimports -w pkg internal cmd tidy: go mod tidy -go=1.16 && go mod tidy -go=1.17 + .PHONY: all build dev dep-npm dep dep-go dep-js dep-list dep-tensorflow dep-upgrade dep-upgrade-js test test-js test-go \ install generate fmt fmt-go fmt-js upgrade start stop terminal root-terminal packer-digitalocean acceptance clean tidy \ docker-develop docker-preview docker-preview-all docker-preview-arm docker-release docker-release-all docker-release-arm \ - install-go install-darktable install-tensorflow devtools tar.gz fix-permissions rootshell; + install-go install-darktable install-tensorflow devtools tar.gz fix-permissions rootshell help \ + docker-local docker-local-all docker-local-bookworm docker-local-bullseye docker-local-buster docker-local-impish \ + docker-local-develop docker-local-develop-all docker-local-develop-bookworm docker-local-develop-bullseye \ + docker-local-develop-buster docker-local-develop-impish; diff --git a/internal/commands/config.go b/internal/commands/config.go index 81cd8d1bf..555ce6a0b 100644 --- a/internal/commands/config.go +++ b/internal/commands/config.go @@ -63,11 +63,10 @@ func configAction(ctx *cli.Context) error { fmt.Printf("%-25s %d\n", "auto-index", conf.AutoIndex()/time.Second) fmt.Printf("%-25s %d\n", "auto-import", conf.AutoImport()/time.Second) - // Features. + // Feature Flags. fmt.Printf("%-25s %t\n", "disable-backups", conf.DisableBackups()) fmt.Printf("%-25s %t\n", "disable-settings", conf.DisableSettings()) fmt.Printf("%-25s %t\n", "disable-places", conf.DisablePlaces()) - fmt.Printf("%-25s %t\n", "disable-exiftool", conf.DisableExifTool()) fmt.Printf("%-25s %t\n", "disable-tensorflow", conf.DisableTensorFlow()) fmt.Printf("%-25s %t\n", "disable-faces", conf.DisableFaces()) fmt.Printf("%-25s %t\n", "disable-classification", conf.DisableClassification()) @@ -76,12 +75,17 @@ func configAction(ctx *cli.Context) error { fmt.Printf("%-25s %t\n", "disable-sips", conf.DisableSips()) fmt.Printf("%-25s %t\n", "disable-heifconvert", conf.DisableHeifConvert()) fmt.Printf("%-25s %t\n", "disable-ffmpeg", conf.DisableFFmpeg()) + fmt.Printf("%-25s %t\n", "disable-exiftool", conf.DisableExifTool()) + + // Format Flags. + fmt.Printf("%-25s %t\n", "exif-bruteforce", conf.ExifBruteForce()) + fmt.Printf("%-25s %t\n", "raw-presets", conf.RawPresets()) // TensorFlow. - fmt.Printf("%-25s %s\n", "tensorflow-version", conf.TensorFlowVersion()) - fmt.Printf("%-25s %s\n", "tensorflow-model-path", conf.TensorFlowModelPath()) fmt.Printf("%-25s %t\n", "detect-nsfw", conf.DetectNSFW()) fmt.Printf("%-25s %t\n", "upload-nsfw", conf.UploadNSFW()) + fmt.Printf("%-25s %s\n", "tensorflow-version", conf.TensorFlowVersion()) + fmt.Printf("%-25s %s\n", "tensorflow-model-path", conf.TensorFlowModelPath()) // UI Defaults. fmt.Printf("%-25s %s\n", "default-locale", conf.DefaultLocale()) @@ -110,7 +114,7 @@ func configAction(ctx *cli.Context) error { fmt.Printf("%-25s %s\n", "api-uri", conf.ApiUri()) fmt.Printf("%-25s %s\n", "base-uri", conf.BaseUri("/")) - // Web Server.. + // Web Server. fmt.Printf("%-25s %s\n", "http-host", conf.HttpHost()) fmt.Printf("%-25s %d\n", "http-port", conf.HttpPort()) fmt.Printf("%-25s %s\n", "http-mode", conf.HttpMode()) @@ -127,7 +131,6 @@ func configAction(ctx *cli.Context) error { fmt.Printf("%-25s %d\n", "database-conns-idle", conf.DatabaseConnsIdle()) // External Tools. - fmt.Printf("%-25s %t\n", "raw-presets", conf.RawPresets()) fmt.Printf("%-25s %s\n", "darktable-bin", conf.DarktableBin()) fmt.Printf("%-25s %s\n", "darktable-cache-path", conf.DarktableCachePath()) fmt.Printf("%-25s %s\n", "darktable-config-path", conf.DarktableConfigPath()) diff --git a/internal/config/config_test.go b/internal/config/config_test.go index fdc3d9b1e..8b44fccb1 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -148,13 +148,6 @@ func TestConfig_ImportPath(t *testing.T) { assert.True(t, strings.HasSuffix(result, "/storage/testdata/import")) } -func TestConfig_ExifToolBin(t *testing.T) { - c := NewConfig(CliTestContext()) - - bin := c.ExifToolBin() - assert.Equal(t, "/usr/bin/exiftool", bin) -} - func TestConfig_CachePath(t *testing.T) { c := NewConfig(CliTestContext()) diff --git a/internal/config/flags.go b/internal/config/flags.go index ba2c47be4..8cb00403a 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -170,16 +170,6 @@ var GlobalFlags = []cli.Flag{ Usage: "disable creating YAML metadata files", EnvVar: "PHOTOPRISM_DISABLE_BACKUPS", }, - cli.BoolFlag{ - Name: "disable-exiftool", - Usage: "disable creating JSON metadata sidecar files with ExifTool", - EnvVar: "PHOTOPRISM_DISABLE_EXIFTOOL", - }, - cli.BoolFlag{ - Name: "disable-ffmpeg", - Usage: "disable video transcoding and thumbnail extraction with FFmpeg", - EnvVar: "PHOTOPRISM_DISABLE_FFMPEG", - }, cli.BoolFlag{ Name: "disable-darktable", Usage: "disable converting RAW files with Darktable", @@ -215,6 +205,26 @@ var GlobalFlags = []cli.Flag{ Usage: "disable image classification", EnvVar: "PHOTOPRISM_DISABLE_CLASSIFICATION", }, + cli.BoolFlag{ + Name: "disable-ffmpeg", + Usage: "disable video transcoding and thumbnail extraction with FFmpeg", + EnvVar: "PHOTOPRISM_DISABLE_FFMPEG", + }, + cli.BoolFlag{ + Name: "disable-exiftool", + Usage: "disable creating JSON metadata sidecar files with ExifTool", + EnvVar: "PHOTOPRISM_DISABLE_EXIFTOOL", + }, + cli.BoolFlag{ + Name: "exif-bruteforce", + Usage: "always perform a brute-force search if no Exif headers were found", + EnvVar: "PHOTOPRISM_EXIF_BRUTEFORCE", + }, + cli.BoolFlag{ + Name: "raw-presets", + Usage: "enable RAW file converter presets (may reduce performance)", + EnvVar: "PHOTOPRISM_RAW_PRESETS", + }, cli.BoolFlag{ Name: "detect-nsfw", Usage: "flag photos as private that may be offensive (requires TensorFlow)", @@ -368,11 +378,6 @@ var GlobalFlags = []cli.Flag{ Usage: "maximum `NUMBER` of idle database connections", EnvVar: "PHOTOPRISM_DATABASE_CONNS_IDLE", }, - cli.BoolFlag{ - Name: "raw-presets", - Usage: "enable RAW file converter presets (may reduce performance)", - EnvVar: "PHOTOPRISM_RAW_PRESETS", - }, cli.StringFlag{ Name: "darktable-bin", Usage: "Darktable CLI `COMMAND` for RAW image conversion", diff --git a/internal/config/fs.go b/internal/config/fs.go index c86de28b8..4e11fa295 100644 --- a/internal/config/fs.go +++ b/internal/config/fs.go @@ -216,21 +216,6 @@ func (c *Config) ImportPath() string { return fs.Abs(c.options.ImportPath) } -// ExifToolBin returns the exiftool executable file name. -func (c *Config) ExifToolBin() string { - return findExecutable(c.options.ExifToolBin, "exiftool") -} - -// ExifToolJson tests if creating JSON metadata sidecar files with Exiftool is enabled. -func (c *Config) ExifToolJson() bool { - return !c.DisableExifTool() -} - -// BackupYaml tests if creating YAML files is enabled. -func (c *Config) BackupYaml() bool { - return !c.DisableBackups() -} - // SidecarPath returns the storage path for generated sidecar files (relative or absolute). func (c *Config) SidecarPath() string { if c.options.SidecarPath == "" { diff --git a/internal/config/fs_test.go b/internal/config/fs_test.go index 8d46e1aee..e3645cc9f 100644 --- a/internal/config/fs_test.go +++ b/internal/config/fs_test.go @@ -11,30 +11,6 @@ func TestConfig_FindExecutable(t *testing.T) { assert.Equal(t, "", findExecutable("yyy", "xxx")) } -func TestConfig_SidecarJson(t *testing.T) { - c := NewConfig(CliTestContext()) - - assert.Equal(t, true, c.ExifToolJson()) - assert.Equal(t, c.DisableExifTool(), !c.ExifToolJson()) - - c.options.DisableExifTool = true - - assert.Equal(t, false, c.ExifToolJson()) - assert.Equal(t, c.DisableExifTool(), !c.ExifToolJson()) -} - -func TestConfig_SidecarYaml(t *testing.T) { - c := NewConfig(CliTestContext()) - - assert.Equal(t, true, c.BackupYaml()) - assert.Equal(t, c.DisableBackups(), !c.BackupYaml()) - - c.options.DisableBackups = true - - assert.Equal(t, false, c.BackupYaml()) - assert.Equal(t, c.DisableBackups(), !c.BackupYaml()) -} - func TestConfig_SidecarPath(t *testing.T) { c := NewConfig(CliTestContext()) diff --git a/internal/config/metadata.go b/internal/config/metadata.go new file mode 100644 index 000000000..e6b1c4402 --- /dev/null +++ b/internal/config/metadata.go @@ -0,0 +1,21 @@ +package config + +// ExifBruteForce checks if a brute-force search should be performed when no Exif headers were found. +func (c *Config) ExifBruteForce() bool { + return c.options.ExifBruteForce || !c.ExifToolJson() +} + +// ExifToolBin returns the exiftool executable file name. +func (c *Config) ExifToolBin() string { + return findExecutable(c.options.ExifToolBin, "exiftool") +} + +// ExifToolJson tests if creating JSON metadata sidecar files with Exiftool is enabled. +func (c *Config) ExifToolJson() bool { + return !c.DisableExifTool() +} + +// BackupYaml tests if creating YAML files is enabled. +func (c *Config) BackupYaml() bool { + return !c.DisableBackups() +} diff --git a/internal/config/metadata_test.go b/internal/config/metadata_test.go new file mode 100644 index 000000000..dde5dec1e --- /dev/null +++ b/internal/config/metadata_test.go @@ -0,0 +1,43 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConfig_ExifBruteForce(t *testing.T) { + c := NewConfig(CliTestContext()) + assert.Equal(t, false, c.ExifBruteForce()) +} + +func TestConfig_ExifToolBin(t *testing.T) { + c := NewConfig(CliTestContext()) + + bin := c.ExifToolBin() + assert.Equal(t, "/usr/bin/exiftool", bin) +} + +func TestConfig_ExifToolJson(t *testing.T) { + c := NewConfig(CliTestContext()) + + assert.Equal(t, true, c.ExifToolJson()) + assert.Equal(t, c.DisableExifTool(), !c.ExifToolJson()) + + c.options.DisableExifTool = true + + assert.Equal(t, false, c.ExifToolJson()) + assert.Equal(t, c.DisableExifTool(), !c.ExifToolJson()) +} + +func TestConfig_SidecarYaml(t *testing.T) { + c := NewConfig(CliTestContext()) + + assert.Equal(t, true, c.BackupYaml()) + assert.Equal(t, c.DisableBackups(), !c.BackupYaml()) + + c.options.DisableBackups = true + + assert.Equal(t, false, c.BackupYaml()) + assert.Equal(t, c.DisableBackups(), !c.BackupYaml()) +} diff --git a/internal/config/options.go b/internal/config/options.go index 64f971295..528fac577 100644 --- a/internal/config/options.go +++ b/internal/config/options.go @@ -65,8 +65,6 @@ type Options struct { DisableBackups bool `yaml:"DisableBackups" json:"DisableBackups" flag:"disable-backups"` DisableSettings bool `yaml:"DisableSettings" json:"-" flag:"disable-settings"` DisablePlaces bool `yaml:"DisablePlaces" json:"DisablePlaces" flag:"disable-places"` - DisableExifTool bool `yaml:"DisableExifTool" json:"DisableExifTool" flag:"disable-exiftool"` - DisableFFmpeg bool `yaml:"DisableFFmpeg" json:"DisableFFmpeg" flag:"disable-ffmpeg"` DisableDarktable bool `yaml:"DisableDarktable" json:"DisableDarktable" flag:"disable-darktable"` DisableRawtherapee bool `yaml:"DisableRawtherapee" json:"DisableRawtherapee" flag:"disable-rawtherapee"` DisableSips bool `yaml:"DisableSips" json:"DisableSips" flag:"disable-sips"` @@ -74,6 +72,10 @@ type Options struct { DisableTensorFlow bool `yaml:"DisableTensorFlow" json:"DisableTensorFlow" flag:"disable-tensorflow"` DisableFaces bool `yaml:"DisableFaces" json:"DisableFaces" flag:"disable-faces"` DisableClassification bool `yaml:"DisableClassification" json:"DisableClassification" flag:"disable-classification"` + DisableFFmpeg bool `yaml:"DisableFFmpeg" json:"DisableFFmpeg" flag:"disable-ffmpeg"` + DisableExifTool bool `yaml:"DisableExifTool" json:"DisableExifTool" flag:"disable-exiftool"` + ExifBruteForce bool `yaml:"ExifBruteForce" json:"ExifBruteForce" flag:"exif-bruteforce"` + RawPresets bool `yaml:"RawPresets" json:"RawPresets" flag:"raw-presets"` DetectNSFW bool `yaml:"DetectNSFW" json:"DetectNSFW" flag:"detect-nsfw"` UploadNSFW bool `yaml:"UploadNSFW" json:"-" flag:"upload-nsfw"` DefaultTheme string `yaml:"DefaultTheme" json:"DefaultTheme" flag:"default-theme"` @@ -102,7 +104,6 @@ type Options struct { HttpPort int `yaml:"HttpPort" json:"-" flag:"http-port"` HttpMode string `yaml:"HttpMode" json:"-" flag:"http-mode"` HttpCompression string `yaml:"HttpCompression" json:"-" flag:"http-compression"` - RawPresets bool `yaml:"RawPresets" json:"RawPresets" flag:"raw-presets"` DarktableBin string `yaml:"DarktableBin" json:"-" flag:"darktable-bin"` DarktableBlacklist string `yaml:"DarktableBlacklist" json:"-" flag:"darktable-blacklist"` RawtherapeeBin string `yaml:"RawtherapeeBin" json:"-" flag:"rawtherapee-bin"` diff --git a/internal/config/test.go b/internal/config/test.go index 6359cc48d..40cd98496 100644 --- a/internal/config/test.go +++ b/internal/config/test.go @@ -63,6 +63,7 @@ func NewTestOptions() *Options { ReadOnly: false, DetectNSFW: true, UploadNSFW: false, + ExifBruteForce: false, AssetsPath: assetsPath, AutoIndex: -1, AutoImport: 7200, diff --git a/internal/meta/data.go b/internal/meta/data.go index 11d19118f..1fb90bdb7 100644 --- a/internal/meta/data.go +++ b/internal/meta/data.go @@ -12,7 +12,7 @@ const ( ImageTypeHDR = 3 // see https://exiftool.org/TagNames/Apple.html ) -// Data represents image meta data. +// Data represents image metadata. type Data struct { FileName string `meta:"FileName"` DocumentID string `meta:"BurstUUID,MediaGroupUUID,ImageUniqueID,OriginalDocumentID,DocumentID"` diff --git a/internal/meta/exif.go b/internal/meta/exif.go index 0121b4af7..1287f74c8 100644 --- a/internal/meta/exif.go +++ b/internal/meta/exif.go @@ -34,15 +34,15 @@ func init() { } } -// Exif parses an image file for Exif meta data and returns as Data struct. -func Exif(fileName string, fileType fs.FileFormat) (data Data, err error) { - err = data.Exif(fileName, fileType) +// Exif parses an image file for Exif metadata and returns as Data struct. +func Exif(fileName string, fileType fs.FileFormat, bruteForce bool) (data Data, err error) { + err = data.Exif(fileName, fileType, bruteForce) return data, err } -// Exif parses an image file for Exif meta data and returns as Data struct. -func (data *Data) Exif(fileName string, fileType fs.FileFormat) (err error) { +// Exif parses an image file for Exif metadata and returns as Data struct. +func (data *Data) Exif(fileName string, fileType fs.FileFormat, bruteForce bool) (err error) { exifMutex.Lock() defer exifMutex.Unlock() @@ -53,7 +53,7 @@ func (data *Data) Exif(fileName string, fileType fs.FileFormat) (err error) { }() // Extract raw Exif block. - rawExif, err := RawExif(fileName, fileType) + rawExif, err := RawExif(fileName, fileType, bruteForce) if err != nil { return err diff --git a/internal/meta/exif_parser.go b/internal/meta/exif_parser.go index 963d84762..a7011d61c 100644 --- a/internal/meta/exif_parser.go +++ b/internal/meta/exif_parser.go @@ -15,7 +15,7 @@ import ( "github.com/photoprism/photoprism/pkg/sanitize" ) -func RawExif(fileName string, fileType fs.FileFormat) (rawExif []byte, err error) { +func RawExif(fileName string, fileType fs.FileFormat, bruteForce bool) (rawExif []byte, err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("metadata: %s in %s (raw exif panic)\nstack: %s", e, sanitize.Log(filepath.Base(fileName)), debug.Stack()) @@ -25,8 +25,10 @@ func RawExif(fileName string, fileType fs.FileFormat) (rawExif []byte, err error // Extract raw Exif block. var parsed bool + // Sanitized and shortened file name for logs. logName := sanitize.Log(filepath.Base(fileName)) + // Try Exif parser for specific media file format first. if fileType == fs.FormatJpeg { jpegMp := jpegstructure.NewJpegMediaParser() @@ -38,7 +40,7 @@ func RawExif(fileName string, fileType fs.FileFormat) (rawExif []byte, err error _, rawExif, err = sl.Exif() if err != nil { - if strings.HasPrefix(err.Error(), "no exif header") { + if !bruteForce || strings.HasPrefix(err.Error(), "no exif header") { return rawExif, fmt.Errorf("metadata: found no exif header in %s (parse jpeg)", logName) } else if strings.HasPrefix(err.Error(), "no exif data") { log.Debugf("metadata: failed parsing %s, starting brute-force search (parse jpeg)", logName) @@ -109,9 +111,13 @@ func RawExif(fileName string, fileType fs.FileFormat) (rawExif []byte, err error parsed = true } } + } else { + log.Infof("metadata: no file format parser for %s, performing brute-force search", logName) + bruteForce = true } - if !parsed { + // Start brute-force search for Exif data? + if !parsed && bruteForce { rawExif, err = exif.SearchFileAndExtractExif(fileName) if err != nil { diff --git a/internal/meta/exif_test.go b/internal/meta/exif_test.go index 00ed32d69..0b93cf2c6 100644 --- a/internal/meta/exif_test.go +++ b/internal/meta/exif_test.go @@ -9,7 +9,7 @@ import ( func TestExif(t *testing.T) { t.Run("photoshop.jpg", func(t *testing.T) { - data, err := Exif("testdata/photoshop.jpg", fs.FormatJpeg) + data, err := Exif("testdata/photoshop.jpg", fs.FormatJpeg, true) if err != nil { t.Fatal(err) @@ -40,7 +40,7 @@ func TestExif(t *testing.T) { }) t.Run("ladybug.jpg", func(t *testing.T) { - data, err := Exif("testdata/ladybug.jpg", fs.FormatJpeg) + data, err := Exif("testdata/ladybug.jpg", fs.FormatJpeg, true) if err != nil { t.Fatal(err) @@ -72,7 +72,7 @@ func TestExif(t *testing.T) { }) t.Run("gopro_hd2.jpg", func(t *testing.T) { - data, err := Exif("testdata/gopro_hd2.jpg", fs.FormatJpeg) + data, err := Exif("testdata/gopro_hd2.jpg", fs.FormatJpeg, true) if err != nil { t.Fatal(err) @@ -102,7 +102,7 @@ func TestExif(t *testing.T) { }) t.Run("tweethog.png", func(t *testing.T) { - _, err := Exif("testdata/tweethog.png", fs.FormatPng) + _, err := Exif("testdata/tweethog.png", fs.FormatPng, true) if err == nil { t.Fatal("err should NOT be nil") @@ -112,7 +112,7 @@ func TestExif(t *testing.T) { }) t.Run("iphone_7.heic", func(t *testing.T) { - data, err := Exif("testdata/iphone_7.heic", fs.FormatHEIF) + data, err := Exif("testdata/iphone_7.heic", fs.FormatHEIF, true) if err != nil { t.Fatal(err) } @@ -133,7 +133,7 @@ func TestExif(t *testing.T) { }) t.Run("gps-2000.jpg", func(t *testing.T) { - data, err := Exif("testdata/gps-2000.jpg", fs.FormatJpeg) + data, err := Exif("testdata/gps-2000.jpg", fs.FormatJpeg, true) if err != nil { t.Fatal(err) @@ -161,7 +161,7 @@ func TestExif(t *testing.T) { }) t.Run("image-2011.jpg", func(t *testing.T) { - data, err := Exif("testdata/image-2011.jpg", fs.FormatJpeg) + data, err := Exif("testdata/image-2011.jpg", fs.FormatJpeg, true) if err != nil { t.Fatal(err) @@ -196,7 +196,7 @@ func TestExif(t *testing.T) { }) t.Run("ship.jpg", func(t *testing.T) { - data, err := Exif("testdata/ship.jpg", fs.FormatJpeg) + data, err := Exif("testdata/ship.jpg", fs.FormatJpeg, true) if err != nil { t.Fatal(err) @@ -217,7 +217,17 @@ func TestExif(t *testing.T) { }) t.Run("no-exif-data.jpg", func(t *testing.T) { - _, err := Exif("testdata/no-exif-data.jpg", fs.FormatJpeg) + _, err := Exif("testdata/no-exif-data.jpg", fs.FormatJpeg, false) + + if err == nil { + t.Fatal("err should NOT be nil") + } + + assert.Equal(t, "metadata: found no exif header in no-exif-data.jpg (parse jpeg)", err.Error()) + }) + + t.Run("no-exif-data.jpg/BruteForce", func(t *testing.T) { + _, err := Exif("testdata/no-exif-data.jpg", fs.FormatJpeg, true) if err == nil { t.Fatal("err should NOT be nil") @@ -227,7 +237,7 @@ func TestExif(t *testing.T) { }) t.Run("screenshot.png", func(t *testing.T) { - data, err := Exif("testdata/screenshot.png", fs.FormatPng) + data, err := Exif("testdata/screenshot.png", fs.FormatPng, true) if err != nil { t.Fatal(err) @@ -238,7 +248,7 @@ func TestExif(t *testing.T) { }) t.Run("orientation.jpg", func(t *testing.T) { - data, err := Exif("testdata/orientation.jpg", fs.FormatJpeg) + data, err := Exif("testdata/orientation.jpg", fs.FormatJpeg, true) if err != nil { t.Fatal(err) @@ -266,13 +276,19 @@ func TestExif(t *testing.T) { }) t.Run("gopher-preview.jpg", func(t *testing.T) { - _, err := Exif("testdata/gopher-preview.jpg", fs.FormatJpeg) + _, err := Exif("testdata/gopher-preview.jpg", fs.FormatJpeg, false) + + assert.EqualError(t, err, "metadata: found no exif header in gopher-preview.jpg (parse jpeg)") + }) + + t.Run("gopher-preview.jpg/BruteForce", func(t *testing.T) { + _, err := Exif("testdata/gopher-preview.jpg", fs.FormatJpeg, true) assert.EqualError(t, err, "metadata: found no exif header in gopher-preview.jpg (search and extract)") }) t.Run("huawei-gps-error.jpg", func(t *testing.T) { - data, err := Exif("testdata/huawei-gps-error.jpg", fs.FormatJpeg) + data, err := Exif("testdata/huawei-gps-error.jpg", fs.FormatJpeg, true) if err != nil { t.Fatal(err) @@ -293,7 +309,7 @@ func TestExif(t *testing.T) { }) t.Run("panorama360.jpg", func(t *testing.T) { - data, err := Exif("testdata/panorama360.jpg", fs.FormatJpeg) + data, err := Exif("testdata/panorama360.jpg", fs.FormatJpeg, true) if err != nil { t.Fatal(err) @@ -325,7 +341,7 @@ func TestExif(t *testing.T) { }) t.Run("exif-example.tiff", func(t *testing.T) { - data, err := Exif("testdata/exif-example.tiff", fs.FormatTiff) + data, err := Exif("testdata/exif-example.tiff", fs.FormatTiff, true) if err != nil { t.Fatal(err) @@ -357,7 +373,7 @@ func TestExif(t *testing.T) { }) t.Run("out-of-range-500.jpg", func(t *testing.T) { - data, err := Exif("testdata/out-of-range-500.jpg", fs.FormatJpeg) + data, err := Exif("testdata/out-of-range-500.jpg", fs.FormatJpeg, true) if err != nil { t.Fatal(err) @@ -389,7 +405,7 @@ func TestExif(t *testing.T) { }) t.Run("digikam.jpg", func(t *testing.T) { - data, err := Exif("testdata/digikam.jpg", fs.FormatJpeg) + data, err := Exif("testdata/digikam.jpg", fs.FormatJpeg, true) if err != nil { t.Fatal(err) @@ -424,7 +440,7 @@ func TestExif(t *testing.T) { }) t.Run("notebook.jpg", func(t *testing.T) { - data, err := Exif("testdata/notebook.jpg", fs.FormatJpeg) + data, err := Exif("testdata/notebook.jpg", fs.FormatJpeg, true) if err != nil { t.Fatal(err) @@ -445,7 +461,7 @@ func TestExif(t *testing.T) { }) t.Run("snow.jpg", func(t *testing.T) { - data, err := Exif("testdata/snow.jpg", fs.FormatJpeg) + data, err := Exif("testdata/snow.jpg", fs.FormatJpeg, true) if err != nil { t.Fatal(err) @@ -466,7 +482,7 @@ func TestExif(t *testing.T) { }) t.Run("keywords.jpg", func(t *testing.T) { - data, err := Exif("testdata/keywords.jpg", fs.FormatJpeg) + data, err := Exif("testdata/keywords.jpg", fs.FormatJpeg, true) if err != nil { t.Fatal(err) @@ -486,7 +502,7 @@ func TestExif(t *testing.T) { }) t.Run("Iceland-P3.jpg", func(t *testing.T) { - data, err := Exif("testdata/Iceland-P3.jpg", fs.FormatJpeg) + data, err := Exif("testdata/Iceland-P3.jpg", fs.FormatJpeg, true) if err != nil { t.Fatal(err) @@ -518,7 +534,7 @@ func TestExif(t *testing.T) { }) t.Run("Iceland-sRGB.jpg", func(t *testing.T) { - data, err := Exif("testdata/Iceland-sRGB.jpg", fs.FormatJpeg) + data, err := Exif("testdata/Iceland-sRGB.jpg", fs.FormatJpeg, true) if err != nil { t.Fatal(err) diff --git a/internal/photoprism/mediafile_meta.go b/internal/photoprism/mediafile_meta.go index c3aab72b0..441b99019 100644 --- a/internal/photoprism/mediafile_meta.go +++ b/internal/photoprism/mediafile_meta.go @@ -72,7 +72,7 @@ func (m *MediaFile) MetaData() (result meta.Data) { var err error if m.ExifSupported() { - err = m.metaData.Exif(m.FileName(), m.FileType()) + err = m.metaData.Exif(m.FileName(), m.FileType(), Config().ExifBruteForce()) } else { err = fmt.Errorf("exif not supported") }