[renamerOnUpdate] Added On/Off Hook, more options (#45)

This commit is contained in:
Belleyy
2022-06-22 23:32:18 +02:00
committed by GitHub
parent 122e6ba539
commit 91b7bc5cf8
3 changed files with 188 additions and 108 deletions

View File

@@ -33,6 +33,8 @@
# $date $title - $tags == 2016-12-29 Her Fantasy Ball - Blowjob Cumshot Facial Tattoo
#
####################################################################
# STASH #
stash_url = "localhost"
# TEMPLATE #
@@ -67,6 +69,9 @@ log_file = r""
######################################
# Settings #
# rename associated file (subtitle, funscript) if present
associated_extension = ["srt", "vtt", "funscript"]
# Character which replaces every space in the filename
# Common values are "." and "_"
# e. g.:
@@ -74,12 +79,20 @@ log_file = r""
# 2016-12-29.Eva.Lovia.-.Her.Fantasy.Ball
filename_splitchar = " "
# replace space for stash field (title, performer...), if you have a title 'I love Stash' it can become 'I_love_Stash'
field_whitespaceSeperator = ""
# put the filename in lowercase
lowercase_Filename = False
# remove these characters if there are present in the filename
removecharac_Filename = ",#"
# Character to use as a performer separator.
performer_splitchar = " "
# Maximum number of performer names in the filename. If there are more than that in a scene the filename will not include any performer name!
performer_limit = 3
# Ignore male performers.
performer_ignore_male = False
# ignore certain gender. Available "MALE" "FEMALE" "TRANSGENDER_MALE" "TRANSGENDER_FEMALE" "INTERSEX" "NON_BINARY"
performer_ignoreGender = []
# If $performer is before $title, prevent having duplicate text.
# e.g.:
@@ -87,6 +100,10 @@ performer_ignore_male = False
# 2016 Dani Daniels - Dani Daniels in ***.mp4 --> 2016 Dani Daniels in ***.mp4
prevent_title_performer = False
# Removes prepositions from the beginning of titles
prepositions_list = ['The', 'A', 'An']
prepositions_removal = False
# Squeeze studio names removes all spaces in studio, parent studio and studio family name
# e. g.:
# Reality Kings --> RealityKings
@@ -122,13 +139,15 @@ only_organized = False
# If the new path is over 240 characters, the plugin will try to reduce it. Set to True to ignore that.
ignore_path_length = False
# Field to remove if the path is too long. First in list will be removed then second then ... if length is still too long.
# Field to remove if the path is too long. First in list will be removed then second then ... if length is still too long.
order_field = ["$video_codec", "$audio_codec", "$resolution", "tags", "rating", "$height", "$studio_family", "$studio", "$parent_studio", "$performer"]
# Alternate way to show diff. Not useful at all.
alt_diff_display = False
# disable/enable the hook. You can edit this value in 'Plugin Tasks' inside of Stash.
enable_hook = True
######################################
# Module Related #
@@ -147,3 +166,4 @@ process_kill_attach = False
# Warning: If you have non-latin characters (Cyrillic, Kanji, Arabic, ...), the result will be extremely different.
use_ascii = False
# =========================

View File

@@ -3,28 +3,27 @@ import json
import os
import re
import sqlite3
import subprocess
import sys
import time
import requests
try:
import psutil # pip install psutil
MODULE_PSUTIL = True
except:
except Exception:
MODULE_PSUTIL = False
try:
import unidecode # pip install Unidecode
MODULE_UNIDECODE = True
except:
except Exception:
MODULE_UNIDECODE = False
import config
import log
DRY_RUN = False
FRAGMENT = json.loads(sys.stdin.read())
@@ -36,11 +35,6 @@ if FRAGMENT_SCENE_ID:
FRAGMENT_SCENE_ID = FRAGMENT_SCENE_ID["id"]
PLUGIN_ARGS = FRAGMENT['args'].get("mode")
if PLUGIN_ARGS:
log.LogDebug("--Starting Plugin 'Renamer'--")
else:
log.LogDebug("--Starting Hook 'Renamer'--")
#log.LogDebug("{}".format(FRAGMENT))
@@ -58,7 +52,7 @@ def callGraphQL(query, variables=None, raise_exception=True):
"Connection": "keep-alive",
"DNT": "1"
}
graphql_domain = 'localhost'
graphql_domain = STASH_URL
# Stash GraphQL endpoint
graphql_url = graphql_scheme + "://" + graphql_domain + ":" + str(graphql_port) + "/graphql"
@@ -66,7 +60,7 @@ def callGraphQL(query, variables=None, raise_exception=True):
if variables is not None:
json['variables'] = variables
try:
response = requests.post(graphql_url, json=json,headers=graphql_headers, cookies=graphql_cookies, timeout=20)
response = requests.post(graphql_url, json=json, headers=graphql_headers, cookies=graphql_cookies, timeout=20)
except Exception as e:
exit_plugin(err="[FATAL] Exception with GraphQL request. {}".format(e))
if response.status_code == 200:
@@ -116,34 +110,23 @@ def graphql_getScene(scene_id):
bitrate
}
studio {
...SlimStudioData
id
name
parent_studio {
id
name
}
}
tags {
...SlimTagData
id
name
}
performers {
...PerformerData
id
name
gender
}
}
fragment SlimStudioData on Studio {
id
name
parent_studio {
id
name
}
aliases
}
fragment SlimTagData on Tag {
id
name
aliases
}
fragment PerformerData on Performer {
id
name
gender
}
"""
variables = {
"id": scene_id
@@ -165,6 +148,7 @@ def graphql_getConfiguration():
result = callGraphQL(query)
return result.get('configuration')
def graphql_getStudio(studio_id):
query = """
query FindStudio($id:ID!) {
@@ -174,9 +158,7 @@ def graphql_getStudio(studio_id):
parent_studio {
id
name
aliases
}
aliases
}
}
"""
@@ -186,36 +168,42 @@ def graphql_getStudio(studio_id):
result = callGraphQL(query, variables)
return result.get("findStudio")
def makeFilename(scene_information, query):
new_filename = str(query)
for field in TEMPLATE_FIELD:
field_name = field.replace("$","")
field_name = field.replace("$", "")
if field in new_filename:
if scene_information.get(field_name):
if field == "$performer":
if re.search(r"\$performer[-\s_]*\$title", new_filename) and scene_information.get('title') and PREVENT_TITLE_PERF:
if re.search("^{}".format(scene_information["performer"]), scene_information["title"]):
log.LogInfo("Ignoring the performer field because it's already in start of title")
new_filename = re.sub('\$performer[-\s_]*', '', new_filename)
new_filename = new_filename.replace(field, "")
continue
new_filename = new_filename.replace(field, scene_information[field_name])
else:
new_filename = re.sub('\${}[-\s_]*'.format(field_name), '', new_filename)
new_filename = new_filename.replace(field, "")
# remove []
new_filename = re.sub('\[\W*]', '', new_filename)
# Remove multiple space/_ in row
new_filename = re.sub('[\s_]{2,}', ' ', new_filename)
# Remove multiple - in row
new_filename = re.sub('(?:[\s_]-){2,}', ' -', new_filename)
# cleanup
new_filename = re.sub(r'[\s_-]+(?=\W{2})', ' ', new_filename)
# remove multi space
new_filename = re.sub(r'\s+', ' ', new_filename)
# remove thing like 'test - ]'
for c in ["[", "("]:
new_filename = re.sub(r'{}[_\s-]+'.format("\\" + c), c, new_filename)
for c in ["]", ")"]:
new_filename = re.sub(r'[_\s-]+{}'.format("\\" + c), c, new_filename)
# remove () []
new_filename = re.sub(r'\(\W*\)|\[\W*\]', '', new_filename)
# Remove space at start/end
new_filename = new_filename.strip(" -")
new_filename = new_filename.strip(" -_")
# Replace spaces with splitchar
new_filename = new_filename.replace(' ', FILENAME_SPLITCHAR)
return new_filename
def find_diff_text(a, b):
def find_diff_text(a: str, b: str):
addi = minus = stay = ""
minus_ = addi_ = 0
for _, s in enumerate(difflib.ndiff(a, b)):
@@ -230,15 +218,15 @@ def find_diff_text(a, b):
addi += s[-1]
addi_ += 1
if minus_ > 20 or addi_ > 20:
log.LogDebug("Diff Checker: +{}; -{};".format(addi_,minus_))
log.LogDebug("Diff Checker: +{}; -{};".format(addi_, minus_))
log.LogDebug("OLD: {}".format(a))
log.LogDebug("NEW: {}".format(b))
else:
log.LogDebug("Original: {}\n- Charac: {}\n+ Charac: {}\n Result: {}".format(a, minus, addi, b))
return
return
def has_handle(fpath,all_result=False):
def has_handle(fpath, all_result=False):
lst = []
for proc in psutil.process_iter():
try:
@@ -253,6 +241,20 @@ def has_handle(fpath,all_result=False):
return lst
def config_edit(name: str, state: bool):
found = 0
with open(config.__file__, 'r') as file:
config_lines = file.readlines()
with open(config.__file__, 'w') as file_w:
for line in config_lines:
if name in line.split("=")[0].strip():
file_w.write("{} = {}\n".format(name, state))
found += 1
else:
file_w.write(line)
return found
def exit_plugin(msg=None, err=None):
if msg is None and err is None:
msg = "plugin ended"
@@ -261,6 +263,24 @@ def exit_plugin(msg=None, err=None):
sys.exit()
if PLUGIN_ARGS:
log.LogDebug("--Starting Plugin 'Renamer'--")
if "enable" in PLUGIN_ARGS:
log.LogInfo("Enable hook")
success = config_edit("enable_hook", True)
elif "disable" in PLUGIN_ARGS:
log.LogInfo("Disable hook")
success = config_edit("enable_hook", False)
if not success:
log.LogError("Script failed to change the value of 'enable_hook' variable")
exit_plugin("script finished")
else:
if not config.enable_hook:
exit_plugin("Hook disabled")
else:
log.LogDebug("--Starting Hook 'Renamer'--")
STASH_URL = config.stash_url
LOGFILE = config.log_file
STASH_SCENE = graphql_getScene(FRAGMENT_SCENE_ID)
@@ -275,14 +295,24 @@ filename_template = None
# READING CONFIG
if config.only_organized and not STASH_SCENE["organized"]:
exit_plugin("Scene ignored (not organized)")
ASSOCIATED_EXT = config.associated_extension
FIELD_WHITESPACE_SEP = config.field_whitespaceSeperator
FILENAME_LOWER = config.lowercase_Filename
FILENAME_SPLITCHAR = config.filename_splitchar
FILENAME_REMOVECHARACTER = config.removecharac_Filename
PERFORMER_SPLITCHAR = config.performer_splitchar
PERFORMER_LIMIT = config.performer_limit
PERFORMER_IGNORE_MALE = config.performer_ignore_male
PERFORMER_IGNOREGENDER = config.performer_ignoreGender
PREVENT_TITLE_PERF = config.prevent_title_performer
PREPOSITIONS_LIST = config.prepositions_list
PREPOSITIONS_REMOVAL = config.prepositions_removal
SQUEEZE_STUDIO_NAMES = config.squeeze_studio_names
@@ -302,9 +332,6 @@ UNICODE_USE = config.use_ascii
ORDER_SHORTFIELD = config.order_field
ALT_DIFF_DISPLAY = config.alt_diff_display
if config.only_organized and not STASH_SCENE["organized"]:
exit_plugin("Scene ignored (not organized)")
# ================================================================ #
# RENAMER #
# Tags > Studios > Default
@@ -342,7 +369,7 @@ if not filename_template:
#log.LogDebug("Using this template: {}".format(filename_template))
current_path = STASH_SCENE["path"]
current_path = str(STASH_SCENE["path"])
# note: contain the dot (.mp4)
file_extension = os.path.splitext(current_path)[1]
# note: basename contains the extension
@@ -355,7 +382,10 @@ scene_information = {}
# Grab Title (without extension if present)
if STASH_SCENE.get("title"):
# Removing extension if present in title
scene_information["title"] = re.sub("{}$".format(file_extension), "", STASH_SCENE["title"])
scene_information["title"] = re.sub(r"{}$".format(file_extension), "", STASH_SCENE["title"])
if PREPOSITIONS_REMOVAL:
for word in PREPOSITIONS_LIST:
scene_information["title"] = re.sub(r"^{}[\s_-]".format(word), "", scene_information["title"])
# Grab Date
scene_information["date"] = STASH_SCENE.get("date")
@@ -366,26 +396,23 @@ if STASH_SCENE.get("rating"):
# Grab Performer
if STASH_SCENE.get("performers"):
perf_list = ""
if len(STASH_SCENE["performers"]) > PERFORMER_LIMIT:
perf_list = []
for perf in STASH_SCENE["performers"]:
if perf.get("gender"):
if perf["gender"] in PERFORMER_IGNOREGENDER:
continue
perf_list.append(perf["name"])
if len(perf_list) > PERFORMER_LIMIT:
log.LogInfo("More than {} performer(s). Ignoring $performer".format(PERFORMER_LIMIT))
else:
for perf in STASH_SCENE["performers"]:
#log.LogDebug(performer)
if PERFORMER_IGNORE_MALE:
if perf["gender"] != "MALE":
perf_list += perf["name"] + PERFORMER_SPLITCHAR
else:
perf_list += perf["name"] + PERFORMER_SPLITCHAR
perf_list = perf_list[:-len(PERFORMER_SPLITCHAR)]
scene_information["performer"] = perf_list
perf_list = []
scene_information["performer"] = PERFORMER_SPLITCHAR.join(perf_list)
# Grab Studio name
if STASH_SCENE.get("studio"):
if SQUEEZE_STUDIO_NAMES:
scene_information["studio"] = STASH_SCENE["studio"].get("name").replace(' ', '')
scene_information["studio"] = STASH_SCENE["studio"]["name"].replace(' ', '')
else:
scene_information["studio"] = STASH_SCENE["studio"].get("name")
scene_information["studio"] = STASH_SCENE["studio"]["name"]
scene_information["studio_family"] = scene_information["studio"]
# Grab Parent name
if STASH_SCENE["studio"].get("parent_studio"):
@@ -397,23 +424,18 @@ if STASH_SCENE.get("studio"):
# Grab Tags
if STASH_SCENE.get("tags"):
tag_list = ""
tag_list = []
for tag in STASH_SCENE["tags"]:
if tag["name"]:
if tag["name"] in TAGS_BLACKLIST:
continue
else:
if len(TAGS_WHITELIST) > 0:
if tag["name"] in TAGS_WHITELIST:
tag_list += tag["name"] + TAGS_SPLITCHAR
else:
continue
else:
tag_list += tag["name"] + TAGS_SPLITCHAR
else:
# ignore tag in blacklist
if tag["name"] in TAGS_BLACKLIST:
continue
tag_list = tag_list[:-len(TAGS_SPLITCHAR)]
scene_information["tags"] = tag_list
# check if there is a whilelist
if len(TAGS_WHITELIST) > 0:
if tag["name"] in TAGS_WHITELIST:
tag_list.append(tag["name"])
else:
tag_list.append(tag["name"])
scene_information["tags"] = TAGS_SPLITCHAR.join(tag_list)
# Grab Height (720p,1080p,4k...)
scene_information["resolution"] = 'SD'
@@ -430,27 +452,38 @@ if STASH_SCENE["file"]["height"] > STASH_SCENE["file"]["width"]:
scene_information["resolution"] = 'VERTICAL'
# Grab Video and Audio codec
scene_information["video_codec"] = STASH_SCENE["file"]["video_codec"]
scene_information["audio_codec"] = STASH_SCENE["file"]["audio_codec"]
log.LogDebug("[{}] Scene information: {}".format(FRAGMENT_SCENE_ID,scene_information))
scene_information["video_codec"] = STASH_SCENE["file"]["video_codec"].upper()
scene_information["audio_codec"] = STASH_SCENE["file"]["audio_codec"].upper()
if scene_information.get("date"):
scene_information["year"] = scene_information["date"][0:4]
if FIELD_WHITESPACE_SEP:
for key, value in scene_information.items():
if type(value) is str:
scene_information[key] = value.replace(" ", FIELD_WHITESPACE_SEP)
elif type(value) is list:
scene_information[key] = [x.replace(" ", FIELD_WHITESPACE_SEP) for x in value]
log.LogDebug("[{}] Scene information: {}".format(FRAGMENT_SCENE_ID, scene_information))
# Create the new filename
new_filename = makeFilename(scene_information, filename_template) + file_extension
if FILENAME_LOWER:
new_filename = new_filename.lower()
# Remove illegal character for Windows ('#' and ',' is not illegal you can remove it)
new_filename = re.sub('[\\/:"*?<>|#,]+', '', new_filename)
# Remove illegal character for Windows
new_filename = re.sub('[\\/:"*?<>|]+', '', new_filename)
if FILENAME_REMOVECHARACTER:
new_filename = re.sub('[{}]+'.format(FILENAME_REMOVECHARACTER), '', new_filename)
# Trying to remove non standard character
if MODULE_UNIDECODE and UNICODE_USE:
new_filename = unidecode.unidecode(new_filename, errors='preserve')
else:
# Using typewriter for Apostrophe
new_filename = re.sub("[]+", "'", new_filename)
new_filename = re.sub("[”“]+", "'", new_filename)
# Replace the old filename by the new in the filepath
new_path = current_path.rstrip(current_filename) + new_filename
@@ -462,7 +495,7 @@ if len(new_path) > 240 and not IGNORE_PATH_LENGTH:
for word in ORDER_SHORTFIELD:
if word not in filename_template:
continue
filename_template = re.sub('\{}[-\s_]*'.format(word), '', filename_template).strip()
filename_template = re.sub(r'\{}[-\s_]*'.format(word), '', filename_template).strip()
log.LogDebug("Removing field: {}".format(word))
new_filename = makeFilename(scene_information, filename_template) + file_extension
new_path = current_path.rstrip(current_filename) + new_filename
@@ -479,12 +512,13 @@ if (new_path == current_path):
exit_plugin("Filename already ok. ({})".format(current_filename))
if ALT_DIFF_DISPLAY:
find_diff_text(current_filename,new_filename)
find_diff_text(current_filename, new_filename)
else:
log.LogDebug("[OLD] Filename: {}".format(current_filename))
log.LogDebug("[NEW] Filename: {}".format(new_filename))
if DRY_RUN:
exit_plugin("Dry run")
# Connect to the DB
try:
@@ -494,23 +528,22 @@ try:
except sqlite3.Error as error:
exit_plugin(err="FATAL SQLITE Error: {}".format(error))
# Looking for duplicate filename
folder_name = os.path.basename(os.path.dirname(new_path))
cursor.execute("SELECT id FROM scenes WHERE path LIKE ? AND NOT id=?;", ["%" + folder_name + "_" + new_filename, FRAGMENT_SCENE_ID])
# Looking for duplicate path
cursor.execute("SELECT id FROM scenes WHERE path LIKE ? AND NOT id=?;", ["%" + current_directory + "_" + new_filename, FRAGMENT_SCENE_ID])
dupl_check = cursor.fetchall()
if len(dupl_check) > 0:
for dupl_row in dupl_check:
log.LogError("Identical path: [{}]".format(dupl_row[0]))
exit_plugin(err="Duplicate path detected, check log!")
# Looking for duplicate filename
cursor.execute("SELECT id FROM scenes WHERE path LIKE ? AND NOT id=?;", ["%" + new_filename, FRAGMENT_SCENE_ID])
dupl_check = cursor.fetchall()
if len(dupl_check) > 0:
for dupl_row in dupl_check:
log.LogInfo("Duplicate filename: [{}]".format(dupl_row[0]))
log.LogWarning("Duplicate filename: [{}]".format(dupl_row[0]))
# OS Rename
if (os.path.isfile(current_path) == True):
if os.path.isfile(current_path):
try:
os.rename(current_path, new_path)
except PermissionError as err:
@@ -532,13 +565,13 @@ if (os.path.isfile(current_path) == True):
else:
log.LogError(err)
sys.exit()
if (os.path.isfile(new_path) == True):
if os.path.isfile(new_path):
log.LogInfo("[OS] File Renamed!")
if LOGFILE:
with open(LOGFILE, 'a', encoding='utf-8') as f:
f.write("{}|{}|{}\n".format(FRAGMENT_SCENE_ID, current_path, new_path))
else:
# I don't think it's possible.
# I don't think it's possible.
exit_plugin(err="[OS] Failed to rename the file ? {}".format(new_path))
else:
exit_plugin(err="[OS] File doesn't exist in your Disk/Drive ({})".format(current_path))
@@ -551,5 +584,23 @@ cursor.close()
sqliteConnection.close()
log.LogInfo("[SQLITE] Database updated and closed!")
if ASSOCIATED_EXT:
for ext in ASSOCIATED_EXT:
p = os.path.splitext(current_path)[0] + "." + ext
p_new = os.path.splitext(new_path)[0] + "." + ext
if os.path.isfile(p):
try:
os.rename(p, p_new)
except Exception as err:
log.LogError("Something prevents renaming this file '{}' - err: {}".format(p, err))
continue
if os.path.isfile(p_new):
log.LogInfo("[OS] Associate file renamed ({})".format(p_new))
if LOGFILE:
try:
with open(LOGFILE, 'a', encoding='utf-8') as f:
f.write("{}|{}|{}\n".format(FRAGMENT_SCENE_ID, p, p_new))
except Exception as err:
os.rename(p_new, p)
log.LogError("Restoring the original name, error writing the logfile: {}".format(err))
exit_plugin("Successful!")

View File

@@ -1,7 +1,7 @@
name: renamerOnUpdate
description: Rename filename based on a template.
url: https://github.com/stashapp/CommunityScripts
version: 1.43
version: 1.5
exec:
- python
- "{pluginDir}/renamerOnUpdate.py"
@@ -11,3 +11,12 @@ hooks:
description: Rename file when you update a scene.
triggeredBy:
- Scene.Update.Post
tasks:
- name: 'Disable'
description: Disable the hook
defaultArgs:
mode: disable
- name: 'Enable'
description: Enable the hook
defaultArgs:
mode: enable