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):
|
||||
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</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()
|
||||
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("</style>"):
|
||||
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("</style>"):
|
||||
doStyleEndTagCheck = False
|
||||
else:
|
||||
if property == "" and line.startswith(f".ID_{scene}" + "{"):
|
||||
continue
|
||||
if line.startswith("</style>"):
|
||||
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")
|
||||
|
||||
@@ -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">
|
||||
@@ -1797,50 +1883,6 @@ function DeleteDupInPath(){
|
||||
<option value="Greater">Greater</option>
|
||||
</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>
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user