From e1f3335db813e847eda6c705b73ebe146c7d3f47 Mon Sep 17 00:00:00 2001
From: David Maisonave <47364845+David-Maisonave@users.noreply.github.com>
Date: Tue, 26 Nov 2024 09:29:26 -0500
Subject: [PATCH] Updates to plugins RenameFile and DupFileManager
RenameFile Plugin Changes
### 0.5.6
- Fixed bug with studio getting the studio ID instead of the name of the studio in rename process.
- Improved performance by having code get all required scene details in one call to stash.
- To remove UI clutter, move rarely used options (performerAppendEnable, studioAppendEnable, tagAppendEnable, & fileRenameViaMove) to renamefile_settings.py
- Change options (performerAppendEnable, studioAppendEnable, tagAppendEnable) to default to True (enabled)
DupFileManager Plugin Changes
### 0.2.2
- Added dropdown menu logic to Advance Menu and reports.
- Added Graylist deletion option to Advance Menu.
- Report option to clear all flags from report.
- Report option to clear all (_DuplicateMarkForDeletion_?) tag from all scenes.
- Report option to delete from Stash DB all scenes with missing files in file system.
- Added popup tag list to report which list all tags associated with scene.
- Added popup performer list to report which list all performers associated with scene.
- Added popup gallery list to report which list all galleries associated with scene.
- Added popup group list to report which list all groups associated with scene.
- After merging tags in report, the report gets updated with the merged scene metadata.
- Added graylist deletion option to [**Advance Duplicate File Deletion Menu**].
- Added pinklist option to Settings->Plugins->Plugins and to [**Advance Duplicate File Deletion Menu**]
- The pinklist is only used with the [**Advance Duplicate File Deletion Menu**], and it's **NOT** used in the primary process to selected candidates for deletion.
- Advance Menu now works with non-tagged scenes that are in the current report.
---
plugins/DupFileManager/DupFileManager.py | 231 +++++++++++++++---
plugins/DupFileManager/DupFileManager.yml | 6 +-
.../DupFileManager_report_config.py | 107 +++++++-
plugins/DupFileManager/README.md | 9 +-
plugins/DupFileManager/advance_options.html | 174 ++++++++-----
.../DupFileManager/version_history/README.md | 17 +-
plugins/RenameFile/README.md | 2 +-
plugins/RenameFile/renamefile.py | 46 ++--
plugins/RenameFile/renamefile.yml | 18 +-
plugins/RenameFile/renamefile_settings.py | 8 +
plugins/RenameFile/version_history/README.md | 6 +
11 files changed, 464 insertions(+), 160 deletions(-)
create mode 100644 plugins/RenameFile/version_history/README.md
diff --git a/plugins/DupFileManager/DupFileManager.py b/plugins/DupFileManager/DupFileManager.py
index 1aa0ed9..cf44c8b 100644
--- a/plugins/DupFileManager/DupFileManager.py
+++ b/plugins/DupFileManager/DupFileManager.py
@@ -10,7 +10,7 @@ except Exception as e:
import traceback, sys
tb = traceback.format_exc()
print(f"ModulesValidate Exception. Error: {e}\nTraceBack={tb}", file=sys.stderr)
-import os, sys, time, pathlib, argparse, platform, shutil, traceback, logging, requests
+import os, sys, time, pathlib, argparse, platform, shutil, traceback, logging, requests, json
from datetime import datetime
from StashPluginHelper import StashPluginHelper
from stashapi.stash_types import PhashDistance
@@ -36,6 +36,7 @@ settings = {
"zvWhitelist": "",
"zwGraylist": "",
"zxBlacklist": "",
+ "zxPinklist": "",
"zyMaxDupToProcess": 0,
"zySwapHighRes": False,
"zySwapLongLength": False,
@@ -69,7 +70,7 @@ stash = StashPluginHelper(
)
stash.convertToAscii = True
-advanceMenuOptions = [ "applyCombo", "applyComboBlacklist", "pathToDelete", "pathToDeleteBlacklist", "sizeToDeleteLess", "sizeToDeleteGreater", "sizeToDeleteBlacklistLess", "sizeToDeleteBlacklistGreater", "durationToDeleteLess", "durationToDeleteGreater", "durationToDeleteBlacklistLess", "durationToDeleteBlacklistGreater",
+advanceMenuOptions = [ "applyCombo", "applyComboPinklist", "applyComboGraylist", "applyComboBlacklist", "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"]
@@ -232,7 +233,10 @@ stash.Trace(f"whitelist = {whitelist}")
blacklist = stash.Setting('zxBlacklist').split(listSeparator)
blacklist = [item.lower() for item in blacklist]
if blacklist == [""] : blacklist = []
-stash.Trace(f"blacklist = {blacklist}")
+pinklist = stash.Setting('zxPinklist').split(listSeparator)
+pinklist = [item.lower() for item in pinklist]
+if pinklist == [""] : pinklist = []
+stash.Trace(f"pinklist = {pinklist}")
def realpath(path):
"""
@@ -561,6 +565,10 @@ htmlHighlightTimeDiff = stash.Setting('htmlHighlightTimeDiff')
htmlPreviewOrStream = "stream" if stash.Setting('streamOverPreview') else "preview"
def writeRowToHtmlReport(fileHtmlReport, DupFile, DupFileToKeep, QtyTagForDel = "?", tagDuplicates = False):
+ htmlTagPrefix = '
'
+ htmlPerformerPrefix = '
'
+ htmlGalleryPrefix = '
'
+ htmlGroupPrefix = '
'
dupFileExist = True if os.path.isfile(DupFile['files'][0]['path']) else False
toKeepFileExist = True if os.path.isfile(DupFileToKeep['files'][0]['path']) else False
fileHtmlReport.write(f"{htmlReportTableRow}")
@@ -611,6 +619,29 @@ def writeRowToHtmlReport(fileHtmlReport, DupFile, DupFileToKeep, QtyTagForDel =
fileHtmlReport.write(f"[Play]")
else:
fileHtmlReport.write("[File NOT Exist]")
+ if len(DupFile['tags']) > 0:
+ fileHtmlReport.write(htmlTagPrefix)
+ for tag in DupFile['tags']:
+ # if not tag['ignore_auto_tag']:
+ fileHtmlReport.write(f"
{tag['name']}
")
+ fileHtmlReport.write("
")
+ if len(DupFile['performers']) > 0:
+ fileHtmlReport.write(htmlPerformerPrefix)
+ for performer in DupFile['performers']:
+ fileHtmlReport.write(f"
{performer['name']}
")
+ fileHtmlReport.write("
")
+ if len(DupFile['galleries']) > 0:
+ fileHtmlReport.write(htmlGalleryPrefix)
+ for gallery in DupFile['galleries']:
+ gallery = stash.find_gallery(gallery['id'])
+ fileHtmlReport.write(f"
{gallery['title']}
")
+ fileHtmlReport.write("
")
+ if len(DupFile['groups']) > 0:
+ fileHtmlReport.write(htmlGroupPrefix)
+ for group in DupFile['groups']:
+ fileHtmlReport.write(f"
{group['group']['name']}
")
+ fileHtmlReport.write("
")
+
fileHtmlReport.write("")
videoPreview = f""
@@ -633,14 +664,41 @@ def writeRowToHtmlReport(fileHtmlReport, DupFile, DupFileToKeep, QtyTagForDel =
else:
fileHtmlReport.write("[File NOT Exist]")
fileHtmlReport.write(f"")
+ if len(DupFileToKeep['tags']) > 0:
+ fileHtmlReport.write(htmlTagPrefix)
+ for tag in DupFileToKeep['tags']:
+ # if not tag['ignore_auto_tag']:
+ fileHtmlReport.write(f"
{tag['name']}
")
+ fileHtmlReport.write("")
+ if len(DupFileToKeep['performers']) > 0:
+ fileHtmlReport.write(htmlPerformerPrefix)
+ for performer in DupFileToKeep['performers']:
+ fileHtmlReport.write(f"
{performer['name']}
")
+ fileHtmlReport.write("")
+ if len(DupFileToKeep['galleries']) > 0:
+ fileHtmlReport.write(htmlGalleryPrefix)
+ for gallery in DupFileToKeep['galleries']:
+ gallery = stash.find_gallery(gallery['id'])
+ fileHtmlReport.write(f"
{gallery['title']}
")
+ fileHtmlReport.write("")
+ if len(DupFileToKeep['groups']) > 0:
+ fileHtmlReport.write(htmlGroupPrefix)
+ for group in DupFileToKeep['groups']:
+ fileHtmlReport.write(f"
{group['group']['name']}
")
+ fileHtmlReport.write("")
# ToDo: Add following buttons:
# rename file
fileHtmlReport.write(f"")
fileHtmlReport.write("\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 '
+htmlFileData = " paths {screenshot sprite " + htmlPreviewOrStream + "} "
+DuplicateCandidateForDeletionList = f"{htmlReportNameFolder}{os.sep}DuplicateCandidateForDeletionList.txt"
+
def mangeDupFiles(merge=False, deleteDup=False, tagDuplicates=False, deleteBlacklistOnly=False, deleteLowerResAndDuration=False):
global reasonDict
+ global htmlFileData
duplicateMarkForDeletion_descp = 'Tag added to duplicate scenes so-as to tag them for deletion.'
stash.Trace(f"duplicateMarkForDeletion = {duplicateMarkForDeletion}")
dupTagId = stash.createTagId(duplicateMarkForDeletion, duplicateMarkForDeletion_descp, ignoreAutoTag=True)
@@ -671,27 +729,29 @@ def mangeDupFiles(merge=False, deleteDup=False, tagDuplicates=False, deleteBlack
stash.Trace("#########################################################################")
stash.Log(f"Waiting for find_duplicate_scenes_diff to return results; matchDupDistance={matchPhaseDistanceText}; significantTimeDiff={significantTimeDiff}", printTo=LOG_STASH_N_PLUGIN)
stash.startSpinningProcessBar()
- htmlFileData = " paths {screenshot sprite " + htmlPreviewOrStream + "} " if createHtmlReport else ""
- mergeFieldData = " code director title rating100 date studio {id} movies {movie {id} } galleries {id} performers {id} urls " if merge else ""
- DupFileSets = stash.find_duplicate_scenes(matchPhaseDistance, fragment='id tags {id name} files {path width height duration size video_codec bit_rate frame_rate} details ' + mergeFieldData + htmlFileData)
+ mergeFieldData = " code director title rating100 date studio {id name} movies {movie {id} } urls " if merge else ""
+ if not createHtmlReport:
+ htmlFileData = ""
+ DupFileSets = stash.find_duplicate_scenes(matchPhaseDistance, fragment= fragmentForSceneDetails + mergeFieldData + htmlFileData)
stash.stopSpinningProcessBar()
qtyResults = len(DupFileSets)
stash.setProgressBarIter(qtyResults)
stash.Trace("#########################################################################")
stash.Log(f"Found {qtyResults} duplicate sets...")
fileHtmlReport = None
- if createHtmlReport:
+ if not os.path.isdir(htmlReportNameFolder):
+ os.mkdir(htmlReportNameFolder)
if not os.path.isdir(htmlReportNameFolder):
- os.mkdir(htmlReportNameFolder)
- if not os.path.isdir(htmlReportNameFolder):
- stash.Error(f"Failed to create report directory {htmlReportNameFolder}.")
- return
+ stash.Error(f"Failed to create report directory {htmlReportNameFolder}.")
+ return
+ if createHtmlReport:
deleteLocalDupReportHtmlFiles(False)
fileHtmlReport = open(htmlReportName, "w")
fileHtmlReport.write(f"{getHtmlReportTableRow(qtyResults, tagDuplicates)}\n")
fileHtmlReport.write(f"{stash.Setting('htmlReportTable')}\n")
htmlReportTableHeader = stash.Setting('htmlReportTableHeader')
fileHtmlReport.write(f"{htmlReportTableRow}{htmlReportTableHeader}Scene{htmlReportTableHeader}Duplicate to Delete{htmlReportTableHeader}Scene-ToKeep{htmlReportTableHeader}Duplicate to Keep\n")
+ fileDuplicateCandidateForDeletionList = open(DuplicateCandidateForDeletionList, "w")
for DupFileSet in DupFileSets:
# stash.Trace(f"DupFileSet={DupFileSet}", toAscii=True)
@@ -819,6 +879,7 @@ def mangeDupFiles(merge=False, deleteDup=False, tagDuplicates=False, deleteBlack
elif moveToTrashCan:
sendToTrash(DupFileName)
stash.destroyScene(DupFile['id'], delete_file=True)
+ updateDuplicateCandidateForDeletionList(DupFile['id'], removeScene = True)
elif tagDuplicates or fileHtmlReport != None:
if excludeFromReportIfSignificantTimeDiff and significantTimeDiffCheck(DupFile, DupFileToKeep, True):
stash.Log(f"Skipping duplicate {DupFile['files'][0]['path']} (ID={DupFile['id']}), because of time difference greater than {significantTimeDiff} for file {DupFileToKeep['files'][0]['path']}.")
@@ -837,6 +898,7 @@ def mangeDupFiles(merge=False, deleteDup=False, tagDuplicates=False, deleteBlack
# add delete only from stash db code and button using DB delete icon
stash.Debug(f"Adding scene {DupFile['id']} to HTML report.")
writeRowToHtmlReport(fileHtmlReport, DupFile, DupFileToKeep, QtyTagForDel, tagDuplicates)
+ fileDuplicateCandidateForDeletionList.write(json.dumps(DupFile) + "\n")
if QtyTagForDelPaginate >= htmlReportPaginate:
QtyTagForDelPaginate = 0
fileHtmlReport.write("\n")
@@ -884,6 +946,7 @@ def mangeDupFiles(merge=False, deleteDup=False, tagDuplicates=False, deleteBlack
if maxDupToProcess > 0 and ((QtyTagForDel > maxDupToProcess) or (QtyTagForDel == 0 and QtyDup > maxDupToProcess)):
break
+ fileDuplicateCandidateForDeletionList.close()
if fileHtmlReport != None:
fileHtmlReport.write("\n")
if PaginateId > 0:
@@ -921,26 +984,32 @@ def findCurrentTagId(tagNames):
for tagName in tagNames:
tagId = stash.find_tags(q=tagName)
if len(tagId) > 0 and 'id' in tagId[0]:
- stash.Debug(f"Using tag name {tagName} with Tag ID {tagId[0]['id']}")
- return tagId[0]['id']
- return "-1"
+ return tagId[0]['id'], tagName
+ return "-1", None
def toJson(data):
- import json
# data = data.replace("'", '"')
data = data.replace("\\", "\\\\")
data = data.replace("\\\\\\\\", "\\\\")
return json.loads(data)
-def getAnAdvanceMenuOptionSelected(taskName, target, isBlackList, pathToDelete, sizeToDelete, durationToDelete, resolutionToDelete, ratingToDelete, tagToDelete, titleToDelete, pathStrToDelete, fileNotExistToDelete, compareToLess, compareToGreater):
+def getAnAdvanceMenuOptionSelected(taskName, target, isTagOnlyScenes, isBlackList, isGrayList, isPinkList, pathToDelete, sizeToDelete, durationToDelete, resolutionToDelete, ratingToDelete, tagToDelete, titleToDelete, pathStrToDelete, fileNotExistToDelete, compareToLess, compareToGreater):
stash.Log(f"Processing taskName = {taskName}, target = {target}")
if "Blacklist" in taskName:
isBlackList = True
+ if "Graylist" in taskName:
+ isGrayList = True
+ if "Pinklist" in taskName:
+ isPinkList = True
if "Less" in taskName:
compareToLess = True
if "Greater" in taskName:
compareToGreater = True
+ if ":TagOnlyScenes" in target:
+ isTagOnlyScenes = True
+ target = target.replace(":TagOnlyScenes","")
+
if "pathToDelete" in taskName:
pathToDelete = target.lower()
elif "sizeToDelete" in taskName:
@@ -961,10 +1030,15 @@ def getAnAdvanceMenuOptionSelected(taskName, target, isBlackList, pathToDelete,
pathStrToDelete = target.lower()
elif "fileNotExistToDelete" in taskName:
fileNotExistToDelete = True
- return isBlackList, pathToDelete, sizeToDelete, durationToDelete, resolutionToDelete, ratingToDelete, tagToDelete, titleToDelete, pathStrToDelete, fileNotExistToDelete, compareToLess, compareToGreater
+ elif "TagOnlyScenes" in taskName:
+ isTagOnlyScenes = True
+ return isTagOnlyScenes, isBlackList, isGrayList, isPinkList, pathToDelete, sizeToDelete, durationToDelete, resolutionToDelete, ratingToDelete, tagToDelete, titleToDelete, pathStrToDelete, fileNotExistToDelete, compareToLess, compareToGreater
def getAdvanceMenuOptionSelected(advanceMenuOptionSelected):
+ isTagOnlyScenes = False
isBlackList = False
+ isGrayList = False
+ isPinkList = False
pathToDelete = ""
sizeToDelete = -1
durationToDelete = -1
@@ -982,16 +1056,27 @@ def getAdvanceMenuOptionSelected(advanceMenuOptionSelected):
if "applyCombo" in stash.PLUGIN_TASK_NAME:
jsonObject = toJson(stash.JSON_INPUT['args']['Target'])
for taskName in jsonObject:
- isBlackList, pathToDelete, sizeToDelete, durationToDelete, resolutionToDelete, ratingToDelete, tagToDelete, titleToDelete, pathStrToDelete, fileNotExistToDelete, compareToLess, compareToGreater = getAnAdvanceMenuOptionSelected(taskName, jsonObject[taskName], isBlackList, pathToDelete, sizeToDelete, durationToDelete, resolutionToDelete, ratingToDelete, tagToDelete, titleToDelete, pathStrToDelete, compareToLess, compareToGreater)
+ isTagOnlyScenes, isBlackList, isGrayList, isPinkList, pathToDelete, sizeToDelete, durationToDelete, resolutionToDelete, ratingToDelete, tagToDelete, titleToDelete, pathStrToDelete, fileNotExistToDelete, compareToLess, compareToGreater = getAnAdvanceMenuOptionSelected(taskName, jsonObject[taskName], isTagOnlyScenes, isBlackList, isGrayList, isPinkList, pathToDelete, sizeToDelete, durationToDelete, resolutionToDelete, ratingToDelete, tagToDelete, titleToDelete, pathStrToDelete, fileNotExistToDelete, compareToLess, compareToGreater)
else:
- return getAnAdvanceMenuOptionSelected(stash.PLUGIN_TASK_NAME, stash.JSON_INPUT['args']['Target'], isBlackList, pathToDelete, sizeToDelete, durationToDelete, resolutionToDelete, ratingToDelete, tagToDelete, titleToDelete, pathStrToDelete, compareToLess, compareToGreater)
- return isBlackList, pathToDelete, sizeToDelete, durationToDelete, resolutionToDelete, ratingToDelete, tagToDelete, titleToDelete, pathStrToDelete, fileNotExistToDelete, compareToLess, compareToGreater
+ return getAnAdvanceMenuOptionSelected(stash.PLUGIN_TASK_NAME, stash.JSON_INPUT['args']['Target'], isTagOnlyScenes, isBlackList, isGrayList, isPinkList, pathToDelete, sizeToDelete, durationToDelete, resolutionToDelete, ratingToDelete, tagToDelete, titleToDelete, pathStrToDelete, compareToLess, compareToGreater)
+ return isTagOnlyScenes, isBlackList, isGrayList, isPinkList, pathToDelete, sizeToDelete, durationToDelete, resolutionToDelete, ratingToDelete, tagToDelete, titleToDelete, pathStrToDelete, fileNotExistToDelete, compareToLess, compareToGreater
+
+def getScenesFromReport():
+ stash.Log(f"Getting candidates for deletion from file {DuplicateCandidateForDeletionList}.")
+ scenes = []
+ lines = None
+ with open(DuplicateCandidateForDeletionList, 'r') as file:
+ lines = file.readlines()
+ for line in lines:
+ scenes += [json.loads(line)]
+ return scenes
# //////////////////////////////////////////////////////////////////////////////
# //////////////////////////////////////////////////////////////////////////////
def manageTagggedDuplicates(deleteScenes=False, clearTag=False, setGrayListTag=False, tagId=-1, advanceMenuOptionSelected=False):
+ tagName = None
if tagId == -1:
- tagId = findCurrentTagId([duplicateMarkForDeletion, base1_duplicateMarkForDeletion, base2_duplicateMarkForDeletion, 'DuplicateMarkForDeletion', '_DuplicateMarkForDeletion'])
+ tagId, tagName = findCurrentTagId([duplicateMarkForDeletion, base1_duplicateMarkForDeletion, base2_duplicateMarkForDeletion, 'DuplicateMarkForDeletion', '_DuplicateMarkForDeletion'])
if int(tagId) < 0:
stash.Warn(f"Could not find tag ID for tag '{duplicateMarkForDeletion}'.")
return
@@ -1000,7 +1085,7 @@ def manageTagggedDuplicates(deleteScenes=False, clearTag=False, setGrayListTag=F
if clearAllDupfileManagerTags:
excludedTags = [duplicateMarkForDeletion, duplicateWhitelistTag, excludeDupFileDeleteTag, graylistMarkForDeletion, longerDurationLowerResolution]
- isBlackList, pathToDelete, sizeToDelete, durationToDelete, resolutionToDelete, ratingToDelete, tagToDelete, titleToDelete, pathStrToDelete, fileNotExistToDelete, compareToLess, compareToGreater = getAdvanceMenuOptionSelected(advanceMenuOptionSelected)
+ isTagOnlyScenes, isBlackList, isGrayList, isPinkList, pathToDelete, sizeToDelete, durationToDelete, resolutionToDelete, ratingToDelete, tagToDelete, titleToDelete, pathStrToDelete, fileNotExistToDelete, compareToLess, compareToGreater = getAdvanceMenuOptionSelected(advanceMenuOptionSelected)
if advanceMenuOptionSelected and deleteScenes and pathToDelete == "" and tagToDelete == "" and titleToDelete == "" and pathStrToDelete == "" and sizeToDelete == -1 and durationToDelete == -1 and resolutionToDelete == -1 and ratingToDelete == -1 and fileNotExistToDelete == False:
stash.Error("Running advance menu option with no options enabled.")
return
@@ -1012,7 +1097,11 @@ def manageTagggedDuplicates(deleteScenes=False, clearTag=False, setGrayListTag=F
QtyFailedQuery = 0
stash.Debug("#########################################################################")
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 title rating100')
+ if isTagOnlyScenes or advanceMenuOptionSelected == False:
+ stash.Log(f"Getting candidates for deletion by using tag-ID {tagId} and tag-name {tagName}; isTagOnlyScenes={isTagOnlyScenes};advanceMenuOptionSelected={advanceMenuOptionSelected}")
+ 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')
+ else:
+ scenes = getScenesFromReport()
stash.stopSpinningProcessBar()
qtyResults = len(scenes)
stash.Log(f"Found {qtyResults} scenes with tag ({duplicateMarkForDeletion})")
@@ -1063,6 +1152,12 @@ def manageTagggedDuplicates(deleteScenes=False, clearTag=False, setGrayListTag=F
if isBlackList:
if not stash.startsWithInList(blacklist, scene['files'][0]['path']):
continue
+ if isGrayList:
+ if not stash.startsWithInList(graylist, scene['files'][0]['path']):
+ continue
+ if isPinkList:
+ if not stash.startsWithInList(pinklist, 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}.")
@@ -1133,7 +1228,7 @@ def manageTagggedDuplicates(deleteScenes=False, clearTag=False, setGrayListTag=F
if fileNotExistToDelete:
if os.path.isfile(scene['files'][0]['path']):
continue
- stash.Warn(f"Deleting duplicate '{DupFileName}'", toAscii=True, printTo=LOG_STASH_N_PLUGIN)
+ stash.Log(f"Deleting duplicate '{DupFileName}'", toAscii=True, printTo=LOG_STASH_N_PLUGIN)
if alternateTrashCanPath != "":
destPath = f"{alternateTrashCanPath }{os.sep}{DupFileNameOnly}"
if os.path.isfile(destPath):
@@ -1142,6 +1237,7 @@ def manageTagggedDuplicates(deleteScenes=False, clearTag=False, setGrayListTag=F
elif moveToTrashCan:
sendToTrash(DupFileName)
result = stash.destroyScene(scene['id'], delete_file=True)
+ updateDuplicateCandidateForDeletionList(scene['id'], removeScene = True)
QtyDeleted += 1
stash.Debug(f"destroyScene result={result} for file {DupFileName};QtyDeleted={QtyDeleted};Count={QtyDup} of {qtyResults}", toAscii=True)
else:
@@ -1217,6 +1313,7 @@ def mergeTags():
sys.stdout.write("{" + f"mergeTags : 'failed', id1: '{scene1}', id2: '{scene2}'" + "}")
return
stash.mergeMetadata(scene1, scene2)
+ updateScenesInReports(scene2['id'])
stash.Log(f"Done merging scenes for scene {scene1['id']} and scene {scene2['id']}")
sys.stdout.write("{" + f"mergeTags : 'complete', id1: '{scene1['id']}', id2: '{scene2['id']}'" + "}")
@@ -1277,8 +1374,33 @@ def removeAllDupTagsFromAllScenes(deleteTags=False):
else:
stash.Log(f"Clear tags {tagsToClear}")
+def updateDuplicateCandidateForDeletionList(scene, removeScene = False):
+ lines = None
+ if not os.path.isfile(DuplicateCandidateForDeletionList):
+ return
+ with open(DuplicateCandidateForDeletionList, 'r') as file:
+ lines = file.readlines()
+ if removeScene:
+ scene_id = scene
+ else:
+ scene_id = scene['id']
+ foundScene = False
+ with open(DuplicateCandidateForDeletionList, 'w') as file:
+ for line in lines:
+ if foundScene:
+ file.write(line)
+ else:
+ sceneDetails = json.loads(line)
+ if sceneDetails['id'] == scene_id:
+ if not removeScene:
+ file.write(json.dumps(scene) + "\n")
+ foundScene = True
+ else:
+ file.write(line)
+
def updateScenesInReport(fileName, scene):
stash.Log(f"Updating table rows with scene {scene} in file {fileName}")
+ results = False
scene1 = -1
scene2 = -1
strToFind = "class=\"ID_"
@@ -1286,10 +1408,12 @@ def updateScenesInReport(fileName, scene):
with open(fileName, 'r') as file:
lines = file.readlines()
stash.Log(f"line count = {len(lines)}")
+ stash.Log(f"Searching for class=\"ID_{scene}\"")
with open(fileName, 'w') as file:
for line in lines:
# stash.Debug(f"line = {line}")
if f"class=\"ID_{scene}\"" in line:
+ stash.Debug(f"Found class ID_{scene} in line: {line}")
idx = 0
while line.find(strToFind, idx) > -1:
idx = line.find(strToFind, idx) + len(strToFind)
@@ -1304,30 +1428,41 @@ def updateScenesInReport(fileName, scene):
elif scene1 != -1 and scene2 != -1:
break
if scene1 != -1 and scene2 != -1:
- sceneDetail1 = stash.find_scene(scene1)
- sceneDetail2 = stash.find_scene(scene2)
- if sceneDetail1 == None or sceneDetail2 == None:
- stash.Error("Could not get scene details for both scene1 ({scene1}) and scene2 ({scene2}); sceneDetail1={sceneDetail1}; sceneDetail2={sceneDetail2};")
+ sceneDetails1 = stash.find_scene(scene1, fragment=fragmentForSceneDetails + htmlFileData)
+ sceneDetails2 = stash.find_scene(scene2, fragment=fragmentForSceneDetails + htmlFileData)
+ if sceneDetails1 == None or sceneDetails2 == None:
+ stash.Error("Could not get scene details for both scene1 ({scene1}) and scene2 ({scene2}); sceneDetails1={sceneDetails1}; sceneDetails2={sceneDetails2};")
else:
- writeRowToHtmlReport(file, sceneDetail1, sceneDetail2)
+ stash.Log(f"Updating in report {fileName} scene {scene1} and scene {scene2}")
+ writeRowToHtmlReport(file, sceneDetails1, sceneDetails2)
+ if scene == sceneDetails1['id']:
+ results = True
+ updateDuplicateCandidateForDeletionList(sceneDetails1)
else:
stash.Error(f"Could not get both scene ID associated with scene {scene}; scene1 = {scene1}; scene2 = {scene2}")
file.write(line)
else:
file.write(line)
+ if scene1 == -1 or scene2 == -1:
+ stash.Log(f"Did not find both scene ID's associated with scene {scene}; scene1 = {scene1}; scene2 = {scene2}")
+ return results
def updateScenesInReports(scene, ReportName = htmlReportName):
if os.path.isfile(ReportName):
- updateScenesInReport(ReportName, scene)
+ if updateScenesInReport(ReportName, scene):
+ return
for x in range(2, 9999):
fileName = ReportName.replace(".html", f"_{x-1}.html")
stash.Debug(f"Checking if file '{fileName}' exist.")
if not os.path.isfile(fileName):
break
- updateScenesInReport(fileName, scene)
+ if updateScenesInReport(fileName, scene):
+ break
+ stash.Debug("updateScenesInReports complete")
else:
stash.Log(f"Report file does not exist: {ReportName}")
+deleteSceneFlagBgColor = "#646464"
def addPropertyToSceneClass(fileName, scene, property):
stash.Log(f"Inserting property {property} for scene {scene} in file {fileName}")
doStyleEndTagCheck = True
@@ -1339,14 +1474,20 @@ def addPropertyToSceneClass(fileName, scene, property):
for line in lines:
# stash.Debug(f"line = {line}")
if doStyleEndTagCheck:
- if property == "" and line.startswith(f".ID_{scene}" + "{"):
- continue
- if line.startswith(""):
- if property != "":
- styleSetting = f".ID_{scene}{property}\n"
- stash.Log(f"styleSetting = {styleSetting}")
- file.write(styleSetting)
- doStyleEndTagCheck = False
+ if scene == None:
+ if line.startswith(f".ID_") and deleteSceneFlagBgColor not in line:
+ continue
+ elif line.startswith(""):
+ doStyleEndTagCheck = False
+ else:
+ if property == "" and line.startswith(f".ID_{scene}" + "{"):
+ continue
+ if line.startswith(""):
+ if property != "":
+ styleSetting = f".ID_{scene}{property}\n"
+ stash.Log(f"styleSetting = {styleSetting}")
+ file.write(styleSetting)
+ doStyleEndTagCheck = False
file.write(line)
def addPropertyToSceneClassToAllFiles(scene, property, ReportName = htmlReportName):
@@ -1370,10 +1511,16 @@ def deleteScene(disableInReport=True, deleteFile=True):
result = None
result = stash.destroyScene(scene, delete_file=deleteFile)
if disableInReport:
- addPropertyToSceneClassToAllFiles(scene, "{background-color:gray;pointer-events:none;}")
+ addPropertyToSceneClassToAllFiles(scene, "{background-color:" + deleteSceneFlagBgColor + ";pointer-events:none;}")
+ updateDuplicateCandidateForDeletionList(scene, removeScene = True)
stash.Log(f"{stash.PLUGIN_TASK_NAME} complete for scene {scene} with results = {result}")
sys.stdout.write("{" + f"{stash.PLUGIN_TASK_NAME} : 'complete', id: '{scene}', result: '{result}'" + "}")
+def clearAllSceneFlags():
+ addPropertyToSceneClassToAllFiles(None, None)
+ stash.Log(f"{stash.PLUGIN_TASK_NAME} complete for all scenes")
+ sys.stdout.write("{" + f"{stash.PLUGIN_TASK_NAME} : 'complete'" + "}")
+
def copyScene(moveScene=False):
scene1, scene2 = getParseData()
if scene1 == None or scene2 == None:
@@ -1384,6 +1531,7 @@ def copyScene(moveScene=False):
result = shutil.copy(scene1['files'][0]['path'], scene2['files'][0]['path'])
if moveScene:
result = stash.destroyScene(scene1['id'], delete_file=True)
+ updateDuplicateCandidateForDeletionList(scene1['id'], removeScene = True)
stash.Log(f"destroyScene for scene {scene1['id']} results = {result}")
stash.Log(f"{stash.PLUGIN_TASK_NAME} complete for scene {scene1['id']} and {scene2['id']}")
sys.stdout.write("{" + f"{stash.PLUGIN_TASK_NAME} : 'complete', id1: '{scene1['id']}', id2: '{scene2['id']}', result: '{result}'" + "}")
@@ -1482,6 +1630,9 @@ try:
elif stash.PLUGIN_TASK_NAME == "flagScene":
flagScene()
stash.Debug(f"{stash.PLUGIN_TASK_NAME} EXIT")
+ elif stash.PLUGIN_TASK_NAME == "clearAllSceneFlags":
+ clearAllSceneFlags()
+ stash.Debug(f"{stash.PLUGIN_TASK_NAME} EXIT")
elif stash.PLUGIN_TASK_NAME == "copyScene":
copyScene()
stash.Debug(f"{stash.PLUGIN_TASK_NAME} EXIT")
diff --git a/plugins/DupFileManager/DupFileManager.yml b/plugins/DupFileManager/DupFileManager.yml
index a756ebe..e128797 100644
--- a/plugins/DupFileManager/DupFileManager.yml
+++ b/plugins/DupFileManager/DupFileManager.yml
@@ -1,6 +1,6 @@
name: DupFileManager
description: Manages duplicate files.
-version: 0.2.1
+version: 0.2.2
url: https://github.com/David-Maisonave/Axter-Stash/tree/main/plugins/DupFileManager
ui:
javascript:
@@ -34,6 +34,10 @@ settings:
displayName: Black List
description: Least preferential paths; Determine primary deletion candidates. E.g. C:\Downloads,C:\DelMe-3rd,C:\DelMe-2nd,C:\DeleteMeFirst
type: STRING
+ zxPinklist:
+ displayName: Pink List
+ description: An [Advance Duplicate File Deletion Menu] option for deletion using paths. E.g. C:\SomeRandomDir,E:\DelPath2
+ type: STRING
zyMaxDupToProcess:
displayName: Max Dup Process
description: (Default=0) Maximum number of duplicates to process. If 0, infinity.
diff --git a/plugins/DupFileManager/DupFileManager_report_config.py b/plugins/DupFileManager/DupFileManager_report_config.py
index 51a2cbb..2673d6c 100644
--- a/plugins/DupFileManager/DupFileManager_report_config.py
+++ b/plugins/DupFileManager/DupFileManager_report_config.py
@@ -64,8 +64,93 @@ li:hover .large {
border-radius: 4px;
box-shadow: 1px 1px 3px 3px rgba(127, 127, 127, 0.15);;
}
+/******** Dropdown buttons *********/
+.dropdown .dropdown_tag .dropdown_performer .dropdown_gallery .dropbtn {
+ font-size: 14px;
+ border: none;
+ outline: none;
+ color: white;
+ padding: 6px 10px;
+ background-color: transparent;
+ font-family: inherit; /* Important for vertical align on mobile phones */
+ margin: 0; /* Important for vertical align on mobile phones */
+}
+.dropdown-content{
+ display: none;
+ position: absolute;
+ background-color: inherit;
+ min-width: 80px;
+ box-shadow: 0px 4px 12px 0px rgba(0,0,0,0.2);
+ z-index: 1;
+}
+.dropdown-content a {
+ float: none;
+ color: black;
+ padding: 6px 10px;
+ text-decoration: none;
+ display: block;
+ text-align: left;
+}
+.dropdown:hover .dropdown-content {
+ display: block;
+}
+/*************-- Dropdown Icons --*************/
+.dropdown_icon {
+ height:22px;
+ width:30px;
+ float:left;
+}
+/*** Dropdown Tag ***/
+.dropdown_tag-content{
+ display: none;
+ position: absolute;
+ background-color: LightCoral;
+ min-width: 80px;
+ box-shadow: 0px 4px 12px 0px rgba(0,0,0,0.2);
+ z-index: 1;
+}
+.dropdown_icon:hover .dropdown_tag-content {
+ display: block;
+}
+/*** Dropdown Performer ***/
+.dropdown_performer-content{
+ display: none;
+ position: absolute;
+ background-color: LightBlue;
+ min-width: 80px;
+ box-shadow: 0px 4px 12px 0px rgba(0,0,0,0.2);
+ z-index: 1;
+}
+.dropdown_icon:hover .dropdown_performer-content {
+ display: block;
+}
+/*** Dropdown Gallery ***/
+.dropdown_gallery-content{
+ display: none;
+ position: absolute;
+ background-color: AntiqueWhite;
+ min-width: 80px;
+ box-shadow: 0px 4px 12px 0px rgba(0,0,0,0.2);
+ z-index: 1;
+}
+.dropdown_icon:hover .dropdown_gallery-content {
+ display: block;
+}
+/*** Dropdown Group ***/
+.dropdown_group-content{
+ display: none;
+ position: absolute;
+ background-color: BurlyWood;
+ min-width: 80px;
+ box-shadow: 0px 4px 12px 0px rgba(0,0,0,0.2);
+ z-index: 1;
+}
+.dropdown_icon:hover .dropdown_group-content {
+ display: block;
+}
-
+
+
+
+