Configure on-demand rendering of regular thumbnail sizes #294
Can be enabled by setting PHOTOPRISM_RESAMPLE_UNCACHED to true Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
parent
ee6dd2be72
commit
1c53a565a7
27 changed files with 432 additions and 239 deletions
|
@ -12,10 +12,11 @@ ENV PHOTOPRISM_PUBLIC true
|
|||
ENV PHOTOPRISM_EXPERIMENTAL true
|
||||
ENV PHOTOPRISM_UPLOAD_NSFW false
|
||||
ENV PHOTOPRISM_DETECT_NSFW true
|
||||
ENV PHOTOPRISM_THUMB_QUALITY 95
|
||||
ENV PHOTOPRISM_THUMB_SIZE 3840
|
||||
ENV PHOTOPRISM_THUMB_LIMIT 3840
|
||||
ENV PHOTOPRISM_THUMB_FILTER lanczos
|
||||
ENV PHOTOPRISM_JPEG_QUALITY 95
|
||||
ENV PHOTOPRISM_RESAMPLE_SIZE 3840
|
||||
ENV PHOTOPRISM_RESAMPLE_LIMIT 3840
|
||||
ENV PHOTOPRISM_RESAMPLE_UNCACHED true
|
||||
ENV PHOTOPRISM_RESAMPLE_FILTER lanczos
|
||||
ENV PHOTOPRISM_GEOCODING_API places
|
||||
|
||||
# Import example photos
|
||||
|
|
|
@ -42,10 +42,14 @@ services:
|
|||
PHOTOPRISM_WEBDAV_PASSWORD: "photoprism" # Plain text only (username "photoprism")
|
||||
PHOTOPRISM_DATABASE_DRIVER: "tidb" # Change to "mysql" for external MySQL or MariaDB
|
||||
PHOTOPRISM_DATABASE_DSN: "root:photoprism@tcp(localhost:2343)/photoprism?parseTime=true"
|
||||
# PHOTOPRISM_THUMB_QUALITY: 95 # High-quality thumbnails (optional)
|
||||
# PHOTOPRISM_THUMB_SIZE: 3840
|
||||
# PHOTOPRISM_THUMB_LIMIT: 3840
|
||||
# PHOTOPRISM_THUMB_FILTER: "lanczos"
|
||||
# PHOTOPRISM_DATABASE_DRIVER: "mysql" # Using MariaDB or MySQL instead of the internal TiDB is optional
|
||||
# PHOTOPRISM_DATABASE_DSN: "photoprism:photoprism@tcp(photoprism-db:3306)/photoprism?parseTime=true"
|
||||
PHOTOPRISM_JPEG_QUALITY: 90 # Use 95 for high-quality thumbnails (requires more storage)
|
||||
PHOTOPRISM_RESAMPLE_FILTER: "lanczos" # Resample filter, best to worst: blackman, lanczos, cubic, linear
|
||||
PHOTOPRISM_RESAMPLE_UNCACHED: "false" # On-demand rendering of thumbnails (high memory and cpu usage)
|
||||
PHOTOPRISM_RESAMPLE_SIZE: 2048 # Cached thumbnail size (default 2048, min 720, max 3840)
|
||||
# PHOTOPRISM_RESAMPLE_SIZE: 3840 # For retina screens (requires more storage)
|
||||
PHOTOPRISM_RESAMPLE_LIMIT: 3840 # On-demand thumbnail size (default 2048, min 720, max 3840)
|
||||
volumes:
|
||||
- "~/Pictures/Originals:/photoprism/originals" # [local path]:[container path]
|
||||
- "~/Pictures/Import:/photoprism/import" # [local path]:[container path] (optional)
|
||||
|
|
|
@ -43,9 +43,12 @@ services:
|
|||
PHOTOPRISM_DATABASE_DSN: "root:photoprism@tcp(localhost:2343)/photoprism?parseTime=true"
|
||||
# PHOTOPRISM_DATABASE_DRIVER: "mysql" # Using MariaDB or MySQL instead of the internal TiDB is optional
|
||||
# PHOTOPRISM_DATABASE_DSN: "photoprism:photoprism@tcp(photoprism-db:3306)/photoprism?parseTime=true"
|
||||
# PHOTOPRISM_THUMB_QUALITY: 95 # High-quality thumbnails (optional, default JPEG quality is 90)
|
||||
# PHOTOPRISM_THUMB_SIZE: 3840 # For retina screens, default is 2048
|
||||
# PHOTOPRISM_THUMB_FILTER: "lanczos" # Resample filter, best to worst: blackman, lanczos, cubic, linear
|
||||
PHOTOPRISM_JPEG_QUALITY: 90 # Use 95 for high-quality thumbnails (requires more storage)
|
||||
PHOTOPRISM_RESAMPLE_FILTER: "lanczos" # Resample filter, best to worst: blackman, lanczos, cubic, linear
|
||||
PHOTOPRISM_RESAMPLE_UNCACHED: "false" # On-demand rendering of thumbnails (high memory and cpu usage)
|
||||
PHOTOPRISM_RESAMPLE_SIZE: 2048 # Cached thumbnail size (default 2048, min 720, max 3840)
|
||||
# PHOTOPRISM_RESAMPLE_SIZE: 3840 # For retina screens (requires more storage)
|
||||
PHOTOPRISM_RESAMPLE_LIMIT: 3840 # On-demand thumbnail size (default 2048, min 720, max 3840)
|
||||
volumes:
|
||||
- "~/Pictures/Originals:/photoprism/originals" # [local path]:[container path]
|
||||
- "~/Pictures/Import:/photoprism/import" # [local path]:[container path] (optional)
|
||||
|
|
2
go.mod
2
go.mod
|
@ -16,7 +16,7 @@ require (
|
|||
github.com/dsoprea/go-logging v0.0.0-20200401235223-7e979d0e0d02 // indirect
|
||||
github.com/dsoprea/go-png-image-structure v0.0.0-20200402000326-c0fdb803026f
|
||||
github.com/dsoprea/go-utility v0.0.0-20200412174200-5aee815e0920 // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/gin-gonic/gin v1.6.2
|
||||
github.com/go-errors/errors v1.0.2 // indirect
|
||||
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d
|
||||
|
|
|
@ -439,7 +439,7 @@ func AlbumThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||
|
||||
if !ok {
|
||||
log.Errorf("album: invalid thumb type %s", typeName)
|
||||
c.Data(http.StatusBadRequest, "image/svg+xml", photoIconSvg)
|
||||
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -466,7 +466,7 @@ func AlbumThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||
|
||||
if !fs.FileExists(fileName) {
|
||||
log.Errorf("album: could not find original for %s", fileName)
|
||||
c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg)
|
||||
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
|
||||
|
||||
// Set missing flag so that the file doesn't show up in search results anymore
|
||||
f.FileMissing = true
|
||||
|
@ -477,33 +477,40 @@ func AlbumThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||
// Use original file if thumb size exceeds limit, see https://github.com/photoprism/photoprism/issues/157
|
||||
if thumbType.ExceedsLimit() && c.Query("download") == "" {
|
||||
log.Debugf("album: using original, thumbnail size exceeds limit (width %d, height %d)", thumbType.Width, thumbType.Height)
|
||||
|
||||
c.File(fileName)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if thumbnail, err := thumb.FromFile(fileName, f.FileHash, conf.ThumbnailsPath(), thumbType.Width, thumbType.Height, thumbType.Options...); err == nil {
|
||||
if c.Query("download") != "" {
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", f.ShareFileName()))
|
||||
}
|
||||
var thumbnail string
|
||||
|
||||
thumbData, err := ioutil.ReadFile(thumbnail)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("album: %s", err)
|
||||
c.Data(http.StatusOK, "image/svg+xml", albumIconSvg)
|
||||
return
|
||||
}
|
||||
|
||||
gc.Set(cacheKey, thumbData, time.Hour)
|
||||
|
||||
log.Debugf("album: %s cached [%s]", cacheKey, time.Since(start))
|
||||
|
||||
c.Data(http.StatusOK, "image/jpeg", thumbData)
|
||||
if conf.ResampleUncached() || thumbType.SkipPreRender() {
|
||||
thumbnail, err = thumb.FromFile(fileName, f.FileHash, conf.ThumbnailsPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
|
||||
} else {
|
||||
log.Errorf("album: %s", err)
|
||||
c.Data(http.StatusBadRequest, "image/svg+xml", photoIconSvg)
|
||||
thumbnail, err = thumb.FromCache(fileName, f.FileHash, conf.ThumbnailsPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("album: %s", err)
|
||||
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
|
||||
return
|
||||
}
|
||||
|
||||
if c.Query("download") != "" {
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", f.ShareFileName()))
|
||||
}
|
||||
|
||||
thumbData, err := ioutil.ReadFile(thumbnail)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("album: %s", err)
|
||||
c.Data(http.StatusOK, "image/svg+xml", albumIconSvg)
|
||||
return
|
||||
}
|
||||
|
||||
gc.Set(cacheKey, thumbData, time.Hour)
|
||||
|
||||
log.Debugf("album: %s cached [%s]", cacheKey, time.Since(start))
|
||||
|
||||
c.Data(http.StatusOK, "image/jpeg", thumbData)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -271,7 +271,7 @@ func TestAlbumThumbnail(t *testing.T) {
|
|||
AlbumThumbnail(router, ctx)
|
||||
r := PerformRequest(app, "GET", "/api/v1/albums/at9lxuqxpogaaba7/thumbnail/xxx")
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
t.Run("album has no photo (because is not existing)", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
|
@ -283,6 +283,6 @@ func TestAlbumThumbnail(t *testing.T) {
|
|||
app, router, ctx := NewApiTest()
|
||||
AlbumThumbnail(router, ctx)
|
||||
r := PerformRequest(app, "GET", "/api/v1/albums/at9lxuqxpogaaba8/thumbnail/tile_500")
|
||||
assert.Equal(t, http.StatusNotFound, r.Code)
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -220,25 +220,32 @@ func LabelThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||
return
|
||||
}
|
||||
|
||||
if thumbnail, err := thumb.FromFile(fileName, f.FileHash, conf.ThumbnailsPath(), thumbType.Width, thumbType.Height, thumbType.Options...); err == nil {
|
||||
thumbData, err := ioutil.ReadFile(thumbnail)
|
||||
var thumbnail string
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("label: %s", err)
|
||||
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
|
||||
return
|
||||
}
|
||||
|
||||
gc.Set(cacheKey, thumbData, time.Hour*4)
|
||||
|
||||
log.Debugf("label: %s cached [%s]", cacheKey, time.Since(start))
|
||||
|
||||
c.Data(http.StatusOK, "image/jpeg", thumbData)
|
||||
if conf.ResampleUncached() || thumbType.SkipPreRender() {
|
||||
thumbnail, err = thumb.FromFile(fileName, f.FileHash, conf.ThumbnailsPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
|
||||
} else {
|
||||
log.Errorf("label: %s", err)
|
||||
thumbnail, err = thumb.FromCache(fileName, f.FileHash, conf.ThumbnailsPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("label: %s", err)
|
||||
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
|
||||
return
|
||||
}
|
||||
|
||||
thumbData, err := ioutil.ReadFile(thumbnail)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("label: %s", err)
|
||||
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
|
||||
return
|
||||
}
|
||||
|
||||
gc.Set(cacheKey, thumbData, time.Hour*4)
|
||||
|
||||
log.Debugf("label: %s cached [%s]", cacheKey, time.Since(start))
|
||||
|
||||
c.Data(http.StatusOK, "image/jpeg", thumbData)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ func GetThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||
|
||||
if !ok {
|
||||
log.Errorf("photo: invalid thumb type %s", txt.Quote(typeName))
|
||||
c.Data(http.StatusBadRequest, "image/svg+xml", photoIconSvg)
|
||||
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -36,12 +36,12 @@ func GetThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||
f, err := q.FileByHash(fileHash)
|
||||
|
||||
if err != nil {
|
||||
c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg)
|
||||
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
|
||||
return
|
||||
}
|
||||
|
||||
if f.FileError != "" {
|
||||
c.Data(http.StatusBadRequest, "image/svg+xml", brokenIconSvg)
|
||||
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ func GetThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||
|
||||
if !fs.FileExists(fileName) {
|
||||
log.Errorf("photo: could not find original for %s", fileName)
|
||||
c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg)
|
||||
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
|
||||
|
||||
// Set missing flag so that the file doesn't show up in search results anymore
|
||||
f.FileMissing = true
|
||||
|
@ -66,19 +66,24 @@ func GetThumbnail(router *gin.RouterGroup, conf *config.Config) {
|
|||
return
|
||||
}
|
||||
|
||||
if thumbnail, err := thumb.FromFile(fileName, f.FileHash, conf.ThumbnailsPath(), thumbType.Width, thumbType.Height, thumbType.Options...); err == nil {
|
||||
if c.Query("download") != "" {
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", f.ShareFileName()))
|
||||
}
|
||||
var thumbnail string
|
||||
|
||||
c.File(thumbnail)
|
||||
if conf.ResampleUncached() || thumbType.SkipPreRender() {
|
||||
thumbnail, err = thumb.FromFile(fileName, f.FileHash, conf.ThumbnailsPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
|
||||
} else {
|
||||
log.Errorf("photo: %s", err)
|
||||
|
||||
f.FileError = err.Error()
|
||||
db.Save(&f)
|
||||
|
||||
c.Data(http.StatusBadRequest, "image/svg+xml", brokenIconSvg)
|
||||
thumbnail, err = thumb.FromCache(fileName, f.FileHash, conf.ThumbnailsPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("photo: %s", err)
|
||||
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
|
||||
return
|
||||
}
|
||||
|
||||
if c.Query("download") != "" {
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", f.ShareFileName()))
|
||||
}
|
||||
|
||||
c.File(thumbnail)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -13,20 +13,20 @@ func TestGetThumbnail(t *testing.T) {
|
|||
GetThumbnail(router, ctx)
|
||||
result := PerformRequest(app, "GET", "/api/v1/thumbnails/1/xxx")
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, result.Code)
|
||||
assert.Equal(t, http.StatusOK, result.Code)
|
||||
})
|
||||
t.Run("invalid hash", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
GetThumbnail(router, ctx)
|
||||
result := PerformRequest(app, "GET", "/api/v1/thumbnails/1/tile_500")
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, result.Code)
|
||||
assert.Equal(t, http.StatusOK, result.Code)
|
||||
})
|
||||
t.Run("could not find original", func(t *testing.T) {
|
||||
app, router, ctx := NewApiTest()
|
||||
GetThumbnail(router, ctx)
|
||||
result := PerformRequest(app, "GET", "/api/v1/thumbnails/123xxx/tile_500")
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, result.Code)
|
||||
assert.Equal(t, http.StatusOK, result.Code)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -25,6 +25,10 @@ var brokenIconSvg = []byte(`
|
|||
<path fill="none" d="M0 0h24v24H0z"/>
|
||||
<path d="M21 5v6.59l-3-3.01-4 4.01-4-4-4 4-3-3.01V5c0-1.1.9-2 2-2h14c1.1 0 2 .9 2 2zm-3 6.42l3 3.01V19c0 1.1-.9 2-2 2H5c-1.1 0-2-.9-2-2v-6.58l3 2.99 4-4 4 4 4-3.99z"/></svg>`)
|
||||
|
||||
var uncachedIconSvg = []byte(`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></svg>`)
|
||||
|
||||
// GET /api/v1/svg/*
|
||||
func GetSvg(router *gin.RouterGroup) {
|
||||
router.GET("/svg/photo", func(c *gin.Context) {
|
||||
|
@ -42,4 +46,8 @@ func GetSvg(router *gin.RouterGroup) {
|
|||
router.GET("/svg/broken", func(c *gin.Context) {
|
||||
c.Data(http.StatusOK, "image/svg+xml", brokenIconSvg)
|
||||
})
|
||||
|
||||
router.GET("/svg/uncached", func(c *gin.Context) {
|
||||
c.Data(http.StatusOK, "image/svg+xml", uncachedIconSvg)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -19,71 +19,72 @@ var ConfigCommand = cli.Command{
|
|||
func configAction(ctx *cli.Context) error {
|
||||
conf := config.NewConfig(ctx)
|
||||
|
||||
fmt.Printf("NAME VALUE\n")
|
||||
fmt.Printf("admin-password %s\n", conf.AdminPassword())
|
||||
fmt.Printf("webdav-password %s\n", conf.WebDAVPassword())
|
||||
fmt.Printf("name %s\n", conf.Name())
|
||||
fmt.Printf("url %s\n", conf.Url())
|
||||
fmt.Printf("title %s\n", conf.Title())
|
||||
fmt.Printf("subtitle %s\n", conf.Subtitle())
|
||||
fmt.Printf("description %s\n", conf.Description())
|
||||
fmt.Printf("author %s\n", conf.Author())
|
||||
fmt.Printf("twitter %s\n", conf.Twitter())
|
||||
fmt.Printf("version %s\n", conf.Version())
|
||||
fmt.Printf("copyright %s\n", conf.Copyright())
|
||||
fmt.Printf("debug %t\n", conf.Debug())
|
||||
fmt.Printf("read-only %t\n", conf.ReadOnly())
|
||||
fmt.Printf("public %t\n", conf.Public())
|
||||
fmt.Printf("experimental %t\n", conf.Experimental())
|
||||
fmt.Printf("workers %d\n", conf.Workers())
|
||||
fmt.Printf("wakeup-interval %d\n", conf.WakeupInterval()/time.Second)
|
||||
fmt.Printf("log-level %s\n", conf.LogLevel())
|
||||
fmt.Printf("log-filename %s\n", conf.LogFilename())
|
||||
fmt.Printf("pid-filename %s\n", conf.PIDFilename())
|
||||
fmt.Printf("config-file %s\n", conf.ConfigFile())
|
||||
fmt.Printf("config-path %s\n", conf.ConfigPath())
|
||||
fmt.Printf("%-25s VALUE\n", "NAME")
|
||||
fmt.Printf("%-25s %s\n", "admin-password", conf.AdminPassword())
|
||||
fmt.Printf("%-25s %s\n", "webdav-password", conf.WebDAVPassword())
|
||||
fmt.Printf("%-25s %s\n", "name", conf.Name())
|
||||
fmt.Printf("%-25s %s\n", "url", conf.Url())
|
||||
fmt.Printf("%-25s %s\n", "title", conf.Title())
|
||||
fmt.Printf("%-25s %s\n", "subtitle", conf.Subtitle())
|
||||
fmt.Printf("%-25s %s\n", "description", conf.Description())
|
||||
fmt.Printf("%-25s %s\n", "author", conf.Author())
|
||||
fmt.Printf("%-25s %s\n", "twitter", conf.Twitter())
|
||||
fmt.Printf("%-25s %s\n", "version", conf.Version())
|
||||
fmt.Printf("%-25s %s\n", "copyright", conf.Copyright())
|
||||
fmt.Printf("%-25s %t\n", "debug", conf.Debug())
|
||||
fmt.Printf("%-25s %t\n", "read-only", conf.ReadOnly())
|
||||
fmt.Printf("%-25s %t\n", "public", conf.Public())
|
||||
fmt.Printf("%-25s %t\n", "experimental", conf.Experimental())
|
||||
fmt.Printf("%-25s %d\n", "workers", conf.Workers())
|
||||
fmt.Printf("%-25s %d\n", "wakeup-interval", conf.WakeupInterval()/time.Second)
|
||||
fmt.Printf("%-25s %s\n", "log-level", conf.LogLevel())
|
||||
fmt.Printf("%-25s %s\n", "log-filename", conf.LogFilename())
|
||||
fmt.Printf("%-25s %s\n", "pid-filename", conf.PIDFilename())
|
||||
fmt.Printf("%-25s %s\n", "config-file", conf.ConfigFile())
|
||||
fmt.Printf("%-25s %s\n", "config-path", conf.ConfigPath())
|
||||
|
||||
fmt.Printf("assets-path %s\n", conf.AssetsPath())
|
||||
fmt.Printf("originals-path %s\n", conf.OriginalsPath())
|
||||
fmt.Printf("import-path %s\n", conf.ImportPath())
|
||||
fmt.Printf("temp-path %s\n", conf.TempPath())
|
||||
fmt.Printf("cache-path %s\n", conf.CachePath())
|
||||
fmt.Printf("thumbnails-path %s\n", conf.ThumbnailsPath())
|
||||
fmt.Printf("resources-path %s\n", conf.ResourcesPath())
|
||||
fmt.Printf("tf-version %s\n", conf.TensorFlowVersion())
|
||||
fmt.Printf("tf-model-path %s\n", conf.TensorFlowModelPath())
|
||||
fmt.Printf("templates-path %s\n", conf.HttpTemplatesPath())
|
||||
fmt.Printf("favicons-path %s\n", conf.HttpFaviconsPath())
|
||||
fmt.Printf("static-path %s\n", conf.HttpStaticPath())
|
||||
fmt.Printf("static-build-path %s\n", conf.HttpStaticBuildPath())
|
||||
fmt.Printf("%-25s %s\n", "assets-path", conf.AssetsPath())
|
||||
fmt.Printf("%-25s %s\n", "originals-path", conf.OriginalsPath())
|
||||
fmt.Printf("%-25s %s\n", "import-path", conf.ImportPath())
|
||||
fmt.Printf("%-25s %s\n", "temp-path", conf.TempPath())
|
||||
fmt.Printf("%-25s %s\n", "cache-path", conf.CachePath())
|
||||
fmt.Printf("%-25s %s\n", "thumbnails-path", conf.ThumbnailsPath())
|
||||
fmt.Printf("%-25s %s\n", "resources-path", conf.ResourcesPath())
|
||||
fmt.Printf("%-25s %s\n", "tf-version", conf.TensorFlowVersion())
|
||||
fmt.Printf("%-25s %s\n", "tf-model-path", conf.TensorFlowModelPath())
|
||||
fmt.Printf("%-25s %s\n", "templates-path", conf.HttpTemplatesPath())
|
||||
fmt.Printf("%-25s %s\n", "favicons-path", conf.HttpFaviconsPath())
|
||||
fmt.Printf("%-25s %s\n", "static-path", conf.HttpStaticPath())
|
||||
fmt.Printf("%-25s %s\n", "static-build-path", conf.HttpStaticBuildPath())
|
||||
|
||||
fmt.Printf("http-host %s\n", conf.HttpServerHost())
|
||||
fmt.Printf("http-port %d\n", conf.HttpServerPort())
|
||||
fmt.Printf("http-mode %s\n", conf.HttpServerMode())
|
||||
fmt.Printf("%-25s %s\n", "http-host", conf.HttpServerHost())
|
||||
fmt.Printf("%-25s %d\n", "http-port", conf.HttpServerPort())
|
||||
fmt.Printf("%-25s %s\n", "http-mode", conf.HttpServerMode())
|
||||
|
||||
fmt.Printf("tidb-host %s\n", conf.TidbServerHost())
|
||||
fmt.Printf("tidb-port %d\n", conf.TidbServerPort())
|
||||
fmt.Printf("tidb-password %s\n", conf.TidbServerPassword())
|
||||
fmt.Printf("tidb-path %s\n", conf.TidbServerPath())
|
||||
fmt.Printf("%-25s %s\n", "tidb-host", conf.TidbServerHost())
|
||||
fmt.Printf("%-25s %d\n", "tidb-port", conf.TidbServerPort())
|
||||
fmt.Printf("%-25s %s\n", "tidb-password", conf.TidbServerPassword())
|
||||
fmt.Printf("%-25s %s\n", "tidb-path", conf.TidbServerPath())
|
||||
|
||||
fmt.Printf("database-driver %s\n", conf.DatabaseDriver())
|
||||
fmt.Printf("database-dsn %s\n", conf.DatabaseDsn())
|
||||
fmt.Printf("%-25s %s\n", "database-driver", conf.DatabaseDriver())
|
||||
fmt.Printf("%-25s %s\n", "database-dsn", conf.DatabaseDsn())
|
||||
|
||||
fmt.Printf("sips-bin %s\n", conf.SipsBin())
|
||||
fmt.Printf("darktable-bin %s\n", conf.DarktableBin())
|
||||
fmt.Printf("exiftool-bin %s\n", conf.ExifToolBin())
|
||||
fmt.Printf("heifconvert-bin %s\n", conf.HeifConvertBin())
|
||||
fmt.Printf("%-25s %s\n", "sips-bin", conf.SipsBin())
|
||||
fmt.Printf("%-25s %s\n", "darktable-bin", conf.DarktableBin())
|
||||
fmt.Printf("%-25s %s\n", "exiftool-bin", conf.ExifToolBin())
|
||||
fmt.Printf("%-25s %s\n", "heifconvert-bin", conf.HeifConvertBin())
|
||||
|
||||
fmt.Printf("detect-nsfw %t\n", conf.DetectNSFW())
|
||||
fmt.Printf("upload-nsfw %t\n", conf.UploadNSFW())
|
||||
fmt.Printf("geocoding-api %s\n", conf.GeoCodingApi())
|
||||
fmt.Printf("thumb-quality %d\n", conf.ThumbQuality())
|
||||
fmt.Printf("thumb-size %d\n", conf.ThumbSize())
|
||||
fmt.Printf("thumb-limit %d\n", conf.ThumbLimit())
|
||||
fmt.Printf("thumb-filter %s\n", conf.ThumbFilter())
|
||||
fmt.Printf("%-25s %t\n", "detect-nsfw", conf.DetectNSFW())
|
||||
fmt.Printf("%-25s %t\n", "upload-nsfw", conf.UploadNSFW())
|
||||
fmt.Printf("%-25s %s\n", "geocoding-api", conf.GeoCodingApi())
|
||||
fmt.Printf("%-25s %d\n", "jpeg-quality", conf.JpegQuality())
|
||||
fmt.Printf("%-25s %d\n", "resample-size", conf.ResampleSize())
|
||||
fmt.Printf("%-25s %d\n", "resample-limit", conf.ResampleLimit())
|
||||
fmt.Printf("%-25s %s\n", "resample-filter", conf.ResampleFilter())
|
||||
fmt.Printf("%-25s %t\n", "resample-uncached", conf.ResampleUncached())
|
||||
|
||||
fmt.Printf("disable-tf %t\n", conf.DisableTensorFlow())
|
||||
fmt.Printf("disable-settings %t\n", conf.DisableSettings())
|
||||
fmt.Printf("%-25s %t\n", "disable-tf", conf.DisableTensorFlow())
|
||||
fmt.Printf("%-25s %t\n", "disable-settings", conf.DisableSettings())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ func TestConfigCommand(t *testing.T) {
|
|||
err = ConfigCommand.Run(ctx)
|
||||
})
|
||||
|
||||
assert.Contains(t, output, "NAME VALUE")
|
||||
assert.Contains(t, output, "NAME VALUE")
|
||||
assert.Contains(t, output, "config-file")
|
||||
assert.Contains(t, output, "darktable-cli")
|
||||
assert.Contains(t, output, "originals-path")
|
||||
|
|
|
@ -99,6 +99,7 @@ func (c *Config) PublicClientConfig() ClientConfig {
|
|||
"colors": colors.All.List(),
|
||||
"categories": []string{},
|
||||
"clip": txt.ClipDefault,
|
||||
"server": RuntimeInfo{},
|
||||
}
|
||||
|
||||
return result
|
||||
|
@ -247,6 +248,7 @@ func (c *Config) ClientConfig() ClientConfig {
|
|||
"colors": colors.All.List(),
|
||||
"categories": categories,
|
||||
"clip": txt.ClipDefault,
|
||||
"server": NewRuntimeInfo(),
|
||||
}
|
||||
|
||||
return result
|
||||
|
|
|
@ -3,7 +3,6 @@ package config
|
|||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -72,10 +71,10 @@ func NewConfig(ctx *cli.Context) *Config {
|
|||
func (c *Config) Propagate() {
|
||||
log.SetLevel(c.LogLevel())
|
||||
|
||||
thumb.JpegQuality = c.ThumbQuality()
|
||||
thumb.PreRenderSize = c.ThumbSize()
|
||||
thumb.MaxRenderSize = c.ThumbLimit()
|
||||
thumb.Filter = c.ThumbFilter()
|
||||
thumb.Size = c.ResampleSize()
|
||||
thumb.Limit = c.ResampleLimit()
|
||||
thumb.Filter = c.ResampleFilter()
|
||||
thumb.JpegQuality = c.JpegQuality()
|
||||
|
||||
c.Settings().Propagate()
|
||||
}
|
||||
|
@ -242,61 +241,6 @@ func (c *Config) WakeupInterval() time.Duration {
|
|||
return time.Duration(c.params.WakeupInterval) * time.Second
|
||||
}
|
||||
|
||||
// ThumbQuality returns the thumbnail jpeg quality setting (25-100).
|
||||
func (c *Config) ThumbQuality() int {
|
||||
if c.params.ThumbQuality > 100 {
|
||||
return 100
|
||||
}
|
||||
|
||||
if c.params.ThumbQuality < 25 {
|
||||
return 25
|
||||
}
|
||||
|
||||
return c.params.ThumbQuality
|
||||
}
|
||||
|
||||
// ThumbSize returns the pre-rendered thumbnail size limit in pixels (720-3840).
|
||||
func (c *Config) ThumbSize() int {
|
||||
if c.params.ThumbSize > 3840 {
|
||||
return 3840
|
||||
}
|
||||
|
||||
if c.params.ThumbSize < 720 {
|
||||
return 720
|
||||
}
|
||||
|
||||
return c.params.ThumbSize
|
||||
}
|
||||
|
||||
// ThumbLimit returns the on-demand thumbnail size limit in pixels (720-3840).
|
||||
func (c *Config) ThumbLimit() int {
|
||||
if c.params.ThumbLimit > 3840 {
|
||||
return 3840
|
||||
}
|
||||
|
||||
if c.params.ThumbLimit < 720 {
|
||||
return 720
|
||||
}
|
||||
|
||||
return c.params.ThumbLimit
|
||||
}
|
||||
|
||||
// ThumbFilter returns the thumbnail resample filter (blackman, lanczos, cubic or linear).
|
||||
func (c *Config) ThumbFilter() thumb.ResampleFilter {
|
||||
switch strings.ToLower(c.params.ThumbFilter) {
|
||||
case "blackman":
|
||||
return thumb.ResampleBlackman
|
||||
case "lanczos":
|
||||
return thumb.ResampleLanczos
|
||||
case "cubic":
|
||||
return thumb.ResampleCubic
|
||||
case "linear":
|
||||
return thumb.ResampleLinear
|
||||
default:
|
||||
return thumb.ResampleCubic
|
||||
}
|
||||
}
|
||||
|
||||
// GeoCodingApi returns the preferred geo coding api (none, osm or places).
|
||||
func (c *Config) GeoCodingApi() string {
|
||||
switch c.params.GeoCodingApi {
|
||||
|
|
|
@ -239,28 +239,33 @@ var GlobalFlags = []cli.Flag{
|
|||
EnvVar: "PHOTOPRISM_GEOCODING_API",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "thumb-quality, q",
|
||||
Name: "jpeg-quality, q",
|
||||
Usage: "jpeg quality of thumbnails (25-100)",
|
||||
Value: 90,
|
||||
EnvVar: "PHOTOPRISM_THUMB_QUALITY",
|
||||
EnvVar: "PHOTOPRISM_JPEG_QUALITY",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "thumb-size, s",
|
||||
Usage: "pre-render size limit in pixels (720-3840)",
|
||||
Name: "resample-size, s",
|
||||
Usage: "pre-rendered thumbnail size limit in pixels (720-3840)",
|
||||
Value: 2048,
|
||||
EnvVar: "PHOTOPRISM_THUMB_SIZE",
|
||||
EnvVar: "PHOTOPRISM_RESAMPLE_SIZE",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "thumb-limit, x",
|
||||
Usage: "on-demand size limit in pixels (720-3840)",
|
||||
Name: "resample-limit, x",
|
||||
Usage: "on-demand thumbnail size limit in pixels (720-3840)",
|
||||
Value: 3840,
|
||||
EnvVar: "PHOTOPRISM_THUMB_LIMIT",
|
||||
EnvVar: "PHOTOPRISM_RESAMPLE_LIMIT",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "thumb-filter, f",
|
||||
Name: "resample-filter, f",
|
||||
Usage: "resample filter (blackman, lanczos, cubic or linear)",
|
||||
Value: "lanczos",
|
||||
EnvVar: "PHOTOPRISM_THUMB_FILTER",
|
||||
EnvVar: "PHOTOPRISM_RESAMPLE_FILTER",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "resample-uncached, u",
|
||||
Usage: "enables on-demand rendering of uncached thumbnails (high memory and cpu usage)",
|
||||
EnvVar: "PHOTOPRISM_RESAMPLE_UNCACHED",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "disable-tf",
|
||||
|
|
|
@ -64,22 +64,23 @@ type Params struct {
|
|||
HttpServerPort int `yaml:"http-port" flag:"http-port"`
|
||||
HttpServerMode string `yaml:"http-mode" flag:"http-mode"`
|
||||
HttpServerPassword string `yaml:"http-password" flag:"http-password"`
|
||||
SipsBin string `yaml:"sips-bin" flag:"sips-bin"`
|
||||
DarktableBin string `yaml:"darktable-bin" flag:"darktable-bin"`
|
||||
ExifToolBin string `yaml:"exiftool-bin" flag:"exiftool-bin"`
|
||||
HeifConvertBin string `yaml:"heifconvert-bin" flag:"heifconvert-bin"`
|
||||
PIDFilename string `yaml:"pid-filename" flag:"pid-filename"`
|
||||
LogFilename string `yaml:"log-filename" flag:"log-filename"`
|
||||
DetachServer bool `yaml:"detach-server" flag:"detach-server"`
|
||||
DetectNSFW bool `yaml:"detect-nsfw" flag:"detect-nsfw"`
|
||||
UploadNSFW bool `yaml:"upload-nsfw" flag:"upload-nsfw"`
|
||||
GeoCodingApi string `yaml:"geocoding-api" flag:"geocoding-api"`
|
||||
ThumbQuality int `yaml:"thumb-quality" flag:"thumb-quality"`
|
||||
ThumbSize int `yaml:"thumb-size" flag:"thumb-size"`
|
||||
ThumbLimit int `yaml:"thumb-limit" flag:"thumb-limit"`
|
||||
ThumbFilter string `yaml:"thumb-filter" flag:"thumb-filter"`
|
||||
DisableTensorFlow bool `yaml:"disable-tf" flag:"disable-tf"`
|
||||
DisableSettings bool `yaml:"disable-settings" flag:"disable-settings"`
|
||||
SipsBin string `yaml:"sips-bin" flag:"sips-bin"`
|
||||
DarktableBin string `yaml:"darktable-bin" flag:"darktable-bin"`
|
||||
ExifToolBin string `yaml:"exiftool-bin" flag:"exiftool-bin"`
|
||||
HeifConvertBin string `yaml:"heifconvert-bin" flag:"heifconvert-bin"`
|
||||
PIDFilename string `yaml:"pid-filename" flag:"pid-filename"`
|
||||
LogFilename string `yaml:"log-filename" flag:"log-filename"`
|
||||
DetachServer bool `yaml:"detach-server" flag:"detach-server"`
|
||||
DetectNSFW bool `yaml:"detect-nsfw" flag:"detect-nsfw"`
|
||||
UploadNSFW bool `yaml:"upload-nsfw" flag:"upload-nsfw"`
|
||||
GeoCodingApi string `yaml:"geocoding-api" flag:"geocoding-api"`
|
||||
JpegQuality int `yaml:"jpeg-quality" flag:"jpeg-quality"`
|
||||
ResampleSize int `yaml:"resample-size" flag:"resample-size"`
|
||||
ResampleLimit int `yaml:"resample-limit" flag:"resample-limit"`
|
||||
ResampleFilter string `yaml:"resample-filter" flag:"resample-filter"`
|
||||
ResampleUncached bool `yaml:"resample-uncached" flag:"resample-uncached"`
|
||||
DisableTensorFlow bool `yaml:"disable-tf" flag:"disable-tf"`
|
||||
DisableSettings bool `yaml:"disable-settings" flag:"disable-settings"`
|
||||
}
|
||||
|
||||
// NewParams creates a new configuration entity by using two methods:
|
||||
|
|
63
internal/config/resample.go
Normal file
63
internal/config/resample.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
)
|
||||
|
||||
// JpegQuality returns the thumbnail jpeg quality setting (25-100).
|
||||
func (c *Config) JpegQuality() int {
|
||||
if c.params.JpegQuality > 100 {
|
||||
return 100
|
||||
}
|
||||
|
||||
if c.params.JpegQuality < 25 {
|
||||
return 25
|
||||
}
|
||||
|
||||
return c.params.JpegQuality
|
||||
}
|
||||
|
||||
// Size returns the pre-rendered thumbnail size limit in pixels (720-3840).
|
||||
func (c *Config) ResampleSize() int {
|
||||
if c.params.ResampleSize > 3840 {
|
||||
return 3840
|
||||
}
|
||||
|
||||
if c.params.ResampleSize < 720 {
|
||||
return 720
|
||||
}
|
||||
|
||||
return c.params.ResampleSize
|
||||
}
|
||||
|
||||
// Limit returns the on-demand thumbnail size limit in pixels (720-3840).
|
||||
func (c *Config) ResampleLimit() int {
|
||||
if c.params.ResampleLimit > 3840 || c.params.ResampleLimit < 720 || c.ResampleSize() > c.params.ResampleLimit {
|
||||
return c.ResampleSize()
|
||||
}
|
||||
|
||||
return c.params.ResampleLimit
|
||||
}
|
||||
|
||||
// ResampleFilter returns the thumbnail resample filter (blackman, lanczos, cubic or linear).
|
||||
func (c *Config) ResampleFilter() thumb.ResampleFilter {
|
||||
switch strings.ToLower(c.params.ResampleFilter) {
|
||||
case "blackman":
|
||||
return thumb.ResampleBlackman
|
||||
case "lanczos":
|
||||
return thumb.ResampleLanczos
|
||||
case "cubic":
|
||||
return thumb.ResampleCubic
|
||||
case "linear":
|
||||
return thumb.ResampleLinear
|
||||
default:
|
||||
return thumb.ResampleCubic
|
||||
}
|
||||
}
|
||||
|
||||
// ResampleUncached returns true for on-demand rendering of uncached thumbnails (high memory and cpu usage).
|
||||
func (c *Config) ResampleUncached() bool {
|
||||
return c.params.ResampleUncached
|
||||
}
|
38
internal/config/runtime.go
Normal file
38
internal/config/runtime.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
)
|
||||
|
||||
// RuntimeInfo represents memory and cpu usage statistics.
|
||||
type RuntimeInfo struct {
|
||||
Cores int `json:"cores"`
|
||||
Routines int `json:"routines"`
|
||||
Memory struct {
|
||||
Used uint64 `json:"used"`
|
||||
Reserved uint64 `json:"reserved"`
|
||||
Info string `json:"info"`
|
||||
} `json:"memory"`
|
||||
}
|
||||
|
||||
// NewRuntimeInfo returns a new RuntimeInfo instance.
|
||||
func NewRuntimeInfo() (r RuntimeInfo) {
|
||||
r = RuntimeInfo{}
|
||||
r.Refresh()
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Refresh updates runtime info values like number of goroutines and memory usage.
|
||||
func (r *RuntimeInfo) Refresh() {
|
||||
r.Cores = runtime.NumCPU()
|
||||
r.Routines = runtime.NumGoroutine()
|
||||
var mem runtime.MemStats
|
||||
runtime.ReadMemStats(&mem)
|
||||
r.Memory.Used = mem.Alloc
|
||||
r.Memory.Reserved = mem.Sys
|
||||
r.Memory.Info = fmt.Sprintf("Used %s / Reserved %s", humanize.Bytes(r.Memory.Used), humanize.Bytes(r.Memory.Reserved))
|
||||
}
|
33
internal/config/runtime_test.go
Normal file
33
internal/config/runtime_test.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewRuntimeInfo(t *testing.T) {
|
||||
info := NewRuntimeInfo()
|
||||
|
||||
assert.LessOrEqual(t, 1, info.Cores)
|
||||
assert.LessOrEqual(t, 1, info.Routines)
|
||||
assert.LessOrEqual(t, uint64(1), info.Memory.Reserved)
|
||||
assert.LessOrEqual(t, uint64(1), info.Memory.Used)
|
||||
}
|
||||
|
||||
func TestRuntimeInfo_Refresh(t *testing.T) {
|
||||
info := NewRuntimeInfo()
|
||||
|
||||
assert.LessOrEqual(t, 1, info.Cores)
|
||||
assert.LessOrEqual(t, 1, info.Routines)
|
||||
assert.LessOrEqual(t, uint64(1), info.Memory.Reserved)
|
||||
assert.LessOrEqual(t, uint64(1), info.Memory.Used)
|
||||
|
||||
info.Refresh()
|
||||
|
||||
assert.LessOrEqual(t, 1, info.Cores)
|
||||
assert.LessOrEqual(t, 1, info.Routines)
|
||||
assert.LessOrEqual(t, uint64(1), info.Memory.Reserved)
|
||||
assert.LessOrEqual(t, uint64(1), info.Memory.Used)
|
||||
}
|
||||
|
|
@ -104,10 +104,10 @@ func NewTestConfig() *Config {
|
|||
|
||||
c.ResetDb(true)
|
||||
|
||||
thumb.JpegQuality = c.ThumbQuality()
|
||||
thumb.PreRenderSize = c.ThumbSize()
|
||||
thumb.MaxRenderSize = c.ThumbLimit()
|
||||
thumb.Filter = c.ThumbFilter()
|
||||
thumb.JpegQuality = c.JpegQuality()
|
||||
thumb.Size = c.ResampleSize()
|
||||
thumb.Limit = c.ResampleLimit()
|
||||
thumb.Filter = c.ResampleFilter()
|
||||
|
||||
return c
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
|
@ -192,6 +193,8 @@ func (imp *Import) Start(opt ImportOptions) {
|
|||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
// Cancel stops the current import operation.
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"runtime"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/karrick/godirwalk"
|
||||
|
@ -165,5 +166,7 @@ func (ind *Index) Start(options IndexOptions) map[string]bool {
|
|||
log.Error(err.Error())
|
||||
}
|
||||
|
||||
runtime.GC()
|
||||
|
||||
return done
|
||||
}
|
||||
|
|
|
@ -38,8 +38,6 @@ func ResampleOptions(opts ...ResampleOption) (method ResampleOption, filter imag
|
|||
method = ResampleFit
|
||||
case ResampleResize:
|
||||
method = ResampleResize
|
||||
default:
|
||||
panic(fmt.Errorf("not a valid resample option: %d", option))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,11 +73,11 @@ func Postfix(width, height int, opts ...ResampleOption) (result string) {
|
|||
}
|
||||
|
||||
func Filename(hash string, thumbPath string, width, height int, opts ...ResampleOption) (filename string, err error) {
|
||||
if width < 0 || width > MaxRenderSize {
|
||||
if InvalidSize(width) {
|
||||
return "", fmt.Errorf("resample: width exceeds limit (%d)", width)
|
||||
}
|
||||
|
||||
if height < 0 || height > MaxRenderSize {
|
||||
if InvalidSize(height) {
|
||||
return "", fmt.Errorf("resample: height exceeds limit (%d)", height)
|
||||
}
|
||||
|
||||
|
@ -103,7 +101,7 @@ func Filename(hash string, thumbPath string, width, height int, opts ...Resample
|
|||
return filename, nil
|
||||
}
|
||||
|
||||
func FromFile(imageFilename string, hash string, thumbPath string, width, height int, opts ...ResampleOption) (fileName string, err error) {
|
||||
func FromCache(imageFilename, hash, thumbPath string, width, height int, opts ...ResampleOption) (fileName string, err error) {
|
||||
if len(hash) < 4 {
|
||||
return "", fmt.Errorf("resample: file hash is empty or too short (%s)", txt.Quote(hash))
|
||||
}
|
||||
|
@ -115,7 +113,7 @@ func FromFile(imageFilename string, hash string, thumbPath string, width, height
|
|||
fileName, err = Filename(hash, thumbPath, width, height, opts...)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("resample: can't determine filename (%s)", err)
|
||||
log.Error(err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
@ -123,6 +121,23 @@ func FromFile(imageFilename string, hash string, thumbPath string, width, height
|
|||
return fileName, nil
|
||||
}
|
||||
|
||||
return "", ErrThumbNotCached
|
||||
}
|
||||
|
||||
func FromFile(imageFilename, hash, thumbPath string, width, height int, opts ...ResampleOption) (fileName string, err error) {
|
||||
if fileName, err := FromCache(imageFilename, hash, thumbPath, width, height, opts...); err == nil {
|
||||
return fileName, err
|
||||
} else if err != ErrThumbNotCached {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fileName, err = Filename(hash, thumbPath, width, height, opts...)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
img, err := imaging.Open(imageFilename, imaging.AutoOrientation(true))
|
||||
|
||||
if err != nil {
|
||||
|
@ -138,11 +153,11 @@ func FromFile(imageFilename string, hash string, thumbPath string, width, height
|
|||
}
|
||||
|
||||
func Create(img *image.Image, fileName string, width, height int, opts ...ResampleOption) (result *image.Image, err error) {
|
||||
if width < 0 || width > MaxRenderSize {
|
||||
if InvalidSize(width) {
|
||||
return img, fmt.Errorf("resample: width has an invalid value (%d)", width)
|
||||
}
|
||||
|
||||
if height < 0 || height > MaxRenderSize {
|
||||
if InvalidSize(height) {
|
||||
return img, fmt.Errorf("resample: height has an invalid value (%d)", height)
|
||||
}
|
||||
|
||||
|
|
|
@ -109,6 +109,35 @@ func TestFromFile(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestFromCache(t *testing.T) {
|
||||
t.Run("missing thumb", func(t *testing.T) {
|
||||
tile50 := Types["tile_50"]
|
||||
src := "testdata/example.jpg"
|
||||
|
||||
assert.FileExists(t, src)
|
||||
|
||||
fileName, err := FromCache(src, "193456789098765432", "testdata", tile50.Width, tile50.Height, tile50.Options...)
|
||||
|
||||
assert.Equal(t, "", fileName)
|
||||
|
||||
if err != ErrThumbNotCached {
|
||||
t.Fatal("ErrThumbNotCached expected")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("missing file", func(t *testing.T) {
|
||||
tile50 := Types["tile_50"]
|
||||
src := "testdata/example.xxx"
|
||||
|
||||
assert.NoFileExists(t, src)
|
||||
|
||||
fileName, err := FromCache(src, "193456789098765432", "testdata", tile50.Width, tile50.Height, tile50.Options...)
|
||||
|
||||
assert.Equal(t, "", fileName)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
t.Run("tile_500", func(t *testing.T) {
|
||||
tile500 := Types["tile_500"]
|
||||
|
|
9
internal/thumb/errors.go
Normal file
9
internal/thumb/errors.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package thumb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrThumbNotCached = errors.New("thumbnail not cached")
|
||||
)
|
|
@ -3,13 +3,25 @@ package thumb
|
|||
import "github.com/disintegration/imaging"
|
||||
|
||||
var (
|
||||
PreRenderSize = 3840
|
||||
MaxRenderSize = 3840
|
||||
Size = 3840
|
||||
Limit = 3840
|
||||
Filter = ResampleLanczos
|
||||
JpegQuality = 95
|
||||
JpegQualitySmall = 80
|
||||
Filter = ResampleLanczos
|
||||
)
|
||||
|
||||
func MaxSize() int {
|
||||
if Size > Limit {
|
||||
return Size
|
||||
}
|
||||
|
||||
return Limit
|
||||
}
|
||||
|
||||
func InvalidSize(size int) bool {
|
||||
return size < 0 || size > MaxSize()
|
||||
}
|
||||
|
||||
const (
|
||||
ResampleBlackman ResampleFilter = "blackman"
|
||||
ResampleLanczos ResampleFilter = "lanczos"
|
||||
|
@ -84,9 +96,9 @@ var DefaultTypes = []string{
|
|||
}
|
||||
|
||||
func (t Type) ExceedsLimit() bool {
|
||||
return t.Width > MaxRenderSize || t.Height > MaxRenderSize
|
||||
return t.Width > MaxSize() || t.Height > MaxSize()
|
||||
}
|
||||
|
||||
func (t Type) SkipPreRender() bool {
|
||||
return t.Width > PreRenderSize || t.Height > PreRenderSize
|
||||
return t.Width > Size || t.Height > Size
|
||||
}
|
||||
|
|
|
@ -7,8 +7,8 @@ import (
|
|||
)
|
||||
|
||||
func TestType_ExceedsLimit(t *testing.T) {
|
||||
PreRenderSize = 1024
|
||||
MaxRenderSize = 2048
|
||||
Size = 1024
|
||||
Limit = 2048
|
||||
|
||||
fit3840 := Types["fit_3840"]
|
||||
assert.True(t, fit3840.ExceedsLimit())
|
||||
|
@ -19,13 +19,13 @@ func TestType_ExceedsLimit(t *testing.T) {
|
|||
tile500 := Types["tile_500"]
|
||||
assert.False(t, tile500.ExceedsLimit())
|
||||
|
||||
PreRenderSize = 3840
|
||||
MaxRenderSize = 3840
|
||||
Size = 3840
|
||||
Limit = 3840
|
||||
}
|
||||
|
||||
func TestType_SkipPreRender(t *testing.T) {
|
||||
PreRenderSize = 1024
|
||||
MaxRenderSize = 2048
|
||||
Size = 1024
|
||||
Limit = 2048
|
||||
|
||||
fit3840 := Types["fit_3840"]
|
||||
assert.True(t, fit3840.SkipPreRender())
|
||||
|
@ -36,6 +36,6 @@ func TestType_SkipPreRender(t *testing.T) {
|
|||
tile500 := Types["tile_500"]
|
||||
assert.False(t, tile500.SkipPreRender())
|
||||
|
||||
PreRenderSize = 3840
|
||||
MaxRenderSize = 3840
|
||||
Size = 3840
|
||||
Limit = 3840
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue