Backend: Add thumb config options and lower defaults #157

Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
Michael Mayer
2020-01-13 11:07:09 +01:00
parent 106e9c3e1e
commit e184cad553
28 changed files with 162 additions and 150 deletions

View File

@@ -12,11 +12,11 @@ import (
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/internal/thumb" "github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding"
@@ -444,13 +444,9 @@ func AlbumThumbnail(router *gin.RouterGroup, conf *config.Config) {
} }
// Use original file if thumb size exceeds limit, see https://github.com/photoprism/photoprism/issues/157 // Use original file if thumb size exceeds limit, see https://github.com/photoprism/photoprism/issues/157
if thumbType.Height > thumb.MaxHeight || thumbType.Width > thumb.MaxWidth { if thumbType.ExceedsLimit() && c.Query("download") == "" {
log.Debugf("album: using original, thumbnail size exceeds limit (width %d, height %d)", thumbType.Width, thumbType.Height) log.Debugf("album: using original, thumbnail size exceeds limit (width %d, height %d)", thumbType.Width, thumbType.Height)
if c.Query("download") != "" {
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", f.DownloadFileName()))
}
c.File(fileName) c.File(fileName)
return return

View File

@@ -5,8 +5,8 @@ import (
"path" "path"
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )

View File

@@ -11,8 +11,8 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/pkg/fs"
) )
var imp *photoprism.Import var imp *photoprism.Import

View File

@@ -12,10 +12,10 @@ import (
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding"
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/thumb" "github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/txt" "github.com/photoprism/photoprism/pkg/txt"
) )
@@ -164,7 +164,7 @@ func LabelThumbnail(router *gin.RouterGroup, conf *config.Config) {
} }
// Use original file if thumb size exceeds limit, see https://github.com/photoprism/photoprism/issues/157 // Use original file if thumb size exceeds limit, see https://github.com/photoprism/photoprism/issues/157
if thumbType.Height > thumb.MaxHeight || thumbType.Width > thumb.MaxWidth { if thumbType.ExceedsLimit() {
log.Debugf("label: using original, thumbnail size exceeds limit (width %d, height %d)", thumbType.Width, thumbType.Height) log.Debugf("label: using original, thumbnail size exceeds limit (width %d, height %d)", thumbType.Width, thumbType.Height)
c.File(fileName) c.File(fileName)

View File

@@ -7,8 +7,8 @@ import (
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/txt" "github.com/photoprism/photoprism/pkg/txt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"

View File

@@ -7,9 +7,9 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/thumb" "github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/fs"
) )
// GET /api/v1/thumbnails/:hash/:type // GET /api/v1/thumbnails/:hash/:type
@@ -51,13 +51,9 @@ func GetThumbnail(router *gin.RouterGroup, conf *config.Config) {
} }
// Use original file if thumb size exceeds limit, see https://github.com/photoprism/photoprism/issues/157 // Use original file if thumb size exceeds limit, see https://github.com/photoprism/photoprism/issues/157
if thumbType.Height > thumb.MaxHeight || thumbType.Width > thumb.MaxWidth { if thumbType.ExceedsLimit() && c.Query("download") == "" {
log.Debugf("photo: using original, thumbnail size exceeds limit (width %d, height %d)", thumbType.Width, thumbType.Height) log.Debugf("photo: using original, thumbnail size exceeds limit (width %d, height %d)", thumbType.Width, thumbType.Height)
if c.Query("download") != "" {
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", f.DownloadFileName()))
}
c.File(fileName) c.File(fileName)
return return

View File

@@ -12,10 +12,10 @@ import (
"github.com/disintegration/imaging" "github.com/disintegration/imaging"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/thumb" "github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/fs"
) )
// GET /api/v1/preview // GET /api/v1/preview

View File

@@ -11,9 +11,9 @@ import (
"time" "time"
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/rnd" "github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/txt" "github.com/photoprism/photoprism/pkg/txt"

View File

@@ -76,6 +76,8 @@ func configAction(ctx *cli.Context) error {
fmt.Printf("geocoding-api %s\n", conf.GeoCodingApi()) fmt.Printf("geocoding-api %s\n", conf.GeoCodingApi())
fmt.Printf("thumb-quality %d\n", conf.ThumbQuality()) fmt.Printf("thumb-quality %d\n", conf.ThumbQuality())
fmt.Printf("thumb-size %d\n", conf.ThumbSize()) fmt.Printf("thumb-size %d\n", conf.ThumbSize())
fmt.Printf("thumb-limit %d\n", conf.ThumbLimit())
fmt.Printf("thumb-algorithm %s\n", conf.ThumbAlgorithm())
return nil return nil
} }

View File

@@ -3,8 +3,8 @@ package commands
import ( import (
"testing" "testing"
"github.com/photoprism/photoprism/pkg/capture"
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/pkg/capture"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View File

@@ -10,8 +10,8 @@ import (
"time" "time"
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/internal/server" "github.com/photoprism/photoprism/internal/server"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/sevlyar/go-daemon" "github.com/sevlyar/go-daemon"
"github.com/urfave/cli" "github.com/urfave/cli"
) )

View File

@@ -4,8 +4,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/photoprism/photoprism/pkg/colors"
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/pkg/colors"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
) )

