mirror of https://github.com/stashapp/stash.git
Add default thumbnails for scenes and images (#2949)
* Use default thumbnail for scene covers * Use defautl thumbnail for image thumbnails
This commit is contained in:
parent
88bfda1980
commit
6ba9f55df0
|
@ -3,6 +3,8 @@ package api
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
|
@ -10,6 +12,7 @@ import (
|
|||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/stashapp/stash/internal/manager"
|
||||
"github.com/stashapp/stash/internal/static"
|
||||
"github.com/stashapp/stash/pkg/file"
|
||||
"github.com/stashapp/stash/pkg/fsutil"
|
||||
"github.com/stashapp/stash/pkg/image"
|
||||
|
@ -55,11 +58,11 @@ func (rs imageRoutes) Thumbnail(w http.ResponseWriter, r *http.Request) {
|
|||
if exists {
|
||||
http.ServeFile(w, r, filepath)
|
||||
} else {
|
||||
// don't return anything if there is no file
|
||||
const useDefault = true
|
||||
|
||||
f := img.Files.Primary()
|
||||
if f == nil {
|
||||
// TODO - probably want to return a placeholder
|
||||
http.Error(w, http.StatusText(404), 404)
|
||||
rs.serveImage(w, r, img, useDefault)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -67,7 +70,8 @@ func (rs imageRoutes) Thumbnail(w http.ResponseWriter, r *http.Request) {
|
|||
data, err := encoder.GetThumbnail(f, models.DefaultGthumbWidth)
|
||||
if err != nil {
|
||||
// don't log for unsupported image format
|
||||
if !errors.Is(err, image.ErrNotSupportedForThumbnail) {
|
||||
// don't log for file not found - can optionally be logged in serveImage
|
||||
if !errors.Is(err, image.ErrNotSupportedForThumbnail) && !errors.Is(err, fs.ErrNotExist) {
|
||||
logger.Errorf("error generating thumbnail for %s: %v", f.Path, err)
|
||||
|
||||
var exitErr *exec.ExitError
|
||||
|
@ -77,7 +81,7 @@ func (rs imageRoutes) Thumbnail(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
// backwards compatibility - fallback to original image instead
|
||||
rs.Image(w, r)
|
||||
rs.serveImage(w, r, img, useDefault)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -97,14 +101,38 @@ func (rs imageRoutes) Thumbnail(w http.ResponseWriter, r *http.Request) {
|
|||
func (rs imageRoutes) Image(w http.ResponseWriter, r *http.Request) {
|
||||
i := r.Context().Value(imageKey).(*models.Image)
|
||||
|
||||
// if image is in a zip file, we need to serve it specifically
|
||||
const useDefault = false
|
||||
rs.serveImage(w, r, i, useDefault)
|
||||
}
|
||||
|
||||
if i.Files.Primary() == nil {
|
||||
func (rs imageRoutes) serveImage(w http.ResponseWriter, r *http.Request, i *models.Image, useDefault bool) {
|
||||
const defaultImageImage = "image/image.svg"
|
||||
|
||||
if i.Files.Primary() != nil {
|
||||
err := i.Files.Primary().Serve(&file.OsFS{}, w, r)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !useDefault {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// only log in debug since it can get noisy
|
||||
logger.Debugf("Error serving %s: %v", i.DisplayName(), err)
|
||||
}
|
||||
|
||||
if !useDefault {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
i.Files.Primary().Serve(&file.OsFS{}, w, r)
|
||||
// fall back to static image
|
||||
f, _ := static.Image.Open(defaultImageImage)
|
||||
defer f.Close()
|
||||
stat, _ := f.Stat()
|
||||
http.ServeContent(w, r, "image.svg", stat.ModTime(), f.(io.ReadSeeker))
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
|
|
@ -3,9 +3,11 @@ package manager
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/stashapp/stash/internal/manager/config"
|
||||
"github.com/stashapp/stash/internal/static"
|
||||
"github.com/stashapp/stash/pkg/fsutil"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
|
@ -74,29 +76,41 @@ func (s *SceneServer) StreamSceneDirect(scene *models.Scene, w http.ResponseWrit
|
|||
}
|
||||
|
||||
func (s *SceneServer) ServeScreenshot(scene *models.Scene, w http.ResponseWriter, r *http.Request) {
|
||||
const defaultSceneImage = "scene/scene.svg"
|
||||
|
||||
filepath := GetInstance().Paths.Scene.GetScreenshotPath(scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm()))
|
||||
|
||||
// fall back to the scene image blob if the file isn't present
|
||||
screenshotExists, _ := fsutil.FileExists(filepath)
|
||||
if screenshotExists {
|
||||
http.ServeFile(w, r, filepath)
|
||||
} else {
|
||||
var cover []byte
|
||||
readTxnErr := txn.WithTxn(r.Context(), s.TxnManager, func(ctx context.Context) error {
|
||||
cover, _ = s.SceneCoverGetter.GetCover(ctx, scene.ID)
|
||||
return nil
|
||||
})
|
||||
if errors.Is(readTxnErr, context.Canceled) {
|
||||
return
|
||||
}
|
||||
if readTxnErr != nil {
|
||||
logger.Warnf("read transaction error on fetch screenshot: %v", readTxnErr)
|
||||
http.Error(w, readTxnErr.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := utils.ServeImage(cover, w, r); err != nil {
|
||||
logger.Warnf("error serving screenshot image: %v", err)
|
||||
}
|
||||
var cover []byte
|
||||
readTxnErr := txn.WithTxn(r.Context(), s.TxnManager, func(ctx context.Context) error {
|
||||
cover, _ = s.SceneCoverGetter.GetCover(ctx, scene.ID)
|
||||
return nil
|
||||
})
|
||||
if errors.Is(readTxnErr, context.Canceled) {
|
||||
return
|
||||
}
|
||||
if readTxnErr != nil {
|
||||
logger.Warnf("read transaction error on fetch screenshot: %v", readTxnErr)
|
||||
http.Error(w, readTxnErr.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if cover == nil {
|
||||
// fallback to default cover if none found
|
||||
// should always be there
|
||||
f, _ := static.Scene.Open(defaultSceneImage)
|
||||
defer f.Close()
|
||||
stat, _ := f.Stat()
|
||||
http.ServeContent(w, r, "scene.svg", stat.ModTime(), f.(io.ReadSeeker))
|
||||
}
|
||||
|
||||
if err := utils.ServeImage(cover, w, r); err != nil {
|
||||
logger.Warnf("error serving screenshot image: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,3 +7,9 @@ var Performer embed.FS
|
|||
|
||||
//go:embed performer_male
|
||||
var PerformerMale embed.FS
|
||||
|
||||
//go:embed scene
|
||||
var Scene embed.FS
|
||||
|
||||
//go:embed image
|
||||
var Image embed.FS
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-384 -104 1280 720">
|
||||
<!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc.
|
||||
Modified from https://github.com/FortAwesome/Font-Awesome/blob/6.x/svgs/solid/image.svg
|
||||
Changed view box and fill style.
|
||||
-->
|
||||
<path d="M0 96C0 60.7 28.7 32 64 32H448c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6h96 32H424c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192c26.5 0 48-21.5 48-48s-21.5-48-48-48s-48 21.5-48 48s21.5 48 48 48z" style="fill:#ffffff;fill-opacity:1"/>
|
||||
</svg>
|
After Width: | Height: | Size: 880 B |
|
@ -0,0 +1,7 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-384 -104 1280 720">
|
||||
<!--! Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc.
|
||||
Modified from https://github.com/FortAwesome/Font-Awesome/blob/6.x/svgs/solid/circle-play.svg
|
||||
Changed view box and added fill style.
|
||||
-->
|
||||
<path d="M512 256c0 141.4-114.6 256-256 256S0 397.4 0 256S114.6 0 256 0S512 114.6 512 256zM188.3 147.1c-7.6 4.2-12.3 12.3-12.3 20.9V344c0 8.7 4.7 16.7 12.3 20.9s16.8 4.1 24.3-.5l144-88c7.1-4.4 11.5-12.1 11.5-20.5s-4.4-16.1-11.5-20.5l-144-88c-7.4-4.5-16.7-4.7-24.3-.5z" style="fill:#ffffff;fill-opacity:1"/>
|
||||
</svg>
|
After Width: | Height: | Size: 733 B |
|
@ -118,14 +118,12 @@ func (f *BaseFile) Info(fs FS) (fs.FileInfo, error) {
|
|||
return f.info(fs, f.Path)
|
||||
}
|
||||
|
||||
func (f *BaseFile) Serve(fs FS, w http.ResponseWriter, r *http.Request) {
|
||||
func (f *BaseFile) Serve(fs FS, w http.ResponseWriter, r *http.Request) error {
|
||||
w.Header().Add("Cache-Control", "max-age=604800000") // 1 Week
|
||||
|
||||
reader, err := f.Open(fs)
|
||||
if err != nil {
|
||||
// assume not found
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
defer reader.Close()
|
||||
|
@ -135,8 +133,7 @@ func (f *BaseFile) Serve(fs FS, w http.ResponseWriter, r *http.Request) {
|
|||
// fallback to direct copy
|
||||
data, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
k, err := w.Write(data)
|
||||
|
@ -144,10 +141,11 @@ func (f *BaseFile) Serve(fs FS, w http.ResponseWriter, r *http.Request) {
|
|||
logger.Warnf("error serving file (wrote %v bytes out of %v): %v", k, len(data), err)
|
||||
}
|
||||
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
http.ServeContent(w, r, f.Basename, f.ModTime, rsc)
|
||||
return nil
|
||||
}
|
||||
|
||||
type Finder interface {
|
||||
|
|
Loading…
Reference in New Issue