forked from Github/Axter-Stash
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.
This commit is contained in:
@@ -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 = '<div class="dropdown_icon"><img src="https://www.axter.com/images/stash/tag.png" alt="Tags" style="width:20px;height:20px;"><i class="fa fa-caret-down"></i><div class="dropdown_tag-content">'
|
||||
htmlPerformerPrefix = '<div class="dropdown_icon"><img src="https://www.axter.com/images/stash/performer.png" alt="Performers" title="Performers" style="width:20px;height:20px;"><i class="fa fa-caret-down"></i><div class="dropdown_performer-content">'
|
||||
htmlGalleryPrefix = '<div class="dropdown_icon"><img src="https://www.axter.com/images/stash/galleries.png" alt="Galleries" title="Galleries" style="width:20px;height:20px;"><i class="fa fa-caret-down"></i><div class="dropdown_gallery-content">'
|
||||
htmlGroupPrefix = '<div class="dropdown_icon"><img src="https://www.axter.com/images/stash/group.png" alt="Groups" title="Groups" style="width:20px;height:20px;"><i class="fa fa-caret-down"></i><div class="dropdown_group-content">'
|
||||
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"<a class=\"link-items\" title=\"Play file locally\" href=\"file://{getPath(DupFile)}\">[Play]</a>")
|
||||
else:
|
||||
fileHtmlReport.write("<b style='color:red;'>[File NOT Exist]<b>")
|
||||
if len(DupFile['tags']) > 0:
|
||||
fileHtmlReport.write(htmlTagPrefix)
|
||||
for tag in DupFile['tags']:
|
||||
# if not tag['ignore_auto_tag']:
|
||||
fileHtmlReport.write(f"<div style='color:black;font-size: 12px;'>{tag['name']}</div>")
|
||||
fileHtmlReport.write("</div></div>")
|
||||
if len(DupFile['performers']) > 0:
|
||||
fileHtmlReport.write(htmlPerformerPrefix)
|
||||
for performer in DupFile['performers']:
|
||||
fileHtmlReport.write(f"<div style='color:black;font-size: 12px;'>{performer['name']}</div>")
|
||||
fileHtmlReport.write("</div></div>")
|
||||
if len(DupFile['galleries']) > 0:
|
||||
fileHtmlReport.write(htmlGalleryPrefix)
|
||||
for gallery in DupFile['galleries']:
|
||||
gallery = stash.find_gallery(gallery['id'])
|
||||
fileHtmlReport.write(f"<div style='color:black;font-size: 12px;'>{gallery['title']}</div>")
|
||||
fileHtmlReport.write("</div></div>")
|
||||
if len(DupFile['groups']) > 0:
|
||||
fileHtmlReport.write(htmlGroupPrefix)
|
||||
for group in DupFile['groups']:
|
||||
fileHtmlReport.write(f"<div style='color:black;font-size: 12px;'>{group['group']['name']}</div>")
|
||||
fileHtmlReport.write("</div></div>")
|
||||
|
||||
fileHtmlReport.write("</p></td>")
|
||||
|
||||
videoPreview = f"<video {htmlReportVideoPreview} poster=\"{DupFileToKeep['paths']['screenshot']}\"><source src=\"{DupFileToKeep['paths'][htmlPreviewOrStream]}\" type=\"video/mp4\"></video>"
|
||||
@@ -633,14 +664,41 @@ def writeRowToHtmlReport(fileHtmlReport, DupFile, DupFileToKeep, QtyTagForDel =
|
||||
else:
|
||||
fileHtmlReport.write("<b style='color:red;'>[File NOT Exist]<b>")
|
||||
fileHtmlReport.write(f"<button class=\"link-button\" title=\"Flag scene as reviewed or as awaiting review.\" value=\"flagScene\" id=\"{DupFileToKeep['id']}\">[Flag]</button>")
|
||||
if len(DupFileToKeep['tags']) > 0:
|
||||
fileHtmlReport.write(htmlTagPrefix)
|
||||
for tag in DupFileToKeep['tags']:
|
||||
# if not tag['ignore_auto_tag']:
|
||||
fileHtmlReport.write(f"<div style='color:black;font-size: 12px;'>{tag['name']}</div>")
|
||||
fileHtmlReport.write("</div></div>")
|
||||
if len(DupFileToKeep['performers']) > 0:
|
||||
fileHtmlReport.write(htmlPerformerPrefix)
|
||||
for performer in DupFileToKeep['performers']:
|
||||
fileHtmlReport.write(f"<div style='color:black;font-size: 12px;'>{performer['name']}</div>")
|
||||
fileHtmlReport.write("</div></div>")
|
||||
if len(DupFileToKeep['galleries']) > 0:
|
||||
fileHtmlReport.write(htmlGalleryPrefix)
|
||||
for gallery in DupFileToKeep['galleries']:
|
||||
gallery = stash.find_gallery(gallery['id'])
|
||||
fileHtmlReport.write(f"<div style='color:black;font-size: 12px;'>{gallery['title']}</div>")
|
||||
fileHtmlReport.write("</div></div>")
|
||||
if len(DupFileToKeep['groups']) > 0:
|
||||
fileHtmlReport.write(htmlGroupPrefix)
|
||||
for group in DupFileToKeep['groups']:
|
||||
fileHtmlReport.write(f"<div style='color:black;font-size: 12px;'>{group['group']['name']}</div>")
|
||||
fileHtmlReport.write("</div></div>")
|
||||
# ToDo: Add following buttons:
|
||||
# rename file
|
||||
fileHtmlReport.write(f"</p></td>")
|
||||
|
||||
fileHtmlReport.write("</tr>\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):
|
||||
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</th>{htmlReportTableHeader}Duplicate to Delete</th>{htmlReportTableHeader}Scene-ToKeep</th>{htmlReportTableHeader}Duplicate to Keep</th></tr>\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("</table>\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("</table>\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()
|
||||
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,6 +1474,12 @@ def addPropertyToSceneClass(fileName, scene, property):
|
||||
for line in lines:
|
||||
# stash.Debug(f"line = {line}")
|
||||
if doStyleEndTagCheck:
|
||||
if scene == None:
|
||||
if line.startswith(f".ID_") and deleteSceneFlagBgColor not in line:
|
||||
continue
|
||||
elif line.startswith("</style>"):
|
||||
doStyleEndTagCheck = False
|
||||
else:
|
||||
if property == "" and line.startswith(f".ID_{scene}" + "{"):
|
||||
continue
|
||||
if line.startswith("</style>"):
|
||||
@@ -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")
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
</style>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||
<script src="https://www.axter.com/js/jquery-3.7.1.min.js"></script>
|
||||
<script src="https://www.axter.com/js/jquery.prompt.js"></script>
|
||||
<link rel="stylesheet" href="https://www.axter.com/js/jquery.prompt.css"/>
|
||||
<script>
|
||||
@@ -111,11 +196,8 @@ function RunPluginOperation(Mode, ActionID, button, asyncAjax){
|
||||
variables: {"plugin_id": "DupFileManager", "args": { "Target" : ActionID, "mode":Mode}},
|
||||
}), success: function(result){
|
||||
console.log(result);
|
||||
// if (Mode !== "flagScene") button.style.visibility = 'hidden';
|
||||
if (Mode === "renameFile"){
|
||||
var myArray = ActionID.split(":");
|
||||
$('.FN_ID_' + myArray[0]).text(trim(myArray[1],"'"));
|
||||
}
|
||||
if (Mode === "renameFile" || Mode === "clearAllSceneFlags" || Mode === "mergeTags")
|
||||
location.replace(location.href);
|
||||
if (!chkBxRemoveValid.checked) alert("Action " + Mode + " for scene(s) ID# " + ActionID + " complete.");
|
||||
}, error: function(XMLHttpRequest, textStatus, errorThrown) {
|
||||
console.log("Ajax failed with Status: " + textStatus + "; Error: " + errorThrown);
|
||||
@@ -191,7 +273,7 @@ $(document).ready(function(){
|
||||
$("button").click(function(){
|
||||
var Mode = this.value;
|
||||
var ActionID = this.id;
|
||||
if (ActionID === "AdvanceMenu")
|
||||
if (ActionID === "AdvanceMenu" || ActionID === "AdvanceMenu_")
|
||||
{
|
||||
var newUrl = window.location.href;
|
||||
newUrl = newUrl.replace(/report\/DuplicateTagScenes[_0-9]*.html/g, "advance_options.html?GQL=" + GraphQl_URL + "&apiKey=" + apiKey);
|
||||
@@ -245,7 +327,16 @@ $(document).ready(function(){
|
||||
<td><table><tr>
|
||||
<td><input type="checkbox" id="RemoveValidatePrompt" name="RemoveValidatePrompt"><label for="RemoveValidatePrompt" title="Disable notice for task completion (Popup).">Disable Complete 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>
|
||||
<td>
|
||||
<div class="dropdown">
|
||||
<button id="AdvanceMenu" title="View advance menu for tagged duplicates." name="AdvanceMenu">Advance Tag Menu <i class="fa fa-caret-down"></i></button>
|
||||
<div class="dropdown-content">
|
||||
<div><button type="button" id="clearAllSceneFlags" value="clearAllSceneFlags" title="Remove flags from report for all scenes, except for deletion flag.">Clear All Scene Flags</button></div>
|
||||
<div><button type="button" id="clear_duplicate_tags_task" value="clear_duplicate_tags_task" title="Remove duplicate (_DuplicateMarkForDeletion_?) tag from all scenes. This action make take a few minutes to complete.">Remove All Scenes Tags</button></div>
|
||||
<div><button type="button" id="fileNotExistToDelete" value="fileNotExistToDelete" title="Delete tagged duplicates for which file does NOT exist.">Delete Files That do Not Exist</button></div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr></table></td>
|
||||
</tr></table></center>
|
||||
<h2>Stash Duplicate Scenes Report (MatchTypePlaceHolder)</h2>\n""",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# DupFileManager: Ver 0.2.1 (By David Maisonave)
|
||||
# DupFileManager: Ver 0.2.2 (By David Maisonave)
|
||||
|
||||
DupFileManager is a [Stash](https://github.com/stashapp/stash) plugin which manages duplicate files in the Stash system.
|
||||
It has both **task** and **tools-UI** components.
|
||||
@@ -102,10 +102,9 @@ That's it!!!
|
||||
- 
|
||||
|
||||
### Future Planned Features
|
||||
- Add an advanced menu that will work with non-tagged reports. It will iterated through the existing report file(s) to aplly deletions, instead of searching Stash DB for tagged files. Planned for 1.1.0 Version.
|
||||
- Greylist deletion option will be added to the advanced menu. Planned for 1.0.5 Version.
|
||||
- Consolidate buttons in report into dropdown buttons. Planned for 1.0.1 Version.
|
||||
- Add logic to merge performers and galaries seperatly from tag merging on report. Planned for 1.0.5 Version.
|
||||
- Add logic to merge group metadata when selecting merge option on report. Planned for 1.1.0 Version.
|
||||
- Add advanced menu directly to the Settings->Tools menu. Planned for 1.5.0 Version.
|
||||
- Add report directly to the Settings->Tools menu. Planned for 1.5.0 Version.
|
||||
- Remove all flags from all scenes option. Planned for 1.0.5 Version.
|
||||
|
||||
|
||||
|
||||
@@ -29,8 +29,47 @@ table, th, td {border:1px solid black;}
|
||||
color:red;
|
||||
}
|
||||
html.wait, html.wait * { cursor: wait !important; }
|
||||
/* The dropdown container */
|
||||
.dropdown {
|
||||
float: left;
|
||||
overflow: hidden;
|
||||
}
|
||||
/******** Dropdown button *********/
|
||||
.dropdown .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 (hidden by default) */
|
||||
.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;
|
||||
}
|
||||
/* Links inside the dropdown */
|
||||
.dropdown-content a {
|
||||
float: none;
|
||||
color: black;
|
||||
padding: 6px 10px;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
text-align: left;
|
||||
}
|
||||
/* Show the dropdown menu on hover */
|
||||
.dropdown:hover .dropdown-content {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||
<script src="https://www.axter.com/js/jquery-3.7.1.min.js"></script>
|
||||
<script>
|
||||
var apiKey = ""; // For Stash installations with a password setup, populate this variable with the apiKey found in Stash->Settings->Security->[API Key]; ----- Or pass in the apiKey at the URL command line. Example: advance_options.html?apiKey=12345G4igiJdgssdgiwqInh5cCI6IkprewJ9hgdsfhgfdhd&GQL=http://localhost:9999/graphql
|
||||
var GraphQl_URL = "http://localhost:9999/graphql";// For Stash installations with non-standard ports or URL's, populate this variable with actual URL; ----- Or pass in the URL at the command line using GQL param. Example: advance_options.html?GQL=http://localhost:9900/graphql
|
||||
@@ -41,11 +80,15 @@ if (urlParams.get('GQL') != null && urlParams.get('GQL') !== "")
|
||||
GraphQl_URL = urlParams.get('GQL');
|
||||
console.log(urlParams);
|
||||
console.log("GQL = " + GraphQl_URL);
|
||||
console.log("apiKey = " + apiKey);
|
||||
|
||||
function RunPluginDupFileManager(Mode, Param = 0, Async = false) {
|
||||
function RunPluginDupFileManager(Mode, Param = 0, Async = false, TagOnlyScenes = false) {
|
||||
$('html').addClass('wait');
|
||||
$("body").css("cursor", "progress");
|
||||
if (TagOnlyScenes)
|
||||
Param += ":TagOnlyScenes";
|
||||
console.log("GraphQl_URL = " + GraphQl_URL + "; Mode = " + Mode + "; Param = " + Param);
|
||||
//if ($("#InPathCheck").prop('checked'))
|
||||
if (apiKey !== "")
|
||||
$.ajaxSetup({beforeSend: function(xhr) {xhr.setRequestHeader('apiKey', apiKey);}});
|
||||
$.ajax({method: "POST", url: GraphQl_URL, contentType: "application/json", dataType: "text", cache: Async, async: Async,
|
||||
@@ -63,12 +106,15 @@ function RunPluginDupFileManager(Mode, Param = 0, Async = false) {
|
||||
console.log("Setting default cursor");
|
||||
}
|
||||
$(document).ready(function(){
|
||||
|
||||
$("button").click(function(){
|
||||
const AddedWarn = "? This will delete the files, and remove them from stash.";
|
||||
console.log(this.id);
|
||||
var blackliststr = "";
|
||||
var comparestr = "less than ";
|
||||
if (this.id.includes("Blacklist")) blackliststr = "in blacklist ";
|
||||
if (this.id.includes("Graylist")) blackliststr = "in graylist ";
|
||||
if (this.id.includes("Pinklist")) blackliststr = "in pinklist ";
|
||||
if (this.id.includes("Greater")) comparestr = "greater than ";
|
||||
else if (this.id.includes("Eq")) comparestr = "equal to ";
|
||||
|
||||
@@ -94,27 +140,27 @@ $(document).ready(function(){
|
||||
else if (this.id === "pathToDelete" || this.id === "pathToDeleteBlacklist")
|
||||
{
|
||||
if (confirm("Are you sure you want to delete tag scenes " + blackliststr + "having _DuplicateMarkForDeletion tags, and in path " + $("#pathToDeleteText").val() + AddedWarn))
|
||||
RunPluginDupFileManager(this.id, $("#pathToDeleteText").val());
|
||||
RunPluginDupFileManager(this.id, $("#pathToDeleteText").val(), true, $("#DupTagOnlyCheck").prop('checked'));
|
||||
}
|
||||
else if (this.id === "sizeToDeleteLess" || this.id === "sizeToDeleteGreater" || this.id === "sizeToDeleteBlacklistLess" || this.id === "sizeToDeleteBlacklistGreater")
|
||||
{
|
||||
if (confirm("Are you sure you want to delete duplicate tag scenes " + blackliststr + "having file size " + comparestr + $("#sizeToDelete").val() + AddedWarn))
|
||||
RunPluginDupFileManager(this.id, $("#sizeToDelete").val());
|
||||
RunPluginDupFileManager(this.id, $("#sizeToDelete").val(), true, $("#DupTagOnlyCheck").prop('checked'));
|
||||
}
|
||||
else if (this.id === "durationToDeleteLess" || this.id === "durationToDeleteGreater" || this.id === "durationToDeleteBlacklistLess" || this.id === "durationToDeleteBlacklistGreater")
|
||||
{
|
||||
if (confirm("Are you sure you want to delete duplicate tag scenes " + blackliststr + "having file duration " + comparestr + $("#durationToDelete").val() + AddedWarn))
|
||||
RunPluginDupFileManager(this.id, $("#durationToDelete").val());
|
||||
RunPluginDupFileManager(this.id, $("#durationToDelete").val(), true, $("#DupTagOnlyCheck").prop('checked'));
|
||||
}
|
||||
else if (this.id === "commonResToDeleteLess" || this.id === "commonResToDeleteEq" || this.id === "commonResToDeleteGreater" || this.id === "commonResToDeleteBlacklistLess" || this.id === "commonResToDeleteBlacklistEq" || this.id === "commonResToDeleteBlacklistGreater")
|
||||
{
|
||||
if (confirm("Are you sure you want to delete duplicate tag scenes " + blackliststr + "having resolution " + comparestr + $("#commonResToDelete").val() + AddedWarn))
|
||||
RunPluginDupFileManager(this.id, $("#commonResToDelete").val());
|
||||
RunPluginDupFileManager(this.id, $("#commonResToDelete").val(), true, $("#DupTagOnlyCheck").prop('checked'));
|
||||
}
|
||||
else if (this.id === "resolutionToDeleteLess" || this.id === "resolutionToDeleteEq" || this.id === "resolutionToDeleteGreater" || this.id === "resolutionToDeleteBlacklistLess" || this.id === "resolutionToDeleteBlacklistEq" || this.id === "resolutionToDeleteBlacklistGreater")
|
||||
{
|
||||
if (confirm("Are you sure you want to delete duplicate tag scenes " + blackliststr + "having resolution " + comparestr + $("#resolutionToDelete").val() + AddedWarn))
|
||||
RunPluginDupFileManager(this.id, $("#resolutionToDelete").val());
|
||||
RunPluginDupFileManager(this.id, $("#resolutionToDelete").val(), true, $("#DupTagOnlyCheck").prop('checked'));
|
||||
}
|
||||
else if (this.id === "ratingToDeleteLess" || this.id === "ratingToDeleteEq" || this.id === "ratingToDeleteGreater" || this.id === "ratingToDeleteBlacklistLess" || this.id === "ratingToDeleteBlacklistEq" || this.id === "ratingToDeleteBlacklistGreater")
|
||||
{
|
||||
@@ -126,33 +172,37 @@ $(document).ready(function(){
|
||||
else
|
||||
result = confirm("Are you sure you want to delete duplicate tag scenes " + blackliststr + "having rating " + comparestr + $("#ratingToDelete").val() + AddedWarn);
|
||||
if (result)
|
||||
RunPluginDupFileManager(this.id, $("#ratingToDelete").val());
|
||||
RunPluginDupFileManager(this.id, $("#ratingToDelete").val(), true, $("#DupTagOnlyCheck").prop('checked'));
|
||||
}
|
||||
else if (this.id === "tagToDelete" || this.id === "tagToDeleteBlacklist")
|
||||
{
|
||||
if (confirm("Are you sure you want to delete tag scenes " + blackliststr + "having _DuplicateMarkForDeletion tags, and having tag " + $("#tagToDeleteText").val() + AddedWarn))
|
||||
RunPluginDupFileManager(this.id, $("#tagToDeleteText").val());
|
||||
RunPluginDupFileManager(this.id, $("#tagToDeleteText").val(), true, $("#DupTagOnlyCheck").prop('checked'));
|
||||
}
|
||||
else if (this.id === "titleToDelete" || this.id === "titleToDeleteBlacklist")
|
||||
{
|
||||
if (confirm("Are you sure you want to delete tag scenes " + blackliststr + "having _DuplicateMarkForDeletion tags, and having title containing " + $("#titleToDeleteText").val() + AddedWarn))
|
||||
RunPluginDupFileManager(this.id, $("#titleToDeleteText").val());
|
||||
RunPluginDupFileManager(this.id, $("#titleToDeleteText").val(), true, $("#DupTagOnlyCheck").prop('checked'));
|
||||
}
|
||||
else if (this.id === "pathStrToDelete" || this.id === "pathStrToDeleteBlacklist")
|
||||
{
|
||||
if (confirm("Are you sure you want to delete tag scenes " + blackliststr + "having _DuplicateMarkForDeletion tags, and having path containing " + $("#pathStrToDeleteText").val() + AddedWarn))
|
||||
RunPluginDupFileManager(this.id, $("#pathStrToDeleteText").val());
|
||||
RunPluginDupFileManager(this.id, $("#pathStrToDeleteText").val(), true, $("#DupTagOnlyCheck").prop('checked'));
|
||||
}
|
||||
else if (this.id === "fileNotExistToDelete" || this.id === "fileNotExistToDeleteBlacklist")
|
||||
{
|
||||
if (confirm("Are you sure you want to delete tag scenes " + blackliststr + "having _DuplicateMarkForDeletion tags, and that do NOT exist in the file system?"))
|
||||
RunPluginDupFileManager(this.id, true);
|
||||
RunPluginDupFileManager(this.id, true, true, $("#DupTagOnlyCheck").prop('checked'));
|
||||
}
|
||||
else if (this.id === "applyCombo" || this.id === "applyComboBlacklist")
|
||||
else if (this.id === "applyCombo" || this.id === "applyCombo_" || this.id === "applyComboBlacklist" || this.id === "applyComboGraylist" || this.id === "applyComboPinklist")
|
||||
{
|
||||
var Blacklist = "";
|
||||
if (this.id === "applyComboBlacklist")
|
||||
Blacklist = "Blacklist";
|
||||
else if (this.id === "applyComboGraylist")
|
||||
Blacklist = "Graylist";
|
||||
else if (this.id === "applyComboPinklist")
|
||||
Blacklist = "Pinklist";
|
||||
var Param = "{";
|
||||
if ($("#InPathCheck").prop('checked'))
|
||||
Param += "\"" + "pathToDelete" + Blacklist + "\":\"" + $("#pathToDeleteText").val().replace("\\", "\\\\") + "\", ";
|
||||
@@ -181,6 +231,8 @@ $(document).ready(function(){
|
||||
Param += "\"" + "pathStrToDelete" + Blacklist + "\":\"" + $("#pathStrToDeleteText").val().replace("\\", "\\\\") + "\", ";
|
||||
if ($("#fileNotExistCheck").prop('checked'))
|
||||
Param += "\"" + "fileNotExistToDelete" + Blacklist + "\":\"true\", ";
|
||||
if ($("#DupTagOnlyCheck_MultiOption").prop('checked'))
|
||||
Param += "\"" + "TagOnlyScenes" + Blacklist + "\":\"true\", ";
|
||||
Param += '}';
|
||||
Param = Param.replace(', }', '}');
|
||||
if (Param === "{}")
|
||||
@@ -190,7 +242,7 @@ $(document).ready(function(){
|
||||
}
|
||||
console.log(Param);
|
||||
if (confirm("Are you sure you want to delete tag scenes " + blackliststr + "having _DuplicateMarkForDeletion tags, and having the selected options" + AddedWarn + "\nSelected options:\n" + Param))
|
||||
RunPluginDupFileManager(this.id, Param);
|
||||
RunPluginDupFileManager(this.id, Param, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -201,16 +253,50 @@ function DeleteDupInPath(){
|
||||
</head>
|
||||
<body>
|
||||
<center><table style="color:darkgreen;background-color:powderblue;">
|
||||
<tr><th>DupFileManager Advance <b style="color:red;">_DuplicateMarkForDeletion_?</b> Tagged Files Menu</th><th>Apply Multiple Options</th></tr>
|
||||
<tr><th><div><b style="color:red;"><i>DupFileManager</i></b></div>Advance Duplicate File Deletion Menu</th><th>Apply Multiple Options</th></tr>
|
||||
<tr>
|
||||
<td><center>
|
||||
<button type="button" id="tag_duplicates_task" value="-1" title="Create new report which tags duplicates with tag name _DuplicateMarkForDeletion using user settings for [Match Duplicate Distance].">Create Duplicate Report with Tagging</button>
|
||||
<button type="button" id="viewreport" title="View duplicate file report.">View Dup Report</button>
|
||||
</center></td>
|
||||
<td>
|
||||
<button type="button" id="applyCombo" title="Apply selected multiple options to delete scenes.">Delete</button>
|
||||
<button type="button" id="applyComboBlacklist" title="Apply selected multiple options to delete scenes in blacklist.">Delete-Blacklist</button>
|
||||
<table style="border-collapse: collapse; border: none;"><tr style="border: none;">
|
||||
<tr><th colspan="3" style="font-size: 12px;border: none;">Create report overriding user [Match Duplicate Distance] and [significantTimeDiff] settings</th></tr>
|
||||
<td style="border: none;"><div class="dropdown">
|
||||
<button type="button" id="create_duplicate_report_task" value="-1" title="Create new report WITHOUT tags using user settings for [Match Duplicate Distance].">Create Duplicate Report <i class="fa fa-caret-down"></i></button>
|
||||
<div class="dropdown-content">
|
||||
<div><button type="button" id="create_duplicate_report_task0a" value="0" title="Create report using [Match Duplicate Distance]=0 (Exact Match). NO tagging.">Create Duplicate Report [Exact Match]</button></div>
|
||||
<div><button type="button" id="create_duplicate_report_task1a" value="1" title="Create report using [Match Duplicate Distance]=1 (High Match). NO tagging.">Create Duplicate Report [High Match]</button></div>
|
||||
<div><button type="button" id="create_duplicate_report_task2a" value="2" title="Create report using [Match Duplicate Distance]=2 (Medium Match). NO tagging.">Create Duplicate Report [Medium Match]</button></div>
|
||||
<div><button type="button" id="create_duplicate_report_task3a" value="3" title="Create report using [Match Duplicate Distance]=3 (Low Match). NO tagging.">Create Duplicate Report [Low Match]</button></div>
|
||||
<div style="height:2px;width:220px;border-width:0;color:gray;background-color:gray;">_</div>
|
||||
<div><button type="button" id="tag_duplicates_task" value="-1" title="Create new report which tags duplicates with tag name _DuplicateMarkForDeletion using user settings for [Match Duplicate Distance].">Create Duplicate Report with Tagging (With Default Match Setting)</button></div>
|
||||
<div><button type="button" id="tag_duplicates_task0a" value="0" title="Create report which tags duplicates with tag name _DuplicateMarkForDeletion_0 and using [Match Duplicate Distance]=0 (Exact Match).">Create Duplicate Tagging Report [Exact Match]</button></div>
|
||||
<div><button type="button" id="tag_duplicates_task1a" value="1" title="Create report which tags duplicates with tag name _DuplicateMarkForDeletion_1 and using [Match Duplicate Distance]=1 (High Match).">Create Duplicate Tagging Report [High Match]</button></div>
|
||||
<div><button type="button" id="tag_duplicates_task2a" value="2" title="Create report which tags duplicates with tag name _DuplicateMarkForDeletion_2 and using [Match Duplicate Distance]=2 (Medium Match).">Create Duplicate Tagging Report [Medium Match]</button></div>
|
||||
<div><button type="button" id="tag_duplicates_task3a" value="3" title="Create report which tags duplicates with tag name _DuplicateMarkForDeletion_3 and using [Match Duplicate Distance]=3 (Low Match).">Create Duplicate Tagging Report [Low Match]</button></div>
|
||||
</div>
|
||||
</div></td>
|
||||
<td style="border: none;padding: 0 15px;">
|
||||
<label for="significantTimeDiff" title="Significant time difference setting, where 1 equals 100% and (.9) equals 90%.">Time Difference%:</label>
|
||||
<input type="number" min="0.25" max="1.00" step="0.01" id="significantTimeDiff" name="significantTimeDiff" title="Significant time difference setting, where 1 equals 100% and (.9) equals 90%." value="0.90">
|
||||
</td>
|
||||
<td style="border: none;padding: 0 15px;">
|
||||
<button type="button" id="viewreport" title="View duplicate file report.">View Dup Report</button>
|
||||
</td>
|
||||
</tr></table>
|
||||
</td>
|
||||
<td>
|
||||
<div class="dropdown">
|
||||
<center>Multi-Options: <button id="applyCombo" title="Apply selected multiple options to delete scenes.">Delete <i class="fa fa-caret-down"></i></button></center>
|
||||
<div class="dropdown-content">
|
||||
<div><button type="button" id="applyCombo_" title="Apply selected multiple options to delete scenes.">Delete All Candidates</button></div>
|
||||
<div><button type="button" id="applyComboBlacklist" title="Apply selected multiple options to delete scenes in blacklist.">Delete-Blacklist</button></div>
|
||||
<div><button type="button" id="applyComboGraylist" title="Apply selected multiple options to delete scenes in graylist.">Delete-Graylist</button></div>
|
||||
<div><button type="button" id="applyComboPinklist" title="Apply selected multiple options to delete scenes in pinklist.">Delete-Pinklist</button></div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label title="When enabled, operations only apply to scenes which have the special tag _DuplicateMarkForDeletion_?" for="DupTagOnlyCheck">Apply action only to scenes with <b>_DuplicateMarkForDeletion_?</b> tag:</label><input title="When enabled, operations only apply to scenes which have the special tag _DuplicateMarkForDeletion_?" type="checkbox" id="DupTagOnlyCheck" name="DupTagOnlyCheck" value="true"></td>
|
||||
<td><label title="When enabled, Multi-Options operations only apply to scenes which have the special tag _DuplicateMarkForDeletion_?" for="DupTagOnlyCheck_MultiOption">Dup Tag:</label><input title="When enabled, Multi-Options operations only apply to scenes which have the special tag _DuplicateMarkForDeletion_?" type="checkbox" id="DupTagOnlyCheck_MultiOption" name="DupTagOnlyCheck" value="true"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><form id="pathToDeleteForm" action="javascript:DeleteDupInPath();" target="_self">
|
||||
@@ -1798,50 +1884,6 @@ function DeleteDupInPath(){
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table></center>
|
||||
<div id="div1"></div>
|
||||
<br>
|
||||
|
||||
<center><table style="color:darkgreen;background-color:powderblue;">
|
||||
<tr><th colspan="2">Create report with different <b style="color:red;">[Match Duplicate Distance]</b> options
|
||||
<br><div style="font-size: 12px;">Overrides user [Match Duplicate Distance] and [significantTimeDiff] settings</div>
|
||||
<form id="significantTimeDiffForm" action="javascript:DeleteDupInPath();" target="_self">
|
||||
<label for="significantTimeDiff" title="Significant time difference setting, where 1 equals 100% and (.9) equals 90%.">Time Difference%:</label>
|
||||
<input type="number" min="0.25" max="1.00" step="0.01" id="significantTimeDiff" name="significantTimeDiff" title="Significant time difference setting, where 1 equals 100% and (.9) equals 90%." value="0.90">
|
||||
</form>
|
||||
</th></tr>
|
||||
<tr>
|
||||
<td><table style="color:darkgreen;background-color:powderblue;">
|
||||
<tr><th title="Create report with tagging (_DuplicateMarkForDeletion_)">Create Report with Tagging</th></tr>
|
||||
<tr><td><center>
|
||||
<button type="button" id="tag_duplicates_task0" value="0" title="Create report which tags duplicates with tag name _DuplicateMarkForDeletion_0 and using [Match Duplicate Distance]=0 (Exact Match).">Create Duplicate Tagging Report [Exact Match]</button>
|
||||
</center></td></tr>
|
||||
<tr><td><center>
|
||||
<button type="button" id="tag_duplicates_task1" value="1" title="Create report which tags duplicates with tag name _DuplicateMarkForDeletion_1 and using [Match Duplicate Distance]=1 (High Match).">Create Duplicate Tagging Report [High Match]</button>
|
||||
</center></td></tr>
|
||||
<tr><td><center>
|
||||
<button type="button" id="tag_duplicates_task2" value="2" title="Create report which tags duplicates with tag name _DuplicateMarkForDeletion_2 and using [Match Duplicate Distance]=2 (Medium Match).">Create Duplicate Tagging Report [Medium Match]</button>
|
||||
</center></td></tr>
|
||||
<tr><td><center>
|
||||
<button type="button" id="tag_duplicates_task3" value="3" title="Create report which tags duplicates with tag name _DuplicateMarkForDeletion_3 and using [Match Duplicate Distance]=3 (Low Match).">Create Duplicate Tagging Report [Low Match]</button>
|
||||
</center></td></tr>
|
||||
</table></td>
|
||||
<td><table style="color:darkgreen;background-color:powderblue;">
|
||||
<tr><th title="Create report with NO tagging (NO Dup Tag)">Create Report without Tagging</th></tr>
|
||||
<tr><td><center>
|
||||
<button type="button" id="create_duplicate_report_task0" value="0" title="Create report using [Match Duplicate Distance]=0 (Exact Match). NO tagging.">Create Duplicate Report [Exact Match]</button>
|
||||
</center></td></tr>
|
||||
<tr><td><center>
|
||||
<button type="button" id="create_duplicate_report_task1" value="1" title="Create report using [Match Duplicate Distance]=1 (High Match). NO tagging.">Create Duplicate Report [High Match]</button>
|
||||
</center></td></tr>
|
||||
<tr><td><center>
|
||||
<button type="button" id="create_duplicate_report_task2" value="2" title="Create report using [Match Duplicate Distance]=2 (Medium Match). NO tagging.">Create Duplicate Report [Medium Match]</button>
|
||||
</center></td></tr>
|
||||
<tr><td><center>
|
||||
<button type="button" id="create_duplicate_report_task3" value="3" title="Create report using [Match Duplicate Distance]=3 (Low Match). NO tagging.">Create Duplicate Report [Low Match]</button>
|
||||
</center></td></tr>
|
||||
</table></td>
|
||||
</tr>
|
||||
<tr><td style="font-size: 12px;" colspan="2">
|
||||
<b>Details:</b>
|
||||
<ol type="I" style="padding-left: 16px;">
|
||||
@@ -1901,6 +1943,8 @@ function DeleteDupInPath(){
|
||||
</ol>
|
||||
</td></tr>
|
||||
</table></center>
|
||||
<div id="div1"></div>
|
||||
|
||||
|
||||
</body></html>
|
||||
|
||||
|
||||
@@ -1,7 +1,22 @@
|
||||
##### This page was added starting on version 0.2.0 to keep track of changes between versions.
|
||||
##### This page was added starting on version 0.2.0 to keep track of newly added features between versions.
|
||||
### 0.2.0
|
||||
- For report, added logic to transfer option settings **[Disable Complete Confirmation]** and **[Disable Delete Confirmation]** when paginating.
|
||||
- Fixed minor bug in advance_options.html for GQL params.
|
||||
### 0.2.1
|
||||
- Added logic to have reports and advanced menu to work with Stash settings requiring a password by adding API-Key as param argument for advance menu, and adding API-Key as variable in reports.
|
||||
- When **[Advance Tag Menu]** is called from reports, it's given both the GQL URL and the apiKey on the URL param, which allows advance menu to work with non-standard URL's and with API-Key.
|
||||
### 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.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# RenameFile: Ver 0.5.5 (By David Maisonave)
|
||||
# RenameFile: Ver 0.5.6 (By David Maisonave)
|
||||
RenameFile is a [Stash](https://github.com/stashapp/stash) plugin. Starting version 0.5.5, user can add the current title to the title input field by clicking on the current title. Also, the Stash database gets updated directly instead of running a scan task as long as the database is version 68.
|
||||
|
||||
- The plugin allows user to rename one scene at a time by editing the **[Title]** field and then clicking **[Save]**.
|
||||
|
||||
@@ -42,12 +42,8 @@ exitMsg = "Change success!!"
|
||||
# **********************************************************************
|
||||
# ----------------------------------------------------------------------
|
||||
settings = {
|
||||
"performerAppend": False,
|
||||
"studioAppend": False,
|
||||
"tagAppend": False,
|
||||
"yRenameEvenIfTitleEmpty": False,
|
||||
"z_keyFIeldsIncludeInFileName": False,
|
||||
"zafileRenameViaMove": False,
|
||||
"zfieldKeyList": DEFAULT_FIELD_KEY_LIST,
|
||||
"zmaximumTagKeys": 12,
|
||||
"zseparators": DEFAULT_SEPERATOR,
|
||||
@@ -114,7 +110,7 @@ if endpointHost == "0.0.0.0":
|
||||
endpoint = f"{stash.JSON_INPUT['server_connection']['Scheme']}://{endpointHost}:{stash.JSON_INPUT['server_connection']['Port']}/graphql"
|
||||
|
||||
# stash.Trace(f"(endpoint={endpoint})")
|
||||
move_files = stash.pluginSettings["zafileRenameViaMove"]
|
||||
move_files = stash.Setting("fileRenameViaMove")
|
||||
fieldKeyList = stash.pluginSettings["zfieldKeyList"] # Default Field Key List with the desired order
|
||||
if not fieldKeyList or fieldKeyList == "":
|
||||
fieldKeyList = DEFAULT_FIELD_KEY_LIST
|
||||
@@ -150,7 +146,7 @@ def getPerformers(scene, title):
|
||||
title = title.lower()
|
||||
results = ""
|
||||
for performer in scene['performers']:
|
||||
name = stash.find_performer(performer['id'])['name']
|
||||
name = performer['name']
|
||||
stash.Trace(f"performer = {name}")
|
||||
if not include_keyField_if_in_name:
|
||||
if name.lower() in title:
|
||||
@@ -162,7 +158,7 @@ def getPerformers(scene, title):
|
||||
def getGalleries(scene, title):
|
||||
results = ""
|
||||
for gallery in scene['galleries']:
|
||||
name = stash.find_gallery(gallery['id'])['title']
|
||||
name = gallery = stash.find_gallery(gallery['id'])['title']
|
||||
stash.Trace(f"gallery = {name}")
|
||||
if not include_keyField_if_in_name:
|
||||
if name.lower() in title:
|
||||
@@ -175,10 +171,9 @@ def getTags(scene, title):
|
||||
title = title.lower()
|
||||
results = ""
|
||||
for tag in scene['tags']:
|
||||
tag_details = stash.find_tag(int(tag['id']))
|
||||
name = tag_details['name']
|
||||
name = tag['name']
|
||||
stash.Trace(f"tag = {name}")
|
||||
if excludeIgnoreAutoTags == True and tag_details['ignore_auto_tag'] == True:
|
||||
if excludeIgnoreAutoTags == True and tag['ignore_auto_tag'] == True:
|
||||
stash.Trace(f"Skipping tag name '{name}' because ignore_auto_tag is True.")
|
||||
continue
|
||||
if not include_keyField_if_in_name:
|
||||
@@ -231,8 +226,12 @@ def form_filename(original_file_stem, scene_details):
|
||||
|
||||
for key in fieldKeyList:
|
||||
if key == 'studio':
|
||||
if stash.pluginSettings["studioAppend"]:
|
||||
studio_name = scene_details.get('code', {})
|
||||
if stash.Setting("studioAppendEnable"):
|
||||
studio = scene_details.get('studio')
|
||||
if studio != None:
|
||||
studio_name = studio.get('name')
|
||||
else:
|
||||
studio_name = None
|
||||
stash.Trace(f"(studio_name={studio_name})")
|
||||
if studio_name:
|
||||
studio_name += POSTFIX_STYLES.get('studio')
|
||||
@@ -251,7 +250,7 @@ def form_filename(original_file_stem, scene_details):
|
||||
else:
|
||||
filename_parts.append(title)
|
||||
elif key == 'performers':
|
||||
if stash.pluginSettings["performerAppend"]:
|
||||
if stash.Setting("performerAppendEnable"):
|
||||
performers = getPerformers(scene_details, title)
|
||||
if performers != "":
|
||||
performers += POSTFIX_STYLES.get('performers')
|
||||
@@ -261,7 +260,7 @@ def form_filename(original_file_stem, scene_details):
|
||||
else:
|
||||
filename_parts.append(performers)
|
||||
elif key == 'date':
|
||||
scene_date = scene_details.get('date', '')
|
||||
scene_date = scene_details.get('date')
|
||||
if scene_date:
|
||||
scene_date += POSTFIX_STYLES.get('date')
|
||||
if WRAPPER_STYLES.get('date'):
|
||||
@@ -269,8 +268,10 @@ def form_filename(original_file_stem, scene_details):
|
||||
if scene_date not in title:
|
||||
filename_parts.append(scene_date)
|
||||
elif key == 'resolution':
|
||||
width = str(scene_details.get('files', [{}])[0].get('width', '')) # Convert width to string
|
||||
height = str(scene_details.get('files', [{}])[0].get('height', '')) # Convert height to string
|
||||
# width = str(scene_details.get('files', [{}])[0].get('width', '')) # Convert width to string
|
||||
# height = str(scene_details.get('files', [{}])[0].get('height', '')) # Convert height to string
|
||||
width = str(scene_details['files'][0]['width'])
|
||||
height = str(scene_details['files'][0]['height'])
|
||||
if width and height:
|
||||
resolution = width + POSTFIX_STYLES.get('width_height_seperator') + height + POSTFIX_STYLES.get('resolution')
|
||||
if WRAPPER_STYLES.get('resolution'):
|
||||
@@ -278,7 +279,7 @@ def form_filename(original_file_stem, scene_details):
|
||||
if resolution not in title:
|
||||
filename_parts.append(resolution)
|
||||
elif key == 'width':
|
||||
width = str(scene_details.get('files', [{}])[0].get('width', '')) # Convert width to string
|
||||
width = str(scene_details['files'][0]['width'])
|
||||
if width:
|
||||
width += POSTFIX_STYLES.get('width')
|
||||
if WRAPPER_STYLES.get('width'):
|
||||
@@ -286,7 +287,7 @@ def form_filename(original_file_stem, scene_details):
|
||||
if width not in title:
|
||||
filename_parts.append(width)
|
||||
elif key == 'height':
|
||||
height = str(scene_details.get('files', [{}])[0].get('height', '')) # Convert height to string
|
||||
height = str(scene_details['files'][0]['height'])
|
||||
if height:
|
||||
height += POSTFIX_STYLES.get('height')
|
||||
if WRAPPER_STYLES.get('height'):
|
||||
@@ -294,7 +295,7 @@ def form_filename(original_file_stem, scene_details):
|
||||
if height not in title:
|
||||
filename_parts.append(height)
|
||||
elif key == 'video_codec':
|
||||
video_codec = scene_details.get('files', [{}])[0].get('video_codec', '').upper() # Convert to uppercase
|
||||
video_codec = scene_details['files'][0]['video_codec'].upper() # Convert to uppercase
|
||||
if video_codec:
|
||||
video_codec += POSTFIX_STYLES.get('video_codec')
|
||||
if WRAPPER_STYLES.get('video_codec'):
|
||||
@@ -302,7 +303,7 @@ def form_filename(original_file_stem, scene_details):
|
||||
if video_codec not in title:
|
||||
filename_parts.append(video_codec)
|
||||
elif key == 'frame_rate':
|
||||
frame_rate = str(scene_details.get('files', [{}])[0].get('frame_rate', '')) + 'FPS' # Convert to string and append ' FPS'
|
||||
frame_rate = str(scene_details['files'][0]['frame_rate']) + 'FPS' # Convert to string and append ' FPS'
|
||||
if frame_rate:
|
||||
frame_rate += POSTFIX_STYLES.get('frame_rate')
|
||||
if WRAPPER_STYLES.get('frame_rate'):
|
||||
@@ -319,7 +320,7 @@ def form_filename(original_file_stem, scene_details):
|
||||
filename_parts.append(galleries)
|
||||
stash.Trace(f"(galleries={galleries})")
|
||||
elif key == 'tags':
|
||||
if stash.pluginSettings["tagAppend"]:
|
||||
if stash.Setting("tagAppendEnable"):
|
||||
tags = getTags(scene_details, title)
|
||||
if tags != "":
|
||||
tags += POSTFIX_STYLES.get('tag')
|
||||
@@ -343,7 +344,8 @@ def form_filename(original_file_stem, scene_details):
|
||||
def rename_scene(scene_id):
|
||||
global exitMsg
|
||||
POST_SCAN_DELAY = 3
|
||||
scene_details = stash.find_scene(scene_id)
|
||||
fragment = 'id title performers {name} tags {id name ignore_auto_tag} studio {name} galleries {id} files {id path width height video_codec frame_rate} date'
|
||||
scene_details = stash.find_scene(scene_id, fragment)
|
||||
stash.Trace(f"(scene_details={scene_details})")
|
||||
if not scene_details:
|
||||
stash.Error(f"Scene with ID {scene_id} not found.")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: RenameFile
|
||||
description: Renames video (scene) file names when the user edits the [Title] field located in the scene [Edit] tab.
|
||||
version: 0.5.5
|
||||
version: 0.5.6
|
||||
url: https://github.com/David-Maisonave/Axter-Stash/tree/main/plugins/RenameFile
|
||||
ui:
|
||||
css:
|
||||
@@ -8,18 +8,6 @@ ui:
|
||||
javascript:
|
||||
- renamefile.js
|
||||
settings:
|
||||
performerAppend:
|
||||
displayName: Append Performers
|
||||
description: Enable to append performers name to file name when renaming a file. Requires performers to be included in [Key Fields] list, which by default it is included.
|
||||
type: BOOLEAN
|
||||
studioAppend:
|
||||
displayName: Append Studio
|
||||
description: Enable to append studio name to file name when renaming a file. Requires studio to be included in [Key Fields] list, which by default it is included.
|
||||
type: BOOLEAN
|
||||
tagAppend:
|
||||
displayName: Append Tags
|
||||
description: Enable to append tag names to file name when renaming a file. Requires tags to be included in [Key Fields] list, which by default it is included.
|
||||
type: BOOLEAN
|
||||
yRenameEvenIfTitleEmpty:
|
||||
displayName: Empty Title Rename
|
||||
description: If enable, rename files even if TITLE field is empty.
|
||||
@@ -28,10 +16,6 @@ settings:
|
||||
displayName: Include Existing Key Field
|
||||
description: Enable to append performer, tags, studios, & galleries even if name already exists in the original file name.
|
||||
type: BOOLEAN
|
||||
zafileRenameViaMove:
|
||||
displayName: Move Instead of Rename
|
||||
description: Enable to move file instead of rename file. (Not recommended for Windows OS)
|
||||
type: BOOLEAN
|
||||
zfieldKeyList:
|
||||
displayName: Key Fields
|
||||
description: '(Default=title,performers,studio,tags) Define key fields to use to format the file name. This is a comma seperated list, and the list should be in the desired format order. For example, if the user wants the performers name before the title, set the performers name first. Example:"performers,title,tags". This is an example of user adding height:"title,performers,tags,height" Here''s an example using all of the supported fields: "title,performers,tags,studio,galleries,resolution,width,height,video_codec,frame_rate,date".'
|
||||
|
||||
@@ -49,6 +49,14 @@ config = {
|
||||
"max_filename_length": 255,
|
||||
# Exclude tags with ignore_auto_tag set to True
|
||||
"excludeIgnoreAutoTags": True,
|
||||
# Enable to append performers name to file name when renaming a file. Requires performers to be included in [Key Fields] list, which by default it is included.
|
||||
"performerAppendEnable": True,
|
||||
# Enable to append studio name to file name when renaming a file. Requires studio to be included in [Key Fields] list, which by default it is included.
|
||||
"studioAppendEnable": True,
|
||||
# Enable to append tag names to file name when renaming a file. Requires tags to be included in [Key Fields] list, which by default it is included.
|
||||
"tagAppendEnable": True,
|
||||
# Enable to move file instead of rename file. (Not recommended for Windows OS)
|
||||
"fileRenameViaMove": False,
|
||||
|
||||
# handleExe is for Windows only.
|
||||
# In Windows, a file can't be renamed if the file is opened by another process.
|
||||
|
||||
6
plugins/RenameFile/version_history/README.md
Normal file
6
plugins/RenameFile/version_history/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
##### This page was added starting on version 0.5.6 to keep track of newly added features between versions.
|
||||
### 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)
|
||||
Reference in New Issue
Block a user