forked from Github/Axter-Stash
Compare commits
12 Commits
10b329afad
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24aa00de8d | ||
|
|
b646da289c | ||
|
|
c383be80f0 | ||
|
|
6ef3c77735 | ||
|
|
fae44f7678 | ||
|
|
5d92f387fa | ||
|
|
b802233efa | ||
|
|
899053cdbc | ||
|
|
d2eb18e217 | ||
|
|
85104e6b15 | ||
|
|
3b029943bd | ||
|
|
1eae580325 |
1
plugins/DupFileManager/.python-version
Normal file
1
plugins/DupFileManager/.python-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3.12.7
|
||||||
@@ -196,17 +196,17 @@ matchPhaseDistanceText = "Exact Match"
|
|||||||
logTraceForAdvanceMenuOpt = False
|
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']:
|
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)
|
stash.enableProgressBar(False)
|
||||||
if stash.JSON_INPUT['args']['Target'].startswith("0"):
|
if str(stash.JSON_INPUT['args']['Target']).startswith("0"):
|
||||||
matchDupDistance = 0
|
matchDupDistance = 0
|
||||||
elif stash.JSON_INPUT['args']['Target'].startswith("1"):
|
elif str(stash.JSON_INPUT['args']['Target']).startswith("1"):
|
||||||
matchDupDistance = 1
|
matchDupDistance = 1
|
||||||
elif stash.JSON_INPUT['args']['Target'].startswith("2"):
|
elif str(stash.JSON_INPUT['args']['Target']).startswith("2"):
|
||||||
matchDupDistance = 2
|
matchDupDistance = 2
|
||||||
elif stash.JSON_INPUT['args']['Target'].startswith("3"):
|
elif str(stash.JSON_INPUT['args']['Target']).startswith("3"):
|
||||||
matchDupDistance = 3
|
matchDupDistance = 3
|
||||||
|
|
||||||
stash.Trace(f"Target = {stash.JSON_INPUT['args']['Target']}")
|
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:
|
if len(targets) > 1:
|
||||||
significantTimeDiff = float(targets[1])
|
significantTimeDiff = float(targets[1])
|
||||||
excludeFromReportIfSignificantTimeDiff = True
|
excludeFromReportIfSignificantTimeDiff = True
|
||||||
@@ -503,11 +503,18 @@ def isBetterVideo(scene1, scene2, swapCandidateCk = False): # is scene2 better t
|
|||||||
return True
|
return True
|
||||||
return False
|
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
|
def significantMoreTimeCompareToBetterVideo(scene1, scene2): # is scene2 better than scene1
|
||||||
if isinstance(scene1, int):
|
if isinstance(scene1, int):
|
||||||
scene1 = stash.find_scene(scene1)
|
scene1 = stash.find_scene(scene1)
|
||||||
scene2 = stash.find_scene(scene2)
|
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
|
return False
|
||||||
if int(scene1['files'][0]['width']) * int(scene1['files'][0]['height']) > int(scene2['files'][0]['width']) * int(scene2['files'][0]['height']):
|
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):
|
if significantTimeDiffCheck(scene1, scene2):
|
||||||
@@ -520,7 +527,7 @@ def significantMoreTimeCompareToBetterVideo(scene1, scene2): # is scene2 better
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def allThingsEqual(scene1, scene2): # If all important things are equal, 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
|
return False
|
||||||
if scene1['files'][0]['width'] != scene2['files'][0]['width']:
|
if scene1['files'][0]['width'] != scene2['files'][0]['width']:
|
||||||
return False
|
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")
|
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 + "} "
|
htmlFileData = " paths {screenshot sprite webp " + htmlPreviewOrStream + "} "
|
||||||
mergeFieldData = " code director title rating100 date studio {id name} urls "
|
mergeFieldData = " code director title rating100 date studio {id name} urls "
|
||||||
fragmentForSceneDetails += mergeFieldData + htmlFileData
|
fragmentForSceneDetails += mergeFieldData + htmlFileData
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ config = {
|
|||||||
# Alternative path to move duplicate files.
|
# Alternative path to move duplicate files.
|
||||||
"dup_path": "", #Example: "C:\\TempDeleteFolder"
|
"dup_path": "", #Example: "C:\\TempDeleteFolder"
|
||||||
# The threshold as to what percentage is consider a significant shorter time.
|
# 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.
|
# If enabled, moves destination file to recycle bin before swapping Hi-Res file.
|
||||||
"toRecycleBeforeSwap" : True,
|
"toRecycleBeforeSwap" : True,
|
||||||
# Character used to seperate items on the whitelist, blacklist, and graylist
|
# Character used to seperate items on the whitelist, blacklist, and graylist
|
||||||
"listSeparator" : ",",
|
"listSeparator" : ",",
|
||||||
# Enable to permanently delete files, instead of moving files to trash can.
|
# 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.
|
# After running a 'Delete Duplicates' task, run Clean, Clean-Generated, and Optimize-Database.
|
||||||
"cleanAfterDel" : True,
|
"cleanAfterDel" : True,
|
||||||
# Generate PHASH after tag or delete task.
|
# 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.
|
# If enabled, favor videos with higher bit rate. Used with either favorBitRateChange option or UI [Swap Bit Rate Change] option.
|
||||||
"favorHighBitRate" : True,
|
"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
|
# 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.
|
# If enabled, favor videos with higher frame rate. Used with either favorFrameRateChange option or UI [Swap Better Frame Rate] option.
|
||||||
"favorHigherFrameRate" : True,
|
"favorHigherFrameRate" : True,
|
||||||
# If enabled, favor videos with better codec according to codecRanking
|
# If enabled, favor videos with better codec according to codecRanking
|
||||||
|
|||||||
@@ -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);;
|
|
||||||
}
|
|
||||||
@@ -50,7 +50,7 @@ report_config = {
|
|||||||
# If enabled, create an HTML report when tagging duplicate files
|
# If enabled, create an HTML report when tagging duplicate files
|
||||||
"createHtmlReport" : True,
|
"createHtmlReport" : True,
|
||||||
# To use a private or an alternate site to access report and advance menu
|
# 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
|
# To use a private or an alternate site to access jquery, easyui, and jquery.prompt
|
||||||
"js_DirURL" : "https://www.axter.com/js/",
|
"js_DirURL" : "https://www.axter.com/js/",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ def modulesInstalled(moduleNames, install=True, silent=False):
|
|||||||
else:
|
else:
|
||||||
if install and (results:=installModule(moduleName)) > 0:
|
if install and (results:=installModule(moduleName)) > 0:
|
||||||
if results == 1:
|
if results == 1:
|
||||||
print(f"Module {moduleName!r} has been installed")
|
if not silent: print(f"Module {moduleName!r} has been installed")
|
||||||
else:
|
else:
|
||||||
if not silent: print(f"Module {moduleName!r} is already installed")
|
if not silent: print(f"Module {moduleName!r} is already installed")
|
||||||
continue
|
continue
|
||||||
@@ -37,7 +37,7 @@ def modulesInstalled(moduleNames, install=True, silent=False):
|
|||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
if install and (results:=installModule(moduleName)) > 0:
|
if install and (results:=installModule(moduleName)) > 0:
|
||||||
if results == 1:
|
if results == 1:
|
||||||
print(f"Module {moduleName!r} has been installed")
|
if not silent: print(f"Module {moduleName!r} has been installed")
|
||||||
else:
|
else:
|
||||||
if not silent: print(f"Module {moduleName!r} is already installed")
|
if not silent: print(f"Module {moduleName!r} is already installed")
|
||||||
continue
|
continue
|
||||||
@@ -73,6 +73,7 @@ def installModule(moduleName):
|
|||||||
pipArg = " --disable-pip-version-check"
|
pipArg = " --disable-pip-version-check"
|
||||||
if isDocker():
|
if isDocker():
|
||||||
pipArg += " --break-system-packages"
|
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 = 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")
|
results = results.strip("\n")
|
||||||
if results.find("Requirement already satisfied:") > -1:
|
if results.find("Requirement already satisfied:") > -1:
|
||||||
|
|||||||
@@ -511,20 +511,25 @@ class StashPluginHelper(StashInterface):
|
|||||||
if self._mergeMetadata == None:
|
if self._mergeMetadata == None:
|
||||||
self.initMergeMetadata(excludeMergeTags)
|
self.initMergeMetadata(excludeMergeTags)
|
||||||
errMsg = None
|
errMsg = None
|
||||||
|
stopRetry = False
|
||||||
for i in range(0, retryCount):
|
for i in range(0, retryCount):
|
||||||
try:
|
try:
|
||||||
if errMsg != None:
|
|
||||||
self.Warn(errMsg)
|
|
||||||
if type(SrcData) is int:
|
if type(SrcData) is int:
|
||||||
SrcData = self.find_scene(SrcData)
|
SrcData = self.find_scene(SrcData)
|
||||||
DestData = self.find_scene(DestData)
|
DestData = self.find_scene(DestData)
|
||||||
return self._mergeMetadata.merge(SrcData, DestData)
|
return self._mergeMetadata.merge(SrcData, DestData)
|
||||||
except (requests.exceptions.ConnectionError, ConnectionResetError):
|
except (requests.exceptions.ConnectionError, ConnectionResetError) as e:
|
||||||
tb = traceback.format_exc()
|
tb = traceback.format_exc()
|
||||||
errMsg = f"Exception calling [mergeMetadata]. Will retry; count({i}); Error: {e}\nTraceBack={tb}"
|
errMsg = f"Exception calling [mergeMetadata]. Will retry; count({i}); Error: {e}\nTraceBack={tb}"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
tb = traceback.format_exc()
|
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)
|
time.sleep(sleepSecondsBetweenRetry)
|
||||||
|
|
||||||
def getUpdateProgressBarIter(self, qtyResults):
|
def getUpdateProgressBarIter(self, qtyResults):
|
||||||
@@ -962,9 +967,9 @@ class StashPluginHelper(StashInterface):
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
if fragment:
|
if fragment:
|
||||||
query = re.sub(r'\.\.\.SceneSlim', fragment, query)
|
query = re.sub(r'\.\.\.SceneSlim', fragment, query)
|
||||||
else:
|
else:
|
||||||
query += "fragment SceneSlim on Scene { id }"
|
query += "fragment SceneSlim on Scene { id }"
|
||||||
|
|
||||||
variables = { "distance": distance, "duration_diff": duration_diff }
|
variables = { "distance": distance, "duration_diff": duration_diff }
|
||||||
result = self.call_GQL(query, variables)
|
result = self.call_GQL(query, variables)
|
||||||
|
|||||||
44
plugins/DupFileManager/web/DupFileManager_report.css
Normal file
44
plugins/DupFileManager/web/DupFileManager_report.css
Normal 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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user