Sharing: Generate share preview images #18
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
parent
1d2a3aaaea
commit
4aa7b6cc97
BIN
assets/static/img/preview.jpg
Normal file
BIN
assets/static/img/preview.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
BIN
assets/static/img/preview.png
Normal file
BIN
assets/static/img/preview.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 82 KiB |
|
@ -7,15 +7,15 @@
|
|||
|
||||
<title>{{ .config.SiteTitle }}</title>
|
||||
|
||||
<meta property="og:title" content="{{ .config.SiteTitle }}: {{ .config.SiteCaption }}"/>
|
||||
<meta property="og:image" content="{{ .config.SiteUrl }}api/v1/preview"/>
|
||||
<meta property="og:url" content="{{ .config.SiteUrl }}"/>
|
||||
<meta property="og:title" content="{{ .config.SiteTitle }}: {{ .config.SiteCaption }}"/>
|
||||
<meta property="og:image" content="{{ .config.SitePreview }}"/>
|
||||
<meta property="og:description" content="{{ .config.SiteDescription }}"/>
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image"/>
|
||||
<meta name="twitter:title" content="{{ .config.SiteTitle }}: {{ .config.SiteCaption }}"/>
|
||||
<meta name="twitter:image" content="{{ .config.SitePreview }}"/>
|
||||
<meta name="twitter:description" content="{{ .config.SiteDescription }}"/>
|
||||
<meta name="twitter:image" content="{{ .config.SiteUrl }}api/v1/preview"/>
|
||||
|
||||
<meta name="author" content="{{ .config.SiteAuthor }}">
|
||||
<meta name="description" content="{{ .config.SiteDescription }}"/>
|
||||
|
|
|
@ -7,15 +7,17 @@
|
|||
|
||||
<title>{{ .config.SiteTitle }}</title>
|
||||
|
||||
<meta property="og:title" content="{{ .config.SiteTitle }}: {{ .config.SiteCaption }}"/>
|
||||
<meta property="og:image" content="{{ .config.SiteUrl }}api/v1/preview"/>
|
||||
{{if .previewUrl}}
|
||||
<meta property="og:url" content="{{ .config.SiteUrl }}"/>
|
||||
<meta property="og:title" content="{{ .config.SiteTitle }}: {{ .config.SiteCaption }}"/>
|
||||
<meta property="og:image" content="{{ .previewUrl }}"/>
|
||||
<meta property="og:description" content="{{ .config.SiteDescription }}"/>
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image"/>
|
||||
<meta name="twitter:title" content="{{ .config.SiteTitle }}: {{ .config.SiteCaption }}"/>
|
||||
<meta name="twitter:image" content="{{ .previewUrl }}"/>
|
||||
<meta name="twitter:description" content="{{ .config.SiteDescription }}"/>
|
||||
<meta name="twitter:image" content="{{ .config.SiteUrl }}api/v1/preview"/>
|
||||
{{end}}
|
||||
|
||||
<meta name="author" content="{{ .config.SiteAuthor }}">
|
||||
<meta name="description" content="{{ .config.SiteDescription }}"/>
|
||||
|
|
|
@ -7,16 +7,16 @@
|
|||
|
||||
<title>{{ .config.SiteTitle }}</title>
|
||||
|
||||
<meta property="og:title" content="{{ .config.SiteTitle }}: {{ .config.SiteCaption }}"/>
|
||||
<meta property="og:image" content="{{ .config.SiteUrl }}api/v1/preview"/>
|
||||
<meta property="og:url" content="{{ .config.SiteUrl }}"/>
|
||||
<meta property="og:title" content="{{ .config.SiteTitle }}: {{ .config.SiteCaption }}"/>
|
||||
<meta property="og:image" content="{{ .config.SitePreview }}"/>
|
||||
<meta property="og:description" content="{{ .config.SiteDescription }}"/>
|
||||
|
||||
<meta name="twitter:site" content="@browseyourlife"/>
|
||||
<meta name="twitter:card" content="summary_large_image"/>
|
||||
<meta name="twitter:title" content="{{ .config.SiteTitle }}: {{ .config.SiteCaption }}"/>
|
||||
<meta name="twitter:image" content="{{ .config.SitePreview }}"/>
|
||||
<meta name="twitter:description" content="{{ .config.SiteDescription }}"/>
|
||||
<meta name="twitter:image" content="{{ .config.SiteUrl }}api/v1/preview"/>
|
||||
<meta name="twitter:site" content="@browseyourlife"/>
|
||||
|
||||
<meta name="author" content="{{ .config.SiteAuthor }}">
|
||||
<meta name="description" content="{{ .config.SiteDescription }}"/>
|
||||
|
@ -39,7 +39,8 @@
|
|||
<![endif]-->
|
||||
|
||||
<div id="photoprism" class="container">
|
||||
<div class="loading rainbow">
|
||||
<div class="logo-large rainbow">
|
||||
<svg id="b6f816b8-2ec9-4341-9df4-026397ed8ecb" data-name="Logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 283.46 283.46"><defs><style>.ba518a38-ee1f-413e-9d3c-314e98d01903{fill:none;stroke:#1d1d1b;stroke-miterlimit:10;stroke-width:4px;}</style></defs><path id="e47e55cb-4800-4b2c-9d8d-6a55eeeef919" data-name="Logo Pfad" class="ba518a38-ee1f-413e-9d3c-314e98d01903" d="M263.24,206.16l-5.8,32.09m-236.61-31,19.09,18.91M167.34,42.91,40,225.58a.13.13,0,0,0,.1.2l217.16,12.45a.12.12,0,0,0,.12-.17L167.55,42.93A.12.12,0,0,0,167.34,42.91ZM141.1,25.24,20.25,207a.16.16,0,0,0,.14.25l242.1-1.05a.17.17,0,0,0,.14-.26L141.37,25.24A.16.16,0,0,0,141.1,25.24Zm.14-.21,26.22,17.7"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,112 +0,0 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
// GET /api/v1/preview
|
||||
func GetPreview(router *gin.RouterGroup) {
|
||||
router.GET("/preview", func(c *gin.Context) {
|
||||
// TODO: proof of concept - code needs refactoring!
|
||||
t := time.Now().Format("20060102")
|
||||
conf := service.Config()
|
||||
thumbPath := path.Join(conf.ThumbPath(), "preview", t[0:4], t[4:6])
|
||||
|
||||
if err := os.MkdirAll(thumbPath, os.ModePerm); err != nil {
|
||||
log.Error(err)
|
||||
c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg)
|
||||
return
|
||||
}
|
||||
|
||||
previewFilename := fmt.Sprintf("%s/%s.jpg", thumbPath, t[6:8])
|
||||
|
||||
if fs.FileExists(previewFilename) {
|
||||
c.File(previewFilename)
|
||||
return
|
||||
}
|
||||
|
||||
var f form.PhotoSearch
|
||||
|
||||
f.Public = true
|
||||
f.Safe = true
|
||||
f.Count = 12
|
||||
f.Order = "relevance"
|
||||
|
||||
p, _, err := query.PhotoSearch(f)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg)
|
||||
return
|
||||
}
|
||||
|
||||
width := 908
|
||||
height := 680
|
||||
x := 0
|
||||
y := 0
|
||||
|
||||
preview := imaging.New(width, height, color.NRGBA{255, 255, 255, 255})
|
||||
thumbType, _ := thumb.Types["tile_224"]
|
||||
|
||||
for _, f := range p {
|
||||
fileName := photoprism.FileName(f.FileRoot, f.FileName)
|
||||
|
||||
if !fs.FileExists(fileName) {
|
||||
log.Errorf("preview: file %s is missing", txt.Quote(f.FileName))
|
||||
c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg)
|
||||
return
|
||||
}
|
||||
|
||||
thumbnail, err := thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg)
|
||||
}
|
||||
|
||||
src, err := imaging.Open(thumbnail)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg)
|
||||
return
|
||||
}
|
||||
|
||||
preview = imaging.Paste(preview, src, image.Pt(x, y))
|
||||
|
||||
x += 228
|
||||
|
||||
if x > width {
|
||||
x = 0
|
||||
y += 228
|
||||
}
|
||||
}
|
||||
|
||||
// Save the resulting image as JPEG.
|
||||
err = imaging.Save(preview, previewFilename)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg)
|
||||
return
|
||||
}
|
||||
|
||||
c.File(previewFilename)
|
||||
})
|
||||
}
|
|
@ -1,38 +1,51 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
func shareHandler(c *gin.Context, conf *config.Config) {
|
||||
token := c.Param("token")
|
||||
links := entity.FindLinks(token, "")
|
||||
// GET /s/:token/...
|
||||
func Shares(router *gin.RouterGroup) {
|
||||
router.GET("/:token", func(c *gin.Context) {
|
||||
conf := service.Config()
|
||||
|
||||
shareToken := c.Param("token")
|
||||
|
||||
links := entity.FindValidLinks(shareToken, "")
|
||||
|
||||
if len(links) == 0 {
|
||||
log.Warnf("sharing: invalid token %s", txt.Quote(token))
|
||||
log.Warn("share: invalid token")
|
||||
c.Redirect(http.StatusTemporaryRedirect, "/")
|
||||
return
|
||||
}
|
||||
|
||||
clientConfig := conf.GuestConfig()
|
||||
|
||||
c.HTML(http.StatusOK, "share.tmpl", gin.H{"config": clientConfig})
|
||||
}
|
||||
c.HTML(http.StatusOK, "share.tmpl", gin.H{"config": clientConfig, "previewUrl": clientConfig.SitePreview})
|
||||
})
|
||||
|
||||
func InitShare(router *gin.RouterGroup) {
|
||||
router.GET("/:token/:uid", func(c *gin.Context) {
|
||||
conf := service.Config()
|
||||
|
||||
router.GET("/:token", func(c *gin.Context) {
|
||||
shareHandler(c, conf)
|
||||
})
|
||||
shareToken := c.Param("token")
|
||||
shareUID := c.Param("uid")
|
||||
|
||||
router.GET("/:token/*uid", func(c *gin.Context) {
|
||||
shareHandler(c, conf)
|
||||
links := entity.FindValidLinks(shareToken, shareUID)
|
||||
|
||||
if len(links) != 1 {
|
||||
log.Warn("share: invalid token or uid")
|
||||
c.Redirect(http.StatusTemporaryRedirect, "/")
|
||||
return
|
||||
}
|
||||
|
||||
clientConfig := conf.GuestConfig()
|
||||
sharePreview := fmt.Sprintf("%ss/%s/%s/preview", clientConfig.SiteUrl, shareToken, shareUID)
|
||||
|
||||
c.HTML(http.StatusOK, "share.tmpl", gin.H{"config": clientConfig, "previewUrl": sharePreview})
|
||||
})
|
||||
}
|
||||
|
|
139
internal/api/share_preview.go
Normal file
139
internal/api/share_preview.go
Normal file
|
@ -0,0 +1,139 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/photoprism"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/service"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
// GET /s/:token/:uid/preview
|
||||
// TODO: Proof of concept, needs refactoring.
|
||||
func SharePreview(router *gin.RouterGroup) {
|
||||
router.GET("/:token/:uid/preview", func(c *gin.Context) {
|
||||
conf := service.Config()
|
||||
shareToken := c.Param("token")
|
||||
shareUID := c.Param("uid")
|
||||
|
||||
links := entity.FindLinks(shareToken, shareUID)
|
||||
|
||||
if len(links) != 1 {
|
||||
log.Warn("share: invalid token (preview)")
|
||||
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
|
||||
return
|
||||
}
|
||||
|
||||
thumbPath := path.Join(conf.ThumbPath(), "share")
|
||||
|
||||
if err := os.MkdirAll(thumbPath, os.ModePerm); err != nil {
|
||||
log.Error(err)
|
||||
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
|
||||
return
|
||||
}
|
||||
|
||||
previewFilename := fmt.Sprintf("%s/%s.jpg", thumbPath, shareUID)
|
||||
yesterday := time.Now().Add(-24 * time.Hour)
|
||||
|
||||
if info, err := os.Stat(previewFilename); err != nil {
|
||||
log.Debugf("share: creating new preview for %s", shareUID)
|
||||
} else if info.ModTime().After(yesterday) {
|
||||
log.Debugf("share: using cached preview for %s", shareUID)
|
||||
c.File(previewFilename)
|
||||
return
|
||||
} else if err := os.Remove(previewFilename); err != nil {
|
||||
log.Errorf("share: could not remove old preview of %s", shareUID)
|
||||
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
|
||||
return
|
||||
}
|
||||
|
||||
var f form.PhotoSearch
|
||||
|
||||
// Previews may only contain public content in shared albums.
|
||||
f.Album = shareUID
|
||||
f.Public = true
|
||||
f.Private = false
|
||||
f.Hidden = false
|
||||
f.Archived = false
|
||||
f.Review = false
|
||||
f.Merged = true
|
||||
|
||||
// Get first 12 album entries.
|
||||
f.Count = 12
|
||||
f.Order = "relevance"
|
||||
|
||||
p, _, err := query.PhotoSearch(f)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
|
||||
return
|
||||
}
|
||||
|
||||
width := 908
|
||||
height := 680
|
||||
x := 0
|
||||
y := 0
|
||||
|
||||
preview := imaging.New(width, height, color.NRGBA{255, 255, 255, 255})
|
||||
thumbType, _ := thumb.Types["tile_224"]
|
||||
|
||||
for _, f := range p {
|
||||
fileName := photoprism.FileName(f.FileRoot, f.FileName)
|
||||
|
||||
if !fs.FileExists(fileName) {
|
||||
log.Errorf("share: file %s is missing (preview)", txt.Quote(f.FileName))
|
||||
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
|
||||
return
|
||||
}
|
||||
|
||||
thumbnail, err := thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
|
||||
}
|
||||
|
||||
src, err := imaging.Open(thumbnail)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
|
||||
return
|
||||
}
|
||||
|
||||
preview = imaging.Paste(preview, src, image.Pt(x, y))
|
||||
|
||||
x += 228
|
||||
|
||||
if x > width {
|
||||
x = 0
|
||||
y += 228
|
||||
}
|
||||
}
|
||||
|
||||
// Save the resulting image as JPEG.
|
||||
err = imaging.Save(preview, previewFilename)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
|
||||
return
|
||||
}
|
||||
|
||||
c.File(previewFilename)
|
||||
})
|
||||
}
|
|
@ -10,8 +10,8 @@ import (
|
|||
func TestGetPreview(t *testing.T) {
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
GetPreview(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/preview")
|
||||
SharePreview(router)
|
||||
r := PerformRequest(app, "GET", "/a/1jxf3jfn2k/st9lxuqxpogaaba7/preview")
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
})
|
||||
}
|
|
@ -32,6 +32,7 @@ func configAction(ctx *cli.Context) error {
|
|||
|
||||
// Site information.
|
||||
fmt.Printf("%-25s %s\n", "site-url", conf.SiteUrl())
|
||||
fmt.Printf("%-25s %s\n", "site-preview", conf.SitePreview())
|
||||
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())
|
||||
|
|
|
@ -18,6 +18,7 @@ type ClientConfig struct {
|
|||
Copyright string `json:"copyright"`
|
||||
Flags string `json:"flags"`
|
||||
SiteUrl string `json:"siteUrl"`
|
||||
SitePreview string `json:"sitePreview"`
|
||||
SiteTitle string `json:"siteTitle"`
|
||||
SiteCaption string `json:"siteCaption"`
|
||||
SiteDescription string `json:"siteDescription"`
|
||||
|
@ -129,6 +130,7 @@ func (c *Config) PublicConfig() ClientConfig {
|
|||
Flags: strings.Join(c.Flags(), " "),
|
||||
Name: c.Name(),
|
||||
SiteUrl: c.SiteUrl(),
|
||||
SitePreview: c.SitePreview(),
|
||||
SiteTitle: c.SiteTitle(),
|
||||
SiteCaption: c.SiteCaption(),
|
||||
SiteDescription: c.SiteDescription(),
|
||||
|
@ -167,6 +169,7 @@ func (c *Config) GuestConfig() ClientConfig {
|
|||
Flags: "readonly public shared",
|
||||
Name: c.Name(),
|
||||
SiteUrl: c.SiteUrl(),
|
||||
SitePreview: c.SitePreview(),
|
||||
SiteTitle: c.SiteTitle(),
|
||||
SiteCaption: c.SiteCaption(),
|
||||
SiteDescription: c.SiteDescription(),
|
||||
|
@ -200,6 +203,7 @@ func (c *Config) UserConfig() ClientConfig {
|
|||
Flags: strings.Join(c.Flags(), " "),
|
||||
Name: c.Name(),
|
||||
SiteUrl: c.SiteUrl(),
|
||||
SitePreview: c.SitePreview(),
|
||||
SiteTitle: c.SiteTitle(),
|
||||
SiteCaption: c.SiteCaption(),
|
||||
SiteDescription: c.SiteDescription(),
|
||||
|
|
|
@ -3,6 +3,7 @@ package config
|
|||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -110,6 +111,19 @@ func (c *Config) SiteUrl() string {
|
|||
return c.params.SiteUrl
|
||||
}
|
||||
|
||||
// SitePreview returns the site preview image URL for sharing.
|
||||
func (c *Config) SitePreview() string {
|
||||
if c.params.SitePreview == "" {
|
||||
return c.SiteUrl() + "static/img/preview.jpg"
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(c.params.SitePreview, "http") {
|
||||
return c.SiteUrl() + c.params.SitePreview
|
||||
}
|
||||
|
||||
return c.params.SitePreview
|
||||
}
|
||||
|
||||
// SiteTitle returns the main site title (default is application name).
|
||||
func (c *Config) SiteTitle() string {
|
||||
if c.params.SiteTitle == "" {
|
||||
|
|
|
@ -40,7 +40,6 @@ var GlobalFlags = []cli.Flag{
|
|||
cli.StringFlag{
|
||||
Name: "webdav-password",
|
||||
Usage: "WebDAV password (none to disable)",
|
||||
Value: "",
|
||||
EnvVar: "PHOTOPRISM_WEBDAV_PASSWORD",
|
||||
},
|
||||
cli.IntFlag{
|
||||
|
@ -55,10 +54,15 @@ var GlobalFlags = []cli.Flag{
|
|||
},
|
||||
cli.StringFlag{
|
||||
Name: "site-url",
|
||||
Usage: "canonical / public site URL",
|
||||
Usage: "public site `URL`",
|
||||
Value: "http://localhost:2342/",
|
||||
EnvVar: "PHOTOPRISM_SITE_URL",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "site-preview",
|
||||
Usage: "public preview image `URL`",
|
||||
EnvVar: "PHOTOPRISM_SITE_PREVIEW",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "site-title",
|
||||
Usage: "site title",
|
||||
|
@ -67,31 +71,29 @@ var GlobalFlags = []cli.Flag{
|
|||
},
|
||||
cli.StringFlag{
|
||||
Name: "site-caption",
|
||||
Usage: "short caption / tagline",
|
||||
Usage: "short site caption",
|
||||
Value: "Browse Your Life",
|
||||
EnvVar: "PHOTOPRISM_SITE_CAPTION",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "site-description",
|
||||
Usage: "long site description",
|
||||
Value: "Open-Source Personal Photo Management.",
|
||||
EnvVar: "PHOTOPRISM_SITE_DESCRIPTION",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "site-author",
|
||||
Usage: "site owner / copyright",
|
||||
Value: "Anonymous",
|
||||
Usage: "site artist or copyright",
|
||||
EnvVar: "PHOTOPRISM_SITE_AUTHOR",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "http-port",
|
||||
Value: 2342,
|
||||
Usage: "HTTP server port",
|
||||
Usage: "http server port `NUMBER``",
|
||||
EnvVar: "PHOTOPRISM_HTTP_PORT",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "http-host",
|
||||
Usage: "HTTP server host",
|
||||
Usage: "http server `IP` address",
|
||||
EnvVar: "PHOTOPRISM_HTTP_HOST",
|
||||
},
|
||||
cli.StringFlag{
|
||||
|
@ -101,14 +103,13 @@ var GlobalFlags = []cli.Flag{
|
|||
},
|
||||
cli.StringFlag{
|
||||
Name: "database-driver",
|
||||
Usage: "database `DRIVER` (sqlite or mysql)",
|
||||
Usage: "database driver `NAME` (sqlite or mysql)",
|
||||
Value: "sqlite",
|
||||
EnvVar: "PHOTOPRISM_DATABASE_DRIVER",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "database-dsn",
|
||||
Usage: "database data source or file name (`DSN`)",
|
||||
Value: "",
|
||||
Usage: "data source or file name (`DSN`)",
|
||||
EnvVar: "PHOTOPRISM_DATABASE_DSN",
|
||||
},
|
||||
cli.IntFlag{
|
||||
|
@ -120,31 +121,27 @@ var GlobalFlags = []cli.Flag{
|
|||
cli.StringFlag{
|
||||
Name: "assets-path",
|
||||
Usage: "assets `PATH` for static files like templates and TensorFlow models",
|
||||
Value: "",
|
||||
EnvVar: "PHOTOPRISM_ASSETS_PATH",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "storage-path",
|
||||
Usage: "storage `PATH` for generated files like cache and index",
|
||||
Value: "",
|
||||
EnvVar: "PHOTOPRISM_STORAGE_PATH",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "import-path",
|
||||
Usage: "optional import `PATH` for copying files to originals",
|
||||
Value: "",
|
||||
EnvVar: "PHOTOPRISM_IMPORT_PATH",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "originals-path",
|
||||
Usage: "originals `PATH` for photo, video and sidecar files",
|
||||
Value: "",
|
||||
EnvVar: "PHOTOPRISM_ORIGINALS_PATH",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "originals-limit",
|
||||
Value: 1000,
|
||||
Usage: "file `SIZE` limit for originals in MB",
|
||||
Usage: "file size limit for originals in `MEGABYTE`",
|
||||
EnvVar: "PHOTOPRISM_ORIGINALS_LIMIT",
|
||||
},
|
||||
cli.StringFlag{
|
||||
|
@ -163,30 +160,25 @@ var GlobalFlags = []cli.Flag{
|
|||
Name: "pid-filename",
|
||||
Usage: "filename for the server process id (pid)",
|
||||
EnvVar: "PHOTOPRISM_PID_FILENAME",
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "cache-path",
|
||||
Usage: "cache `PATH`",
|
||||
Value: "",
|
||||
EnvVar: "PHOTOPRISM_CACHE_PATH",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "temp-path",
|
||||
Usage: "temporary `PATH` for uploads and downloads",
|
||||
Value: "",
|
||||
EnvVar: "PHOTOPRISM_TEMP_PATH",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "config-file, c",
|
||||
Usage: "load configuration from `FILENAME`",
|
||||
Value: "",
|
||||
EnvVar: "PHOTOPRISM_CONFIG_FILE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "settings-path",
|
||||
Usage: "settings `PATH`",
|
||||
Value: "",
|
||||
EnvVar: "PHOTOPRISM_SETTINGS_PATH",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
|
@ -242,7 +234,6 @@ var GlobalFlags = []cli.Flag{
|
|||
cli.StringFlag{
|
||||
Name: "sidecar-path",
|
||||
Usage: "storage `PATH` for automatically created sidecar files (relative or absolute)",
|
||||
Value: "",
|
||||
EnvVar: "PHOTOPRISM_SIDECAR_PATH",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
|
@ -263,19 +254,18 @@ var GlobalFlags = []cli.Flag{
|
|||
},
|
||||
cli.StringFlag{
|
||||
Name: "download-token",
|
||||
Usage: "url `TOKEN` for file downloads",
|
||||
Value: "",
|
||||
Usage: "`SECRET` url token for file downloads",
|
||||
EnvVar: "PHOTOPRISM_DOWNLOAD_TOKEN",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "preview-token",
|
||||
Usage: "url `TOKEN` for thumbnails and video streaming",
|
||||
Usage: "`SECRET` url token for thumbnails and video streaming",
|
||||
Value: "static",
|
||||
EnvVar: "PHOTOPRISM_PREVIEW_TOKEN",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "thumb-filter, f",
|
||||
Usage: "resample filter (best to worst: blackman, lanczos, cubic, linear)",
|
||||
Usage: "resample filter `NAME` (best to worst: blackman, lanczos, cubic, linear)",
|
||||
Value: "lanczos",
|
||||
EnvVar: "PHOTOPRISM_THUMB_FILTER",
|
||||
},
|
||||
|
@ -286,19 +276,19 @@ var GlobalFlags = []cli.Flag{
|
|||
},
|
||||
cli.IntFlag{
|
||||
Name: "thumb-size, s",
|
||||
Usage: "default thumbnail size limit in pixels (720-3840)",
|
||||
Usage: "default thumbnail size limit in `PIXELS` (720-3840)",
|
||||
Value: 2048,
|
||||
EnvVar: "PHOTOPRISM_THUMB_SIZE",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "thumb-limit, x",
|
||||
Usage: "on-demand thumbnail size limit in pixels (720-3840)",
|
||||
Usage: "on-demand thumbnail size limit in `PIXELS` (720-3840)",
|
||||
Value: 3840,
|
||||
EnvVar: "PHOTOPRISM_THUMB_LIMIT",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "jpeg-quality, q",
|
||||
Usage: "set to 95 for high-quality thumbnails (25-100)",
|
||||
Usage: "choose 95 for high-quality thumbnails (25-100)",
|
||||
Value: 90,
|
||||
EnvVar: "PHOTOPRISM_JPEG_QUALITY",
|
||||
},
|
||||
|
|
|
@ -32,6 +32,7 @@ type Params struct {
|
|||
Version string
|
||||
Copyright string
|
||||
SiteUrl string `yaml:"site-url" flag:"site-url"`
|
||||
SitePreview string `yaml:"site-preview" flag:"site-preview"`
|
||||
SiteTitle string `yaml:"site-title" flag:"site-title"`
|
||||
SiteCaption string `yaml:"site-caption" flag:"site-caption"`
|
||||
SiteDescription string `yaml:"site-description" flag:"site-description"`
|
||||
|
|
|
@ -21,7 +21,7 @@ func TestLink_Expired(t *testing.T) {
|
|||
|
||||
link := NewLink("st9lxuqxpogaaba1", true, false)
|
||||
|
||||
link.ModifiedAt = Timestamp().Add(-7* Day)
|
||||
link.ModifiedAt = Timestamp().Add(-7 * Day)
|
||||
link.ShareExpires = 0
|
||||
|
||||
assert.False(t, link.Expired())
|
||||
|
|
|
@ -25,7 +25,6 @@ type PhotoSearch struct {
|
|||
Public bool `form:"public"`
|
||||
Private bool `form:"private"`
|
||||
Favorite bool `form:"favorite"`
|
||||
Safe bool `form:"safe"`
|
||||
Lat float32 `form:"lat"`
|
||||
Lng float32 `form:"lng"`
|
||||
Dist uint `form:"dist"`
|
||||
|
|
|
@ -30,7 +30,6 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
|
|||
api.CreateSession(v1)
|
||||
api.DeleteSession(v1)
|
||||
|
||||
api.GetPreview(v1)
|
||||
api.GetThumbnail(v1)
|
||||
api.GetDownload(v1)
|
||||
api.GetVideo(v1)
|
||||
|
@ -115,10 +114,11 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
|
|||
api.Websocket(v1)
|
||||
}
|
||||
|
||||
// Link sharing.
|
||||
share := router.Group("/s")
|
||||
// Configure link sharing.
|
||||
s := router.Group("/s")
|
||||
{
|
||||
api.InitShare(share)
|
||||
api.Shares(s)
|
||||
api.SharePreview(s)
|
||||
}
|
||||
|
||||
// WebDAV server for file management, sync and sharing.
|
||||
|
|
Loading…
Reference in a new issue