Skip to content

Commit db450a1

Browse files
authored
Merge branch 'stashapp:main' into main
2 parents 1ff9f1d + f5ff6af commit db450a1

File tree

9 files changed

+106
-36
lines changed

9 files changed

+106
-36
lines changed

plugins/DateParser/date_parser.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import os
12
import sys, json
2-
3+
from PythonDepManager import ensure_import
4+
ensure_import("dateparser>=1.2.1")
5+
ensure_import("stashapi:stashapp-tools")
36
import stashapi.log as log
47
from stashapi.stashapp import StashInterface
58
import re
@@ -11,30 +14,38 @@ def main():
1114
global pattern
1215

1316
pattern = re.compile(
14-
r"\D(\d{4}|\d{1,2})[\._\- /\\](\d{1,2}|[a-zA-Z]{3,}\.*)[\._\- /\\](\d{4}|\d{1,2})\D"
17+
r"\D((\d{4}|\d{1,2})[\._\- /\\](\d{1,2}|[a-zA-Z]{3,}\.*)[\._\- /\\](\d{4}|\d{1,2}))\D*"
1518
)
1619
json_input = json.loads(sys.stdin.read())
1720
mode_arg = json_input["args"]["mode"]
1821

1922
stash = StashInterface(json_input["server_connection"])
20-
23+
config = stash.get_configuration()["plugins"]
24+
settings = {"setTitle": False}
25+
if "date_parser" in config:
26+
settings.update(config["date_parser"])
2127
if mode_arg == "gallery":
22-
find_date_for_galleries()
28+
find_date_for_galleries(settings)
2329

2430

2531
def parse_date_candidate(string):
2632
result = None
2733
for match in pattern.finditer(string):
28-
g1 = match.group(1)
29-
g2 = match.group(2)
30-
g3 = match.group(3)
34+
g0 = match.group(1)
35+
g1 = match.group(2)
36+
g2 = match.group(3)
37+
g3 = match.group(4)
3138
temp = parse(g1 + " " + g2 + " " + g3)
3239
if temp:
33-
result = temp.strftime("%Y-%m-%d")
40+
potential_title = None
41+
_,ext = os.path.splitext(string)
42+
if not ext and g0 in os.path.basename(string):
43+
potential_title = os.path.basename(string).replace(g0, "").strip()
44+
result = [temp.strftime("%Y-%m-%d"), potential_title]
3445
return result
3546

3647

37-
def find_date_for_galleries():
48+
def find_date_for_galleries(settings):
3849

3950
galleries = stash.find_galleries(f={"is_missing": "date"})
4051

@@ -60,9 +71,11 @@ def find_date_for_galleries():
6071
"Gallery ID ("
6172
+ gallery.get("id")
6273
+ ") has matched the date : "
63-
+ acceptableDate
74+
+ acceptableDate[0]
6475
)
65-
updateObject = {"id": gallery.get("id"), "date": acceptableDate}
76+
updateObject = {"id": gallery.get("id"), "date": acceptableDate[0]}
77+
if settings['setTitle'] and not gallery.get("title") and acceptableDate[1]:
78+
updateObject["title"] = acceptableDate[1]
6679
stash.update_gallery(updateObject)
6780

6881

plugins/DateParser/date_parser.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
name: Gallery Date Parser
2+
# requires: PythonDepManager
23
description: Find date in path or filename and add it to the gallery
3-
version: 0.3.0
4+
version: 1.1.0
45
exec:
56
- python
67
- "{pluginDir}/date_parser.py"
@@ -10,3 +11,8 @@ tasks:
1011
description: Add the date on galleries based on their path
1112
defaultArgs:
1213
mode: gallery
14+
settings:
15+
setTitle:
16+
displayName: Set title ?
17+
description: Set title from folder name minus the found date ? Only applies on galleries without titles
18+
type: BOOLEAN

