stashapp / CommunityScripts

This is a public repository containing plugin and utility scripts created by the Stash Community.
https://docs.stashapp.cc/add-ons/
GNU Affero General Public License v3.0
185 stars 145 forks source link

Batch Import of Scene Metadata from Stash-Box to Stash #303

Closed 8ullyMaguire closed 4 months ago

8ullyMaguire commented 4 months ago

Is your feature request related to a problem? Please describe. When automatically identifying a scene fails, I often struggle to identify the specific scene when I only know the studio or performer and duration.

Describe the solution you'd like I would like to be able to batch import scene metadata from stash-box into stash, specifically for a given studio or performer. This would allow me to easily sort and filter scenes by duration, making it simpler to identify the scene I'm looking for.

Describe alternatives you've considered I have considered manually searching for each scene individually, but this is time-consuming and inefficient.

Additional context This feature would significantly enhance my workflow by providing a more efficient way to manage and identify scenes in stash.

Here is a sample script to get all scenes given a studio id. Now I have to add all these scenes to stash with the given studio id.

This works

title = "Example scene"
studio_id = 18
variables = { "input": { "title": title, "studio_id": studio_id } }
query = """
mutation SceneCreate($input: SceneCreateInput!) {
    sceneCreate(input: $input) {
        id
    }
}
"""
requests.post("http://localhost:9999/graphql", json={"query": query, "variables": variables})

but if I add duration to the query I get the error

Error creating scene: 422 - {"errors":[{"message":"unknown field","path":["variable","input","duration"],"extensions":{"code":"GRAPHQL_VALIDATION_FAILED"}}],"data":null}

Here is the script I have so far:

import requests

# GraphQL endpoint URL
STASHBOX_GRAPHQL_ENDPOINT = "https://stashdb.org/graphql"
STASH_GRAPHQL_ENDPOINT = "http://localhost:9999/graphql"
STASHBOX_API_KEY = "your-stash-box-api-key"

def execute_graphql_query(query, variables=None):
    headers = {
        "Content-Type": "application/json",
        "ApiKey": STASHBOX_API_KEY
    }
    payload = {"query": query}
    if variables:
        payload["variables"] = variables

    response = requests.post(STASHBOX_GRAPHQL_ENDPOINT, json=payload, headers=headers)
    response.raise_for_status()
    return response.json()["data"]

def get_studio_scenes(studio_id):
    query = """
    query Scenes($input: SceneQueryInput!) {
        queryScenes(input: $input) {
            count
            scenes {
                id
                release_date
                title
                duration
            }
            __typename
        }
    }
    """

    variables = {
        "input": {
            "direction": "DESC",
            "page": 1,
            "parentStudio": studio_id,
            "per_page": 20,
            "sort": "DATE"
        }
    }

    result = execute_graphql_query(query, variables)
    scenes = result["queryScenes"]["scenes"]
    total_scenes = result["queryScenes"]["count"]

    # Yield the initial set of scenes
    for scene in scenes:
        yield scene

    # Fetch and yield remaining pages
    page = 2
    while len(scenes) < total_scenes:
        variables["input"]["page"] = page
        result = execute_graphql_query(query, variables)
        scenes = result["queryScenes"]["scenes"]
        for scene in scenes:
            yield scene
        page += 1

def add_scene(title, duration, studio_id):
    query = """
    mutation SceneCreate($input: SceneCreateInput!) {
        sceneCreate(input: $input) {
            id
        }
    }
    """

    variables = {
        "input": {
            "title": title,
            "duration": duration,
            "studio_id": studio_id
        }
    }

    response = requests.post(STASH_GRAPHQL_ENDPOINT, json={"query": query, "variables": variables})
    if response.status_code == 200:
        data = response.json()
        if "errors" in data:
            print(f"Error creating scene: {data['errors']}")
        else:
            scene_id = data["data"]["sceneCreate"]["id"]
            print(f"Scene '{title}' created successfully with ID: {scene_id}")
    else:
        print(f"Error creating scene: {response.status_code} - {response.text}")

studio_id = "sample_studio_id"
for scene in get_studio_scenes(studio_id):
    print(f"Scene ID: {scene['id']}, Title: {scene['title']}, Release Date: {scene['release_date']}, Duration: {scene['duration']}")
    add_scene(scene['title'], scene['duration'], studio_id)
8ullyMaguire commented 4 months ago

The duration is associated to a file instead of a scene in stash so instead of adding the scenes and sorting them by duration in stash I have to sort them without adding to stash:

import requests
from typing import Dict, List, Optional, Union
import logging
from datetime import timedelta

STASHBOX_GRAPHQL_ENDPOINT = "https://stashdb.org/graphql"
STASHBOX_API_KEY = "your-stash-box-api-key"

# Configure logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')

def execute_graphql_query(query: str, variables: Optional[Dict] = None) -> Dict:
    headers = {
        "Content-Type": "application/json",
        "ApiKey": STASHBOX_API_KEY
    }
    payload = {"query": query}
    if variables:
        payload["variables"] = variables

    logging.debug(f"Executing GraphQL query: {query}")
    logging.debug(f"Variables: {variables}")

    try:
        response = requests.post(STASHBOX_GRAPHQL_ENDPOINT, json=payload, headers=headers)
        response.raise_for_status()
    except requests.exceptions.RequestException as e:
        logging.error(f"Error executing GraphQL query: {e}")
        raise

    logging.debug(f"Response: {response.json()}")
    return response.json()["data"]

def get_studio_scenes(studio_id: str) -> List[Dict]:
    query = """
    query Scenes($input: SceneQueryInput!) {
        queryScenes(input: $input) {
            count
            scenes {
                id
                release_date
                title
                duration
            }
            __typename
        }
    }
    """

    page = 1
    total_scenes = float("inf")  # Initialize with a large value
    all_scenes: List[Dict] = []  # Create a list to store all fetched scenes

    while True:
        variables = {
            "input": {
                "direction": "DESC",
                "page": page,
                "parentStudio": studio_id,
                "per_page": 20,
                "sort": "DATE",
            }
        }

        logging.debug(f"Fetching scenes for page {page}")
        try:
            result = execute_graphql_query(query, variables)
        except Exception as e:
            logging.error(f"Error fetching scenes for page {page}: {e}")
            break

        scenes = result["queryScenes"]["scenes"]
        total_scenes = result["queryScenes"]["count"]
        logging.debug(f"Page {page}: Fetched {len(scenes)} scenes, total scenes: {total_scenes}")
        all_scenes.extend(scenes)  # Append the fetched scenes to the all_scenes list

        # Break if we have fetched all the scenes
        if len(all_scenes) >= total_scenes:
            break

        page += 1

    return all_scenes

def format_duration(duration: int) -> str:
    return str(timedelta(seconds=duration))

def main() -> None:
    studio_id = "sample_studio_id"
    scenes = list(get_studio_scenes(studio_id))

    if not scenes:
        logging.warning("No scenes fetched")
        return

    scenes.sort(key=lambda x: x['duration'] or 0, reverse=True)

    for scene in scenes:
        print(
            f"Duration: {format_duration(scene['duration'] or 0)}",
            f"Release Date: {scene['release_date']}, "
            f"Title: {scene['title']}, Scene ID: {scene['id']}"
        )

if __name__ == "__main__":
    main()