mirror of
https://github.com/photoprism/photoprism.git
synced 2025-10-30 11:46:36 +08:00
People: Improve thumb size config and flag descriptions #22
This commit is contained in:
@@ -57,8 +57,8 @@ func RemoveFromFolderCache(rootName string) {
|
|||||||
func RemoveFromAlbumCoverCache(uid string) {
|
func RemoveFromAlbumCoverCache(uid string) {
|
||||||
cache := service.CoverCache()
|
cache := service.CoverCache()
|
||||||
|
|
||||||
for typeName := range thumb.Sizes {
|
for thumbName := range thumb.Sizes {
|
||||||
cacheKey := CacheKey(albumCover, uid, typeName)
|
cacheKey := CacheKey(albumCover, uid, string(thumbName))
|
||||||
|
|
||||||
cache.Delete(cacheKey)
|
cache.Delete(cacheKey)
|
||||||
|
|
||||||
|
|||||||
@@ -20,14 +20,16 @@ const (
|
|||||||
labelCover = "label-cover"
|
labelCover = "label-cover"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GET /api/v1/albums/:uid/t/:token/:type
|
// AlbumCover returns an album cover image.
|
||||||
|
//
|
||||||
|
// GET /api/v1/albums/:uid/t/:token/:size
|
||||||
//
|
//
|
||||||
// Parameters:
|
// Parameters:
|
||||||
// uid: string album uid
|
// uid: string album uid
|
||||||
// token: string security token (see config)
|
// token: string security token (see config)
|
||||||
// type: string thumb type, see photoprism.ThumbnailTypes
|
// size: string thumb type, see photoprism.ThumbnailTypes
|
||||||
func AlbumCover(router *gin.RouterGroup) {
|
func AlbumCover(router *gin.RouterGroup) {
|
||||||
router.GET("/albums/:uid/t/:token/:type", func(c *gin.Context) {
|
router.GET("/albums/:uid/t/:token/:size", func(c *gin.Context) {
|
||||||
if InvalidPreviewToken(c) {
|
if InvalidPreviewToken(c) {
|
||||||
c.Data(http.StatusForbidden, "image/svg+xml", albumIconSvg)
|
c.Data(http.StatusForbidden, "image/svg+xml", albumIconSvg)
|
||||||
return
|
return
|
||||||
@@ -35,19 +37,19 @@ func AlbumCover(router *gin.RouterGroup) {
|
|||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
conf := service.Config()
|
conf := service.Config()
|
||||||
typeName := c.Param("type")
|
thumbName := thumb.Name(c.Param("size"))
|
||||||
uid := c.Param("uid")
|
uid := c.Param("uid")
|
||||||
|
|
||||||
size, ok := thumb.Sizes[typeName]
|
size, ok := thumb.Sizes[thumbName]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Errorf("%s: invalid type %s", albumCover, typeName)
|
log.Errorf("%s: invalid size %s", albumCover, thumbName)
|
||||||
c.Data(http.StatusOK, "image/svg+xml", albumIconSvg)
|
c.Data(http.StatusOK, "image/svg+xml", albumIconSvg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cache := service.CoverCache()
|
cache := service.CoverCache()
|
||||||
cacheKey := CacheKey(albumCover, uid, typeName)
|
cacheKey := CacheKey(albumCover, uid, string(thumbName))
|
||||||
|
|
||||||
if cacheData, ok := cache.Get(cacheKey); ok {
|
if cacheData, ok := cache.Get(cacheKey); ok {
|
||||||
log.Debugf("api: cache hit for %s [%s]", cacheKey, time.Since(start))
|
log.Debugf("api: cache hit for %s [%s]", cacheKey, time.Since(start))
|
||||||
@@ -130,14 +132,16 @@ func AlbumCover(router *gin.RouterGroup) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /api/v1/labels/:uid/t/:token/:type
|
// LabelCover returns a label cover image.
|
||||||
|
//
|
||||||
|
// GET /api/v1/labels/:uid/t/:token/:size
|
||||||
//
|
//
|
||||||
// Parameters:
|
// Parameters:
|
||||||
// uid: string label uid
|
// uid: string label uid
|
||||||
// token: string security token (see config)
|
// token: string security token (see config)
|
||||||
// type: string thumb type, see photoprism.ThumbnailTypes
|
// size: string thumb type, see photoprism.ThumbnailTypes
|
||||||
func LabelCover(router *gin.RouterGroup) {
|
func LabelCover(router *gin.RouterGroup) {
|
||||||
router.GET("/labels/:uid/t/:token/:type", func(c *gin.Context) {
|
router.GET("/labels/:uid/t/:token/:size", func(c *gin.Context) {
|
||||||
if InvalidPreviewToken(c) {
|
if InvalidPreviewToken(c) {
|
||||||
c.Data(http.StatusForbidden, "image/svg+xml", labelIconSvg)
|
c.Data(http.StatusForbidden, "image/svg+xml", labelIconSvg)
|
||||||
return
|
return
|
||||||
@@ -145,19 +149,19 @@ func LabelCover(router *gin.RouterGroup) {
|
|||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
conf := service.Config()
|
conf := service.Config()
|
||||||
typeName := c.Param("type")
|
thumbName := thumb.Name(c.Param("size"))
|
||||||
uid := c.Param("uid")
|
uid := c.Param("uid")
|
||||||
|
|
||||||
size, ok := thumb.Sizes[typeName]
|
size, ok := thumb.Sizes[thumbName]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Errorf("%s: invalid type %s", labelCover, txt.Quote(typeName))
|
log.Errorf("%s: invalid size %s", labelCover, thumbName)
|
||||||
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
|
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cache := service.CoverCache()
|
cache := service.CoverCache()
|
||||||
cacheKey := CacheKey(labelCover, uid, typeName)
|
cacheKey := CacheKey(labelCover, uid, string(thumbName))
|
||||||
|
|
||||||
if cacheData, ok := cache.Get(cacheKey); ok {
|
if cacheData, ok := cache.Get(cacheKey); ok {
|
||||||
log.Debugf("api: cache hit for %s [%s]", cacheKey, time.Since(start))
|
log.Debugf("api: cache hit for %s [%s]", cacheKey, time.Since(start))
|
||||||
|
|||||||
@@ -18,14 +18,16 @@ const (
|
|||||||
folderCover = "folder-cover"
|
folderCover = "folder-cover"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GET /api/v1/folders/t/:hash/:token/:type
|
// FolderCover returns a folder cover image.
|
||||||
|
//
|
||||||
|
// GET /api/v1/folders/t/:hash/:token/:size
|
||||||
//
|
//
|
||||||
// Parameters:
|
// Parameters:
|
||||||
// uid: string folder uid
|
// uid: string folder uid
|
||||||
// token: string url security token, see config
|
// token: string url security token, see config
|
||||||
// type: string thumb type, see thumb.Sizes
|
// size: string thumb type, see thumb.Sizes
|
||||||
func GetFolderCover(router *gin.RouterGroup) {
|
func FolderCover(router *gin.RouterGroup) {
|
||||||
router.GET("/folders/t/:uid/:token/:type", func(c *gin.Context) {
|
router.GET("/folders/t/:uid/:token/:size", func(c *gin.Context) {
|
||||||
if InvalidPreviewToken(c) {
|
if InvalidPreviewToken(c) {
|
||||||
c.Data(http.StatusForbidden, "image/svg+xml", folderIconSvg)
|
c.Data(http.StatusForbidden, "image/svg+xml", folderIconSvg)
|
||||||
return
|
return
|
||||||
@@ -34,21 +36,21 @@ func GetFolderCover(router *gin.RouterGroup) {
|
|||||||
start := time.Now()
|
start := time.Now()
|
||||||
conf := service.Config()
|
conf := service.Config()
|
||||||
uid := c.Param("uid")
|
uid := c.Param("uid")
|
||||||
typeName := c.Param("type")
|
thumbName := thumb.Name(c.Param("size"))
|
||||||
download := c.Query("download") != ""
|
download := c.Query("download") != ""
|
||||||
|
|
||||||
size, ok := thumb.Sizes[typeName]
|
size, ok := thumb.Sizes[thumbName]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Errorf("folder: invalid thumb type %s", txt.Quote(typeName))
|
log.Errorf("%s: invalid size %s", folderCover, thumbName)
|
||||||
c.Data(http.StatusOK, "image/svg+xml", folderIconSvg)
|
c.Data(http.StatusOK, "image/svg+xml", folderIconSvg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if size.Uncached() && !conf.ThumbUncached() {
|
if size.Uncached() && !conf.ThumbUncached() {
|
||||||
typeName, size = thumb.Find(conf.ThumbSizePrecached())
|
thumbName, size = thumb.Find(conf.ThumbSizePrecached())
|
||||||
|
|
||||||
if typeName == "" {
|
if thumbName == "" {
|
||||||
log.Errorf("folder: invalid thumb size %d", conf.ThumbSizePrecached())
|
log.Errorf("folder: invalid thumb size %d", conf.ThumbSizePrecached())
|
||||||
c.Data(http.StatusOK, "image/svg+xml", folderIconSvg)
|
c.Data(http.StatusOK, "image/svg+xml", folderIconSvg)
|
||||||
return
|
return
|
||||||
@@ -56,7 +58,7 @@ func GetFolderCover(router *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cache := service.CoverCache()
|
cache := service.CoverCache()
|
||||||
cacheKey := CacheKey(folderCover, uid, typeName)
|
cacheKey := CacheKey(folderCover, uid, string(thumbName))
|
||||||
|
|
||||||
if cacheData, ok := cache.Get(cacheKey); ok {
|
if cacheData, ok := cache.Get(cacheKey); ok {
|
||||||
log.Debugf("api: cache hit for %s [%s]", cacheKey, time.Since(start))
|
log.Debugf("api: cache hit for %s [%s]", cacheKey, time.Since(start))
|
||||||
|
|||||||
@@ -10,28 +10,28 @@ import (
|
|||||||
func TestGetFolderCover(t *testing.T) {
|
func TestGetFolderCover(t *testing.T) {
|
||||||
t.Run("no cover yet", func(t *testing.T) {
|
t.Run("no cover yet", func(t *testing.T) {
|
||||||
app, router, conf := NewApiTest()
|
app, router, conf := NewApiTest()
|
||||||
GetFolderCover(router)
|
FolderCover(router)
|
||||||
r := PerformRequest(app, "GET", "/api/v1/folders/t/dqo63pn35k2d495z/"+conf.PreviewToken()+"/tile_500")
|
r := PerformRequest(app, "GET", "/api/v1/folders/t/dqo63pn35k2d495z/"+conf.PreviewToken()+"/tile_500")
|
||||||
assert.Equal(t, http.StatusOK, r.Code)
|
assert.Equal(t, http.StatusOK, r.Code)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("invalid thumb type", func(t *testing.T) {
|
t.Run("invalid thumb type", func(t *testing.T) {
|
||||||
app, router, conf := NewApiTest()
|
app, router, conf := NewApiTest()
|
||||||
GetFolderCover(router)
|
FolderCover(router)
|
||||||
r := PerformRequest(app, "GET", "/api/v1/folders/t/dqo63pn35k2d495z/"+conf.PreviewToken()+"/xxx")
|
r := PerformRequest(app, "GET", "/api/v1/folders/t/dqo63pn35k2d495z/"+conf.PreviewToken()+"/xxx")
|
||||||
assert.Equal(t, http.StatusOK, r.Code)
|
assert.Equal(t, http.StatusOK, r.Code)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("invalid token", func(t *testing.T) {
|
t.Run("invalid token", func(t *testing.T) {
|
||||||
app, router, _ := NewApiTest()
|
app, router, _ := NewApiTest()
|
||||||
GetFolderCover(router)
|
FolderCover(router)
|
||||||
r := PerformRequest(app, "GET", "/api/v1/folders/t/dqo63pn35k2d495z/xxx/tile_500")
|
r := PerformRequest(app, "GET", "/api/v1/folders/t/dqo63pn35k2d495z/xxx/tile_500")
|
||||||
assert.Equal(t, http.StatusForbidden, r.Code)
|
assert.Equal(t, http.StatusForbidden, r.Code)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("could not find original", func(t *testing.T) {
|
t.Run("could not find original", func(t *testing.T) {
|
||||||
app, router, conf := NewApiTest()
|
app, router, conf := NewApiTest()
|
||||||
GetFolderCover(router)
|
FolderCover(router)
|
||||||
r := PerformRequest(app, "GET", "/api/v1/folders/t/dqo63pn2f87f02oi/"+conf.PreviewToken()+"/fit_7680")
|
r := PerformRequest(app, "GET", "/api/v1/folders/t/dqo63pn2f87f02oi/"+conf.PreviewToken()+"/fit_7680")
|
||||||
assert.Equal(t, http.StatusOK, r.Code)
|
assert.Equal(t, http.StatusOK, r.Code)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -18,14 +18,14 @@ import (
|
|||||||
|
|
||||||
// GetThumb returns a thumbnail image matching the hash and type.
|
// GetThumb returns a thumbnail image matching the hash and type.
|
||||||
//
|
//
|
||||||
// GET /api/v1/t/:hash/:token/:type
|
// GET /api/v1/t/:hash/:token/:size
|
||||||
//
|
//
|
||||||
// Parameters:
|
// Parameters:
|
||||||
// hash: string sha1 file hash
|
// hash: string sha1 file hash
|
||||||
// token: string url security token, see config
|
// token: string url security token, see config
|
||||||
// type: string thumb type, see thumb.Sizes
|
// size: string thumb type, see thumb.Sizes
|
||||||
func GetThumb(router *gin.RouterGroup) {
|
func GetThumb(router *gin.RouterGroup) {
|
||||||
router.GET("/t/:hash/:token/:type", func(c *gin.Context) {
|
router.GET("/t/:hash/:token/:size", func(c *gin.Context) {
|
||||||
if InvalidPreviewToken(c) {
|
if InvalidPreviewToken(c) {
|
||||||
c.Data(http.StatusForbidden, "image/svg+xml", brokenIconSvg)
|
c.Data(http.StatusForbidden, "image/svg+xml", brokenIconSvg)
|
||||||
return
|
return
|
||||||
@@ -34,21 +34,21 @@ func GetThumb(router *gin.RouterGroup) {
|
|||||||
start := time.Now()
|
start := time.Now()
|
||||||
conf := service.Config()
|
conf := service.Config()
|
||||||
fileHash := c.Param("hash")
|
fileHash := c.Param("hash")
|
||||||
typeName := c.Param("type")
|
thumbName := thumb.Name(c.Param("size"))
|
||||||
download := c.Query("download") != ""
|
download := c.Query("download") != ""
|
||||||
|
|
||||||
size, ok := thumb.Sizes[typeName]
|
size, ok := thumb.Sizes[thumbName]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Errorf("thumbs: invalid type %s", txt.Quote(typeName))
|
log.Errorf("thumbs: invalid size %s", thumbName)
|
||||||
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
|
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if size.Uncached() && !conf.ThumbUncached() {
|
if size.Uncached() && !conf.ThumbUncached() {
|
||||||
typeName, size = thumb.Find(conf.ThumbSizePrecached())
|
thumbName, size = thumb.Find(conf.ThumbSizePrecached())
|
||||||
|
|
||||||
if typeName == "" {
|
if thumbName == "" {
|
||||||
log.Errorf("thumbs: invalid size %d", conf.ThumbSizePrecached())
|
log.Errorf("thumbs: invalid size %d", conf.ThumbSizePrecached())
|
||||||
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
|
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
|
||||||
return
|
return
|
||||||
@@ -56,7 +56,7 @@ func GetThumb(router *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cache := service.ThumbCache()
|
cache := service.ThumbCache()
|
||||||
cacheKey := CacheKey("thumbs", fileHash, typeName)
|
cacheKey := CacheKey("thumbs", fileHash, string(thumbName))
|
||||||
|
|
||||||
if cacheData, ok := cache.Get(cacheKey); ok {
|
if cacheData, ok := cache.Get(cacheKey); ok {
|
||||||
log.Debugf("api: cache hit for %s [%s]", cacheKey, time.Since(start))
|
log.Debugf("api: cache hit for %s [%s]", cacheKey, time.Since(start))
|
||||||
@@ -173,15 +173,15 @@ func GetThumb(router *gin.RouterGroup) {
|
|||||||
|
|
||||||
// GetThumbCrop returns a cropped thumbnail image matching the hash and type.
|
// GetThumbCrop returns a cropped thumbnail image matching the hash and type.
|
||||||
//
|
//
|
||||||
// GET /api/v1/t/:hash/:token/:type/:area
|
// GET /api/v1/t/:hash/:token/:size/:area
|
||||||
//
|
//
|
||||||
// Parameters:
|
// Parameters:
|
||||||
// hash: string sha1 file hash
|
// hash: string sha1 file hash
|
||||||
// token: string url security token, see config
|
// token: string url security token, see config
|
||||||
// type: string thumb type, see thumb.Sizes
|
// size: string thumb type, see thumb.Sizes
|
||||||
// area: string image area identifier, e.g. 022004010015
|
// area: string image area identifier, e.g. 022004010015
|
||||||
func GetThumbCrop(router *gin.RouterGroup) {
|
func GetThumbCrop(router *gin.RouterGroup) {
|
||||||
router.GET("/t/:hash/:token/:type/:area", func(c *gin.Context) {
|
router.GET("/t/:hash/:token/:size/:area", func(c *gin.Context) {
|
||||||
if InvalidPreviewToken(c) {
|
if InvalidPreviewToken(c) {
|
||||||
c.Data(http.StatusForbidden, "image/svg+xml", brokenIconSvg)
|
c.Data(http.StatusForbidden, "image/svg+xml", brokenIconSvg)
|
||||||
return
|
return
|
||||||
@@ -189,18 +189,18 @@ func GetThumbCrop(router *gin.RouterGroup) {
|
|||||||
|
|
||||||
conf := service.Config()
|
conf := service.Config()
|
||||||
fileHash := c.Param("hash")
|
fileHash := c.Param("hash")
|
||||||
typeName := c.Param("type")
|
thumbName := thumb.Name(c.Param("size"))
|
||||||
cropArea := c.Param("area")
|
cropArea := c.Param("area")
|
||||||
download := c.Query("download") != ""
|
download := c.Query("download") != ""
|
||||||
|
|
||||||
size, ok := thumb.Sizes[typeName]
|
size, ok := thumb.Sizes[thumbName]
|
||||||
|
|
||||||
if !ok || len(size.Options) < 1 {
|
if !ok || len(size.Options) < 1 {
|
||||||
log.Errorf("thumbs: invalid type %s", txt.Quote(typeName))
|
log.Errorf("thumbs: invalid size %s", thumbName)
|
||||||
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
|
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
|
||||||
return
|
return
|
||||||
} else if size.Options[0] != thumb.ResampleCrop {
|
} else if size.Options[0] != thumb.ResampleCrop {
|
||||||
log.Errorf("thumbs: invalid crop %s", txt.Quote(typeName))
|
log.Errorf("thumbs: invalid size %s", thumbName)
|
||||||
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
|
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -220,7 +220,7 @@ func GetThumbCrop(router *gin.RouterGroup) {
|
|||||||
AddThumbCacheHeader(c)
|
AddThumbCacheHeader(c)
|
||||||
|
|
||||||
if download {
|
if download {
|
||||||
c.FileAttachment(fileName, typeName+fs.JpegExt)
|
c.FileAttachment(fileName, thumbName.Jpeg())
|
||||||
} else {
|
} else {
|
||||||
c.File(fileName)
|
c.File(fileName)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import (
|
|||||||
"github.com/photoprism/photoprism/pkg/txt"
|
"github.com/photoprism/photoprism/pkg/txt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// SharePreview returns a link share preview image.
|
||||||
|
//
|
||||||
// GET /s/:token/:uid/preview
|
// GET /s/:token/:uid/preview
|
||||||
// TODO: Proof of concept, needs refactoring.
|
// TODO: Proof of concept, needs refactoring.
|
||||||
func SharePreview(router *gin.RouterGroup) {
|
func SharePreview(router *gin.RouterGroup) {
|
||||||
@@ -88,7 +90,7 @@ func SharePreview(router *gin.RouterGroup) {
|
|||||||
return
|
return
|
||||||
} else if count < 12 {
|
} else if count < 12 {
|
||||||
f := p[0]
|
f := p[0]
|
||||||
size, _ := thumb.Sizes["fit_720"]
|
size, _ := thumb.Sizes[thumb.Fit720]
|
||||||
|
|
||||||
fileName := photoprism.FileName(f.FileRoot, f.FileName)
|
fileName := photoprism.FileName(f.FileRoot, f.FileName)
|
||||||
|
|
||||||
@@ -117,7 +119,7 @@ func SharePreview(router *gin.RouterGroup) {
|
|||||||
y := 0
|
y := 0
|
||||||
|
|
||||||
preview := imaging.New(width, height, color.NRGBA{255, 255, 255, 255})
|
preview := imaging.New(width, height, color.NRGBA{255, 255, 255, 255})
|
||||||
size, _ := thumb.Sizes["tile_224"]
|
size, _ := thumb.Sizes[thumb.Tile224]
|
||||||
|
|
||||||
for _, f := range p {
|
for _, f := range p {
|
||||||
fileName := photoprism.FileName(f.FileRoot, f.FileName)
|
fileName := photoprism.FileName(f.FileRoot, f.FileName)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
var ResampleCommand = cli.Command{
|
var ResampleCommand = cli.Command{
|
||||||
Name: "resample",
|
Name: "resample",
|
||||||
Aliases: []string{"thumbs"},
|
Aliases: []string{"thumbs"},
|
||||||
Usage: "Pre-caches thumbnails to reduce memory and cpu usage",
|
Usage: "Pre-caches thumbnail images for improved performance",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "force, f",
|
Name: "force, f",
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ func init() {
|
|||||||
t := thumb.Sizes[name]
|
t := thumb.Sizes[name]
|
||||||
|
|
||||||
if t.Public {
|
if t.Public {
|
||||||
Thumbs = append(Thumbs, ThumbSize{Size: name, Use: t.Use, Width: t.Width, Height: t.Height})
|
Thumbs = append(Thumbs, ThumbSize{Size: string(name), Use: t.Use, Width: t.Width, Height: t.Height})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -380,25 +380,25 @@ var GlobalFlags = []cli.Flag{
|
|||||||
EnvVar: "PHOTOPRISM_PREVIEW_TOKEN",
|
EnvVar: "PHOTOPRISM_PREVIEW_TOKEN",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "thumb-filter, f",
|
Name: "thumb-filter",
|
||||||
Usage: "downscaling filter `NAME` (best to worst: blackman, lanczos, cubic, linear)",
|
Usage: "image downscaling `FILTER` (best to worst: blackman, lanczos, cubic, linear)",
|
||||||
Value: "lanczos",
|
Value: "lanczos",
|
||||||
EnvVar: "PHOTOPRISM_THUMB_FILTER",
|
EnvVar: "PHOTOPRISM_THUMB_FILTER",
|
||||||
},
|
},
|
||||||
cli.IntFlag{
|
cli.IntFlag{
|
||||||
Name: "thumb-size, s",
|
Name: "thumb-size, s",
|
||||||
Usage: "pre-cached thumbnail size in `PIXELS` (720-7680)",
|
Usage: "max pre-cached thumbnail size in `PIXELS` (720-7680)",
|
||||||
Value: 2048,
|
Value: 2048,
|
||||||
EnvVar: "PHOTOPRISM_THUMB_SIZE",
|
EnvVar: "PHOTOPRISM_THUMB_SIZE",
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "thumb-uncached, u",
|
Name: "thumb-uncached, u",
|
||||||
Usage: "enable dynamic thumbnail rendering (high memory and cpu usage)",
|
Usage: "enable on-demand thumbnail generation (high memory and cpu usage)",
|
||||||
EnvVar: "PHOTOPRISM_THUMB_UNCACHED",
|
EnvVar: "PHOTOPRISM_THUMB_UNCACHED",
|
||||||
},
|
},
|
||||||
cli.IntFlag{
|
cli.IntFlag{
|
||||||
Name: "thumb-size-uncached, x",
|
Name: "thumb-size-uncached, x",
|
||||||
Usage: "dynamic rendering size limit in `PIXELS` (720-7680)",
|
Usage: "on-demand thumbnail generation size limit in `PIXELS` (720-7680)",
|
||||||
Value: 7680,
|
Value: 7680,
|
||||||
EnvVar: "PHOTOPRISM_THUMB_SIZE_UNCACHED",
|
EnvVar: "PHOTOPRISM_THUMB_SIZE_UNCACHED",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/lucasb-eyer/go-colorful"
|
"github.com/lucasb-eyer/go-colorful"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/thumb"
|
||||||
"github.com/photoprism/photoprism/pkg/colors"
|
"github.com/photoprism/photoprism/pkg/colors"
|
||||||
"github.com/photoprism/photoprism/pkg/txt"
|
"github.com/photoprism/photoprism/pkg/txt"
|
||||||
)
|
)
|
||||||
@@ -16,7 +18,7 @@ func (m *MediaFile) Colors(thumbPath string) (perception colors.ColorPerception,
|
|||||||
return perception, fmt.Errorf("%s is not a jpeg", txt.Quote(m.BaseName()))
|
return perception, fmt.Errorf("%s is not a jpeg", txt.Quote(m.BaseName()))
|
||||||
}
|
}
|
||||||
|
|
||||||
img, err := m.Resample(thumbPath, "colors")
|
img, err := m.Resample(thumbPath, thumb.Colors)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("colors: %s in %s (resample)", err, txt.Quote(m.BaseName()))
|
log.Debugf("colors: %s in %s (resample)", err, txt.Quote(m.BaseName()))
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/classify"
|
"github.com/photoprism/photoprism/internal/classify"
|
||||||
|
"github.com/photoprism/photoprism/internal/thumb"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/pkg/txt"
|
"github.com/photoprism/photoprism/pkg/txt"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -12,18 +14,18 @@ import (
|
|||||||
func (ind *Index) classifyImage(jpeg *MediaFile) (results classify.Labels) {
|
func (ind *Index) classifyImage(jpeg *MediaFile) (results classify.Labels) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
var thumbs []string
|
var sizes []thumb.Name
|
||||||
|
|
||||||
if jpeg.AspectRatio() == 1 {
|
if jpeg.AspectRatio() == 1 {
|
||||||
thumbs = []string{"tile_224"}
|
sizes = []thumb.Name{thumb.Tile224}
|
||||||
} else {
|
} else {
|
||||||
thumbs = []string{"tile_224", "left_224", "right_224"}
|
sizes = []thumb.Name{thumb.Tile224, thumb.Left224, thumb.Right224}
|
||||||
}
|
}
|
||||||
|
|
||||||
var labels classify.Labels
|
var labels classify.Labels
|
||||||
|
|
||||||
for _, thumb := range thumbs {
|
for _, size := range sizes {
|
||||||
filename, err := jpeg.Thumbnail(Config().ThumbPath(), thumb)
|
filename, err := jpeg.Thumbnail(Config().ThumbPath(), size)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("%s in %s", err, txt.Quote(jpeg.BaseName()))
|
log.Debugf("%s in %s", err, txt.Quote(jpeg.BaseName()))
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/face"
|
"github.com/photoprism/photoprism/internal/face"
|
||||||
|
"github.com/photoprism/photoprism/internal/thumb"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/pkg/txt"
|
"github.com/photoprism/photoprism/pkg/txt"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,15 +16,15 @@ func (ind *Index) detectFaces(jpeg *MediaFile) face.Faces {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var minSize int
|
var minSize int
|
||||||
var thumbSize string
|
var thumbSize thumb.Name
|
||||||
|
|
||||||
// Select best thumbnail depending on configured size.
|
// Select best thumbnail depending on configured size.
|
||||||
if Config().ThumbSizePrecached() < 1280 {
|
if Config().ThumbSizePrecached() < 1280 {
|
||||||
minSize = 30
|
minSize = 30
|
||||||
thumbSize = "fit_720"
|
thumbSize = thumb.Fit720
|
||||||
} else {
|
} else {
|
||||||
minSize = 40
|
minSize = 40
|
||||||
thumbSize = "fit_1280"
|
thumbSize = thumb.Fit1280
|
||||||
}
|
}
|
||||||
|
|
||||||
thumbName, err := jpeg.Thumbnail(Config().ThumbPath(), thumbSize)
|
thumbName, err := jpeg.Thumbnail(Config().ThumbPath(), thumbSize)
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/meta"
|
"github.com/photoprism/photoprism/internal/meta"
|
||||||
"github.com/photoprism/photoprism/internal/nsfw"
|
"github.com/photoprism/photoprism/internal/nsfw"
|
||||||
"github.com/photoprism/photoprism/internal/query"
|
"github.com/photoprism/photoprism/internal/query"
|
||||||
|
"github.com/photoprism/photoprism/internal/thumb"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/pkg/fs"
|
"github.com/photoprism/photoprism/pkg/fs"
|
||||||
"github.com/photoprism/photoprism/pkg/txt"
|
"github.com/photoprism/photoprism/pkg/txt"
|
||||||
)
|
)
|
||||||
@@ -751,7 +753,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||||||
|
|
||||||
// NSFW returns true if media file might be offensive and detection is enabled.
|
// NSFW returns true if media file might be offensive and detection is enabled.
|
||||||
func (ind *Index) NSFW(jpeg *MediaFile) bool {
|
func (ind *Index) NSFW(jpeg *MediaFile) bool {
|
||||||
filename, err := jpeg.Thumbnail(Config().ThumbPath(), "fit_720")
|
filename, err := jpeg.Thumbnail(Config().ThumbPath(), thumb.Fit720)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
|
|
||||||
"github.com/disintegration/imaging"
|
"github.com/disintegration/imaging"
|
||||||
"github.com/djherbis/times"
|
"github.com/djherbis/times"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/entity"
|
"github.com/photoprism/photoprism/internal/entity"
|
||||||
"github.com/photoprism/photoprism/internal/meta"
|
"github.com/photoprism/photoprism/internal/meta"
|
||||||
"github.com/photoprism/photoprism/internal/thumb"
|
"github.com/photoprism/photoprism/internal/thumb"
|
||||||
@@ -887,12 +888,12 @@ func (m *MediaFile) Orientation() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Thumbnail returns a thumbnail filename.
|
// Thumbnail returns a thumbnail filename.
|
||||||
func (m *MediaFile) Thumbnail(path string, typeName string) (filename string, err error) {
|
func (m *MediaFile) Thumbnail(path string, sizeName thumb.Name) (filename string, err error) {
|
||||||
size, ok := thumb.Sizes[typeName]
|
size, ok := thumb.Sizes[sizeName]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Errorf("media: invalid type %s", typeName)
|
log.Errorf("media: invalid type %s", sizeName)
|
||||||
return "", fmt.Errorf("media: invalid type %s", typeName)
|
return "", fmt.Errorf("media: invalid type %s", sizeName)
|
||||||
}
|
}
|
||||||
|
|
||||||
thumbnail, err := thumb.FromFile(m.FileName(), m.Hash(), path, size.Width, size.Height, m.Orientation(), size.Options...)
|
thumbnail, err := thumb.FromFile(m.FileName(), m.Hash(), path, size.Width, size.Height, m.Orientation(), size.Options...)
|
||||||
@@ -907,8 +908,8 @@ func (m *MediaFile) Thumbnail(path string, typeName string) (filename string, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Resample returns a resampled image of the file.
|
// Resample returns a resampled image of the file.
|
||||||
func (m *MediaFile) Resample(path string, typeName string) (img image.Image, err error) {
|
func (m *MediaFile) Resample(path string, sizeName thumb.Name) (img image.Image, err error) {
|
||||||
filename, err := m.Thumbnail(path, typeName)
|
filename, err := m.Thumbnail(path, sizeName)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -937,7 +938,7 @@ func (m *MediaFile) ResampleDefault(thumbPath string, force bool) (err error) {
|
|||||||
|
|
||||||
var originalImg image.Image
|
var originalImg image.Image
|
||||||
var sourceImg image.Image
|
var sourceImg image.Image
|
||||||
var sourceImgType string
|
var sourceName thumb.Name
|
||||||
|
|
||||||
for _, name := range thumb.DefaultSizes {
|
for _, name := range thumb.DefaultSizes {
|
||||||
size := thumb.Sizes[name]
|
size := thumb.Sizes[name]
|
||||||
@@ -948,7 +949,7 @@ func (m *MediaFile) ResampleDefault(thumbPath string, force bool) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if fileName, err := thumb.FileName(hash, thumbPath, size.Width, size.Height, size.Options...); err != nil {
|
if fileName, err := thumb.FileName(hash, thumbPath, size.Width, size.Height, size.Options...); err != nil {
|
||||||
log.Errorf("media: failed creating %s (%s)", txt.Quote(name), err)
|
log.Errorf("media: failed creating %s (%s)", txt.Quote(string(name)), err)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
@@ -970,18 +971,18 @@ func (m *MediaFile) ResampleDefault(thumbPath string, force bool) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if size.Source != "" {
|
if size.Source != "" {
|
||||||
if size.Source == sourceImgType && sourceImg != nil {
|
if size.Source == sourceName && sourceImg != nil {
|
||||||
_, err = thumb.Create(sourceImg, fileName, size.Width, size.Height, size.Options...)
|
_, err = thumb.Create(sourceImg, fileName, size.Width, size.Height, size.Options...)
|
||||||
} else {
|
} else {
|
||||||
_, err = thumb.Create(originalImg, fileName, size.Width, size.Height, size.Options...)
|
_, err = thumb.Create(originalImg, fileName, size.Width, size.Height, size.Options...)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sourceImg, err = thumb.Create(originalImg, fileName, size.Width, size.Height, size.Options...)
|
sourceImg, err = thumb.Create(originalImg, fileName, size.Width, size.Height, size.Options...)
|
||||||
sourceImgType = name
|
sourceName = name
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("media: failed creating %s (%s)", txt.Quote(name), err)
|
log.Errorf("media: failed creating %s (%s)", txt.Quote(string(name)), err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/entity"
|
"github.com/photoprism/photoprism/internal/entity"
|
||||||
"github.com/photoprism/photoprism/internal/thumb"
|
"github.com/photoprism/photoprism/internal/thumb"
|
||||||
"github.com/photoprism/photoprism/pkg/fs"
|
"github.com/photoprism/photoprism/pkg/fs"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1822,7 +1823,7 @@ func TestMediaFile_Resample(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
thumbnail, err := image.Resample(thumbsPath, "tile_500")
|
thumbnail, err := image.Resample(thumbsPath, thumb.Tile500)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -1873,7 +1874,7 @@ func TestMediaFile_RenderDefaultThumbs(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
thumbFilename, err := thumb.FileName(m.Hash(), thumbsPath, thumb.Sizes["tile_50"].Width, thumb.Sizes["tile_50"].Height, thumb.Sizes["tile_50"].Options...)
|
thumbFilename, err := thumb.FileName(m.Hash(), thumbsPath, thumb.Sizes[thumb.Tile50].Width, thumb.Sizes[thumb.Tile50].Height, thumb.Sizes[thumb.Tile50].Options...)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|||||||
@@ -87,23 +87,6 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
|
|||||||
api.PhotoPrimary(v1)
|
api.PhotoPrimary(v1)
|
||||||
api.PhotoUnstack(v1)
|
api.PhotoUnstack(v1)
|
||||||
|
|
||||||
api.GetSubjects(v1)
|
|
||||||
api.GetSubject(v1)
|
|
||||||
|
|
||||||
api.GetLabels(v1)
|
|
||||||
api.UpdateLabel(v1)
|
|
||||||
api.GetLabelLinks(v1)
|
|
||||||
api.CreateLabelLink(v1)
|
|
||||||
api.UpdateLabelLink(v1)
|
|
||||||
api.DeleteLabelLink(v1)
|
|
||||||
api.LikeLabel(v1)
|
|
||||||
api.DislikeLabel(v1)
|
|
||||||
api.LabelCover(v1)
|
|
||||||
|
|
||||||
api.GetFoldersOriginals(v1)
|
|
||||||
api.GetFoldersImport(v1)
|
|
||||||
api.GetFolderCover(v1)
|
|
||||||
|
|
||||||
api.Upload(v1)
|
api.Upload(v1)
|
||||||
api.StartImport(v1)
|
api.StartImport(v1)
|
||||||
api.CancelImport(v1)
|
api.CancelImport(v1)
|
||||||
@@ -118,6 +101,24 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
|
|||||||
api.BatchAlbumsDelete(v1)
|
api.BatchAlbumsDelete(v1)
|
||||||
api.BatchLabelsDelete(v1)
|
api.BatchLabelsDelete(v1)
|
||||||
|
|
||||||
|
api.GetSubjects(v1)
|
||||||
|
api.GetSubject(v1)
|
||||||
|
|
||||||
|
api.LabelCover(v1)
|
||||||
|
api.GetLabels(v1)
|
||||||
|
api.UpdateLabel(v1)
|
||||||
|
api.GetLabelLinks(v1)
|
||||||
|
api.CreateLabelLink(v1)
|
||||||
|
api.UpdateLabelLink(v1)
|
||||||
|
api.DeleteLabelLink(v1)
|
||||||
|
api.LikeLabel(v1)
|
||||||
|
api.DislikeLabel(v1)
|
||||||
|
|
||||||
|
api.FolderCover(v1)
|
||||||
|
api.GetFoldersOriginals(v1)
|
||||||
|
api.GetFoldersImport(v1)
|
||||||
|
|
||||||
|
api.AlbumCover(v1)
|
||||||
api.GetAlbum(v1)
|
api.GetAlbum(v1)
|
||||||
api.CreateAlbum(v1)
|
api.CreateAlbum(v1)
|
||||||
api.UpdateAlbum(v1)
|
api.UpdateAlbum(v1)
|
||||||
@@ -130,7 +131,6 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
|
|||||||
api.DeleteAlbumLink(v1)
|
api.DeleteAlbumLink(v1)
|
||||||
api.LikeAlbum(v1)
|
api.LikeAlbum(v1)
|
||||||
api.DislikeAlbum(v1)
|
api.DislikeAlbum(v1)
|
||||||
api.AlbumCover(v1)
|
|
||||||
api.CloneAlbums(v1)
|
api.CloneAlbums(v1)
|
||||||
api.AddPhotosToAlbum(v1)
|
api.AddPhotosToAlbum(v1)
|
||||||
api.RemovePhotosFromAlbum(v1)
|
api.RemovePhotosFromAlbum(v1)
|
||||||
|
|||||||
@@ -15,57 +15,6 @@ import (
|
|||||||
"github.com/disintegration/imaging"
|
"github.com/disintegration/imaging"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ResampleOptions extracts filter, format, and method from resample options.
|
|
||||||
func ResampleOptions(opts ...ResampleOption) (method ResampleOption, filter imaging.ResampleFilter, format fs.FileFormat) {
|
|
||||||
method = ResampleFit
|
|
||||||
filter = imaging.Lanczos
|
|
||||||
format = fs.FormatJpeg
|
|
||||||
|
|
||||||
for _, option := range opts {
|
|
||||||
switch option {
|
|
||||||
case ResamplePng:
|
|
||||||
format = fs.FormatPng
|
|
||||||
case ResampleNearestNeighbor:
|
|
||||||
filter = imaging.NearestNeighbor
|
|
||||||
case ResampleDefault:
|
|
||||||
filter = Filter.Imaging()
|
|
||||||
case ResampleFillTopLeft:
|
|
||||||
method = ResampleFillTopLeft
|
|
||||||
case ResampleFillCenter:
|
|
||||||
method = ResampleFillCenter
|
|
||||||
case ResampleFillBottomRight:
|
|
||||||
method = ResampleFillBottomRight
|
|
||||||
case ResampleFit:
|
|
||||||
method = ResampleFit
|
|
||||||
case ResampleResize:
|
|
||||||
method = ResampleResize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return method, filter, format
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resample downscales an image and returns it.
|
|
||||||
func Resample(img image.Image, width, height int, opts ...ResampleOption) image.Image {
|
|
||||||
var resImg image.Image
|
|
||||||
|
|
||||||
method, filter, _ := ResampleOptions(opts...)
|
|
||||||
|
|
||||||
if method == ResampleFit {
|
|
||||||
resImg = imaging.Fit(img, width, height, filter)
|
|
||||||
} else if method == ResampleFillCenter {
|
|
||||||
resImg = imaging.Fill(img, width, height, imaging.Center, filter)
|
|
||||||
} else if method == ResampleFillTopLeft {
|
|
||||||
resImg = imaging.Fill(img, width, height, imaging.TopLeft, filter)
|
|
||||||
} else if method == ResampleFillBottomRight {
|
|
||||||
resImg = imaging.Fill(img, width, height, imaging.BottomRight, filter)
|
|
||||||
} else if method == ResampleResize {
|
|
||||||
resImg = imaging.Resize(img, width, height, filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resImg
|
|
||||||
}
|
|
||||||
|
|
||||||
// Suffix returns the thumb cache file suffix.
|
// Suffix returns the thumb cache file suffix.
|
||||||
func Suffix(width, height int, opts ...ResampleOption) (result string) {
|
func Suffix(width, height int, opts ...ResampleOption) (result string) {
|
||||||
method, _, format := ResampleOptions(opts...)
|
method, _, format := ResampleOptions(opts...)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func TestResampleOptions(t *testing.T) {
|
|||||||
|
|
||||||
func TestResample(t *testing.T) {
|
func TestResample(t *testing.T) {
|
||||||
t.Run("tile50 options", func(t *testing.T) {
|
t.Run("tile50 options", func(t *testing.T) {
|
||||||
tile50 := Sizes["tile_50"]
|
tile50 := Sizes[Tile50]
|
||||||
|
|
||||||
src := "testdata/example.jpg"
|
src := "testdata/example.jpg"
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ func TestResample(t *testing.T) {
|
|||||||
assert.Equal(t, 50, boundsNew.Max.Y)
|
assert.Equal(t, 50, boundsNew.Max.Y)
|
||||||
})
|
})
|
||||||
t.Run("left_224 options", func(t *testing.T) {
|
t.Run("left_224 options", func(t *testing.T) {
|
||||||
left224 := Sizes["left_224"]
|
left224 := Sizes[Left224]
|
||||||
|
|
||||||
src := "testdata/example.jpg"
|
src := "testdata/example.jpg"
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ func TestResample(t *testing.T) {
|
|||||||
assert.Equal(t, 224, boundsNew.Max.Y)
|
assert.Equal(t, 224, boundsNew.Max.Y)
|
||||||
})
|
})
|
||||||
t.Run("right_224 options", func(t *testing.T) {
|
t.Run("right_224 options", func(t *testing.T) {
|
||||||
right224 := Sizes["right_224"]
|
right224 := Sizes[Right224]
|
||||||
|
|
||||||
src := "testdata/example.jpg"
|
src := "testdata/example.jpg"
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ func TestResample(t *testing.T) {
|
|||||||
assert.Equal(t, 224, boundsNew.Max.Y)
|
assert.Equal(t, 224, boundsNew.Max.Y)
|
||||||
})
|
})
|
||||||
t.Run("fit_1280 options", func(t *testing.T) {
|
t.Run("fit_1280 options", func(t *testing.T) {
|
||||||
fit1280 := Sizes["fit_1280"]
|
fit1280 := Sizes[Fit1280]
|
||||||
|
|
||||||
src := "testdata/example.jpg"
|
src := "testdata/example.jpg"
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ func TestResample(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSuffix(t *testing.T) {
|
func TestSuffix(t *testing.T) {
|
||||||
tile50 := Sizes["tile_50"]
|
tile50 := Sizes[Tile50]
|
||||||
|
|
||||||
result := Suffix(tile50.Width, tile50.Height, tile50.Options...)
|
result := Suffix(tile50.Width, tile50.Height, tile50.Options...)
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ func TestSuffix(t *testing.T) {
|
|||||||
|
|
||||||
func TestFileName(t *testing.T) {
|
func TestFileName(t *testing.T) {
|
||||||
t.Run("colors", func(t *testing.T) {
|
t.Run("colors", func(t *testing.T) {
|
||||||
colorThumb := Sizes["colors"]
|
colorThumb := Sizes[Colors]
|
||||||
|
|
||||||
result, err := FileName("123456789098765432", "testdata", colorThumb.Width, colorThumb.Height, colorThumb.Options...)
|
result, err := FileName("123456789098765432", "testdata", colorThumb.Width, colorThumb.Height, colorThumb.Options...)
|
||||||
|
|
||||||
@@ -158,7 +158,7 @@ func TestFileName(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("fit_720", func(t *testing.T) {
|
t.Run("fit_720", func(t *testing.T) {
|
||||||
fit720 := Sizes["fit_720"]
|
fit720 := Sizes[Fit720]
|
||||||
|
|
||||||
result, err := FileName("123456789098765432", "testdata", fit720.Width, fit720.Height, fit720.Options...)
|
result, err := FileName("123456789098765432", "testdata", fit720.Width, fit720.Height, fit720.Options...)
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ func TestFileName(t *testing.T) {
|
|||||||
assert.Equal(t, "testdata/1/2/3/123456789098765432_720x720_fit.jpg", result)
|
assert.Equal(t, "testdata/1/2/3/123456789098765432_720x720_fit.jpg", result)
|
||||||
})
|
})
|
||||||
t.Run("invalid width", func(t *testing.T) {
|
t.Run("invalid width", func(t *testing.T) {
|
||||||
colorThumb := Sizes["colors"]
|
colorThumb := Sizes[Colors]
|
||||||
|
|
||||||
result, err := FileName("123456789098765432", "testdata", -2, colorThumb.Height, colorThumb.Options...)
|
result, err := FileName("123456789098765432", "testdata", -2, colorThumb.Height, colorThumb.Options...)
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ func TestFileName(t *testing.T) {
|
|||||||
assert.Empty(t, result)
|
assert.Empty(t, result)
|
||||||
})
|
})
|
||||||
t.Run("invalid height", func(t *testing.T) {
|
t.Run("invalid height", func(t *testing.T) {
|
||||||
colorThumb := Sizes["colors"]
|
colorThumb := Sizes[Colors]
|
||||||
|
|
||||||
result, err := FileName("123456789098765432", "testdata", colorThumb.Width, -3, colorThumb.Options...)
|
result, err := FileName("123456789098765432", "testdata", colorThumb.Width, -3, colorThumb.Options...)
|
||||||
|
|
||||||
@@ -191,7 +191,7 @@ func TestFileName(t *testing.T) {
|
|||||||
assert.Empty(t, result)
|
assert.Empty(t, result)
|
||||||
})
|
})
|
||||||
t.Run("invalid hash", func(t *testing.T) {
|
t.Run("invalid hash", func(t *testing.T) {
|
||||||
colorThumb := Sizes["colors"]
|
colorThumb := Sizes[Colors]
|
||||||
|
|
||||||
result, err := FileName("12", "testdata", colorThumb.Width, colorThumb.Height, colorThumb.Options...)
|
result, err := FileName("12", "testdata", colorThumb.Width, colorThumb.Height, colorThumb.Options...)
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ func TestFileName(t *testing.T) {
|
|||||||
assert.Empty(t, result)
|
assert.Empty(t, result)
|
||||||
})
|
})
|
||||||
t.Run("invalid thumb path", func(t *testing.T) {
|
t.Run("invalid thumb path", func(t *testing.T) {
|
||||||
colorThumb := Sizes["colors"]
|
colorThumb := Sizes[Colors]
|
||||||
|
|
||||||
result, err := FileName("123456789098765432", "", colorThumb.Width, colorThumb.Height, colorThumb.Options...)
|
result, err := FileName("123456789098765432", "", colorThumb.Width, colorThumb.Height, colorThumb.Options...)
|
||||||
|
|
||||||
@@ -216,7 +216,7 @@ func TestFileName(t *testing.T) {
|
|||||||
|
|
||||||
func TestFromFile(t *testing.T) {
|
func TestFromFile(t *testing.T) {
|
||||||
t.Run("colors", func(t *testing.T) {
|
t.Run("colors", func(t *testing.T) {
|
||||||
colorThumb := Sizes["colors"]
|
colorThumb := Sizes[Colors]
|
||||||
src := "testdata/example.gif"
|
src := "testdata/example.gif"
|
||||||
dst := "testdata/1/2/3/123456789098765432_3x3_resize.png"
|
dst := "testdata/1/2/3/123456789098765432_3x3_resize.png"
|
||||||
|
|
||||||
@@ -234,7 +234,7 @@ func TestFromFile(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("orientation >1 ", func(t *testing.T) {
|
t.Run("orientation >1 ", func(t *testing.T) {
|
||||||
colorThumb := Sizes["colors"]
|
colorThumb := Sizes[Colors]
|
||||||
src := "testdata/example.gif"
|
src := "testdata/example.gif"
|
||||||
dst := "testdata/1/2/3/123456789098765432_3x3_resize.png"
|
dst := "testdata/1/2/3/123456789098765432_3x3_resize.png"
|
||||||
|
|
||||||
@@ -252,7 +252,7 @@ func TestFromFile(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("missing file", func(t *testing.T) {
|
t.Run("missing file", func(t *testing.T) {
|
||||||
colorThumb := Sizes["colors"]
|
colorThumb := Sizes[Colors]
|
||||||
src := "testdata/example.xxx"
|
src := "testdata/example.xxx"
|
||||||
|
|
||||||
assert.NoFileExists(t, src)
|
assert.NoFileExists(t, src)
|
||||||
@@ -263,7 +263,7 @@ func TestFromFile(t *testing.T) {
|
|||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
})
|
})
|
||||||
t.Run("empty filename", func(t *testing.T) {
|
t.Run("empty filename", func(t *testing.T) {
|
||||||
colorThumb := Sizes["colors"]
|
colorThumb := Sizes[Colors]
|
||||||
|
|
||||||
fileName, err := FromFile("", "193456789098765432", "testdata", colorThumb.Width, colorThumb.Height, OrientationNormal, colorThumb.Options...)
|
fileName, err := FromFile("", "193456789098765432", "testdata", colorThumb.Width, colorThumb.Height, OrientationNormal, colorThumb.Options...)
|
||||||
|
|
||||||
@@ -277,7 +277,7 @@ func TestFromFile(t *testing.T) {
|
|||||||
|
|
||||||
func TestFromCache(t *testing.T) {
|
func TestFromCache(t *testing.T) {
|
||||||
t.Run("missing thumb", func(t *testing.T) {
|
t.Run("missing thumb", func(t *testing.T) {
|
||||||
tile50 := Sizes["tile_50"]
|
tile50 := Sizes[Tile50]
|
||||||
src := "testdata/example.jpg"
|
src := "testdata/example.jpg"
|
||||||
|
|
||||||
assert.FileExists(t, src)
|
assert.FileExists(t, src)
|
||||||
@@ -292,7 +292,7 @@ func TestFromCache(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("missing file", func(t *testing.T) {
|
t.Run("missing file", func(t *testing.T) {
|
||||||
tile50 := Sizes["tile_50"]
|
tile50 := Sizes[Tile50]
|
||||||
src := "testdata/example.xxx"
|
src := "testdata/example.xxx"
|
||||||
|
|
||||||
assert.NoFileExists(t, src)
|
assert.NoFileExists(t, src)
|
||||||
@@ -303,7 +303,7 @@ func TestFromCache(t *testing.T) {
|
|||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
})
|
})
|
||||||
t.Run("invalid hash", func(t *testing.T) {
|
t.Run("invalid hash", func(t *testing.T) {
|
||||||
tile50 := Sizes["tile_50"]
|
tile50 := Sizes[Tile50]
|
||||||
src := "testdata/example.jpg"
|
src := "testdata/example.jpg"
|
||||||
|
|
||||||
assert.FileExists(t, src)
|
assert.FileExists(t, src)
|
||||||
@@ -317,7 +317,7 @@ func TestFromCache(t *testing.T) {
|
|||||||
assert.Empty(t, fileName)
|
assert.Empty(t, fileName)
|
||||||
})
|
})
|
||||||
t.Run("empty filename", func(t *testing.T) {
|
t.Run("empty filename", func(t *testing.T) {
|
||||||
tile50 := Sizes["tile_50"]
|
tile50 := Sizes[Tile50]
|
||||||
|
|
||||||
fileName, err := FromCache("", "193456789098765432", "testdata", tile50.Width, tile50.Height, tile50.Options...)
|
fileName, err := FromCache("", "193456789098765432", "testdata", tile50.Width, tile50.Height, tile50.Options...)
|
||||||
|
|
||||||
@@ -331,7 +331,7 @@ func TestFromCache(t *testing.T) {
|
|||||||
|
|
||||||
func TestCreate(t *testing.T) {
|
func TestCreate(t *testing.T) {
|
||||||
t.Run("tile_500", func(t *testing.T) {
|
t.Run("tile_500", func(t *testing.T) {
|
||||||
tile500 := Sizes["tile_500"]
|
tile500 := Sizes[Tile500]
|
||||||
src := "testdata/example.jpg"
|
src := "testdata/example.jpg"
|
||||||
dst := "testdata/example.tile_500.jpg"
|
dst := "testdata/example.tile_500.jpg"
|
||||||
|
|
||||||
@@ -368,7 +368,7 @@ func TestCreate(t *testing.T) {
|
|||||||
assert.Equal(t, 500, boundsNew.Max.Y)
|
assert.Equal(t, 500, boundsNew.Max.Y)
|
||||||
})
|
})
|
||||||
t.Run("width & height <= 150", func(t *testing.T) {
|
t.Run("width & height <= 150", func(t *testing.T) {
|
||||||
tile500 := Sizes["tile_500"]
|
tile500 := Sizes[Tile500]
|
||||||
src := "testdata/example.jpg"
|
src := "testdata/example.jpg"
|
||||||
dst := "testdata/example.tile_500.jpg"
|
dst := "testdata/example.tile_500.jpg"
|
||||||
|
|
||||||
@@ -405,7 +405,7 @@ func TestCreate(t *testing.T) {
|
|||||||
assert.Equal(t, 150, boundsNew.Max.Y)
|
assert.Equal(t, 150, boundsNew.Max.Y)
|
||||||
})
|
})
|
||||||
t.Run("invalid width", func(t *testing.T) {
|
t.Run("invalid width", func(t *testing.T) {
|
||||||
tile500 := Sizes["tile_500"]
|
tile500 := Sizes[Tile500]
|
||||||
src := "testdata/example.jpg"
|
src := "testdata/example.jpg"
|
||||||
dst := "testdata/example.tile_500.jpg"
|
dst := "testdata/example.tile_500.jpg"
|
||||||
|
|
||||||
@@ -433,7 +433,7 @@ func TestCreate(t *testing.T) {
|
|||||||
t.Log(resized)
|
t.Log(resized)
|
||||||
})
|
})
|
||||||
t.Run("invalid height", func(t *testing.T) {
|
t.Run("invalid height", func(t *testing.T) {
|
||||||
tile500 := Sizes["tile_500"]
|
tile500 := Sizes[Tile500]
|
||||||
src := "testdata/example.jpg"
|
src := "testdata/example.jpg"
|
||||||
dst := "testdata/example.tile_500.jpg"
|
dst := "testdata/example.tile_500.jpg"
|
||||||
|
|
||||||
|
|||||||
31
internal/thumb/names.go
Normal file
31
internal/thumb/names.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package thumb
|
||||||
|
|
||||||
|
import "github.com/photoprism/photoprism/pkg/fs"
|
||||||
|
|
||||||
|
// Name represents a thumbnail size name.
|
||||||
|
type Name string
|
||||||
|
|
||||||
|
// Jpeg returns the thumbnail name with a jpeg file extension suffix as string.
|
||||||
|
func (n Name) Jpeg() string {
|
||||||
|
return string(n) + fs.JpegExt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Names of thumbnail sizes.
|
||||||
|
const (
|
||||||
|
Tile50 Name = "tile_50"
|
||||||
|
Tile100 Name = "tile_100"
|
||||||
|
Crop160 Name = "crop_160"
|
||||||
|
Tile224 Name = "tile_224"
|
||||||
|
Tile500 Name = "tile_500"
|
||||||
|
Colors Name = "colors"
|
||||||
|
Left224 Name = "left_224"
|
||||||
|
Right224 Name = "right_224"
|
||||||
|
Fit720 Name = "fit_720"
|
||||||
|
Fit1280 Name = "fit_1280"
|
||||||
|
Fit1920 Name = "fit_1920"
|
||||||
|
Fit2048 Name = "fit_2048"
|
||||||
|
Fit2560 Name = "fit_2560"
|
||||||
|
Fit3840 Name = "fit_3840"
|
||||||
|
Fit4096 Name = "fit_4096"
|
||||||
|
Fit7680 Name = "fit_7680"
|
||||||
|
)
|
||||||
28
internal/thumb/resample.go
Normal file
28
internal/thumb/resample.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package thumb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
|
||||||
|
"github.com/disintegration/imaging"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Resample downscales an image and returns it.
|
||||||
|
func Resample(img image.Image, width, height int, opts ...ResampleOption) image.Image {
|
||||||
|
var resImg image.Image
|
||||||
|
|
||||||
|
method, filter, _ := ResampleOptions(opts...)
|
||||||
|
|
||||||
|
if method == ResampleFit {
|
||||||
|
resImg = imaging.Fit(img, width, height, filter)
|
||||||
|
} else if method == ResampleFillCenter {
|
||||||
|
resImg = imaging.Fill(img, width, height, imaging.Center, filter)
|
||||||
|
} else if method == ResampleFillTopLeft {
|
||||||
|
resImg = imaging.Fill(img, width, height, imaging.TopLeft, filter)
|
||||||
|
} else if method == ResampleFillBottomRight {
|
||||||
|
resImg = imaging.Fill(img, width, height, imaging.BottomRight, filter)
|
||||||
|
} else if method == ResampleResize {
|
||||||
|
resImg = imaging.Resize(img, width, height, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resImg
|
||||||
|
}
|
||||||
27
internal/thumb/resample_filters.go
Normal file
27
internal/thumb/resample_filters.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package thumb
|
||||||
|
|
||||||
|
import "github.com/disintegration/imaging"
|
||||||
|
|
||||||
|
const (
|
||||||
|
ResampleBlackman ResampleFilter = "blackman"
|
||||||
|
ResampleLanczos ResampleFilter = "lanczos"
|
||||||
|
ResampleCubic ResampleFilter = "cubic"
|
||||||
|
ResampleLinear ResampleFilter = "linear"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ResampleFilter string
|
||||||
|
|
||||||
|
func (a ResampleFilter) Imaging() imaging.ResampleFilter {
|
||||||
|
switch a {
|
||||||
|
case ResampleBlackman:
|
||||||
|
return imaging.Blackman
|
||||||
|
case ResampleLanczos:
|
||||||
|
return imaging.Lanczos
|
||||||
|
case ResampleCubic:
|
||||||
|
return imaging.CatmullRom
|
||||||
|
case ResampleLinear:
|
||||||
|
return imaging.Linear
|
||||||
|
default:
|
||||||
|
return imaging.Lanczos
|
||||||
|
}
|
||||||
|
}
|
||||||
59
internal/thumb/resample_options.go
Normal file
59
internal/thumb/resample_options.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package thumb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/disintegration/imaging"
|
||||||
|
"github.com/photoprism/photoprism/pkg/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ResampleOption int
|
||||||
|
|
||||||
|
const (
|
||||||
|
ResampleFillCenter ResampleOption = iota
|
||||||
|
ResampleFillTopLeft
|
||||||
|
ResampleFillBottomRight
|
||||||
|
ResampleFit
|
||||||
|
ResampleCrop
|
||||||
|
ResampleResize
|
||||||
|
ResampleNearestNeighbor
|
||||||
|
ResampleDefault
|
||||||
|
ResamplePng
|
||||||
|
)
|
||||||
|
|
||||||
|
var ResampleMethods = map[ResampleOption]string{
|
||||||
|
ResampleFillCenter: "center",
|
||||||
|
ResampleFillTopLeft: "left",
|
||||||
|
ResampleFillBottomRight: "right",
|
||||||
|
ResampleFit: "fit",
|
||||||
|
ResampleCrop: "crop",
|
||||||
|
ResampleResize: "resize",
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResampleOptions extracts filter, format, and method from resample options.
|
||||||
|
func ResampleOptions(opts ...ResampleOption) (method ResampleOption, filter imaging.ResampleFilter, format fs.FileFormat) {
|
||||||
|
method = ResampleFit
|
||||||
|
filter = imaging.Lanczos
|
||||||
|
format = fs.FormatJpeg
|
||||||
|
|
||||||
|
for _, option := range opts {
|
||||||
|
switch option {
|
||||||
|
case ResamplePng:
|
||||||
|
format = fs.FormatPng
|
||||||
|
case ResampleNearestNeighbor:
|
||||||
|
filter = imaging.NearestNeighbor
|
||||||
|
case ResampleDefault:
|
||||||
|
filter = Filter.Imaging()
|
||||||
|
case ResampleFillTopLeft:
|
||||||
|
method = ResampleFillTopLeft
|
||||||
|
case ResampleFillCenter:
|
||||||
|
method = ResampleFillCenter
|
||||||
|
case ResampleFillBottomRight:
|
||||||
|
method = ResampleFillBottomRight
|
||||||
|
case ResampleFit:
|
||||||
|
method = ResampleFit
|
||||||
|
case ResampleResize:
|
||||||
|
method = ResampleResize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return method, filter, format
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
package thumb
|
package thumb
|
||||||
|
|
||||||
import "github.com/disintegration/imaging"
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
SizePrecached = 2048
|
SizePrecached = 2048
|
||||||
SizeUncached = 7680
|
SizeUncached = 7680
|
||||||
@@ -22,102 +20,57 @@ func InvalidSize(size int) bool {
|
|||||||
return size < 0 || size > MaxSize()
|
return size < 0 || size > MaxSize()
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
ResampleBlackman ResampleFilter = "blackman"
|
|
||||||
ResampleLanczos ResampleFilter = "lanczos"
|
|
||||||
ResampleCubic ResampleFilter = "cubic"
|
|
||||||
ResampleLinear ResampleFilter = "linear"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ResampleFilter string
|
|
||||||
|
|
||||||
func (a ResampleFilter) Imaging() imaging.ResampleFilter {
|
|
||||||
switch a {
|
|
||||||
case ResampleBlackman:
|
|
||||||
return imaging.Blackman
|
|
||||||
case ResampleLanczos:
|
|
||||||
return imaging.Lanczos
|
|
||||||
case ResampleCubic:
|
|
||||||
return imaging.CatmullRom
|
|
||||||
case ResampleLinear:
|
|
||||||
return imaging.Linear
|
|
||||||
default:
|
|
||||||
return imaging.Lanczos
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
ResampleFillCenter ResampleOption = iota
|
|
||||||
ResampleFillTopLeft
|
|
||||||
ResampleFillBottomRight
|
|
||||||
ResampleFit
|
|
||||||
ResampleCrop
|
|
||||||
ResampleResize
|
|
||||||
ResampleNearestNeighbor
|
|
||||||
ResampleDefault
|
|
||||||
ResamplePng
|
|
||||||
)
|
|
||||||
|
|
||||||
type ResampleOption int
|
|
||||||
|
|
||||||
var ResampleMethods = map[ResampleOption]string{
|
|
||||||
ResampleFillCenter: "center",
|
|
||||||
ResampleFillTopLeft: "left",
|
|
||||||
ResampleFillBottomRight: "right",
|
|
||||||
ResampleFit: "fit",
|
|
||||||
ResampleCrop: "crop",
|
|
||||||
ResampleResize: "resize",
|
|
||||||
}
|
|
||||||
|
|
||||||
type Size struct {
|
type Size struct {
|
||||||
Use string `json:"use"`
|
Use string `json:"use"`
|
||||||
Source string `json:"-"`
|
Source Name `json:"-"`
|
||||||
Width int `json:"w"`
|
Width int `json:"w"`
|
||||||
Height int `json:"h"`
|
Height int `json:"h"`
|
||||||
Public bool `json:"-"`
|
Public bool `json:"-"`
|
||||||
Options []ResampleOption `json:"-"`
|
Options []ResampleOption `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SizeMap map[string]Size
|
type SizeMap map[Name]Size
|
||||||
|
|
||||||
|
// Sizes contains the properties of all thumbnail sizes.
|
||||||
var Sizes = SizeMap{
|
var Sizes = SizeMap{
|
||||||
"tile_50": {"Lists", "tile_500", 50, 50, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
|
Tile50: {"Lists", Tile500, 50, 50, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
|
||||||
"tile_100": {"Maps", "tile_500", 100, 100, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
|
Tile100: {"Maps", Tile500, 100, 100, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
|
||||||
"crop_160": {"FaceNet", "", 160, 160, false, []ResampleOption{ResampleCrop, ResampleDefault}},
|
Crop160: {"FaceNet", "", 160, 160, false, []ResampleOption{ResampleCrop, ResampleDefault}},
|
||||||
"tile_224": {"TensorFlow, Mosaic", "tile_500", 224, 224, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
|
Tile224: {"TensorFlow, Mosaic", Tile500, 224, 224, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
|
||||||
"tile_500": {"Tiles", "", 500, 500, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
|
Tile500: {"Tiles", "", 500, 500, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
|
||||||
"colors": {"Color Detection", "fit_720", 3, 3, false, []ResampleOption{ResampleResize, ResampleNearestNeighbor, ResamplePng}},
|
Colors: {"Color Detection", Fit720, 3, 3, false, []ResampleOption{ResampleResize, ResampleNearestNeighbor, ResamplePng}},
|
||||||
"left_224": {"TensorFlow", "fit_720", 224, 224, false, []ResampleOption{ResampleFillTopLeft, ResampleDefault}},
|
Left224: {"TensorFlow", Fit720, 224, 224, false, []ResampleOption{ResampleFillTopLeft, ResampleDefault}},
|
||||||
"right_224": {"TensorFlow", "fit_720", 224, 224, false, []ResampleOption{ResampleFillBottomRight, ResampleDefault}},
|
Right224: {"TensorFlow", Fit720, 224, 224, false, []ResampleOption{ResampleFillBottomRight, ResampleDefault}},
|
||||||
"fit_720": {"Mobile, TV", "", 720, 720, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
Fit720: {"Mobile, TV", "", 720, 720, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||||
"fit_1280": {"Mobile, HD Ready TV", "fit_2048", 1280, 1024, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
Fit1280: {"Mobile, HD Ready TV", Fit2048, 1280, 1024, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||||
"fit_1920": {"Mobile, Full HD TV", "fit_2048", 1920, 1200, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
Fit1920: {"Mobile, Full HD TV", Fit2048, 1920, 1200, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||||
"fit_2048": {"Tablets, Cinema 2K", "", 2048, 2048, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
Fit2048: {"Tablets, Cinema 2K", "", 2048, 2048, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||||
"fit_2560": {"Quad HD, Retina Display", "", 2560, 1600, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
Fit2560: {"Quad HD, Retina Display", "", 2560, 1600, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||||
"fit_3840": {"Ultra HD", "", 3840, 2400, false, []ResampleOption{ResampleFit, ResampleDefault}}, // Deprecated in favor of fit_4096
|
Fit3840: {"Ultra HD", "", 3840, 2400, false, []ResampleOption{ResampleFit, ResampleDefault}}, // Deprecated in favor of fit_4096
|
||||||
"fit_4096": {"Ultra HD, Retina 4K", "", 4096, 4096, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
Fit4096: {"Ultra HD, Retina 4K", "", 4096, 4096, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||||
"fit_7680": {"8K Ultra HD 2, Retina 6K", "", 7680, 4320, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
Fit7680: {"8K Ultra HD 2, Retina 6K", "", 7680, 4320, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||||
}
|
}
|
||||||
|
|
||||||
var DefaultSizes = []string{
|
// DefaultSizes contains all default size names.
|
||||||
"fit_7680",
|
var DefaultSizes = []Name{
|
||||||
"fit_4096",
|
Fit7680,
|
||||||
"fit_2560",
|
Fit4096,
|
||||||
"fit_2048",
|
Fit2560,
|
||||||
"fit_1920",
|
Fit2048,
|
||||||
"fit_1280",
|
Fit1920,
|
||||||
"fit_720",
|
Fit1280,
|
||||||
"right_224",
|
Fit720,
|
||||||
"left_224",
|
Right224,
|
||||||
"colors",
|
Left224,
|
||||||
"tile_500",
|
Colors,
|
||||||
"tile_224",
|
Tile500,
|
||||||
"tile_100",
|
Tile224,
|
||||||
"tile_50",
|
Tile100,
|
||||||
|
Tile50,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find returns the largest default thumbnail type for the given size limit.
|
// Find returns the largest default thumbnail type for the given size limit.
|
||||||
func Find(limit int) (name string, result Size) {
|
func Find(limit int) (name Name, size Size) {
|
||||||
for _, name = range DefaultSizes {
|
for _, name = range DefaultSizes {
|
||||||
t := Sizes[name]
|
t := Sizes[name]
|
||||||
|
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ func TestSize_ExceedsLimit(t *testing.T) {
|
|||||||
SizePrecached = 1024
|
SizePrecached = 1024
|
||||||
SizeUncached = 2048
|
SizeUncached = 2048
|
||||||
|
|
||||||
fit4096 := Sizes["fit_4096"]
|
fit4096 := Sizes[Fit4096]
|
||||||
assert.True(t, fit4096.ExceedsLimit())
|
assert.True(t, fit4096.ExceedsLimit())
|
||||||
|
|
||||||
fit2048 := Sizes["fit_2048"]
|
fit2048 := Sizes[Fit2048]
|
||||||
assert.False(t, fit2048.ExceedsLimit())
|
assert.False(t, fit2048.ExceedsLimit())
|
||||||
|
|
||||||
tile500 := Sizes["tile_500"]
|
tile500 := Sizes[Tile500]
|
||||||
assert.False(t, tile500.ExceedsLimit())
|
assert.False(t, tile500.ExceedsLimit())
|
||||||
|
|
||||||
SizePrecached = 2048
|
SizePrecached = 2048
|
||||||
@@ -27,13 +27,13 @@ func TestSize_Uncached(t *testing.T) {
|
|||||||
SizePrecached = 1024
|
SizePrecached = 1024
|
||||||
SizeUncached = 2048
|
SizeUncached = 2048
|
||||||
|
|
||||||
fit4096 := Sizes["fit_4096"]
|
fit4096 := Sizes[Fit4096]
|
||||||
assert.True(t, fit4096.Uncached())
|
assert.True(t, fit4096.Uncached())
|
||||||
|
|
||||||
fit2048 := Sizes["fit_2048"]
|
fit2048 := Sizes[Fit2048]
|
||||||
assert.True(t, fit2048.Uncached())
|
assert.True(t, fit2048.Uncached())
|
||||||
|
|
||||||
tile500 := Sizes["tile_500"]
|
tile500 := Sizes[Tile500]
|
||||||
assert.False(t, tile500.Uncached())
|
assert.False(t, tile500.Uncached())
|
||||||
|
|
||||||
SizePrecached = 2048
|
SizePrecached = 2048
|
||||||
@@ -57,16 +57,16 @@ func TestResampleFilter_Imaging(t *testing.T) {
|
|||||||
|
|
||||||
func TestFind(t *testing.T) {
|
func TestFind(t *testing.T) {
|
||||||
t.Run("2048", func(t *testing.T) {
|
t.Run("2048", func(t *testing.T) {
|
||||||
tName, tType := Find(2048)
|
name, size := Find(2048)
|
||||||
assert.Equal(t, "fit_2048", tName)
|
assert.Equal(t, Fit2048, name)
|
||||||
assert.Equal(t, 2048, tType.Width)
|
assert.Equal(t, 2048, size.Width)
|
||||||
assert.Equal(t, 2048, tType.Height)
|
assert.Equal(t, 2048, size.Height)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("2000", func(t *testing.T) {
|
t.Run("2000", func(t *testing.T) {
|
||||||
tName, tType := Find(2000)
|
name, size := Find(2000)
|
||||||
assert.Equal(t, "fit_1920", tName)
|
assert.Equal(t, Fit1920, name)
|
||||||
assert.Equal(t, 1920, tType.Width)
|
assert.Equal(t, 1920, size.Width)
|
||||||
assert.Equal(t, 1200, tType.Height)
|
assert.Equal(t, 1200, size.Height)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ func (worker *Share) Start() (err error) {
|
|||||||
srcFileName := photoprism.FileName(file.File.FileRoot, file.File.FileName)
|
srcFileName := photoprism.FileName(file.File.FileRoot, file.File.FileName)
|
||||||
|
|
||||||
if a.ShareSize != "" {
|
if a.ShareSize != "" {
|
||||||
size, ok := thumb.Sizes[a.ShareSize]
|
size, ok := thumb.Sizes[thumb.Name(a.ShareSize)]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Errorf("share: invalid size %s", a.ShareSize)
|
log.Errorf("share: invalid size %s", a.ShareSize)
|
||||||
|
|||||||
Reference in New Issue
Block a user