From e61dda495a917797fc81df721352cf5f0907a101 Mon Sep 17 00:00:00 2001 From: Michael Mayer Date: Tue, 19 May 2020 11:00:17 +0200 Subject: [PATCH] Add PHOTOPRISM_SIDECAR_HIDDEN and PHOTOPRISM_JPEG_HIDDEN flags #311 Signed-off-by: Michael Mayer --- assets/config/photoprism.yml | 5 +++++ docker-compose.travis.yml | 9 ++++++++ docker-compose.yml | 11 ++++++++- docker/demo/Dockerfile | 10 +++++---- docker/photoprism/arm64/docker-compose.yml | 8 ++++--- docker/photoprism/docker-compose.yml | 8 ++++--- internal/api/photo.go | 7 +++--- internal/commands/config.go | 4 +++- internal/config/filenames.go | 5 +++++ internal/config/flags.go | 26 +++++++++++++++------- internal/config/params.go | 2 ++ internal/config/resample.go | 5 +++++ internal/config/test.go | 1 + internal/entity/file_test.go | 5 +++-- internal/entity/photo_yaml.go | 11 +++++++++ internal/meta/exif.go | 3 ++- internal/photoprism/convert.go | 10 ++++----- internal/photoprism/import.go | 2 +- internal/photoprism/import_worker.go | 2 +- internal/photoprism/index.go | 2 +- internal/photoprism/index_mediafile.go | 4 ++-- internal/photoprism/index_worker.go | 2 +- internal/photoprism/mediafile.go | 12 +++++----- internal/photoprism/mediafile_test.go | 4 ++-- internal/photoprism/metadata.go | 2 +- internal/photoprism/photoprism.go | 3 --- internal/photoprism/resample.go | 2 +- internal/remote/webdav/webdav_test.go | 2 +- internal/thumb/jpeg_test.go | 3 ++- pkg/fs/filetype.go | 5 +++++ pkg/fs/fs.go | 3 +++ 31 files changed, 125 insertions(+), 53 deletions(-) diff --git a/assets/config/photoprism.yml b/assets/config/photoprism.yml index 7b8f62f30..2258e73a5 100644 --- a/assets/config/photoprism.yml +++ b/assets/config/photoprism.yml @@ -26,3 +26,8 @@ database-dsn: root:photoprism@tcp(localhost:2343)/photoprism?parseTime=true pid-filename: ~/.local/share/photoprism/photoprism.pid log-filename: ~/.local/share/photoprism/photoprism.log detach-server: false +sidecar-json: false +sidecar-yaml: false +sidecar-hidden: true +jpeg-quality: 90 +jpeg-hidden: true diff --git a/docker-compose.travis.yml b/docker-compose.travis.yml index 704917a75..5cf09d4dd 100644 --- a/docker-compose.travis.yml +++ b/docker-compose.travis.yml @@ -29,6 +29,15 @@ services: PHOTOPRISM_SUBTITLE: "Browse your life" PHOTOPRISM_DESCRIPTION: "Personal Photo Management tested by Travis CI." PHOTOPRISM_AUTHOR: "PhotoPrism.org" + PHOTOPRISM_THUMB_FILTER: "lanczos" # Resample filter, best to worst: blackman, lanczos, cubic, linear + PHOTOPRISM_THUMB_UNCACHED: "true" # On-demand rendering of default thumbnails (high memory and cpu usage) + PHOTOPRISM_THUMB_SIZE: 2048 # Default thumbnail size limit (default 2048, min 720, max 3840) + PHOTOPRISM_THUMB_LIMIT: 3840 # On-demand thumbnail size limit (default 2048, min 720, max 3840) + PHOTOPRISM_JPEG_QUALITY: 90 # Use 95 for high-quality thumbnails (requires more storage) + PHOTOPRISM_JPEG_HIDDEN: "true" # Create JPEG files in .photoprism (when converting other file types) + PHOTOPRISM_SIDECAR_JSON: "true" # Read metadata from JSON sidecar files created by exiftool + PHOTOPRISM_SIDECAR_YAML: "true" # Backup photo metadata to YAML sidecar files + PHOTOPRISM_SIDECAR_HIDDEN: "true" # Create JSON and YAML sidecar files in .photoprism (if enabled) CODECOV_TOKEN: CODECOV_ENV: CODECOV_URL: diff --git a/docker-compose.yml b/docker-compose.yml index d3b60056e..8b8cad7c7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,6 +13,7 @@ services: - ".:/go/src/github.com/photoprism/photoprism" shm_size: "2gb" environment: + TF_CPP_MIN_LOG_LEVEL: 0 # Show TensorFlow log messages for development PHOTOPRISM_URL: "http://localhost:2342/" PHOTOPRISM_DEBUG: "true" PHOTOPRISM_READONLY: "false" @@ -39,7 +40,15 @@ services: PHOTOPRISM_TITLE: "PhotoPrism" PHOTOPRISM_SUBTITLE: "Browse your life" PHOTOPRISM_AUTHOR: "PhotoPrism.org" - TF_CPP_MIN_LOG_LEVEL: 0 + PHOTOPRISM_THUMB_FILTER: "lanczos" # Resample filter, best to worst: blackman, lanczos, cubic, linear + PHOTOPRISM_THUMB_UNCACHED: "true" # On-demand rendering of default thumbnails (high memory and cpu usage) + PHOTOPRISM_THUMB_SIZE: 2048 # Default thumbnail size limit (default 2048, min 720, max 3840) + PHOTOPRISM_THUMB_LIMIT: 3840 # On-demand thumbnail size limit (default 2048, min 720, max 3840) + PHOTOPRISM_JPEG_QUALITY: 90 # Use 95 for high-quality thumbnails (requires more storage) + PHOTOPRISM_JPEG_HIDDEN: "true" # Create JPEG files in .photoprism (when converting other file types) + PHOTOPRISM_SIDECAR_JSON: "true" # Read metadata from JSON sidecar files created by exiftool + PHOTOPRISM_SIDECAR_YAML: "true" # Backup photo metadata to YAML sidecar files + PHOTOPRISM_SIDECAR_HIDDEN: "true" # Create JSON and YAML sidecar files in .photoprism (if enabled) photoprism-db: image: mariadb:10.5 diff --git a/docker/demo/Dockerfile b/docker/demo/Dockerfile index 6a67f0ccb..efb66e950 100644 --- a/docker/demo/Dockerfile +++ b/docker/demo/Dockerfile @@ -16,14 +16,16 @@ ENV PHOTOPRISM_PUBLIC true ENV PHOTOPRISM_EXPERIMENTAL true ENV PHOTOPRISM_UPLOAD_NSFW false ENV PHOTOPRISM_DETECT_NSFW true -ENV PHOTOPRISM_JPEG_QUALITY 95 +ENV PHOTOPRISM_SIDECAR_JSON true +ENV PHOTOPRISM_SIDECAR_YAML false +ENV PHOTOPRISM_SIDECAR_HIDDEN true +ENV PHOTOPRISM_GEOCODING_API places ENV PHOTOPRISM_THUMB_FILTER lanczos ENV PHOTOPRISM_THUMB_UNCACHED true ENV PHOTOPRISM_THUMB_SIZE 3840 ENV PHOTOPRISM_THUMB_LIMIT 3840 -ENV PHOTOPRISM_GEOCODING_API places -ENV PHOTOPRISM_SIDECAR_JSON true -ENV PHOTOPRISM_SIDECAR_YAML false +ENV PHOTOPRISM_JPEG_QUALITY 95 +ENV PHOTOPRISM_JPEG_HIDDEN false # Import example photos RUN photoprism import diff --git a/docker/photoprism/arm64/docker-compose.yml b/docker/photoprism/arm64/docker-compose.yml index 791738879..71d55d181 100644 --- a/docker/photoprism/arm64/docker-compose.yml +++ b/docker/photoprism/arm64/docker-compose.yml @@ -43,14 +43,16 @@ services: PHOTOPRISM_DATABASE_DSN: "root:photoprism@tcp(localhost:2343)/photoprism?parseTime=true" # PHOTOPRISM_DATABASE_DRIVER: "mysql" # Using MariaDB or MySQL instead of the internal TiDB is optional # PHOTOPRISM_DATABASE_DSN: "photoprism:photoprism@tcp(photoprism-db:3306)/photoprism?parseTime=true" - PHOTOPRISM_JPEG_QUALITY: 90 # Use 95 for high-quality thumbnails (requires more storage) + # PHOTOPRISM_SIDECAR_JSON: "true" # Read metadata from JSON sidecar files created by exiftool + # PHOTOPRISM_SIDECAR_YAML: "true" # Backup photo metadata to YAML sidecar files + PHOTOPRISM_SIDECAR_HIDDEN: "true" # Create JSON and YAML sidecar files in .photoprism (if enabled) PHOTOPRISM_THUMB_FILTER: "lanczos" # Resample filter, best to worst: blackman, lanczos, cubic, linear PHOTOPRISM_THUMB_UNCACHED: "false" # On-demand rendering of default thumbnails (high memory and cpu usage) PHOTOPRISM_THUMB_SIZE: 2048 # Default thumbnail size limit (default 2048, min 720, max 3840) # PHOTOPRISM_THUMB_SIZE: 3840 # For retina screens (requires more storage) PHOTOPRISM_THUMB_LIMIT: 3840 # On-demand thumbnail size limit (default 2048, min 720, max 3840) - # PHOTOPRISM_SIDECAR_JSON: "true" # read metadata from json sidecar files created by exiftool - # PHOTOPRISM_SIDECAR_YAML: "true" # backup photo metadata to yaml sidecar files + PHOTOPRISM_JPEG_QUALITY: 90 # Use 95 for high-quality thumbnails (requires more storage) + PHOTOPRISM_JPEG_HIDDEN: "true" # Create JPEG files in .photoprism (when converting other file types) volumes: - "~/Pictures/Originals:/photoprism/originals" # [local path]:[container path] - "~/Pictures/Import:/photoprism/import" # [local path]:[container path] (optional) diff --git a/docker/photoprism/docker-compose.yml b/docker/photoprism/docker-compose.yml index f7d46435c..0bfd2bfef 100644 --- a/docker/photoprism/docker-compose.yml +++ b/docker/photoprism/docker-compose.yml @@ -42,14 +42,16 @@ services: PHOTOPRISM_DATABASE_DSN: "root:photoprism@tcp(localhost:2343)/photoprism?parseTime=true" # PHOTOPRISM_DATABASE_DRIVER: "mysql" # Using MariaDB or MySQL instead of the internal TiDB is optional # PHOTOPRISM_DATABASE_DSN: "photoprism:photoprism@tcp(photoprism-db:3306)/photoprism?parseTime=true" - PHOTOPRISM_JPEG_QUALITY: 90 # Use 95 for high-quality thumbnails (requires more storage) + # PHOTOPRISM_SIDECAR_JSON: "true" # Read metadata from JSON sidecar files created by exiftool + # PHOTOPRISM_SIDECAR_YAML: "true" # Backup photo metadata to YAML sidecar files + PHOTOPRISM_SIDECAR_HIDDEN: "true" # Create JSON and YAML sidecar files in .photoprism (if enabled) PHOTOPRISM_THUMB_FILTER: "lanczos" # Resample filter, best to worst: blackman, lanczos, cubic, linear PHOTOPRISM_THUMB_UNCACHED: "false" # On-demand rendering of default thumbnails (high memory and cpu usage) PHOTOPRISM_THUMB_SIZE: 2048 # Default thumbnail size limit (default 2048, min 720, max 3840) # PHOTOPRISM_THUMB_SIZE: 3840 # For retina screens (requires more storage) PHOTOPRISM_THUMB_LIMIT: 3840 # On-demand thumbnail size limit (default 2048, min 720, max 3840) - # PHOTOPRISM_SIDECAR_JSON: "true" # read metadata from json sidecar files created by exiftool - # PHOTOPRISM_SIDECAR_YAML: "true" # backup photo metadata to yaml sidecar files + PHOTOPRISM_JPEG_QUALITY: 90 # Use 95 for high-quality thumbnails (requires more storage) + PHOTOPRISM_JPEG_HIDDEN: "true" # Create JPEG files in .photoprism (when converting other file types) volumes: - "~/Pictures/Originals:/photoprism/originals" # [local path]:[container path] - "~/Pictures/Import:/photoprism/import" # [local path]:[container path] (optional) diff --git a/internal/api/photo.go b/internal/api/photo.go index 3663ed5cb..4fe26966d 100644 --- a/internal/api/photo.go +++ b/internal/api/photo.go @@ -4,14 +4,12 @@ import ( "fmt" "net/http" "path" - "path/filepath" "github.com/gin-gonic/gin" "github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/form" - "github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/txt" @@ -21,7 +19,8 @@ import ( func SavePhotoAsYaml(p entity.Photo, conf *config.Config) { // Write YAML sidecar file (optional). if conf.SidecarYaml() { - yamlFile := filepath.Join(conf.OriginalsPath(), p.PhotoPath, photoprism.HiddenPath, p.PhotoName) + ".yml" + yamlFile := p.YamlFileName(conf.OriginalsPath(), conf.SidecarHidden()) + if err := p.SaveAsYaml(yamlFile); err != nil { log.Errorf("photo: %s (update yaml)", err) } else { @@ -168,7 +167,7 @@ func GetPhotoYaml(router *gin.RouterGroup, conf *config.Config) { } if c.Query("download") != "" { - c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", c.Param("uuid")+".yml")) + c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", c.Param("uuid")+fs.YamlExt)) } c.Data(http.StatusOK, "text/x-yaml; charset=utf-8", data) diff --git a/internal/commands/config.go b/internal/commands/config.go index 84ddfb81f..a42084034 100644 --- a/internal/commands/config.go +++ b/internal/commands/config.go @@ -94,17 +94,19 @@ func configAction(ctx *cli.Context) error { fmt.Printf("%-25s %s\n", "exiftool-bin", conf.ExifToolBin()) fmt.Printf("%-25s %t\n", "sidecar-json", conf.SidecarJson()) fmt.Printf("%-25s %t\n", "sidecar-yaml", conf.SidecarYaml()) + fmt.Printf("%-25s %t\n", "sidecar-hidden", conf.SidecarHidden()) // Places / Geocoding API fmt.Printf("%-25s %s\n", "geocoding-api", conf.GeoCodingApi()) // Thumbnails - fmt.Printf("%-25s %d\n", "jpeg-quality", conf.JpegQuality()) fmt.Printf("%-25s %s\n", "thumb-filter", conf.ThumbFilter()) fmt.Printf("%-25s %t\n", "thumb-uncached", conf.ThumbUncached()) fmt.Printf("%-25s %d\n", "thumb-size", conf.ThumbSize()) fmt.Printf("%-25s %d\n", "thumb-limit", conf.ThumbLimit()) fmt.Printf("%-25s %s\n", "thumb-path", conf.ThumbPath()) + fmt.Printf("%-25s %d\n", "jpeg-quality", conf.JpegQuality()) + fmt.Printf("%-25s %t\n", "jpeg-hidden", conf.JpegHidden()) return nil } diff --git a/internal/config/filenames.go b/internal/config/filenames.go index 045fb5d48..582536fa5 100644 --- a/internal/config/filenames.go +++ b/internal/config/filenames.go @@ -165,6 +165,11 @@ func (c *Config) SidecarYaml() bool { return c.params.SidecarYaml } +// SidecarHidden returns true if new sidecar files should be created in a .photoprism sub directory (hidden). +func (c *Config) SidecarHidden() bool { + return c.params.SidecarHidden +} + // HeifConvertBin returns the heif-convert executable file name. func (c *Config) HeifConvertBin() string { return findExecutable(c.params.HeifConvertBin, "heif-convert") diff --git a/internal/config/flags.go b/internal/config/flags.go index 739bc1b38..779620931 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -175,14 +175,19 @@ var GlobalFlags = []cli.Flag{ }, cli.BoolFlag{ Name: "sidecar-json, j", - Usage: "read metadata from json sidecar files created by exiftool", + Usage: "read metadata from JSON sidecar files created by exiftool", EnvVar: "PHOTOPRISM_SIDECAR_JSON", }, cli.BoolFlag{ Name: "sidecar-yaml, y", - Usage: "backup photo metadata to yaml sidecar files", + Usage: "backup photo metadata to YAML sidecar files", EnvVar: "PHOTOPRISM_SIDECAR_YAML", }, + cli.BoolFlag{ + Name: "sidecar-hidden", + Usage: "create JSON and YAML sidecar files in .photoprism if enabled", + EnvVar: "PHOTOPRISM_SIDECAR_HIDDEN", + }, cli.IntFlag{ Name: "http-port", Value: 2342, @@ -248,12 +253,6 @@ var GlobalFlags = []cli.Flag{ Value: "places", EnvVar: "PHOTOPRISM_GEOCODING_API", }, - cli.IntFlag{ - Name: "jpeg-quality, q", - Usage: "set to 95 for high-quality thumbnails (25-100)", - Value: 90, - EnvVar: "PHOTOPRISM_JPEG_QUALITY", - }, cli.StringFlag{ Name: "thumb-filter, f", Usage: "resample filter (best to worst: blackman, lanczos, cubic, linear)", @@ -277,6 +276,17 @@ var GlobalFlags = []cli.Flag{ Value: 3840, EnvVar: "PHOTOPRISM_THUMB_LIMIT", }, + cli.IntFlag{ + Name: "jpeg-quality, q", + Usage: "set to 95 for high-quality thumbnails (25-100)", + Value: 90, + EnvVar: "PHOTOPRISM_JPEG_QUALITY", + }, + cli.BoolFlag{ + Name: "jpeg-hidden", + Usage: "create JPEG files in .photoprism when converting other file types", + EnvVar: "PHOTOPRISM_JPEG_HIDDEN", + }, cli.BoolFlag{ Name: "disable-tf", Usage: "don't use TensorFlow for image classification", diff --git a/internal/config/params.go b/internal/config/params.go index 6ec68a3b1..919da865e 100644 --- a/internal/config/params.go +++ b/internal/config/params.go @@ -70,12 +70,14 @@ type Params struct { ExifToolBin string `yaml:"exiftool-bin" flag:"exiftool-bin"` SidecarJson bool `yaml:"sidecar-json" flag:"sidecar-json"` SidecarYaml bool `yaml:"sidecar-yaml" flag:"sidecar-yaml"` + SidecarHidden bool `yaml:"sidecar-hidden" flag:"sidecar-hidden"` PIDFilename string `yaml:"pid-filename" flag:"pid-filename"` LogFilename string `yaml:"log-filename" flag:"log-filename"` DetachServer bool `yaml:"detach-server" flag:"detach-server"` DetectNSFW bool `yaml:"detect-nsfw" flag:"detect-nsfw"` UploadNSFW bool `yaml:"upload-nsfw" flag:"upload-nsfw"` GeoCodingApi string `yaml:"geocoding-api" flag:"geocoding-api"` + JpegHidden bool `yaml:"jpeg-hidden" flag:"jpeg-hidden"` JpegQuality int `yaml:"jpeg-quality" flag:"jpeg-quality"` ThumbFilter string `yaml:"thumb-filter" flag:"thumb-filter"` ThumbUncached bool `yaml:"thumb-uncached" flag:"thumb-uncached"` diff --git a/internal/config/resample.go b/internal/config/resample.go index 335c66f7a..c0523b8ac 100644 --- a/internal/config/resample.go +++ b/internal/config/resample.go @@ -6,6 +6,11 @@ import ( "github.com/photoprism/photoprism/internal/thumb" ) +// JpegHidden returns true if JPEG files should be created in a .photoprism sub directory (hidden). +func (c *Config) JpegHidden() bool { + return c.params.JpegHidden +} + // JpegQuality returns the jpeg quality for resampling, use 95 for high-quality thumbs (25-100). func (c *Config) JpegQuality() int { if c.params.JpegQuality > 100 { diff --git a/internal/config/test.go b/internal/config/test.go index 72630567f..2e72a2520 100644 --- a/internal/config/test.go +++ b/internal/config/test.go @@ -43,6 +43,7 @@ func NewTestParams() *Params { ReadOnly: false, DetectNSFW: true, UploadNSFW: false, + SidecarHidden: true, DarktableBin: "/usr/bin/darktable-cli", ExifToolBin: "/usr/bin/exiftool", AssetsPath: assetsPath, diff --git a/internal/entity/file_test.go b/internal/entity/file_test.go index f17b3b644..6ce2bc157 100644 --- a/internal/entity/file_test.go +++ b/internal/entity/file_test.go @@ -4,6 +4,7 @@ import ( "testing" "time" + "github.com/photoprism/photoprism/pkg/fs" "github.com/stretchr/testify/assert" ) @@ -30,7 +31,7 @@ func TestFile_DownloadFileName(t *testing.T) { filename := file.ShareFileName() assert.Contains(t, filename, "20190115-000000-Berlin-Morning-Mood") - assert.Contains(t, filename, ".jpg") + assert.Contains(t, filename, fs.JpegExt) }) t.Run("photo without title", func(t *testing.T) { photo := &Photo{TakenAtLocal: time.Date(2019, 01, 15, 0, 0, 0, 0, time.UTC), PhotoTitle: ""} @@ -39,7 +40,7 @@ func TestFile_DownloadFileName(t *testing.T) { filename := file.ShareFileName() assert.Contains(t, filename, "20190115-000000-123") - assert.Contains(t, filename, ".jpg") + assert.Contains(t, filename, fs.JpegExt) }) t.Run("photo without photo", func(t *testing.T) { file := &File{Photo: nil, FileType: "jpg", FileHash: "123Hash", FileUUID: "foobar345678765"} diff --git a/internal/entity/photo_yaml.go b/internal/entity/photo_yaml.go index 59dbc2d7f..36c59e79e 100644 --- a/internal/entity/photo_yaml.go +++ b/internal/entity/photo_yaml.go @@ -3,7 +3,9 @@ package entity import ( "io/ioutil" "os" + "path/filepath" + "github.com/photoprism/photoprism/pkg/fs" "gopkg.in/yaml.v2" ) @@ -53,3 +55,12 @@ func (m *Photo) LoadFromYaml(fileName string) error { return nil } + +// YamlFileName returns the YAML backup file name. +func (m *Photo) YamlFileName(originalsPath string, hidden bool) string { + if hidden { + return filepath.Join(originalsPath, m.PhotoPath, fs.HiddenPath, m.PhotoName) + fs.YamlExt + } + + return filepath.Join(originalsPath, m.PhotoPath, m.PhotoName) + fs.YamlExt +} diff --git a/internal/meta/exif.go b/internal/meta/exif.go index 4cf711d36..1b983ef29 100644 --- a/internal/meta/exif.go +++ b/internal/meta/exif.go @@ -13,6 +13,7 @@ import ( "github.com/dsoprea/go-exif/v2/common" "github.com/dsoprea/go-jpeg-image-structure" "github.com/dsoprea/go-png-image-structure" + "github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/txt" "gopkg.in/ugjka/go-tz.v2/tz" ) @@ -51,7 +52,7 @@ func (data *Data) Exif(fileName string) (err error) { fileExtension := strings.ToLower(path.Ext(fileName)) - if fileExtension == ".jpg" || fileExtension == ".jpeg" { + if fileExtension == fs.JpegExt || fileExtension == ".jpeg" { jmp := jpegstructure.NewJpegMediaParser() sl, err := jmp.ParseFile(fileName) diff --git a/internal/photoprism/convert.go b/internal/photoprism/convert.go index babf0f4a9..e0c02d55b 100644 --- a/internal/photoprism/convert.go +++ b/internal/photoprism/convert.go @@ -52,7 +52,7 @@ func (c *Convert) Start(path string) error { } done := make(map[string]bool) - ignore := fs.NewIgnoreList(IgnoreFile, true, false) + ignore := fs.NewIgnoreList(fs.IgnoreFile, true, false) if err := ignore.Dir(path); err != nil { log.Infof("convert: %s", err) @@ -136,7 +136,7 @@ func (c *Convert) ConvertCommand(mf *MediaFile, jpegName string, xmpName string) // ToJson uses exiftool to export metadata to a json file. func (c *Convert) ToJson(mf *MediaFile, hidden bool) (*MediaFile, error) { - jsonName := fs.TypeJson.FindSub(mf.FileName(), HiddenPath, c.conf.Settings().Index.Group) + jsonName := fs.TypeJson.FindSub(mf.FileName(), fs.HiddenPath, c.conf.Settings().Index.Group) result, err := NewMediaFile(jsonName) @@ -202,7 +202,7 @@ func (c *Convert) ToJpeg(image *MediaFile, hidden bool) (*MediaFile, error) { return image, nil } - jpegName := fs.TypeJpeg.FindSub(image.FileName(), HiddenPath, c.conf.Settings().Index.Group) + jpegName := fs.TypeJpeg.FindSub(image.FileName(), fs.HiddenPath, c.conf.Settings().Index.Group) mediaFile, err := NewMediaFile(jpegName) @@ -215,9 +215,9 @@ func (c *Convert) ToJpeg(image *MediaFile, hidden bool) (*MediaFile, error) { } if hidden { - jpegName = image.HiddenName(".jpg", c.conf.Settings().Index.Group) + jpegName = image.HiddenName(fs.JpegExt, c.conf.Settings().Index.Group) } else { - jpegName = image.RelatedName(".jpg", c.conf.Settings().Index.Group) + jpegName = image.RelatedName(fs.JpegExt, c.conf.Settings().Index.Group) } fileName := image.RelativeName(c.conf.OriginalsPath()) diff --git a/internal/photoprism/import.go b/internal/photoprism/import.go index 201954b24..4b72e16e8 100644 --- a/internal/photoprism/import.go +++ b/internal/photoprism/import.go @@ -84,7 +84,7 @@ func (imp *Import) Start(opt ImportOptions) map[string]bool { } indexOpt := IndexOptionsAll() - ignore := fs.NewIgnoreList(IgnoreFile, true, false) + ignore := fs.NewIgnoreList(fs.IgnoreFile, true, false) if err := ignore.Dir(importPath); err != nil { log.Infof("import: %s", err) diff --git a/internal/photoprism/import_worker.go b/internal/photoprism/import_worker.go index 5cde1a953..03a9a9050 100644 --- a/internal/photoprism/import_worker.go +++ b/internal/photoprism/import_worker.go @@ -103,7 +103,7 @@ func ImportWorker(jobs <-chan ImportJob) { } if imp.conf.SidecarJson() && !f.HasJson() { - if jsonFile, err := imp.convert.ToJson(f, true); err != nil { + if jsonFile, err := imp.convert.ToJson(f, imp.conf.SidecarHidden()); err != nil { log.Errorf("import: creating json sidecar file failed (%s)", err.Error()) } else { log.Infof("import: %s created", fs.RelativeName(jsonFile.FileName(), imp.originalsPath())) diff --git a/internal/photoprism/index.go b/internal/photoprism/index.go index 49ac9ef6b..f03cc8695 100644 --- a/internal/photoprism/index.go +++ b/internal/photoprism/index.go @@ -94,7 +94,7 @@ func (ind *Index) Start(opt IndexOptions) map[string]bool { }() } - ignore := fs.NewIgnoreList(IgnoreFile, true, false) + ignore := fs.NewIgnoreList(fs.IgnoreFile, true, false) if err := ignore.Dir(originalsPath); err != nil { log.Infof("index: %s", err) diff --git a/internal/photoprism/index_mediafile.go b/internal/photoprism/index_mediafile.go index 8aee16d52..e75794fe6 100644 --- a/internal/photoprism/index_mediafile.go +++ b/internal/photoprism/index_mediafile.go @@ -350,7 +350,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) ( return result } } else { - if yamlName := fs.TypeYaml.FindSub(m.FileName(), HiddenPath, ind.conf.Settings().Index.Group); yamlName != "" { + if yamlName := fs.TypeYaml.FindSub(m.FileName(), fs.HiddenPath, ind.conf.Settings().Index.Group); yamlName != "" { if err := photo.LoadFromYaml(yamlName); err != nil { log.Errorf("index: %s (restore from yaml)", err.Error()) } else { @@ -491,7 +491,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) ( // Write YAML sidecar file (optional). if file.FilePrimary && ind.conf.SidecarYaml() { - yamlFile := m.HiddenName(".yml", ind.conf.Settings().Index.Group) + yamlFile := photo.YamlFileName(ind.originalsPath(), ind.conf.SidecarHidden()) if err := photo.SaveAsYaml(yamlFile); err != nil { log.Errorf("index: %s (update yaml)", err.Error()) diff --git a/internal/photoprism/index_worker.go b/internal/photoprism/index_worker.go index ace949680..6e60aab96 100644 --- a/internal/photoprism/index_worker.go +++ b/internal/photoprism/index_worker.go @@ -44,7 +44,7 @@ func IndexWorker(jobs <-chan IndexJob) { } if ind.conf.SidecarJson() && !f.HasJson() { - if jsonFile, err := ind.convert.ToJson(f, true); err != nil { + if jsonFile, err := ind.convert.ToJson(f, ind.conf.SidecarHidden()); err != nil { log.Errorf("index: creating json sidecar file failed (%s)", err.Error()) } else { log.Infof("index: %s created", fs.RelativeName(jsonFile.FileName(), ind.originalsPath())) diff --git a/internal/photoprism/mediafile.go b/internal/photoprism/mediafile.go index fd56f1477..b2d0a13f8 100644 --- a/internal/photoprism/mediafile.go +++ b/internal/photoprism/mediafile.go @@ -311,7 +311,7 @@ func (m *MediaFile) RelatedFiles(stripSequence bool) (result RelatedFiles, err e // Add hidden JPEG if exists. if !result.ContainsJpeg() && result.Main != nil { - if jpegName := fs.TypeJpeg.FindSub(result.Main.FileName(), HiddenPath, stripSequence); jpegName != "" { + if jpegName := fs.TypeJpeg.FindSub(result.Main.FileName(), fs.HiddenPath, stripSequence); jpegName != "" { if resultFile, err := NewMediaFile(jpegName); err == nil { result.Files = append(result.Files, resultFile) } @@ -357,7 +357,7 @@ func (m *MediaFile) RelativePath(directory string) string { } // Remove hidden sub directory if exists. - if path.Base(pathname) == HiddenPath { + if path.Base(pathname) == fs.HiddenPath { pathname = path.Dir(pathname) } @@ -395,7 +395,7 @@ func (m *MediaFile) AbsBase(stripSequence bool) string { // HiddenName returns the a filename with the same base name and a given extension in a hidden sub directory. func (m *MediaFile) HiddenName(fileExt string, stripSequence bool) string { - return fs.SubFileName(m.FileName(), HiddenPath, fileExt, stripSequence) + return fs.SubFileName(m.FileName(), fs.HiddenPath, fileExt, stripSequence) } // RelatedName returns the a filename with the same base name and a given extension in the same directory. @@ -611,7 +611,7 @@ func (m *MediaFile) Jpeg() (*MediaFile, error) { return m, nil } - jpegFilename := fs.TypeJpeg.FindSub(m.FileName(), HiddenPath, false) + jpegFilename := fs.TypeJpeg.FindSub(m.FileName(), fs.HiddenPath, false) if jpegFilename == "" { return nil, fmt.Errorf("no jpeg found for %s", m.FileName()) @@ -626,7 +626,7 @@ func (m *MediaFile) HasJpeg() bool { return true } - return fs.TypeJpeg.FindSub(m.FileName(), HiddenPath, false) != "" + return fs.TypeJpeg.FindSub(m.FileName(), fs.HiddenPath, false) != "" } // HasJson returns true if this file has or is a json sidecar file. @@ -635,7 +635,7 @@ func (m *MediaFile) HasJson() bool { return true } - return fs.TypeJson.FindSub(m.FileName(), HiddenPath, false) != "" + return fs.TypeJson.FindSub(m.FileName(), fs.HiddenPath, false) != "" } func (m *MediaFile) decodeDimensions() error { diff --git a/internal/photoprism/mediafile_test.go b/internal/photoprism/mediafile_test.go index db3dd7d94..816ac8a5f 100644 --- a/internal/photoprism/mediafile_test.go +++ b/internal/photoprism/mediafile_test.go @@ -450,7 +450,7 @@ func TestMediaFile_RelativePath(t *testing.T) { assert.Equal(t, conf.ExamplesPath(), path) }) t.Run("hidden root", func(t *testing.T) { - path := mediaFile.RelativePath(filepath.Join(conf.ExamplesPath(), HiddenPath)) + path := mediaFile.RelativePath(filepath.Join(conf.ExamplesPath(), fs.HiddenPath)) assert.Equal(t, "", path) }) } @@ -639,7 +639,7 @@ func TestMediaFile_Extension(t *testing.T) { mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/elephants.jpg") assert.Nil(t, err) - assert.Equal(t, ".jpg", mediaFile.Extension()) + assert.Equal(t, fs.JpegExt, mediaFile.Extension()) }) } diff --git a/internal/photoprism/metadata.go b/internal/photoprism/metadata.go index 4013f0c61..cf6a77f7c 100644 --- a/internal/photoprism/metadata.go +++ b/internal/photoprism/metadata.go @@ -13,7 +13,7 @@ func (m *MediaFile) MetaData() (result meta.Data, err error) { m.metaDataOnce.Do(func() { err = m.metaData.Exif(m.FileName()) - if jsonFile := fs.TypeJson.FindSub(m.FileName(), HiddenPath, false); jsonFile == "" { + if jsonFile := fs.TypeJson.FindSub(m.FileName(), fs.HiddenPath, false); jsonFile == "" { log.Debugf("mediafile: no json sidecar file found for %s", txt.Quote(filepath.Base(m.FileName()))) } else if jsonErr := m.metaData.JSON(jsonFile); jsonErr != nil { log.Warn(jsonErr) diff --git a/internal/photoprism/photoprism.go b/internal/photoprism/photoprism.go index a1f267dbc..692fcc320 100644 --- a/internal/photoprism/photoprism.go +++ b/internal/photoprism/photoprism.go @@ -12,6 +12,3 @@ import ( ) var log = event.Log - -const IgnoreFile = ".ppignore" -const HiddenPath = ".photoprism" diff --git a/internal/photoprism/resample.go b/internal/photoprism/resample.go index feb69c9ac..d42b7c8e9 100644 --- a/internal/photoprism/resample.go +++ b/internal/photoprism/resample.go @@ -47,7 +47,7 @@ func (rs *Resample) Start(force bool) error { } done := make(map[string]bool) - ignore := fs.NewIgnoreList(IgnoreFile, true, false) + ignore := fs.NewIgnoreList(fs.IgnoreFile, true, false) if err := ignore.Dir(originalsPath); err != nil { log.Infof("resample: %s", err) diff --git a/internal/remote/webdav/webdav_test.go b/internal/remote/webdav/webdav_test.go index 15074dd99..88a6ae70b 100644 --- a/internal/remote/webdav/webdav_test.go +++ b/internal/remote/webdav/webdav_test.go @@ -139,7 +139,7 @@ func TestClient_UploadAndDelete(t *testing.T) { assert.IsType(t, Client{}, c) - tempName := rnd.UUID() + ".jpg" + tempName := rnd.UUID() + fs.JpegExt if err := c.Upload("testdata/example.jpg", tempName); err != nil { t.Fatal(err) diff --git a/internal/thumb/jpeg_test.go b/internal/thumb/jpeg_test.go index a9184bbac..6d6d48fcd 100644 --- a/internal/thumb/jpeg_test.go +++ b/internal/thumb/jpeg_test.go @@ -4,6 +4,7 @@ import ( "os" "testing" + "github.com/photoprism/photoprism/pkg/fs" "github.com/stretchr/testify/assert" ) @@ -13,7 +14,7 @@ func TestJpeg(t *testing.T) { for _, ext := range formats { t.Run(ext, func(t *testing.T) { src := "testdata/example." + ext - dst := "testdata/example." + ext + ".jpg" + dst := "testdata/example." + ext + fs.JpegExt assert.NoFileExists(t, dst) diff --git a/pkg/fs/filetype.go b/pkg/fs/filetype.go index eac3d4f4a..d4d4c59f4 100644 --- a/pkg/fs/filetype.go +++ b/pkg/fs/filetype.go @@ -36,6 +36,11 @@ const ( type FileExtensions map[string]FileType type TypeExtensions map[FileType][]string +const ( + YamlExt = ".yml" + JpegExt = ".jpg" +) + // FileExt contains the filename extensions of file formats known to PhotoPrism. var FileExt = FileExtensions{ ".bmp": TypeBitmap, diff --git a/pkg/fs/fs.go b/pkg/fs/fs.go index e76fe439f..2cc5decf3 100644 --- a/pkg/fs/fs.go +++ b/pkg/fs/fs.go @@ -18,6 +18,9 @@ import ( "strings" ) +const IgnoreFile = ".ppignore" +const HiddenPath = ".photoprism" + // FileExists returns true if file exists and is not a directory. func FileExists(fileName string) bool { if fileName == "" {