This commit is contained in:
Christoph Califice
2025-10-09 20:05:31 -03:00
parent ed22ef22bc
commit 0a5f88d75a
1442 changed files with 101562 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
# stashdb performer gallery
https://discourse.stashapp.cc/t/stashdb-performer-gallery/1411

View File

@@ -0,0 +1,17 @@
id: stashdb-performer-gallery
name: stashdb performer gallery
metadata:
description: Automatically download performer images from stashdb or other stash-boxes.
Add the [Stashbox Performer Gallery] tag to a performer and it will create a gallery
of images from that stash-box database. Apply the tag [Set Profile Image] to an
image to set it as the profile image of that performer. Note you will need to
configure the download path and add this as a path under settings > library
version: 0.2-034a8a7
date: "2025-05-18 19:37:19"
requires: []
source_repository: https://stashapp.github.io/CommunityScripts/stable/index.yml
files:
- stashdb-performer-gallery.py
- README.md
- requirements.txt
- stashdb-performer-gallery.yml

View File

@@ -0,0 +1,2 @@
requests
stashapp-tools

View File

@@ -0,0 +1,470 @@
import stashapi.log as log
from stashapi.stashapp import StashInterface
from stashapi.stashbox import StashBoxInterface
import os
import sys
import requests
import json
import requests
from pathlib import Path
import base64
per_page = 100
request_s = requests.Session()
stash_boxes = {}
scrapers = {}
def processImages(img):
log.debug("image: %s" % (img,))
image_data = None
for file in [x["path"] for x in img["visual_files"]]:
if settings["path"] in file:
index_file = Path(Path(file).parent) / (Path(file).stem + ".json")
log.debug(index_file)
if index_file.exists():
log.debug("loading index file %s" % (index_file,))
with open(index_file) as f:
index = json.load(f)
index["id"] = img["id"]
if image_data:
image_data["gallery_ids"].extend(index["gallery_ids"])
else:
image_data = index
if image_data:
# log.debug(image_data)
stash.update_image(image_data)
def processPerformers():
query = {
"tags": {
"depth": 0,
"excludes": [],
"modifier": "INCLUDES_ALL",
"value": [tag_stashbox_performer_gallery],
}
}
performers = stash.find_performers(f=query)
for performer in performers:
processPerformer(performer)
def processPerformer(performer):
dir = Path(settings["path"]) / performer["id"]
dir.mkdir(parents=True, exist_ok=True)
nogallery = dir / ".nogallery"
nogallery.touch()
for sid in performer["stash_ids"]:
log.debug(sid)
processPerformerStashid(sid["endpoint"], sid["stash_id"], performer)
def get_stashbox(endpoint):
for sbx_config in stash.get_configuration()["general"]["stashBoxes"]:
if sbx_config["endpoint"] == endpoint:
stashbox = StashBoxInterface(
{"endpoint": sbx_config["endpoint"], "api_key": sbx_config["api_key"]}
)
stash_boxes[endpoint] = stashbox
return stashbox
def processPerformerStashid(endpoint, stashid, p):
log.info(
"processing performer %s, %s endpoint: %s, stash id: %s"
% (
p["name"],
p["id"],
endpoint,
stashid,
)
)
index_file = os.path.join(settings["path"], p["id"], "index.json")
if os.path.exists(index_file):
with open(os.path.join(settings["path"], p["id"], "index.json")) as f:
index = json.load(f)
else:
index = {"files": {}, "galleries": {}, "performer_id": p["id"]}
modified = False
stashbox = get_stashbox(endpoint)
if stashbox:
query = """id
name
images {
id
url
}
urls{
url
type
}
"""
perf = stashbox.find_performer(stashid, fragment=query)
log.debug(perf)
if endpoint not in index["galleries"]:
gallery_input = {
"title": "%s - %s "
% (
p["name"],
endpoint[8:-8],
),
"urls": [
"%s/performers/%s"
% (
endpoint[:-8],
stashid,
)
],
"tag_ids": [tag_stashbox_performer_gallery],
"performer_ids": [p["id"]],
}
gal = stash.create_gallery(gallery_input)
log.info("Created gallery %s" % (gal,))
index["galleries"][endpoint] = gal
modified = True
# check if the gallery still exists and has not been deleted
current_gal = stash.find_gallery(index["galleries"][endpoint])
log.debug("current: %s" % (current_gal,))
if current_gal is None:
log.debug("deleted?")
gallery_input = {
"title": "%s - %s "
% (
p["name"],
endpoint[:-8],
),
"urls": [
"%s/performers/%s"
% (
endpoint[:-8],
stashid,
)
],
"tag_ids": [tag_stashbox_performer_gallery],
"performer_ids": [p["id"]],
}
gal = stash.create_gallery(gallery_input)
log.info("Created gallery %s" % (gal,))
index["galleries"][endpoint] = gal
modified = True
if modified:
with open(index_file, "w") as f:
json.dump(index, f)
for img in perf["images"]:
image_index = Path(settings["path"]) / p["id"] / (img["id"] + ".json")
if not image_index.exists():
with open(image_index, "w") as f:
image_data = {
"title": img["id"],
"urls": [img["url"]],
"performer_ids": [p["id"]],
"tag_ids": [tag_stashbox_performer_gallery],
"gallery_ids": [index["galleries"][endpoint]],
}
json.dump(image_data, f)
filename = Path(settings["path"]) / p["id"] / (img["id"] + ".jpg")
if not os.path.exists(filename):
log.info(
"Downloading image %s to %s"
% (
img["url"],
filename,
)
)
r = requests.get(img["url"])
with open(filename, "wb") as f:
f.write(r.content)
f.close()
# modified=True
else:
log.debug("image already downloaded")
# scrape urls on the performer using the url scrapers in stash
if settings["runPerformerScraper"] and len(perf["urls"]) > 0:
# we need to determine what scrapers we have and what url patterns they accept, query what url patterns are supported, should only need to check once
if len(scrapers) == 0:
scrapers_graphql = """query ListPerformerScrapers {
listScrapers(types: [PERFORMER]) {
id
name
performer {
urls
supported_scrapes
}
}
}"""
res = stash.callGQL(scrapers_graphql)
for r in res["listScrapers"]:
if r["performer"]["urls"]:
for url in r["performer"]["urls"]:
scrapers[url] = r
for u in perf["urls"]:
for url in scrapers.keys():
if url in u["url"]:
log.info(
"Running stash scraper on performer url: %s" % (u["url"],)
)
res = stash.scrape_performer_url(u["url"])
# Check if the scraper returned a result
if res is not None:
log.debug(res)
# it's possible for multiple images to be returned by a scraper so incriment a number each image
image_id = 1
if res["images"]:
for image in res["images"]:
image_index = (
Path(settings["path"])
/ p["id"]
/ (
"%s-%s.json"
% (
scrapers[url]["id"],
image_id,
)
)
)
if not image_index.exists():
with open(image_index, "w") as f:
image_data = {
"title": "%s - %s "
% (
scrapers[url]["id"],
image_id,
),
"details": "name: %s\ngender: %s\nurl: %s\ntwitter: %s\ninstagram: %s\nbirthdate: %s\nethnicity: %s\ncountry: %s\neye_color: %s\nheight: %s\nmeasurements: %s\nfake tits: %s\npenis_length: %s\n career length: %s\ntattoos: %s\npiercings: %s\nhair_color: %s\nweight: %s\n description: %s\n"
% (
res["name"],
res["gender"],
res["url"],
res["twitter"],
res["instagram"],
res["birthdate"],
res["ethnicity"],
res["country"],
res["eye_color"],
res["height"],
res["measurements"],
res["fake_tits"],
res["penis_length"],
res["career_length"],
res["tattoos"],
res["piercings"],
res["hair_color"],
res["weight"],
res["details"],
),
"urls": [
u["url"],
],
"performer_ids": [p["id"]],
"tag_ids": [
tag_stashbox_performer_gallery
],
"gallery_ids": [
index["galleries"][endpoint]
],
}
json.dump(image_data, f)
filename = (
Path(settings["path"])
/ p["id"]
/ (
"%s-%s.jpg"
% (
scrapers[url]["id"],
image_id,
)
)
)
if not filename.exists():
if image.startswith("data:"):
with open(filename, "wb") as f:
f.write(
base64.b64decode(
image.split("base64,")[1]
)
)
f.close()
else:
with open(image_index, "w") as f:
image_data = {
"title": "%s - %s "
% (
scrapers[url]["id"],
image_id,
),
"details": "%s" % (res,),
"urls": [u["url"], image],
"performer_ids": [p["id"]],
"tag_ids": [
tag_stashbox_performer_gallery
],
"gallery_ids": [
index["galleries"][endpoint]
],
}
json.dump(image_data, f)
filename = (
Path(settings["path"])
/ p["id"]
/ ("%s.jpg" % (image_id,))
)
r = requests.get(img["url"])
if r.status_code == 200:
with open(filename, "wb") as f:
f.write(r.content)
f.close()
image_id = image_id + 1
# log.debug('%s %s' % (url['url'],url['type'],))
# stash.scraper
# scrape=stash.scrape_performer_url(ur)
else:
log.error("endpoint %s not configured, skipping" % (endpoint,))
def setPerformerPicture(img):
if len(img["performers"]) == 1:
log.debug(img["paths"]["image"])
res = request_s.get(img["paths"]["image"])
log.debug(res.headers["Content-Type"])
if res.status_code == 200:
encoded = base64.b64encode(res.content).decode()
new_performer = {
"id": img["performers"][0]["id"],
"image": "data:{0};base64,{1}".format(
res.headers["Content-Type"], encoded
),
}
log.info("updating performer with tagged image %s" % (new_performer["id"],))
stash.update_performer(new_performer)
def processQueue():
for id in settings["queue"].split(","):
if len(id) > 0:
p = stash.find_performer(id)
processPerformer(p)
# queue has not changed since we started, clear setting
if (
stash.get_configuration()["plugins"]["stashdb-performer-gallery"]
== settings["queue"]
):
stash.configure_plugin("stashdb-performer-gallery", {"queue": ""})
stash.metadata_scan(paths=[settings["path"]])
stash.run_plugin_task("stashdb-performer-gallery", "relink missing images")
else:
# update remove the completed entries from the queue string leaving the unprocessed and schedule the task again
log.debug("updating queue")
stash.configure_plugin(
"stashdb-performer-gallery",
{
"queue": stash.get_configuration()["plugins"][
"stashdb-performer-gallery"
]["queue"].removeprefix(settings["queue"])
},
)
stash.run_plugin_task(
"stashdb-performer-gallery", "Process Performers", args={"full": False}
)
def relink_images(performer_id=None):
query = {
"path": {"modifier": "INCLUDES", "value": settings["path"]},
}
if performer_id == None:
query["is_missing"] = "galleries"
query["path"] = {"modifier": "INCLUDES", "value": settings["path"]}
else:
query["path"] = {
"modifier": "INCLUDES",
"value": str(Path(settings["path"]) / performer_id / ""),
}
# else:
# query["file_count"] = {"modifier": "NOT_EQUALS", "value": 1}
total = stash.find_images(f=query, get_count=True)[0]
i = 0
images = []
while i < total:
images = stash.find_images(f=query, filter={"page": i, "per_page": per_page})
for img in images:
log.debug("image: %s" % (img,))
processImages(img)
i = i + 1
log.progress((i / total))
json_input = json.loads(sys.stdin.read())
FRAGMENT_SERVER = json_input["server_connection"]
stash = StashInterface(FRAGMENT_SERVER)
config = stash.get_configuration()["plugins"]
settings = {
"path": "/download_dir",
"runPerformerScraper": False,
}
if "stashdb-performer-gallery" in config:
settings.update(config["stashdb-performer-gallery"])
# log.info('config: %s ' % (settings,))
tag_stashbox_performer_gallery = stash.find_tag(
"[Stashbox Performer Gallery]", create=True
).get("id")
tag_performer_image = stash.find_tag("[Set Profile Image]", create=True).get("id")
if "stasdb-performer-gallery" in config:
settings.update(config["stasdb-performer-gallery"])
if "mode" in json_input["args"]:
PLUGIN_ARGS = json_input["args"]["mode"]
if "performer" in json_input["args"]:
p = stash.find_performer(json_input["args"]["performer"])
if tag_stashbox_performer_gallery in [x["id"] for x in p["tags"]]:
processPerformer(p)
stash.metadata_scan(paths=[settings["path"]])
stash.run_plugin_task(
"stashdb-performer-gallery",
"relink missing images",
args={"performer_id": p["id"]},
)
elif "processPerformers" in PLUGIN_ARGS:
processPerformers()
stash.metadata_scan([settings["path"]])
stash.run_plugin_task(
"stashdb-performer-gallery", "relink missing images", args={}
)
elif "processImages" in PLUGIN_ARGS:
if "performer_id" in json_input["args"]:
relink_images(performer_id=json_input["args"]["performer_id"])
else:
relink_images()
elif "hookContext" in json_input["args"]:
id = json_input["args"]["hookContext"]["id"]
if json_input["args"]["hookContext"]["type"] == "Image.Create.Post":
img = stash.find_image(image_in=id)
processImages(img)
if json_input["args"]["hookContext"]["type"] == "Image.Update.Post":
img = stash.find_image(image_in=id)
if tag_performer_image in [x["id"] for x in img["tags"]]:
setPerformerPicture(img)
if json_input["args"]["hookContext"]["type"] == "Performer.Update.Post":
stash.run_plugin_task(
"stashdb-performer-gallery", "Process Performers", args={"performer": id}
)

