Compare commits

..

12 Commits

Author SHA1 Message Date
Chris King
24aa00de8d Adjust duration difference check to allow small non-significant differences if one file is better in other ways
Duration difference now respects the setting for what is significant duration difference
2025-02-16 06:18:47 -08:00
Chris King
b646da289c Change some default settings 2025-02-16 06:16:54 -08:00
Chris King
c383be80f0 Fixed formatting of report CSS
Added z-index: 1 to large preview image so it appears over other elements
2025-02-16 02:41:12 -08:00
Chris King
6ef3c77735 Set remoteReportDirURL to Stash custom_served_folders URL 2025-02-16 02:13:19 -08:00
Chris King
fae44f7678 Move html, css, and js files for report viewing into web folder which allows for easy self hosting in Stash via custom_served_folders 2025-02-16 02:12:27 -08:00
Chris King
5d92f387fa Write error message before waiting for retry in mergeMetadata
Only retry merging if there is a connection error as other errors related to data are extremely unlikely to resolve themselves after retries
2025-02-16 01:51:27 -08:00
Chris King
b802233efa Fix inconsistent whitespace/indent usage causing Pylance to complain 2025-02-16 01:49:35 -08:00
Chris King
899053cdbc Silenced output of module has been installed messages as it was breaking the next action by writing to the console 2025-02-16 01:48:42 -08:00
Chris King
d2eb18e217 Add "--root-user-action ignore" flag to pip command on Docker to cleanup generated error log with warning in Stash 2025-02-16 01:47:10 -08:00
Chris King
85104e6b15 Add id subfield to performers in graphql fragment used to merge metadata as mergeItems function requires ID 2025-02-16 01:45:40 -08:00
Chris King
3b029943bd Fix type error when there is only a single target in json input args (target was an int instead of a string) 2025-02-16 01:44:27 -08:00
Chris King
1eae580325 Set python version to 3.12.7 as used in Stash docker container 2025-02-16 01:40:56 -08:00
11 changed files with 78 additions and 48 deletions

View File

@@ -0,0 +1 @@
3.12.7

View File

