People: Improve thumb size config and flag descriptions #22
This commit is contained in:
parent
b2a30d8091
commit
b9d1c7afb3
|
@ -57,8 +57,8 @@ func RemoveFromFolderCache(rootName string) {
|
|||
func RemoveFromAlbumCoverCache(uid string) {
|
||||
cache := service.CoverCache()
|
||||
|
||||
for typeName := range thumb.Sizes {
|
||||
cacheKey := CacheKey(albumCover, uid, typeName)
|
||||
for thumbName := range thumb.Sizes {
|
||||
cacheKey := CacheKey(albumCover, uid, string(thumbName))
|
||||
|
||||
cache.Delete(cacheKey)
|
||||
|
||||
|
|
|
@ -20,14 +20,16 @@ const (
|
|||
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:
|
||||
// uid: string album uid
|
||||
// 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) {
|
||||
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) {
|
||||
c.Data(http.StatusForbidden, "image/svg+xml", albumIconSvg)
|
||||
return
|
||||
|
@ -35,19 +37,19 @@ func AlbumCover(router *gin.RouterGroup) {
|
|||
|
||||
start := time.Now()
|
||||
conf := service.Config()
|
||||
typeName := c.Param("type")
|
||||
thumbName := thumb.Name(c.Param("size"))
|
||||
uid := c.Param("uid")
|
||||
|
||||
size, ok := thumb.Sizes[typeName]
|
||||
size, ok := thumb.Sizes[thumbName]
|
||||
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
cache := service.CoverCache()
|
||||
cacheKey := CacheKey(albumCover, uid, typeName)
|
||||
cacheKey := CacheKey(albumCover, uid, string(thumbName))
|
||||
|
||||
if cacheData, ok := cache.Get(cacheKey); ok {
|
||||
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:
|
||||
// uid: string label uid
|
||||
// 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) {
|
||||
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) {
|
||||
c.Data(http.StatusForbidden, "image/svg+xml", labelIconSvg)
|
||||
return
|
||||
|
@ -145,19 +149,19 @@ func LabelCover(router *gin.RouterGroup) {
|
|||
|
||||
start := time.Now()
|
||||
conf := service.Config()
|
||||
typeName := c.Param("type")
|
||||
thumbName := thumb.Name(c.Param("size"))
|
||||
uid := c.Param("uid")
|
||||
|
||||
size, ok := thumb.Sizes[typeName]
|
||||
size, ok := thumb.Sizes[thumbName]
|
||||
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
cache := service.CoverCache()
|
||||
cacheKey := CacheKey(labelCover, uid, typeName)
|
||||
cacheKey := CacheKey(labelCover, uid, string(thumbName))
|
||||
|
||||
if cacheData, ok := cache.Get(cacheKey); ok {
|
||||
log.Debugf("api: cache hit for %s [%s]", cacheKey, time.Since(start))
|
||||
|
|
|
@ -18,14 +18,16 @@ const (
|
|||
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:
|
||||
// uid: string folder uid
|
||||
// token: string url security token, see config
|
||||
// type: string thumb type, see thumb.Sizes
|
||||
func GetFolderCover(router *gin.RouterGroup) {
|
||||
router.GET("/folders/t/:uid/:token/:type", func(c *gin.Context) {
|
||||
// size: string thumb type, see thumb.Sizes
|
||||
func FolderCover(router *gin.RouterGroup) {
|
||||
router.GET("/folders/t/:uid/:token/:size", func(c *gin.Context) {
|
||||
if InvalidPreviewToken(c) {
|
||||
c.Data(http.StatusForbidden, "image/svg+xml", folderIconSvg)
|
||||
return
|
||||
|
@ -34,21 +36,21 @@ func GetFolderCover(router *gin.RouterGroup) {
|
|||
start := time.Now()
|
||||
conf := service.Config()
|
||||
uid := c.Param("uid")
|
||||
typeName := c.Param("type")
|
||||
thumbName := thumb.Name(c.Param("size"))
|
||||
download := c.Query("download") != ""
|
||||
|
||||
size, ok := thumb.Sizes[typeName]
|
||||
size, ok := thumb.Sizes[thumbName]
|
||||
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
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())
|
||||
c.Data(http.StatusOK, "image/svg+xml", folderIconSvg)
|
||||
return
|
||||
|
@ -56,7 +58,7 @@ func GetFolderCover(router *gin.RouterGroup) {
|
|||
}
|
||||
|
||||
cache := service.CoverCache()
|
||||
cacheKey := CacheKey(folderCover, uid, typeName)
|
||||
cacheKey := CacheKey(folderCover, uid, string(thumbName))
|
||||
|
||||
if cacheData, ok := cache.Get(cacheKey); ok {
|
||||
log.Debugf("api: cache hit for %s [%s]", cacheKey, time.Since(start))
|
||||
|
|
|
@ -10,28 +10,28 @@ import (
|
|||
func TestGetFolderCover(t *testing.T) {
|
||||
t.Run("no cover yet", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
GetFolderCover(router)
|
||||
FolderCover(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/folders/t/dqo63pn35k2d495z/"+conf.PreviewToken()+"/tile_500")
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
|
||||
t.Run("invalid thumb type", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
GetFolderCover(router)
|
||||
FolderCover(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/folders/t/dqo63pn35k2d495z/"+conf.PreviewToken()+"/xxx")
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
|
||||
t.Run("invalid token", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
GetFolderCover(router)
|
||||
FolderCover(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/folders/t/dqo63pn35k2d495z/xxx/tile_500")
|
||||
assert.Equal(t, http.StatusForbidden, r.Code)
|
||||
})
|
||||
|
||||
t.Run("could not find original", func(t *testing.T) {
|
||||
app, router, conf := NewApiTest()
|
||||
GetFolderCover(router)
|
||||
FolderCover(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/folders/t/dqo63pn2f87f02oi/"+conf.PreviewToken()+"/fit_7680")
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
|
|
|
@ -18,14 +18,14 @@ import (
|
|||
|
||||
// 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:
|
||||
// hash: string sha1 file hash
|
||||
// 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) {
|
||||
router.GET("/t/:hash/:token/:type", func(c *gin.Context) {
|
||||
router.GET("/t/:hash/:token/:size", func(c *gin.Context) {
|
||||
if InvalidPreviewToken(c) {
|
||||
c.Data(http.StatusForbidden, "image/svg+xml", brokenIconSvg)
|
||||
return
|
||||
|
@ -34,21 +34,21 @@ func GetThumb(router *gin.RouterGroup) {
|
|||
start := time.Now()
|
||||
conf := service.Config()
|
||||
fileHash := c.Param("hash")
|
||||
typeName := c.Param("type")
|
||||
thumbName := thumb.Name(c.Param("size"))
|
||||
download := c.Query("download") != ""
|
||||
|
||||
size, ok := thumb.Sizes[typeName]
|
||||
size, ok := thumb.Sizes[thumbName]
|
||||
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
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())
|
||||
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
|
||||
return
|
||||
|
@ -56,7 +56,7 @@ func GetThumb(router *gin.RouterGroup) {
|
|||
}
|
||||
|
||||
cache := service.ThumbCache()
|
||||
cacheKey := CacheKey("thumbs", fileHash, typeName)
|
||||
cacheKey := CacheKey("thumbs", fileHash, string(thumbName))
|
||||
|
||||
if cacheData, ok := cache.Get(cacheKey); ok {
|
||||
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.
|
||||
//
|
||||
// GET /api/v1/t/:hash/:token/:type/:area
|
||||
// GET /api/v1/t/:hash/:token/:size/:area
|
||||
//
|
||||
// Parameters:
|
||||
// hash: string sha1 file hash
|
||||
// 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
|
||||
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) {
|
||||
c.Data(http.StatusForbidden, "image/svg+xml", brokenIconSvg)
|
||||
return
|
||||
|
@ -189,18 +189,18 @@ func GetThumbCrop(router *gin.RouterGroup) {
|
|||
|
||||
conf := service.Config()
|
||||
fileHash := c.Param("hash")
|
||||
typeName := c.Param("type")
|
||||
thumbName := thumb.Name(c.Param("size"))
|
||||
cropArea := c.Param("area")
|
||||
download := c.Query("download") != ""
|
||||
|
||||
size, ok := thumb.Sizes[typeName]
|
||||
size, ok := thumb.Sizes[thumbName]
|
||||
|
||||
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)
|
||||
return
|
||||
} 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)
|
||||
return
|
||||
}
|
||||
|
@ -220,7 +220,7 @@ func GetThumbCrop(router *gin.RouterGroup) {
|
|||
AddThumbCacheHeader(c)
|
||||
|
||||
if download {
|
||||
c.FileAttachment(fileName, typeName+fs.JpegExt)
|
||||
c.FileAttachment(fileName, thumbName.Jpeg())
|
||||
} else {
|
||||
c.File(fileName)
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ import (
|
|||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
// SharePreview returns a link share preview image.
|
||||
//
|
||||
// GET /s/:token/:uid/preview
|
||||
// TODO: Proof of concept, needs refactoring.
|
||||
func SharePreview(router *gin.RouterGroup) {
|
||||
|
@ -88,7 +90,7 @@ func SharePreview(router *gin.RouterGroup) {
|
|||
return
|
||||
} else if count < 12 {
|
||||
f := p[0]
|
||||
size, _ := thumb.Sizes["fit_720"]
|
||||
size, _ := thumb.Sizes[thumb.Fit720]
|
||||
|
||||
fileName := photoprism.FileName(f.FileRoot, f.FileName)
|
||||
|
||||
|
@ -117,7 +119,7 @@ func SharePreview(router *gin.RouterGroup) {
|
|||
y := 0
|
||||
|
||||
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 {
|
||||
fileName := photoprism.FileName(f.FileRoot, f.FileName)
|
||||
|
|
|
@ -13,7 +13,7 @@ import (
|
|||
var ResampleCommand = cli.Command{
|
||||
Name: "resample",
|
||||
Aliases: []string{"thumbs"},
|
||||
Usage: "Pre-caches thumbnails to reduce memory and cpu usage",
|
||||
Usage: "Pre-caches thumbnail images for improved performance",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "force, f",
|
||||
|
|
|
@ -77,7 +77,7 @@ func init() {
|
|||
t := thumb.Sizes[name]
|
||||
|
||||
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",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "thumb-filter, f",
|
||||
Usage: "downscaling filter `NAME` (best to worst: blackman, lanczos, cubic, linear)",
|
||||
Name: "thumb-filter",
|
||||
Usage: "image downscaling `FILTER` (best to worst: blackman, lanczos, cubic, linear)",
|
||||
Value: "lanczos",
|
||||
EnvVar: "PHOTOPRISM_THUMB_FILTER",
|
||||
},
|
||||
cli.IntFlag{
|
||||
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,
|
||||
EnvVar: "PHOTOPRISM_THUMB_SIZE",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
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",
|
||||
},
|
||||
cli.IntFlag{
|
||||
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,
|
||||
EnvVar: "PHOTOPRISM_THUMB_SIZE_UNCACHED",
|
||||
},
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
"math"
|
||||
|
||||
"github.com/lucasb-eyer/go-colorful"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
"github.com/photoprism/photoprism/pkg/colors"
|
||||
"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()))
|
||||
}
|
||||
|
||||
img, err := m.Resample(thumbPath, "colors")
|
||||
img, err := m.Resample(thumbPath, thumb.Colors)
|
||||
|
||||
if err != nil {
|
||||
log.Debugf("colors: %s in %s (resample)", err, txt.Quote(m.BaseName()))
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/classify"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
|
@ -12,18 +14,18 @@ import (
|
|||
func (ind *Index) classifyImage(jpeg *MediaFile) (results classify.Labels) {
|
||||
start := time.Now()
|
||||
|
||||
var thumbs []string
|
||||
var sizes []thumb.Name
|
||||
|
||||
if jpeg.AspectRatio() == 1 {
|
||||
thumbs = []string{"tile_224"}
|
||||
sizes = []thumb.Name{thumb.Tile224}
|
||||
} else {
|
||||
thumbs = []string{"tile_224", "left_224", "right_224"}
|
||||
sizes = []thumb.Name{thumb.Tile224, thumb.Left224, thumb.Right224}
|
||||
}
|
||||
|
||||
var labels classify.Labels
|
||||
|
||||
for _, thumb := range thumbs {
|
||||
filename, err := jpeg.Thumbnail(Config().ThumbPath(), thumb)
|
||||
for _, size := range sizes {
|
||||
filename, err := jpeg.Thumbnail(Config().ThumbPath(), size)
|
||||
|
||||
if err != nil {
|
||||
log.Debugf("%s in %s", err, txt.Quote(jpeg.BaseName()))
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/face"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
|
@ -14,15 +16,15 @@ func (ind *Index) detectFaces(jpeg *MediaFile) face.Faces {
|
|||
}
|
||||
|
||||
var minSize int
|
||||
var thumbSize string
|
||||
var thumbSize thumb.Name
|
||||
|
||||
// Select best thumbnail depending on configured size.
|
||||
if Config().ThumbSizePrecached() < 1280 {
|
||||
minSize = 30
|
||||
thumbSize = "fit_720"
|
||||
thumbSize = thumb.Fit720
|
||||
} else {
|
||||
minSize = 40
|
||||
thumbSize = "fit_1280"
|
||||
thumbSize = thumb.Fit1280
|
||||
}
|
||||
|
||||
thumbName, err := jpeg.Thumbnail(Config().ThumbPath(), thumbSize)
|
||||
|
|
|
@ -15,6 +15,8 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/meta"
|
||||
"github.com/photoprism/photoprism/internal/nsfw"
|
||||
"github.com/photoprism/photoprism/internal/query"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"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.
|
||||
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 {
|
||||
log.Error(err)
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/djherbis/times"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/meta"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
|
@ -887,12 +888,12 @@ func (m *MediaFile) Orientation() int {
|
|||
}
|
||||
|
||||
// Thumbnail returns a thumbnail filename.
|
||||
func (m *MediaFile) Thumbnail(path string, typeName string) (filename string, err error) {
|
||||
size, ok := thumb.Sizes[typeName]
|
||||
func (m *MediaFile) Thumbnail(path string, sizeName thumb.Name) (filename string, err error) {
|
||||
size, ok := thumb.Sizes[sizeName]
|
||||
|
||||
if !ok {
|
||||
log.Errorf("media: invalid type %s", typeName)
|
||||
return "", fmt.Errorf("media: invalid type %s", typeName)
|
||||
log.Errorf("media: invalid type %s", sizeName)
|
||||
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...)
|
||||
|
@ -907,8 +908,8 @@ func (m *MediaFile) Thumbnail(path string, typeName string) (filename string, er
|
|||
}
|
||||
|
||||
// Resample returns a resampled image of the file.
|
||||
func (m *MediaFile) Resample(path string, typeName string) (img image.Image, err error) {
|
||||
filename, err := m.Thumbnail(path, typeName)
|
||||
func (m *MediaFile) Resample(path string, sizeName thumb.Name) (img image.Image, err error) {
|
||||
filename, err := m.Thumbnail(path, sizeName)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -937,7 +938,7 @@ func (m *MediaFile) ResampleDefault(thumbPath string, force bool) (err error) {
|
|||
|
||||
var originalImg image.Image
|
||||
var sourceImg image.Image
|
||||
var sourceImgType string
|
||||
var sourceName thumb.Name
|
||||
|
||||
for _, name := range thumb.DefaultSizes {
|
||||
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 {
|
||||
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
|
||||
} else {
|
||||
|
@ -970,18 +971,18 @@ func (m *MediaFile) ResampleDefault(thumbPath string, force bool) (err error) {
|
|||
}
|
||||
|
||||
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...)
|
||||
} else {
|
||||
_, err = thumb.Create(originalImg, fileName, size.Width, size.Height, size.Options...)
|
||||
}
|
||||
} else {
|
||||
sourceImg, err = thumb.Create(originalImg, fileName, size.Width, size.Height, size.Options...)
|
||||
sourceImgType = name
|
||||
sourceName = name
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
@ -1822,7 +1823,7 @@ func TestMediaFile_Resample(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
thumbnail, err := image.Resample(thumbsPath, "tile_500")
|
||||
thumbnail, err := image.Resample(thumbsPath, thumb.Tile500)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -1873,7 +1874,7 @@ func TestMediaFile_RenderDefaultThumbs(t *testing.T) {
|
|||
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 {
|
||||
t.Fatal(err)
|
||||
|
|
|
@ -87,23 +87,6 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
|
|||
api.PhotoPrimary(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.StartImport(v1)
|
||||
api.CancelImport(v1)
|
||||
|
@ -118,6 +101,24 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
|
|||
api.BatchAlbumsDelete(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.CreateAlbum(v1)
|
||||
api.UpdateAlbum(v1)
|
||||
|
@ -130,7 +131,6 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
|
|||
api.DeleteAlbumLink(v1)
|
||||
api.LikeAlbum(v1)
|
||||
api.DislikeAlbum(v1)
|
||||
api.AlbumCover(v1)
|
||||
api.CloneAlbums(v1)
|
||||
api.AddPhotosToAlbum(v1)
|
||||
api.RemovePhotosFromAlbum(v1)
|
||||
|
|
|
@ -15,57 +15,6 @@ import (
|
|||
"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.
|
||||
func Suffix(width, height int, opts ...ResampleOption) (result string) {
|
||||
method, _, format := ResampleOptions(opts...)
|
||||
|
|
|
@ -35,7 +35,7 @@ func TestResampleOptions(t *testing.T) {
|
|||
|
||||
func TestResample(t *testing.T) {
|
||||
t.Run("tile50 options", func(t *testing.T) {
|
||||
tile50 := Sizes["tile_50"]
|
||||
tile50 := Sizes[Tile50]
|
||||
|
||||
src := "testdata/example.jpg"
|
||||
|
||||
|
@ -60,7 +60,7 @@ func TestResample(t *testing.T) {
|
|||
assert.Equal(t, 50, boundsNew.Max.Y)
|
||||
})
|
||||
t.Run("left_224 options", func(t *testing.T) {
|
||||
left224 := Sizes["left_224"]
|
||||
left224 := Sizes[Left224]
|
||||
|
||||
src := "testdata/example.jpg"
|
||||
|
||||
|
@ -85,7 +85,7 @@ func TestResample(t *testing.T) {
|
|||
assert.Equal(t, 224, boundsNew.Max.Y)
|
||||
})
|
||||
t.Run("right_224 options", func(t *testing.T) {
|
||||
right224 := Sizes["right_224"]
|
||||
right224 := Sizes[Right224]
|
||||
|
||||
src := "testdata/example.jpg"
|
||||
|
||||
|
@ -110,7 +110,7 @@ func TestResample(t *testing.T) {
|
|||
assert.Equal(t, 224, boundsNew.Max.Y)
|
||||
})
|
||||
t.Run("fit_1280 options", func(t *testing.T) {
|
||||
fit1280 := Sizes["fit_1280"]
|
||||
fit1280 := Sizes[Fit1280]
|
||||
|
||||
src := "testdata/example.jpg"
|
||||
|
||||
|
@ -137,7 +137,7 @@ func TestResample(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSuffix(t *testing.T) {
|
||||
tile50 := Sizes["tile_50"]
|
||||
tile50 := Sizes[Tile50]
|
||||
|
||||
result := Suffix(tile50.Width, tile50.Height, tile50.Options...)
|
||||
|
||||
|
@ -146,7 +146,7 @@ func TestSuffix(t *testing.T) {
|
|||
|
||||
func TestFileName(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...)
|
||||
|
||||
|
@ -158,7 +158,7 @@ func TestFileName(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...)
|
||||
|
||||
|
@ -169,7 +169,7 @@ func TestFileName(t *testing.T) {
|
|||
assert.Equal(t, "testdata/1/2/3/123456789098765432_720x720_fit.jpg", result)
|
||||
})
|
||||
t.Run("invalid width", func(t *testing.T) {
|
||||
colorThumb := Sizes["colors"]
|
||||
colorThumb := Sizes[Colors]
|
||||
|
||||
result, err := FileName("123456789098765432", "testdata", -2, colorThumb.Height, colorThumb.Options...)
|
||||
|
||||
|
@ -180,7 +180,7 @@ func TestFileName(t *testing.T) {
|
|||
assert.Empty(t, result)
|
||||
})
|
||||
t.Run("invalid height", func(t *testing.T) {
|
||||
colorThumb := Sizes["colors"]
|
||||
colorThumb := Sizes[Colors]
|
||||
|
||||
result, err := FileName("123456789098765432", "testdata", colorThumb.Width, -3, colorThumb.Options...)
|
||||
|
||||
|
@ -191,7 +191,7 @@ func TestFileName(t *testing.T) {
|
|||
assert.Empty(t, result)
|
||||
})
|
||||
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...)
|
||||
|
||||
|
@ -202,7 +202,7 @@ func TestFileName(t *testing.T) {
|
|||
assert.Empty(t, result)
|
||||
})
|
||||
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...)
|
||||
|
||||
|
@ -216,7 +216,7 @@ func TestFileName(t *testing.T) {
|
|||
|
||||
func TestFromFile(t *testing.T) {
|
||||
t.Run("colors", func(t *testing.T) {
|
||||
colorThumb := Sizes["colors"]
|
||||
colorThumb := Sizes[Colors]
|
||||
src := "testdata/example.gif"
|
||||
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) {
|
||||
colorThumb := Sizes["colors"]
|
||||
colorThumb := Sizes[Colors]
|
||||
src := "testdata/example.gif"
|
||||
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) {
|
||||
colorThumb := Sizes["colors"]
|
||||
colorThumb := Sizes[Colors]
|
||||
src := "testdata/example.xxx"
|
||||
|
||||
assert.NoFileExists(t, src)
|
||||
|
@ -263,7 +263,7 @@ func TestFromFile(t *testing.T) {
|
|||
assert.Error(t, err)
|
||||
})
|
||||
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...)
|
||||
|
||||
|
@ -277,7 +277,7 @@ func TestFromFile(t *testing.T) {
|
|||
|
||||
func TestFromCache(t *testing.T) {
|
||||
t.Run("missing thumb", func(t *testing.T) {
|
||||
tile50 := Sizes["tile_50"]
|
||||
tile50 := Sizes[Tile50]
|
||||
src := "testdata/example.jpg"
|
||||
|
||||
assert.FileExists(t, src)
|
||||
|
@ -292,7 +292,7 @@ func TestFromCache(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("missing file", func(t *testing.T) {
|
||||
tile50 := Sizes["tile_50"]
|
||||
tile50 := Sizes[Tile50]
|
||||
src := "testdata/example.xxx"
|
||||
|
||||
assert.NoFileExists(t, src)
|
||||
|
@ -303,7 +303,7 @@ func TestFromCache(t *testing.T) {
|
|||
assert.Error(t, err)
|
||||
})
|
||||
t.Run("invalid hash", func(t *testing.T) {
|
||||
tile50 := Sizes["tile_50"]
|
||||
tile50 := Sizes[Tile50]
|
||||
src := "testdata/example.jpg"
|
||||
|
||||
assert.FileExists(t, src)
|
||||
|
@ -317,7 +317,7 @@ func TestFromCache(t *testing.T) {
|
|||
assert.Empty(t, fileName)
|
||||
})
|
||||
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...)
|
||||
|
||||
|
@ -331,7 +331,7 @@ func TestFromCache(t *testing.T) {
|
|||
|
||||
func TestCreate(t *testing.T) {
|
||||
t.Run("tile_500", func(t *testing.T) {
|
||||
tile500 := Sizes["tile_500"]
|
||||
tile500 := Sizes[Tile500]
|
||||
src := "testdata/example.jpg"
|
||||
dst := "testdata/example.tile_500.jpg"
|
||||
|
||||
|
@ -368,7 +368,7 @@ func TestCreate(t *testing.T) {
|
|||
assert.Equal(t, 500, boundsNew.Max.Y)
|
||||
})
|
||||
t.Run("width & height <= 150", func(t *testing.T) {
|
||||
tile500 := Sizes["tile_500"]
|
||||
tile500 := Sizes[Tile500]
|
||||
src := "testdata/example.jpg"
|
||||
dst := "testdata/example.tile_500.jpg"
|
||||
|
||||
|
@ -405,7 +405,7 @@ func TestCreate(t *testing.T) {
|
|||
assert.Equal(t, 150, boundsNew.Max.Y)
|
||||
})
|
||||
t.Run("invalid width", func(t *testing.T) {
|
||||
tile500 := Sizes["tile_500"]
|
||||
tile500 := Sizes[Tile500]
|
||||
src := "testdata/example.jpg"
|
||||
dst := "testdata/example.tile_500.jpg"
|
||||
|
||||
|
@ -433,7 +433,7 @@ func TestCreate(t *testing.T) {
|
|||
t.Log(resized)
|
||||
})
|
||||
t.Run("invalid height", func(t *testing.T) {
|
||||
tile500 := Sizes["tile_500"]
|
||||
tile500 := Sizes[Tile500]
|
||||
src := "testdata/example.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
|
||||
|
||||
import "github.com/disintegration/imaging"
|
||||
|
||||
var (
|
||||
SizePrecached = 2048
|
||||
SizeUncached = 7680
|
||||
|
@ -22,102 +20,57 @@ func InvalidSize(size int) bool {
|
|||
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 {
|
||||
Use string `json:"use"`
|
||||
Source string `json:"-"`
|
||||
Source Name `json:"-"`
|
||||
Width int `json:"w"`
|
||||
Height int `json:"h"`
|
||||
Public bool `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{
|
||||
"tile_50": {"Lists", "tile_500", 50, 50, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
|
||||
"tile_100": {"Maps", "tile_500", 100, 100, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
|
||||
"crop_160": {"FaceNet", "", 160, 160, false, []ResampleOption{ResampleCrop, ResampleDefault}},
|
||||
"tile_224": {"TensorFlow, Mosaic", "tile_500", 224, 224, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
|
||||
"tile_500": {"Tiles", "", 500, 500, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
|
||||
"colors": {"Color Detection", "fit_720", 3, 3, false, []ResampleOption{ResampleResize, ResampleNearestNeighbor, ResamplePng}},
|
||||
"left_224": {"TensorFlow", "fit_720", 224, 224, false, []ResampleOption{ResampleFillTopLeft, ResampleDefault}},
|
||||
"right_224": {"TensorFlow", "fit_720", 224, 224, false, []ResampleOption{ResampleFillBottomRight, ResampleDefault}},
|
||||
"fit_720": {"Mobile, TV", "", 720, 720, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
"fit_1280": {"Mobile, HD Ready TV", "fit_2048", 1280, 1024, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
"fit_1920": {"Mobile, Full HD TV", "fit_2048", 1920, 1200, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
"fit_2048": {"Tablets, Cinema 2K", "", 2048, 2048, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
"fit_2560": {"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
|
||||
"fit_4096": {"Ultra HD, Retina 4K", "", 4096, 4096, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
"fit_7680": {"8K Ultra HD 2, Retina 6K", "", 7680, 4320, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
Tile50: {"Lists", Tile500, 50, 50, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
|
||||
Tile100: {"Maps", Tile500, 100, 100, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
|
||||
Crop160: {"FaceNet", "", 160, 160, false, []ResampleOption{ResampleCrop, ResampleDefault}},
|
||||
Tile224: {"TensorFlow, Mosaic", Tile500, 224, 224, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
|
||||
Tile500: {"Tiles", "", 500, 500, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
|
||||
Colors: {"Color Detection", Fit720, 3, 3, false, []ResampleOption{ResampleResize, ResampleNearestNeighbor, ResamplePng}},
|
||||
Left224: {"TensorFlow", Fit720, 224, 224, false, []ResampleOption{ResampleFillTopLeft, ResampleDefault}},
|
||||
Right224: {"TensorFlow", Fit720, 224, 224, false, []ResampleOption{ResampleFillBottomRight, ResampleDefault}},
|
||||
Fit720: {"Mobile, TV", "", 720, 720, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
Fit1280: {"Mobile, HD Ready TV", Fit2048, 1280, 1024, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
Fit1920: {"Mobile, Full HD TV", Fit2048, 1920, 1200, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
Fit2048: {"Tablets, Cinema 2K", "", 2048, 2048, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
Fit2560: {"Quad HD, Retina Display", "", 2560, 1600, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
Fit3840: {"Ultra HD", "", 3840, 2400, false, []ResampleOption{ResampleFit, ResampleDefault}}, // Deprecated in favor of fit_4096
|
||||
Fit4096: {"Ultra HD, Retina 4K", "", 4096, 4096, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
Fit7680: {"8K Ultra HD 2, Retina 6K", "", 7680, 4320, true, []ResampleOption{ResampleFit, ResampleDefault}},
|
||||
}
|
||||
|
||||
var DefaultSizes = []string{
|
||||
"fit_7680",
|
||||
"fit_4096",
|
||||
"fit_2560",
|
||||
"fit_2048",
|
||||
"fit_1920",
|
||||
"fit_1280",
|
||||
"fit_720",
|
||||
"right_224",
|
||||
"left_224",
|
||||
"colors",
|
||||
"tile_500",
|
||||
"tile_224",
|
||||
"tile_100",
|
||||
"tile_50",
|
||||
// DefaultSizes contains all default size names.
|
||||
var DefaultSizes = []Name{
|
||||
Fit7680,
|
||||
Fit4096,
|
||||
Fit2560,
|
||||
Fit2048,
|
||||
Fit1920,
|
||||
Fit1280,
|
||||
Fit720,
|
||||
Right224,
|
||||
Left224,
|
||||
Colors,
|
||||
Tile500,
|
||||
Tile224,
|
||||
Tile100,
|
||||
Tile50,
|
||||
}
|
||||
|
||||
// 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 {
|
||||
t := Sizes[name]
|
||||
|
||||
|
|
|
@ -10,13 +10,13 @@ func TestSize_ExceedsLimit(t *testing.T) {
|
|||
SizePrecached = 1024
|
||||
SizeUncached = 2048
|
||||
|
||||
fit4096 := Sizes["fit_4096"]
|
||||
fit4096 := Sizes[Fit4096]
|
||||
assert.True(t, fit4096.ExceedsLimit())
|
||||
|
||||
fit2048 := Sizes["fit_2048"]
|
||||
fit2048 := Sizes[Fit2048]
|
||||
assert.False(t, fit2048.ExceedsLimit())
|
||||
|
||||
tile500 := Sizes["tile_500"]
|
||||
tile500 := Sizes[Tile500]
|
||||
assert.False(t, tile500.ExceedsLimit())
|
||||
|
||||
SizePrecached = 2048
|
||||
|
@ -27,13 +27,13 @@ func TestSize_Uncached(t *testing.T) {
|
|||
SizePrecached = 1024
|
||||
SizeUncached = 2048
|
||||
|
||||
fit4096 := Sizes["fit_4096"]
|
||||
fit4096 := Sizes[Fit4096]
|
||||
assert.True(t, fit4096.Uncached())
|
||||
|
||||
fit2048 := Sizes["fit_2048"]
|
||||
fit2048 := Sizes[Fit2048]
|
||||
assert.True(t, fit2048.Uncached())
|
||||
|
||||
tile500 := Sizes["tile_500"]
|
||||
tile500 := Sizes[Tile500]
|
||||
assert.False(t, tile500.Uncached())
|
||||
|
||||
SizePrecached = 2048
|
||||
|
@ -57,16 +57,16 @@ func TestResampleFilter_Imaging(t *testing.T) {
|
|||
|
||||
func TestFind(t *testing.T) {
|
||||
t.Run("2048", func(t *testing.T) {
|
||||
tName, tType := Find(2048)
|
||||
assert.Equal(t, "fit_2048", tName)
|
||||
assert.Equal(t, 2048, tType.Width)
|
||||
assert.Equal(t, 2048, tType.Height)
|
||||
name, size := Find(2048)
|
||||
assert.Equal(t, Fit2048, name)
|
||||
assert.Equal(t, 2048, size.Width)
|
||||
assert.Equal(t, 2048, size.Height)
|
||||
})
|
||||
|
||||
t.Run("2000", func(t *testing.T) {
|
||||
tName, tType := Find(2000)
|
||||
assert.Equal(t, "fit_1920", tName)
|
||||
assert.Equal(t, 1920, tType.Width)
|
||||
assert.Equal(t, 1200, tType.Height)
|
||||
name, size := Find(2000)
|
||||
assert.Equal(t, Fit1920, name)
|
||||
assert.Equal(t, 1920, size.Width)
|
||||
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)
|
||||
|
||||
if a.ShareSize != "" {
|
||||
size, ok := thumb.Sizes[a.ShareSize]
|
||||
size, ok := thumb.Sizes[thumb.Name(a.ShareSize)]
|
||||
|
||||
if !ok {
|
||||
log.Errorf("share: invalid size %s", a.ShareSize)
|
||||
|
|
Loading…
Reference in a new issue