Generate cover image (#376)

* Make mutating metadata ops mutation
* Implement scene generate screenshot
* Remove fetch policy on metadata mutations
* Port UI changes to v2.5
* Set generated image in database
This commit is contained in:
WithoutPants
2020-03-12 08:34:04 +11:00
committed by GitHub
parent 34d829338d
commit 3de6955a9e
28 changed files with 442 additions and 168 deletions

View File

@@ -0,0 +1,53 @@
package api
import (
"context"
"github.com/stashapp/stash/pkg/manager"
"github.com/stashapp/stash/pkg/models"
)
func (r *mutationResolver) MetadataScan(ctx context.Context, input models.ScanMetadataInput) (string, error) {
manager.GetInstance().Scan(input.UseFileMetadata)
return "todo", nil
}
func (r *mutationResolver) MetadataImport(ctx context.Context) (string, error) {
manager.GetInstance().Import()
return "todo", nil
}
func (r *mutationResolver) MetadataExport(ctx context.Context) (string, error) {
manager.GetInstance().Export()
return "todo", nil
}
func (r *mutationResolver) MetadataGenerate(ctx context.Context, input models.GenerateMetadataInput) (string, error) {
manager.GetInstance().Generate(input.Sprites, input.Previews, input.Markers, input.Transcodes)
return "todo", nil
}
func (r *mutationResolver) MetadataAutoTag(ctx context.Context, input models.AutoTagMetadataInput) (string, error) {
manager.GetInstance().AutoTag(input.Performers, input.Studios, input.Tags)
return "todo", nil
}
func (r *mutationResolver) MetadataClean(ctx context.Context) (string, error) {
manager.GetInstance().Clean()
return "todo", nil
}
func (r *mutationResolver) JobStatus(ctx context.Context) (*models.MetadataUpdateStatus, error) {
status := manager.GetInstance().Status
ret := models.MetadataUpdateStatus{
Progress: status.Progress,
Status: status.Status.String(),
Message: "",
}
return &ret, nil
}
func (r *mutationResolver) StopJob(ctx context.Context) (bool, error) {
return manager.GetInstance().Status.Stop(), nil
}

View File

@@ -500,3 +500,13 @@ func (r *mutationResolver) SceneResetO(ctx context.Context, id string) (int, err
return newVal, nil
}
func (r *mutationResolver) SceneGenerateScreenshot(ctx context.Context, id string, at *float64) (string, error) {
if at != nil {
manager.GetInstance().GenerateScreenshot(id, *at)
} else {
manager.GetInstance().GenerateDefaultScreenshot(id)
}
return "todo", nil
}

View File

@@ -7,36 +7,6 @@ import (
"github.com/stashapp/stash/pkg/models"
)
func (r *queryResolver) MetadataScan(ctx context.Context, input models.ScanMetadataInput) (string, error) {
manager.GetInstance().Scan(input.UseFileMetadata)
return "todo", nil
}
func (r *queryResolver) MetadataImport(ctx context.Context) (string, error) {
manager.GetInstance().Import()
return "todo", nil
}
func (r *queryResolver) MetadataExport(ctx context.Context) (string, error) {
manager.GetInstance().Export()
return "todo", nil
}
func (r *queryResolver) MetadataGenerate(ctx context.Context, input models.GenerateMetadataInput) (string, error) {
manager.GetInstance().Generate(input.Sprites, input.Previews, input.Markers, input.Transcodes)
return "todo", nil
}
func (r *queryResolver) MetadataAutoTag(ctx context.Context, input models.AutoTagMetadataInput) (string, error) {
manager.GetInstance().AutoTag(input.Performers, input.Studios, input.Tags)
return "todo", nil
}
func (r *queryResolver) MetadataClean(ctx context.Context) (string, error) {
manager.GetInstance().Clean()
return "todo", nil
}
func (r *queryResolver) JobStatus(ctx context.Context) (*models.MetadataUpdateStatus, error) {
status := manager.GetInstance().Status
ret := models.MetadataUpdateStatus{
@@ -47,7 +17,3 @@ func (r *queryResolver) JobStatus(ctx context.Context) (*models.MetadataUpdateSt
return &ret, nil
}
func (r *queryResolver) StopJob(ctx context.Context) (bool, error) {
return manager.GetInstance().Status.Stop(), nil
}

View File

@@ -10,7 +10,7 @@ type ScreenshotOptions struct {
Verbosity string
}
func (e *Encoder) Screenshot(probeResult VideoFile, options ScreenshotOptions) {
func (e *Encoder) Screenshot(probeResult VideoFile, options ScreenshotOptions) error {
if options.Verbosity == "" {
options.Verbosity = "error"
}
@@ -28,5 +28,7 @@ func (e *Encoder) Screenshot(probeResult VideoFile, options ScreenshotOptions) {
"-f", "image2",
options.OutputPath,
}
_, _ = e.run(probeResult, args)
_, err := e.run(probeResult, args)
return err
}

View File

@@ -216,6 +216,55 @@ func (s *singleton) Generate(sprites bool, previews bool, markers bool, transcod
}()
}
func (s *singleton) GenerateDefaultScreenshot(sceneId string) {
s.generateScreenshot(sceneId, nil)
}
func (s *singleton) GenerateScreenshot(sceneId string, at float64) {
s.generateScreenshot(sceneId, &at)
}
// generate default screenshot if at is nil
func (s *singleton) generateScreenshot(sceneId string, at *float64) {
if s.Status.Status != Idle {
return
}
s.Status.SetStatus(Generate)
s.Status.indefiniteProgress()
qb := models.NewSceneQueryBuilder()
instance.Paths.Generated.EnsureTmpDir()
go func() {
defer s.returnToIdleState()
sceneIdInt, err := strconv.Atoi(sceneId)
if err != nil {
logger.Errorf("Error parsing scene id %s: %s", sceneId, err.Error())
return
}
scene, err := qb.Find(sceneIdInt)
if err != nil || scene == nil {
logger.Errorf("failed to get scene for generate")
return
}
task := GenerateScreenshotTask{
Scene: *scene,
ScreenshotAt: at,
}
var wg sync.WaitGroup
wg.Add(1)
go task.Start(&wg)
wg.Wait()
logger.Infof("Generate finished")
}()
}
func (s *singleton) AutoTag(performerIds []string, studioIds []string, tagIds []string) {
if s.Status.Status != Idle {
return

16
pkg/manager/screenshot.go Normal file
View File

@@ -0,0 +1,16 @@
package manager
import (
"github.com/stashapp/stash/pkg/ffmpeg"
)
func makeScreenshot(probeResult ffmpeg.VideoFile, outputPath string, quality int, width int, time float64) {
encoder := ffmpeg.NewEncoder(instance.FFMPEGPath)
options := ffmpeg.ScreenshotOptions{
OutputPath: outputPath,
Quality: quality,
Time: time,
Width: width,
}
encoder.Screenshot(probeResult, options)
}

View File

@@ -0,0 +1,84 @@
package manager
import (
"context"
"io/ioutil"
"os"
"sync"
"time"
"github.com/stashapp/stash/pkg/database"
"github.com/stashapp/stash/pkg/ffmpeg"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
)
type GenerateScreenshotTask struct {
Scene models.Scene
ScreenshotAt *float64
}
func (t *GenerateScreenshotTask) Start(wg *sync.WaitGroup) {
defer wg.Done()
scenePath := t.Scene.Path
probeResult, err := ffmpeg.NewVideoFile(instance.FFProbePath, scenePath)
if err != nil {
logger.Error(err.Error())
return
}
var at float64
if t.ScreenshotAt == nil {
at = float64(probeResult.Duration) * 0.2
} else {
at = *t.ScreenshotAt
}
checksum := t.Scene.Checksum
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
logger.Debugf("Creating screenshot for %s", scenePath)
makeScreenshot(*probeResult, normalPath, 2, probeResult.Width, at)
f, err := os.Open(normalPath)
if err != nil {
logger.Errorf("Error reading screenshot: %s", err.Error())
return
}
defer f.Close()
coverImageData, err := ioutil.ReadAll(f)
if err != nil {
logger.Errorf("Error reading screenshot: %s", err.Error())
return
}
ctx := context.TODO()
tx := database.DB.MustBeginTx(ctx, nil)
qb := models.NewSceneQueryBuilder()
updatedTime := time.Now()
updatedScene := models.ScenePartial{
ID: t.Scene.ID,
UpdatedAt: &models.SQLiteTimestamp{Timestamp: updatedTime},
}
updatedScene.Cover = &coverImageData
err = SetSceneScreenshot(t.Scene.Checksum, coverImageData)
_, err = qb.Update(updatedScene, tx)
if err != nil {
logger.Errorf("Error setting screenshot: %s", err.Error())
return
}
if err := tx.Commit(); err != nil {
logger.Errorf("Error setting screenshot: %s", err.Error())
return
}
}

View File

@@ -177,28 +177,19 @@ func (t *ScanTask) makeScreenshots(probeResult *ffmpeg.VideoFile, checksum strin
logger.Infof("Regenerating images for %s", t.FilePath)
}
at := float64(probeResult.Duration) * 0.2
if !thumbExists {
logger.Debugf("Creating thumbnail for %s", t.FilePath)
t.makeScreenshot(*probeResult, thumbPath, 5, 320)
makeScreenshot(*probeResult, thumbPath, 5, 320, at)
}
if !normalExists {
logger.Debugf("Creating screenshot for %s", t.FilePath)
t.makeScreenshot(*probeResult, normalPath, 2, probeResult.Width)
makeScreenshot(*probeResult, normalPath, 2, probeResult.Width, at)
}
}
func (t *ScanTask) makeScreenshot(probeResult ffmpeg.VideoFile, outputPath string, quality int, width int) {
encoder := ffmpeg.NewEncoder(instance.FFMPEGPath)
options := ffmpeg.ScreenshotOptions{
OutputPath: outputPath,
Quality: quality,
Time: float64(probeResult.Duration) * 0.2,
Width: width,
}
encoder.Screenshot(probeResult, options)
}
func (t *ScanTask) calculateChecksum() (string, error) {
logger.Infof("%s not found. Calculating checksum...", t.FilePath)
checksum, err := utils.MD5FromFilePath(t.FilePath)