From 3de6955a9ed1f84af96105a600b9accbbe8a523c Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Thu, 12 Mar 2020 08:34:04 +1100 Subject: [PATCH] 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 --- graphql/documents/mutations/metadata.graphql | 27 ++++++ graphql/documents/mutations/scene.graphql | 6 +- .../queries/settings/metadata.graphql | 28 ------- graphql/schema/schema.graphql | 32 +++---- pkg/api/resolver_mutation_metadata.go | 53 ++++++++++++ pkg/api/resolver_mutation_scene.go | 10 +++ pkg/api/resolver_query_metadata.go | 34 -------- pkg/ffmpeg/encoder_screenshot.go | 6 +- pkg/manager/manager_tasks.go | 49 +++++++++++ pkg/manager/screenshot.go | 16 ++++ pkg/manager/task_generate_screenshot.go | 84 +++++++++++++++++++ pkg/manager/task_scan.go | 17 +--- .../PerformerOperationsPanel.tsx | 2 +- .../components/Scenes/SceneDetails/Scene.tsx | 6 ++ .../SceneDetails/SceneOperationsPanel.tsx | 36 ++++++++ .../SettingsTasksPanel/GenerateButton.tsx | 2 +- .../SettingsTasksPanel/SettingsTasksPanel.tsx | 12 +-- .../Studios/StudioDetails/Studio.tsx | 2 +- ui/v2.5/src/components/Tags/TagList.tsx | 2 +- ui/v2.5/src/core/StashService.ts | 55 ++++++------ .../SettingsTasksPanel/GenerateButton.tsx | 2 +- .../SettingsTasksPanel/SettingsTasksPanel.tsx | 12 +-- .../Studios/StudioDetails/Studio.tsx | 2 +- ui/v2/src/components/Tags/TagList.tsx | 2 +- .../PerformerOperationsPanel.tsx | 2 +- .../components/scenes/SceneDetails/Scene.tsx | 9 ++ .../SceneDetails/SceneOperationsPanel.tsx | 47 +++++++++++ ui/v2/src/core/StashService.ts | 55 ++++++------ 28 files changed, 442 insertions(+), 168 deletions(-) create mode 100644 graphql/documents/mutations/metadata.graphql create mode 100644 pkg/api/resolver_mutation_metadata.go create mode 100644 pkg/manager/screenshot.go create mode 100644 pkg/manager/task_generate_screenshot.go create mode 100644 ui/v2.5/src/components/Scenes/SceneDetails/SceneOperationsPanel.tsx create mode 100644 ui/v2/src/components/scenes/SceneDetails/SceneOperationsPanel.tsx diff --git a/graphql/documents/mutations/metadata.graphql b/graphql/documents/mutations/metadata.graphql new file mode 100644 index 000000000..d02f10b09 --- /dev/null +++ b/graphql/documents/mutations/metadata.graphql @@ -0,0 +1,27 @@ +mutation MetadataImport { + metadataImport +} + +mutation MetadataExport { + metadataExport +} + +mutation MetadataScan($input: ScanMetadataInput!) { + metadataScan(input: $input) +} + +mutation MetadataGenerate($input: GenerateMetadataInput!) { + metadataGenerate(input: $input) +} + +mutation MetadataAutoTag($input: AutoTagMetadataInput!) { + metadataAutoTag(input: $input) +} + +mutation MetadataClean { + metadataClean +} + +mutation StopJob { + stopJob +} \ No newline at end of file diff --git a/graphql/documents/mutations/scene.graphql b/graphql/documents/mutations/scene.graphql index f3c0fe00d..df19c8e8c 100644 --- a/graphql/documents/mutations/scene.graphql +++ b/graphql/documents/mutations/scene.graphql @@ -78,4 +78,8 @@ mutation SceneResetO($id: ID!) { mutation SceneDestroy($id: ID!, $delete_file: Boolean, $delete_generated : Boolean) { sceneDestroy(input: {id: $id, delete_file: $delete_file, delete_generated: $delete_generated}) -} \ No newline at end of file +} + +mutation SceneGenerateScreenshot($id: ID!, $at: Float) { + sceneGenerateScreenshot(id: $id, at: $at) +} diff --git a/graphql/documents/queries/settings/metadata.graphql b/graphql/documents/queries/settings/metadata.graphql index a9092b8ea..376f8e4a0 100644 --- a/graphql/documents/queries/settings/metadata.graphql +++ b/graphql/documents/queries/settings/metadata.graphql @@ -1,27 +1,3 @@ -query MetadataImport { - metadataImport -} - -query MetadataExport { - metadataExport -} - -query MetadataScan($input: ScanMetadataInput!) { - metadataScan(input: $input) -} - -query MetadataGenerate($input: GenerateMetadataInput!) { - metadataGenerate(input: $input) -} - -query MetadataAutoTag($input: AutoTagMetadataInput!) { - metadataAutoTag(input: $input) -} - -query MetadataClean { - metadataClean -} - query JobStatus { jobStatus { progress @@ -29,7 +5,3 @@ query JobStatus { message } } - -query StopJob { - stopJob -} \ No newline at end of file diff --git a/graphql/schema/schema.graphql b/graphql/schema/schema.graphql index 1382fb92f..201ed1d6e 100644 --- a/graphql/schema/schema.graphql +++ b/graphql/schema/schema.graphql @@ -77,21 +77,7 @@ type Query { # Metadata - """Start an import. Returns the job ID""" - metadataImport: String! - """Start an export. Returns the job ID""" - metadataExport: String! - """Start a scan. Returns the job ID""" - metadataScan(input: ScanMetadataInput!): String! - """Start generating content. Returns the job ID""" - metadataGenerate(input: GenerateMetadataInput!): String! - """Start auto-tagging. Returns the job ID""" - metadataAutoTag(input: AutoTagMetadataInput!): String! - """Clean metadata. Returns the job ID""" - metadataClean: String! - jobStatus: MetadataUpdateStatus! - stopJob: Boolean! # Get everything @@ -120,6 +106,9 @@ type Mutation { """Resets the o-counter for a scene to 0. Returns the new value""" sceneResetO(id: ID!): Int! + """Generates screenshot at specified time in seconds. Leave empty to generate default screenshot""" + sceneGenerateScreenshot(id: ID!, at: Float): String! + sceneMarkerCreate(input: SceneMarkerCreateInput!): SceneMarker sceneMarkerUpdate(input: SceneMarkerUpdateInput!): SceneMarker sceneMarkerDestroy(id: ID!): Boolean! @@ -143,6 +132,21 @@ type Mutation { """Change general configuration options""" configureGeneral(input: ConfigGeneralInput!): ConfigGeneralResult! configureInterface(input: ConfigInterfaceInput!): ConfigInterfaceResult! + + """Start an import. Returns the job ID""" + metadataImport: String! + """Start an export. Returns the job ID""" + metadataExport: String! + """Start a scan. Returns the job ID""" + metadataScan(input: ScanMetadataInput!): String! + """Start generating content. Returns the job ID""" + metadataGenerate(input: GenerateMetadataInput!): String! + """Start auto-tagging. Returns the job ID""" + metadataAutoTag(input: AutoTagMetadataInput!): String! + """Clean metadata. Returns the job ID""" + metadataClean: String! + + stopJob: Boolean! } type Subscription { diff --git a/pkg/api/resolver_mutation_metadata.go b/pkg/api/resolver_mutation_metadata.go new file mode 100644 index 000000000..aeec1ef96 --- /dev/null +++ b/pkg/api/resolver_mutation_metadata.go @@ -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 +} diff --git a/pkg/api/resolver_mutation_scene.go b/pkg/api/resolver_mutation_scene.go index 6b4b643a3..ac920954b 100644 --- a/pkg/api/resolver_mutation_scene.go +++ b/pkg/api/resolver_mutation_scene.go @@ -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 +} diff --git a/pkg/api/resolver_query_metadata.go b/pkg/api/resolver_query_metadata.go index ad8dcbf3e..862d91eae 100644 --- a/pkg/api/resolver_query_metadata.go +++ b/pkg/api/resolver_query_metadata.go @@ -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 -} diff --git a/pkg/ffmpeg/encoder_screenshot.go b/pkg/ffmpeg/encoder_screenshot.go index f9923cf1e..bd1926ba3 100644 --- a/pkg/ffmpeg/encoder_screenshot.go +++ b/pkg/ffmpeg/encoder_screenshot.go @@ -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 } diff --git a/pkg/manager/manager_tasks.go b/pkg/manager/manager_tasks.go index db6da95c4..169372036 100644 --- a/pkg/manager/manager_tasks.go +++ b/pkg/manager/manager_tasks.go @@ -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 diff --git a/pkg/manager/screenshot.go b/pkg/manager/screenshot.go new file mode 100644 index 000000000..fc417ede7 --- /dev/null +++ b/pkg/manager/screenshot.go @@ -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) +} diff --git a/pkg/manager/task_generate_screenshot.go b/pkg/manager/task_generate_screenshot.go new file mode 100644 index 000000000..c4360b482 --- /dev/null +++ b/pkg/manager/task_generate_screenshot.go @@ -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 + } +} diff --git a/pkg/manager/task_scan.go b/pkg/manager/task_scan.go index 579a3fc00..ecdb995ee 100644 --- a/pkg/manager/task_scan.go +++ b/pkg/manager/task_scan.go @@ -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) diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerOperationsPanel.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerOperationsPanel.tsx index 5a8ab7e43..bb5bd1de8 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerOperationsPanel.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerOperationsPanel.tsx @@ -18,7 +18,7 @@ export const PerformerOperationsPanel: React.FC = ({ return; } try { - await StashService.queryMetadataAutoTag({ performers: [performer.id] }); + await StashService.mutateMetadataAutoTag({ performers: [performer.id] }); Toast.success({ content: "Started auto tagging" }); } catch (e) { Toast.error(e); diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx index ceda00397..80ce28e69 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx @@ -14,6 +14,7 @@ import { SceneFileInfoPanel } from "./SceneFileInfoPanel"; import { SceneEditPanel } from "./SceneEditPanel"; import { SceneDetailPanel } from "./SceneDetailPanel"; import { OCounterButton } from "./OCounterButton"; +import { SceneOperationsPanel } from "./SceneOperationsPanel"; export const Scene: React.FC = () => { const { id = "new" } = useParams(); @@ -144,6 +145,11 @@ export const Scene: React.FC = () => { onDelete={() => history.push("/scenes")} /> + + + diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneOperationsPanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneOperationsPanel.tsx new file mode 100644 index 000000000..730531b80 --- /dev/null +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneOperationsPanel.tsx @@ -0,0 +1,36 @@ +import { Button } from "react-bootstrap"; +import React, { FunctionComponent } from "react"; +import * as GQL from "src/core/generated-graphql"; +import { StashService } from "src/core/StashService"; +import { useToast } from "src/hooks"; +import { JWUtils } from "src/utils"; + +interface IOperationsPanelProps { + scene: GQL.SceneDataFragment; +} + +export const SceneOperationsPanel: FunctionComponent = (props: IOperationsPanelProps) => { + const Toast = useToast(); + const [generateScreenshot] = StashService.useSceneGenerateScreenshot(); + + async function onGenerateScreenshot(at?: number) { + await generateScreenshot({ + variables: { + id: props.scene.id, + at: at + } + }); + Toast.success({ content: "Generating screenshot" }); + } + + return ( + <> + + + + ); +}; diff --git a/ui/v2.5/src/components/Settings/SettingsTasksPanel/GenerateButton.tsx b/ui/v2.5/src/components/Settings/SettingsTasksPanel/GenerateButton.tsx index 5fc900f1b..083006357 100644 --- a/ui/v2.5/src/components/Settings/SettingsTasksPanel/GenerateButton.tsx +++ b/ui/v2.5/src/components/Settings/SettingsTasksPanel/GenerateButton.tsx @@ -12,7 +12,7 @@ export const GenerateButton: React.FC = () => { async function onGenerate() { try { - await StashService.queryMetadataGenerate({ + await StashService.mutateMetadataGenerate({ sprites, previews, markers, diff --git a/ui/v2.5/src/components/Settings/SettingsTasksPanel/SettingsTasksPanel.tsx b/ui/v2.5/src/components/Settings/SettingsTasksPanel/SettingsTasksPanel.tsx index 357808ff4..d834533ef 100644 --- a/ui/v2.5/src/components/Settings/SettingsTasksPanel/SettingsTasksPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsTasksPanel/SettingsTasksPanel.tsx @@ -68,7 +68,7 @@ export const SettingsTasksPanel: React.FC = () => { function onImport() { setIsImportAlertOpen(false); - StashService.queryMetadataImport().then(() => { + StashService.mutateMetadataImport().then(() => { jobStatus.refetch(); }); } @@ -91,7 +91,7 @@ export const SettingsTasksPanel: React.FC = () => { function onClean() { setIsCleanAlertOpen(false); - StashService.queryMetadataClean().then(() => { + StashService.mutateMetadataClean().then(() => { jobStatus.refetch(); }); } @@ -115,7 +115,7 @@ export const SettingsTasksPanel: React.FC = () => { async function onScan() { try { - await StashService.queryMetadataScan({ useFileMetadata }); + await StashService.mutateMetadataScan({ useFileMetadata }); Toast.success({ content: "Started scan" }); jobStatus.refetch(); } catch (e) { @@ -134,7 +134,7 @@ export const SettingsTasksPanel: React.FC = () => { async function onAutoTag() { try { - await StashService.queryMetadataAutoTag(getAutoTagInput()); + await StashService.mutateMetadataAutoTag(getAutoTagInput()); Toast.success({ content: "Started auto tagging" }); jobStatus.refetch(); } catch (e) { @@ -153,7 +153,7 @@ export const SettingsTasksPanel: React.FC = () => { id="stop" variant="danger" onClick={() => - StashService.queryStopJob().then(() => jobStatus.refetch()) + StashService.mutateStopJob().then(() => jobStatus.refetch()) } > Stop @@ -277,7 +277,7 @@ export const SettingsTasksPanel: React.FC = () => { variant="secondary" type="submit" onClick={() => - StashService.queryMetadataExport().then(() => { + StashService.mutateMetadataExport().then(() => { jobStatus.refetch(); }) } diff --git a/ui/v2.5/src/components/Studios/StudioDetails/Studio.tsx b/ui/v2.5/src/components/Studios/StudioDetails/Studio.tsx index 51419244c..f6a123934 100644 --- a/ui/v2.5/src/components/Studios/StudioDetails/Studio.tsx +++ b/ui/v2.5/src/components/Studios/StudioDetails/Studio.tsx @@ -115,7 +115,7 @@ export const Studio: React.FC = () => { async function onAutoTag() { if (!studio.id) return; try { - await StashService.queryMetadataAutoTag({ studios: [studio.id] }); + await StashService.mutateMetadataAutoTag({ studios: [studio.id] }); Toast.success({ content: "Started auto tagging" }); } catch (e) { Toast.error(e); diff --git a/ui/v2.5/src/components/Tags/TagList.tsx b/ui/v2.5/src/components/Tags/TagList.tsx index 8a28c054a..46ba73abc 100644 --- a/ui/v2.5/src/components/Tags/TagList.tsx +++ b/ui/v2.5/src/components/Tags/TagList.tsx @@ -62,7 +62,7 @@ export const TagList: React.FC = () => { async function onAutoTag(tag: GQL.TagDataFragment) { if (!tag) return; try { - await StashService.queryMetadataAutoTag({ tags: [tag.id] }); + await StashService.mutateMetadataAutoTag({ tags: [tag.id] }); Toast.success({ content: "Started auto tagging" }); } catch (e) { Toast.error(e); diff --git a/ui/v2.5/src/core/StashService.ts b/ui/v2.5/src/core/StashService.ts index d5b494171..b76d253c2 100644 --- a/ui/v2.5/src/core/StashService.ts +++ b/ui/v2.5/src/core/StashService.ts @@ -407,6 +407,12 @@ export class StashService { }); } + public static useSceneGenerateScreenshot() { + return GQL.useSceneGenerateScreenshotMutation({ + update: () => StashService.invalidateQueries(["findScenes"]), + }); + } + private static studioMutationImpactedQueries = [ "findStudios", "findScenes", @@ -506,10 +512,9 @@ export class StashService { }); } - public static queryStopJob() { - return StashService.client.query({ - query: GQL.StopJobDocument, - fetchPolicy: "network-only" + public static mutateStopJob() { + return StashService.client.mutate({ + mutation: GQL.StopJobDocument, }); } @@ -566,48 +571,42 @@ export class StashService { }); } - public static queryMetadataScan(input: GQL.ScanMetadataInput) { - return StashService.client.query({ - query: GQL.MetadataScanDocument, + public static mutateMetadataScan(input: GQL.ScanMetadataInput) { + return StashService.client.mutate({ + mutation: GQL.MetadataScanDocument, variables: { input }, - fetchPolicy: "network-only" }); } - public static queryMetadataAutoTag(input: GQL.AutoTagMetadataInput) { - return StashService.client.query({ - query: GQL.MetadataAutoTagDocument, + public static mutateMetadataAutoTag(input: GQL.AutoTagMetadataInput) { + return StashService.client.mutate({ + mutation: GQL.MetadataAutoTagDocument, variables: { input }, - fetchPolicy: "network-only" }); } - public static queryMetadataGenerate(input: GQL.GenerateMetadataInput) { - return StashService.client.query({ - query: GQL.MetadataGenerateDocument, + public static mutateMetadataGenerate(input: GQL.GenerateMetadataInput) { + return StashService.client.mutate({ + mutation: GQL.MetadataGenerateDocument, variables: { input }, - fetchPolicy: "network-only" }); } - public static queryMetadataClean() { - return StashService.client.query({ - query: GQL.MetadataCleanDocument, - fetchPolicy: "network-only" + public static mutateMetadataClean() { + return StashService.client.mutate({ + mutation: GQL.MetadataCleanDocument, }); } - public static queryMetadataExport() { - return StashService.client.query({ - query: GQL.MetadataExportDocument, - fetchPolicy: "network-only" + public static mutateMetadataExport() { + return StashService.client.mutate({ + mutation: GQL.MetadataExportDocument, }); } - public static queryMetadataImport() { - return StashService.client.query({ - query: GQL.MetadataImportDocument, - fetchPolicy: "network-only" + public static mutateMetadataImport() { + return StashService.client.mutate({ + mutation: GQL.MetadataImportDocument, }); } diff --git a/ui/v2/src/components/Settings/SettingsTasksPanel/GenerateButton.tsx b/ui/v2/src/components/Settings/SettingsTasksPanel/GenerateButton.tsx index a37bea831..edbd52c85 100644 --- a/ui/v2/src/components/Settings/SettingsTasksPanel/GenerateButton.tsx +++ b/ui/v2/src/components/Settings/SettingsTasksPanel/GenerateButton.tsx @@ -18,7 +18,7 @@ export const GenerateButton: FunctionComponent = () => { async function onGenerate() { try { - await StashService.queryMetadataGenerate({sprites, previews, markers, transcodes}); + await StashService.mutateMetadataGenerate({sprites, previews, markers, transcodes}); ToastUtils.success("Started generating"); } catch (e) { ErrorUtils.handle(e); diff --git a/ui/v2/src/components/Settings/SettingsTasksPanel/SettingsTasksPanel.tsx b/ui/v2/src/components/Settings/SettingsTasksPanel/SettingsTasksPanel.tsx index 16e3dab89..83a9d41fd 100644 --- a/ui/v2/src/components/Settings/SettingsTasksPanel/SettingsTasksPanel.tsx +++ b/ui/v2/src/components/Settings/SettingsTasksPanel/SettingsTasksPanel.tsx @@ -78,7 +78,7 @@ export const SettingsTasksPanel: FunctionComponent = (props: IProps) => function onImport() { setIsImportAlertOpen(false); - StashService.queryMetadataImport().then(() => { jobStatus.refetch()}); + StashService.mutateMetadataImport().then(() => { jobStatus.refetch()}); } function renderImportAlert() { @@ -102,7 +102,7 @@ export const SettingsTasksPanel: FunctionComponent = (props: IProps) => function onClean() { setIsCleanAlertOpen(false); - StashService.queryMetadataClean().then(() => { jobStatus.refetch()}); + StashService.mutateMetadataClean().then(() => { jobStatus.refetch()}); } function renderCleanAlert() { @@ -127,7 +127,7 @@ export const SettingsTasksPanel: FunctionComponent = (props: IProps) => async function onScan() { try { - await StashService.queryMetadataScan({useFileMetadata: useFileMetadata}); + await StashService.mutateMetadataScan({useFileMetadata: useFileMetadata}); ToastUtils.success("Started scan"); jobStatus.refetch(); } catch (e) { @@ -146,7 +146,7 @@ export const SettingsTasksPanel: FunctionComponent = (props: IProps) => async function onAutoTag() { try { - await StashService.queryMetadataAutoTag(getAutoTagInput()); + await StashService.mutateMetadataAutoTag(getAutoTagInput()); ToastUtils.success("Started auto tagging"); jobStatus.refetch(); } catch (e) { @@ -162,7 +162,7 @@ export const SettingsTasksPanel: FunctionComponent = (props: IProps) => return ( <> -