View File

@@ -0,0 +1,41 @@
name: stashdb performer gallery
description: Automatically download performer images from stashdb or other stash-boxes. Add the [Stashbox Performer Gallery] tag to a performer and it will create a gallery of images from that stash-box database. Apply the tag [Set Profile Image] to an image to set it as the profile image of that performer. Note you will need to configure the download path and add this as a path under settings > library
version: 0.2
url: https://github.com/stashapp/CommunityScripts/
exec:
- python
- "{pluginDir}/stashdb-performer-gallery.py"
interface: raw
settings:
path:
displayName: Download parent folder
description: Download location for files, note this should be in a different folder to stash and in a folder covered by stash. You may need to create a new library path to cover this directory.
type: STRING
runPerformerScraper:
displayName: Run stash scrapers on profile urls
description: Run scrapers on profile urls
type: BOOLEAN
hooks:
- name: modify performer
description: Download galleries on performer update if the [performer gallery] tag is applied
triggeredBy:
- Performer.Update.Post
- name: image add
description: Add images
triggeredBy:
- Image.Create.Post
- name: set profile images
description: Set profile images py adding the [Set Profile Image] tag to the image, there must be exactly 1 performer tagged on the image.
triggeredBy:
- Image.Update.Post
tasks:
- name: "Process Performers"
description: Fetch performer images for performers with the [performer gallery] tag
defaultArgs:
mode: processPerformers
- name: "relink missing images"
description: reprocess missing images
defaultArgs:
mode: processImages