config:stash
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revision | |||
config:stash [2024/07/28 12:49] – [Custom CSS] Wulf Rajek | config:stash [2024/07/28 13:08] (current) – Wulf Rajek | ||
---|---|---|---|
Line 163: | Line 163: | ||
} | } | ||
})(); | })(); | ||
+ | </ | ||
+ | |||
+ | ===== Scene merge script after video conversions ===== | ||
+ | |||
+ | After converting video files already included in stash and doing a rescan, new scenes will be created for the new files. This leaves duplicate scenes in the database. | ||
+ | |||
+ | This script creates a backup of the stash database first, then finds all scenes in stash that have the same filenames but different extensions, merges the scenes into the older one and optionally delete the non-mp4 file from the merged scene. | ||
+ | |||
+ | This script uses stashapp-tools module to interface with stash. Installation of this: | ||
+ | < | ||
+ | pip install stashapp-tools | ||
+ | </ | ||
+ | |||
+ | This script performs a dry run unless the -n option is provided to avoid data loss. It can take a filename or part of a path as option or the -a option to scan all stash scenes. The -d parameter deletes the non-mp4 files after merging scenes. | ||
+ | |||
+ | < | ||
+ | usage: stash-merge.py [-h] [-a] [-d] [-n] [filename] | ||
+ | |||
+ | Merge stash scenes with the same filenames in different formats, optionally delete non-mp4 files. | ||
+ | |||
+ | positional arguments: | ||
+ | filename | ||
+ | |||
+ | options: | ||
+ | -h, --help | ||
+ | -a, --all Process all scenes with duplicate filenames | ||
+ | -d, --delete | ||
+ | -n, --non-dry-run | ||
+ | </ | ||
+ | |||
+ | Adjust the scheme/ | ||
+ | <code python stash-merge.py> | ||
+ | import stashapi.log as log | ||
+ | from stashapi.stashapp import StashInterface | ||
+ | import argparse | ||
+ | import os, sys | ||
+ | |||
+ | def find_scenes_by_path_regex(self, | ||
+ | |||
+ | query = """ | ||
+ | query FindScenes($filter: | ||
+ | findScenesByPathRegex(filter: | ||
+ | count | ||
+ | scenes { | ||
+ | ...Scene | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | """ | ||
+ | if fragment: | ||
+ | query = re.sub(r' | ||
+ | |||
+ | filter[" | ||
+ | variables = { | ||
+ | " | ||
+ | " | ||
+ | } | ||
+ | |||
+ | result = self.call_GQL(query, | ||
+ | if get_count: | ||
+ | return result[' | ||
+ | else: | ||
+ | return result[' | ||
+ | |||
+ | def merge_scenes_with_same_filename(stash_scheme, | ||
+ | stash = StashInterface({ | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }) | ||
+ | |||
+ | if dryrun: | ||
+ | print(" | ||
+ | else: | ||
+ | print(" | ||
+ | |||
+ | print(" | ||
+ | query = """ | ||
+ | mutation backupDatabase{ | ||
+ | backupDatabase(input: | ||
+ | } | ||
+ | """ | ||
+ | variables = {} | ||
+ | stash.call_GQL(query, | ||
+ | |||
+ | if file is None: # Fetch all scenes | ||
+ | scenes = stash.find_scenes() | ||
+ | else: | ||
+ | # note rsplit fails for files without extension in directories starting with dot, but path in stash db is absolute | ||
+ | file = file.rsplit(' | ||
+ | scenes = find_scenes_by_path_regex(stash, | ||
+ | # | ||
+ | |||
+ | # print(len(scenes)) | ||
+ | |||
+ | # Group scenes by path without extension | ||
+ | scenes_by_path_ex_ext = {} | ||
+ | for scene in scenes: | ||
+ | # | ||
+ | for files in scene[' | ||
+ | # note rsplit fails for files without extension in directories starting with dot, but path in stash db is absolute | ||
+ | path = files[' | ||
+ | if path not in scenes_by_path_ex_ext: | ||
+ | scenes_by_path_ex_ext[path] = [] | ||
+ | scenes_by_path_ex_ext[path].append(scene) | ||
+ | # | ||
+ | |||
+ | for path, scene_group in scenes_by_path_ex_ext.items(): | ||
+ | if len(scene_group) > 1: | ||
+ | if dryrun: | ||
+ | print(f" | ||
+ | # Identify the target scene to merge into | ||
+ | # | ||
+ | #always merge into the lowest scene id | ||
+ | target_scene = sorted(scene_group, | ||
+ | if dryrun: | ||
+ | print(f" | ||
+ | target_scene_id = target_scene[' | ||
+ | mp4_file_id = None | ||
+ | print(f" | ||
+ | |||
+ | for scene in scene_group: | ||
+ | if scene[' | ||
+ | # Merge scene into target | ||
+ | print(f" | ||
+ | if not dryrun: | ||
+ | stash.merge_scenes(scene[' | ||
+ | |||
+ | # Identify mp4 file | ||
+ | for files in scene[' | ||
+ | if files[' | ||
+ | mp4_file_id = files[' | ||
+ | print(f" | ||
+ | |||
+ | # Set the primary file to be the mp4 file | ||
+ | if mp4_file_id: | ||
+ | print(f" | ||
+ | if not dryrun: | ||
+ | # first set all files NOT future primary to not be primary | ||
+ | stash.sql_commit(" | ||
+ | # now set new primary file id | ||
+ | stash.sql_commit(" | ||
+ | |||
+ | # Optionally delete non-mp4 files if more than 1 file is assigned to the scene | ||
+ | if delete_non_mp4 and len(scene_group) > 1 and mp4_file_id: | ||
+ | for scene in scene_group: | ||
+ | for files in scene[' | ||
+ | if files[' | ||
+ | print(f" | ||
+ | if not dryrun: | ||
+ | stash.destroy_files(files[' | ||
+ | print("" | ||
+ | |||
+ | def main(): | ||
+ | parser = argparse.ArgumentParser(description=" | ||
+ | |||
+ | # Optional argument for filename | ||
+ | parser.add_argument(' | ||
+ | |||
+ | # Non-positional flags with short forms | ||
+ | parser.add_argument(' | ||
+ | parser.add_argument(' | ||
+ | parser.add_argument(' | ||
+ | |||
+ | if len(sys.argv)==1: | ||
+ | print(" | ||
+ | parser.print_help(sys.stderr) | ||
+ | sys.exit(1) | ||
+ | |||
+ | args = parser.parse_args() | ||
+ | |||
+ | if args.filename: | ||
+ | FILE=args.filename | ||
+ | |||
+ | if args.filename and args.delete: | ||
+ | print(f" | ||
+ | merge_scenes_with_same_filename(STASH_SCHEME, | ||
+ | elif args.filename: | ||
+ | print(f" | ||
+ | merge_scenes_with_same_filename(STASH_SCHEME, | ||
+ | elif args.all and args.delete: | ||
+ | print(" | ||
+ | merge_scenes_with_same_filename(STASH_SCHEME, | ||
+ | elif args.all: | ||
+ | print(" | ||
+ | merge_scenes_with_same_filename(STASH_SCHEME, | ||
+ | else: | ||
+ | print(" | ||
+ | |||
+ | if __name__ == " | ||
+ | # Replace with your Stash app URL | ||
+ | STASH_SCHEME = " | ||
+ | STASH_HOST = " | ||
+ | STASH_PORT = " | ||
+ | |||
+ | main() | ||
+ | # | ||
</ | </ |
config/stash.txt · Last modified: 2024/07/28 13:08 by Wulf Rajek