Added advance menu option

This commit is contained in:
David Maisonave
2024-11-01 15:07:47 -04:00
parent be8e632699
commit 504814b43c
9 changed files with 2291 additions and 171 deletions

View File

@@ -33,4 +33,35 @@
margin: 1em; margin: 1em;
} }
/* Dashed border */
hr.dashed {
border-top: 3px dashed #bbb;
}
/* Dotted border */
hr.dotted {
border-top: 3px dotted #bbb;
}
/* Solid border */
hr.solid {
border-top: 3px solid #bbb;
}
/* Rounded border */
hr.rounded {
border-top: 8px solid #bbb;
border-radius: 5px;
}
h3.under_construction {
color:red;
background-color:yellow;
}
h3.submenu {
color:Tomato;
background-color:rgba(100, 100, 100);
}
/*# sourceMappingURL=DupFileManager.css.map */ /*# sourceMappingURL=DupFileManager.css.map */

File diff suppressed because one or more lines are too long

View File

@@ -21,20 +21,33 @@ parser.add_argument('--remove_dup', '-r', dest='remove', action='store_true', he
parse_args = parser.parse_args() parse_args = parser.parse_args()
settings = { settings = {
"matchDupDistance": 0,
"mergeDupFilename": False, "mergeDupFilename": False,
"whitelistDelDupInSameFolder": False, "whitelistDelDupInSameFolder": False,
"zvWhitelist": "",
"zwGraylist": "",
"zxBlacklist": "",
"zyMaxDupToProcess": 0,
"zySwapHighRes": False,
"zySwapLongLength": False,
"zySwapBetterBitRate": False,
"zySwapCodec": False,
"zySwapBetterFrameRate": False,
"zzDebug": False,
"zzTracing": False,
"zzObsoleteSettingsCheckVer2": False, # This is a hidden variable that is NOT displayed in the UI
# Obsolete setting names
"zWhitelist": "",
"zxGraylist": "",
"zyBlacklist": "",
"zyMatchDupDistance": 0,
"zSwapHighRes": False, "zSwapHighRes": False,
"zSwapLongLength": False, "zSwapLongLength": False,
"zSwapBetterBitRate": False, "zSwapBetterBitRate": False,
"zSwapCodec": False, "zSwapCodec": False,
"zSwapBetterFrameRate": False, "zSwapBetterFrameRate": False,
"zWhitelist": "",
"zxGraylist": "",
"zyBlacklist": "",
"zyMatchDupDistance": 0,
"zyMaxDupToProcess": 0,
"zzDebug": False,
"zzTracing": False,
} }
stash = StashPluginHelper( stash = StashPluginHelper(
stash_url=parse_args.stash_url, stash_url=parse_args.stash_url,
@@ -46,23 +59,53 @@ stash = StashPluginHelper(
DebugFieldName="zzDebug", DebugFieldName="zzDebug",
) )
stash.convertToAscii = True stash.convertToAscii = True
doJsonReturnModeTypes = ["tag_duplicates_task", "removeDupTag", "addExcludeTag", "removeExcludeTag", "mergeTags", "getLocalDupReportPath", "createDuplicateReportWithoutTagging", "deleteLocalDupReportHtmlFiles"]
advanceMenuOptions = [ "pathToDelete", "pathToDeleteBlacklist", "sizeToDeleteLess", "sizeToDeleteGreater", "sizeToDeleteBlacklistLess", "sizeToDeleteBlacklistGreater", "durationToDeleteLess", "durationToDeleteGreater", "durationToDeleteBlacklistLess", "durationToDeleteBlacklistGreater",
"commonResToDeleteLess", "commonResToDeleteEq", "commonResToDeleteGreater", "commonResToDeleteBlacklistLess", "commonResToDeleteBlacklistEq", "commonResToDeleteBlacklistGreater", "resolutionToDeleteLess", "resolutionToDeleteEq", "resolutionToDeleteGreater",
"resolutionToDeleteBlacklistLess", "resolutionToDeleteBlacklistEq", "resolutionToDeleteBlacklistGreater", "ratingToDeleteLess", "ratingToDeleteEq", "ratingToDeleteGreater", "ratingToDeleteBlacklistLess", "ratingToDeleteBlacklistEq", "ratingToDeleteBlacklistGreater",
"tagToDelete", "tagToDeleteBlacklist", "titleToDelete", "titleToDeleteBlacklist", "pathStrToDelete", "pathStrToDeleteBlacklist"]
doJsonReturnModeTypes = ["tag_duplicates_task", "removeDupTag", "addExcludeTag", "removeExcludeTag", "mergeTags", "getLocalDupReportPath",
"createDuplicateReportWithoutTagging", "deleteLocalDupReportHtmlFiles", "clear_duplicate_tags_task",
"deleteAllDupFileManagerTags", "deleteBlackListTaggedDuplicatesTask", "deleteTaggedDuplicatesLwrResOrLwrDuration",
"deleteBlackListTaggedDuplicatesLwrResOrLwrDuration"]
doJsonReturnModeTypes += [advanceMenuOptions]
doJsonReturn = False doJsonReturn = False
if stash.PLUGIN_TASK_NAME in doJsonReturnModeTypes: if len(sys.argv) < 2 and stash.PLUGIN_TASK_NAME in doJsonReturnModeTypes:
doJsonReturn = True doJsonReturn = True
stash.log_to_norm = stash.LogTo.FILE stash.log_to_norm = stash.LogTo.FILE
elif stash.PLUGIN_TASK_NAME == "doEarlyExit":
time.sleep(3)
stash.Log("Doing early exit because of task name")
time.sleep(3)
exit(0)
stash.Log("******************* Starting *******************") stash.Log("******************* Starting *******************")
if len(sys.argv) > 1: if len(sys.argv) > 1:
stash.Log(f"argv = {sys.argv}") stash.Log(f"argv = {sys.argv}")
else: else:
stash.Trace(f"No command line arguments. JSON_INPUT['args'] = {stash.JSON_INPUT['args']}; PLUGIN_TASK_NAME = {stash.PLUGIN_TASK_NAME}") stash.Debug(f"No command line arguments. JSON_INPUT['args'] = {stash.JSON_INPUT['args']}; PLUGIN_TASK_NAME = {stash.PLUGIN_TASK_NAME}; argv = {sys.argv}")
stash.status(logLevel=logging.DEBUG) stash.status(logLevel=logging.DEBUG)
# stash.Trace(f"\nStarting (__file__={__file__}) (stash.CALLED_AS_STASH_PLUGIN={stash.CALLED_AS_STASH_PLUGIN}) (stash.DEBUG_TRACING={stash.DEBUG_TRACING}) (stash.PLUGIN_TASK_NAME={stash.PLUGIN_TASK_NAME})************************************************") # stash.Trace(f"\nStarting (__file__={__file__}) (stash.CALLED_AS_STASH_PLUGIN={stash.CALLED_AS_STASH_PLUGIN}) (stash.DEBUG_TRACING={stash.DEBUG_TRACING}) (stash.PLUGIN_TASK_NAME={stash.PLUGIN_TASK_NAME})************************************************")
# stash.encodeToUtf8 = True # stash.encodeToUtf8 = True
# ToDo: Remove below commented out lines of code
# Test code that should be deleted after testing is complete
# stash.configure_plugin(stash.PLUGIN_ID, {"zSwapHighRes" : True})
# stash.configure_plugin(stash.PLUGIN_ID, {"zSwapLongLength" : True})
# stash.configure_plugin(stash.PLUGIN_ID, {"zSwapBetterFrameRate" : True})
# stash.configure_plugin(stash.PLUGIN_ID, {"zzObsoleteSettingsCheckVer2" : False})
# stash.configure_plugin(stash.PLUGIN_ID, {"zSwapBetterBitRate" : True})
# stash.configure_plugin(stash.PLUGIN_ID, {"zSwapCodec" : True})
# stash.configure_plugin(stash.PLUGIN_ID, {"zyMatchDupDistance" : 1})
# stash.configure_plugin(stash.PLUGIN_ID, {"zySwapCodec" : True})
# stash.configure_plugin(stash.PLUGIN_ID, {"zxGraylist" : "B:\\_\\"})
# exit(0)
obsoleteSettingsToConvert = {"zWhitelist" : "zvWhitelist", "zxGraylist" : "zwGraylist", "zyBlacklist" : "zxBlacklist", "zyMatchDupDistance" : "matchDupDistance", "zSwapHighRes" : "zySwapHighRes", "zSwapLongLength" : "zySwapLongLength", "zSwapBetterBitRate" : "zySwapBetterBitRate", "zSwapCodec" : "zySwapCodec", "zSwapBetterFrameRate" : "zySwapBetterFrameRate"}
stash.replaceObsoleteSettings(obsoleteSettingsToConvert, "zzObsoleteSettingsCheckVer2")
LOG_STASH_N_PLUGIN = stash.LogTo.STASH if stash.CALLED_AS_STASH_PLUGIN else stash.LogTo.CONSOLE + stash.LogTo.FILE LOG_STASH_N_PLUGIN = stash.LogTo.STASH if stash.CALLED_AS_STASH_PLUGIN else stash.LogTo.CONSOLE + stash.LogTo.FILE
listSeparator = stash.Setting('listSeparator', ',', notEmpty=True) listSeparator = stash.Setting('listSeparator', ',', notEmpty=True)
@@ -79,11 +122,11 @@ significantTimeDiff = stash.Setting('significantTimeDiff')
toRecycleBeforeSwap = stash.Setting('toRecycleBeforeSwap') toRecycleBeforeSwap = stash.Setting('toRecycleBeforeSwap')
cleanAfterDel = stash.Setting('cleanAfterDel') cleanAfterDel = stash.Setting('cleanAfterDel')
swapHighRes = stash.Setting('zSwapHighRes') swapHighRes = stash.Setting('zySwapHighRes')
swapLongLength = stash.Setting('zSwapLongLength') swapLongLength = stash.Setting('zySwapLongLength')
swapBetterBitRate = stash.Setting('zSwapBetterBitRate') swapBetterBitRate = stash.Setting('zySwapBetterBitRate')
swapCodec = stash.Setting('zSwapCodec') swapCodec = stash.Setting('zySwapCodec')
swapBetterFrameRate = stash.Setting('zSwapBetterFrameRate') swapBetterFrameRate = stash.Setting('zySwapBetterFrameRate')
favorLongerFileName = stash.Setting('favorLongerFileName') favorLongerFileName = stash.Setting('favorLongerFileName')
favorLargerFileSize = stash.Setting('favorLargerFileSize') favorLargerFileSize = stash.Setting('favorLargerFileSize')
favorBitRateChange = stash.Setting('favorBitRateChange') favorBitRateChange = stash.Setting('favorBitRateChange')
@@ -106,7 +149,7 @@ tagLongDurationLowRes = stash.Setting('tagLongDurationLowRes')
bitRateIsImporantComp = stash.Setting('bitRateIsImporantComp') bitRateIsImporantComp = stash.Setting('bitRateIsImporantComp')
codecIsImporantComp = stash.Setting('codecIsImporantComp') codecIsImporantComp = stash.Setting('codecIsImporantComp')
matchDupDistance = int(stash.Setting('zyMatchDupDistance')) matchDupDistance = int(stash.Setting('matchDupDistance'))
matchPhaseDistance = PhashDistance.EXACT matchPhaseDistance = PhashDistance.EXACT
matchPhaseDistanceText = "Exact Match" matchPhaseDistanceText = "Exact Match"
if matchDupDistance == 1: if matchDupDistance == 1:
@@ -130,7 +173,7 @@ duplicateMarkForDeletion = stash.Setting('DupFileTag')
if duplicateMarkForDeletion == "": if duplicateMarkForDeletion == "":
duplicateMarkForDeletion = 'DuplicateMarkForDeletion' duplicateMarkForDeletion = 'DuplicateMarkForDeletion'
base1_duplicateWhitelistTag = duplicateMarkForDeletion base1_duplicateMarkForDeletion = duplicateMarkForDeletion
duplicateWhitelistTag = stash.Setting('DupWhiteListTag') duplicateWhitelistTag = stash.Setting('DupWhiteListTag')
if duplicateWhitelistTag == "": if duplicateWhitelistTag == "":
@@ -160,7 +203,7 @@ if stash.Setting('underscoreDupFileTag') and not duplicateMarkForDeletion.starts
else: else:
stash.Trace(f"duplicateMarkForDeletion = {duplicateMarkForDeletion}") stash.Trace(f"duplicateMarkForDeletion = {duplicateMarkForDeletion}")
base2_duplicateWhitelistTag = duplicateMarkForDeletion base2_duplicateMarkForDeletion = duplicateMarkForDeletion
if stash.Setting('appendMatchDupDistance'): if stash.Setting('appendMatchDupDistance'):
duplicateMarkForDeletion += f"_{matchDupDistance}" duplicateMarkForDeletion += f"_{matchDupDistance}"
@@ -168,15 +211,15 @@ if stash.Setting('appendMatchDupDistance'):
stash.initMergeMetadata(excludeMergeTags) stash.initMergeMetadata(excludeMergeTags)
graylist = stash.Setting('zxGraylist').split(listSeparator) graylist = stash.Setting('zwGraylist').split(listSeparator)
graylist = [item.lower() for item in graylist] graylist = [item.lower() for item in graylist]
if graylist == [""] : graylist = [] if graylist == [""] : graylist = []
stash.Trace(f"graylist = {graylist}") stash.Trace(f"graylist = {graylist}")
whitelist = stash.Setting('zWhitelist').split(listSeparator) whitelist = stash.Setting('zvWhitelist').split(listSeparator)
whitelist = [item.lower() for item in whitelist] whitelist = [item.lower() for item in whitelist]
if whitelist == [""] : whitelist = [] if whitelist == [""] : whitelist = []
stash.Trace(f"whitelist = {whitelist}") stash.Trace(f"whitelist = {whitelist}")
blacklist = stash.Setting('zyBlacklist').split(listSeparator) blacklist = stash.Setting('zxBlacklist').split(listSeparator)
blacklist = [item.lower() for item in blacklist] blacklist = [item.lower() for item in blacklist]
if blacklist == [""] : blacklist = [] if blacklist == [""] : blacklist = []
stash.Trace(f"blacklist = {blacklist}") stash.Trace(f"blacklist = {blacklist}")
@@ -457,9 +500,12 @@ def getPath(Scene, getParent = False):
return pathlib.Path(path).resolve().parent return pathlib.Path(path).resolve().parent
return path return path
def getHtmlReportTableRow(qtyResults): def getHtmlReportTableRow(qtyResults, tagDuplicates):
htmlReportPrefix = stash.Setting('htmlReportPrefix') htmlReportPrefix = stash.Setting('htmlReportPrefix')
htmlReportPrefix = htmlReportPrefix.replace('http://127.0.0.1:9999/graphql', stash.url)
htmlReportPrefix = htmlReportPrefix.replace('http://localhost:9999/graphql', stash.url) htmlReportPrefix = htmlReportPrefix.replace('http://localhost:9999/graphql', stash.url)
if tagDuplicates == False:
htmlReportPrefix = htmlReportPrefix.replace('name="AdvanceMenu"', "hidden")
htmlReportPrefix = htmlReportPrefix.replace('(QtyPlaceHolder)', f'{qtyResults}') htmlReportPrefix = htmlReportPrefix.replace('(QtyPlaceHolder)', f'{qtyResults}')
htmlReportPrefix = htmlReportPrefix.replace('(MatchTypePlaceHolder)', f'(Match Type = {matchPhaseDistanceText})') htmlReportPrefix = htmlReportPrefix.replace('(MatchTypePlaceHolder)', f'(Match Type = {matchPhaseDistanceText})')
htmlReportPrefix = htmlReportPrefix.replace('(DateCreatedPlaceHolder)', datetime.now().strftime("%d-%b-%Y, %H:%M:%S")) htmlReportPrefix = htmlReportPrefix.replace('(DateCreatedPlaceHolder)', datetime.now().strftime("%d-%b-%Y, %H:%M:%S"))
@@ -490,7 +536,7 @@ def logReason(DupFileToKeep, Scene, reason):
htmlReportName = f"{stash.PLUGINS_PATH}{os.sep}{stash.Setting('htmlReportName')}" htmlReportName = f"{stash.PLUGINS_PATH}{os.sep}{stash.Setting('htmlReportName')}"
def mangeDupFiles(merge=False, deleteDup=False, tagDuplicates=False): def mangeDupFiles(merge=False, deleteDup=False, tagDuplicates=False, deleteBlacklistOnly=False, deleteLowerResAndDuration=False):
global reasonDict global reasonDict
duplicateMarkForDeletion_descp = 'Tag added to duplicate scenes so-as to tag them for deletion.' duplicateMarkForDeletion_descp = 'Tag added to duplicate scenes so-as to tag them for deletion.'
stash.Trace(f"duplicateMarkForDeletion = {duplicateMarkForDeletion}") stash.Trace(f"duplicateMarkForDeletion = {duplicateMarkForDeletion}")
@@ -536,8 +582,9 @@ def mangeDupFiles(merge=False, deleteDup=False, tagDuplicates=False):
stash.Log(f"Found {qtyResults} duplicate sets...") stash.Log(f"Found {qtyResults} duplicate sets...")
fileHtmlReport = None fileHtmlReport = None
if createHtmlReport: if createHtmlReport:
deleteLocalDupReportHtmlFiles(False)
fileHtmlReport = open(htmlReportName, "w") fileHtmlReport = open(htmlReportName, "w")
fileHtmlReport.write(f"{getHtmlReportTableRow(qtyResults)}\n") fileHtmlReport.write(f"{getHtmlReportTableRow(qtyResults, tagDuplicates)}\n")
fileHtmlReport.write(f"{stash.Setting('htmlReportTable')}\n") fileHtmlReport.write(f"{stash.Setting('htmlReportTable')}\n")
htmlReportTableHeader = stash.Setting('htmlReportTableHeader') htmlReportTableHeader = stash.Setting('htmlReportTableHeader')
fileHtmlReport.write(f"{htmlReportTableRow}{htmlReportTableHeader}Scene</th>{htmlReportTableHeader}Duplicate to Delete</th>{htmlReportTableHeader}Scene-ToKeep</th>{htmlReportTableHeader}Duplicate to Keep</th></tr>\n") fileHtmlReport.write(f"{htmlReportTableRow}{htmlReportTableHeader}Scene</th>{htmlReportTableHeader}Duplicate to Delete</th>{htmlReportTableHeader}Scene-ToKeep</th>{htmlReportTableHeader}Duplicate to Keep</th></tr>\n")
@@ -656,8 +703,10 @@ def mangeDupFiles(merge=False, deleteDup=False, tagDuplicates=False):
else: else:
# ToDo: Add merge logic here # ToDo: Add merge logic here
if deleteDup: if deleteDup:
QtyDeleted += 1
DupFileName = DupFile['files'][0]['path'] DupFileName = DupFile['files'][0]['path']
if not deleteBlacklistOnly or stash.startsWithInList(blacklist, DupFile['files'][0]['path']):
if not deleteLowerResAndDuration or (isBetterVideo(DupFile, DupFileToKeep) and not significantMoreTimeCompareToBetterVideo(DupFileToKeep, DupFile)) or (significantMoreTimeCompareToBetterVideo(DupFile, DupFileToKeep) and not isBetterVideo(DupFileToKeep, DupFile)):
QtyDeleted += 1
DupFileNameOnly = pathlib.Path(DupFileName).stem DupFileNameOnly = pathlib.Path(DupFileName).stem
stash.Warn(f"Deleting duplicate '{DupFileName}';QtyDup={QtyDup};Set={QtyDupSet} of {qtyResults};QtyDeleted={QtyDeleted}", toAscii=True, printTo=LOG_STASH_N_PLUGIN) stash.Warn(f"Deleting duplicate '{DupFileName}';QtyDup={QtyDup};Set={QtyDupSet} of {qtyResults};QtyDeleted={QtyDeleted}", toAscii=True, printTo=LOG_STASH_N_PLUGIN)
if alternateTrashCanPath != "": if alternateTrashCanPath != "":
@@ -755,7 +804,7 @@ def mangeDupFiles(merge=False, deleteDup=False, tagDuplicates=False):
fileHtmlReport.close() fileHtmlReport.close()
PaginateId+=1 PaginateId+=1
fileHtmlReport = open(nextHtmReport, "w") fileHtmlReport = open(nextHtmReport, "w")
fileHtmlReport.write(f"{getHtmlReportTableRow(qtyResults)}\n") fileHtmlReport.write(f"{getHtmlReportTableRow(qtyResults, tagDuplicates)}\n")
if PaginateId > 1: if PaginateId > 1:
prevHtmReport = htmlReportNameHomePage.replace(".html", f"_{PaginateId-1}.html") prevHtmReport = htmlReportNameHomePage.replace(".html", f"_{PaginateId-1}.html")
else: else:
@@ -781,7 +830,7 @@ def mangeDupFiles(merge=False, deleteDup=False, tagDuplicates=False):
didAddTag = 1 if didAddTag else 0 didAddTag = 1 if didAddTag else 0
stash.Log(f"Tagging duplicate {DupFile['files'][0]['path']} for deletion;AddTag={didAddTag};Qty={QtyDup};Set={QtyDupSet} of {qtyResults};NewlyTag={QtyNewlyTag};isTag={QtyTagForDel}", toAscii=True, printTo=LOG_STASH_N_PLUGIN) stash.Log(f"Tagging duplicate {DupFile['files'][0]['path']} for deletion;AddTag={didAddTag};Qty={QtyDup};Set={QtyDupSet} of {qtyResults};NewlyTag={QtyNewlyTag};isTag={QtyTagForDel}", toAscii=True, printTo=LOG_STASH_N_PLUGIN)
stash.Trace(SepLine) stash.Trace(SepLine)
if maxDupToProcess > 0 and QtyDup > maxDupToProcess: if maxDupToProcess > 0 and ((QtyTagForDel > maxDupToProcess) or (QtyTagForDel == 0 and QtyDup > maxDupToProcess)):
break break
if fileHtmlReport != None: if fileHtmlReport != None:
@@ -818,14 +867,9 @@ def mangeDupFiles(merge=False, deleteDup=False, tagDuplicates=False):
# Delete all DupFileManager created tags # Delete all DupFileManager created tags
fileHtmlReport.write(f"{stash.Setting('htmlReportPostfix')}") fileHtmlReport.write(f"{stash.Setting('htmlReportPostfix')}")
fileHtmlReport.close() fileHtmlReport.close()
# ToDo: Add a better working method to open HTML page htmlReportName
stash.Log(f"Opening web page {htmlReportName}")
import webbrowser
webbrowser.open(htmlReportName, new=2, autoraise=True)
os.system(f"start file://{htmlReportName}")
stash.Log(f"************************************************************", printTo = stash.LogTo.STASH) stash.Log(f"************************************************************", printTo = stash.LogTo.STASH)
stash.Log(f"************************************************************", printTo = stash.LogTo.STASH) stash.Log(f"************************************************************", printTo = stash.LogTo.STASH)
stash.Log(f"View Stash duplicate report using the following link: file://{htmlReportName}", printTo = stash.LogTo.STASH) stash.Log(f"View Stash duplicate report using Stash->Settings->Tools->[Duplicate File Report]", printTo = stash.LogTo.STASH)
stash.Log(f"************************************************************", printTo = stash.LogTo.STASH) stash.Log(f"************************************************************", printTo = stash.LogTo.STASH)
stash.Log(f"************************************************************", printTo = stash.LogTo.STASH) stash.Log(f"************************************************************", printTo = stash.LogTo.STASH)
@@ -843,16 +887,57 @@ def mangeDupFiles(merge=False, deleteDup=False, tagDuplicates=False):
sys.stdout.write("Report complete") sys.stdout.write("Report complete")
def findCurrentTagId(tagNames): def findCurrentTagId(tagNames):
tagNames = [i for n, i in enumerate(tagNames) if i not in tagNames[:n]] # tagNames = [i for n, i in enumerate(tagNames) if i not in tagNames[:n]]
for tagName in tagNames: for tagName in tagNames:
tagId = stash.find_tags(q=tagName) tagId = stash.find_tags(q=tagName)
if len(tagId) > 0 and 'id' in tagId[0]: if len(tagId) > 0 and 'id' in tagId[0]:
stash.Debug("Using tag name {tagName} with Tag ID {tagId[0]['id']}") stash.Debug(f"Using tag name {tagName} with Tag ID {tagId[0]['id']}")
return tagId[0]['id'] return tagId[0]['id']
return "-1" return "-1"
def manageTagggedDuplicates(deleteScenes=False, clearTag=False, setGrayListTag=False): def getAdvanceMenuOptionSelected():
tagId = findCurrentTagId([duplicateMarkForDeletion, base1_duplicateWhitelistTag, base2_duplicateWhitelistTag, 'DuplicateMarkForDeletion', '_DuplicateMarkForDeletion']) isBlackList = False
pathToDelete = ""
sizeToDelete = -1
durationToDelete = -1
resolutionToDelete = -1
ratingToDelete = -1
tagToDelete = ""
titleToDelete = ""
pathStrToDelete = ""
compareToLess = False
compareToGreater = False
if 'Target' in stash.JSON_INPUT['args']:
if "Blacklist" in stash.PLUGIN_TASK_NAME:
isBlackList = True
if "Less" in stash.PLUGIN_TASK_NAME:
compareToLess = True
if "Greater" in stash.PLUGIN_TASK_NAME:
compareToGreater = True
if "pathToDelete" in stash.PLUGIN_TASK_NAME:
pathToDelete = stash.JSON_INPUT['args']['Target'].lower()
elif "sizeToDelete" in stash.PLUGIN_TASK_NAME:
sizeToDelete = int(stash.JSON_INPUT['args']['Target'])
elif "durationToDelete" in stash.PLUGIN_TASK_NAME:
durationToDelete = int(stash.JSON_INPUT['args']['Target'])
elif "commonResToDelete" in stash.PLUGIN_TASK_NAME:
resolutionToDelete = int(stash.JSON_INPUT['args']['Target'])
elif "resolutionToDelete" in stash.PLUGIN_TASK_NAME:
resolutionToDelete = int(stash.JSON_INPUT['args']['Target'])
elif "ratingToDelete" in stash.PLUGIN_TASK_NAME:
ratingToDelete = int(stash.JSON_INPUT['args']['Target']) * 20
elif "tagToDelete" in stash.PLUGIN_TASK_NAME:
tagToDelete = stash.JSON_INPUT['args']['Target'].lower()
elif "titleToDelete" in stash.PLUGIN_TASK_NAME:
titleToDelete = stash.JSON_INPUT['args']['Target'].lower()
elif "pathStrToDelete" in stash.PLUGIN_TASK_NAME:
pathStrToDelete = stash.JSON_INPUT['args']['Target'].lower()
return isBlackList, pathToDelete, sizeToDelete, durationToDelete, resolutionToDelete, ratingToDelete, tagToDelete, titleToDelete, pathStrToDelete, compareToLess, compareToGreater
def manageTagggedDuplicates(deleteScenes=False, clearTag=False, setGrayListTag=False, tagId=-1, advanceMenuOptionSelected=False):
if tagId == -1:
tagId = findCurrentTagId([duplicateMarkForDeletion, base1_duplicateMarkForDeletion, base2_duplicateMarkForDeletion, 'DuplicateMarkForDeletion', '_DuplicateMarkForDeletion'])
if int(tagId) < 0: if int(tagId) < 0:
stash.Warn(f"Could not find tag ID for tag '{duplicateMarkForDeletion}'.") stash.Warn(f"Could not find tag ID for tag '{duplicateMarkForDeletion}'.")
return return
@@ -861,6 +946,20 @@ def manageTagggedDuplicates(deleteScenes=False, clearTag=False, setGrayListTag=F
if clearAllDupfileManagerTags: if clearAllDupfileManagerTags:
excludedTags = [duplicateMarkForDeletion, duplicateWhitelistTag, excludeDupFileDeleteTag, graylistMarkForDeletion, longerDurationLowerResolution] excludedTags = [duplicateMarkForDeletion, duplicateWhitelistTag, excludeDupFileDeleteTag, graylistMarkForDeletion, longerDurationLowerResolution]
isBlackList = False
pathToDelete = ""
sizeToDelete = -1
durationToDelete = -1
resolutionToDelete = -1
ratingToDelete = -1
tagToDelete = ""
titleToDelete = ""
pathStrToDelete = ""
compareToLess = False
compareToGreater = False
if advanceMenuOptionSelected:
isBlackList, pathToDelete, sizeToDelete, durationToDelete, resolutionToDelete, ratingToDelete, tagToDelete, titleToDelete, pathStrToDelete, compareToLess, compareToGreater = getAdvanceMenuOptionSelected()
QtyDup = 0 QtyDup = 0
QtyDeleted = 0 QtyDeleted = 0
QtyClearedTags = 0 QtyClearedTags = 0
@@ -868,7 +967,7 @@ def manageTagggedDuplicates(deleteScenes=False, clearTag=False, setGrayListTag=F
QtyFailedQuery = 0 QtyFailedQuery = 0
stash.Debug("#########################################################################") stash.Debug("#########################################################################")
stash.startSpinningProcessBar() stash.startSpinningProcessBar()
scenes = stash.find_scenes(f={"tags": {"value":tagId, "modifier":"INCLUDES"}}, fragment='id tags {id name} files {path width height duration size video_codec bit_rate frame_rate} details') scenes = stash.find_scenes(f={"tags": {"value":tagId, "modifier":"INCLUDES"}}, fragment='id tags {id name} files {path width height duration size video_codec bit_rate frame_rate} details title rating100')
stash.stopSpinningProcessBar() stash.stopSpinningProcessBar()
qtyResults = len(scenes) qtyResults = len(scenes)
stash.Log(f"Found {qtyResults} scenes with tag ({duplicateMarkForDeletion})") stash.Log(f"Found {qtyResults} scenes with tag ({duplicateMarkForDeletion})")
@@ -915,6 +1014,80 @@ def manageTagggedDuplicates(deleteScenes=False, clearTag=False, setGrayListTag=F
elif deleteScenes: elif deleteScenes:
DupFileName = scene['files'][0]['path'] DupFileName = scene['files'][0]['path']
DupFileNameOnly = pathlib.Path(DupFileName).stem DupFileNameOnly = pathlib.Path(DupFileName).stem
if advanceMenuOptionSelected:
if isBlackList:
if not stash.startsWithInList(blacklist, scene['files'][0]['path']):
continue
if pathToDelete != "":
if not DupFileName.lower().startswith(pathToDelete):
stash.Debug(f"Skipping file {DupFileName} because it does not start with {pathToDelete}.")
continue
elif pathStrToDelete != "":
if not pathStrToDelete in DupFileName.lower():
stash.Debug(f"Skipping file {DupFileName} because it does not contain value {pathStrToDelete}.")
continue
elif sizeToDelete != -1:
compareTo = int(scene['files'][0]['size'])
if compareToLess:
if not (compareTo < sizeToDelete):
continue
elif compareToGreater:
if not (compareTo > sizeToDelete):
continue
else:
if not compareTo == sizeToDelete:
continue
elif durationToDelete != -1:
compareTo = int(scene['files'][0]['duration'])
if compareToLess:
if not (compareTo < durationToDelete):
continue
elif compareToGreater:
if not (compareTo > durationToDelete):
continue
else:
if not compareTo == durationToDelete:
continue
elif resolutionToDelete != -1:
compareTo = int(scene['files'][0]['width']) * int(scene['files'][0]['height'])
if compareToLess:
if not (compareTo < resolutionToDelete):
continue
elif compareToGreater:
if not (compareTo > resolutionToDelete):
continue
else:
if not compareTo == resolutionToDelete:
continue
elif ratingToDelete != -1:
if scene['rating100'] == "None":
compareTo = 0
else:
compareTo = int(scene['rating100'])
if compareToLess:
if not (compareTo < resolutionToDelete):
continue
elif compareToGreater:
if not (compareTo > resolutionToDelete):
continue
else:
if not compareTo == resolutionToDelete:
continue
elif titleToDelete != "":
if not titleToDelete in scene['title'].lower():
stash.Debug(f"Skipping file {DupFileName} because it does not contain value {titleToDelete} in title ({scene['title']}).")
continue
elif tagToDelete != "":
doProcessThis = False
for tag in scene['tags']:
if tag['name'].lower() == tagToDelete:
doProcessThis = True
break
if doProcessThis == False:
continue
else:
continue
stash.Warn(f"Deleting duplicate '{DupFileName}'", toAscii=True, printTo=LOG_STASH_N_PLUGIN) stash.Warn(f"Deleting duplicate '{DupFileName}'", toAscii=True, printTo=LOG_STASH_N_PLUGIN)
if alternateTrashCanPath != "": if alternateTrashCanPath != "":
destPath = f"{alternateTrashCanPath }{os.sep}{DupFileNameOnly}" destPath = f"{alternateTrashCanPath }{os.sep}{DupFileNameOnly}"
@@ -932,14 +1105,13 @@ def manageTagggedDuplicates(deleteScenes=False, clearTag=False, setGrayListTag=F
stash.Debug("#####################################################") stash.Debug("#####################################################")
stash.Log(f"QtyDup={QtyDup}, QtyClearedTags={QtyClearedTags}, QtySetGraylistTag={QtySetGraylistTag}, QtyDeleted={QtyDeleted}, QtyFailedQuery={QtyFailedQuery}", printTo=LOG_STASH_N_PLUGIN) stash.Log(f"QtyDup={QtyDup}, QtyClearedTags={QtyClearedTags}, QtySetGraylistTag={QtySetGraylistTag}, QtyDeleted={QtyDeleted}, QtyFailedQuery={QtyFailedQuery}", printTo=LOG_STASH_N_PLUGIN)
killScanningJobs() killScanningJobs()
if deleteScenes: if deleteScenes and not advanceMenuOptionSelected:
if cleanAfterDel: if cleanAfterDel:
stash.Log("Adding clean jobs to the Task Queue", printTo=LOG_STASH_N_PLUGIN) stash.Log("Adding clean jobs to the Task Queue", printTo=LOG_STASH_N_PLUGIN)
stash.metadata_clean() stash.metadata_clean()
stash.metadata_clean_generated() stash.metadata_clean_generated()
stash.optimise_database() stash.optimise_database()
if doNotGeneratePhash:
stash.metadata_generate({"phashes": True})
def removeDupTag(): def removeDupTag():
if 'Target' not in stash.JSON_INPUT['args']: if 'Target' not in stash.JSON_INPUT['args']:
@@ -989,28 +1161,6 @@ def mergeTags():
stash.Log(f"Done merging scenes for scene {scenes[0]} and scene {scenes[1]}") stash.Log(f"Done merging scenes for scene {scenes[0]} and scene {scenes[1]}")
sys.stdout.write("{" + f"mergeTags : 'complete', id1: '{scene1}', id2: '{scene2}'" + "}") sys.stdout.write("{" + f"mergeTags : 'complete', id1: '{scene1}', id2: '{scene2}'" + "}")
def openWebpage():
htmlReportName = f"{stash.PLUGINS_PATH}{os.sep}{stash.Setting('htmlReportName')}"
stash.Log(f"Opening web page {htmlReportName}")
# chromePath = r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
chromePath = r"C:\PROGRA~2\Google\Chrome\Application\chrome.exe"
firefox = r"C:\PROGRA~1\MOZILL~1\firefox.exe"
browserPath = chromePath
if os.path.isfile(browserPath):
stash.Log(f"Opening web page {htmlReportName} with user {os.getlogin()} using {browserPath}")
# pw_record = pwd.getpwnam(os.getlogin())
# uid = pw_record.pw_uid
# gid = pw_record.pw_gid
# os.setgid(gid)
# os.setuid(uid)
# stash.Log(f"Using uid={uid} and gid={gid}")
# stash.executeProcess(f"\"{browserPath}\" -incognito --new-window \"file://{htmlReportName}\"", ExecDetach=True)
os.system(f"{browserPath} -incognito --new-window \"file://{htmlReportName}\"")
# else:
import webbrowser
webbrowser.open(htmlReportName, new=2, autoraise=True)
os.system(f"start file:///{htmlReportName}")
def getLocalDupReportPath(): def getLocalDupReportPath():
htmlReportExist = "true" if os.path.isfile(htmlReportName) else "false" htmlReportExist = "true" if os.path.isfile(htmlReportName) else "false"
# htmlReportExist = "false" # htmlReportExist = "false"
@@ -1019,7 +1169,7 @@ def getLocalDupReportPath():
stash.Log(f"Sending json value {jsonReturn}") stash.Log(f"Sending json value {jsonReturn}")
sys.stdout.write(jsonReturn) sys.stdout.write(jsonReturn)
def deleteLocalDupReportHtmlFiles(): def deleteLocalDupReportHtmlFiles(doJsonOutput = True):
htmlReportExist = "true" if os.path.isfile(htmlReportName) else "false" htmlReportExist = "true" if os.path.isfile(htmlReportName) else "false"
if os.path.isfile(htmlReportName): if os.path.isfile(htmlReportName):
stash.Log(f"Deleting file {htmlReportName}") stash.Log(f"Deleting file {htmlReportName}")
@@ -1033,10 +1183,42 @@ def deleteLocalDupReportHtmlFiles():
os.remove(fileName) os.remove(fileName)
else: else:
stash.Log(f"Report file does not exist: {htmlReportName}") stash.Log(f"Report file does not exist: {htmlReportName}")
if doJsonOutput:
jsonReturn = "{'LocalDupReportExist' : " + f"{htmlReportExist}" + ", 'Path': '" + f"{htmlReportName}" + "', 'qty': '" + f"{x}" + "'}" jsonReturn = "{'LocalDupReportExist' : " + f"{htmlReportExist}" + ", 'Path': '" + f"{htmlReportName}" + "', 'qty': '" + f"{x}" + "'}"
stash.Log(f"Sending json value {jsonReturn}") stash.Log(f"Sending json value {jsonReturn}")
sys.stdout.write(jsonReturn) sys.stdout.write(jsonReturn)
def removeTagFromAllScenes(tagName, deleteTags):
tagId = stash.find_tags(q=tagName)
if len(tagId) > 0 and 'id' in tagId[0]:
if deleteTags:
stash.Debug(f"Deleting tag name {tagName} with Tag ID {tagId[0]['id']} from stash.")
stash.destroy_tag(int(tagId[0]['id']))
else:
stash.Debug(f"Removing tag name {tagName} with Tag ID {tagId[0]['id']} from all scenes.")
manageTagggedDuplicates(clearTag=True, tagId=int(tagId[0]['id']))
return True
return False
def removeAllDupTagsFromAllScenes(deleteTags=False):
tagsToClear = [duplicateMarkForDeletion, base1_duplicateMarkForDeletion, base2_duplicateMarkForDeletion, graylistMarkForDeletion, longerDurationLowerResolution, duplicateWhitelistTag]
for x in range(0, 3):
tagsToClear += [base1_duplicateMarkForDeletion + f"_{x}"]
for x in range(0, 3):
tagsToClear += [base2_duplicateMarkForDeletion + f"_{x}"]
tagsToClear = list(set(tagsToClear)) # Remove duplicates
validTags = []
for tagToClear in tagsToClear:
if removeTagFromAllScenes(tagToClear, deleteTags):
validTags +=[tagToClear]
if doJsonReturn:
jsonReturn = "{'removeAllDupTagFromAllScenes' : " + f"{duplicateMarkForDeletion}" + ", 'OtherTags': '" + f"{validTags}" + "'}"
stash.Log(f"Sending json value {jsonReturn}")
sys.stdout.write(jsonReturn)
else:
stash.Log(f"Clear tags {tagsToClear}")
# ToDo: Add additional menu items option only for bottom of report: # ToDo: Add additional menu items option only for bottom of report:
# Remove from stash all files no longer part of stash library # Remove from stash all files no longer part of stash library
# Remove from stash all files in the Exclusion list (Not supporting regexps) # Remove from stash all files in the Exclusion list (Not supporting regexps)
@@ -1052,7 +1234,7 @@ try:
mangeDupFiles(deleteDup=True, merge=mergeDupFilename) mangeDupFiles(deleteDup=True, merge=mergeDupFilename)
stash.Debug(f"{stash.PLUGIN_TASK_NAME} EXIT") stash.Debug(f"{stash.PLUGIN_TASK_NAME} EXIT")
elif stash.PLUGIN_TASK_NAME == "clear_duplicate_tags_task": elif stash.PLUGIN_TASK_NAME == "clear_duplicate_tags_task":
manageTagggedDuplicates(clearTag=True) removeAllDupTagsFromAllScenes()
stash.Debug(f"{stash.PLUGIN_TASK_NAME} EXIT") stash.Debug(f"{stash.PLUGIN_TASK_NAME} EXIT")
elif stash.PLUGIN_TASK_NAME == "graylist_tag_task": elif stash.PLUGIN_TASK_NAME == "graylist_tag_task":
manageTagggedDuplicates(setGrayListTag=True) manageTagggedDuplicates(setGrayListTag=True)
@@ -1081,6 +1263,18 @@ try:
elif stash.PLUGIN_TASK_NAME == "createDuplicateReportWithoutTagging": elif stash.PLUGIN_TASK_NAME == "createDuplicateReportWithoutTagging":
mangeDupFiles(tagDuplicates=False, merge=mergeDupFilename) mangeDupFiles(tagDuplicates=False, merge=mergeDupFilename)
stash.Debug(f"{stash.PLUGIN_TASK_NAME} EXIT") stash.Debug(f"{stash.PLUGIN_TASK_NAME} EXIT")
elif stash.PLUGIN_TASK_NAME == "deleteAllDupFileManagerTags":
removeAllDupTagsFromAllScenes(deleteTags=True)
stash.Debug(f"{stash.PLUGIN_TASK_NAME} EXIT")
elif stash.PLUGIN_TASK_NAME == "deleteBlackListTaggedDuplicatesTask":
mangeDupFiles(deleteDup=True, merge=mergeDupFilename, deleteBlacklistOnly=True)
stash.Debug(f"{stash.PLUGIN_TASK_NAME} EXIT")
elif stash.PLUGIN_TASK_NAME == "deleteTaggedDuplicatesLwrResOrLwrDuration":
mangeDupFiles(deleteDup=True, merge=mergeDupFilename, deleteLowerResAndDuration=True)
stash.Debug(f"{stash.PLUGIN_TASK_NAME} EXIT")
elif stash.PLUGIN_TASK_NAME == "deleteBlackListTaggedDuplicatesLwrResOrLwrDuration":
mangeDupFiles(deleteDup=True, merge=mergeDupFilename, deleteBlacklistOnly=True, deleteLowerResAndDuration=True)
stash.Debug(f"{stash.PLUGIN_TASK_NAME} EXIT")
elif parse_args.dup_tag: elif parse_args.dup_tag:
stash.PLUGIN_TASK_NAME = "dup_tag" stash.PLUGIN_TASK_NAME = "dup_tag"
mangeDupFiles(tagDuplicates=True, merge=mergeDupFilename) mangeDupFiles(tagDuplicates=True, merge=mergeDupFilename)
@@ -1091,12 +1285,15 @@ try:
stash.Debug(f"Delete Tagged duplicates EXIT") stash.Debug(f"Delete Tagged duplicates EXIT")
elif parse_args.clear_tag: elif parse_args.clear_tag:
stash.PLUGIN_TASK_NAME = "clear_tag" stash.PLUGIN_TASK_NAME = "clear_tag"
manageTagggedDuplicates(clearTag=True) removeAllDupTagsFromAllScenes()
stash.Debug(f"Clear duplicate tags EXIT") stash.Debug(f"Clear duplicate tags EXIT")
elif parse_args.remove: elif parse_args.remove:
stash.PLUGIN_TASK_NAME = "remove" stash.PLUGIN_TASK_NAME = "remove"
mangeDupFiles(deleteDup=True, merge=mergeDupFilename) mangeDupFiles(deleteDup=True, merge=mergeDupFilename)
stash.Debug(f"Delete duplicate EXIT") stash.Debug(f"Delete duplicate EXIT")
elif len(sys.argv) < 2 and stash.PLUGIN_TASK_NAME in advanceMenuOptions:
manageTagggedDuplicates(deleteScenes=True, advanceMenuOptionSelected=True)
stash.Debug(f"{stash.PLUGIN_TASK_NAME} EXIT")
else: else:
stash.Log(f"Nothing to do!!! (PLUGIN_ARGS_MODE={stash.PLUGIN_TASK_NAME})") stash.Log(f"Nothing to do!!! (PLUGIN_ARGS_MODE={stash.PLUGIN_TASK_NAME})")
except Exception as e: except Exception as e:

View File

@@ -10,6 +10,10 @@ ui:
- DupFileManager.css.map - DupFileManager.css.map
- DupFileManager.js.map - DupFileManager.js.map
settings: settings:
matchDupDistance:
displayName: Match Duplicate Distance
description: (Default=0) Where 0 = Exact Match, 1 = High Match, 2 = Medium Match, and 3 = Low Match.
type: NUMBER
mergeDupFilename: mergeDupFilename:
displayName: Merge Duplicate Tags displayName: Merge Duplicate Tags
description: Before deletion, merge metadata from duplicate. E.g. Tag names, performers, studios, title, galleries, rating, details, etc... description: Before deletion, merge metadata from duplicate. E.g. Tag names, performers, studios, title, galleries, rating, details, etc...
@@ -18,46 +22,42 @@ settings:
displayName: Whitelist Delete In Same Folder displayName: Whitelist Delete In Same Folder
description: Allow whitelist deletion of duplicates within the same whitelist folder. description: Allow whitelist deletion of duplicates within the same whitelist folder.
type: BOOLEAN type: BOOLEAN
zSwapBetterBitRate: zvWhitelist:
displayName: Swap Better Bit Rate
description: Swap better bit rate for duplicate files. Use with DupFileManager_config.py file option favorHighBitRate
type: BOOLEAN
zSwapBetterFrameRate:
displayName: Swap Better Frame Rate
description: Swap better frame rate for duplicates. Use with DupFileManager_config.py file option favorHigherFrameRate
type: BOOLEAN
zSwapCodec:
displayName: Swap Better Codec
description: If enabled, swap better codec duplicate files to preferred path.
type: BOOLEAN
zSwapHighRes:
displayName: Swap Higher Resolution
description: If enabled, swap higher resolution duplicate files to preferred path.
type: BOOLEAN
zSwapLongLength:
displayName: Swap Longer Duration
description: If enabled, swap longer duration media files to preferred path. Longer is determine by significantLongerTime field.
type: BOOLEAN
zWhitelist:
displayName: White List displayName: White List
description: A comma seperated list of paths NOT to be deleted. E.g. C:\Favorite\,E:\MustKeep\ description: A comma seperated list of paths NOT to be deleted. E.g. C:\Favorite\,E:\MustKeep\
type: STRING type: STRING
zxGraylist: zwGraylist:
displayName: Gray List displayName: Gray List
description: Preferential paths to determine which duplicate should be kept. E.g. C:\2nd_Fav,C:\3rd_Fav,C:\4th_Fav,H:\ShouldKeep description: Preferential paths to determine which duplicate should be kept. E.g. C:\2nd_Fav,C:\3rd_Fav,C:\4th_Fav,H:\ShouldKeep
type: STRING type: STRING
zyBlacklist: zxBlacklist:
displayName: Black List displayName: Black List
description: Least preferential paths; Determine primary deletion candidates. E.g. C:\Downloads,C:\DelMe-3rd,C:\DelMe-2nd,C:\DeleteMeFirst description: Least preferential paths; Determine primary deletion candidates. E.g. C:\Downloads,C:\DelMe-3rd,C:\DelMe-2nd,C:\DeleteMeFirst
type: STRING type: STRING
zyMatchDupDistance:
displayName: Match Duplicate Distance
description: (Default=0) Where 0 = Exact Match, 1 = High Match, 2 = Medium Match, and 3 = Low Match.
type: NUMBER
zyMaxDupToProcess: zyMaxDupToProcess:
displayName: Max Dup Process displayName: Max Dup Process
description: (Default=0) Maximum number of duplicates to process. If 0, infinity. description: (Default=0) Maximum number of duplicates to process. If 0, infinity.
type: NUMBER type: NUMBER
zySwapBetterBitRate:
displayName: Swap Better Bit Rate
description: Swap better bit rate for duplicate files. Use with DupFileManager_config.py file option favorHighBitRate
type: BOOLEAN
zySwapBetterFrameRate:
displayName: Swap Better Frame Rate
description: Swap better frame rate for duplicates. Use with DupFileManager_config.py file option favorHigherFrameRate
type: BOOLEAN
zySwapCodec:
displayName: Swap Better Codec
description: If enabled, swap better codec duplicate files to preferred path.
type: BOOLEAN
zySwapHighRes:
displayName: Swap Higher Resolution
description: If enabled, swap higher resolution duplicate files to preferred path.
type: BOOLEAN
zySwapLongLength:
displayName: Swap Longer Duration
description: If enabled, swap longer duration media files to preferred path. Longer is determine by significantLongerTime field.
type: BOOLEAN
zzDebug: zzDebug:
displayName: Debug displayName: Debug
description: Enable debug so-as to add additional debug logging in Stash\plugins\DupFileManager\DupFileManager.log description: Enable debug so-as to add additional debug logging in Stash\plugins\DupFileManager\DupFileManager.log
@@ -87,11 +87,3 @@ tasks:
description: Delete duplicate scenes. Performs deletion without first tagging. description: Delete duplicate scenes. Performs deletion without first tagging.
defaultArgs: defaultArgs:
mode: delete_duplicates_task mode: delete_duplicates_task
- name: Generate PHASH Matching
description: Generate PHASH file matching. Used for file comparisons.
defaultArgs:
mode: generate_phash_task
- name: Tag Graylist
description: Set tag _GraylistMarkForDeletion to scenes having DuplicateMarkForDeletion tag and that are in the Graylist.
defaultArgs:
mode: graylist_tag_task

View File

@@ -119,7 +119,7 @@ table, th, td {border:1px solid black;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script> <script>
function RunPluginDupFileManager(Mode, ActionID, chkBxRemoveValid, button) { function RunPluginDupFileManager(Mode, ActionID, chkBxRemoveValid, button) {
$.ajax({method: "POST", url: "http://127.0.0.1:9999/graphql", contentType: "application/json", dataType: "text", $.ajax({method: "POST", url: "http://localhost:9999/graphql", contentType: "application/json", dataType: "text",
data: JSON.stringify({ data: JSON.stringify({
query: `mutation RunPluginOperation($plugin_id:ID!,$args:Map!){runPluginOperation(plugin_id:$plugin_id,args:$args)}`, query: `mutation RunPluginOperation($plugin_id:ID!,$args:Map!){runPluginOperation(plugin_id:$plugin_id,args:$args)}`,
variables: {"plugin_id": "DupFileManager", "args": { "Target" : ActionID, "mode":Mode}}, variables: {"plugin_id": "DupFileManager", "args": { "Target" : ActionID, "mode":Mode}},
@@ -139,7 +139,7 @@ $(document).ready(function(){
return return
} }
const SceneId = this.id; const SceneId = this.id;
$.ajax({method: "POST", url: "http://127.0.0.1:9999/graphql", contentType: "application/json", $.ajax({method: "POST", url: "http://localhost:9999/graphql", contentType: "application/json",
data: JSON.stringify({ data: JSON.stringify({
query: `mutation SceneDestroy($input:SceneDestroyInput!) {sceneDestroy(input: $input)}`, query: `mutation SceneDestroy($input:SceneDestroyInput!) {sceneDestroy(input: $input)}`,
variables: {"input":{"delete_file":true,"id":SceneId}}, variables: {"input":{"delete_file":true,"id":SceneId}},
@@ -149,6 +149,12 @@ $(document).ready(function(){
}}); }});
this.style.visibility = 'hidden'; this.style.visibility = 'hidden';
} }
else if (this.id === "AdvanceMenu")
{
var newUrl = window.location.href;
newUrl = newUrl.replace(/DuplicateTagScenes[_0-9]*.html/g, "DupFileManager/advance_options.html?GQL=http://localhost:9999/graphql");
window.open(newUrl, "_blank");
}
else else
RunPluginDupFileManager(this.value, this.id, chkBxRemoveValid, this) RunPluginDupFileManager(this.value, this.id, chkBxRemoveValid, this)
}); });
@@ -166,6 +172,7 @@ $(document).ready(function(){
<td><table><tr> <td><table><tr>
<td><input type="checkbox" id="RemoveValidatePrompt" name="RemoveValidatePrompt"><label for="RemoveValidatePrompt" title="Disable Validate Prompts (Popups)">Disable Task Prompt</label><br></td> <td><input type="checkbox" id="RemoveValidatePrompt" name="RemoveValidatePrompt"><label for="RemoveValidatePrompt" title="Disable Validate Prompts (Popups)">Disable Task Prompt</label><br></td>
<td><input type="checkbox" id="RemoveToKeepConfirm" name="RemoveToKeepConfirm"><label for="RemoveToKeepConfirm" title="Disable confirmation prompts for delete scenes">Disable Delete Confirmation</label><br></td> <td><input type="checkbox" id="RemoveToKeepConfirm" name="RemoveToKeepConfirm"><label for="RemoveToKeepConfirm" title="Disable confirmation prompts for delete scenes">Disable Delete Confirmation</label><br></td>
<td><button id="AdvanceMenu" title="View advance menu for tagged duplicates." name="AdvanceMenu">Advance Tag Menu</button></td>
</tr></table></td> </tr></table></td>
</tr></table></center> </tr></table></center>
<h2>Stash Duplicate Scenes Report (MatchTypePlaceHolder)</h2>\n""", <h2>Stash Duplicate Scenes Report (MatchTypePlaceHolder)</h2>\n""",

View File

@@ -421,6 +421,48 @@ class StashPluginHelper(StashInterface):
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)
# Replaces obsolete UI settings variable with new name. Only use this with strings and numbers.
# Example usage:
# obsoleteSettingsToConvert = {"OldVariableName" : "NewVariableName", "AnotherOldVarName" : "NewName2"}
# stash.replaceObsoleteSettings(obsoleteSettingsToConvert, "ObsoleteSettingsCheckVer2")
def replaceObsoleteSettings(self, settingSet:dict, SettingToCheckFirst="", init_defaults=False):
if SettingToCheckFirst == "" or self.Setting(SettingToCheckFirst) == False:
for key in settingSet:
obsoleteVar = self.Setting(key)
if isinstance(obsoleteVar, bool):
if obsoleteVar:
if self.Setting(settingSet[key]) == False:
self.Log(f"Detected obsolete (bool) settings ({key}). Moving obsolete settings to new setting name {settingSet[key]}.")
results = self.configure_plugin(self.PLUGIN_ID, {settingSet[key]:self.Setting(key), key : False}, init_defaults)
self.Debug(f"configure_plugin = {results}")
else:
self.Log(f"Detected obsolete (bool) settings ({key}), and deleting it's content because new setting name ({settingSet[key]}) is already populated.")
results = self.configure_plugin(self.PLUGIN_ID, {key : False}, init_defaults)
self.Debug(f"configure_plugin = {results}")
elif isinstance(obsoleteVar, int): # Both int and bool type returns true here
if obsoleteVar > 0:
if self.Setting(settingSet[key]) > 0:
self.Log(f"Detected obsolete (int) settings ({key}), and deleting it's content because new setting name ({settingSet[key]}) is already populated.")
results = self.configure_plugin(self.PLUGIN_ID, {key : 0}, init_defaults)
self.Debug(f"configure_plugin = {results}")
else:
self.Log(f"Detected obsolete (int) settings ({key}). Moving obsolete settings to new setting name {settingSet[key]}.")
results = self.configure_plugin(self.PLUGIN_ID, {settingSet[key]:self.Setting(key), key : 0}, init_defaults)
self.Debug(f"configure_plugin = {results}")
elif obsoleteVar != "":
if self.Setting(settingSet[key]) == "":
self.Log(f"Detected obsolete (str) settings ({key}). Moving obsolete settings to new setting name {settingSet[key]}.")
results = self.configure_plugin(self.PLUGIN_ID, {settingSet[key]:self.Setting(key), key : ""}, init_defaults)
self.Debug(f"configure_plugin = {results}")
else:
self.Log(f"Detected obsolete (str) settings ({key}), and deleting it's content because new setting name ({settingSet[key]}) is already populated.")
results = self.configure_plugin(self.PLUGIN_ID, {key : ""}, init_defaults)
self.Debug(f"configure_plugin = {results}")
if SettingToCheckFirst != "":
results = self.configure_plugin(self.PLUGIN_ID, {SettingToCheckFirst : True}, init_defaults)
self.Debug(f"configure_plugin = {results}")
def executeProcess(self, args, ExecDetach=False): def executeProcess(self, args, ExecDetach=False):
pid = None pid = None
self.Trace(f"self.IS_WINDOWS={self.IS_WINDOWS} args={args}") self.Trace(f"self.IS_WINDOWS={self.IS_WINDOWS} args={args}")

File diff suppressed because it is too large Load Diff

View File

@@ -421,6 +421,48 @@ class StashPluginHelper(StashInterface):
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)
# Replaces obsolete UI settings variable with new name. Only use this with strings and numbers.
# Example usage:
# obsoleteSettingsToConvert = {"OldVariableName" : "NewVariableName", "AnotherOldVarName" : "NewName2"}
# stash.replaceObsoleteSettings(obsoleteSettingsToConvert, "ObsoleteSettingsCheckVer2")
def replaceObsoleteSettings(self, settingSet:dict, SettingToCheckFirst="", init_defaults=False):
if SettingToCheckFirst == "" or self.Setting(SettingToCheckFirst) == False:
for key in settingSet:
obsoleteVar = self.Setting(key)
if isinstance(obsoleteVar, bool):
if obsoleteVar:
if self.Setting(settingSet[key]) == False:
self.Log(f"Detected obsolete (bool) settings ({key}). Moving obsolete settings to new setting name {settingSet[key]}.")
results = self.configure_plugin(self.PLUGIN_ID, {settingSet[key]:self.Setting(key), key : False}, init_defaults)
self.Debug(f"configure_plugin = {results}")
else:
self.Log(f"Detected obsolete (bool) settings ({key}), and deleting it's content because new setting name ({settingSet[key]}) is already populated.")
results = self.configure_plugin(self.PLUGIN_ID, {key : False}, init_defaults)
self.Debug(f"configure_plugin = {results}")
elif isinstance(obsoleteVar, int): # Both int and bool type returns true here
if obsoleteVar > 0:
if self.Setting(settingSet[key]) > 0:
self.Log(f"Detected obsolete (int) settings ({key}), and deleting it's content because new setting name ({settingSet[key]}) is already populated.")
results = self.configure_plugin(self.PLUGIN_ID, {key : 0}, init_defaults)
self.Debug(f"configure_plugin = {results}")
else:
self.Log(f"Detected obsolete (int) settings ({key}). Moving obsolete settings to new setting name {settingSet[key]}.")
results = self.configure_plugin(self.PLUGIN_ID, {settingSet[key]:self.Setting(key), key : 0}, init_defaults)
self.Debug(f"configure_plugin = {results}")
elif obsoleteVar != "":
if self.Setting(settingSet[key]) == "":
self.Log(f"Detected obsolete (str) settings ({key}). Moving obsolete settings to new setting name {settingSet[key]}.")
results = self.configure_plugin(self.PLUGIN_ID, {settingSet[key]:self.Setting(key), key : ""}, init_defaults)
self.Debug(f"configure_plugin = {results}")
else:
self.Log(f"Detected obsolete (str) settings ({key}), and deleting it's content because new setting name ({settingSet[key]}) is already populated.")
results = self.configure_plugin(self.PLUGIN_ID, {key : ""}, init_defaults)
self.Debug(f"configure_plugin = {results}")
if SettingToCheckFirst != "":
results = self.configure_plugin(self.PLUGIN_ID, {SettingToCheckFirst : True}, init_defaults)
self.Debug(f"configure_plugin = {results}")
def executeProcess(self, args, ExecDetach=False): def executeProcess(self, args, ExecDetach=False):
pid = None pid = None
self.Trace(f"self.IS_WINDOWS={self.IS_WINDOWS} args={args}") self.Trace(f"self.IS_WINDOWS={self.IS_WINDOWS} args={args}")

View File

@@ -421,6 +421,48 @@ class StashPluginHelper(StashInterface):
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)
# Replaces obsolete UI settings variable with new name. Only use this with strings and numbers.
# Example usage:
# obsoleteSettingsToConvert = {"OldVariableName" : "NewVariableName", "AnotherOldVarName" : "NewName2"}
# stash.replaceObsoleteSettings(obsoleteSettingsToConvert, "ObsoleteSettingsCheckVer2")
def replaceObsoleteSettings(self, settingSet:dict, SettingToCheckFirst="", init_defaults=False):
if SettingToCheckFirst == "" or self.Setting(SettingToCheckFirst) == False:
for key in settingSet:
obsoleteVar = self.Setting(key)
if isinstance(obsoleteVar, bool):
if obsoleteVar:
if self.Setting(settingSet[key]) == False:
self.Log(f"Detected obsolete (bool) settings ({key}). Moving obsolete settings to new setting name {settingSet[key]}.")
results = self.configure_plugin(self.PLUGIN_ID, {settingSet[key]:self.Setting(key), key : False}, init_defaults)
self.Debug(f"configure_plugin = {results}")
else:
self.Log(f"Detected obsolete (bool) settings ({key}), and deleting it's content because new setting name ({settingSet[key]}) is already populated.")
results = self.configure_plugin(self.PLUGIN_ID, {key : False}, init_defaults)
self.Debug(f"configure_plugin = {results}")
elif isinstance(obsoleteVar, int): # Both int and bool type returns true here
if obsoleteVar > 0:
if self.Setting(settingSet[key]) > 0:
self.Log(f"Detected obsolete (int) settings ({key}), and deleting it's content because new setting name ({settingSet[key]}) is already populated.")
results = self.configure_plugin(self.PLUGIN_ID, {key : 0}, init_defaults)
self.Debug(f"configure_plugin = {results}")
else:
self.Log(f"Detected obsolete (int) settings ({key}). Moving obsolete settings to new setting name {settingSet[key]}.")
results = self.configure_plugin(self.PLUGIN_ID, {settingSet[key]:self.Setting(key), key : 0}, init_defaults)
self.Debug(f"configure_plugin = {results}")
elif obsoleteVar != "":
if self.Setting(settingSet[key]) == "":
self.Log(f"Detected obsolete (str) settings ({key}). Moving obsolete settings to new setting name {settingSet[key]}.")
results = self.configure_plugin(self.PLUGIN_ID, {settingSet[key]:self.Setting(key), key : ""}, init_defaults)
self.Debug(f"configure_plugin = {results}")
else:
self.Log(f"Detected obsolete (str) settings ({key}), and deleting it's content because new setting name ({settingSet[key]}) is already populated.")
results = self.configure_plugin(self.PLUGIN_ID, {key : ""}, init_defaults)
self.Debug(f"configure_plugin = {results}")
if SettingToCheckFirst != "":
results = self.configure_plugin(self.PLUGIN_ID, {SettingToCheckFirst : True}, init_defaults)
self.Debug(f"configure_plugin = {results}")
def executeProcess(self, args, ExecDetach=False): def executeProcess(self, args, ExecDetach=False):
pid = None pid = None
self.Trace(f"self.IS_WINDOWS={self.IS_WINDOWS} args={args}") self.Trace(f"self.IS_WINDOWS={self.IS_WINDOWS} args={args}")