From 10726aac773b6a3c5eb840f21b0625a8c427afec Mon Sep 17 00:00:00 2001 From: spaceyuck Date: Sun, 1 Jun 2025 12:41:45 +0200 Subject: [PATCH 1/2] tagGalleriesFromImages plugin that tags galleries with all tags of contained images --- .../tagGalleriesFromImages/requirements.txt | 1 + .../tagGalleriesFromImages.py | 112 ++++++++++++++++++ .../tagGalleriesFromImages.yml | 30 +++++ 3 files changed, 143 insertions(+) create mode 100755 plugins/tagGalleriesFromImages/requirements.txt create mode 100755 plugins/tagGalleriesFromImages/tagGalleriesFromImages.py create mode 100755 plugins/tagGalleriesFromImages/tagGalleriesFromImages.yml diff --git a/plugins/tagGalleriesFromImages/requirements.txt b/plugins/tagGalleriesFromImages/requirements.txt new file mode 100755 index 00000000..e0fcf029 --- /dev/null +++ b/plugins/tagGalleriesFromImages/requirements.txt @@ -0,0 +1 @@ +stashapp-tools diff --git a/plugins/tagGalleriesFromImages/tagGalleriesFromImages.py b/plugins/tagGalleriesFromImages/tagGalleriesFromImages.py new file mode 100755 index 00000000..8412c192 --- /dev/null +++ b/plugins/tagGalleriesFromImages/tagGalleriesFromImages.py @@ -0,0 +1,112 @@ +import stashapi.log as log +from stashapi.stashapp import StashInterface +import sys +import json + +def processAll(): + exclusion_marker_tag_id = None + if settings["excludeWithTag"] != "": + exclussion_marker_tag = stash.find_tag(settings["excludeWithTag"]) + if exclussion_marker_tag is not None: + exclusion_marker_tag_id = exclussion_marker_tag['id'] + + query = { + "image_count": { + "modifier": "NOT_EQUALS", + "value": 0, + }, + } + if settings['excludeOrganized']: + query["organized"] = False + if exclusion_marker_tag_id is not None: + query["tags"] = { + "value": [exclusion_marker_tag_id], + "modifier": "EXCLUDES" + } + + total_count = stash.find_galleries(f=query, filter={"page": 0, "per_page": 0}, get_count=True)[0] + i = 0 + while i < total_count: + log.progress((i / total_count)) + + galleries = stash.find_galleries(f=query, filter={"page": i, "per_page": 1}) + if len(galleries) == 0: + break + gallery = galleries[0] + + processGallery(gallery) + + i = i + 1 + + +def processGallery(gallery : dict): + tags = [] + performersIds = [] + should_tag = True + if settings["excludeWithTag"] != "": + for tag in gallery["tags"]: + if tag["name"] == settings["excludeWithTag"]: + should_tag = False + break + + if settings['excludeOrganized']: + if gallery['organized']: + should_tag = False + + if should_tag: + existing_tag_ids = {t['id'] for t in gallery['tags']} + existing_performer_ids = {p['id'] for p in gallery['performers']} + + images = stash.find_gallery_images(gallery["id"], fragment='tags { id name } performers { id name }') + if len(images) > 0: + tag_ids = set() + tag_names = set() + + performer_ids = set() + performer_names = set() + + for image in images: + image_tag_ids = [tag['id'] for tag in image['tags']] + image_tag_names = [tag['name'] for tag in image['tags']] + tag_ids.update(image_tag_ids) + tag_names.update(image_tag_names) + + image_performer_ids = [performer['id'] for performer in image['performers']] + image_performer_names = [performer['name'] for performer in image['performers']] + performer_ids.update(image_performer_ids) + performer_names.update(image_performer_names) + + new_tags_ids = tag_ids - existing_tag_ids + new_performer_ids = performer_ids - existing_performer_ids + + if len(new_tags_ids) > 0 or len(new_performer_ids) > 0: + log.info(f"updating gallery {gallery["id"]} from {len(images)} images with tags {tag_names} ({len(new_tags_ids)} new) and performers {performer_names} ({len(new_performer_ids)} new)") + stash.update_galleries({"ids": gallery["id"], "tag_ids": {"mode": "ADD", "ids": list(new_tags_ids)}, "performer_ids": {"mode": "ADD", "ids": list(new_performer_ids)}}) + + +json_input = json.loads(sys.stdin.read()) +FRAGMENT_SERVER = json_input["server_connection"] +stash = StashInterface(FRAGMENT_SERVER) +config = stash.get_configuration() +settings = { + "excludeWithTag": "", + "excludeOrganized": False +} +if "tagGalleriesFromImages" in config["plugins"]: + settings.update(config["plugins"]["tagGalleriesFromImages"]) + +if "mode" in json_input["args"]: + PLUGIN_ARGS = json_input["args"]["mode"] + if "processAll" in PLUGIN_ARGS: + processAll() +elif "hookContext" in json_input["args"]: + id = json_input["args"]["hookContext"]["id"] + if ( + ( + json_input["args"]["hookContext"]["type"] == "Gallery.Update.Post" + or json_input["args"]["hookContext"]["type"] == "Gallery.Create.Post" + ) and "inputFields" in json_input["args"]["hookContext"] + and len(json_input["args"]["hookContext"]["inputFields"]) > 2 + ): + gallery = stash.find_gallery(id) + processGallery(gallery) diff --git a/plugins/tagGalleriesFromImages/tagGalleriesFromImages.yml b/plugins/tagGalleriesFromImages/tagGalleriesFromImages.yml new file mode 100755 index 00000000..99e67ae3 --- /dev/null +++ b/plugins/tagGalleriesFromImages/tagGalleriesFromImages.yml @@ -0,0 +1,30 @@ +name: Tag galleries from images +description: tags galleries with tags of contained images. +version: 0.1 +exec: + - python + - "{pluginDir}/tagGalleriesFromImages.py" +interface: raw + +hooks: + - name: update gallery + description: Will tag galleries with tags of contained images + triggeredBy: + - Gallery.Update.Post + - Gallery.Create.Post + +settings: + excludeOrganized: + displayName: Exclude galleries marked as organized + description: Do not automatically tag galleries if it is marked as organized + type: BOOLEAN + excludeWithTag: + displayName: Exclude galleries with tag from Hook + description: Do not automatically tag galleries if the gallery has this tag + type: STRING + +tasks: + - name: "Tag all galleries" + description: Loops through all galleries, and applies the tags of the contained images. Can take a long time on large db's. + defaultArgs: + mode: processAll From 475fc06bc1ab8d990a97637c53c808ad0f0aca5f Mon Sep 17 00:00:00 2001 From: spaceyuck Date: Wed, 11 Jun 2025 06:53:55 +0200 Subject: [PATCH 2/2] tagGalleriesFromImages: small changes to make code compatible with Python before 3.12 --- plugins/tagGalleriesFromImages/tagGalleriesFromImages.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/tagGalleriesFromImages/tagGalleriesFromImages.py b/plugins/tagGalleriesFromImages/tagGalleriesFromImages.py index 8412c192..bc5e5a39 100755 --- a/plugins/tagGalleriesFromImages/tagGalleriesFromImages.py +++ b/plugins/tagGalleriesFromImages/tagGalleriesFromImages.py @@ -57,7 +57,7 @@ def processGallery(gallery : dict): existing_tag_ids = {t['id'] for t in gallery['tags']} existing_performer_ids = {p['id'] for p in gallery['performers']} - images = stash.find_gallery_images(gallery["id"], fragment='tags { id name } performers { id name }') + images = stash.find_gallery_images(gallery['id'], fragment='tags { id name } performers { id name }') if len(images) > 0: tag_ids = set() tag_names = set() @@ -80,8 +80,8 @@ def processGallery(gallery : dict): new_performer_ids = performer_ids - existing_performer_ids if len(new_tags_ids) > 0 or len(new_performer_ids) > 0: - log.info(f"updating gallery {gallery["id"]} from {len(images)} images with tags {tag_names} ({len(new_tags_ids)} new) and performers {performer_names} ({len(new_performer_ids)} new)") - stash.update_galleries({"ids": gallery["id"], "tag_ids": {"mode": "ADD", "ids": list(new_tags_ids)}, "performer_ids": {"mode": "ADD", "ids": list(new_performer_ids)}}) + log.info(f"updating gallery {gallery['id']} from {len(images)} images with tags {tag_names} ({len(new_tags_ids)} new) and performers {performer_names} ({len(new_performer_ids)} new)") + stash.update_galleries({"ids": gallery['id'], "tag_ids": {"mode": "ADD", "ids": list(new_tags_ids)}, "performer_ids": {"mode": "ADD", "ids": list(new_performer_ids)}}) json_input = json.loads(sys.stdin.read()) @@ -100,7 +100,7 @@ def processGallery(gallery : dict): if "processAll" in PLUGIN_ARGS: processAll() elif "hookContext" in json_input["args"]: - id = json_input["args"]["hookContext"]["id"] + id = json_input["args"]["hookContext"]['id'] if ( ( json_input["args"]["hookContext"]["type"] == "Gallery.Update.Post"