From ae98a18b5295fb3c7608c09111a73bf3376158e9 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Fri, 24 Jul 2020 16:47:15 +0200 Subject: [PATCH] add album cover scaling and caching --- go.mod | 1 + go.sum | 3 ++ server/ctrlsubsonic/handlers_raw.go | 68 +++++++++++++++++++++-------- 3 files changed, 55 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index f364b7b..21cc728 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ require ( github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/sprig v2.22.0+incompatible github.com/cespare/xxhash v1.1.0 + github.com/disintegration/imaging v1.6.2 github.com/dustin/go-humanize v1.0.0 github.com/faiface/beep v1.0.2 github.com/google/uuid v1.1.1 // indirect diff --git a/go.sum b/go.sum index 5e28c02..5341869 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= +github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= @@ -169,6 +171,7 @@ golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMx golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 h1:KYGJGHOQy8oSi1fDlSpcZF0+juKwk/hEMv5SiwHogR0= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw= golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/mobile v0.0.0-20180806140643-507816974b79 h1:t2JRgCWkY7Qaa1J2jal+wqC9OjbyHCHwIA9rVlRUSMo= diff --git a/server/ctrlsubsonic/handlers_raw.go b/server/ctrlsubsonic/handlers_raw.go index 461a9bb..46e16a9 100644 --- a/server/ctrlsubsonic/handlers_raw.go +++ b/server/ctrlsubsonic/handlers_raw.go @@ -1,12 +1,15 @@ package ctrlsubsonic import ( + "fmt" "io" "log" "net/http" + "os" "path" "time" + "github.com/disintegration/imaging" "github.com/jinzhu/gorm" "go.senan.xyz/gonic/server/ctrlsubsonic/params" @@ -53,30 +56,61 @@ func streamUpdateStats(dbc *db.DB, userID, albumID int) { dbc.Save(&play) } +const ( + coverDefaultSize = 600 + coverCacheFormat = "png" +) + func (c *Controller) ServeGetCoverArt(w http.ResponseWriter, r *http.Request) *spec.Response { params := r.Context().Value(CtxParams).(params.Params) id, err := params.GetID("id") if err != nil { return spec.NewError(10, "please provide an `id` parameter") } - folder := &db.Album{} - err = c.DB. - Select("id, left_path, right_path, cover"). - First(folder, id.Value). - Error - if gorm.IsRecordNotFoundError(err) { - return spec.NewError(10, "could not find a cover with that id") + size := params.GetOrInt("size", coverDefaultSize) + cacheFile := fmt.Sprintf("%s/%s-%d.%s", c.CachePath, id.String(), size, + coverCacheFormat) + _, err = os.Stat(cacheFile) + if os.IsNotExist(err) { + log.Printf("serving cover `%s`: cache [%s/%d] miss!\n", + cacheFile, coverCacheFormat, size) + folder := &db.Album{} + err = c.DB. + Select("id, left_path, right_path, cover"). + First(folder, id.Value). + Error + if gorm.IsRecordNotFoundError(err) { + return spec.NewError(10, "could not find a cover with that id") + } + if folder.Cover == "" { + return spec.NewError(10, "no cover found for that folder") + } + absPath := path.Join( + c.MusicPath, + folder.LeftPath, + folder.RightPath, + folder.Cover, + ) + src, err := imaging.Open(absPath) + if err != nil { + log.Printf("resizing cover `%s`: error: %v\n", absPath, err) + return nil + } + width := size + if width > src.Bounds().Dx() { + // don't upscale images + width = src.Bounds().Dx() + } + err = imaging.Save(imaging.Resize(src, width, 0, imaging.Lanczos), cacheFile) + if err != nil { + log.Printf("caching cover `%s`: error: %v\n", cacheFile, err) + return nil + } + } else { + log.Printf("serving cover `%s`: cache [%s/%d] hit!\n", + cacheFile, coverCacheFormat, size) } - if folder.Cover == "" { - return spec.NewError(10, "no cover found for that folder") - } - absPath := path.Join( - c.MusicPath, - folder.LeftPath, - folder.RightPath, - folder.Cover, - ) - http.ServeFile(w, r, absPath) + http.ServeFile(w, r, cacheFile) return nil }