Update StashPluginHelper.py

This commit is contained in:
David Maisonave
2024-08-17 02:10:27 -04:00
parent 2e8b7a43fe
commit ead989ff91

View File

@@ -1,12 +1,8 @@
import stashapi.log as stashLog # stashapi.log by default for error and critical logging
from stashapi.stashapp import StashInterface from stashapi.stashapp import StashInterface
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
import inspect import inspect, sys, os, pathlib, logging, json
import sys import concurrent.futures
import os from stashapi.stash_types import PhashDistance
import pathlib
import logging
import json
import __main__ import __main__
# StashPluginHelper (By David Maisonave aka Axter) # StashPluginHelper (By David Maisonave aka Axter)
@@ -16,7 +12,6 @@ import __main__
# Logging includes source code line number # Logging includes source code line number
# Sets a maximum plugin log file size # Sets a maximum plugin log file size
# Stash Interface Features: # Stash Interface Features:
# Sets STASH_INTERFACE with StashInterface
# Gets STASH_URL value from command line argument and/or from STDIN_READ # Gets STASH_URL value from command line argument and/or from STDIN_READ
# Sets FRAGMENT_SERVER based on command line arguments or STDIN_READ # Sets FRAGMENT_SERVER based on command line arguments or STDIN_READ
# Sets PLUGIN_ID based on the main script file name (in lower case) # Sets PLUGIN_ID based on the main script file name (in lower case)
@@ -27,14 +22,15 @@ import __main__
# Gets DEBUG_TRACING value from command line argument and/or from UI and/or from config file # Gets DEBUG_TRACING value from command line argument and/or from UI and/or from config file
# Sets RUNNING_IN_COMMAND_LINE_MODE to True if detects multiple arguments # Sets RUNNING_IN_COMMAND_LINE_MODE to True if detects multiple arguments
# Sets CALLED_AS_STASH_PLUGIN to True if it's able to read from STDIN_READ # Sets CALLED_AS_STASH_PLUGIN to True if it's able to read from STDIN_READ
class StashPluginHelper: class StashPluginHelper(StashInterface):
# Primary Members for external reference # Primary Members for external reference
PLUGIN_TASK_NAME = None PLUGIN_TASK_NAME = None
PLUGIN_ID = None PLUGIN_ID = None
PLUGIN_CONFIGURATION = None PLUGIN_CONFIGURATION = None
PLUGINS_PATH = None
pluginSettings = None pluginSettings = None
pluginConfig = None pluginConfig = None
STASH_INTERFACE = None STASH_INTERFACE_INIT = False
STASH_URL = None STASH_URL = None
STASH_CONFIGURATION = None STASH_CONFIGURATION = None
JSON_INPUT = None JSON_INPUT = None
@@ -42,6 +38,9 @@ class StashPluginHelper:
DRY_RUN = False DRY_RUN = False
CALLED_AS_STASH_PLUGIN = False CALLED_AS_STASH_PLUGIN = False
RUNNING_IN_COMMAND_LINE_MODE = False RUNNING_IN_COMMAND_LINE_MODE = False
FRAGMENT_SERVER = None
STASHPATHSCONFIG = None
STASH_PATHS = []
# printTo argument # printTo argument
LOG_TO_FILE = 1 LOG_TO_FILE = 1
@@ -59,9 +58,9 @@ class StashPluginHelper:
LOG_FILE_DIR = None LOG_FILE_DIR = None
LOG_FILE_NAME = None LOG_FILE_NAME = None
STDIN_READ = None STDIN_READ = None
FRAGMENT_SERVER = None pluginLog = None
logger = None logLinePreviousHits = []
traceOncePreviousHits = [] thredPool = None
# Prefix message value # Prefix message value
LEV_TRACE = "TRACE: " LEV_TRACE = "TRACE: "
@@ -77,7 +76,8 @@ class StashPluginHelper:
# Externally modifiable variables # Externally modifiable variables
log_to_err_set = LOG_TO_FILE + LOG_TO_STDERR # This can be changed by the calling source in order to customize what targets get error messages log_to_err_set = LOG_TO_FILE + LOG_TO_STDERR # This can be changed by the calling source in order to customize what targets get error messages
log_to_norm = LOG_TO_FILE + LOG_TO_CONSOLE # Can be change so-as to set target output for normal logging log_to_norm = LOG_TO_FILE + LOG_TO_CONSOLE # Can be change so-as to set target output for normal logging
log_to_wrn_set = LOG_TO_FILE + LOG_TO_STASH # This can be changed by the calling source in order to customize what targets get warning messages # Warn message goes to both plugin log file and stash when sent to Stash log file.
log_to_wrn_set = LOG_TO_STASH # This can be changed by the calling source in order to customize what targets get warning messages
def __init__(self, def __init__(self,
debugTracing = None, # Set debugTracing to True so as to output debug and trace logging debugTracing = None, # Set debugTracing to True so as to output debug and trace logging
@@ -96,7 +96,9 @@ class StashPluginHelper:
fragmentServer = None, fragmentServer = None,
stash_url = None, # Stash URL (endpoint URL) Example: http://localhost:9999 stash_url = None, # Stash URL (endpoint URL) Example: http://localhost:9999
DebugTraceFieldName = "zzdebugTracing", DebugTraceFieldName = "zzdebugTracing",
DryRunFieldName = "zzdryRun"): DryRunFieldName = "zzdryRun",
setStashLoggerAsPluginLogger = False):
self.thredPool = concurrent.futures.ThreadPoolExecutor(max_workers=2)
if logToWrnSet: self.log_to_wrn_set = logToWrnSet if logToWrnSet: self.log_to_wrn_set = logToWrnSet
if logToErrSet: self.log_to_err_set = logToErrSet if logToErrSet: self.log_to_err_set = logToErrSet
if logToNormSet: self.log_to_norm = logToNormSet if logToNormSet: self.log_to_norm = logToNormSet
@@ -143,7 +145,8 @@ class StashPluginHelper:
self.FRAGMENT_SERVER['Scheme'] = endpointUrlArr[0] self.FRAGMENT_SERVER['Scheme'] = endpointUrlArr[0]
self.FRAGMENT_SERVER['Host'] = endpointUrlArr[1][2:] self.FRAGMENT_SERVER['Host'] = endpointUrlArr[1][2:]
self.FRAGMENT_SERVER['Port'] = endpointUrlArr[2] self.FRAGMENT_SERVER['Port'] = endpointUrlArr[2]
self.STASH_INTERFACE = self.ExtendStashInterface(self.FRAGMENT_SERVER) super().__init__(self.FRAGMENT_SERVER)
self.STASH_INTERFACE_INIT = True
else: else:
try: try:
self.STDIN_READ = sys.stdin.read() self.STDIN_READ = sys.stdin.read()
@@ -156,11 +159,17 @@ class StashPluginHelper:
self.PLUGIN_TASK_NAME = self.JSON_INPUT["args"]["mode"] self.PLUGIN_TASK_NAME = self.JSON_INPUT["args"]["mode"]
self.FRAGMENT_SERVER = self.JSON_INPUT["server_connection"] self.FRAGMENT_SERVER = self.JSON_INPUT["server_connection"]
self.STASH_URL = f"{self.FRAGMENT_SERVER['Scheme']}://{self.FRAGMENT_SERVER['Host']}:{self.FRAGMENT_SERVER['Port']}" self.STASH_URL = f"{self.FRAGMENT_SERVER['Scheme']}://{self.FRAGMENT_SERVER['Host']}:{self.FRAGMENT_SERVER['Port']}"
self.STASH_INTERFACE = self.ExtendStashInterface(self.FRAGMENT_SERVER) super().__init__(self.FRAGMENT_SERVER)
self.STASH_INTERFACE_INIT = True
if self.STASH_INTERFACE: if self.STASH_INTERFACE_INIT:
self.PLUGIN_CONFIGURATION = self.STASH_INTERFACE.get_configuration()["plugins"] self.PLUGIN_CONFIGURATION = self.get_configuration()["plugins"]
self.STASH_CONFIGURATION = self.STASH_INTERFACE.get_configuration()["general"] self.STASH_CONFIGURATION = self.get_configuration()["general"]
self.STASHPATHSCONFIG = self.STASH_CONFIGURATION['stashes']
if 'pluginsPath' in self.STASH_CONFIGURATION:
self.PLUGINS_PATH = self.STASH_CONFIGURATION['pluginsPath']
for item in self.STASHPATHSCONFIG:
self.STASH_PATHS.append(item["path"])
if settings: if settings:
self.pluginSettings = settings self.pluginSettings = settings
if self.PLUGIN_ID in self.PLUGIN_CONFIGURATION: if self.PLUGIN_ID in self.PLUGIN_CONFIGURATION:
@@ -172,7 +181,12 @@ class StashPluginHelper:
if self.DEBUG_TRACING: self.LOG_LEVEL = logging.DEBUG if self.DEBUG_TRACING: self.LOG_LEVEL = logging.DEBUG
logging.basicConfig(level=self.LOG_LEVEL, format=logFormat, datefmt=dateFmt, handlers=[RFH]) logging.basicConfig(level=self.LOG_LEVEL, format=logFormat, datefmt=dateFmt, handlers=[RFH])
self.logger = logging.getLogger(pathlib.Path(self.MAIN_SCRIPT_NAME).stem) self.pluginLog = logging.getLogger(pathlib.Path(self.MAIN_SCRIPT_NAME).stem)
if setStashLoggerAsPluginLogger:
self.log = self.pluginLog
def __del__(self):
self.thredPool.shutdown(wait=False)
def Log(self, logMsg, printTo = 0, logLevel = logging.INFO, lineNo = -1, levelStr = "", logAlways = False): def Log(self, logMsg, printTo = 0, logLevel = logging.INFO, lineNo = -1, levelStr = "", logAlways = False):
if printTo == 0: if printTo == 0:
@@ -192,32 +206,33 @@ class StashPluginHelper:
# print(f"{LN_Str}, {logAlways}, {self.LOG_LEVEL}, {logging.DEBUG}, {levelStr}, {logMsg}") # print(f"{LN_Str}, {logAlways}, {self.LOG_LEVEL}, {logging.DEBUG}, {levelStr}, {logMsg}")
if logLevel == logging.DEBUG and (logAlways == False or self.LOG_LEVEL == logging.DEBUG): if logLevel == logging.DEBUG and (logAlways == False or self.LOG_LEVEL == logging.DEBUG):
if levelStr == "": levelStr = self.LEV_DBG if levelStr == "": levelStr = self.LEV_DBG
if printTo & self.LOG_TO_FILE: self.logger.debug(f"{LN_Str} {levelStr}{logMsg}") if printTo & self.LOG_TO_FILE: self.pluginLog.debug(f"{LN_Str} {levelStr}{logMsg}")
if printTo & self.LOG_TO_STASH: stashLog.debug(f"{LN_Str} {levelStr}{logMsg}") if printTo & self.LOG_TO_STASH: self.log.debug(f"{LN_Str} {levelStr}{logMsg}")
elif logLevel == logging.INFO or logLevel == logging.DEBUG: elif logLevel == logging.INFO or logLevel == logging.DEBUG:
if levelStr == "": levelStr = self.LEV_INF if logLevel == logging.INFO else self.LEV_DBG if levelStr == "": levelStr = self.LEV_INF if logLevel == logging.INFO else self.LEV_DBG
if printTo & self.LOG_TO_FILE: self.logger.info(f"{LN_Str} {levelStr}{logMsg}") if printTo & self.LOG_TO_FILE: self.pluginLog.info(f"{LN_Str} {levelStr}{logMsg}")
if printTo & self.LOG_TO_STASH: stashLog.info(f"{LN_Str} {levelStr}{logMsg}") if printTo & self.LOG_TO_STASH: self.log.info(f"{LN_Str} {levelStr}{logMsg}")
elif logLevel == logging.WARN: elif logLevel == logging.WARN:
if levelStr == "": levelStr = self.LEV_WRN if levelStr == "": levelStr = self.LEV_WRN
if printTo & self.LOG_TO_FILE: self.logger.warning(f"{LN_Str} {levelStr}{logMsg}") if printTo & self.LOG_TO_FILE: self.pluginLog.warning(f"{LN_Str} {levelStr}{logMsg}")
if printTo & self.LOG_TO_STASH: stashLog.warning(f"{LN_Str} {levelStr}{logMsg}") if printTo & self.LOG_TO_STASH: self.log.warning(f"{LN_Str} {levelStr}{logMsg}")
elif logLevel == logging.ERROR: elif logLevel == logging.ERROR:
if levelStr == "": levelStr = self.LEV_ERR if levelStr == "": levelStr = self.LEV_ERR
if printTo & self.LOG_TO_FILE: self.logger.error(f"{LN_Str} {levelStr}{logMsg}") if printTo & self.LOG_TO_FILE: self.pluginLog.error(f"{LN_Str} {levelStr}{logMsg}")
if printTo & self.LOG_TO_STASH: stashLog.error(f"{LN_Str} {levelStr}{logMsg}") if printTo & self.LOG_TO_STASH: self.log.error(f"{LN_Str} {levelStr}{logMsg}")
elif logLevel == logging.CRITICAL: elif logLevel == logging.CRITICAL:
if levelStr == "": levelStr = self.LEV_CRITICAL if levelStr == "": levelStr = self.LEV_CRITICAL
if printTo & self.LOG_TO_FILE: self.logger.critical(f"{LN_Str} {levelStr}{logMsg}") if printTo & self.LOG_TO_FILE: self.pluginLog.critical(f"{LN_Str} {levelStr}{logMsg}")
if printTo & self.LOG_TO_STASH: stashLog.error(f"{LN_Str} {levelStr}{logMsg}") if printTo & self.LOG_TO_STASH: self.log.error(f"{LN_Str} {levelStr}{logMsg}")
if (printTo & self.LOG_TO_CONSOLE) and (logLevel != logging.DEBUG or self.DEBUG_TRACING or logAlways): if (printTo & self.LOG_TO_CONSOLE) and (logLevel != logging.DEBUG or self.DEBUG_TRACING or logAlways):
print(f"{LN_Str} {levelStr}{logMsg}") print(f"{LN_Str} {levelStr}{logMsg}")
if (printTo & self.LOG_TO_STDERR) and (logLevel != logging.DEBUG or self.DEBUG_TRACING or logAlways): if (printTo & self.LOG_TO_STDERR) and (logLevel != logging.DEBUG or self.DEBUG_TRACING or logAlways):
print(f"StdErr: {LN_Str} {levelStr}{logMsg}", file=sys.stderr) print(f"StdErr: {LN_Str} {levelStr}{logMsg}", file=sys.stderr)
def Trace(self, logMsg = "", printTo = 0, logAlways = False): def Trace(self, logMsg = "", printTo = 0, logAlways = False, lineNo = -1):
if printTo == 0: printTo = self.LOG_TO_FILE if printTo == 0: printTo = self.LOG_TO_FILE
lineNo = inspect.currentframe().f_back.f_lineno if lineNo == -1:
lineNo = inspect.currentframe().f_back.f_lineno
logLev = logging.INFO if logAlways else logging.DEBUG logLev = logging.INFO if logAlways else logging.DEBUG
if self.DEBUG_TRACING or logAlways: if self.DEBUG_TRACING or logAlways:
if logMsg == "": if logMsg == "":
@@ -226,17 +241,25 @@ class StashPluginHelper:
# Log once per session. Only logs the first time called from a particular line number in the code. # Log once per session. Only logs the first time called from a particular line number in the code.
def TraceOnce(self, logMsg = "", printTo = 0, logAlways = False): def TraceOnce(self, logMsg = "", printTo = 0, logAlways = False):
if printTo == 0: printTo = self.LOG_TO_FILE
lineNo = inspect.currentframe().f_back.f_lineno lineNo = inspect.currentframe().f_back.f_lineno
logLev = logging.INFO if logAlways else logging.DEBUG
if self.DEBUG_TRACING or logAlways: if self.DEBUG_TRACING or logAlways:
FuncAndLineNo = f"{inspect.currentframe().f_back.f_code.co_name}:{lineNo}" FuncAndLineNo = f"{inspect.currentframe().f_back.f_code.co_name}:{lineNo}"
if FuncAndLineNo in traceOncePreviousHits: if FuncAndLineNo in self.logLinePreviousHits:
return return
traceOncePreviousHits.append(FuncAndLineNo) self.logLinePreviousHits.append(FuncAndLineNo)
if logMsg == "": self.Trace(logMsg, printTo, logAlways, lineNo)
logMsg = f"Line number {lineNo}..."
self.Log(logMsg, printTo, logLev, lineNo, self.LEV_TRACE, logAlways) # Log INFO on first call, then do Trace on remaining calls.
def LogOnce(self, logMsg = "", printTo = 0, logAlways = False, traceOnRemainingCalls = True):
if printTo == 0: printTo = self.LOG_TO_FILE
lineNo = inspect.currentframe().f_back.f_lineno
FuncAndLineNo = f"{inspect.currentframe().f_back.f_code.co_name}:{lineNo}"
if FuncAndLineNo in self.logLinePreviousHits:
if traceOnRemainingCalls:
self.Trace(logMsg, printTo, logAlways, lineNo)
else:
self.logLinePreviousHits.append(FuncAndLineNo)
self.Log(logMsg, printTo, logging.INFO, lineNo)
def Warn(self, logMsg, printTo = 0): def Warn(self, logMsg, printTo = 0):
if printTo == 0: printTo = self.log_to_wrn_set if printTo == 0: printTo = self.log_to_wrn_set
@@ -255,26 +278,92 @@ class StashPluginHelper:
self.Log(f"StashPluginHelper Status: (CALLED_AS_STASH_PLUGIN={self.CALLED_AS_STASH_PLUGIN}), (RUNNING_IN_COMMAND_LINE_MODE={self.RUNNING_IN_COMMAND_LINE_MODE}), (DEBUG_TRACING={self.DEBUG_TRACING}), (DRY_RUN={self.DRY_RUN}), (PLUGIN_ID={self.PLUGIN_ID}), (PLUGIN_TASK_NAME={self.PLUGIN_TASK_NAME}), (STASH_URL={self.STASH_URL}), (MAIN_SCRIPT_NAME={self.MAIN_SCRIPT_NAME})", self.Log(f"StashPluginHelper Status: (CALLED_AS_STASH_PLUGIN={self.CALLED_AS_STASH_PLUGIN}), (RUNNING_IN_COMMAND_LINE_MODE={self.RUNNING_IN_COMMAND_LINE_MODE}), (DEBUG_TRACING={self.DEBUG_TRACING}), (DRY_RUN={self.DRY_RUN}), (PLUGIN_ID={self.PLUGIN_ID}), (PLUGIN_TASK_NAME={self.PLUGIN_TASK_NAME}), (STASH_URL={self.STASH_URL}), (MAIN_SCRIPT_NAME={self.MAIN_SCRIPT_NAME})",
printTo, logLevel, lineNo) printTo, logLevel, lineNo)
def ExecuteProcess(self, args, ExecDetach=False):
import platform, subprocess
is_windows = any(platform.win32_ver())
pid = None
self.Trace(f"is_windows={is_windows} args={args}")
if is_windows:
if ExecDetach:
self.Trace("Executing process using Windows DETACHED_PROCESS")
DETACHED_PROCESS = 0x00000008
pid = subprocess.Popen(args,creationflags=DETACHED_PROCESS, shell=True).pid
else:
pid = subprocess.Popen(args, shell=True).pid
else:
self.Trace("Executing process using normal Popen")
pid = subprocess.Popen(args).pid
self.Trace(f"pid={pid}")
return pid
def ExecutePythonScript(self, args, ExecDetach=True):
PythonExe = f"{sys.executable}"
argsWithPython = [f"{PythonExe}"] + args
return self.ExecuteProcess(argsWithPython,ExecDetach=ExecDetach)
def Submit(*args, **kwargs):
thredPool.submit(*args, **kwargs)
# Extends class StashInterface with functions which are not yet in the class # Extends class StashInterface with functions which are not yet in the class
class ExtendStashInterface(StashInterface): def metadata_autotag(self, paths:list=[], performers:list=[], studios:list=[], tags:list=[]):
def metadata_autotag(self, paths:list=[], dry_run=False): query = """
if not paths: mutation MetadataAutoTag($input:AutoTagMetadataInput!) {
return metadataAutoTag(input: $input)
}
"""
metadata_autotag_input = {
"paths":paths,
"performers": performers,
"studios":studios,
"tags":tags,
}
result = self.call_GQL(query, {"input": metadata_autotag_input})
return result
def backup_database(self):
return self.call_GQL("mutation { backupDatabase(input: {download: false})}")
query = """ def optimise_database(self):
mutation MetadataAutoTag($input:AutoTagMetadataInput!) { return self.call_GQL("mutation OptimiseDatabase { optimiseDatabase }")
metadataAutoTag(input: $input)
} def metadata_clean_generated(self, blobFiles=True, dryRun=False, imageThumbnails=True, markers=True, screenshots=True, sprites=True, transcodes=True):
""" query = """
mutation MetadataCleanGenerated($input: CleanGeneratedInput!) {
metadata_autotag_input = { metadataCleanGenerated(input: $input)
"paths": paths }
} """
result = self.call_GQL(query, {"input": metadata_autotag_input}) clean_metadata_input = {
return result "blobFiles": blobFiles,
"dryRun": dryRun,
def backup_database(self): "imageThumbnails": imageThumbnails,
return self.call_GQL("mutation { backupDatabase(input: {download: false})}") "markers": markers,
"screenshots": screenshots,
def optimise_database(self): "sprites": sprites,
return self.call_GQL("mutation OptimiseDatabase { optimiseDatabase }") "transcodes": transcodes,
}
result = self.call_GQL(query, {"input": clean_metadata_input})
return result
def rename_generated_files(self):
return self.call_GQL("mutation MigrateHashNaming {migrateHashNaming}")
# def find_duplicate_scenes(self, distance: PhashDistance=PhashDistance.EXACT, fragment=None):
# query = """
# query FindDuplicateScenes($distance: Int) {
# findDuplicateScenes(distance: $distance) {
# ...SceneSlim
# }
# }
# """
# if fragment:
# query = re.sub(r'\.\.\.SceneSlim', fragment, query)
# else:
# query = """
# query FindDuplicateScenes($distance: Int) {
# findDuplicateScenes(distance: $distance)
# }
# """
# variables = {
# "distance": distance
# }
# result = self.call_GQL(query, variables)
# return result['findDuplicateScenes']