mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 04:44:37 +03:00
Add filesystem based blob storage (#3187)
* Refactor transaction hooks. Add preCommit * Add BlobStore * Use blobStore for tag images * Use blobStore for studio images * Use blobStore for performer images * Use blobStore for scene covers * Don't generate screenshots in legacy directory * Run post-hooks outside original transaction * Use blobStore for movie images * Remove unnecessary DestroyImage methods * Add missing filter for scene cover * Add covers to generate options * Add generate cover option to UI * Add screenshot migration * Delete thumb files as part of screenshot migration
This commit is contained in:
@@ -3,25 +3,32 @@ package manager
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/scene"
|
||||
"github.com/stashapp/stash/pkg/scene/generate"
|
||||
)
|
||||
|
||||
type GenerateScreenshotTask struct {
|
||||
Scene models.Scene
|
||||
ScreenshotAt *float64
|
||||
fileNamingAlgorithm models.HashAlgorithm
|
||||
txnManager Repository
|
||||
type GenerateCoverTask struct {
|
||||
Scene models.Scene
|
||||
ScreenshotAt *float64
|
||||
txnManager Repository
|
||||
Overwrite bool
|
||||
}
|
||||
|
||||
func (t *GenerateScreenshotTask) Start(ctx context.Context) {
|
||||
func (t *GenerateCoverTask) GetDescription() string {
|
||||
return fmt.Sprintf("Generating cover for %s", t.Scene.GetTitle())
|
||||
}
|
||||
|
||||
func (t *GenerateCoverTask) Start(ctx context.Context) {
|
||||
scenePath := t.Scene.Path
|
||||
|
||||
if err := t.txnManager.WithReadTxn(ctx, func(ctx context.Context) error {
|
||||
return t.Scene.LoadPrimaryFile(ctx, t.txnManager.File)
|
||||
}); err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
|
||||
videoFile := t.Scene.Files.Primary()
|
||||
if videoFile == nil {
|
||||
return
|
||||
@@ -34,12 +41,8 @@ func (t *GenerateScreenshotTask) Start(ctx context.Context) {
|
||||
at = *t.ScreenshotAt
|
||||
}
|
||||
|
||||
checksum := t.Scene.GetHash(t.fileNamingAlgorithm)
|
||||
normalPath := instance.Paths.Scene.GetScreenshotPath(checksum)
|
||||
|
||||
// we'll generate the screenshot, grab the generated data and set it
|
||||
// in the database. We'll use SetSceneScreenshot to set the data
|
||||
// which also generates the thumbnail
|
||||
// in the database.
|
||||
|
||||
logger.Debugf("Creating screenshot for %s", scenePath)
|
||||
|
||||
@@ -51,35 +54,19 @@ func (t *GenerateScreenshotTask) Start(ctx context.Context) {
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
if err := g.Screenshot(context.TODO(), videoFile.Path, checksum, videoFile.Width, videoFile.Duration, generate.ScreenshotOptions{
|
||||
coverImageData, err := g.Screenshot(context.TODO(), videoFile.Path, videoFile.Width, videoFile.Duration, generate.ScreenshotOptions{
|
||||
At: &at,
|
||||
}); err != nil {
|
||||
})
|
||||
if err != nil {
|
||||
logger.Errorf("Error generating screenshot: %v", err)
|
||||
logErrorOutput(err)
|
||||
return
|
||||
}
|
||||
|
||||
f, err := os.Open(normalPath)
|
||||
if err != nil {
|
||||
logger.Errorf("Error reading screenshot: %s", err.Error())
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
coverImageData, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
logger.Errorf("Error reading screenshot: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := t.txnManager.WithTxn(ctx, func(ctx context.Context) error {
|
||||
qb := t.txnManager.Scene
|
||||
updatedScene := models.NewScenePartial()
|
||||
|
||||
if err := scene.SetScreenshot(instance.Paths, checksum, coverImageData); err != nil {
|
||||
return fmt.Errorf("error writing screenshot: %v", err)
|
||||
}
|
||||
|
||||
// update the scene cover table
|
||||
if err := qb.UpdateCover(ctx, t.Scene.ID, coverImageData); err != nil {
|
||||
return fmt.Errorf("error setting screenshot: %v", err)
|
||||
@@ -96,3 +83,19 @@ func (t *GenerateScreenshotTask) Start(ctx context.Context) {
|
||||
logger.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// required returns true if the sprite needs to be generated
|
||||
func (t GenerateCoverTask) required(ctx context.Context) bool {
|
||||
if t.Overwrite {
|
||||
return true
|
||||
}
|
||||
|
||||
// if the scene has a cover, then we don't need to generate it
|
||||
hasCover, err := t.txnManager.Scene.HasCover(ctx, t.Scene.ID)
|
||||
if err != nil {
|
||||
logger.Errorf("Error getting cover: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
return !hasCover
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user