mirror of
https://github.com/stashapp/stash.git
synced 2025-12-15 04:44:28 +01:00
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:
parent
34d829338d
commit
3de6955a9e
28 changed files with 442 additions and 168 deletions
27
graphql/documents/mutations/metadata.graphql
Normal file
27
graphql/documents/mutations/metadata.graphql
Normal file
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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})
|
||||
}
|
||||
}
|
||||
|
||||
mutation SceneGenerateScreenshot($id: ID!, $at: Float) {
|
||||
sceneGenerateScreenshot(id: $id, at: $at)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
53
pkg/api/resolver_mutation_metadata.go
Normal file
53
pkg/api/resolver_mutation_metadata.go
Normal 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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
16
pkg/manager/screenshot.go
Normal 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)
|
||||
}
|
||||
84
pkg/manager/task_generate_screenshot.go
Normal file
84
pkg/manager/task_generate_screenshot.go
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export const PerformerOperationsPanel: React.FC<IPerformerOperationsProps> = ({
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -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")}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab eventKey="scene-operations-panel" title="Operations">
|
||||
<SceneOperationsPanel
|
||||
scene={scene}
|
||||
/>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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<IOperationsPanelProps> = (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 (
|
||||
<>
|
||||
<Button className="edit-button" onClick={() => onGenerateScreenshot(JWUtils.getPlayer().getPosition())}>
|
||||
Generate thumbnail from current
|
||||
</Button>
|
||||
<Button className="edit-button" onClick={() => onGenerateScreenshot()}>
|
||||
Generate default thumbnail
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -12,7 +12,7 @@ export const GenerateButton: React.FC = () => {
|
|||
|
||||
async function onGenerate() {
|
||||
try {
|
||||
await StashService.queryMetadataGenerate({
|
||||
await StashService.mutateMetadataGenerate({
|
||||
sprites,
|
||||
previews,
|
||||
markers,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<GQL.StopJobQuery>({
|
||||
query: GQL.StopJobDocument,
|
||||
fetchPolicy: "network-only"
|
||||
public static mutateStopJob() {
|
||||
return StashService.client.mutate<GQL.StopJobMutation>({
|
||||
mutation: GQL.StopJobDocument,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -566,48 +571,42 @@ export class StashService {
|
|||
});
|
||||
}
|
||||
|
||||
public static queryMetadataScan(input: GQL.ScanMetadataInput) {
|
||||
return StashService.client.query<GQL.MetadataScanQuery>({
|
||||
query: GQL.MetadataScanDocument,
|
||||
public static mutateMetadataScan(input: GQL.ScanMetadataInput) {
|
||||
return StashService.client.mutate<GQL.MetadataScanMutation>({
|
||||
mutation: GQL.MetadataScanDocument,
|
||||
variables: { input },
|
||||
fetchPolicy: "network-only"
|
||||
});
|
||||
}
|
||||
|
||||
public static queryMetadataAutoTag(input: GQL.AutoTagMetadataInput) {
|
||||
return StashService.client.query<GQL.MetadataAutoTagQuery>({
|
||||
query: GQL.MetadataAutoTagDocument,
|
||||
public static mutateMetadataAutoTag(input: GQL.AutoTagMetadataInput) {
|
||||
return StashService.client.mutate<GQL.MetadataAutoTagMutation>({
|
||||
mutation: GQL.MetadataAutoTagDocument,
|
||||
variables: { input },
|
||||
fetchPolicy: "network-only"
|
||||
});
|
||||
}
|
||||
|
||||
public static queryMetadataGenerate(input: GQL.GenerateMetadataInput) {
|
||||
return StashService.client.query<GQL.MetadataGenerateQuery>({
|
||||
query: GQL.MetadataGenerateDocument,
|
||||
public static mutateMetadataGenerate(input: GQL.GenerateMetadataInput) {
|
||||
return StashService.client.mutate<GQL.MetadataGenerateMutation>({
|
||||
mutation: GQL.MetadataGenerateDocument,
|
||||
variables: { input },
|
||||
fetchPolicy: "network-only"
|
||||
});
|
||||
}
|
||||
|
||||
public static queryMetadataClean() {
|
||||
return StashService.client.query<GQL.MetadataCleanQuery>({
|
||||
query: GQL.MetadataCleanDocument,
|
||||
fetchPolicy: "network-only"
|
||||
public static mutateMetadataClean() {
|
||||
return StashService.client.mutate<GQL.MetadataCleanMutation>({
|
||||
mutation: GQL.MetadataCleanDocument,
|
||||
});
|
||||
}
|
||||
|
||||
public static queryMetadataExport() {
|
||||
return StashService.client.query<GQL.MetadataExportQuery>({
|
||||
query: GQL.MetadataExportDocument,
|
||||
fetchPolicy: "network-only"
|
||||
public static mutateMetadataExport() {
|
||||
return StashService.client.mutate<GQL.MetadataExportMutation>({
|
||||
mutation: GQL.MetadataExportDocument,
|
||||
});
|
||||
}
|
||||
|
||||
public static queryMetadataImport() {
|
||||
return StashService.client.query<GQL.MetadataImportQuery>({
|
||||
query: GQL.MetadataImportDocument,
|
||||
fetchPolicy: "network-only"
|
||||
public static mutateMetadataImport() {
|
||||
return StashService.client.mutate<GQL.MetadataImportMutation>({
|
||||
mutation: GQL.MetadataImportDocument,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export const GenerateButton: FunctionComponent<IProps> = () => {
|
|||
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ export const SettingsTasksPanel: FunctionComponent<IProps> = (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<IProps> = (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<IProps> = (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<IProps> = (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<IProps> = (props: IProps) =>
|
|||
return (
|
||||
<>
|
||||
<FormGroup>
|
||||
<Button id="stop" text="Stop" intent="danger" onClick={() => StashService.queryStopJob().then(() => jobStatus.refetch())} />
|
||||
<Button id="stop" text="Stop" intent="danger" onClick={() => StashService.mutateStopJob().then(() => jobStatus.refetch())} />
|
||||
</FormGroup>
|
||||
</>
|
||||
);
|
||||
|
|
@ -256,7 +256,7 @@ export const SettingsTasksPanel: FunctionComponent<IProps> = (props: IProps) =>
|
|||
labelFor="export"
|
||||
inline={true}
|
||||
>
|
||||
<Button id="export" text="Export" onClick={() => StashService.queryMetadataExport().then(() => { jobStatus.refetch()})} />
|
||||
<Button id="export" text="Export" onClick={() => StashService.mutateMetadataExport().then(() => { jobStatus.refetch()})} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ export const Studio: FunctionComponent<IProps> = (props: IProps) => {
|
|||
return;
|
||||
}
|
||||
try {
|
||||
await StashService.queryMetadataAutoTag({ studios: [studio.id]});
|
||||
await StashService.mutateMetadataAutoTag({ studios: [studio.id]});
|
||||
ToastUtils.success("Started auto tagging");
|
||||
} catch (e) {
|
||||
ErrorUtils.handle(e);
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ export const TagList: FunctionComponent<IProps> = (props: IProps) => {
|
|||
return;
|
||||
}
|
||||
try {
|
||||
await StashService.queryMetadataAutoTag({ tags: [tag.id]});
|
||||
await StashService.mutateMetadataAutoTag({ tags: [tag.id]});
|
||||
ToastUtils.success("Started auto tagging");
|
||||
} catch (e) {
|
||||
ErrorUtils.handle(e);
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export const PerformerOperationsPanel: FunctionComponent<IPerformerOperationsPro
|
|||
return;
|
||||
}
|
||||
try {
|
||||
await StashService.queryMetadataAutoTag({ performers: [props.performer.id]});
|
||||
await StashService.mutateMetadataAutoTag({ performers: [props.performer.id]});
|
||||
ToastUtils.success("Started auto tagging");
|
||||
} catch (e) {
|
||||
ErrorUtils.handle(e);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { ScenePerformerPanel } from "./ScenePerformerPanel";
|
|||
import { SceneMoviePanel } from "./SceneMoviePanel";
|
||||
import { ErrorUtils } from "../../../utils/errors";
|
||||
import { IOCounterButtonProps, OCounterButton } from "../OCounterButton";
|
||||
import { SceneOperationsPanel } from "./SceneOperationsPanel";
|
||||
|
||||
interface ISceneProps extends IBaseProps {}
|
||||
|
||||
|
|
@ -160,6 +161,14 @@ export const Scene: FunctionComponent<ISceneProps> = (props: ISceneProps) => {
|
|||
onDelete={() => props.history.push("/scenes")}
|
||||
/>}
|
||||
/>
|
||||
<Tab
|
||||
id="scene-operations-panel"
|
||||
title="Operations"
|
||||
panel={
|
||||
<SceneOperationsPanel
|
||||
scene={modifiedScene}
|
||||
/>}
|
||||
/>
|
||||
|
||||
<Tabs.Expander />
|
||||
<OCounterButton
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
import {
|
||||
Button,
|
||||
} from "@blueprintjs/core";
|
||||
import React, { FunctionComponent } from "react";
|
||||
import * as GQL from "../../../core/generated-graphql";
|
||||
import { StashService } from "../../../core/StashService";
|
||||
import { SceneHelpers } from "../helpers";
|
||||
import { ToastUtils } from "../../../utils/toasts";
|
||||
|
||||
interface IOperationsPanelProps {
|
||||
scene: GQL.SceneDataFragment;
|
||||
}
|
||||
|
||||
export const SceneOperationsPanel: FunctionComponent<IOperationsPanelProps> = (props: IOperationsPanelProps) => {
|
||||
|
||||
const jwplayer = SceneHelpers.getPlayer();
|
||||
const generateScreenshot = StashService.useSceneGenerateScreenshot();
|
||||
|
||||
async function onGenerateScreenshot() {
|
||||
let position = jwplayer.getPosition();
|
||||
|
||||
await generateScreenshot({
|
||||
variables: {
|
||||
id: props.scene.id,
|
||||
at: position
|
||||
}
|
||||
});
|
||||
ToastUtils.success("Generating screenshot");
|
||||
}
|
||||
|
||||
async function onGenerateDefaultScreenshot() {
|
||||
await generateScreenshot({
|
||||
variables: {
|
||||
id: props.scene.id,
|
||||
}
|
||||
});
|
||||
ToastUtils.success("Generating screenshot");
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button className="edit-button" text="Generate thumbnail from current" onClick={() => onGenerateScreenshot()}/>
|
||||
<Button className="edit-button" text="Generate default thumbnail" onClick={() => onGenerateDefaultScreenshot()}/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -355,6 +355,12 @@ export class StashService {
|
|||
});
|
||||
}
|
||||
|
||||
public static useSceneGenerateScreenshot() {
|
||||
return GQL.useSceneGenerateScreenshot({
|
||||
update: () => StashService.invalidateQueries(["findScenes"]),
|
||||
});
|
||||
}
|
||||
|
||||
private static studioMutationImpactedQueries = [
|
||||
"findStudios",
|
||||
"findScenes",
|
||||
|
|
@ -467,10 +473,9 @@ export class StashService {
|
|||
});
|
||||
}
|
||||
|
||||
public static queryStopJob() {
|
||||
return StashService.client.query<GQL.StopJobQuery>({
|
||||
query: GQL.StopJobDocument,
|
||||
fetchPolicy: "network-only",
|
||||
public static mutateStopJob() {
|
||||
return StashService.client.mutate<GQL.StopJobMutation>({
|
||||
mutation: GQL.StopJobDocument,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -521,48 +526,42 @@ export class StashService {
|
|||
});
|
||||
}
|
||||
|
||||
public static queryMetadataScan(input: GQL.ScanMetadataInput) {
|
||||
return StashService.client.query<GQL.MetadataScanQuery>({
|
||||
query: GQL.MetadataScanDocument,
|
||||
public static mutateMetadataScan(input: GQL.ScanMetadataInput) {
|
||||
return StashService.client.mutate<GQL.MetadataScanMutation>({
|
||||
mutation: GQL.MetadataScanDocument,
|
||||
variables: { input },
|
||||
fetchPolicy: "network-only",
|
||||
});
|
||||
}
|
||||
|
||||
public static queryMetadataAutoTag(input: GQL.AutoTagMetadataInput) {
|
||||
return StashService.client.query<GQL.MetadataAutoTagQuery>({
|
||||
query: GQL.MetadataAutoTagDocument,
|
||||
public static mutateMetadataAutoTag(input: GQL.AutoTagMetadataInput) {
|
||||
return StashService.client.mutate<GQL.MetadataAutoTagMutation>({
|
||||
mutation: GQL.MetadataAutoTagDocument,
|
||||
variables: { input },
|
||||
fetchPolicy: "network-only",
|
||||
});
|
||||
}
|
||||
|
||||
public static queryMetadataGenerate(input: GQL.GenerateMetadataInput) {
|
||||
return StashService.client.query<GQL.MetadataGenerateQuery>({
|
||||
query: GQL.MetadataGenerateDocument,
|
||||
public static mutateMetadataGenerate(input: GQL.GenerateMetadataInput) {
|
||||
return StashService.client.mutate<GQL.MetadataGenerateMutation>({
|
||||
mutation: GQL.MetadataGenerateDocument,
|
||||
variables: { input },
|
||||
fetchPolicy: "network-only",
|
||||
});
|
||||
}
|
||||
|
||||
public static queryMetadataClean() {
|
||||
return StashService.client.query<GQL.MetadataCleanQuery>({
|
||||
query: GQL.MetadataCleanDocument,
|
||||
fetchPolicy: "network-only",
|
||||
public static mutateMetadataClean() {
|
||||
return StashService.client.mutate<GQL.MetadataCleanMutation>({
|
||||
mutation: GQL.MetadataCleanDocument,
|
||||
});
|
||||
}
|
||||
|
||||
public static queryMetadataExport() {
|
||||
return StashService.client.query<GQL.MetadataExportQuery>({
|
||||
query: GQL.MetadataExportDocument,
|
||||
fetchPolicy: "network-only",
|
||||
public static mutateMetadataExport() {
|
||||
return StashService.client.mutate<GQL.MetadataExportMutation>({
|
||||
mutation: GQL.MetadataExportDocument,
|
||||
});
|
||||
}
|
||||
|
||||
public static queryMetadataImport() {
|
||||
return StashService.client.query<GQL.MetadataImportQuery>({
|
||||
query: GQL.MetadataImportDocument,
|
||||
fetchPolicy: "network-only",
|
||||
public static mutateMetadataImport() {
|
||||
return StashService.client.mutate<GQL.MetadataImportMutation>({
|
||||
mutation: GQL.MetadataImportDocument,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue