Skip to content

Scene Operations

Operations for managing scenes (videos).

Bases: StashClientProtocol

Mixin for scene-related client methods.

Functions

find_scene async

find_scene(id: str) -> Scene | None

Find a scene by its ID.

Parameters:

Name Type Description Default
id str

The ID of the scene to find

required

Returns:

Type Description
Scene | None

Scene object if found, None otherwise

Raises:

Type Description
ValueError

If scene ID is None or empty

Examples:

Find a scene and check its title:

scene = await client.find_scene("123")

if scene:
    print(f"Found scene: {scene.title}")

Access scene relationships:

scene = await client.find_scene("123")

if scene:
    # Get performer names
    performers = [p.name for p in scene.performers]
    # Get studio name
    studio_name = scene.studio.name if scene.studio else None
    # Get tag names
    tags = [t.name for t in scene.tags]

Check scene paths:

scene = await client.find_scene("123")

if scene:
    # Get streaming URL
    stream_url = scene.paths.stream
    # Get preview URL
    preview_url = scene.paths.preview

find_scenes async

find_scenes(
    filter_: dict[str, Any] | None = None,
    scene_filter: dict[str, Any] | None = None,
    q: str | None = None,
) -> FindScenesResultType

Find scenes matching the given filters.

Parameters:

Name Type Description Default
filter_ dict[str, Any] | None

Optional general filter parameters: - q: str (search query) - direction: SortDirectionEnum (ASC/DESC) - page: int - per_page: int - sort: str (field to sort by)

None
q str | None

Optional search query (alternative to filter_["q"])

None
scene_filter dict[str, Any] | None

Optional scene-specific filter: - file_count: IntCriterionInput - is_missing: str (what data is missing) - organized: bool - path: StringCriterionInput - performer_count: IntCriterionInput - performer_tags: HierarchicalMultiCriterionInput - performers: MultiCriterionInput - rating100: IntCriterionInput - resolution: ResolutionEnum - studios: HierarchicalMultiCriterionInput - tag_count: IntCriterionInput - tags: HierarchicalMultiCriterionInput - title: StringCriterionInput

None

Returns:

Type Description
FindScenesResultType

FindScenesResultType containing: - count: Total number of matching scenes - duration: Total duration in seconds - filesize: Total size in bytes - scenes: List of Scene objects

Examples:

Find all organized scenes:

result = await client.find_scenes(
    scene_filter={"organized": True}
)
print(f"Found {result.count} organized scenes")
for scene in result.scenes:
    print(f"- {scene.title}")

Find scenes with specific performers:

result = await client.find_scenes(
    scene_filter={
        "performers": {
            "value": ["performer1", "performer2"],
            "modifier": "INCLUDES_ALL"
        }
    }
)

Find scenes with high rating and sort by date:

result = await client.find_scenes(
    filter_={
        "direction": "DESC",
        "sort": "date",
    },
    scene_filter={
        "rating100": {
            "value": 80,
            "modifier": "GREATER_THAN"
        }
    }
)

Paginate results:

result = await client.find_scenes(
    filter_={
        "page": 1,
        "per_page": 25,
    }
)

create_scene async

create_scene(scene: Scene) -> Scene

Create a new scene in Stash.

Parameters:

Name Type Description Default
scene Scene

Scene object with the data to create. Required fields: - title: Scene title - urls: List of URLs associated with the scene - organized: Whether the scene is organized

Note: created_at and updated_at are handled by Stash

required

Returns:

Type Description
Scene

Created Scene object with ID and any server-generated fields

Raises:

Type Description
ValueError

If the scene data is invalid

TransportError

If the request fails

Examples:

Create a basic scene:

scene = Scene(
    title="My Scene",
    urls=["https://example.com/scene"],
    organized=True,  # created_at and updated_at handled by Stash
)
created = await client.create_scene(scene)
print(f"Created scene with ID: {created.id}")

Create scene with relationships:

scene = Scene(
    title="My Scene",
    urls=["https://example.com/scene"],
    organized=True,  # created_at and updated_at handled by Stash
    # Add relationships
    performers=[performer1, performer2],
    studio=studio,
    tags=[tag1, tag2],
)
created = await client.create_scene(scene)

Create scene with metadata:

scene = Scene(
    title="My Scene",
    urls=["https://example.com/scene"],
    organized=True,  # created_at and updated_at handled by Stash
    # Add metadata
    details="Scene description",
    date="2024-01-31",
    rating100=85,
    code="SCENE123",
)
created = await client.create_scene(scene)

