CLI: Update and display the list of supported file formats #2247
This commit is contained in:
parent
68ba289d6c
commit
e42b870c09
21 changed files with 485 additions and 256 deletions
|
@ -56,31 +56,7 @@ func main() {
|
|||
app.Copyright = appCopyright
|
||||
app.EnableBashCompletion = true
|
||||
app.Flags = config.GlobalFlags
|
||||
|
||||
app.Commands = []cli.Command{
|
||||
commands.StartCommand,
|
||||
commands.StopCommand,
|
||||
commands.StatusCommand,
|
||||
commands.IndexCommand,
|
||||
commands.ImportCommand,
|
||||
commands.CopyCommand,
|
||||
commands.FacesCommand,
|
||||
commands.PlacesCommand,
|
||||
commands.PurgeCommand,
|
||||
commands.CleanUpCommand,
|
||||
commands.OptimizeCommand,
|
||||
commands.MomentsCommand,
|
||||
commands.ConvertCommand,
|
||||
commands.ThumbsCommand,
|
||||
commands.MigrationsCommand,
|
||||
commands.BackupCommand,
|
||||
commands.RestoreCommand,
|
||||
commands.ResetCommand,
|
||||
commands.PasswdCommand,
|
||||
commands.UsersCommand,
|
||||
commands.ShowCommand,
|
||||
commands.VersionCommand,
|
||||
}
|
||||
app.Commands = commands.PhotoPrism
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
log.Error(err)
|
||||
|
|
|
@ -151,10 +151,15 @@ export default class Util {
|
|||
return "Advanced Video Coding (AVC) / H.264";
|
||||
case "hvc1":
|
||||
return "High Efficiency Video Coding (HEVC) / H.265";
|
||||
case "av01":
|
||||
return "AOMedia Video 1 (AV1)";
|
||||
case "mpeg":
|
||||
return "Moving Picture Experts Group (MPEG)";
|
||||
case "mjpg":
|
||||
return "Motion JPEG (MJPEG)";
|
||||
return "Motion JPEG (M-JPEG)";
|
||||
case "heif":
|
||||
case "heic":
|
||||
return "High Efficiency Image File Format (HEIC)";
|
||||
return "High Efficiency Image File Format (HEIF)";
|
||||
case "1":
|
||||
return "Uncompressed";
|
||||
case "2":
|
||||
|
|
3
go.mod
3
go.mod
|
@ -80,8 +80,11 @@ require (
|
|||
github.com/gosimple/unidecode v1.0.1 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/mandykoh/go-parallel v0.1.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
|
|
7
go.sum
7
go.sum
|
@ -233,7 +233,10 @@ github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlW
|
|||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||
github.com/mattn/go-sqlite3 v2.0.1+incompatible h1:xQ15muvnzGBHpIpdrNi1DA5x0+TcBZzsIDwmw9uTHzw=
|
||||
github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
|
@ -245,6 +248,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
|||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/montanaflynn/stats v0.6.6 h1:Duep6KMIDpY4Yo11iFsvyqJDyfzLF9+sndUKT+v64GQ=
|
||||
github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/paulmach/go.geojson v1.4.0 h1:5x5moCkCtDo5x8af62P9IOAYGQcYHtxz2QJ3x1DoCgY=
|
||||
|
@ -261,6 +266,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
|
|
|
@ -1,9 +1,28 @@
|
|||
/*
|
||||
Package commands contains commands and flags used by the photoprism application.
|
||||
|
||||
Additional information concerning the command-line interface can be found in our Developer Guide:
|
||||
Package commands provides photoprism CLI (sub-)commands.
|
||||
|
||||
Copyright (c) 2018 - 2022 Michael Mayer <hello@photoprism.app>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under Version 3 of the GNU Affero General Public License (the "AGPL"):
|
||||
<https://docs.photoprism.app/license/agpl>
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
The AGPL is supplemented by our Trademark and Brand Guidelines,
|
||||
which describe how our Brand Assets may be used:
|
||||
<https://photoprism.app/trademark>
|
||||
|
||||
Feel free to send an e-mail to hello@photoprism.app if you have questions,
|
||||
want to support our work, or just want to say hello.
|
||||
|
||||
Additional information can be found in our Developer Guide:
|
||||
<https://docs.photoprism.app/developer-guide/>
|
||||
|
||||
https://github.com/photoprism/photoprism/wiki/Commands
|
||||
*/
|
||||
package commands
|
||||
|
||||
|
@ -11,13 +30,41 @@ import (
|
|||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/sevlyar/go-daemon"
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/sevlyar/go-daemon"
|
||||
)
|
||||
|
||||
var log = event.Log
|
||||
|
||||
// PhotoPrism contains the photoprism CLI (sub-)commands.
|
||||
var PhotoPrism = []cli.Command{
|
||||
StartCommand,
|
||||
StopCommand,
|
||||
StatusCommand,
|
||||
IndexCommand,
|
||||
ImportCommand,
|
||||
CopyCommand,
|
||||
FacesCommand,
|
||||
PlacesCommand,
|
||||
PurgeCommand,
|
||||
CleanUpCommand,
|
||||
OptimizeCommand,
|
||||
MomentsCommand,
|
||||
ConvertCommand,
|
||||
ThumbsCommand,
|
||||
MigrationsCommand,
|
||||
BackupCommand,
|
||||
RestoreCommand,
|
||||
ResetCommand,
|
||||
PasswdCommand,
|
||||
UsersCommand,
|
||||
ShowCommand,
|
||||
VersionCommand,
|
||||
}
|
||||
|
||||
// childAlreadyRunning tests if a .pid file at filePath is a running process.
|
||||
// it returns the pid value and the running status (true or false).
|
||||
func childAlreadyRunning(filePath string) (pid int, running bool) {
|
||||
|
|
|
@ -48,7 +48,8 @@ func TestIndexCommand(t *testing.T) {
|
|||
// Expected index command output.
|
||||
assert.Contains(t, output, "indexing originals")
|
||||
assert.Contains(t, output, "classify: loading")
|
||||
assert.Contains(t, output, "indexed 0 files")
|
||||
assert.Contains(t, output, "indexed")
|
||||
assert.Contains(t, output, "files")
|
||||
} else {
|
||||
t.Fatal("log output missing")
|
||||
}
|
||||
|
|
|
@ -2,174 +2,34 @@ package commands
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/pkg/report"
|
||||
)
|
||||
|
||||
var ShowConfigCommand = cli.Command{
|
||||
Name: "config",
|
||||
Usage: "Displays global configuration values",
|
||||
Name: "config",
|
||||
Usage: "Displays global configuration values",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "no-wrap, n",
|
||||
Usage: "disable text-wrapping",
|
||||
},
|
||||
},
|
||||
Action: showConfigAction,
|
||||
}
|
||||
|
||||
// showConfigAction lists configuration options and their values.
|
||||
func showConfigAction(ctx *cli.Context) error {
|
||||
conf := config.NewConfig(ctx)
|
||||
conf.SetLogLevel(logrus.FatalLevel)
|
||||
|
||||
dbDriver := conf.DatabaseDriver()
|
||||
rows, cols := conf.Table()
|
||||
|
||||
fmt.Printf("%-25s VALUE\n", "NAME")
|
||||
|
||||
// Flags.
|
||||
fmt.Printf("%-25s %t\n", "debug", conf.Debug())
|
||||
fmt.Printf("%-25s %s\n", "log-level", conf.LogLevel())
|
||||
fmt.Printf("%-25s %t\n", "public", conf.Public())
|
||||
fmt.Printf("%-25s %s\n", "admin-password", strings.Repeat("*", utf8.RuneCountInString(conf.AdminPassword())))
|
||||
fmt.Printf("%-25s %t\n", "read-only", conf.ReadOnly())
|
||||
fmt.Printf("%-25s %t\n", "experimental", conf.Experimental())
|
||||
|
||||
// Config.
|
||||
fmt.Printf("%-25s %s\n", "config-file", conf.ConfigFile())
|
||||
fmt.Printf("%-25s %s\n", "config-path", conf.ConfigPath())
|
||||
fmt.Printf("%-25s %s\n", "settings-file", conf.SettingsFile())
|
||||
|
||||
// Originals.
|
||||
fmt.Printf("%-25s %s\n", "originals-path", conf.OriginalsPath())
|
||||
fmt.Printf("%-25s %d\n", "originals-limit", conf.OriginalsLimit())
|
||||
fmt.Printf("%-25s %d\n", "resolution-limit", conf.ResolutionLimit())
|
||||
|
||||
// Other paths.
|
||||
fmt.Printf("%-25s %s\n", "storage-path", conf.StoragePath())
|
||||
fmt.Printf("%-25s %s\n", "sidecar-path", conf.SidecarPath())
|
||||
fmt.Printf("%-25s %s\n", "cache-path", conf.CachePath())
|
||||
fmt.Printf("%-25s %s\n", "albums-path", conf.AlbumsPath())
|
||||
fmt.Printf("%-25s %s\n", "backup-path", conf.BackupPath())
|
||||
fmt.Printf("%-25s %s\n", "import-path", conf.ImportPath())
|
||||
fmt.Printf("%-25s %s\n", "assets-path", conf.AssetsPath())
|
||||
fmt.Printf("%-25s %s\n", "static-path", conf.StaticPath())
|
||||
fmt.Printf("%-25s %s\n", "build-path", conf.BuildPath())
|
||||
fmt.Printf("%-25s %s\n", "img-path", conf.ImgPath())
|
||||
fmt.Printf("%-25s %s\n", "templates-path", conf.TemplatesPath())
|
||||
fmt.Printf("%-25s %s\n", "temp-path", conf.TempPath())
|
||||
|
||||
// Workers.
|
||||
fmt.Printf("%-25s %d\n", "workers", conf.Workers())
|
||||
fmt.Printf("%-25s %s\n", "wakeup-interval", conf.WakeupInterval().String())
|
||||
fmt.Printf("%-25s %d\n", "auto-index", conf.AutoIndex()/time.Second)
|
||||
fmt.Printf("%-25s %d\n", "auto-import", conf.AutoImport()/time.Second)
|
||||
|
||||
// Feature Flags.
|
||||
fmt.Printf("%-25s %t\n", "disable-backups", conf.DisableBackups())
|
||||
fmt.Printf("%-25s %t\n", "disable-settings", conf.DisableSettings())
|
||||
fmt.Printf("%-25s %t\n", "disable-places", conf.DisablePlaces())
|
||||
fmt.Printf("%-25s %t\n", "disable-tensorflow", conf.DisableTensorFlow())
|
||||
fmt.Printf("%-25s %t\n", "disable-faces", conf.DisableFaces())
|
||||
fmt.Printf("%-25s %t\n", "disable-classification", conf.DisableClassification())
|
||||
fmt.Printf("%-25s %t\n", "disable-ffmpeg", conf.DisableFFmpeg())
|
||||
fmt.Printf("%-25s %t\n", "disable-exiftool", conf.DisableExifTool())
|
||||
fmt.Printf("%-25s %t\n", "disable-heifconvert", conf.DisableHeifConvert())
|
||||
fmt.Printf("%-25s %t\n", "disable-darktable", conf.DisableDarktable())
|
||||
fmt.Printf("%-25s %t\n", "disable-rawtherapee", conf.DisableRawtherapee())
|
||||
fmt.Printf("%-25s %t\n", "disable-sips", conf.DisableSips())
|
||||
fmt.Printf("%-25s %t\n", "disable-raw", conf.DisableRaw())
|
||||
|
||||
// Format Flags.
|
||||
fmt.Printf("%-25s %t\n", "raw-presets", conf.RawPresets())
|
||||
fmt.Printf("%-25s %t\n", "exif-bruteforce", conf.ExifBruteForce())
|
||||
|
||||
// TensorFlow.
|
||||
fmt.Printf("%-25s %t\n", "detect-nsfw", conf.DetectNSFW())
|
||||
fmt.Printf("%-25s %t\n", "upload-nsfw", conf.UploadNSFW())
|
||||
fmt.Printf("%-25s %s\n", "tensorflow-version", conf.TensorFlowVersion())
|
||||
fmt.Printf("%-25s %s\n", "tensorflow-model-path", conf.TensorFlowModelPath())
|
||||
|
||||
// UI Defaults.
|
||||
fmt.Printf("%-25s %s\n", "default-locale", conf.DefaultLocale())
|
||||
|
||||
// Progressive Web App.
|
||||
fmt.Printf("%-25s %s\n", "app-icon", conf.AppIcon())
|
||||
fmt.Printf("%-25s %s\n", "app-name", conf.AppName())
|
||||
fmt.Printf("%-25s %s\n", "app-mode", conf.AppMode())
|
||||
|
||||
// Site Infos.
|
||||
fmt.Printf("%-25s %s\n", "cdn-url", conf.CdnUrl("/"))
|
||||
fmt.Printf("%-25s %s\n", "site-url", conf.SiteUrl())
|
||||
fmt.Printf("%-25s %s\n", "site-author", conf.SiteAuthor())
|
||||
fmt.Printf("%-25s %s\n", "site-title", conf.SiteTitle())
|
||||
fmt.Printf("%-25s %s\n", "site-caption", conf.SiteCaption())
|
||||
fmt.Printf("%-25s %s\n", "site-description", conf.SiteDescription())
|
||||
fmt.Printf("%-25s %s\n", "site-preview", conf.SitePreview())
|
||||
|
||||
// Legal info.
|
||||
fmt.Printf("%-25s %s\n", "imprint", conf.Imprint())
|
||||
fmt.Printf("%-25s %s\n", "imprint-url", conf.ImprintUrl())
|
||||
|
||||
// URIs.
|
||||
fmt.Printf("%-25s %s\n", "content-uri", conf.ContentUri())
|
||||
fmt.Printf("%-25s %s\n", "static-uri", conf.StaticUri())
|
||||
fmt.Printf("%-25s %s\n", "api-uri", conf.ApiUri())
|
||||
fmt.Printf("%-25s %s\n", "base-uri", conf.BaseUri("/"))
|
||||
|
||||
// Web Server.
|
||||
fmt.Printf("%-25s %s\n", "http-host", conf.HttpHost())
|
||||
fmt.Printf("%-25s %d\n", "http-port", conf.HttpPort())
|
||||
fmt.Printf("%-25s %s\n", "http-mode", conf.HttpMode())
|
||||
|
||||
// Database.
|
||||
fmt.Printf("%-25s %s\n", "database-driver", dbDriver)
|
||||
fmt.Printf("%-25s %s\n", "database-server", conf.DatabaseServer())
|
||||
fmt.Printf("%-25s %s\n", "database-host", conf.DatabaseHost())
|
||||
fmt.Printf("%-25s %s\n", "database-port", conf.DatabasePortString())
|
||||
fmt.Printf("%-25s %s\n", "database-name", conf.DatabaseName())
|
||||
fmt.Printf("%-25s %s\n", "database-user", conf.DatabaseUser())
|
||||
fmt.Printf("%-25s %s\n", "database-password", strings.Repeat("*", utf8.RuneCountInString(conf.DatabasePassword())))
|
||||
fmt.Printf("%-25s %d\n", "database-conns", conf.DatabaseConns())
|
||||
fmt.Printf("%-25s %d\n", "database-conns-idle", conf.DatabaseConnsIdle())
|
||||
|
||||
// External Tools.
|
||||
fmt.Printf("%-25s %s\n", "darktable-bin", conf.DarktableBin())
|
||||
fmt.Printf("%-25s %s\n", "darktable-cache-path", conf.DarktableCachePath())
|
||||
fmt.Printf("%-25s %s\n", "darktable-config-path", conf.DarktableConfigPath())
|
||||
fmt.Printf("%-25s %s\n", "darktable-blacklist", conf.DarktableBlacklist())
|
||||
fmt.Printf("%-25s %s\n", "rawtherapee-bin", conf.RawtherapeeBin())
|
||||
fmt.Printf("%-25s %s\n", "rawtherapee-blacklist", conf.RawtherapeeBlacklist())
|
||||
fmt.Printf("%-25s %s\n", "sips-bin", conf.SipsBin())
|
||||
fmt.Printf("%-25s %s\n", "heifconvert-bin", conf.HeifConvertBin())
|
||||
fmt.Printf("%-25s %s\n", "ffmpeg-bin", conf.FFmpegBin())
|
||||
fmt.Printf("%-25s %s\n", "ffmpeg-encoder", conf.FFmpegEncoder())
|
||||
fmt.Printf("%-25s %d\n", "ffmpeg-bitrate", conf.FFmpegBitrate())
|
||||
fmt.Printf("%-25s %s\n", "exiftool-bin", conf.ExifToolBin())
|
||||
|
||||
// Thumbnails.
|
||||
fmt.Printf("%-25s %s\n", "download-token", conf.DownloadToken())
|
||||
fmt.Printf("%-25s %s\n", "preview-token", conf.PreviewToken())
|
||||
fmt.Printf("%-25s %s\n", "thumb-color", conf.ThumbColor())
|
||||
fmt.Printf("%-25s %s\n", "thumb-filter", conf.ThumbFilter())
|
||||
fmt.Printf("%-25s %d\n", "thumb-size", conf.ThumbSizePrecached())
|
||||
fmt.Printf("%-25s %d\n", "thumb-size-uncached", conf.ThumbSizeUncached())
|
||||
fmt.Printf("%-25s %t\n", "thumb-uncached", conf.ThumbUncached())
|
||||
fmt.Printf("%-25s %s\n", "thumb-path", conf.ThumbPath())
|
||||
fmt.Printf("%-25s %d\n", "jpeg-quality", conf.JpegQuality())
|
||||
fmt.Printf("%-25s %d\n", "jpeg-size", conf.JpegSize())
|
||||
|
||||
// Facial Recognition.
|
||||
fmt.Printf("%-25s %d\n", "face-size", conf.FaceSize())
|
||||
fmt.Printf("%-25s %f\n", "face-score", conf.FaceScore())
|
||||
fmt.Printf("%-25s %d\n", "face-overlap", conf.FaceOverlap())
|
||||
fmt.Printf("%-25s %d\n", "face-cluster-size", conf.FaceClusterSize())
|
||||
fmt.Printf("%-25s %d\n", "face-cluster-score", conf.FaceClusterScore())
|
||||
fmt.Printf("%-25s %d\n", "face-cluster-core", conf.FaceClusterCore())
|
||||
fmt.Printf("%-25s %f\n", "face-cluster-dist", conf.FaceClusterDist())
|
||||
fmt.Printf("%-25s %f\n", "face-match-dist", conf.FaceMatchDist())
|
||||
|
||||
// Daemon Mode.
|
||||
fmt.Printf("%-25s %s\n", "pid-filename", conf.PIDFilename())
|
||||
fmt.Printf("%-25s %s\n", "log-filename", conf.LogFilename())
|
||||
fmt.Println(report.Markdown(rows, cols, !ctx.Bool("no-wrap")))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ func TestConfigCommand(t *testing.T) {
|
|||
}
|
||||
|
||||
// Expected config command output.
|
||||
assert.Contains(t, output, "NAME VALUE")
|
||||
assert.Contains(t, output, "config-file")
|
||||
assert.Contains(t, output, "darktable-cli")
|
||||
assert.Contains(t, output, "originals-path")
|
||||
|
|
|
@ -3,19 +3,33 @@ package commands
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/report"
|
||||
)
|
||||
|
||||
var ShowFormatsCommand = cli.Command{
|
||||
Name: "formats",
|
||||
Usage: "Displays supported media and sidecar file formats",
|
||||
Name: "formats",
|
||||
Usage: "Displays supported media and sidecar file formats",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "compact, c",
|
||||
Usage: "hide format descriptions to make the output more compact",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "no-wrap, n",
|
||||
Usage: "disable text-wrapping so the output can be pasted into Markdown files",
|
||||
},
|
||||
},
|
||||
Action: showFormatsAction,
|
||||
}
|
||||
|
||||
// showFormatsAction lists supported media and sidecar file formats.
|
||||
func showFormatsAction(ctx *cli.Context) error {
|
||||
formats := fs.Extensions.Formats(true).Markdown()
|
||||
fmt.Println(formats)
|
||||
rows, cols := fs.Extensions.Formats(true).Table(!ctx.Bool("compact"), true, true)
|
||||
|
||||
fmt.Println(report.Markdown(rows, cols, !ctx.Bool("no-wrap")))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ type ClientConfig struct {
|
|||
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"`
|
||||
|
@ -227,6 +228,7 @@ func (c *Config) PublicConfig() ClientConfig {
|
|||
Version: c.Version(),
|
||||
Copyright: c.Copyright(),
|
||||
Debug: c.Debug(),
|
||||
Trace: c.Trace(),
|
||||
Test: c.Test(),
|
||||
Demo: c.Demo(),
|
||||
Sponsor: c.Sponsor(),
|
||||
|
@ -299,6 +301,7 @@ func (c *Config) GuestConfig() ClientConfig {
|
|||
Version: c.Version(),
|
||||
Copyright: c.Copyright(),
|
||||
Debug: c.Debug(),
|
||||
Trace: c.Trace(),
|
||||
Test: c.Test(),
|
||||
Demo: c.Demo(),
|
||||
Sponsor: c.Sponsor(),
|
||||
|
@ -365,6 +368,7 @@ func (c *Config) UserConfig() ClientConfig {
|
|||
Version: c.Version(),
|
||||
Copyright: c.Copyright(),
|
||||
Debug: c.Debug(),
|
||||
Trace: c.Trace(),
|
||||
Test: c.Test(),
|
||||
Demo: c.Demo(),
|
||||
Sponsor: c.Sponsor(),
|
||||
|
|
|
@ -445,11 +445,19 @@ func (c *Config) ImprintUrl() string {
|
|||
return c.options.ImprintUrl
|
||||
}
|
||||
|
||||
// Debug checks if debug mode is enabled.
|
||||
// Debug checks if debug mode is enabled, shows non-essential log messages.
|
||||
func (c *Config) Debug() bool {
|
||||
if c.Trace() {
|
||||
return true
|
||||
}
|
||||
return c.options.Debug
|
||||
}
|
||||
|
||||
// Trace checks if trace mode is enabled, shows all log messages.
|
||||
func (c *Config) Trace() bool {
|
||||
return c.options.Trace
|
||||
}
|
||||
|
||||
// Test checks if test mode is enabled.
|
||||
func (c *Config) Test() bool {
|
||||
return c.options.Test
|
||||
|
@ -467,7 +475,9 @@ func (c *Config) Sponsor() bool {
|
|||
|
||||
// Public checks if app runs in public mode and requires no authentication.
|
||||
func (c *Config) Public() bool {
|
||||
if c.Demo() {
|
||||
if c.Auth() {
|
||||
return false
|
||||
} else if c.Demo() {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -506,12 +516,19 @@ func (c *Config) AdminPassword() string {
|
|||
return c.options.AdminPassword
|
||||
}
|
||||
|
||||
// Auth checks if authentication is always required.
|
||||
func (c *Config) Auth() bool {
|
||||
return c.options.Auth
|
||||
}
|
||||
|
||||
// LogLevel returns the Logrus log level.
|
||||
func (c *Config) LogLevel() logrus.Level {
|
||||
// Normalize string.
|
||||
c.options.LogLevel = strings.ToLower(strings.TrimSpace(c.options.LogLevel))
|
||||
|
||||
if c.Debug() && c.options.LogLevel != logrus.TraceLevel.String() {
|
||||
if c.Trace() {
|
||||
c.options.LogLevel = logrus.TraceLevel.String()
|
||||
} else if c.Debug() && c.options.LogLevel != logrus.TraceLevel.String() {
|
||||
c.options.LogLevel = logrus.DebugLevel.String()
|
||||
}
|
||||
|
||||
|
@ -522,6 +539,11 @@ func (c *Config) LogLevel() logrus.Level {
|
|||
}
|
||||
}
|
||||
|
||||
// SetLogLevel sets the Logrus log level.
|
||||
func (c *Config) SetLogLevel(level logrus.Level) {
|
||||
log.SetLevel(level)
|
||||
}
|
||||
|
||||
// Shutdown services and workers.
|
||||
func (c *Config) Shutdown() {
|
||||
mutex.People.Cancel()
|
||||
|
|
|
@ -22,14 +22,16 @@ type Options struct {
|
|||
Version string `json:"-"`
|
||||
Copyright string `json:"-"`
|
||||
PartnerID string `yaml:"-" json:"-" flag:"partner-id"`
|
||||
AdminPassword string `yaml:"AdminPassword" json:"-" flag:"admin-password"`
|
||||
LogLevel string `yaml:"LogLevel" json:"-" flag:"log-level"`
|
||||
Debug bool `yaml:"Debug" json:"Debug" flag:"debug"`
|
||||
Trace bool `yaml:"Trace" json:"Trace" flag:"Trace"`
|
||||
AdminPassword string `yaml:"AdminPassword" json:"-" flag:"admin-password"`
|
||||
Auth bool `yaml:"Auth" json:"-" flag:"auth"`
|
||||
Public bool `yaml:"Public" json:"-" flag:"public"`
|
||||
Test bool `yaml:"-" json:"Test,omitempty" flag:"test"`
|
||||
Unsafe bool `yaml:"-" json:"-" flag:"unsafe"`
|
||||
Demo bool `yaml:"Demo" json:"-" flag:"demo"`
|
||||
Sponsor bool `yaml:"-" json:"-" flag:"sponsor"`
|
||||
Public bool `yaml:"Public" json:"-" flag:"public"`
|
||||
ReadOnly bool `yaml:"ReadOnly" json:"ReadOnly" flag:"read-only"`
|
||||
Experimental bool `yaml:"Experimental" json:"Experimental" flag:"experimental"`
|
||||
ConfigPath string `yaml:"ConfigPath" json:"-" flag:"config-path"`
|
||||
|
|
186
internal/config/table.go
Normal file
186
internal/config/table.go
Normal file
|
@ -0,0 +1,186 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
// Table returns global config values as a table for reporting.
|
||||
func (c *Config) Table() (rows [][]string, cols []string) {
|
||||
cols = []string{"Value", "Name"}
|
||||
|
||||
rows = [][]string{
|
||||
{"log-level", c.LogLevel().String()},
|
||||
{"debug", fmt.Sprintf("%t", c.Debug())},
|
||||
{"trace", fmt.Sprintf("%t", c.Trace())},
|
||||
{"admin-password", strings.Repeat("*", utf8.RuneCountInString(c.AdminPassword()))},
|
||||
{"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-file", c.ConfigFile()},
|
||||
{"config-path", c.ConfigPath()},
|
||||
{"settings-file", c.SettingsFile()},
|
||||
|
||||
// Originals.
|
||||
{"originals-path", c.OriginalsPath()},
|
||||
{"originals-limit", fmt.Sprintf("%d", c.OriginalsLimit())},
|
||||
{"resolution-limit", fmt.Sprintf("%d", c.ResolutionLimit())},
|
||||
|
||||
// Other paths.
|
||||
{"storage-path", c.StoragePath()},
|
||||
{"sidecar-path", c.SidecarPath()},
|
||||
{"cache-path", c.CachePath()},
|
||||
{"albums-path", c.AlbumsPath()},
|
||||
{"backup-path", c.BackupPath()},
|
||||
{"import-path", c.ImportPath()},
|
||||
{"assets-path", c.AssetsPath()},
|
||||
{"static-path", c.StaticPath()},
|
||||
{"build-path", c.BuildPath()},
|
||||
{"img-path", c.ImgPath()},
|
||||
{"templates-path", c.TemplatesPath()},
|
||||
{"temp-path", c.TempPath()},
|
||||
|
||||
// Workers.
|
||||
{"workers", fmt.Sprintf("%d", c.Workers())},
|
||||
{"wakeup-interval", c.WakeupInterval().String()},
|
||||
{"auto-index", fmt.Sprintf("%d", c.AutoIndex()/time.Second)},
|
||||
{"auto-import", fmt.Sprintf("%d", c.AutoImport()/time.Second)},
|
||||
|
||||
// Feature Flags.
|
||||
{"disable-backups", fmt.Sprintf("%t", c.DisableBackups())},
|
||||
{"disable-settings", fmt.Sprintf("%t", c.DisableSettings())},
|
||||
{"disable-places", fmt.Sprintf("%t", c.DisablePlaces())},
|
||||
{"disable-tensorflow", fmt.Sprintf("%t", c.DisableTensorFlow())},
|
||||
{"disable-faces", fmt.Sprintf("%t", c.DisableFaces())},
|
||||
{"disable-classification", fmt.Sprintf("%t", c.DisableClassification())},
|
||||
{"disable-ffmpeg", fmt.Sprintf("%t", c.DisableFFmpeg())},
|
||||
{"disable-exiftool", fmt.Sprintf("%t", c.DisableExifTool())},
|
||||
{"disable-heifconvert", fmt.Sprintf("%t", c.DisableHeifConvert())},
|
||||
{"disable-darktable", fmt.Sprintf("%t", c.DisableDarktable())},
|
||||
{"disable-rawtherapee", fmt.Sprintf("%t", c.DisableRawtherapee())},
|
||||
{"disable-sips", fmt.Sprintf("%t", c.DisableSips())},
|
||||
{"disable-raw", fmt.Sprintf("%t", c.DisableRaw())},
|
||||
|
||||
// Format Flags.
|
||||
{"raw-presets", fmt.Sprintf("%t", c.RawPresets())},
|
||||
{"exif-bruteforce", fmt.Sprintf("%t", c.ExifBruteForce())},
|
||||
|
||||
// TensorFlow.
|
||||
{"detect-nsfw", fmt.Sprintf("%t", c.DetectNSFW())},
|
||||
{"upload-nsfw", fmt.Sprintf("%t", c.UploadNSFW())},
|
||||
{"tensorflow-version", c.TensorFlowVersion()},
|
||||
{"tensorflow-model-path", c.TensorFlowModelPath()},
|
||||
|
||||
// UI Defaults.
|
||||
{"default-locale", c.DefaultLocale()},
|
||||
|
||||
// Progressive Web App.
|
||||
{"app-icon", c.AppIcon()},
|
||||
{"app-name", c.AppName()},
|
||||
{"app-mode", c.AppMode()},
|
||||
|
||||
// Site Infos.
|
||||
{"cdn-url", c.CdnUrl("/")},
|
||||
{"site-url", c.SiteUrl()},
|
||||
{"site-author", c.SiteAuthor()},
|
||||
{"site-title", c.SiteTitle()},
|
||||
{"site-caption", c.SiteCaption()},
|
||||
{"site-description", c.SiteDescription()},
|
||||
{"site-preview", c.SitePreview()},
|
||||
|
||||
// Legal info.
|
||||
{"imprint", c.Imprint()},
|
||||
{"imprint-url", c.ImprintUrl()},
|
||||
|
||||
// URIs.
|
||||
{"content-uri", c.ContentUri()},
|
||||
{"static-uri", c.StaticUri()},
|
||||
{"api-uri", c.ApiUri()},
|
||||
{"base-uri", c.BaseUri("/")},
|
||||
|
||||
// Web Server.
|
||||
{"http-host", c.HttpHost()},
|
||||
{"http-port", fmt.Sprintf("%d", c.HttpPort())},
|
||||
{"http-mode", c.HttpMode()},
|
||||
|
||||
// Database.
|
||||
{"database-driver", c.DatabaseDriver()},
|
||||
{"database-server", c.DatabaseServer()},
|
||||
{"database-host", c.DatabaseHost()},
|
||||
{"database-port", c.DatabasePortString()},
|
||||
{"database-name", c.DatabaseName()},
|
||||
{"database-user", c.DatabaseUser()},
|
||||
{"database-password", strings.Repeat("*", utf8.RuneCountInString(c.DatabasePassword()))},
|
||||
{"database-conns", fmt.Sprintf("%d", c.DatabaseConns())},
|
||||
{"database-conns-idle", fmt.Sprintf("%d", c.DatabaseConnsIdle())},
|
||||
|
||||
// External Tools.
|
||||
{"darktable-bin", c.DarktableBin()},
|
||||
{"darktable-cache-path", c.DarktableCachePath()},
|
||||
{"darktable-config-path", c.DarktableConfigPath()},
|
||||
{"darktable-blacklist", c.DarktableBlacklist()},
|
||||
{"rawtherapee-bin", c.RawtherapeeBin()},
|
||||
{"rawtherapee-blacklist", c.RawtherapeeBlacklist()},
|
||||
{"sips-bin", c.SipsBin()},
|
||||
{"heifconvert-bin", c.HeifConvertBin()},
|
||||
{"ffmpeg-bin", c.FFmpegBin()},
|
||||
{"ffmpeg-encoder", c.FFmpegEncoder().String()},
|
||||
{"ffmpeg-bitrate", fmt.Sprintf("%d", c.FFmpegBitrate())},
|
||||
{"exiftool-bin", c.ExifToolBin()},
|
||||
|
||||
// Thumbnails.
|
||||
{"download-token", c.DownloadToken()},
|
||||
{"preview-token", c.PreviewToken()},
|
||||
{"thumb-color", c.ThumbColor()},
|
||||
{"thumb-filter", string(c.ThumbFilter())},
|
||||
{"thumb-size", fmt.Sprintf("%d", c.ThumbSizePrecached())},
|
||||
{"thumb-size-uncached", fmt.Sprintf("%d", c.ThumbSizeUncached())},
|
||||
{"thumb-uncached", fmt.Sprintf("%t", c.ThumbUncached())},
|
||||
{"thumb-path", c.ThumbPath()},
|
||||
{"jpeg-quality", fmt.Sprintf("%d", c.JpegQuality())},
|
||||
{"jpeg-size", fmt.Sprintf("%d", c.JpegSize())},
|
||||
|
||||
// Facial Recognition.
|
||||
{"face-size", fmt.Sprintf("%d", c.FaceSize())},
|
||||
{"face-score", fmt.Sprintf("%f", c.FaceScore())},
|
||||
{"face-overlap", fmt.Sprintf("%d", c.FaceOverlap())},
|
||||
{"face-cluster-size", fmt.Sprintf("%d", c.FaceClusterSize())},
|
||||
{"face-cluster-score", fmt.Sprintf("%d", c.FaceClusterScore())},
|
||||
{"face-cluster-core", fmt.Sprintf("%d", c.FaceClusterCore())},
|
||||
{"face-cluster-dist", fmt.Sprintf("%f", c.FaceClusterDist())},
|
||||
{"face-match-dist", fmt.Sprintf("%f", c.FaceMatchDist())},
|
||||
|
||||
// Daemon Mode.
|
||||
{"pid-filename", c.PIDFilename()},
|
||||
{"log-filename", c.LogFilename()},
|
||||
}
|
||||
|
||||
return rows, cols
|
||||
}
|
||||
|
||||
// MarkdownTable returns global config values as a markdown formatted table.
|
||||
func (c *Config) MarkdownTable(autoWrap bool) string {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
rows, cols := c.Table()
|
||||
|
||||
table := tablewriter.NewWriter(buf)
|
||||
|
||||
table.SetAutoWrapText(autoWrap)
|
||||
table.SetAutoFormatHeaders(false)
|
||||
table.SetHeader(cols)
|
||||
table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false})
|
||||
table.SetCenterSeparator("|")
|
||||
table.AppendBulk(rows)
|
||||
table.Render()
|
||||
|
||||
return buf.String()
|
||||
}
|
|
@ -78,9 +78,11 @@ func NewTestOptions(pkg string) *Options {
|
|||
Name: "PhotoPrism",
|
||||
Version: "0.0.0",
|
||||
Copyright: "(c) 2018-2022 Michael Mayer",
|
||||
Public: true,
|
||||
Auth: false,
|
||||
Test: true,
|
||||
Debug: true,
|
||||
Public: true,
|
||||
Trace: false,
|
||||
Experimental: true,
|
||||
ReadOnly: false,
|
||||
DetectNSFW: true,
|
||||
|
|
27
internal/event/event.go
Normal file
27
internal/event/event.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
|
||||
Package event provides a publish-subscribe event hub and a global logger.
|
||||
|
||||
Copyright (c) 2018 - 2022 Michael Mayer <hello@photoprism.app>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under Version 3 of the GNU Affero General Public License (the "AGPL"):
|
||||
<https://docs.photoprism.app/license/agpl>
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
The AGPL is supplemented by our Trademark and Brand Guidelines,
|
||||
which describe how our Brand Assets may be used:
|
||||
<https://photoprism.app/trademark>
|
||||
|
||||
Feel free to send an e-mail to hello@photoprism.app if you have questions,
|
||||
want to support our work, or just want to say hello.
|
||||
|
||||
Additional information can be found in our Developer Guide:
|
||||
<https://docs.photoprism.app/developer-guide/>
|
||||
|
||||
*/
|
||||
package event
|
|
@ -72,8 +72,10 @@ var Extensions = FileExtensions{
|
|||
".avi": FormatAvi,
|
||||
".av1": FormatAV1,
|
||||
".avc": FormatAVC,
|
||||
".mpg": FormatMpg,
|
||||
".mpeg": FormatMpg,
|
||||
".mpg": FormatMPEG,
|
||||
".mpeg": FormatMPEG,
|
||||
".mjpg": FormatMJPEG,
|
||||
".mjpeg": FormatMJPEG,
|
||||
".mp2": FormatMp2,
|
||||
".mpv": FormatMp2,
|
||||
".mp": FormatMp4,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
|
@ -35,6 +34,8 @@ const (
|
|||
FormatHEVC Format = "hevc" // H.265, High Efficiency Video Coding (HEVC)
|
||||
FormatAVC Format = "avc" // H.264, Advanced Video Coding (AVC), MPEG-4 Part 10, used internally
|
||||
FormatAV1 Format = "av1" // Alliance for Open Media Video
|
||||
FormatMPEG Format = "mpg" // Moving Picture Experts Group (MPEG)
|
||||
FormatMJPEG Format = "mjpg" // Motion JPEG (M-JPEG)
|
||||
FormatMov Format = "mov" // QuickTime File Format, can contain AVC, HEVC,...
|
||||
FormatMp2 Format = "mp2" // MPEG-2, H.222/H.262
|
||||
FormatMp4 Format = "mp4" // MPEG-4 Container based on QuickTime, can contain AVC, HEVC,...
|
||||
|
@ -43,7 +44,6 @@ const (
|
|||
Format3g2 Format = "3g2" // Similar to 3GP, consumes less space & bandwidth
|
||||
FormatFlv Format = "flv" // Flash Video
|
||||
FormatMkv Format = "mkv" // Matroska Multimedia Container, free and open
|
||||
FormatMpg Format = "mpg" // Moving Picture Experts Group (MPEG)
|
||||
FormatMts Format = "mts" // AVCHD (Advanced Video Coding High Definition)
|
||||
FormatOgv Format = "ogv" // Ogg container format maintained by the Xiph.Org, free and open
|
||||
FormatWMV Format = "wmv" // Windows Media Video
|
||||
|
@ -60,55 +60,70 @@ const (
|
|||
|
||||
// FormatDesc contains human-readable descriptions for supported file formats
|
||||
var FormatDesc = map[Format]string{
|
||||
FormatJpeg: "JPEG (Joint Photographic Experts Group)",
|
||||
FormatJpeg: "Joint Photographic Experts Group (JPEG)",
|
||||
FormatPng: "Portable Network Graphics",
|
||||
FormatGif: "Graphics Interchange Format",
|
||||
FormatTiff: "Tag Image File Format",
|
||||
FormatBitmap: "Bitmap",
|
||||
FormatRaw: "Unprocessed Image Data",
|
||||
FormatMpo: "Stereoscopic 3D Format based on JPEG",
|
||||
FormatHEIF: "High Efficiency Image File Format",
|
||||
FormatRaw: "Unprocessed Sensor Data",
|
||||
FormatMpo: "Stereoscopic (3D JPEG)",
|
||||
FormatHEIF: "High Efficiency Image File Format (HEIF)",
|
||||
FormatWebP: "Google WebP",
|
||||
FormatWebM: "Google WebM",
|
||||
FormatHEVC: "H.265, High Efficiency Video Coding",
|
||||
FormatAVC: "H.264, Advanced Video Coding, MPEG-4 Part 10",
|
||||
FormatAV1: "Alliance for Open Media",
|
||||
FormatMov: "QuickTime File Format",
|
||||
FormatMp2: "MPEG-2, H.222, H.262",
|
||||
FormatMp4: "MPEG-4 Part 14 Multimedia Container",
|
||||
FormatHEVC: "High Efficiency Video Coding (HEVC, HVC1, H.265)",
|
||||
FormatAVC: "Advanced Video Coding (AVC, AVC1, H.264, MPEG-4 Part 10)",
|
||||
FormatAV1: "AOMedia Video 1 (AV1, AV01)",
|
||||
FormatMov: "Apple QuickTime (QT)",
|
||||
FormatMp2: "MPEG 2 (H.262, H.222)",
|
||||
FormatMp4: "Multimedia Container (MPEG-4 Part 14)",
|
||||
FormatAvi: "Microsoft Audio Video Interleave",
|
||||
Format3gp: "MPEG-4 Part 12, Mobile Multimedia Container",
|
||||
Format3g2: "Multimedia Container for 3G CDMA2000, based on 3GP",
|
||||
FormatWMV: "Microsoft Windows Media",
|
||||
Format3gp: "Mobile Multimedia Container (MPEG-4 Part 12)",
|
||||
Format3g2: "Mobile Multimedia Container for CDMA2000 (based on 3GP)",
|
||||
FormatFlv: "Flash Video",
|
||||
FormatMkv: "Matroska Multimedia Container",
|
||||
FormatMpg: "MPEG (Moving Picture Experts Group)",
|
||||
FormatMts: "AVCHD (Advanced Video Coding High Definition)",
|
||||
FormatMkv: "Matroska Multimedia Container (MKV, MCF, EBML)",
|
||||
FormatMPEG: "Moving Picture Experts Group (MPEG)",
|
||||
FormatMJPEG: "Motion JPEG (M-JPEG)",
|
||||
FormatMts: "Advanced Video Coding High Definition (AVCHD)",
|
||||
FormatOgv: "Ogg Media by Xiph.Org",
|
||||
FormatWMV: "Windows Media",
|
||||
FormatXMP: "Adobe Extensible Metadata Platform",
|
||||
FormatAAE: "Apple Image Edits",
|
||||
FormatXML: "Extensible Markup Language",
|
||||
FormatJson: "Serialized JSON Data (Exiftool, Google Photos)",
|
||||
FormatYaml: "Serialized YAML Data (Metadata, Config Values)",
|
||||
FormatYaml: "Serialized YAML Data (Config, Metadata)",
|
||||
FormatToml: "Serialized TOML Data (Tom's Obvious, Minimal Language)",
|
||||
FormatText: "Plain Text",
|
||||
FormatMarkdown: "Markdown Formatted Text",
|
||||
FormatOther: "Other",
|
||||
}
|
||||
|
||||
// Markdown returns a file format table in markdown text format.
|
||||
func (m FileFormats) Markdown() string {
|
||||
// Table returns a file format documentation table.
|
||||
func (m FileFormats) Table(withDesc, withType, withExt bool) (rows [][]string, cols []string) {
|
||||
cols = make([]string, 0, 4)
|
||||
cols = append(cols, "Format")
|
||||
|
||||
results := make([][]string, 0, len(m))
|
||||
t := 0
|
||||
|
||||
max := func(x, y int) int {
|
||||
if x < y {
|
||||
return y
|
||||
if withDesc {
|
||||
cols = append(cols, "Description")
|
||||
}
|
||||
|
||||
if withType {
|
||||
if withDesc {
|
||||
t = 2
|
||||
} else {
|
||||
t = 1
|
||||
}
|
||||
|
||||
return x
|
||||
cols = append(cols, "Type")
|
||||
}
|
||||
|
||||
if withExt {
|
||||
cols = append(cols, "Extensions")
|
||||
}
|
||||
|
||||
rows = make([][]string, 0, len(m))
|
||||
|
||||
ucFirst := func(str string) string {
|
||||
for i, v := range str {
|
||||
return string(unicode.ToUpper(v)) + str[i+1:]
|
||||
|
@ -116,40 +131,36 @@ func (m FileFormats) Markdown() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
l0, l1, l2, l3 := 12, 12, 12, 12
|
||||
|
||||
for f, ext := range m {
|
||||
sort.Slice(ext, func(i, j int) bool {
|
||||
return ext[i] < ext[j]
|
||||
})
|
||||
|
||||
v := make([]string, 4)
|
||||
v[0] = strings.ToUpper(f.String())
|
||||
v[1] = FormatDesc[f]
|
||||
v[2] = ucFirst(string(MediaTypes[f]))
|
||||
v[3] = strings.Join(ext, ", ")
|
||||
l0, l1, l2, l3 = max(l0, len(v[0])), max(l1, len(v[1])), max(l2, len(v[2])), max(l3, len(v[3]))
|
||||
results = append(results, v)
|
||||
v := make([]string, 0, 4)
|
||||
v = append(v, strings.ToUpper(f.String()))
|
||||
|
||||
if withDesc {
|
||||
v = append(v, FormatDesc[f])
|
||||
}
|
||||
|
||||
if withType {
|
||||
v = append(v, ucFirst(string(MediaTypes[f])))
|
||||
}
|
||||
|
||||
if withExt {
|
||||
v = append(v, strings.Join(ext, ", "))
|
||||
}
|
||||
|
||||
rows = append(rows, v)
|
||||
}
|
||||
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
if results[i][2] == results[j][2] {
|
||||
return results[i][0] < results[j][0]
|
||||
sort.Slice(rows, func(i, j int) bool {
|
||||
if t > 0 && rows[i][t] == rows[j][t] {
|
||||
return rows[i][0] < rows[j][0]
|
||||
} else {
|
||||
return results[i][2] < results[j][2]
|
||||
return rows[i][t] < rows[j][t]
|
||||
}
|
||||
})
|
||||
|
||||
rows := make([]string, len(results)+2)
|
||||
|
||||
cols := fmt.Sprintf("| %%-%ds | %%-%ds | %%-%ds | %%-%ds |\n", l0, l1, l2, l3)
|
||||
|
||||
rows = append(rows, fmt.Sprintf(cols, "Format", "Description", "Type", "Extensions"))
|
||||
rows = append(rows, fmt.Sprintf("|:%s-|:%s-|:%s-|:%s-|\n", strings.Repeat("-", l0), strings.Repeat("-", l1), strings.Repeat("-", l2), strings.Repeat("-", l3)))
|
||||
|
||||
for _, r := range results {
|
||||
rows = append(rows, fmt.Sprintf(cols, r[0], r[1], r[2], r[3]))
|
||||
}
|
||||
|
||||
return strings.Join(rows, "")
|
||||
return rows, cols
|
||||
}
|
||||
|
|
|
@ -7,11 +7,20 @@ import (
|
|||
)
|
||||
|
||||
func TestFileFormats_Markdown(t *testing.T) {
|
||||
t.Run("Render", func(t *testing.T) {
|
||||
t.Run("All", func(t *testing.T) {
|
||||
f := Extensions.Formats(true)
|
||||
result := f.Markdown()
|
||||
|
||||
// fmt.Print(result)
|
||||
assert.NotEmpty(t, result)
|
||||
rows, cols := f.Table(true, true, true)
|
||||
assert.NotEmpty(t, rows)
|
||||
assert.NotEmpty(t, cols)
|
||||
assert.Len(t, cols, 4)
|
||||
assert.GreaterOrEqual(t, len(rows), 30)
|
||||
})
|
||||
t.Run("Compact", func(t *testing.T) {
|
||||
f := Extensions.Formats(true)
|
||||
rows, cols := f.Table(false, false, false)
|
||||
assert.NotEmpty(t, rows)
|
||||
assert.NotEmpty(t, cols)
|
||||
assert.Len(t, cols, 1)
|
||||
assert.GreaterOrEqual(t, len(rows), 30)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -28,7 +28,8 @@ var MediaTypes = map[Format]MediaType{
|
|||
FormatAvi: MediaVideo,
|
||||
FormatAVC: MediaVideo,
|
||||
FormatAV1: MediaVideo,
|
||||
FormatMpg: MediaVideo,
|
||||
FormatMPEG: MediaVideo,
|
||||
FormatMJPEG: MediaVideo,
|
||||
FormatMp2: MediaVideo,
|
||||
FormatMp4: MediaVideo,
|
||||
FormatMkv: MediaVideo,
|
||||
|
|
24
pkg/report/markdown.go
Normal file
24
pkg/report/markdown.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package report
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
// Markdown returns markdown formatted table.
|
||||
func Markdown(rows [][]string, cols []string, autoWrap bool) string {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
table := tablewriter.NewWriter(buf)
|
||||
|
||||
table.SetAutoWrapText(autoWrap)
|
||||
table.SetAutoFormatHeaders(false)
|
||||
table.SetHeader(cols)
|
||||
table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false})
|
||||
table.SetCenterSeparator("|")
|
||||
table.AppendBulk(rows)
|
||||
table.Render()
|
||||
|
||||
return buf.String()
|
||||
}
|
27
pkg/report/report.go
Normal file
27
pkg/report/report.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
|
||||
Package report provides rendering of report results, for example as Markdown.
|
||||
|
||||
Copyright (c) 2018 - 2022 Michael Mayer <hello@photoprism.app>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under Version 3 of the GNU Affero General Public License (the "AGPL"):
|
||||
<https://docs.photoprism.app/license/agpl>
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
The AGPL is supplemented by our Trademark and Brand Guidelines,
|
||||
which describe how our Brand Assets may be used:
|
||||
<https://photoprism.app/trademark>
|
||||
|
||||
Feel free to send an e-mail to hello@photoprism.app if you have questions,
|
||||
want to support our work, or just want to say hello.
|
||||
|
||||
Additional information can be found in our Developer Guide:
|
||||
<https://docs.photoprism.app/developer-guide/>
|
||||
|
||||
*/
|
||||
package report
|
Loading…
Reference in a new issue