View File

@@ -3,6 +3,7 @@ package config
import ( import (
"context" "context"
"runtime" "runtime"
"strings"
"time" "time"
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
@@ -55,8 +56,9 @@ func NewConfig(ctx *cli.Context) *Config {
log.SetLevel(c.LogLevel()) log.SetLevel(c.LogLevel())
thumb.JpegQuality = c.ThumbQuality() thumb.JpegQuality = c.ThumbQuality()
thumb.MaxWidth = c.ThumbSize() thumb.PreRenderSize = c.ThumbSize()
thumb.MaxHeight = c.ThumbSize() thumb.MaxRenderSize = c.ThumbLimit()
thumb.Algorithm = c.ThumbAlgorithm()
return c return c
} }
@@ -211,10 +213,10 @@ func (c *Config) ThumbQuality() int {
return c.config.ThumbQuality return c.config.ThumbQuality
} }
// ThumbSize returns the thumbnail size limit in pixels (720-16384). // ThumbSize returns the pre-rendered thumbnail size limit in pixels (720-3840).
func (c *Config) ThumbSize() int { func (c *Config) ThumbSize() int {
if c.config.ThumbSize > 16384 { if c.config.ThumbSize > 3840 {
return 16384 return 3840
} }
if c.config.ThumbSize < 720 { if c.config.ThumbSize < 720 {
@@ -224,6 +226,33 @@ func (c *Config) ThumbSize() int {
return c.config.ThumbSize return c.config.ThumbSize
} }
// ThumbLimit returns the on-demand thumbnail size limit in pixels (720-3840).
func (c *Config) ThumbLimit() int {
if c.config.ThumbLimit > 3840 {
return 3840
}
if c.config.ThumbLimit < 720 {
return 720
}
return c.config.ThumbLimit
}
// ThumbAlgorithm returns the thumbnail algorithm name (lanczos, cubic or linear).
func (c *Config) ThumbAlgorithm() thumb.ResampleAlgorithm {
switch strings.ToLower(c.config.ThumbAlgorithm) {
case "lanczos":
return thumb.ResampleLanczos
case "cubic":
return thumb.ResampleCubic
case "linear":
return thumb.ResampleLinear
default:
return thumb.ResampleLanczos
}
}
// GeoCodingApi returns the preferred geo coding api (none, osm or places). // GeoCodingApi returns the preferred geo coding api (none, osm or places).
func (c *Config) GeoCodingApi() string { func (c *Config) GeoCodingApi() string {
switch c.config.GeoCodingApi { switch c.config.GeoCodingApi {

View File

@@ -233,13 +233,25 @@ var GlobalFlags = []cli.Flag{
cli.IntFlag{ cli.IntFlag{
Name: "thumb-quality, q", Name: "thumb-quality, q",
Usage: "jpeg quality of thumbnails (25-100)", Usage: "jpeg quality of thumbnails (25-100)",
Value: 95, Value: 90,
EnvVar: "PHOTOPRISM_THUMB_QUALITY", EnvVar: "PHOTOPRISM_THUMB_QUALITY",
}, },
cli.IntFlag{ cli.IntFlag{
Name: "thumb-size, s", Name: "thumb-size, s",
Usage: "max thumbnail size in pixels (720-16384)", Usage: "pre-rendered thumbnail size limit in pixels (720-3840)",
Value: 8192, Value: 2048,
EnvVar: "PHOTOPRISM_THUMB_SIZE", EnvVar: "PHOTOPRISM_THUMB_SIZE",
}, },
cli.IntFlag{
Name: "thumb-limit, x",
Usage: "on-demand thumbnail size limit in pixels (720-3840)",
Value: 3840,
EnvVar: "PHOTOPRISM_THUMB_LIMIT",
},
cli.StringFlag{
Name: "thumb-algorithm, a",
Usage: "thumbnail algorithm (lanczos, cubic or linear)",
Value: "lanczos",
EnvVar: "PHOTOPRISM_THUMB_ALGORITHM",
},
} }

View File

@@ -74,6 +74,8 @@ type Params struct {
GeoCodingApi string `yaml:"geocoding-api" flag:"geocoding-api"` GeoCodingApi string `yaml:"geocoding-api" flag:"geocoding-api"`
ThumbQuality int `yaml:"thumb-quality" flag:"thumb-quality"` ThumbQuality int `yaml:"thumb-quality" flag:"thumb-quality"`
ThumbSize int `yaml:"thumb-size" flag:"thumb-size"` ThumbSize int `yaml:"thumb-size" flag:"thumb-size"`
ThumbLimit int `yaml:"thumb-limit" flag:"thumb-limit"`
ThumbAlgorithm string `yaml:"thumb-algorithm" flag:"thumb-algorithm"`
} }
// NewParams() creates a new configuration entity by using two methods: // NewParams() creates a new configuration entity by using two methods:

View File

@@ -10,8 +10,8 @@ import (
_ "github.com/jinzhu/gorm/dialects/mysql" _ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/sqlite" _ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/internal/thumb" "github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
@@ -95,8 +95,9 @@ func NewTestConfig() *Config {
c.ImportSQL(c.ExamplesPath() + "/fixtures.sql") c.ImportSQL(c.ExamplesPath() + "/fixtures.sql")
thumb.JpegQuality = c.ThumbQuality() thumb.JpegQuality = c.ThumbQuality()
thumb.MaxWidth = c.ThumbSize() thumb.PreRenderSize = c.ThumbSize()
thumb.MaxHeight = c.ThumbSize() thumb.MaxRenderSize = c.ThumbLimit()
thumb.Algorithm = c.ThumbAlgorithm()
return c return c
} }

View File

@@ -5,6 +5,7 @@ import (
"testing" "testing"
"github.com/photoprism/photoprism/internal/maps/osm" "github.com/photoprism/photoprism/internal/maps/osm"
"github.com/photoprism/photoprism/internal/maps/places"
"github.com/photoprism/photoprism/pkg/s2" "github.com/photoprism/photoprism/pkg/s2"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -49,17 +50,15 @@ func TestLocation_Assign(t *testing.T) {
lng := 13.40953 lng := 13.40953
id := s2.Token(lat, lng) id := s2.Token(lat, lng)
o, err := osm.FindLocation(id) o, err := places.FindLocation(id)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
assert.Equal(t, "Fernsehturm Berlin", o.LocName) assert.Equal(t, "Fernsehturm Berlin", o.Name())
assert.Equal(t, "10178", o.Address.Postcode) assert.Equal(t, "Berlin", o.State())
assert.Equal(t, "Berlin", o.Address.State) assert.Equal(t, "de", o.CountryCode())
assert.Equal(t, "de", o.Address.CountryCode)
assert.Equal(t, "Germany", o.Address.Country)
var l Location var l Location
@@ -157,72 +156,12 @@ func TestLocation_Assign(t *testing.T) {
assert.Equal(t, "Berlin, Germany", l.LocLabel) assert.Equal(t, "Berlin, Germany", l.LocLabel)
}) })
t.Run("PinkBeach", func(t *testing.T) {
lat := 35.26967222222222
lng := 23.53711666666667
id := s2.Token(lat, lng)
o, err := osm.FindLocation(id)
if err != nil {
t.Fatal(err)
}
assert.False(t, o.Cached)
assert.Equal(t, "Pink Beach", o.LocName)
assert.Equal(t, "", o.Address.Postcode)
assert.Equal(t, "Crete", o.Address.State)
assert.Equal(t, "gr", o.Address.CountryCode)
assert.Equal(t, "Greece", o.Address.Country)
var l Location
if err := l.Assign(o); err != nil {
t.Fatal(err)
}
assert.True(t, strings.HasPrefix(l.ID, "149ce785"))
assert.Equal(t, "Pink Beach", l.LocName)
assert.Equal(t, "Chrisoskalitissa, Crete, Greece", l.LocLabel)
})
t.Run("NewJersey", func(t *testing.T) {
lat := 40.74290
lng := -74.04862
id := s2.Token(lat, lng)
o, err := osm.FindLocation(id)
if err != nil {
t.Fatal(err)
}
assert.False(t, o.Cached)
assert.Equal(t, "", o.LocName)
assert.Equal(t, "07307", o.Address.Postcode)
assert.Equal(t, "New Jersey", o.Address.State)
assert.Equal(t, "us", o.Address.CountryCode)
assert.Equal(t, "United States of America", o.Address.Country)
var l Location
if err := l.Assign(o); err != nil {
t.Fatal(err)
}
assert.True(t, strings.HasPrefix(l.ID, "89c25741"))
assert.Equal(t, "", l.LocName)
assert.Equal(t, "Jersey City, New Jersey, USA", l.LocLabel)
})
t.Run("SouthAfrica", func(t *testing.T) { t.Run("SouthAfrica", func(t *testing.T) {
lat := -31.976301666666668 lat := -31.976301666666668
lng := 29.148046666666666 lng := 29.148046666666666
id := s2.Token(lat, lng) id := s2.Token(lat, lng)
o, err := osm.FindLocation(id) o, err := places.FindLocation(id)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -230,11 +169,9 @@ func TestLocation_Assign(t *testing.T) {
assert.False(t, o.Cached) assert.False(t, o.Cached)
assert.Equal(t, "R411", o.LocName) assert.Equal(t, "", o.Name())
assert.Equal(t, "", o.Address.Postcode) assert.Equal(t, "Eastern Cape", o.State())
assert.Equal(t, "Eastern Cape", o.Address.State) assert.Equal(t, "za", o.CountryCode())
assert.Equal(t, "za", o.Address.CountryCode)
assert.Equal(t, "South Africa", o.Address.Country)
var l Location var l Location
@@ -243,29 +180,34 @@ func TestLocation_Assign(t *testing.T) {
} }
assert.True(t, strings.HasPrefix(l.ID, "1e5e4205")) assert.True(t, strings.HasPrefix(l.ID, "1e5e4205"))
assert.Equal(t, "R411", l.LocName) assert.Equal(t, "", l.LocName)
assert.Equal(t, "Eastern Cape, South Africa", l.LocLabel) assert.Equal(t, "Eastern Cape, South Africa", l.LocLabel)
}) })
t.Run("Unknown", func(t *testing.T) { t.Run("ocean", func(t *testing.T) {
lat := -21.976301666666668 lat := -21.976301666666668
lng := 49.148046666666666 lng := 49.148046666666666
id := s2.Token(lat, lng) id := s2.Token(lat, lng)
log.Printf("ID: %s", id) log.Printf("ID: %s", id)
o, err := osm.FindLocation(id) o, err := places.FindLocation(id)
log.Printf("Output: %+v", o) log.Printf("Output: %+v", o)
if err == nil { if err != nil {
t.Fatal("expected error") t.Fatal(err)
} }
assert.False(t, o.Cached)
var l Location var l Location
assert.Error(t, l.Assign(o)) if err := l.Assign(o); err != nil {
assert.Equal(t, "unknown", l.LocCategory) t.Fatal(err)
}
assert.Equal(t, "Indian Ocean", l.LocName)
assert.Equal(t, "", l.LocCategory)
assert.Equal(t, "", l.LocCity)
// TODO: Should be zz for international waters, fixed in places server
// assert.Equal(t, "", l.LocCountry)
}) })
} }

View File

@@ -6,8 +6,8 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/photoprism/photoprism/pkg/colors"
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/pkg/colors"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View File

@@ -12,8 +12,8 @@ import (
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/internal/mutex" "github.com/photoprism/photoprism/internal/mutex"
"github.com/photoprism/photoprism/pkg/fs"
) )
// Import represents an importer that can copy/move MediaFiles to the originals directory. // Import represents an importer that can copy/move MediaFiles to the originals directory.

View File

@@ -73,7 +73,7 @@ func importWorker(jobs <-chan ImportJob) {
if jpg, err := importedMainFile.Jpeg(); err != nil { if jpg, err := importedMainFile.Jpeg(); err != nil {
log.Error(err) log.Error(err)
} else { } else {
if err := jpg.CreateDefaultThumbnails(imp.conf.ThumbnailsPath(), false); err != nil { if err := jpg.RenderDefaultThumbnails(imp.conf.ThumbnailsPath(), false); err != nil {
log.Errorf("import: could not create default thumbnails (%s)", err) log.Errorf("import: could not create default thumbnails (%s)", err)
} }
} }

View File

@@ -14,11 +14,11 @@ import (
"github.com/disintegration/imaging" "github.com/disintegration/imaging"
"github.com/djherbis/times" "github.com/djherbis/times"
"github.com/photoprism/photoprism/pkg/capture"
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/internal/meta" "github.com/photoprism/photoprism/internal/meta"
"github.com/photoprism/photoprism/internal/thumb" "github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/capture"
"github.com/photoprism/photoprism/pkg/fs"
) )
// MediaFile represents a single photo, video or sidecar file. // MediaFile represents a single photo, video or sidecar file.
@@ -714,7 +714,7 @@ func (m *MediaFile) Resample(path string, typeName string) (img image.Image, err
return imaging.Open(filename, imaging.AutoOrientation(true)) return imaging.Open(filename, imaging.AutoOrientation(true))
} }
func (m *MediaFile) CreateDefaultThumbnails(thumbPath string, force bool) (err error) { func (m *MediaFile) RenderDefaultThumbnails(thumbPath string, force bool) (err error) {
defer log.Debug(capture.Time(time.Now(), fmt.Sprintf("thumbs: created for \"%s\"", m.Filename()))) defer log.Debug(capture.Time(time.Now(), fmt.Sprintf("thumbs: created for \"%s\"", m.Filename())))
hash := m.Hash() hash := m.Hash()
@@ -732,7 +732,7 @@ func (m *MediaFile) CreateDefaultThumbnails(thumbPath string, force bool) (err e
for _, name := range thumb.DefaultTypes { for _, name := range thumb.DefaultTypes {
thumbType := thumb.Types[name] thumbType := thumb.Types[name]
if thumbType.Height > thumb.MaxHeight || thumbType.Width > thumb.MaxWidth { if thumbType.SkipPreRender() {
// Skip, size exceeds limit // Skip, size exceeds limit
continue continue
} }

View File

@@ -4,8 +4,8 @@ import (
"os" "os"
"testing" "testing"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/internal/thumb" "github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/config"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -958,7 +958,7 @@ func TestMediaFile_CreateDefaultThumbnails(t *testing.T) {
m, err := NewMediaFile(conf.ExamplesPath() + "/elephants.jpg") m, err := NewMediaFile(conf.ExamplesPath() + "/elephants.jpg")
assert.Nil(t, err) assert.Nil(t, err)
err = m.CreateDefaultThumbnails(thumbsPath, true) err = m.RenderDefaultThumbnails(thumbsPath, true)
assert.Empty(t, err) assert.Empty(t, err)
@@ -968,7 +968,7 @@ func TestMediaFile_CreateDefaultThumbnails(t *testing.T) {
assert.FileExists(t, thumbFilename) assert.FileExists(t, thumbFilename)
err = m.CreateDefaultThumbnails(thumbsPath, false) err = m.RenderDefaultThumbnails(thumbsPath, false)
assert.Empty(t, err) assert.Empty(t, err)
} }

View File

@@ -8,7 +8,7 @@ type ThumbnailsJob struct {
func thumbnailsWorker(jobs <-chan ThumbnailsJob) { func thumbnailsWorker(jobs <-chan ThumbnailsJob) {
for job := range jobs { for job := range jobs {
if err := job.mediaFile.CreateDefaultThumbnails(job.path, job.force); err != nil { if err := job.mediaFile.RenderDefaultThumbnails(job.path, job.force); err != nil {
log.Errorf("thumbs: %s", err) log.Errorf("thumbs: %s", err)
} }
} }

View File

@@ -5,9 +5,9 @@ import (
"strings" "strings"
"time" "time"
"github.com/photoprism/photoprism/pkg/capture"
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/pkg/capture"
) )
// AlbumResult contains found albums // AlbumResult contains found albums

View File

@@ -6,9 +6,9 @@ import (
"time" "time"
"github.com/gosimple/slug" "github.com/gosimple/slug"
"github.com/photoprism/photoprism/pkg/capture"
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/pkg/capture"
) )
// LabelResult contains found labels // LabelResult contains found labels

View File

@@ -6,9 +6,9 @@ import (
"time" "time"
"github.com/gosimple/slug" "github.com/gosimple/slug"
"github.com/photoprism/photoprism/pkg/capture"
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/pkg/capture"
) )
// PhotoResult contains found photos and their main file plus other meta data. // PhotoResult contains found photos and their main file plus other meta data.

View File

@@ -25,8 +25,8 @@ func ResampleOptions(opts ...ResampleOption) (method ResampleOption, filter imag
format = fs.TypePng format = fs.TypePng
case ResampleNearestNeighbor: case ResampleNearestNeighbor:
filter = imaging.NearestNeighbor filter = imaging.NearestNeighbor
case ResampleLanczos: case ResampleDefault:
filter = imaging.Lanczos filter = Algorithm.Filter()
case ResampleFillTopLeft: case ResampleFillTopLeft:
method = ResampleFillTopLeft method = ResampleFillTopLeft
case ResampleFillCenter: case ResampleFillCenter:
@@ -72,11 +72,11 @@ func Postfix(width, height int, opts ...ResampleOption) (result string) {
} }
func Filename(hash string, thumbPath string, width, height int, opts ...ResampleOption) (filename string, err error) { func Filename(hash string, thumbPath string, width, height int, opts ...ResampleOption) (filename string, err error) {
if width < 0 || width > MaxWidth { if width < 0 || width > MaxRenderSize {
return "", fmt.Errorf("thumbs: width exceeds limit (%d)", width) return "", fmt.Errorf("thumbs: width exceeds limit (%d)", width)
} }
if height < 0 || height > MaxHeight { if height < 0 || height > MaxRenderSize {
return "", fmt.Errorf("thumbs: height exceeds limit (%d)", height) return "", fmt.Errorf("thumbs: height exceeds limit (%d)", height)
} }
@@ -135,11 +135,11 @@ func FromFile(imageFilename string, hash string, thumbPath string, width, height
} }
func Create(img image.Image, fileName string, width, height int, opts ...ResampleOption) (result image.Image, err error) { func Create(img image.Image, fileName string, width, height int, opts ...ResampleOption) (result image.Image, err error) {
if width < 0 || width > MaxWidth { if width < 0 || width > MaxRenderSize {
return img, fmt.Errorf("thumbs: width has an invalid value (%d)", width) return img, fmt.Errorf("thumbs: width has an invalid value (%d)", width)
} }
if height < 0 || height > MaxHeight { if height < 0 || height > MaxRenderSize {
return img, fmt.Errorf("thumbs: height has an invalid value (%d)", height) return img, fmt.Errorf("thumbs: height has an invalid value (%d)", height)
} }

View File

@@ -1,12 +1,36 @@
package thumb package thumb
import "github.com/disintegration/imaging"
var ( var (
MaxWidth = 8192 PreRenderSize = 3840
MaxHeight = 8192 MaxRenderSize = 3840
JpegQuality = 95 JpegQuality = 95
JpegQualitySmall = 80 JpegQualitySmall = 80
Algorithm = ResampleLanczos
) )
const (
ResampleLanczos ResampleAlgorithm = "lanczos"
ResampleCubic = "cubic"
ResampleLinear = "linear"
)
type ResampleAlgorithm string
func (a ResampleAlgorithm) Filter() imaging.ResampleFilter {
switch a {
case ResampleLanczos:
return imaging.Lanczos
case ResampleCubic:
return imaging.CatmullRom
case ResampleLinear:
return imaging.Linear
default:
return imaging.Lanczos
}
}
const ( const (
ResampleFillCenter ResampleOption = iota ResampleFillCenter ResampleOption = iota
ResampleFillTopLeft ResampleFillTopLeft
@@ -14,7 +38,7 @@ const (
ResampleFit ResampleFit
ResampleResize ResampleResize
ResampleNearestNeighbor ResampleNearestNeighbor
ResampleLanczos ResampleDefault
ResamplePng ResamplePng
) )
@@ -37,21 +61,29 @@ type Type struct {
} }
var Types = map[string]Type{ var Types = map[string]Type{
"tile_50": {"tile_500", 50, 50, false, []ResampleOption{ResampleFillCenter, ResampleLanczos}}, "tile_50": {"tile_500", 50, 50, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
"tile_100": {"tile_500", 100, 100, false, []ResampleOption{ResampleFillCenter, ResampleLanczos}}, "tile_100": {"tile_500", 100, 100, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
"tile_224": {"tile_500", 224, 224, false, []ResampleOption{ResampleFillCenter, ResampleLanczos}}, "tile_224": {"tile_500", 224, 224, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
"tile_500": {"", 500, 500, false, []ResampleOption{ResampleFillCenter, ResampleLanczos}}, "tile_500": {"", 500, 500, false, []ResampleOption{ResampleFillCenter, ResampleDefault}},
"colors": {"fit_720", 3, 3, false, []ResampleOption{ResampleResize, ResampleNearestNeighbor, ResamplePng}}, "colors": {"fit_720", 3, 3, false, []ResampleOption{ResampleResize, ResampleNearestNeighbor, ResamplePng}},
"left_224": {"fit_720", 224, 224, false, []ResampleOption{ResampleFillTopLeft, ResampleLanczos}}, "left_224": {"fit_720", 224, 224, false, []ResampleOption{ResampleFillTopLeft, ResampleDefault}},
"right_224": {"fit_720", 224, 224, false, []ResampleOption{ResampleFillBottomRight, ResampleLanczos}}, "right_224": {"fit_720", 224, 224, false, []ResampleOption{ResampleFillBottomRight, ResampleDefault}},
"fit_720": {"", 720, 720, true, []ResampleOption{ResampleFit, ResampleLanczos}}, "fit_720": {"", 720, 720, true, []ResampleOption{ResampleFit, ResampleDefault}},
"fit_1280": {"fit_2048", 1280, 1024, true, []ResampleOption{ResampleFit, ResampleLanczos}}, "fit_1280": {"fit_2048", 1280, 1024, true, []ResampleOption{ResampleFit, ResampleDefault}},
"fit_1920": {"fit_2048", 1920, 1200, true, []ResampleOption{ResampleFit, ResampleLanczos}}, "fit_1920": {"fit_2048", 1920, 1200, true, []ResampleOption{ResampleFit, ResampleDefault}},
"fit_2048": {"", 2048, 2048, true, []ResampleOption{ResampleFit, ResampleLanczos}}, "fit_2048": {"", 2048, 2048, true, []ResampleOption{ResampleFit, ResampleDefault}},
"fit_2560": {"", 2560, 1600, true, []ResampleOption{ResampleFit, ResampleLanczos}}, "fit_2560": {"", 2560, 1600, true, []ResampleOption{ResampleFit, ResampleDefault}},
"fit_3840": {"", 3840, 2400, true, []ResampleOption{ResampleFit, ResampleLanczos}}, "fit_3840": {"", 3840, 2400, true, []ResampleOption{ResampleFit, ResampleDefault}},
} }
var DefaultTypes = []string{ var DefaultTypes = []string{
"fit_3840", "fit_2560", "fit_2048", "fit_1920", "fit_1280", "fit_720", "right_224", "left_224", "colors", "tile_500", "tile_224", "tile_100", "tile_50", "fit_3840", "fit_2560", "fit_2048", "fit_1920", "fit_1280", "fit_720", "right_224", "left_224", "colors", "tile_500", "tile_224", "tile_100", "tile_50",
} }
func (t Type) ExceedsLimit() bool {
return t.Width > MaxRenderSize || t.Height > MaxRenderSize
}
func (t Type) SkipPreRender() bool {
return t.Width > PreRenderSize || t.Height > PreRenderSize
}