photoprism/internal/config/client.go
Michael Mayer 82d61d1f93 File Types: Add experimental support for animated GIFs #590 #2207
Animated GIFs are transcoded to AVC because it is much smaller and
thus also suitable for long/large animations. In addition, this commit
adds support for more metadata fields such as frame rate, number of
frames, file capture timestamp (unix milliseconds), media type,
and software version. Support for SVG files can later be implemented in
a similar way.
2022-04-13 22:17:59 +02:00

505 lines
16 KiB
Go

package config
import (
"strings"
"time"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/pkg/colors"
"github.com/photoprism/photoprism/pkg/txt"
)
// ClientConfig represents HTTP client / Web UI config options.
type ClientConfig struct {
Mode string `json:"mode"`
Name string `json:"name"`
Version string `json:"version"`
Copyright string `json:"copyright"`
Flags string `json:"flags"`
BaseUri string `json:"baseUri"`
StaticUri string `json:"staticUri"`
CssUri string `json:"cssUri"`
JsUri string `json:"jsUri"`
ManifestUri string `json:"manifestUri"`
ApiUri string `json:"apiUri"`
ContentUri string `json:"contentUri"`
SiteUrl string `json:"siteUrl"`
SiteDomain string `json:"siteDomain"`
SiteAuthor string `json:"siteAuthor"`
SiteTitle string `json:"siteTitle"`
SiteCaption string `json:"siteCaption"`
SiteDescription string `json:"siteDescription"`
SitePreview string `json:"sitePreview"`
Imprint string `json:"imprint"`
ImprintUrl string `json:"imprintUrl"`
AppName string `json:"appName"`
AppMode string `json:"appMode"`
AppIcon string `json:"appIcon"`
Debug bool `json:"debug"`
Trace bool `json:"trace"`
Test bool `json:"test"`
Demo bool `json:"demo"`
Sponsor bool `json:"sponsor"`
ReadOnly bool `json:"readonly"`
UploadNSFW bool `json:"uploadNSFW"`
Public bool `json:"public"`
Experimental bool `json:"experimental"`
AlbumCategories []string `json:"albumCategories"`
Albums entity.Albums `json:"albums"`
Cameras entity.Cameras `json:"cameras"`
Lenses entity.Lenses `json:"lenses"`
Countries entity.Countries `json:"countries"`
People entity.People `json:"people"`
Thumbs ThumbSizes `json:"thumbs"`
Status string `json:"status"`
MapKey string `json:"mapKey"`
DownloadToken string `json:"downloadToken"`
PreviewToken string `json:"previewToken"`
Settings Settings `json:"settings"`
Disable ClientDisable `json:"disable"`
Count ClientCounts `json:"count"`
Pos ClientPosition `json:"pos"`
Years Years `json:"years"`
Colors []map[string]string `json:"colors"`
Categories CategoryLabels `json:"categories"`
Clip int `json:"clip"`
Server RuntimeInfo `json:"server"`
}
// Years represents a list of years.
type Years []int
// ClientDisable represents disabled client features a user cannot turn back on.
type ClientDisable struct {
Backups bool `json:"backups"`
WebDAV bool `json:"webdav"`
Settings bool `json:"settings"`
Places bool `json:"places"`
ExifTool bool `json:"exiftool"`
FFmpeg bool `json:"ffmpeg"`
Raw bool `json:"raw"`
Darktable bool `json:"darktable"`
Rawtherapee bool `json:"rawtherapee"`
Sips bool `json:"sips"`
HeifConvert bool `json:"heifconvert"`
TensorFlow bool `json:"tensorflow"`
Faces bool `json:"faces"`
Classification bool `json:"classification"`
}
// ClientCounts represents photo, video and album counts for the client UI.
type ClientCounts struct {
All int `json:"all"`
Photos int `json:"photos"`
Live int `json:"live"`
Videos int `json:"videos"`
Cameras int `json:"cameras"`
Lenses int `json:"lenses"`
Countries int `json:"countries"`
Hidden int `json:"hidden"`
Favorites int `json:"favorites"`
Private int `json:"private"`
Review int `json:"review"`
Stories int `json:"stories"`
Albums int `json:"albums"`
Moments int `json:"moments"`
Months int `json:"months"`
Folders int `json:"folders"`
Files int `json:"files"`
People int `json:"people"`
Places int `json:"places"`
States int `json:"states"`
Labels int `json:"labels"`
LabelMaxPhotos int `json:"labelMaxPhotos"`
}
type CategoryLabels []CategoryLabel
type CategoryLabel struct {
LabelUID string `json:"UID"`
CustomSlug string `json:"Slug"`
LabelName string `json:"Name"`
}
type ClientPosition struct {
PhotoUID string `json:"uid"`
CellID string `json:"cid"`
TakenAt time.Time `json:"utc"`
PhotoLat float64 `json:"lat"`
PhotoLng float64 `json:"lng"`
}
// Flags returns config flags as string slice.
func (c *Config) Flags() (flags []string) {
if c.Public() {
flags = append(flags, "public")
}
if c.Debug() {
flags = append(flags, "debug")
}
if c.Test() {
flags = append(flags, "test")
}
if c.Demo() {
flags = append(flags, "demo")
}
if c.Sponsor() {
flags = append(flags, "sponsor")
}
if c.Experimental() {
flags = append(flags, "experimental")
}
if c.ReadOnly() {
flags = append(flags, "readonly")
}
if !c.DisableSettings() {
flags = append(flags, "settings")
}
if !c.Settings().UI.Scrollbar {
flags = append(flags, "hide-scrollbar")
}
return flags
}
// PublicConfig returns public client config options with as little information as possible.
func (c *Config) PublicConfig() ClientConfig {
if c.Public() {
return c.UserConfig()
}
assets := c.ClientAssets()
settings := c.Settings()
result := ClientConfig{
Settings: Settings{
UI: settings.UI,
Search: settings.Search,
Maps: settings.Maps,
Features: settings.Features,
Share: settings.Share,
},
Disable: ClientDisable{
Backups: true,
WebDAV: true,
Settings: c.DisableSettings(),
Places: c.DisablePlaces(),
ExifTool: true,
FFmpeg: true,
Raw: true,
Darktable: true,
Rawtherapee: true,
Sips: true,
HeifConvert: true,
TensorFlow: true,
Faces: true,
Classification: true,
},
Flags: strings.Join(c.Flags(), " "),
Mode: "public",
Name: c.Name(),
BaseUri: c.BaseUri(""),
StaticUri: c.StaticUri(),
CssUri: assets.AppCssUri(),
JsUri: assets.AppJsUri(),
ApiUri: c.ApiUri(),
ContentUri: c.ContentUri(),
SiteUrl: c.SiteUrl(),
SiteDomain: c.SiteDomain(),
SiteAuthor: c.SiteAuthor(),
SiteTitle: c.SiteTitle(),
SiteCaption: c.SiteCaption(),
SiteDescription: c.SiteDescription(),
SitePreview: c.SitePreview(),
Imprint: c.Imprint(),
ImprintUrl: c.ImprintUrl(),
AppName: c.AppName(),
AppMode: c.AppMode(),
AppIcon: c.AppIcon(),
Version: c.Version(),
Copyright: c.Copyright(),
Debug: c.Debug(),
Trace: c.Trace(),
Test: c.Test(),
Demo: c.Demo(),
Sponsor: c.Sponsor(),
ReadOnly: c.ReadOnly(),
Public: c.Public(),
Experimental: c.Experimental(),
Status: "",
MapKey: "",
Thumbs: Thumbs,
Colors: colors.All.List(),
ManifestUri: c.ClientManifestUri(),
Clip: txt.ClipDefault,
PreviewToken: "public",
DownloadToken: "public",
}
return result
}
// GuestConfig returns client config options for the sharing with guests.
func (c *Config) GuestConfig() ClientConfig {
assets := c.ClientAssets()
settings := c.Settings()
result := ClientConfig{
Settings: Settings{
UI: settings.UI,
Search: settings.Search,
Maps: settings.Maps,
Features: settings.Features,
Share: settings.Share,
},
Disable: ClientDisable{
Backups: true,
WebDAV: c.DisableWebDAV(),
Settings: c.DisableSettings(),
Places: c.DisablePlaces(),
ExifTool: true,
FFmpeg: true,
Raw: true,
Darktable: true,
Rawtherapee: true,
Sips: true,
HeifConvert: true,
TensorFlow: true,
Faces: true,
Classification: true,
},
Flags: strings.Join(c.Flags(), " "),
Mode: "guest",
Name: c.Name(),
BaseUri: c.BaseUri(""),
StaticUri: c.StaticUri(),
CssUri: assets.ShareCssUri(),
JsUri: assets.ShareJsUri(),
ApiUri: c.ApiUri(),
ContentUri: c.ContentUri(),
SiteUrl: c.SiteUrl(),
SiteDomain: c.SiteDomain(),
SiteAuthor: c.SiteAuthor(),
SiteTitle: c.SiteTitle(),
SiteCaption: c.SiteCaption(),
SiteDescription: c.SiteDescription(),
SitePreview: c.SitePreview(),
Imprint: c.Imprint(),
ImprintUrl: c.ImprintUrl(),
AppName: c.AppName(),
AppMode: c.AppMode(),
AppIcon: c.AppIcon(),
Version: c.Version(),
Copyright: c.Copyright(),
Debug: c.Debug(),
Trace: c.Trace(),
Test: c.Test(),
Demo: c.Demo(),
Sponsor: c.Sponsor(),
ReadOnly: true,
UploadNSFW: c.UploadNSFW(),
Public: true,
Experimental: false,
Colors: colors.All.List(),
Thumbs: Thumbs,
Status: c.Hub().Status,
MapKey: c.Hub().MapKey(),
DownloadToken: c.DownloadToken(),
PreviewToken: c.PreviewToken(),
ManifestUri: c.ClientManifestUri(),
Clip: txt.ClipDefault,
}
return result
}
// UserConfig returns client configuration options for registered users.
func (c *Config) UserConfig() ClientConfig {
assets := c.ClientAssets()
result := ClientConfig{
Settings: *c.Settings(),
Disable: ClientDisable{
Backups: c.DisableBackups(),
WebDAV: c.DisableWebDAV(),
Settings: c.DisableSettings(),
Places: c.DisablePlaces(),
ExifTool: c.DisableExifTool(),
FFmpeg: c.DisableFFmpeg(),
Raw: c.DisableRaw(),
Darktable: c.DisableDarktable(),
Rawtherapee: c.DisableRawtherapee(),
Sips: c.DisableSips(),
HeifConvert: c.DisableHeifConvert(),
TensorFlow: c.DisableTensorFlow(),
Faces: c.DisableFaces(),
Classification: c.DisableClassification(),
},
Flags: strings.Join(c.Flags(), " "),
Mode: "user",
Name: c.Name(),
BaseUri: c.BaseUri(""),
StaticUri: c.StaticUri(),
CssUri: assets.AppCssUri(),
JsUri: assets.AppJsUri(),
ApiUri: c.ApiUri(),
ContentUri: c.ContentUri(),
SiteUrl: c.SiteUrl(),
SiteDomain: c.SiteDomain(),
SiteAuthor: c.SiteAuthor(),
SiteTitle: c.SiteTitle(),
SiteCaption: c.SiteCaption(),
SiteDescription: c.SiteDescription(),
SitePreview: c.SitePreview(),
Imprint: c.Imprint(),
ImprintUrl: c.ImprintUrl(),
AppName: c.AppName(),
AppMode: c.AppMode(),
AppIcon: c.AppIcon(),
Version: c.Version(),
Copyright: c.Copyright(),
Debug: c.Debug(),
Trace: c.Trace(),
Test: c.Test(),
Demo: c.Demo(),
Sponsor: c.Sponsor(),
ReadOnly: c.ReadOnly(),
UploadNSFW: c.UploadNSFW(),
Public: c.Public(),
Experimental: c.Experimental(),
Colors: colors.All.List(),
Thumbs: Thumbs,
Status: c.Hub().Status,
MapKey: c.Hub().MapKey(),
DownloadToken: c.DownloadToken(),
PreviewToken: c.PreviewToken(),
ManifestUri: c.ClientManifestUri(),
Clip: txt.ClipDefault,
Server: NewRuntimeInfo(),
}
c.Db().
Table("photos").
Select("photo_uid, cell_id, photo_lat, photo_lng, taken_at").
Where("deleted_at IS NULL AND photo_lat <> 0 AND photo_lng <> 0").
Order("taken_at DESC").
Limit(1).Offset(0).
Take(&result.Pos)
c.Db().
Table("cameras").
Where("camera_slug <> 'zz' AND camera_slug <> ''").
Select("COUNT(*) AS cameras").
Take(&result.Count)
c.Db().
Table("lenses").
Where("lens_slug <> 'zz' AND lens_slug <> ''").
Select("COUNT(*) AS lenses").
Take(&result.Count)
c.Db().
Table("photos").
Select("SUM(photo_type = 'video' AND photo_quality >= 0 AND photo_private = 0) AS videos, " +
"SUM(photo_type = 'live' AND photo_quality >= 0 AND photo_private = 0) AS live, " +
"SUM(photo_quality = -1) AS hidden, SUM(photo_type IN ('image','raw','animated') AND photo_private = 0 AND photo_quality >= 0) AS photos, " +
"SUM(photo_type IN ('image','raw','live','animated') AND photo_quality < 3 AND photo_quality >= 0 AND photo_private = 0) AS review, " +
"SUM(photo_favorite = 1 AND photo_private = 0 AND photo_quality >= 0) AS favorites, " +
"SUM(photo_private = 1 AND photo_quality >= 0) AS private").
Where("photos.id NOT IN (SELECT photo_id FROM files WHERE file_primary = 1 AND (file_missing = 1 OR file_error <> ''))").
Where("deleted_at IS NULL").
Take(&result.Count)
result.Count.All = result.Count.Photos + result.Count.Live + result.Count.Videos
c.Db().
Table("labels").
Select("MAX(photo_count) AS label_max_photos, COUNT(*) AS labels").
Where("photo_count > 0").
Where("deleted_at IS NULL").
Where("(label_priority >= 0 OR label_favorite = 1)").
Take(&result.Count)
c.Db().
Table("albums").
Select("SUM(album_type = ?) AS albums, SUM(album_type = ?) AS moments, SUM(album_type = ?) AS months, SUM(album_type = ?) AS states, SUM(album_type = ?) AS folders", entity.AlbumDefault, entity.AlbumMoment, entity.AlbumMonth, entity.AlbumState, entity.AlbumFolder).
Where("deleted_at IS NULL AND (albums.album_type <> 'folder' OR albums.album_path IN (SELECT photos.photo_path FROM photos WHERE photos.deleted_at IS NULL))").
Take(&result.Count)
c.Db().
Table("files").
Select("COUNT(media_id) AS files").
Take(&result.Count)
c.Db().
Table("countries").
Select("(COUNT(*) - 1) AS countries").
Take(&result.Count)
c.Db().
Table("places").
Select("SUM(photo_count > 0) AS places").
Where("id <> 'zz'").
Take(&result.Count)
c.Db().
Order("country_slug").
Find(&result.Countries)
// People are subjects with type person.
result.Count.People, _ = query.PeopleCount()
result.People, _ = query.People()
c.Db().
Where("id IN (SELECT photos.camera_id FROM photos WHERE photos.photo_quality >= 0 OR photos.deleted_at IS NULL)").
Where("deleted_at IS NULL").
Limit(10000).Order("camera_slug").
Find(&result.Cameras)
c.Db().
Where("deleted_at IS NULL").
Limit(10000).Order("lens_slug").
Find(&result.Lenses)
c.Db().
Where("deleted_at IS NULL AND album_favorite = 1").
Limit(20).Order("album_title").
Find(&result.Albums)
c.Db().
Table("photos").
Where("photo_year > 0 AND (photos.photo_quality >= 0 OR photos.deleted_at IS NULL)").
Order("photo_year DESC").
Pluck("DISTINCT photo_year", &result.Years)
c.Db().
Table("categories").
Select("l.label_uid, l.custom_slug, l.label_name").
Joins("JOIN labels l ON categories.category_id = l.id").
Where("l.deleted_at IS NULL").
Group("l.custom_slug").
Order("l.custom_slug").
Limit(1000).Offset(0).
Scan(&result.Categories)
c.Db().
Table("albums").
Select("album_category").
Where("deleted_at IS NULL AND album_category <> ''").
Group("album_category").
Order("album_category").
Limit(1000).Offset(0).
Pluck("album_category", &result.AlbumCategories)
return result
}