plugins/bulkImageScrape/BulkImageScrape.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: Bulk Image Scrape
22
# requires: PythonDepManager
33
description: Apply an image scraper to all images
4-
version: 0.3.2
4+
version: 0.3.3
55
url: https://github.com/stashapp/CommunityScripts/
66
exec:
77
- python
@@ -33,6 +33,9 @@ settings:
3333
SkipEntriesNum:
3434
displayName: number of entries to skip over (mostly for rerunning after an error on large collections)
3535
type: NUMBER
36+
PerformerIdsFilter:
37+
displayName: List of performers IDs (comma separated) to filter images the plugins sees
38+
type: STRING
3639

3740
tasks:
3841
- name: "Bulk Image Scrape"

plugins/bulkImageScrape/bulkImageScrape.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,28 @@ def parse_skip_tags(client: StashInterface, skip_tags: str) -> list[str]:
114114
tag_ids.append(tag_id)
115115
return tag_ids
116116

117+
def parse_performerIds_filter(performerIdsFilter: str) -> list[str]:
118+
"""
119+
Parse a list of performer ids
120+
"""
121+
if performerIdsFilter == "" or performerIdsFilter is None:
122+
return []
123+
124+
performerIdsFilter = performerIdsFilter.split(",")
125+
return performerIdsFilter
117126

118-
def build_image_filter(skip_tags: list[str], exclude_organized: bool) -> dict:
127+
128+
def build_image_filter(skip_tags: list[str], performerIdsFilter: list[str], exclude_organized: bool
129+
) -> dict:
119130
image_filter: dict = {}
120131

132+
if performerIdsFilter is not None and len(performerIdsFilter) > 0:
133+
log.info(f"Images filtered for performer ID : {performerIdsFilter}")
134+
image_filter["performers"] = {
135+
"value": performerIdsFilter,
136+
"modifier": "INCLUDES",
137+
}
138+
121139
if exclude_organized:
122140
image_filter["organized"] = False
123141

@@ -134,12 +152,12 @@ def build_image_filter(skip_tags: list[str], exclude_organized: bool) -> dict:
134152

135153

136154
def count_all_images(
137-
client: StashInterface, skip_tags: list[str], exclude_organized: bool
155+
client: StashInterface, skip_tags: list[str], performerIdsFilter: list[str], exclude_organized: bool
138156
) -> int:
139157
"""
140158
count all images from the stash
141159
"""
142-
image_filter: dict = build_image_filter(skip_tags=skip_tags, exclude_organized=exclude_organized)
160+
image_filter: dict = build_image_filter(skip_tags=skip_tags, performerIdsFilter=performerIdsFilter, exclude_organized=exclude_organized)
143161