update_scene async

update_scene(scene: Scene) -> Scene

Update an existing scene in Stash.

Parameters:

Name Type Description Default
scene Scene

Scene object with updated data. Required fields: - id: Scene ID to update Any other fields that are set will be updated. Fields that are None will be ignored.

required

Returns:

Type Description
Scene

Updated Scene object with any server-generated fields

Raises:

Type Description
ValueError

If the scene data is invalid

TransportError

If the request fails

Examples:

Update scene title and rating:

scene = await client.find_scene("123")
if scene:
    scene.title = "New Title"
    scene.rating100 = 90
    updated = await client.update_scene(scene)
    print(f"Updated scene: {updated.title}")

Update scene relationships:

scene = await client.find_scene("123")
if scene:
    # Add new performers
    scene.performers.extend([new_performer1, new_performer2])
    # Set new studio
    scene.studio = new_studio
    # Add new tags
    scene.tags.extend([new_tag1, new_tag2])
    updated = await client.update_scene(scene)

Update scene metadata:

scene = await client.find_scene("123")
if scene:
    # Update metadata
    scene.details = "New description"
    scene.date = "2024-01-31"
    scene.code = "NEWCODE123"
    scene.organized = True
    updated = await client.update_scene(scene)

Update scene URLs:

scene = await client.find_scene("123")
if scene:
    # Replace URLs
    scene.urls = [
        "https://example.com/new-url",
    ]
    updated = await client.update_scene(scene)

Remove scene relationships:

scene = await client.find_scene("123")
if scene:
    # Clear studio
    scene.studio = None
    # Clear performers
    scene.performers = []
    updated = await client.update_scene(scene)

find_duplicate_scenes async

find_duplicate_scenes(
    distance: int | None = None,
    duration_diff: float | None = None,
) -> list[list[Scene]]

Find groups of scenes that are perceptual duplicates.

Parameters:

Name Type Description Default
distance int | None

Maximum phash distance between scenes to be considered duplicates

None
duration_diff float | None

Maximum difference in seconds between scene durations

None

Returns:

Type Description
list[list[Scene]]

List of scene groups, where each group is a list of duplicate scenes

parse_scene_filenames async

parse_scene_filenames(
    filter_: dict[str, Any] | None = None,
    config: dict[str, Any] | None = None,
) -> dict[str, Any]

Parse scene filenames using the given configuration.

Parameters:

Name Type Description Default
filter_ dict[str, Any] | None

Optional filter to select scenes

None
config dict[str, Any] | None

Parser configuration: - whitespace_separator: bool - field_separator: str - fields: list[str]

None

Returns:

Type Description
dict[str, Any]

Dictionary containing parse results

scene_wall async

scene_wall(q: str | None = None) -> list[Scene]

Get random scenes for the wall.

Parameters:

Name Type Description Default
q str | None

Optional search query

None

Returns:

Type Description
list[Scene]

List of random Scene objects

bulk_scene_update async

bulk_scene_update(
    input_data: dict[str, Any],
) -> list[Scene]

Update multiple scenes at once.

Parameters:

Name Type Description Default
input_data dict[str, Any]

Dictionary containing: - ids: List of scene IDs to update - Any other fields to update on all scenes

required

Returns:

Type Description
list[Scene]

List of updated Scene objects

scenes_update async

scenes_update(scenes: list[Scene]) -> list[Scene]

Update multiple scenes with individual data.

Parameters:

Name Type Description Default
scenes list[Scene]

List of Scene objects to update, each must have an ID

required

Returns:

Type Description
list[Scene]

List of updated Scene objects

scene_generate_screenshot async

scene_generate_screenshot(
    id: str, at: float | None = None
) -> str

Generate a screenshot for a scene.

Parameters:

Name Type Description Default
id str

Scene ID

required
at float | None

Optional time in seconds to take screenshot at

None

Returns:

Type Description
str

Path to the generated screenshot

Raises:

Type Description
ValueError

If the scene is not found

TransportError

If the request fails

find_scene_by_hash async

find_scene_by_hash(
    input_data: SceneHashInput | dict[str, Any],
) -> Scene | None

Find a scene by its hash (checksum or oshash).

Parameters:

Name Type Description Default
input_data SceneHashInput | dict[str, Any]

SceneHashInput object or dictionary containing: - checksum: MD5 checksum of the file (optional) - oshash: OSHash of the file (optional)

Note: At least one hash must be provided

required

Returns:

Type Description
Scene | None

Scene object if found, None otherwise

Examples:

Find scene by MD5 checksum:

scene = await client.find_scene_by_hash({
    "checksum": "abc123def456..."
})
if scene:
    print(f"Found scene: {scene.title}")

Find scene by OSHash:

scene = await client.find_scene_by_hash({
    "oshash": "xyz789..."
})

Using the input type:

from ...types import SceneHashInput

input_data = SceneHashInput(checksum="abc123")
scene = await client.find_scene_by_hash(input_data)

scene_destroy async

scene_destroy(
    input_data: SceneDestroyInput | dict[str, Any],
) -> bool

Delete a scene.

Parameters:

Name Type Description Default
input_data SceneDestroyInput | dict[str, Any]

SceneDestroyInput object or dictionary containing: - id: Scene ID to delete (required) - delete_file: Whether to delete the scene's file (optional, default: False) - delete_generated: Whether to delete generated files (optional, default: True)

required

Returns:

Type Description
bool

True if the scene was successfully deleted

Raises:

Type Description
ValueError

If the scene ID is invalid

TransportError

If the request fails

Examples:

Delete a scene without deleting the file:

result = await client.scene_destroy({
    "id": "123",
    "delete_file": False,
    "delete_generated": True
})
print(f"Scene deleted: {result}")

Delete a scene and its file:

result = await client.scene_destroy({
    "id": "123",
    "delete_file": True,
    "delete_generated": True
})

Using the input type:

from ...types import SceneDestroyInput

input_data = SceneDestroyInput(
    id="123",
    delete_file=True,
    delete_generated=True
)
result = await client.scene_destroy(input_data)

scenes_destroy async

scenes_destroy(
    input_data: ScenesDestroyInput | dict[str, Any],
) -> bool

Delete multiple scenes.

Parameters:

Name Type Description Default
input_data ScenesDestroyInput | dict[str, Any]

ScenesDestroyInput object or dictionary containing: - ids: List of scene IDs to delete (required) - delete_file: Whether to delete the scenes' files (optional, default: False) - delete_generated: Whether to delete generated files (optional, default: True)

required

Returns:

Type Description
bool

True if the scenes were successfully deleted

Raises:

Type Description
ValueError

If any scene ID is invalid

TransportError

If the request fails

Examples:

Delete multiple scenes without deleting files:

result = await client.scenes_destroy({
    "ids": ["123", "456", "789"],
    "delete_file": False,
    "delete_generated": True
})
print(f"Scenes deleted: {result}")

Delete multiple scenes and their files:

result = await client.scenes_destroy({
    "ids": ["123", "456"],
    "delete_file": True,
    "delete_generated": True
})

Using the input type:

from ...types import ScenesDestroyInput

input_data = ScenesDestroyInput(
    ids=["123", "456", "789"],
    delete_file=False,
    delete_generated=True
)
result = await client.scenes_destroy(input_data)

scene_merge async

scene_merge(
    input_data: SceneMergeInput | dict[str, Any],
) -> Scene

Merge multiple scenes into one destination scene.

Parameters:

Name Type Description Default
input_data SceneMergeInput | dict[str, Any]

SceneMergeInput object or dictionary containing: - source: List of source scene IDs to merge (required) - destination: Destination scene ID (required) - values: Optional SceneUpdateInput with values to apply to merged scene - play_history: Whether to merge play history (optional, default: False) - o_history: Whether to merge o-count history (optional, default: False)

required

Returns:

Type Description
Scene

Updated destination Scene object

Raises:

Type Description
ValueError

If the input data is invalid

TransportError

If the request fails

Examples:

Merge two scenes into one:

merged = await client.scene_merge({
    "source": ["123", "456"],
    "destination": "789"
})
print(f"Merged into scene: {merged.title}")

Merge scenes and update metadata:

from stash_graphql_client.types import SceneMergeInput

input_data = SceneMergeInput(
    source=["123", "456"],
    destination="789",
    values={"title": "Merged Scene"},
    play_history=True,
    o_history=True
)
merged = await client.scene_merge(input_data)

scene_add_o async

scene_add_o(
    id: str, times: list[Timestamp] | None = None
) -> HistoryMutationResult

Add O-count entry for a scene.

Parameters:

Name Type Description Default
id str

Scene ID

required
times list[Timestamp] | None

Optional list of timestamps. If not provided, uses current time.

None

Returns:

Type Description
HistoryMutationResult

HistoryMutationResult containing: - count: New O-count value - history: List of all O timestamps

Examples:

Add O-count with current time:

result = await client.scene_add_o("123")
print(f"New O-count: {result.count}")

Add O-count with specific times:

result = await client.scene_add_o(
    "123",
    times=["2024-01-15T10:30:00Z", "2024-01-16T14:20:00Z"]
)

scene_delete_o async

scene_delete_o(
    id: str, times: list[Timestamp] | None = None
) -> HistoryMutationResult

Delete O-count entry from a scene.

Parameters:

Name Type Description Default
id str

Scene ID

required
times list[Timestamp] | None

Optional list of timestamps to remove. If not provided, removes last entry.

None

Returns:

Type Description
HistoryMutationResult

HistoryMutationResult containing: - count: New O-count value - history: List of remaining O timestamps

Examples:

Remove last O-count entry:

result = await client.scene_delete_o("123")
print(f"New O-count: {result.count}")

Remove specific timestamp:

result = await client.scene_delete_o(
    "123",
    times=["2024-01-15T10:30:00Z"]
)

scene_reset_o async

scene_reset_o(id: str) -> int

Reset scene O-count to 0.

Parameters:

Name Type Description Default
id str

Scene ID

required

Returns:

Type Description
int

New O-count value (0)

Example
count = await client.scene_reset_o("123")
print(f"O-count reset to: {count}")

scene_save_activity async

scene_save_activity(
    id: str,
    resume_time: float | None = None,
    play_duration: float | None = None,
) -> bool

Save scene playback activity.

Parameters:

Name Type Description Default
id str

Scene ID

required
resume_time float | None

Resume time point in seconds

None
play_duration float | None

Duration played in seconds

None

Returns:

Type Description
bool

True if activity was saved successfully

Examples:

Save resume point:

await client.scene_save_activity("123", resume_time=120.5)

Save play duration:

await client.scene_save_activity("123", play_duration=300.0)

Save both:

await client.scene_save_activity(
    "123",
    resume_time=120.5,
    play_duration=300.0
)

scene_reset_activity async

scene_reset_activity(
    id: str,
    reset_resume: bool = False,
    reset_duration: bool = False,
) -> bool

Reset scene activity tracking.

Parameters:

Name Type Description Default
id str

Scene ID

required
reset_resume bool

Whether to reset resume time point

False
reset_duration bool

Whether to reset play duration

False

Returns:

Type Description
bool

True if activity was reset successfully

Examples:

Reset resume point:

await client.scene_reset_activity("123", reset_resume=True)

Reset play duration:

await client.scene_reset_activity("123", reset_duration=True)

Reset both:

await client.scene_reset_activity(
    "123",
    reset_resume=True,
    reset_duration=True
)

scene_add_play async

scene_add_play(
    id: str, times: list[Timestamp] | None = None
) -> HistoryMutationResult

Add play count entry for a scene.

Parameters:

Name Type Description Default
id str

Scene ID

required
times list[Timestamp] | None

Optional list of timestamps. If not provided, uses current time.

None

Returns:

Type Description
HistoryMutationResult

HistoryMutationResult containing: - count: New play count value - history: List of all play timestamps

Examples:

Add play with current time:

result = await client.scene_add_play("123")
print(f"New play count: {result.count}")

Add play with specific times:

result = await client.scene_add_play(
    "123",
    times=["2024-01-15T10:30:00Z"]
)

scene_delete_play async

scene_delete_play(
    id: str, times: list[Timestamp] | None = None
) -> HistoryMutationResult

Delete play count entry from a scene.

Parameters:

Name Type Description Default
id str

Scene ID

required
times list[Timestamp] | None

Optional list of timestamps to remove. If not provided, removes last entry.

None

Returns:

Type Description
HistoryMutationResult

HistoryMutationResult containing: - count: New play count value - history: List of remaining play timestamps

Examples:

Remove last play entry:

result = await client.scene_delete_play("123")
print(f"New play count: {result.count}")

Remove specific timestamp:

result = await client.scene_delete_play(
    "123",
    times=["2024-01-15T10:30:00Z"]
)

scene_reset_play_count async

scene_reset_play_count(id: str) -> int

Reset scene play count to 0.

Parameters:

Name Type Description Default
id str

Scene ID

required

Returns:

Type Description
int

New play count value (0)

Example
count = await client.scene_reset_play_count("123")
print(f"Play count reset to: {count}")

find_scenes_by_path_regex async