@@ -196,17 +196,17 @@ matchPhaseDistanceText = "Exact Match"
logTraceForAdvanceMenuOpt = False
if (stash.PLUGIN_TASK_NAME == "tag_duplicates_task" or stash.PLUGIN_TASK_NAME == "create_duplicate_report_task") and 'Target' in stash.JSON_INPUT['args']:
stash.enableProgressBar(False)
if stash.JSON_INPUT['args']['Target'].startswith("0"):
if str(stash.JSON_INPUT['args']['Target']).startswith("0"):
matchDupDistance = 0
elif stash.JSON_INPUT['args']['Target'].startswith("1"):
elif str(stash.JSON_INPUT['args']['Target']).startswith("1"):
matchDupDistance = 1
elif stash.JSON_INPUT['args']['Target'].startswith("2"):
elif str(stash.JSON_INPUT['args']['Target']).startswith("2"):
matchDupDistance = 2
elif stash.JSON_INPUT['args']['Target'].startswith("3"):
elif str(stash.JSON_INPUT['args']['Target']).startswith("3"):
matchDupDistance = 3
stash.Trace(f"Target = {stash.JSON_INPUT['args']['Target']}")
targets = stash.JSON_INPUT['args']['Target'].split(":")
targets = str(stash.JSON_INPUT['args']['Target']).split(":")
if len(targets) > 1:
significantTimeDiff = float(targets[1])
excludeFromReportIfSignificantTimeDiff = True
@@ -503,11 +503,18 @@ def isBetterVideo(scene1, scene2, swapCandidateCk = False): # is scene2 better t
return True
return False
def isSignificantTimeDiff(duration1, duration2):
dur1 = int(duration1)
dur2 = int(duration2)
return abs(dur1 - dur2) / max(abs(dur1), abs(dur2)) > (1 - significantTimeDiff)
def significantMoreTimeCompareToBetterVideo(scene1, scene2): # is scene2 better than scene1
if isinstance(scene1, int):
scene1 = stash.find_scene(scene1)
scene2 = stash.find_scene(scene2)
if int(scene1['files'][0]['duration']) >= int(scene2['files'][0]['duration']):
dur1 = int(scene1['files'][0]['duration'])
dur2 = int(scene2['files'][0]['duration'])
if dur1 >= dur2 or not isSignificantTimeDiff(dur1, dur2):
return False
if int(scene1['files'][0]['width']) * int(scene1['files'][0]['height']) > int(scene2['files'][0]['width']) * int(scene2['files'][0]['height']):
if significantTimeDiffCheck(scene1, scene2):
@@ -520,7 +527,7 @@ def significantMoreTimeCompareToBetterVideo(scene1, scene2): # is scene2 better
return True
def allThingsEqual(scene1, scene2): # If all important things are equal, return true
if int(scene1['files'][0]['duration']) != int(scene2['files'][0]['duration']):
if isSignificantTimeDiff(scene1['files'][0]['duration'], scene2['files'][0]['duration']):
return False
if scene1['files'][0]['width'] != scene2['files'][0]['width']:
return False
@@ -973,7 +980,7 @@ def writeRowToHtmlReport(fileHtmlReport, DupFile, DupFileToKeep, itemIndex, tagD
fileHtmlReport.write(f"</tr>{ToDeleteSceneIDSrchStr}{DupFile['id']}{ToKeepSceneIDSrchStr}{DupFileToKeep['id']}{itemIndexSrchStr}{itemIndex}:: -->\n")
fragmentForSceneDetails = 'id tags {id name ignore_auto_tag} groups {group {name} } performers {name} galleries {id} files {path width height duration size video_codec bit_rate frame_rate} details '
fragmentForSceneDetails = 'id tags {id name ignore_auto_tag} groups {group {name} } performers {id name} galleries {id} files {path width height duration size video_codec bit_rate frame_rate} details '
htmlFileData = " paths {screenshot sprite webp " + htmlPreviewOrStream + "} "
mergeFieldData = " code director title rating100 date studio {id name} urls "
fragmentForSceneDetails += mergeFieldData + htmlFileData

View File

@@ -7,13 +7,13 @@ config = {
# Alternative path to move duplicate files.
"dup_path": "", #Example: "C:\\TempDeleteFolder"
# The threshold as to what percentage is consider a significant shorter time.
"significantTimeDiff" : .90, # 90% threshold
"significantTimeDiff" : .98, # 90% threshold
# If enabled, moves destination file to recycle bin before swapping Hi-Res file.
"toRecycleBeforeSwap" : True,
# Character used to seperate items on the whitelist, blacklist, and graylist
"listSeparator" : ",",
# Enable to permanently delete files, instead of moving files to trash can.
"permanentlyDelete" : False,
"permanentlyDelete" : True,
# After running a 'Delete Duplicates' task, run Clean, Clean-Generated, and Optimize-Database.
"cleanAfterDel" : True,
# Generate PHASH after tag or delete task.
@@ -61,7 +61,7 @@ config = {
# If enabled, favor videos with higher bit rate. Used with either favorBitRateChange option or UI [Swap Bit Rate Change] option.
"favorHighBitRate" : True,
# If enabled, favor videos with a different frame rate value. If favorHigherFrameRate is true, favor higher rate. If favorHigherFrameRate is false, favor lower rate
"favorFrameRateChange" : True,
"favorFrameRateChange" : False,
# If enabled, favor videos with higher frame rate. Used with either favorFrameRateChange option or UI [Swap Better Frame Rate] option.
"favorHigherFrameRate" : True,
# If enabled, favor videos with better codec according to codecRanking

View File

@@ -1,28 +0,0 @@
h2 {text-align: center;}
table, th, td {border:1px solid black;}
.inline {
display: inline;
}
.scene-details{text-align: center;font-size: small;}
.reason-details{text-align: left;font-size: small;}
.link-items{text-align: center;font-size: small;}
ul {
padding: 0;
}
li {
list-style-type: none;
padding: 1px;
position: relative;
}
.large {
position: absolute;
left: -9999px;
}
li:hover .large {
left: 20px;
top: -150px;
}
.large-image {
border-radius: 4px;
box-shadow: 1px 1px 3px 3px rgba(127, 127, 127, 0.15);;
}

View File

@@ -50,7 +50,7 @@ report_config = {
# If enabled, create an HTML report when tagging duplicate files
"createHtmlReport" : True,
# To use a private or an alternate site to access report and advance menu
"remoteReportDirURL" : "https://stash.axter.com/1.1/",
"remoteReportDirURL" : "https://stash.tremendousturtle.tools/custom/",
# To use a private or an alternate site to access jquery, easyui, and jquery.prompt
"js_DirURL" : "https://www.axter.com/js/",
}

View File

@@ -23,7 +23,7 @@ def modulesInstalled(moduleNames, install=True, silent=False):
else:
if install and (results:=installModule(moduleName)) > 0:
if results == 1:
print(f"Module {moduleName!r} has been installed")
if not silent: print(f"Module {moduleName!r} has been installed")
else:
if not silent: print(f"Module {moduleName!r} is already installed")
continue
@@ -37,7 +37,7 @@ def modulesInstalled(moduleNames, install=True, silent=False):
except ImportError as e:
if install and (results:=installModule(moduleName)) > 0:
if results == 1:
print(f"Module {moduleName!r} has been installed")
if not silent: print(f"Module {moduleName!r} has been installed")
else:
if not silent: print(f"Module {moduleName!r} is already installed")
continue
@@ -73,6 +73,7 @@ def installModule(moduleName):
pipArg = " --disable-pip-version-check"
if isDocker():
pipArg += " --break-system-packages"
pipArg += " --root-user-action ignore"
results = os.popen(f"{sys.executable} -m pip install {moduleName}{pipArg}").read() # May need to be f"{sys.executable} -m pip install {moduleName}"
results = results.strip("\n")
if results.find("Requirement already satisfied:") > -1:

View File

@@ -511,20 +511,25 @@ class StashPluginHelper(StashInterface):
if self._mergeMetadata == None:
self.initMergeMetadata(excludeMergeTags)
errMsg = None
stopRetry = False
for i in range(0, retryCount):
try:
if errMsg != None:
self.Warn(errMsg)
if type(SrcData) is int:
SrcData = self.find_scene(SrcData)
DestData = self.find_scene(DestData)
return self._mergeMetadata.merge(SrcData, DestData)
except (requests.exceptions.ConnectionError, ConnectionResetError):
except (requests.exceptions.ConnectionError, ConnectionResetError) as e:
tb = traceback.format_exc()
errMsg = f"Exception calling [mergeMetadata]. Will retry; count({i}); Error: {e}\nTraceBack={tb}"
except Exception as e:
tb = traceback.format_exc()
errMsg = f"Exception calling [mergeMetadata]. Will retry; count({i}); Error: {e}\nTraceBack={tb}"
errMsg = f"Data Exception calling [mergeMetadata]. Will not retry; count({i}); Error: {e}\nTraceBack={tb}"
stopRetry = True
if errMsg != None:
self.Warn(errMsg)
if stopRetry:
break
time.sleep(sleepSecondsBetweenRetry)
def getUpdateProgressBarIter(self, qtyResults):
@@ -962,9 +967,9 @@ class StashPluginHelper(StashInterface):
}
"""
if fragment:
query = re.sub(r'\.\.\.SceneSlim', fragment, query)
query = re.sub(r'\.\.\.SceneSlim', fragment, query)
else:
query += "fragment SceneSlim on Scene { id }"
query += "fragment SceneSlim on Scene { id }"
variables = { "distance": distance, "duration_diff": duration_diff }
result = self.call_GQL(query, variables)

View File

@@ -0,0 +1,44 @@
h2 {
text-align: center;
}
table,
th,
td {
border: 1px solid black;
}
.inline {
display: inline;
}
.scene-details {
text-align: center;
font-size: small;
}
.reason-details {
text-align: left;
font-size: small;
}
.link-items {
text-align: center;
font-size: small;
}
ul {
padding: 0;
}
li {
list-style-type: none;
padding: 1px;
position: relative;
}
.large {
position: absolute;
left: -9999px;
}
li:hover .large {
left: 20px;
top: -150px;
z-index: 1;
}
.large-image {
border-radius: 4px;
box-shadow: 1px 1px 3px 3px rgba(127, 127, 127, 0.15);
}