144162
all_results: dict = {
145163
"page": 1,
@@ -154,12 +172,12 @@ def count_all_images(
154172

155173

156174
def get_all_images(
157-
client: StashInterface, skip_tags: list[str], exclude_organized: bool, skip_entries: int = 0
175+
client: StashInterface, skip_tags: list[str], performerIdsFilter: list[str], exclude_organized: bool, skip_entries: int = 0
158176
) -> Generator[dict, None, None]:
159177
"""
160178
Get all images from the stash
161179
"""
162-
image_filter: dict = build_image_filter(skip_tags=skip_tags, exclude_organized=exclude_organized)
180+
image_filter: dict = build_image_filter(skip_tags=skip_tags, performerIdsFilter=performerIdsFilter, exclude_organized=exclude_organized)
163181

164182
page_size = 100
165183
page = 1
@@ -281,12 +299,14 @@ def update_image(client: StashInterface, update: dict) -> dict | None:
281299
settings: dict[str, any] = {
282300
"ScraperID": "",
283301
"SkipTags": "",
302+
"PerformerIdsFilter": "",
284303
"CreateMissingPerformers": False,
285304
"CreateMissingStudios": False,
286305
"CreateMissingTags": False,
287306
"MergeExistingTags": False,
288307
"ExcludeOrganized": False,
289308
"SkipEntriesNum": 0,
309+
290310
}
291311

292312
if "BulkImageScrape" in config:
@@ -308,14 +328,15 @@ def update_image(client: StashInterface, update: dict) -> dict | None:
308328
validate_stashapi(StashItem.IMAGE, stash)
309329
scraper_id: str = validate_scraper(stash, settings["ScraperID"])
310330
parsed_skip_tags: list[str] = parse_skip_tags(stash, settings["SkipTags"])
331+
parsed_performerids_filter: list[str] = parse_performerIds_filter(settings["PerformerIdsFilter"])
311332

312333
#
313334
# MAIN
314335
#
315336

316337
log.info("Querying images from stash")
317338
total_images: int = count_all_images(
318-
stash, parsed_skip_tags, settings["ExcludeOrganized"]
339+
stash, parsed_skip_tags, parsed_performerids_filter, settings["ExcludeOrganized"]
319340
) - settings["SkipEntriesNum"]
320341

321342
if total_images == 0:
@@ -325,7 +346,7 @@ def update_image(client: StashInterface, update: dict) -> dict | None:
325346
log.info(f"Found {total_images} images")
326347

327348
images: Generator[dict, None, None] = get_all_images(
328-
stash, parsed_skip_tags, settings["ExcludeOrganized"], settings["SkipEntriesNum"]
349+
stash, parsed_skip_tags, parsed_performerids_filter, settings["ExcludeOrganized"], settings["SkipEntriesNum"]
329350
)
330351

331352
for i, image in enumerate(images, start=1):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Image Date From Metadata
2+
3+
https://discourse.stashapp.cc/t/image-date-from-metadata/2225

plugins/nfoSceneParser/config.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
# ! Not yet implemented. Currently, only "with files" is supported
1111
nfo_location = "with files"
1212

13+
# By default the plugin will look for an nfo based on file name:
14+
# "Movie Title (2023).nfo"
15+
# If you want to use a custom file name eg. "movie.nfo", set it here.
16+
custom_nfo_name = ""
17+
1318
# If True, will never update already "organized" scenes.
1419
skip_organized = True
1520

@@ -41,6 +46,15 @@
4146
create_missing_tags = True
4247
create_missing_movies = True
4348

49+
# Choose which field should be parsed into user rating
50+
# and if it should be multiplied by a factor.
51+
user_rating_field = "userrating"
52+
user_rating_multiplier = 1
53+
54+
# Let you decide from where genres should be loaded from.
55+
# Possible values: "tags", "genres", "both"
56+
load_tags_from = "both"
57+
4458
###############################################################################
4559
# Do not change config below unless you are absolutely sure of what you do...
4660
###############################################################################

plugins/nfoSceneParser/nfoParser.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@ def __init__(self, scene_path, defaults=None, folder_mode=False):
1616
self._defaults = defaults
1717
# Finds nfo file
1818
self._nfo_file = None
19+
dir_path = os.path.dirname(scene_path)
1920
if config.nfo_location.lower() == "with files":
2021
if folder_mode:
2122
# look in current dir & parents for a folder.nfo file...
22-
dir_path = os.path.dirname(scene_path)
2323
self._nfo_file = self._find_in_parents(dir_path, "folder.nfo")
2424
else:
25-
self._nfo_file = os.path.splitext(scene_path)[0] + ".nfo"
25+
if len(getattr(config, "custom_nfo_name", "")) > 0:
26+
self._nfo_file = os.path.join(dir_path, config.custom_nfo_name)
27+
else:
28+
self._nfo_file = os.path.splitext(scene_path)[0] + ".nfo"
2629
# else:
2730
# TODO: support dedicated dir instead of "with files" (compatibility with nfo exporters)
2831
self._nfo_root = None
@@ -56,7 +59,7 @@ def __read_cover_image_file(self):
5659
# Not found? Look tor folder image...
5760
path_dir = os.path.dirname(self._nfo_file)
5861
folder_files = sorted(glob.glob(f"{glob.escape(path_dir)}{os.path.sep}*.*"))
59-
folder_pattern = re.compile("^.*(landscape\\d{0,2}|thumb\\d{0,2}|poster\\d{0,2}|cover\\d{0,2})\\.(jpe?g|png|webp)$", re.I)
62+
folder_pattern = re.compile("^.*(landscape\\d{0,2}|thumb\\d{0,2}|poster\\d{0,2}|folder\\d{0,2}|cover\\d{0,2})\\.(jpe?g|png|webp)$", re.I)
6063
result = self.__match_image_files(folder_files, folder_pattern)
6164
return result
6265

@@ -103,7 +106,8 @@ def __extract_cover_images_b64(self):
103106
return file_images
104107

105108
def __extract_nfo_rating(self):
106-
user_rating = round(float(self._nfo_root.findtext("userrating") or 0))
109+
multiplier = getattr(config, "user_rating_multiplier", 1)
110+
user_rating = round(float(self._nfo_root.findtext(getattr(config, "user_rating_field", "userrating")) or 0) * multiplier)
107111
if user_rating > 0:
108112
return user_rating
109113
# <rating> is converted to a scale of 5 if needed
@@ -124,17 +128,20 @@ def __extract_nfo_date(self):
124128
return self._nfo_root.findtext("premiered") or year
125129

126130
def __extract_nfo_tags(self):
131+
source = getattr(config, "load_tags_from", "both").lower()
127132
file_tags = []
128-
# from nfo <tag>
129-
tags = self._nfo_root.findall("tag")
130-
for tag in tags:
131-
if tag.text:
132-
file_tags.append(tag.text)
133-
# from nfo <genre>
134-
genres = self._nfo_root.findall("genre")
135-
for genre in genres:
136-
if genre.text:
137-
file_tags.append(genre.text)
133+
if source in ["tags", "both"]:
134+
# from nfo <tag>
135+
tags = self._nfo_root.findall("tag")
136+
for tag in tags:
137+
if tag.text:
138+
file_tags.append(tag.text)
139+
if source in ["genres", "both"]:
140+
# from nfo <genre>
141+
genres = self._nfo_root.findall("genre")
142+
for genre in genres:
143+
if genre.text:
144+
file_tags.append(genre.text)
138145
return list(set(file_tags))
139146

140147
def __extract_nfo_actors(self):
@@ -147,6 +154,8 @@ def __extract_nfo_actors(self):
147154

148155
def parse(self):
149156
if not self._nfo_file or not os.path.exists(self._nfo_file):
157+
if self._nfo_file:
158+
log.LogDebug(f"The NFO file \"{os.path.split(self._nfo_file)[1]}\" was not found")
150159
return {}
151160
log.LogDebug("Parsing '{}'".format(self._nfo_file))
152161
# Parse NFO xml content

plugins/nfoSceneParser/nfoSceneParser.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: nfoSceneParser
22
description: Fills scene data from NFO or filename pattern
33
url: https://github.com/stashapp/CommunityScripts/tree/main/plugins/nfoSceneParser
4-
version: 1.3.1
4+
version: 1.4.0
55
exec:
66
- python
77
- "{pluginDir}/nfoSceneParser.py"
@@ -16,4 +16,4 @@ tasks:
1616
description: Reload all scenes that have specific "marker" tag (see plugin's config.py)
1717
defaultArgs:
1818
mode: reload
19-
# Last Updated January 3, 2024
19+
# Last Updated June 24, 2025

plugins/nfoSceneParser/stashInterface.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ def gql_updateScene(self, scene_id, scene_data):
149149
"date": scene_data["date"],
150150
"rating100": scene_data["rating"],
151151
"urls": scene_data["urls"],
152+
"studio_id": scene_data["studio_id"],
152153
"code": scene_data["code"],
153154
"performer_ids": scene_data["performer_ids"],
154155
"tag_ids": scene_data["tag_ids"],

0 commit comments

Comments
 (0)