find_scenes_by_path_regex(
    filter_: dict[str, Any] | None = None,
) -> FindScenesResultType

Find scenes by path regex pattern.

Parameters:

Name Type Description Default
filter_ dict[str, Any] | None

Filter parameters

None

Returns:

Type Description
FindScenesResultType

FindScenesResultType containing: - count: Total number of matches - duration: Total duration in seconds - filesize: Total file size in bytes - scenes: List of Scene objects matching the path pattern

scene_streams async

scene_streams(scene_id: str) -> list[SceneStreamEndpoint]

Get streaming endpoints for a scene.

Parameters:

Name Type Description Default
scene_id str

The ID of the scene

required

Returns:

Type Description
list[SceneStreamEndpoint]

List of SceneStreamEndpoint objects containing: - url: Stream URL - mime_type: MIME type of the stream - label: Label for the stream quality/format

Examples:

Get streaming endpoints:

streams = await client.scene_streams("123")
for stream in streams:
    print(f"{stream.label}: {stream.url} ({stream.mime_type})")

Access specific stream properties:

streams = await client.scene_streams("123")
if streams:
    primary_stream = streams[0]
    print(f"Primary stream URL: {primary_stream.url}")

merge_scene_markers async

merge_scene_markers(
    target_scene_id: str, source_scene_ids: list[str]
) -> list[Any]

Merge scene markers from source scenes to target scene.

This utility method copies all markers from one or more source scenes to a target scene. Useful when consolidating duplicate scenes or merging content.

Parameters:

Name Type Description Default
target_scene_id str

The ID of the target scene to copy markers to

required
source_scene_ids list[str]

List of source scene IDs to copy markers from

required

Returns:

Type Description
list[Any]

List of SceneMarker objects that were created on the target scene

Examples:

Merge markers from a single source:

markers = await client.merge_scene_markers(
    target_scene_id="123",
    source_scene_ids=["456"]
)
print(f"Copied {len(markers)} markers to target scene")

Merge markers from multiple sources:

markers = await client.merge_scene_markers(
    target_scene_id="123",
    source_scene_ids=["456", "789", "101"]
)
for marker in markers:
    print(f"Marker: {marker.title} at {marker.seconds}s")

Use with scene merge workflow:

# First merge the scenes
merged = await client.scene_merge({
    "source": ["source1", "source2"],
    "destination": "target"
})

# Then copy markers
markers = await client.merge_scene_markers(
    target_scene_id="target",
    source_scene_ids=["source1", "source2"]
)

find_duplicate_scenes_wrapper async

find_duplicate_scenes_wrapper(
    distance: int = 0, duration_diff: float | None = None
) -> list[list[Scene]]

Find duplicate scenes with sensible default parameters.

This is a convenience wrapper around find_duplicate_scenes() that provides better defaults for common use cases. A distance of 0 finds exact phash matches (true duplicates), while higher values find similar scenes.

Parameters:

Name Type Description Default
distance int

Maximum phash distance (default: 0 for exact duplicates) - 0: Exact phash matches (identical frames) - 1-5: Very similar scenes (same content, different encode) - 6-10: Similar scenes (same source, different quality) - 11+: Potentially different scenes

0
duration_diff float | None

Maximum duration difference in seconds (default: None) - None: No duration filtering - 0.0: Exact duration match - 1.0-10.0: Similar duration (accounts for encoding differences) - 10.0+: Loose duration matching

None

Returns:

Type Description
list[list[Scene]]

List of scene groups, where each group is a list of duplicate scenes

Examples:

Find exact duplicates (phash distance = 0):

duplicates = await client.find_duplicate_scenes_wrapper()
for group in duplicates:
    print(f"Found {len(group)} exact duplicates:")
    for scene in group:
        print(f"  - {scene.title}")

Find similar scenes (allow small phash differences):

similar = await client.find_duplicate_scenes_wrapper(distance=5)
for group in similar:
    print(f"Found {len(group)} similar scenes")

Find duplicates with similar duration:

duplicates = await client.find_duplicate_scenes_wrapper(
    distance=0,
    duration_diff=2.0  # Within 2 seconds
)

Use in cleanup workflow:

# Find exact duplicates
duplicates = await client.find_duplicate_scenes_wrapper()

for group in duplicates:
    # Keep the first scene, delete the rest
    to_delete = [scene.id for scene in group[1:]]
    if to_delete:
        await client.scenes_destroy({
            "ids": to_delete,
            "delete_file": True
        })