diff --git a/assets/static/img/wallpaper/kashmir.jpg b/assets/static/img/wallpaper/kashmir.jpg new file mode 100644 index 000000000..65061c775 Binary files /dev/null and b/assets/static/img/wallpaper/kashmir.jpg differ diff --git a/assets/templates/index.tmpl b/assets/templates/index.tmpl index 4557bb8ce..a6d4e9948 100644 --- a/assets/templates/index.tmpl +++ b/assets/templates/index.tmpl @@ -4,7 +4,7 @@ - {{ .config.SiteTitle }} + {{if and .config.SiteCaption .config.Sponsor }}{{ .config.SiteCaption }}{{else}}{{ .config.Name }}{{end}} diff --git a/cmd/photoprism/photoprism.go b/cmd/photoprism/photoprism.go index 634daf57b..433aa25d2 100644 --- a/cmd/photoprism/photoprism.go +++ b/cmd/photoprism/photoprism.go @@ -28,10 +28,11 @@ import ( "os" "path/filepath" + "github.com/urfave/cli" + "github.com/photoprism/photoprism/internal/commands" "github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/event" - "github.com/urfave/cli" ) var version = "development" @@ -55,7 +56,7 @@ func main() { app.Version = version app.Copyright = appCopyright app.EnableBashCompletion = true - app.Flags = config.GlobalFlags + app.Flags = config.Flags.Cli() app.Commands = commands.PhotoPrism if err := app.Run(os.Args); err != nil { diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml index 4a2c20ada..6d041876d 100644 --- a/docker-compose.ci.yml +++ b/docker-compose.ci.yml @@ -17,7 +17,6 @@ services: - "~/.cache/go-mod:/go/pkg/mod" environment: PHOTOPRISM_SITE_URL: "http://localhost:2342/" - PHOTOPRISM_SITE_TITLE: "PhotoPrism" PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App" PHOTOPRISM_SITE_DESCRIPTION: "Open-Source Photo Management" PHOTOPRISM_SITE_AUTHOR: "@photoprism_app" diff --git a/docker-compose.latest.yml b/docker-compose.latest.yml index 6ef38a0b5..4969e1be6 100644 --- a/docker-compose.latest.yml +++ b/docker-compose.latest.yml @@ -24,7 +24,6 @@ services: PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # initial "admin" password (minimum 8 characters) ## Public server URL incl http:// or https:// and /path, :port is optional PHOTOPRISM_SITE_URL: "https://latest.localssl.dev/" - PHOTOPRISM_SITE_TITLE: "PhotoPrism" PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App" PHOTOPRISM_SITE_DESCRIPTION: "Open-Source Photo Management" PHOTOPRISM_SITE_AUTHOR: "@photoprism_app" diff --git a/docker-compose.local.yml b/docker-compose.local.yml index 8e9e1d430..0ea49d34d 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -24,7 +24,6 @@ services: PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # initial "admin" password (minimum 8 characters) ## Public server URL incl http:// or https:// and /path, :port is optional PHOTOPRISM_SITE_URL: "https://latest.localssl.dev/" - PHOTOPRISM_SITE_TITLE: "PhotoPrism" PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App" PHOTOPRISM_SITE_DESCRIPTION: "Open-Source Photo Management" PHOTOPRISM_SITE_AUTHOR: "@photoprism_app" diff --git a/docker-compose.postgres.yml b/docker-compose.postgres.yml index dc7d9ed44..8c2e4ac28 100644 --- a/docker-compose.postgres.yml +++ b/docker-compose.postgres.yml @@ -26,7 +26,6 @@ services: shm_size: "2gb" environment: PHOTOPRISM_SITE_URL: "http://localhost:2342/" - PHOTOPRISM_SITE_TITLE: "PhotoPrism" PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App" PHOTOPRISM_SITE_DESCRIPTION: "Open-Source Photo Management" PHOTOPRISM_SITE_AUTHOR: "@photoprism_app" diff --git a/docker-compose.yml b/docker-compose.yml index f02a521c2..d68032e82 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,7 +36,6 @@ services: PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # initial "admin" password (minimum 8 characters) ## External development server URL incl http:// or https:// and /path, :port is optional PHOTOPRISM_SITE_URL: "https://app.localssl.dev/" - PHOTOPRISM_SITE_TITLE: "PhotoPrism" PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App" PHOTOPRISM_SITE_DESCRIPTION: "Tags and finds pictures without getting in your way!" PHOTOPRISM_SITE_AUTHOR: "@photoprism_app" diff --git a/docker/examples/arm64/docker-compose.yml b/docker/examples/arm64/docker-compose.yml index 869de7a49..13b242eb3 100644 --- a/docker/examples/arm64/docker-compose.yml +++ b/docker/examples/arm64/docker-compose.yml @@ -86,7 +86,6 @@ services: PHOTOPRISM_DATABASE_NAME: "photoprism" # MariaDB or MySQL database schema name PHOTOPRISM_DATABASE_USER: "photoprism" # MariaDB or MySQL database user name PHOTOPRISM_DATABASE_PASSWORD: "insecure" # MariaDB or MySQL database user password - PHOTOPRISM_SITE_TITLE: "PhotoPrism" PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App" PHOTOPRISM_SITE_DESCRIPTION: "" PHOTOPRISM_SITE_AUTHOR: "" diff --git a/docker/examples/armv7/docker-compose.yml b/docker/examples/armv7/docker-compose.yml index c4ecf038e..7357dadb5 100644 --- a/docker/examples/armv7/docker-compose.yml +++ b/docker/examples/armv7/docker-compose.yml @@ -81,7 +81,6 @@ services: PHOTOPRISM_DATABASE_NAME: "photoprism" # MariaDB or MySQL database schema name PHOTOPRISM_DATABASE_USER: "photoprism" # MariaDB or MySQL database user name PHOTOPRISM_DATABASE_PASSWORD: "insecure" # MariaDB or MySQL database user password - PHOTOPRISM_SITE_TITLE: "PhotoPrism" PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App" PHOTOPRISM_SITE_DESCRIPTION: "" PHOTOPRISM_SITE_AUTHOR: "" diff --git a/docker/examples/cloud/docker-compose.yml b/docker/examples/cloud/docker-compose.yml index 6893aefa8..a220eaeb5 100644 --- a/docker/examples/cloud/docker-compose.yml +++ b/docker/examples/cloud/docker-compose.yml @@ -129,7 +129,6 @@ services: environment: ## !! CHANGE site url if your server has a public domain name e.g. "https://photos.yourdomain.com/" !! PHOTOPRISM_SITE_URL: "https://_public_ip_/" - PHOTOPRISM_SITE_TITLE: "PhotoPrism" PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App" PHOTOPRISM_SITE_DESCRIPTION: "" PHOTOPRISM_SITE_AUTHOR: "" diff --git a/docker/examples/docker-compose.yml b/docker/examples/docker-compose.yml index 6d2dc2caf..155d6d0be 100644 --- a/docker/examples/docker-compose.yml +++ b/docker/examples/docker-compose.yml @@ -77,7 +77,6 @@ services: PHOTOPRISM_DATABASE_NAME: "photoprism" # MariaDB or MySQL database schema name PHOTOPRISM_DATABASE_USER: "photoprism" # MariaDB or MySQL database user name PHOTOPRISM_DATABASE_PASSWORD: "insecure" # MariaDB or MySQL database user password - PHOTOPRISM_SITE_TITLE: "PhotoPrism" PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App" PHOTOPRISM_SITE_DESCRIPTION: "" PHOTOPRISM_SITE_AUTHOR: "" diff --git a/docker/examples/macos/docker-compose.yml b/docker/examples/macos/docker-compose.yml index a37ee4ecc..b51c72518 100644 --- a/docker/examples/macos/docker-compose.yml +++ b/docker/examples/macos/docker-compose.yml @@ -73,7 +73,6 @@ services: PHOTOPRISM_DATABASE_NAME: "photoprism" # MariaDB or MySQL database schema name PHOTOPRISM_DATABASE_USER: "photoprism" # MariaDB or MySQL database user name PHOTOPRISM_DATABASE_PASSWORD: "insecure" # MariaDB or MySQL database user password - PHOTOPRISM_SITE_TITLE: "PhotoPrism" PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App" PHOTOPRISM_SITE_DESCRIPTION: "" PHOTOPRISM_SITE_AUTHOR: "" diff --git a/docker/examples/scheduler/docker-compose.yml b/docker/examples/scheduler/docker-compose.yml index ec0aef5df..f7901ade1 100644 --- a/docker/examples/scheduler/docker-compose.yml +++ b/docker/examples/scheduler/docker-compose.yml @@ -79,7 +79,6 @@ services: PHOTOPRISM_DATABASE_NAME: "photoprism" # MariaDB or MySQL database schema name PHOTOPRISM_DATABASE_USER: "photoprism" # MariaDB or MySQL database user name PHOTOPRISM_DATABASE_PASSWORD: "insecure" # MariaDB or MySQL database user password - PHOTOPRISM_SITE_TITLE: "PhotoPrism" PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App" PHOTOPRISM_SITE_DESCRIPTION: "" PHOTOPRISM_SITE_AUTHOR: "" diff --git a/docker/examples/sqlite/docker-compose.yml b/docker/examples/sqlite/docker-compose.yml index 356fa68ae..b01960ee1 100644 --- a/docker/examples/sqlite/docker-compose.yml +++ b/docker/examples/sqlite/docker-compose.yml @@ -72,7 +72,6 @@ services: PHOTOPRISM_DETECT_NSFW: "false" # flag photos as private that MAY be offensive (requires TensorFlow) PHOTOPRISM_UPLOAD_NSFW: "true" # allows uploads that MAY be offensive PHOTOPRISM_DATABASE_DRIVER: "sqlite" # SQLite is an embedded database that doesn't require a server - PHOTOPRISM_SITE_TITLE: "PhotoPrism" PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App" PHOTOPRISM_SITE_DESCRIPTION: "" PHOTOPRISM_SITE_AUTHOR: "" diff --git a/docker/examples/windows/docker-compose.yml b/docker/examples/windows/docker-compose.yml index e6938f8e9..a3950d7fb 100644 --- a/docker/examples/windows/docker-compose.yml +++ b/docker/examples/windows/docker-compose.yml @@ -78,7 +78,6 @@ services: PHOTOPRISM_DATABASE_NAME: "photoprism" # MariaDB or MySQL database schema name PHOTOPRISM_DATABASE_USER: "photoprism" # MariaDB or MySQL database user name PHOTOPRISM_DATABASE_PASSWORD: "insecure" # MariaDB or MySQL database user password - PHOTOPRISM_SITE_TITLE: "PhotoPrism" PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App" PHOTOPRISM_SITE_DESCRIPTION: "" PHOTOPRISM_SITE_AUTHOR: "" diff --git a/docker/photoprism/armv7/Dockerfile b/docker/photoprism/armv7/Dockerfile index fbc855c56..f01bf75c1 100644 --- a/docker/photoprism/armv7/Dockerfile +++ b/docker/photoprism/armv7/Dockerfile @@ -44,7 +44,6 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \ PHOTOPRISM_DETECT_NSFW="false" \ PHOTOPRISM_EXPERIMENTAL="false" \ PHOTOPRISM_SITE_URL="http://localhost:2342/" \ - PHOTOPRISM_SITE_TITLE="PhotoPrism" \ PHOTOPRISM_SITE_CAPTION="AI-Powered Photos App" \ PHOTOPRISM_SITE_DESCRIPTION="" \ PHOTOPRISM_SITE_AUTHOR="" \ diff --git a/docker/photoprism/bookworm/Dockerfile b/docker/photoprism/bookworm/Dockerfile index 5632058f3..b86619987 100644 --- a/docker/photoprism/bookworm/Dockerfile +++ b/docker/photoprism/bookworm/Dockerfile @@ -39,7 +39,6 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \ PHOTOPRISM_DETECT_NSFW="false" \ PHOTOPRISM_EXPERIMENTAL="false" \ PHOTOPRISM_SITE_URL="http://localhost:2342/" \ - PHOTOPRISM_SITE_TITLE="PhotoPrism" \ PHOTOPRISM_SITE_CAPTION="AI-Powered Photos App" \ PHOTOPRISM_SITE_DESCRIPTION="" \ PHOTOPRISM_SITE_AUTHOR="" \ diff --git a/docker/photoprism/bullseye/Dockerfile b/docker/photoprism/bullseye/Dockerfile index 70d835aa0..662709743 100644 --- a/docker/photoprism/bullseye/Dockerfile +++ b/docker/photoprism/bullseye/Dockerfile @@ -39,7 +39,6 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \ PHOTOPRISM_DETECT_NSFW="false" \ PHOTOPRISM_EXPERIMENTAL="false" \ PHOTOPRISM_SITE_URL="http://localhost:2342/" \ - PHOTOPRISM_SITE_TITLE="PhotoPrism" \ PHOTOPRISM_SITE_CAPTION="AI-Powered Photos App" \ PHOTOPRISM_SITE_DESCRIPTION="" \ PHOTOPRISM_SITE_AUTHOR="" \ diff --git a/docker/photoprism/buster/Dockerfile b/docker/photoprism/buster/Dockerfile index 49793c128..78ba4daf4 100644 --- a/docker/photoprism/buster/Dockerfile +++ b/docker/photoprism/buster/Dockerfile @@ -44,7 +44,6 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \ PHOTOPRISM_DETECT_NSFW="false" \ PHOTOPRISM_EXPERIMENTAL="false" \ PHOTOPRISM_SITE_URL="http://localhost:2342/" \ - PHOTOPRISM_SITE_TITLE="PhotoPrism" \ PHOTOPRISM_SITE_CAPTION="AI-Powered Photos App" \ PHOTOPRISM_SITE_DESCRIPTION="" \ PHOTOPRISM_SITE_AUTHOR="" \ diff --git a/docker/photoprism/impish/Dockerfile b/docker/photoprism/impish/Dockerfile index 87b6fdc72..eac96d24d 100644 --- a/docker/photoprism/impish/Dockerfile +++ b/docker/photoprism/impish/Dockerfile @@ -44,7 +44,6 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \ PHOTOPRISM_DETECT_NSFW="false" \ PHOTOPRISM_EXPERIMENTAL="false" \ PHOTOPRISM_SITE_URL="http://localhost:2342/" \ - PHOTOPRISM_SITE_TITLE="PhotoPrism" \ PHOTOPRISM_SITE_CAPTION="AI-Powered Photos App" \ PHOTOPRISM_SITE_DESCRIPTION="" \ PHOTOPRISM_SITE_AUTHOR="" \ diff --git a/docker/photoprism/jammy/Dockerfile b/docker/photoprism/jammy/Dockerfile index e7c781768..d9c3a39c2 100644 --- a/docker/photoprism/jammy/Dockerfile +++ b/docker/photoprism/jammy/Dockerfile @@ -44,7 +44,6 @@ ENV PHOTOPRISM_ARCH=$TARGETARCH \ PHOTOPRISM_DETECT_NSFW="false" \ PHOTOPRISM_EXPERIMENTAL="false" \ PHOTOPRISM_SITE_URL="http://localhost:2342/" \ - PHOTOPRISM_SITE_TITLE="PhotoPrism" \ PHOTOPRISM_SITE_CAPTION="AI-Powered Photos App" \ PHOTOPRISM_SITE_DESCRIPTION="" \ PHOTOPRISM_SITE_AUTHOR="" \ diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 691daab18..09ea7f75e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -4402,9 +4402,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.117", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.117.tgz", - "integrity": "sha512-ypZHxY+Sf/PXu7LVN+xoeanyisnJeSOy8Ki439L/oLueZb4c72FI45zXcK3gPpmTwyufh9m6NnbMLXnJh/0Fxg==" + "version": "1.4.118", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.118.tgz", + "integrity": "sha512-maZIKjnYDvF7Fs35nvVcyr44UcKNwybr93Oba2n3HkKDFAtk0svERkLN/HyczJDS3Fo4wU9th9fUQd09ZLtj1w==" }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -11753,9 +11753,9 @@ } }, "node_modules/tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, "node_modules/tsscmp": { "version": "1.0.6", @@ -16059,9 +16059,9 @@ } }, "electron-to-chromium": { - "version": "1.4.117", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.117.tgz", - "integrity": "sha512-ypZHxY+Sf/PXu7LVN+xoeanyisnJeSOy8Ki439L/oLueZb4c72FI45zXcK3gPpmTwyufh9m6NnbMLXnJh/0Fxg==" + "version": "1.4.118", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.118.tgz", + "integrity": "sha512-maZIKjnYDvF7Fs35nvVcyr44UcKNwybr93Oba2n3HkKDFAtk0svERkLN/HyczJDS3Fo4wU9th9fUQd09ZLtj1w==" }, "emoji-regex": { "version": "8.0.0", @@ -21324,9 +21324,9 @@ } }, "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" }, "tsscmp": { "version": "1.0.6", diff --git a/frontend/src/app.js b/frontend/src/app.js index 336431200..1787ca488 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -165,12 +165,22 @@ router.beforeEach((to, from, next) => { }); router.afterEach((to) => { - if (to.meta.title && config.values.siteTitle !== to.meta.title) { - config.page.title = $gettext(to.meta.title); - window.document.title = config.values.siteTitle + ": " + config.page.title; + const t = to.meta["title"] ? to.meta["title"] : ""; + + if (t !== "" && config.values.siteTitle !== t && config.values.name !== t) { + config.page.title = $gettext(t); + if (config.page.title === "") { + window.document.title = config.values.siteTitle; + } else { + window.document.title = config.page.title + " – " + config.values.siteTitle; + } } else { - config.page.title = config.values.siteTitle; - window.document.title = config.values.siteTitle + ": " + config.values.siteCaption; + config.page.title = config.values.name; + if (config.values.siteCaption === "" || !config.values.sponsor) { + window.document.title = config.values.siteTitle; + } else { + window.document.title = config.values.siteCaption; + } } }); diff --git a/frontend/src/component/footer.vue b/frontend/src/component/footer.vue index 9f49bd8c5..58c8397f4 100644 --- a/frontend/src/component/footer.vue +++ b/frontend/src/component/footer.vue @@ -2,16 +2,18 @@ - - Build {{ $config.get("version") }}
+ PhotoPrism® needs your support +
Build {{ $config.get("version") }}
- - © 2018-2022 PhotoPrism UG
- 3rd-party software packages + + + 3rd-party software packages
+
+ © 2018-2022 PhotoPrism UG
diff --git a/frontend/src/css/app.css b/frontend/src/css/app.css index 7e04c104b..ed5fe43bd 100644 --- a/frontend/src/css/app.css +++ b/frontend/src/css/app.css @@ -86,6 +86,10 @@ footer { padding: 1rem 2rem; } +#photoprism .p-about-footer .body-1 { + line-height: 1.8em; +} + main { padding: 0; margin: 0; diff --git a/frontend/src/options/options.js b/frontend/src/options/options.js index 244bc71e0..46efe08d8 100644 --- a/frontend/src/options/options.js +++ b/frontend/src/options/options.js @@ -283,6 +283,7 @@ export const MapsStyle = () => [ { text: $gettext("Streets"), value: "streets", + sponsor: true, }, { text: $gettext("Hybrid"), diff --git a/frontend/src/pages/about/about.vue b/frontend/src/pages/about/about.vue index 9bf399be7..424d7d1e4 100644 --- a/frontend/src/pages/about/about.vue +++ b/frontend/src/pages/about/about.vue @@ -2,7 +2,7 @@
- About + About {{ $config.get('name') }} diff --git a/frontend/src/pages/auth/login.vue b/frontend/src/pages/auth/login.vue index d78e7918b..29be24cbf 100644 --- a/frontend/src/pages/auth/login.vue +++ b/frontend/src/pages/auth/login.vue @@ -1,5 +1,5 @@ @@ -76,6 +97,7 @@ export default { name: "PPageAuthLogin", data() { const c = this.$config.values; + const sponsor = this.$config.isSponsor(); return { colors: { @@ -85,12 +107,13 @@ export default { }, loading: false, showPassword: false, - username: "", + username: sponsor ? "" : "admin", password: "", - sponsor: this.$config.isSponsor(), + sponsor: sponsor, config: this.$config.values, siteDescription: c.siteDescription ? c.siteDescription : c.siteCaption, nextUrl: this.$route.params.nextUrl ? this.$route.params.nextUrl : "/", + wallpaperUri: c.wallpaperUri, rtl: this.$rtl, }; }, @@ -106,6 +129,13 @@ export default { this.$scrollbar.show(); }, methods: { + wallpaper() { + if (this.wallpaperUri) { + return `background-image: url(${this.wallpaperUri});`; + } + + return ""; + }, login() { if (!this.username || !this.password) { return; diff --git a/internal/commands/show.go b/internal/commands/show.go index 58b329256..c07b99b24 100644 --- a/internal/commands/show.go +++ b/internal/commands/show.go @@ -9,6 +9,7 @@ var ShowCommand = cli.Command{ Name: "show", Usage: "Configuration and system report subcommands", Subcommands: []cli.Command{ + ShowFlagsCommand, ShowConfigCommand, ShowTagsCommand, ShowFiltersCommand, diff --git a/internal/commands/show_flags.go b/internal/commands/show_flags.go new file mode 100644 index 000000000..ee928646f --- /dev/null +++ b/internal/commands/show_flags.go @@ -0,0 +1,119 @@ +package commands + +import ( + "fmt" + + "github.com/sirupsen/logrus" + "github.com/urfave/cli" + + "github.com/photoprism/photoprism/internal/config" + "github.com/photoprism/photoprism/pkg/report" +) + +// ShowFlagsCommand configures the command name, flags, and action. +var ShowFlagsCommand = cli.Command{ + Name: "flags", + Usage: "Shows environment variable command-line parameter names", + Flags: report.CliFlags, + Action: showFlagsAction, +} + +var faceFlagsInfo = `!!! info "" + To [recognize faces](../user-guide/organize/people.md), PhotoPrism first extracts crops from your images using a + [library](https://github.com/esimov/pigo) based on [pixel intensity comparisons](https://arxiv.org/pdf/1305.4537.pdf). + These are then fed into TensorFlow to compute [512-dimensional vectors](https://www.cv-foundation.org/openaccess/content_cvpr_2015/papers/Schroff_FaceNet_A_Unified_2015_CVPR_paper.pdf) + for characterization. In the final step, the [DBSCAN algorithm](https://en.wikipedia.org/wiki/DBSCAN) + attempts to cluster these so-called face embeddings, so they can be matched to persons with just a few clicks. + A reasonable range for the similarity distance between face embeddings is between 0.60 and 0.70, with a higher + value being more aggressive and leading to larger clusters with more false positives. + To cluster a smaller number of faces, you can reduce the core to 3 or 2 similar faces. + +We recommend that only advanced users change these parameters:` + +// showFlagsAction shows environment variable command-line parameter names. +func showFlagsAction(ctx *cli.Context) error { + conf := config.NewConfig(ctx) + conf.SetLogLevel(logrus.FatalLevel) + + rows, cols := config.Flags.Report() + + // CSV Export? + if ctx.Bool("csv") || ctx.Bool("tsv") { + result, err := report.Render(rows, cols, report.CliFormat(ctx)) + + fmt.Println(result) + + return err + } + + type Section struct { + Start string + Caption string + Info string + } + + s := []Section{ + {Start: "PHOTOPRISM_ADMIN_PASSWORD", Caption: "Authentication"}, + {Start: "PHOTOPRISM_LOG_LEVEL", Caption: "Logging"}, + {Start: "PHOTOPRISM_CONFIG_PATH", Caption: "Storage"}, + {Start: "PHOTOPRISM_WORKERS", Caption: "Index Workers"}, + {Start: "PHOTOPRISM_READONLY", Caption: "Feature Flags"}, + {Start: "PHOTOPRISM_DEFAULT_LOCALE", Caption: "Customization"}, + {Start: "PHOTOPRISM_CDN_URL", Caption: "Site Information"}, + {Start: "PHOTOPRISM_HTTP_PORT", Caption: "Web Server"}, + {Start: "PHOTOPRISM_DATABASE_DRIVER", Caption: "Database Connection"}, + {Start: "PHOTOPRISM_DARKTABLE_BIN", Caption: "File Converters"}, + {Start: "PHOTOPRISM_DOWNLOAD_TOKEN", Caption: "Security Tokens"}, + {Start: "PHOTOPRISM_THUMB_COLOR", Caption: "Image Quality"}, + {Start: "PHOTOPRISM_FACE_SIZE", Caption: "Face Recognition", + Info: faceFlagsInfo}, + {Start: "PHOTOPRISM_PID_FILENAME", Caption: "Daemon Mode", + Info: "If you start the server as a *daemon* in the background, you can additionally specify a filename for the log and the process ID:"}, + } + + j := 0 + + for i, sec := range s { + fmt.Printf("### %s ###\n\n", sec.Caption) + if sec.Info != "" && ctx.Bool("md") { + fmt.Printf("%s\n\n", sec.Info) + } + + secRows := make([][]string, 0, len(rows)) + + for { + row := rows[j] + + if len(row) < 1 { + continue + } + + if i < len(s)-1 { + if s[i+1].Start == row[0] { + break + } + } + + secRows = append(secRows, row) + j++ + + if j >= len(rows) { + break + } + } + + result, err := report.Render(secRows, cols, report.CliFormat(ctx)) + + if err != nil { + return err + } + + fmt.Println(result) + + if j >= len(rows) { + break + } + } + + return nil +} diff --git a/internal/commands/users.go b/internal/commands/users.go index 6a0714306..c80d5d26a 100644 --- a/internal/commands/users.go +++ b/internal/commands/users.go @@ -31,6 +31,7 @@ var UsersCommand = cli.Command{ Name: "add", Usage: "Adds a new user", Action: usersAddAction, + Hidden: !config.Sponsor(), Flags: []cli.Flag{ cli.StringFlag{ Name: "fullname, n", diff --git a/internal/config/cli_flag.go b/internal/config/cli_flag.go new file mode 100644 index 000000000..cb59442b0 --- /dev/null +++ b/internal/config/cli_flag.go @@ -0,0 +1,66 @@ +package config + +import ( + "reflect" + + "github.com/photoprism/photoprism/pkg/list" + "github.com/urfave/cli" +) + +// CliFlag represents a command-line parameter. +type CliFlag struct { + Flag cli.DocGenerationFlag + Tags []string +} + +// Skip checks if the parameter should be skipped based on a list of tags. +func (f CliFlag) Skip(tags []string) bool { + return len(f.Tags) > 0 && !list.ContainsAny(f.Tags, tags) +} + +// Fields returns the flag struct fields. +func (f CliFlag) Fields() reflect.Value { + fields := reflect.ValueOf(f.Flag) + + for fields.Kind() == reflect.Ptr { + fields = reflect.Indirect(fields) + } + + return fields +} + +// Hidden checks if the flag is hidden. +func (f CliFlag) Hidden() bool { + field := f.Fields().FieldByName("Hidden") + + if !field.IsValid() || !field.Bool() { + return false + } + + return true +} + +// EnvVar returns the flag environment variable name. +func (f CliFlag) EnvVar() string { + field := f.Fields().FieldByName("EnvVar") + + if !field.IsValid() { + return "" + } + + return field.String() +} + +// Name returns the command flag name. +func (f CliFlag) Name() string { + return f.Flag.GetName() +} + +// Usage returns the command flag usage. +func (f CliFlag) Usage() string { + if list.Contains(f.Tags, EnvSponsor) { + return f.Flag.GetUsage() + " *sponsors only*" + } else { + return f.Flag.GetUsage() + } +} diff --git a/internal/config/cli_flag_test.go b/internal/config/cli_flag_test.go new file mode 100644 index 000000000..ae95d957f --- /dev/null +++ b/internal/config/cli_flag_test.go @@ -0,0 +1,66 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/urfave/cli" +) + +func TestCliFlag_Skip(t *testing.T) { + withTags := CliFlag{ + Flag: cli.StringFlag{ + Name: "with-tags", + Usage: "`STRING`", + EnvVar: "PHOTOPRISM_WITH_TAGS", + }, + Tags: []string{"foo", "bar"}, + } + + noTags := CliFlag{ + Flag: cli.StringFlag{ + Name: "no-tags", + Usage: "`STRING`", + EnvVar: "PHOTOPRISM_NO_TAGS", + }, + Tags: []string{}, + } + + t.Run("True", func(t *testing.T) { + assert.True(t, withTags.Skip([]string{"baz"})) + assert.False(t, noTags.Skip([]string{"baz"})) + }) + t.Run("False", func(t *testing.T) { + assert.False(t, withTags.Skip([]string{"foo"})) + assert.False(t, noTags.Skip([]string{"foo"})) + }) +} + +func TestCliFlag_Hidden(t *testing.T) { + hidden := CliFlag{ + Flag: cli.StringFlag{ + Name: "is-hidden", + Usage: "`STRING`", + EnvVar: "PHOTOPRISM_HIDDEN", + Hidden: true, + }, + Tags: []string{"foo", "bar"}, + } + + visible := CliFlag{ + Flag: cli.StringFlag{ + Name: "is-visible", + Usage: "`STRING`", + EnvVar: "PHOTOPRISM_VISIBLE", + Hidden: false, + }, + Tags: []string{}, + } + + t.Run("True", func(t *testing.T) { + assert.True(t, hidden.Hidden()) + }) + t.Run("False", func(t *testing.T) { + assert.False(t, visible.Hidden()) + }) +} diff --git a/internal/config/cli_flags.go b/internal/config/cli_flags.go new file mode 100644 index 000000000..f9dcfcb19 --- /dev/null +++ b/internal/config/cli_flags.go @@ -0,0 +1,36 @@ +package config + +import ( + "github.com/photoprism/photoprism/pkg/list" + "github.com/urfave/cli" +) + +// CliFlags represents a list of command-line parameters. +type CliFlags []CliFlag + +// Cli returns the currently active command-line parameters. +func (f CliFlags) Cli() (result []cli.Flag) { + var tags []string + + switch { + case Sponsor(): + tags = []string{EnvSponsor} + } + + return f.Find(tags) +} + +// Find finds command-line parameters based on a list of tags. +func (f CliFlags) Find(tags []string) (result []cli.Flag) { + result = make([]cli.Flag, 0, len(f)) + + for _, flag := range f { + if len(flag.Tags) > 0 && !list.ContainsAny(flag.Tags, tags) { + continue + } + + result = append(result, flag.Flag) + } + + return result +} diff --git a/internal/config/cli_flags_report.go b/internal/config/cli_flags_report.go new file mode 100644 index 000000000..e7752ce09 --- /dev/null +++ b/internal/config/cli_flags_report.go @@ -0,0 +1,19 @@ +package config + +// Report returns global config values as a table for reporting. +func (f CliFlags) Report() (rows [][]string, cols []string) { + cols = []string{"Variable", "Flag", "Usage"} + + rows = make([][]string, 0, len(f)) + + for _, flag := range Flags { + if flag.Hidden() { + continue + } + + row := []string{flag.EnvVar(), flag.Name(), flag.Usage()} + rows = append(rows, row) + } + + return rows, cols +} diff --git a/internal/config/cli_flags_test.go b/internal/config/cli_flags_test.go new file mode 100644 index 000000000..170c78a64 --- /dev/null +++ b/internal/config/cli_flags_test.go @@ -0,0 +1,25 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCliFlags_Cli(t *testing.T) { + cliFlags := Flags.Cli() + standard := Flags.Find([]string{}) + + assert.Greater(t, len(cliFlags), len(standard)) +} + +func TestCliFlags_Find(t *testing.T) { + cliFlags := Flags.Cli() + standard := Flags.Find([]string{}) + sponsor := Flags.Find([]string{EnvSponsor}) + other := Flags.Find([]string{"other"}) + + assert.Equal(t, len(standard), len(other)) + assert.Equal(t, len(cliFlags), len(sponsor)) + assert.Less(t, len(other), len(sponsor)) +} diff --git a/internal/config/client_config.go b/internal/config/client_config.go index cd84640b1..bfaf46f25 100644 --- a/internal/config/client_config.go +++ b/internal/config/client_config.go @@ -26,6 +26,7 @@ type ClientConfig struct { ManifestUri string `json:"manifestUri"` ApiUri string `json:"apiUri"` ContentUri string `json:"contentUri"` + WallpaperUri string `json:"wallpaperUri"` SiteUrl string `json:"siteUrl"` SiteDomain string `json:"siteDomain"` SiteAuthor string `json:"siteAuthor"` @@ -227,6 +228,7 @@ func (c *Config) PublicConfig() ClientConfig { AppName: c.AppName(), AppMode: c.AppMode(), AppIcon: c.AppIcon(), + WallpaperUri: c.WallpaperUri(), Version: c.Version(), Copyright: c.Copyright(), Debug: c.Debug(), @@ -300,6 +302,7 @@ func (c *Config) GuestConfig() ClientConfig { AppName: c.AppName(), AppMode: c.AppMode(), AppIcon: c.AppIcon(), + WallpaperUri: c.WallpaperUri(), Version: c.Version(), Copyright: c.Copyright(), Debug: c.Debug(), @@ -367,6 +370,7 @@ func (c *Config) UserConfig() ClientConfig { AppName: c.AppName(), AppMode: c.AppMode(), AppIcon: c.AppIcon(), + WallpaperUri: c.WallpaperUri(), Version: c.Version(), Copyright: c.Copyright(), Debug: c.Debug(), diff --git a/internal/config/config.go b/internal/config/config.go index 4341a1660..eac64971a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -71,14 +71,16 @@ func init() { } } -func initLogger(debug bool) { +func initLogger() { once.Do(func() { log.SetFormatter(&logrus.TextFormatter{ DisableColors: false, FullTimestamp: true, }) - if debug { + if Env(EnvTrace) { + log.SetLevel(logrus.TraceLevel) + } else if Env(EnvDebug) { log.SetLevel(logrus.DebugLevel) } else { log.SetLevel(logrus.InfoLevel) @@ -89,7 +91,7 @@ func initLogger(debug bool) { // NewConfig initialises a new configuration file func NewConfig(ctx *cli.Context) *Config { // Initialize logger. - initLogger(ctx.GlobalBool("debug")) + initLogger() // Initialize options from config file and CLI context. c := &Config{ @@ -291,6 +293,10 @@ func (c *Config) SerialChecksum() string { // Name returns the application name ("PhotoPrism"). func (c *Config) Name() string { + if c.Sponsor() && c.options.Name == "PhotoPrism" { + c.options.Name = "PhotoPrism+" + } + return c.options.Name } @@ -331,6 +337,10 @@ func (c *Config) ApiUri() string { // CdnUrl returns the optional content delivery network URI without trailing slash. func (c *Config) CdnUrl(res string) string { + if c.NoSponsor() { + return res + } + return strings.TrimRight(c.options.CdnUrl, "/") + res } @@ -369,7 +379,7 @@ func (c *Config) SiteAuthor() string { // SiteTitle returns the main site title (default is application name). func (c *Config) SiteTitle() string { - if c.options.SiteTitle == "" { + if c.options.SiteTitle == "" || c.NoSponsor() { return c.Name() } @@ -401,7 +411,7 @@ func (c *Config) SitePreview() string { // Imprint returns the legal info text for the page footer. func (c *Config) Imprint() string { - if !c.Sponsor() || c.Test() { + if c.NoSponsor() { return MsgSponsor } @@ -410,7 +420,7 @@ func (c *Config) Imprint() string { // ImprintUrl returns the legal info url. func (c *Config) ImprintUrl() string { - if !c.Sponsor() || c.Test() { + if c.NoSponsor() { return SignUpURL } @@ -422,6 +432,7 @@ func (c *Config) Debug() bool { if c.Trace() { return true } + return c.options.Debug } @@ -445,6 +456,11 @@ func (c *Config) Sponsor() bool { return c.options.Sponsor || c.Test() } +// NoSponsor reports if the instance is not operated by a sponsor. +func (c *Config) NoSponsor() bool { + return !c.Sponsor() && !c.Demo() +} + // Public checks if app runs in public mode and requires no authentication. func (c *Config) Public() bool { if c.Auth() { diff --git a/internal/config/config_ui.go b/internal/config/config_customize.go similarity index 57% rename from internal/config/config_ui.go rename to internal/config/config_customize.go index 9608ff04d..f5c12685a 100644 --- a/internal/config/config_ui.go +++ b/internal/config/config_customize.go @@ -1,18 +1,20 @@ package config import ( + "path" "path/filepath" "strings" "github.com/photoprism/photoprism/internal/i18n" + "github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/txt" ) // DefaultTheme returns the default user interface theme name. func (c *Config) DefaultTheme() string { - if c.options.DefaultTheme == "" || !c.Sponsor() { + if c.options.DefaultTheme == "" || c.NoSponsor() { return "default" } @@ -32,7 +34,7 @@ func (c *Config) DefaultLocale() string { func (c *Config) AppIcon() string { defaultIcon := "logo" - if c.options.AppIcon == "" || c.options.AppIcon == defaultIcon { + if c.NoSponsor() || c.options.AppIcon == "" || c.options.AppIcon == defaultIcon { // Default. } else if fs.FileExists(c.AppIconsPath(c.options.AppIcon, "512.png")) { return c.options.AppIcon @@ -44,9 +46,9 @@ func (c *Config) AppIcon() string { // AppIconsPath returns the path to the app icons. func (c *Config) AppIconsPath(name ...string) string { if len(name) > 0 { - folder := []string{c.StaticPath(), "icons"} - folder = append(folder, name...) - return filepath.Join(folder...) + filePath := []string{c.StaticPath(), "icons"} + filePath = append(filePath, name...) + return filepath.Join(filePath...) } return filepath.Join(c.StaticPath(), "icons") @@ -56,7 +58,7 @@ func (c *Config) AppIconsPath(name ...string) string { func (c *Config) AppName() string { name := strings.TrimSpace(c.options.AppName) - if name == "" { + if c.NoSponsor() || name == "" { name = c.SiteTitle() } @@ -83,3 +85,36 @@ func (c *Config) AppMode() string { return "standalone" } } + +// WallpaperUri returns the login screen background image `URI`. +func (c *Config) WallpaperUri() string { + if c.NoSponsor() { + return "" + } else if strings.Contains(c.options.WallpaperUri, "/") { + return c.options.WallpaperUri + } + + assetPath := "img/wallpaper" + + // Empty URI? + if c.options.WallpaperUri == "" { + if !fs.PathExists(filepath.Join(c.StaticPath(), assetPath)) { + return "" + } + + c.options.WallpaperUri = "default.jpg" + } else if !strings.Contains(c.options.WallpaperUri, ".") { + c.options.WallpaperUri += fs.ExtJPEG + } + + // Valid URI? Local file? + if p := clean.Path(c.options.WallpaperUri); p == "" { + return "" + } else if fs.FileExists(filepath.Join(c.StaticPath(), assetPath, p)) { + c.options.WallpaperUri = path.Join(c.StaticUri(), assetPath, p) + } else { + c.options.WallpaperUri = "" + } + + return c.options.WallpaperUri +} diff --git a/internal/config/config_ui_test.go b/internal/config/config_customize_test.go similarity index 70% rename from internal/config/config_ui_test.go rename to internal/config/config_customize_test.go index 0bc9ceacb..3ae838ab5 100644 --- a/internal/config/config_ui_test.go +++ b/internal/config/config_customize_test.go @@ -11,11 +11,20 @@ func TestConfig_DefaultTheme(t *testing.T) { c := NewConfig(CliTestContext()) assert.Equal(t, "default", c.DefaultTheme()) + c.options.Demo = false c.options.Sponsor = false + c.options.Test = false c.options.DefaultTheme = "grayscale" assert.Equal(t, "default", c.DefaultTheme()) c.options.Sponsor = true assert.Equal(t, "grayscale", c.DefaultTheme()) + c.options.Sponsor = false + c.options.Test = true + assert.Equal(t, "grayscale", c.DefaultTheme()) + c.options.Sponsor = false + c.options.Test = false + assert.Equal(t, "default", c.DefaultTheme()) + c.options.Sponsor = true c.options.DefaultTheme = "" assert.Equal(t, "default", c.DefaultTheme()) c.options.Sponsor = false @@ -77,3 +86,19 @@ func TestConfig_AppMode(t *testing.T) { assert.Equal(t, "standalone", c.AppMode()) } + +func TestConfig_WallpaperUri(t *testing.T) { + c := NewConfig(CliTestContext()) + + assert.Equal(t, "", c.WallpaperUri()) + c.options.WallpaperUri = "kashmir" + assert.Equal(t, "/static/img/wallpaper/kashmir.jpg", c.WallpaperUri()) + c.options.WallpaperUri = "https://cdn.photoprism.app/wallpaper/welcome.jpg" + assert.Equal(t, "https://cdn.photoprism.app/wallpaper/welcome.jpg", c.WallpaperUri()) + c.options.Test = false + assert.Equal(t, "", c.WallpaperUri()) + c.options.Test = true + assert.Equal(t, "https://cdn.photoprism.app/wallpaper/welcome.jpg", c.WallpaperUri()) + c.options.WallpaperUri = "" + assert.Equal(t, "", c.WallpaperUri()) +} diff --git a/internal/config/config_features.go b/internal/config/config_features.go index 06d08bb59..52b1c2570 100644 --- a/internal/config/config_features.go +++ b/internal/config/config_features.go @@ -1,5 +1,10 @@ package config +// Sponsor checks if sponsor features should be enabled. +func Sponsor() bool { + return Env(EnvDemo, EnvSponsor, EnvTest) +} + // DisableWebDAV checks if the built-in WebDAV server should be disabled. func (c *Config) DisableWebDAV() bool { if c.ReadOnly() || c.Demo() { diff --git a/internal/config/config_report.go b/internal/config/config_report.go index 33d6d389c..ce0442ab1 100644 --- a/internal/config/config_report.go +++ b/internal/config/config_report.go @@ -12,14 +12,15 @@ func (c *Config) Report() (rows [][]string, cols []string) { cols = []string{"Value", "Name"} rows = [][]string{ + // Authentication. {"admin-password", strings.Repeat("*", utf8.RuneCountInString(c.AdminPassword()))}, + {"auth", fmt.Sprintf("%t", c.Auth())}, + {"public", fmt.Sprintf("%t", c.Public())}, + + // Logging. {"log-level", c.LogLevel().String()}, {"debug", fmt.Sprintf("%t", c.Debug())}, {"trace", fmt.Sprintf("%t", c.Trace())}, - {"auth", fmt.Sprintf("%t", c.Auth())}, - {"public", fmt.Sprintf("%t", c.Public())}, - {"read-only", fmt.Sprintf("%t", c.ReadOnly())}, - {"experimental", fmt.Sprintf("%t", c.Experimental())}, // Config. {"config-path", c.ConfigPath()}, @@ -55,6 +56,8 @@ func (c *Config) Report() (rows [][]string, cols []string) { {"auto-import", fmt.Sprintf("%d", c.AutoImport()/time.Second)}, // Feature Flags. + {"read-only", fmt.Sprintf("%t", c.ReadOnly())}, + {"experimental", fmt.Sprintf("%t", c.Experimental())}, {"disable-backups", fmt.Sprintf("%t", c.DisableBackups())}, {"disable-settings", fmt.Sprintf("%t", c.DisableSettings())}, {"disable-places", fmt.Sprintf("%t", c.DisablePlaces())}, @@ -79,13 +82,13 @@ func (c *Config) Report() (rows [][]string, cols []string) { {"tensorflow-version", c.TensorFlowVersion()}, {"tensorflow-model-path", c.TensorFlowModelPath()}, - // UI Defaults. + // Customization. {"default-locale", c.DefaultLocale()}, - - // Progressive Web App. + {"default-theme", c.DefaultTheme()}, {"app-icon", c.AppIcon()}, {"app-name", c.AppName()}, {"app-mode", c.AppMode()}, + {"wallpaper-uri", c.WallpaperUri()}, // Site Infos. {"cdn-url", c.CdnUrl("/")}, diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 1f8b1ff78..e9f73c913 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -12,6 +12,7 @@ import ( ) func TestMain(m *testing.M) { + _ = os.Setenv("PHOTOPRISM_TEST", "true") log = logrus.StandardLogger() log.SetLevel(logrus.TraceLevel) diff --git a/internal/config/env.go b/internal/config/env.go new file mode 100644 index 000000000..d59415ead --- /dev/null +++ b/internal/config/env.go @@ -0,0 +1,28 @@ +package config + +import ( + "os" + "strings" + + "github.com/photoprism/photoprism/pkg/list" +) + +// Environment names. +const ( + EnvDebug = "debug" + EnvTrace = "trace" + EnvDemo = "demo" + EnvSponsor = "sponsor" + EnvTest = "test" +) + +// Env checks the presence of environment and command-line flags. +func Env(vars ...string) bool { + for _, s := range vars { + if os.Getenv("PHOTOPRISM_"+strings.ToUpper(s)) == "true" || list.Contains(os.Args, "--"+s) { + return true + } + } + + return false +} diff --git a/internal/config/env_test.go b/internal/config/env_test.go new file mode 100644 index 000000000..0cc4d7cfd --- /dev/null +++ b/internal/config/env_test.go @@ -0,0 +1,16 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEnv(t *testing.T) { + t.Run("True", func(t *testing.T) { + assert.True(t, Env(EnvTest)) + }) + t.Run("False", func(t *testing.T) { + assert.False(t, Env("foo")) + }) +} diff --git a/internal/config/global_flags.go b/internal/config/global_flags.go index aade32a82..90bd9acf5 100644 --- a/internal/config/global_flags.go +++ b/internal/config/global_flags.go @@ -12,578 +12,725 @@ import ( "github.com/photoprism/photoprism/internal/thumb" ) -// GlobalFlags describes global command-line parameters and flags. -var GlobalFlags = []cli.Flag{ - cli.StringFlag{ - Name: "admin-password, pw", - Usage: fmt.Sprintf("initial admin `PASSWORD`, must have at least %d characters", entity.PasswordLen), - EnvVar: "PHOTOPRISM_ADMIN_PASSWORD", - }, - cli.StringFlag{ - Name: "log-level, l", - Usage: "log message verbosity `LEVEL` (trace, debug, info, warning, error, fatal, panic)", - Value: "info", - EnvVar: "PHOTOPRISM_LOG_LEVEL", - }, - cli.BoolFlag{ - Name: "debug", - Usage: "enable debug mode, show non-essential log messages", - EnvVar: "PHOTOPRISM_DEBUG", - }, - cli.BoolFlag{ - Name: "trace", - Usage: "enable trace mode, show all log messages", - EnvVar: "PHOTOPRISM_TRACE", - }, - cli.BoolFlag{ - Name: "auth, a", - Usage: "always require password authentication, overrides the public flag if it is set", - EnvVar: "PHOTOPRISM_AUTH", - }, - cli.BoolFlag{ - Name: "public, p", - Usage: "disable password authentication, WebDAV, and the advanced settings page", - EnvVar: "PHOTOPRISM_PUBLIC", - }, - cli.BoolFlag{ - Name: "test", - Hidden: true, - Usage: "enable test mode", - }, - cli.BoolFlag{ - Name: "unsafe", - Hidden: true, - Usage: "enable unsafe mode", - EnvVar: "PHOTOPRISM_UNSAFE", - }, - cli.BoolFlag{ - Name: "demo", - Hidden: true, - Usage: "enable demo mode", - EnvVar: "PHOTOPRISM_DEMO", - }, - cli.BoolFlag{ - Name: "sponsor", - Hidden: true, - Usage: "your continuous support helps to pay for development and operating expenses", - EnvVar: "PHOTOPRISM_SPONSOR", - }, - cli.BoolFlag{ - Name: "read-only, r", - Usage: "disable import, upload, delete, and all other operations that require write permissions", - EnvVar: "PHOTOPRISM_READONLY", - }, - cli.BoolFlag{ - Name: "experimental, e", - Usage: "enable experimental features", - EnvVar: "PHOTOPRISM_EXPERIMENTAL", - }, - cli.StringFlag{ - Name: "partner-id", - Hidden: true, - Usage: "hosting partner id", - EnvVar: "PHOTOPRISM_PARTNER_ID", - }, - cli.StringFlag{ - Name: "config-path, c", - Usage: "config storage `PATH`, values in options.yml override CLI flags and environment variables if present", - EnvVar: "PHOTOPRISM_CONFIG_PATH", - }, - cli.StringFlag{ - Name: "defaults-yaml, y", - Usage: "load config defaults from `FILE` if exists, does not override CLI flags and environment variables", - Value: "/etc/photoprism/defaults.yml", - EnvVar: "PHOTOPRISM_DEFAULTS_YAML", - }, - cli.StringFlag{ - Name: "originals-path, o", - Usage: "storage `PATH` of your original media files (photos and videos)", - EnvVar: "PHOTOPRISM_ORIGINALS_PATH", - }, - cli.IntFlag{ - Name: "originals-limit, mb", - Value: 1000, - Usage: "maximum size of media files in `MB` (1-100000; -1 to disable)", - EnvVar: "PHOTOPRISM_ORIGINALS_LIMIT", - }, - cli.IntFlag{ - Name: "resolution-limit, mp", - Value: 100, - Usage: "maximum resolution of media files in `MEGAPIXELS` (1-900; -1 to disable)", - EnvVar: "PHOTOPRISM_RESOLUTION_LIMIT", - }, - cli.StringFlag{ - Name: "storage-path, s", - Usage: "writable storage `PATH` for sidecar, cache, and database files", - EnvVar: "PHOTOPRISM_STORAGE_PATH", - }, - cli.StringFlag{ - Name: "sidecar-path, sc", - Usage: "custom relative or absolute sidecar `PATH` (optional)", - EnvVar: "PHOTOPRISM_SIDECAR_PATH", - }, - cli.StringFlag{ - Name: "backup-path, ba", - Usage: "custom backup `PATH` for index backup files (optional)", - EnvVar: "PHOTOPRISM_BACKUP_PATH", - }, - cli.StringFlag{ - Name: "cache-path, ca", - Usage: "custom cache `PATH` for sessions and thumbnail files (optional)", - EnvVar: "PHOTOPRISM_CACHE_PATH", - }, - cli.StringFlag{ - Name: "import-path, im", - Usage: "base `PATH` from which files can be imported to originals (optional)", - EnvVar: "PHOTOPRISM_IMPORT_PATH", - }, - cli.StringFlag{ - Name: "assets-path, as", - Usage: "assets `PATH` containing static resources like icons, models, and translations", - EnvVar: "PHOTOPRISM_ASSETS_PATH", - }, - cli.StringFlag{ - Name: "temp-path, tmp", - Usage: "temporary file `PATH` (optional)", - EnvVar: "PHOTOPRISM_TEMP_PATH", - }, - cli.IntFlag{ - Name: "workers, w", - Usage: "maximum `NUMBER` of indexing workers, default depends on the number of physical cores", - EnvVar: "PHOTOPRISM_WORKERS", - Value: cpuid.CPU.PhysicalCores / 2, - }, - cli.StringFlag{ - Name: "wakeup-interval, i", - Usage: "`DURATION` between worker runs required for face recognition and index maintenance (1-86400s)", - Value: DefaultWakeupInterval.String(), - EnvVar: "PHOTOPRISM_WAKEUP_INTERVAL", - }, - cli.IntFlag{ - Name: "auto-index", - Usage: "WebDAV auto index safety delay in `SECONDS` (-1 to disable)", - Value: DefaultAutoIndexDelay, - EnvVar: "PHOTOPRISM_AUTO_INDEX", - }, - cli.IntFlag{ - Name: "auto-import", - Usage: "WebDAV auto import safety delay in `SECONDS` (-1 to disable)", - Value: DefaultAutoImportDelay, - EnvVar: "PHOTOPRISM_AUTO_IMPORT", - }, - cli.BoolFlag{ - Name: "disable-webdav", - Usage: "disable built-in WebDAV server", - EnvVar: "PHOTOPRISM_DISABLE_WEBDAV", - }, - cli.BoolFlag{ - Name: "disable-settings", - Usage: "disable settings UI and API", - EnvVar: "PHOTOPRISM_DISABLE_SETTINGS", - }, - cli.BoolFlag{ - Name: "disable-places", - Usage: "disable reverse geocoding and maps", - EnvVar: "PHOTOPRISM_DISABLE_PLACES", - }, - cli.BoolFlag{ - Name: "disable-backups", - Usage: "disable backing up albums and photo metadata to YAML files", - EnvVar: "PHOTOPRISM_DISABLE_BACKUPS", - }, - cli.BoolFlag{ - Name: "disable-tensorflow", - Usage: "disable all features depending on TensorFlow", - EnvVar: "PHOTOPRISM_DISABLE_TENSORFLOW", - }, - cli.BoolFlag{ - Name: "disable-faces", - Usage: "disable facial recognition", - EnvVar: "PHOTOPRISM_DISABLE_FACES", - }, - cli.BoolFlag{ - Name: "disable-classification", - 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: "disable-heifconvert", - Usage: "disable conversion of HEIC/HEIF files", - EnvVar: "PHOTOPRISM_DISABLE_HEIFCONVERT", - }, - cli.BoolFlag{ - Name: "disable-darktable", - Usage: "disable conversion of RAW files with Darktable", - EnvVar: "PHOTOPRISM_DISABLE_DARKTABLE", - }, - cli.BoolFlag{ - Name: "disable-rawtherapee", - Usage: "disable conversion of RAW files with RawTherapee", - EnvVar: "PHOTOPRISM_DISABLE_RAWTHERAPEE", - }, - cli.BoolFlag{ - Name: "disable-sips", - Usage: "disable conversion of RAW files with Sips (macOS only)", - EnvVar: "PHOTOPRISM_DISABLE_SIPS", - }, - cli.BoolFlag{ - Name: "disable-raw", - Usage: "disable indexing and conversion of RAW files", - EnvVar: "PHOTOPRISM_DISABLE_RAW", - }, - cli.BoolFlag{ - Name: "raw-presets", - Usage: "enables applying user presets when converting RAW files (reduces performance)", - EnvVar: "PHOTOPRISM_RAW_PRESETS", - }, - 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: "detect-nsfw", - Usage: "flag photos as private that may be offensive (requires TensorFlow)", - EnvVar: "PHOTOPRISM_DETECT_NSFW", - }, - cli.BoolFlag{ - Name: "upload-nsfw, n", - Usage: "allow uploads that may be offensive", - EnvVar: "PHOTOPRISM_UPLOAD_NSFW", - }, - cli.StringFlag{ - Name: "default-theme", - Usage: "standard user interface theme `NAME`", - Hidden: true, - EnvVar: "PHOTOPRISM_DEFAULT_THEME", - }, - cli.StringFlag{ - Name: "default-locale, lang", - Usage: "standard user interface language `CODE`", - Value: i18n.Default.Locale(), - EnvVar: "PHOTOPRISM_DEFAULT_LOCALE", - }, - cli.StringFlag{ - Name: "app-icon", - Usage: "web app `ICON` (logo, app, crisp, mint, bold)", - EnvVar: "PHOTOPRISM_APP_ICON", - }, - cli.StringFlag{ - Name: "app-name", - Usage: "web app `NAME` when installed on a device", - Value: "PhotoPrism", - EnvVar: "PHOTOPRISM_APP_NAME", - }, - cli.StringFlag{ - Name: "app-mode", - Usage: "web app `MODE` (fullscreen, standalone, minimal-ui, browser)", - Value: "standalone", - EnvVar: "PHOTOPRISM_APP_MODE", - }, - cli.StringFlag{ - Name: "cdn-url", - Usage: "content delivery network `URL` (optional)", - EnvVar: "PHOTOPRISM_CDN_URL", - }, - cli.StringFlag{ - Name: "site-url, url", - Usage: "public site `URL`", - Value: "http://localhost:2342/", - EnvVar: "PHOTOPRISM_SITE_URL", - }, - cli.StringFlag{ - Name: "site-author", - Usage: "site `OWNER`, copyright, or artist", - EnvVar: "PHOTOPRISM_SITE_AUTHOR", - }, - cli.StringFlag{ - Name: "site-title", - Usage: "site `TITLE`", - Value: "PhotoPrism", - EnvVar: "PHOTOPRISM_SITE_TITLE", - }, - cli.StringFlag{ - Name: "site-caption", - Usage: "site `CAPTION`", - Value: "AI-Powered Photos App", - EnvVar: "PHOTOPRISM_SITE_CAPTION", - }, - cli.StringFlag{ - Name: "site-description", - Usage: "site `DESCRIPTION` (optional)", - EnvVar: "PHOTOPRISM_SITE_DESCRIPTION", - }, - cli.StringFlag{ - Name: "site-preview", - Usage: "site preview image `URL` (optional)", - EnvVar: "PHOTOPRISM_SITE_PREVIEW", - }, - cli.StringFlag{ - Name: "imprint", - Usage: "legal `INFO`, displayed in the page footer", - Value: "", - EnvVar: "PHOTOPRISM_IMPRINT", - }, - cli.StringFlag{ - Name: "imprint-url", - Usage: "legal info `URL` (optional)", - Value: "", - EnvVar: "PHOTOPRISM_IMPRINT_URL", - }, - cli.IntFlag{ - Name: "http-port, port", - Value: 2342, - Usage: "http server port `NUMBER`", - EnvVar: "PHOTOPRISM_HTTP_PORT", - }, - cli.StringFlag{ - Name: "http-host, ip", - Usage: "http server `IP` address", - EnvVar: "PHOTOPRISM_HTTP_HOST", - }, - cli.StringFlag{ - Name: "http-mode, mode", - Usage: "http server `MODE` (debug, release, or test)", - EnvVar: "PHOTOPRISM_HTTP_MODE", - }, - cli.StringFlag{ - Name: "http-compression, z", - Usage: "http server compression `METHOD` (none or gzip)", - EnvVar: "PHOTOPRISM_HTTP_COMPRESSION", - }, - cli.StringFlag{ - Name: "database-driver, db", - Usage: "database `DRIVER` (sqlite, mysql)", - Value: "sqlite", - EnvVar: "PHOTOPRISM_DATABASE_DRIVER", - }, - cli.StringFlag{ - Name: "database-dsn, dsn", - Usage: "database connection `DSN` (sqlite file, optional for mysql)", - EnvVar: "PHOTOPRISM_DATABASE_DSN", - }, - cli.StringFlag{ - Name: "database-server, db-server", - Usage: "database `HOST` incl. port e.g. \"mariadb:3306\" (or socket path)", - EnvVar: "PHOTOPRISM_DATABASE_SERVER", - }, - cli.StringFlag{ - Name: "database-name, db-name", - Value: "photoprism", - Usage: "database schema `NAME`", - EnvVar: "PHOTOPRISM_DATABASE_NAME", - }, - cli.StringFlag{ - Name: "database-user, db-user", - Value: "photoprism", - Usage: "database user `NAME`", - EnvVar: "PHOTOPRISM_DATABASE_USER", - }, - cli.StringFlag{ - Name: "database-password, db-pass", - Usage: "database user `PASSWORD`", - EnvVar: "PHOTOPRISM_DATABASE_PASSWORD", - }, - cli.IntFlag{ - Name: "database-conns", - Usage: "maximum `NUMBER` of open database connections", - EnvVar: "PHOTOPRISM_DATABASE_CONNS", - }, - cli.IntFlag{ - Name: "database-conns-idle", - Usage: "maximum `NUMBER` of idle database connections", - EnvVar: "PHOTOPRISM_DATABASE_CONNS_IDLE", - }, - cli.StringFlag{ - Name: "darktable-bin", - Usage: "Darktable CLI `COMMAND` for RAW to JPEG conversion", - Value: "darktable-cli", - EnvVar: "PHOTOPRISM_DARKTABLE_BIN", - }, - cli.StringFlag{ - Name: "darktable-blacklist", - Usage: "do not use Darktable to convert files with these `EXTENSIONS`", - Value: "dng,cr3", - EnvVar: "PHOTOPRISM_DARKTABLE_BLACKLIST", - }, - cli.StringFlag{ - Name: "darktable-cache-path", - Usage: "custom Darktable cache `PATH`", - Value: "", - EnvVar: "PHOTOPRISM_DARKTABLE_CACHE_PATH", - }, - cli.StringFlag{ - Name: "darktable-config-path", - Usage: "custom Darktable config `PATH`", - Value: "", - EnvVar: "PHOTOPRISM_DARKTABLE_CONFIG_PATH", - }, - cli.StringFlag{ - Name: "rawtherapee-bin", - Usage: "RawTherapee CLI `COMMAND` for RAW to JPEG conversion", - Value: "rawtherapee-cli", - EnvVar: "PHOTOPRISM_RAWTHERAPEE_BIN", - }, - cli.StringFlag{ - Name: "rawtherapee-blacklist", - Usage: "do not use RawTherapee to convert files with these `EXTENSIONS`", - Value: "", - EnvVar: "PHOTOPRISM_RAWTHERAPEE_BLACKLIST", - }, - cli.StringFlag{ - Name: "sips-bin", - Usage: "Sips `COMMAND` for RAW to JPEG conversion (macOS only)", - Value: "sips", - EnvVar: "PHOTOPRISM_SIPS_BIN", - }, - cli.StringFlag{ - Name: "heifconvert-bin", - Usage: "HEIC/HEIF image conversion `COMMAND`", - Value: "heif-convert", - EnvVar: "PHOTOPRISM_HEIFCONVERT_BIN", - }, - cli.StringFlag{ - Name: "ffmpeg-bin", - Usage: "FFmpeg `COMMAND` for video transcoding and thumbnail extraction", - Value: "ffmpeg", - EnvVar: "PHOTOPRISM_FFMPEG_BIN", - }, - cli.StringFlag{ - Name: "ffmpeg-encoder, vc", - Usage: "FFmpeg AVC encoder `NAME`", - Value: "libx264", - EnvVar: "PHOTOPRISM_FFMPEG_ENCODER", - }, - cli.IntFlag{ - Name: "ffmpeg-bitrate, vb", - Usage: "maximum FFmpeg encoding `BITRATE` (Mbit/s)", - Value: 50, - EnvVar: "PHOTOPRISM_FFMPEG_BITRATE", - }, - cli.StringFlag{ - Name: "exiftool-bin", - Usage: "ExifTool `COMMAND` for extracting metadata", - Value: "exiftool", - EnvVar: "PHOTOPRISM_EXIFTOOL_BIN", - }, - cli.StringFlag{ - Name: "download-token", - Usage: "`SECRET` download URL token for originals (default: random)", - EnvVar: "PHOTOPRISM_DOWNLOAD_TOKEN", - }, - cli.StringFlag{ - Name: "preview-token", - Usage: "`SECRET` thumbnail and video streaming URL token (default: random)", - EnvVar: "PHOTOPRISM_PREVIEW_TOKEN", - }, - cli.StringFlag{ - Name: "thumb-color", - Usage: "standard color `PROFILE` for thumbnails (leave blank to disable)", - Value: "sRGB", - EnvVar: "PHOTOPRISM_THUMB_COLOR", - }, - cli.StringFlag{ - Name: "thumb-filter, filter", - Usage: "image downscaling filter `NAME` (best to worst: blackman, lanczos, cubic, linear)", - Value: "lanczos", - EnvVar: "PHOTOPRISM_THUMB_FILTER", - }, - cli.IntFlag{ - Name: "thumb-size", - Usage: "maximum size of thumbnails created during indexing in `PIXELS` (720-7680)", - Value: 2048, - EnvVar: "PHOTOPRISM_THUMB_SIZE", - }, - cli.IntFlag{ - Name: "thumb-size-uncached", - Usage: "maximum size of missing thumbnails created on demand in `PIXELS` (720-7680)", - Value: 7680, - EnvVar: "PHOTOPRISM_THUMB_SIZE_UNCACHED", - }, - cli.BoolFlag{ - Name: "thumb-uncached, u", - Usage: "enable on-demand creation of missing thumbnails (high memory and cpu usage)", - EnvVar: "PHOTOPRISM_THUMB_UNCACHED", - }, - cli.StringFlag{ - Name: "jpeg-quality, q", - Usage: "`QUALITY` of created JPEG sidecars and thumbnails (25-100, best, high, default, low, worst)", - Value: thumb.JpegQuality.String(), - EnvVar: "PHOTOPRISM_JPEG_QUALITY", - }, - cli.IntFlag{ - Name: "jpeg-size", - Usage: "maximum size of created JPEG sidecar files in `PIXELS` (720-30000)", - Value: 7680, - EnvVar: "PHOTOPRISM_JPEG_SIZE", - }, - cli.IntFlag{ - Name: "face-size", - Usage: "minimum size of faces in `PIXELS` (20-10000)", - Value: face.SizeThreshold, - EnvVar: "PHOTOPRISM_FACE_SIZE", - }, - cli.Float64Flag{ - Name: "face-score", - Usage: "minimum face `QUALITY` score (1-100)", - Value: face.ScoreThreshold, - EnvVar: "PHOTOPRISM_FACE_SCORE", - }, - cli.IntFlag{ - Name: "face-overlap", - Usage: "face area overlap threshold in `PERCENT` (1-100)", - Value: face.OverlapThreshold, - EnvVar: "PHOTOPRISM_FACE_OVERLAP", - }, - cli.IntFlag{ - Name: "face-cluster-size", - Usage: "minimum size of automatically clustered faces in `PIXELS` (20-10000)", - Value: face.ClusterSizeThreshold, - EnvVar: "PHOTOPRISM_FACE_CLUSTER_SIZE", - }, - cli.IntFlag{ - Name: "face-cluster-score", - Usage: "minimum `QUALITY` score of automatically clustered faces (1-100)", - Value: face.ClusterScoreThreshold, - EnvVar: "PHOTOPRISM_FACE_CLUSTER_SCORE", - }, - cli.IntFlag{ - Name: "face-cluster-core", - Usage: "`NUMBER` of faces forming a cluster core (1-100)", - Value: face.ClusterCore, - EnvVar: "PHOTOPRISM_FACE_CLUSTER_CORE", - }, - cli.Float64Flag{ - Name: "face-cluster-dist", - Usage: "similarity `DISTANCE` of faces forming a cluster core (0.1-1.5)", - Value: face.ClusterDist, - EnvVar: "PHOTOPRISM_FACE_CLUSTER_DIST", - }, - cli.Float64Flag{ - Name: "face-match-dist", - Usage: "similarity `OFFSET` for matching faces with existing clusters (0.1-1.5)", - Value: face.MatchDist, - EnvVar: "PHOTOPRISM_FACE_MATCH_DIST", - }, - cli.StringFlag{ - Name: "pid-filename", - Usage: "process id `FILE`, used only in daemon mode", - EnvVar: "PHOTOPRISM_PID_FILENAME", - }, - cli.StringFlag{ - Name: "log-filename", - Usage: "server log `FILE`, used only in daemon mode", - EnvVar: "PHOTOPRISM_LOG_FILENAME", - Value: "", - }, +// Flags lists all global command-line parameters. +var Flags = CliFlags{ + CliFlag{ + Flag: cli.StringFlag{ + Name: "admin-password, pw", + Usage: fmt.Sprintf("initial admin `PASSWORD`, must have at least %d characters", entity.PasswordLen), + EnvVar: "PHOTOPRISM_ADMIN_PASSWORD", + }, + }, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "public, p", + Usage: "disable password authentication, WebDAV, and the advanced settings page", + EnvVar: "PHOTOPRISM_PUBLIC", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "auth, a", + Usage: "always require password authentication, overrides the public flag if it is set", + EnvVar: "PHOTOPRISM_AUTH", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "log-level, l", + Usage: "log message verbosity `LEVEL` (trace, debug, info, warning, error, fatal, panic)", + Value: "info", + EnvVar: "PHOTOPRISM_LOG_LEVEL", + }, + }, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "debug", + Usage: "enable debug mode, show non-essential log messages", + EnvVar: "PHOTOPRISM_DEBUG", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "trace", + Usage: "enable trace mode, show all log messages", + EnvVar: "PHOTOPRISM_TRACE", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "test", + Hidden: true, + Usage: "enable test mode", + }, + }, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "unsafe", + Hidden: true, + Usage: "enable unsafe mode", + EnvVar: "PHOTOPRISM_UNSAFE", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "demo", + Hidden: true, + Usage: "enable demo mode", + EnvVar: "PHOTOPRISM_DEMO", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "sponsor", + Hidden: true, + Usage: "your continuous support helps to pay for development and operating expenses", + EnvVar: "PHOTOPRISM_SPONSOR", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "partner-id", + Hidden: true, + Usage: "hosting partner id", + EnvVar: "PHOTOPRISM_PARTNER_ID", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "config-path, c", + Usage: "config storage `PATH`, values in options.yml override CLI flags and environment variables if present", + EnvVar: "PHOTOPRISM_CONFIG_PATH", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "defaults-yaml, y", + Usage: "load config defaults from `FILE` if exists, does not override CLI flags and environment variables", + Value: "/etc/photoprism/defaults.yml", + EnvVar: "PHOTOPRISM_DEFAULTS_YAML", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "originals-path, o", + Usage: "storage `PATH` of your original media files (photos and videos)", + EnvVar: "PHOTOPRISM_ORIGINALS_PATH", + }}, + CliFlag{ + Flag: cli.IntFlag{ + Name: "originals-limit, mb", + Value: 1000, + Usage: "maximum size of media files in `MB` (1-100000; -1 to disable)", + EnvVar: "PHOTOPRISM_ORIGINALS_LIMIT", + }}, + CliFlag{ + Flag: cli.IntFlag{ + Name: "resolution-limit, mp", + Value: 100, + Usage: "maximum resolution of media files in `MEGAPIXELS` (1-900; -1 to disable)", + EnvVar: "PHOTOPRISM_RESOLUTION_LIMIT", + }, + Tags: []string{EnvSponsor}, + }, + CliFlag{ + Flag: cli.StringFlag{ + Name: "storage-path, s", + Usage: "writable storage `PATH` for sidecar, cache, and database files", + EnvVar: "PHOTOPRISM_STORAGE_PATH", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "sidecar-path, sc", + Usage: "custom relative or absolute sidecar `PATH` *optional*", + EnvVar: "PHOTOPRISM_SIDECAR_PATH", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "backup-path, ba", + Usage: "custom backup `PATH` for index backup files *optional*", + EnvVar: "PHOTOPRISM_BACKUP_PATH", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "cache-path, ca", + Usage: "custom cache `PATH` for sessions and thumbnail files *optional*", + EnvVar: "PHOTOPRISM_CACHE_PATH", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "import-path, im", + Usage: "base `PATH` from which files can be imported to originals *optional*", + EnvVar: "PHOTOPRISM_IMPORT_PATH", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "assets-path, as", + Usage: "assets `PATH` containing static resources like icons, models, and translations", + EnvVar: "PHOTOPRISM_ASSETS_PATH", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "temp-path, tmp", + Usage: "temporary file `PATH` *optional*", + EnvVar: "PHOTOPRISM_TEMP_PATH", + }}, + CliFlag{ + Flag: cli.IntFlag{ + Name: "workers, w", + Usage: "maximum `NUMBER` of indexing workers, default depends on the number of physical cores", + EnvVar: "PHOTOPRISM_WORKERS", + Value: cpuid.CPU.PhysicalCores / 2, + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "wakeup-interval, i", + Usage: "`DURATION` between worker runs required for face recognition and index maintenance (1-86400s)", + Value: DefaultWakeupInterval.String(), + EnvVar: "PHOTOPRISM_WAKEUP_INTERVAL", + }}, + CliFlag{ + Flag: cli.IntFlag{ + Name: "auto-index", + Usage: "WebDAV auto index safety delay in `SECONDS` (-1 to disable)", + Value: DefaultAutoIndexDelay, + EnvVar: "PHOTOPRISM_AUTO_INDEX", + }}, + CliFlag{ + Flag: cli.IntFlag{ + Name: "auto-import", + Usage: "WebDAV auto import safety delay in `SECONDS` (-1 to disable)", + Value: DefaultAutoImportDelay, + EnvVar: "PHOTOPRISM_AUTO_IMPORT", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "read-only, r", + Usage: "disable import, upload, delete, and all other operations that require write permissions", + EnvVar: "PHOTOPRISM_READONLY", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "experimental, e", + Usage: "enable experimental features", + EnvVar: "PHOTOPRISM_EXPERIMENTAL", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "disable-webdav", + Usage: "disable built-in WebDAV server", + EnvVar: "PHOTOPRISM_DISABLE_WEBDAV", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "disable-settings", + Usage: "disable settings UI and API", + EnvVar: "PHOTOPRISM_DISABLE_SETTINGS", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "disable-places", + Usage: "disable reverse geocoding and maps", + EnvVar: "PHOTOPRISM_DISABLE_PLACES", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "disable-backups", + Usage: "disable backing up albums and photo metadata to YAML files", + EnvVar: "PHOTOPRISM_DISABLE_BACKUPS", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "disable-tensorflow", + Usage: "disable all features depending on TensorFlow", + EnvVar: "PHOTOPRISM_DISABLE_TENSORFLOW", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "disable-faces", + Usage: "disable facial recognition", + EnvVar: "PHOTOPRISM_DISABLE_FACES", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "disable-classification", + Usage: "disable image classification", + EnvVar: "PHOTOPRISM_DISABLE_CLASSIFICATION", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "disable-ffmpeg", + Usage: "disable video transcoding and thumbnail extraction with FFmpeg", + EnvVar: "PHOTOPRISM_DISABLE_FFMPEG", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "disable-exiftool", + Usage: "disable creating JSON metadata sidecar files with ExifTool", + EnvVar: "PHOTOPRISM_DISABLE_EXIFTOOL", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "disable-heifconvert", + Usage: "disable conversion of HEIC/HEIF files", + EnvVar: "PHOTOPRISM_DISABLE_HEIFCONVERT", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "disable-darktable", + Usage: "disable conversion of RAW files with Darktable", + EnvVar: "PHOTOPRISM_DISABLE_DARKTABLE", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "disable-rawtherapee", + Usage: "disable conversion of RAW files with RawTherapee", + EnvVar: "PHOTOPRISM_DISABLE_RAWTHERAPEE", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "disable-sips", + Usage: "disable conversion of RAW files with Sips *macOS only*", + EnvVar: "PHOTOPRISM_DISABLE_SIPS", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "disable-raw", + Usage: "disable indexing and conversion of RAW files", + EnvVar: "PHOTOPRISM_DISABLE_RAW", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "raw-presets", + Usage: "enables applying user presets when converting RAW files (reduces performance)", + EnvVar: "PHOTOPRISM_RAW_PRESETS", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "exif-bruteforce", + Usage: "always perform a brute-force search if no Exif headers were found", + EnvVar: "PHOTOPRISM_EXIF_BRUTEFORCE", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "detect-nsfw", + Usage: "flag photos as private that may be offensive (requires TensorFlow)", + EnvVar: "PHOTOPRISM_DETECT_NSFW", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "upload-nsfw, n", + Usage: "allow uploads that may be offensive", + EnvVar: "PHOTOPRISM_UPLOAD_NSFW", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "default-locale, lang", + Usage: "standard user interface language `CODE`", + Value: i18n.Default.Locale(), + EnvVar: "PHOTOPRISM_DEFAULT_LOCALE", + }, + }, + CliFlag{ + Flag: cli.StringFlag{ + Name: "default-theme", + Usage: "standard user interface theme `NAME`", + EnvVar: "PHOTOPRISM_DEFAULT_THEME", + }, + Tags: []string{EnvSponsor}, + }, + CliFlag{ + Flag: cli.StringFlag{ + Name: "app-mode", + Usage: "progressive web app `MODE` (fullscreen, standalone, minimal-ui, browser)", + Value: "standalone", + EnvVar: "PHOTOPRISM_APP_MODE", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "app-icon", + Usage: "progressive web app `ICON` (logo, app, crisp, mint, bold)", + EnvVar: "PHOTOPRISM_APP_ICON", + }, + Tags: []string{EnvSponsor}, + }, + CliFlag{ + Flag: cli.StringFlag{ + Name: "app-name", + Usage: "progressive web app `NAME` when installed on a device", + Value: "", + EnvVar: "PHOTOPRISM_APP_NAME", + }, + Tags: []string{EnvSponsor}, + }, + CliFlag{ + Flag: cli.StringFlag{ + Name: "imprint", + Usage: "legal `INFORMATION`, displayed in the page footer", + Value: "", + EnvVar: "PHOTOPRISM_IMPRINT", + }, + Tags: []string{EnvSponsor}, + }, + CliFlag{ + Flag: cli.StringFlag{ + Name: "imprint-url", + Usage: "legal information `URL`", + Value: "", + EnvVar: "PHOTOPRISM_IMPRINT_URL", + }, + Tags: []string{EnvSponsor}, + }, + CliFlag{ + Flag: cli.StringFlag{ + Name: "wallpaper-uri", + Usage: "login screen background image `URI`", + EnvVar: "PHOTOPRISM_WALLPAPER_URI", + Value: "", + }, + Tags: []string{EnvSponsor}, + }, + CliFlag{ + Flag: cli.StringFlag{ + Name: "cdn-url", + Usage: "content delivery network `URL`", + EnvVar: "PHOTOPRISM_CDN_URL", + }, + Tags: []string{EnvSponsor}, + }, + CliFlag{ + Flag: cli.StringFlag{ + Name: "site-url, url", + Usage: "public site `URL`", + Value: "http://localhost:2342/", + EnvVar: "PHOTOPRISM_SITE_URL", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "site-author", + Usage: "site `OWNER`, copyright, or artist", + EnvVar: "PHOTOPRISM_SITE_AUTHOR", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "site-title", + Usage: "site `TITLE`", + Value: "", + EnvVar: "PHOTOPRISM_SITE_TITLE", + }, + Tags: []string{EnvSponsor}, + }, + CliFlag{ + Flag: cli.StringFlag{ + Name: "site-caption", + Usage: "site `CAPTION`", + Value: "AI-Powered Photos App", + EnvVar: "PHOTOPRISM_SITE_CAPTION", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "site-description", + Usage: "site `DESCRIPTION` *optional*", + EnvVar: "PHOTOPRISM_SITE_DESCRIPTION", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "site-preview", + Usage: "sharing preview image `URL`", + EnvVar: "PHOTOPRISM_SITE_PREVIEW", + }, + Tags: []string{EnvSponsor}, + }, + CliFlag{ + Flag: cli.IntFlag{ + Name: "http-port, port", + Value: 2342, + Usage: "http server port `NUMBER`", + EnvVar: "PHOTOPRISM_HTTP_PORT", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "http-host, ip", + Usage: "http server `IP` address", + EnvVar: "PHOTOPRISM_HTTP_HOST", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "http-mode, mode", + Usage: "http server `MODE` (debug, release, or test)", + EnvVar: "PHOTOPRISM_HTTP_MODE", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "http-compression, z", + Usage: "http server compression `METHOD` (none or gzip)", + EnvVar: "PHOTOPRISM_HTTP_COMPRESSION", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "database-driver, db", + Usage: "database `DRIVER` (sqlite, mysql)", + Value: "sqlite", + EnvVar: "PHOTOPRISM_DATABASE_DRIVER", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "database-dsn, dsn", + Usage: "database connection `DSN` (sqlite file, optional for mysql)", + EnvVar: "PHOTOPRISM_DATABASE_DSN", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "database-server, db-server", + Usage: "database `HOST` incl. port e.g. \"mariadb:3306\" (or socket path)", + EnvVar: "PHOTOPRISM_DATABASE_SERVER", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "database-name, db-name", + Value: "photoprism", + Usage: "database schema `NAME`", + EnvVar: "PHOTOPRISM_DATABASE_NAME", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "database-user, db-user", + Value: "photoprism", + Usage: "database user `NAME`", + EnvVar: "PHOTOPRISM_DATABASE_USER", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "database-password, db-pass", + Usage: "database user `PASSWORD`", + EnvVar: "PHOTOPRISM_DATABASE_PASSWORD", + }}, + CliFlag{ + Flag: cli.IntFlag{ + Name: "database-conns", + Usage: "maximum `NUMBER` of open database connections", + EnvVar: "PHOTOPRISM_DATABASE_CONNS", + }}, + CliFlag{ + Flag: cli.IntFlag{ + Name: "database-conns-idle", + Usage: "maximum `NUMBER` of idle database connections", + EnvVar: "PHOTOPRISM_DATABASE_CONNS_IDLE", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "darktable-bin", + Usage: "Darktable CLI `COMMAND` for RAW to JPEG conversion", + Value: "darktable-cli", + EnvVar: "PHOTOPRISM_DARKTABLE_BIN", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "darktable-blacklist", + Usage: "do not use Darktable to convert files with these `EXTENSIONS`", + Value: "dng,cr3", + EnvVar: "PHOTOPRISM_DARKTABLE_BLACKLIST", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "darktable-cache-path", + Usage: "custom Darktable cache `PATH`", + Value: "", + EnvVar: "PHOTOPRISM_DARKTABLE_CACHE_PATH", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "darktable-config-path", + Usage: "custom Darktable config `PATH`", + Value: "", + EnvVar: "PHOTOPRISM_DARKTABLE_CONFIG_PATH", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "rawtherapee-bin", + Usage: "RawTherapee CLI `COMMAND` for RAW to JPEG conversion", + Value: "rawtherapee-cli", + EnvVar: "PHOTOPRISM_RAWTHERAPEE_BIN", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "rawtherapee-blacklist", + Usage: "do not use RawTherapee to convert files with these `EXTENSIONS`", + Value: "", + EnvVar: "PHOTOPRISM_RAWTHERAPEE_BLACKLIST", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "sips-bin", + Usage: "Sips `COMMAND` for RAW to JPEG conversion *macOS only*", + Value: "sips", + EnvVar: "PHOTOPRISM_SIPS_BIN", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "heifconvert-bin", + Usage: "HEIC/HEIF image conversion `COMMAND`", + Value: "heif-convert", + EnvVar: "PHOTOPRISM_HEIFCONVERT_BIN", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "ffmpeg-bin", + Usage: "FFmpeg `COMMAND` for video transcoding and thumbnail extraction", + Value: "ffmpeg", + EnvVar: "PHOTOPRISM_FFMPEG_BIN", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "ffmpeg-encoder, vc", + Usage: "FFmpeg AVC encoder `NAME`", + Value: "libx264", + EnvVar: "PHOTOPRISM_FFMPEG_ENCODER", + }, + Tags: []string{EnvSponsor}, + }, + CliFlag{ + Flag: cli.IntFlag{ + Name: "ffmpeg-bitrate, vb", + Usage: "maximum FFmpeg encoding `BITRATE` (Mbit/s)", + Value: 50, + EnvVar: "PHOTOPRISM_FFMPEG_BITRATE", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "exiftool-bin", + Usage: "ExifTool `COMMAND` for extracting metadata", + Value: "exiftool", + EnvVar: "PHOTOPRISM_EXIFTOOL_BIN", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "download-token", + Usage: "`SECRET` download URL token for originals (default: random)", + EnvVar: "PHOTOPRISM_DOWNLOAD_TOKEN", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "preview-token", + Usage: "`SECRET` thumbnail and video streaming URL token (default: random)", + EnvVar: "PHOTOPRISM_PREVIEW_TOKEN", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "thumb-color", + Usage: "standard color `PROFILE` for thumbnails (leave blank to disable)", + Value: "sRGB", + EnvVar: "PHOTOPRISM_THUMB_COLOR", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "thumb-filter, filter", + Usage: "image downscaling filter `NAME` (best to worst: blackman, lanczos, cubic, linear)", + Value: "lanczos", + EnvVar: "PHOTOPRISM_THUMB_FILTER", + }}, + CliFlag{ + Flag: cli.IntFlag{ + Name: "thumb-size", + Usage: "maximum size of thumbnails created during indexing in `PIXELS` (720-7680)", + Value: 2048, + EnvVar: "PHOTOPRISM_THUMB_SIZE", + }}, + CliFlag{ + Flag: cli.IntFlag{ + Name: "thumb-size-uncached", + Usage: "maximum size of missing thumbnails created on demand in `PIXELS` (720-7680)", + Value: 7680, + EnvVar: "PHOTOPRISM_THUMB_SIZE_UNCACHED", + }}, + CliFlag{ + Flag: cli.BoolFlag{ + Name: "thumb-uncached, u", + Usage: "enable on-demand creation of missing thumbnails (high memory and cpu usage)", + EnvVar: "PHOTOPRISM_THUMB_UNCACHED", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "jpeg-quality, q", + Usage: "`QUALITY` of created JPEG sidecars and thumbnails (25-100, best, high, default, low, worst)", + Value: thumb.JpegQuality.String(), + EnvVar: "PHOTOPRISM_JPEG_QUALITY", + }}, + CliFlag{ + Flag: cli.IntFlag{ + Name: "jpeg-size", + Usage: "maximum size of created JPEG sidecar files in `PIXELS` (720-30000)", + Value: 7680, + EnvVar: "PHOTOPRISM_JPEG_SIZE", + }}, + CliFlag{ + Flag: cli.IntFlag{ + Name: "face-size", + Usage: "minimum size of faces in `PIXELS` (20-10000)", + Value: face.SizeThreshold, + EnvVar: "PHOTOPRISM_FACE_SIZE", + }}, + CliFlag{ + Flag: cli.Float64Flag{ + Name: "face-score", + Usage: "minimum face `QUALITY` score (1-100)", + Value: face.ScoreThreshold, + EnvVar: "PHOTOPRISM_FACE_SCORE", + }}, + CliFlag{ + Flag: cli.IntFlag{ + Name: "face-overlap", + Usage: "face area overlap threshold in `PERCENT` (1-100)", + Value: face.OverlapThreshold, + EnvVar: "PHOTOPRISM_FACE_OVERLAP", + }, + }, + CliFlag{ + Flag: cli.IntFlag{ + Name: "face-cluster-size", + Usage: "minimum size of automatically clustered faces in `PIXELS` (20-10000)", + Value: face.ClusterSizeThreshold, + EnvVar: "PHOTOPRISM_FACE_CLUSTER_SIZE", + }, + Tags: []string{EnvSponsor}, + }, + CliFlag{ + Flag: cli.IntFlag{ + Name: "face-cluster-score", + Usage: "minimum `QUALITY` score of automatically clustered faces (1-100)", + Value: face.ClusterScoreThreshold, + EnvVar: "PHOTOPRISM_FACE_CLUSTER_SCORE", + }, + Tags: []string{EnvSponsor}, + }, + CliFlag{ + Flag: cli.IntFlag{ + Name: "face-cluster-core", + Usage: "`NUMBER` of faces forming a cluster core (1-100)", + Value: face.ClusterCore, + EnvVar: "PHOTOPRISM_FACE_CLUSTER_CORE", + }, + Tags: []string{EnvSponsor}, + }, + CliFlag{ + Flag: cli.Float64Flag{ + Name: "face-cluster-dist", + Usage: "similarity `DISTANCE` of faces forming a cluster core (0.1-1.5)", + Value: face.ClusterDist, + EnvVar: "PHOTOPRISM_FACE_CLUSTER_DIST", + }, + Tags: []string{EnvSponsor}, + }, + CliFlag{ + Flag: cli.Float64Flag{ + Name: "face-match-dist", + Usage: "similarity `OFFSET` for matching faces with existing clusters (0.1-1.5)", + Value: face.MatchDist, + EnvVar: "PHOTOPRISM_FACE_MATCH_DIST", + }, + Tags: []string{EnvSponsor}, + }, + CliFlag{ + Flag: cli.StringFlag{ + Name: "pid-filename", + Usage: "process id `FILE` *daemon-mode only*", + EnvVar: "PHOTOPRISM_PID_FILENAME", + }}, + CliFlag{ + Flag: cli.StringFlag{ + Name: "log-filename", + Usage: "server log `FILE` *daemon-mode only*", + EnvVar: "PHOTOPRISM_LOG_FILENAME", + Value: "", + }}, } diff --git a/internal/config/global_options.go b/internal/config/global_options.go index bd0d0ce97..e17b36b00 100644 --- a/internal/config/global_options.go +++ b/internal/config/global_options.go @@ -74,6 +74,7 @@ type Options struct { AppIcon string `yaml:"AppIcon" json:"AppIcon" flag:"app-icon"` AppName string `yaml:"AppName" json:"AppName" flag:"app-name"` AppMode string `yaml:"AppMode" json:"AppMode" flag:"app-mode"` + WallpaperUri string `yaml:"WallpaperUri" json:"WallpaperUri" flag:"wallpaper-uri"` CdnUrl string `yaml:"CdnUrl" json:"CdnUrl" flag:"cdn-url"` SiteUrl string `yaml:"SiteUrl" json:"SiteUrl" flag:"site-url"` SiteAuthor string `yaml:"SiteAuthor" json:"SiteAuthor" flag:"site-author"` diff --git a/internal/config/test.go b/internal/config/test.go index 2719488d9..17aaa95b4 100644 --- a/internal/config/test.go +++ b/internal/config/test.go @@ -200,6 +200,7 @@ func CliTestContext() *cli.Context { globalSet.String("darktable-cli", config.DarktableBin, "doc") globalSet.String("darktable-blacklist", config.DarktableBlacklist, "doc") globalSet.String("wakeup-interval", "1h34m9s", "doc") + globalSet.Bool("test", true, "doc") globalSet.Bool("debug", false, "doc") globalSet.Bool("detect-nsfw", config.DetectNSFW, "doc") globalSet.Int("auto-index", config.AutoIndex, "doc") @@ -224,6 +225,7 @@ func CliTestContext() *cli.Context { LogError(c.Set("darktable-blacklist", "raf,cr3")) LogError(c.Set("wakeup-interval", "1h34m9s")) LogError(c.Set("detect-nsfw", "true")) + LogError(c.Set("test", "true")) LogError(c.Set("auto-index", strconv.Itoa(config.AutoIndex))) LogError(c.Set("auto-import", strconv.Itoa(config.AutoImport))) diff --git a/internal/search/photos_filter_portrait_test.go b/internal/search/photos_filter_portrait_test.go index 2fe443215..dfa4e1c07 100644 --- a/internal/search/photos_filter_portrait_test.go +++ b/internal/search/photos_filter_portrait_test.go @@ -19,7 +19,7 @@ func TestPhotosQueryPortrait(t *testing.T) { t.Fatal(err) } - assert.GreaterOrEqual(t, len(photos0), 40) + assert.GreaterOrEqual(t, len(photos0), 39) t.Run("false > yes", func(t *testing.T) { var f form.SearchPhotos diff --git a/pkg/clean/uri.go b/pkg/clean/uri.go new file mode 100644 index 000000000..baeecdd9a --- /dev/null +++ b/pkg/clean/uri.go @@ -0,0 +1,22 @@ +package clean + +import ( + "net/url" + "strings" +) + +// Uri removes invalid character from an uri string. +func Uri(s string) string { + if s == "" || reject(s, 512) || strings.Contains(s, "..") { + return "" + } + + // Trim whitespace. + s = strings.TrimSpace(s) + + if uri, err := url.Parse(s); err != nil { + return "" + } else { + return uri.String() + } +} diff --git a/pkg/clean/uri_test.go b/pkg/clean/uri_test.go new file mode 100644 index 000000000..1f5ef039a --- /dev/null +++ b/pkg/clean/uri_test.go @@ -0,0 +1,18 @@ +package clean + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUri(t *testing.T) { + t.Run("Valid", func(t *testing.T) { + result := Uri("https://docs.photoprism.app/getting-started/config-options/#file-converters") + assert.Equal(t, "https://docs.photoprism.app/getting-started/config-options/#file-converters", result) + }) + t.Run("Invalid", func(t *testing.T) { + result := Uri("https://..docs.photoprism.app/gettin\\g-started/config-options/\tfile-converters") + assert.Equal(t, "", result) + }) +}