forked from Github/Axter-Stash
Fixed bug in RenameFile when running on Linux
This commit is contained in:
@@ -7,13 +7,10 @@ import os, sys, time, pathlib, argparse, platform, shutil, logging
|
|||||||
from StashPluginHelper import StashPluginHelper
|
from StashPluginHelper import StashPluginHelper
|
||||||
from DupFileManager_config import config # Import config from DupFileManager_config.py
|
from DupFileManager_config import config # Import config from DupFileManager_config.py
|
||||||
|
|
||||||
# ToDo: Add schedule for deletion date argument at command line. Variable can also be fetched from JSON_INPUT["args"]
|
|
||||||
# This variable will be used in function setTagId when initializing variable BaseDupStr
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('--url', '-u', dest='stash_url', type=str, help='Add Stash URL')
|
parser.add_argument('--url', '-u', dest='stash_url', type=str, help='Add Stash URL')
|
||||||
parser.add_argument('--trace', '-t', dest='trace', action='store_true', help='Enables debug trace mode.')
|
parser.add_argument('--trace', '-t', dest='trace', action='store_true', help='Enables debug trace mode.')
|
||||||
parser.add_argument('--add_dup_tag', '-a', dest='dup_tag', action='store_true', help='Set a tag to duplicate files.')
|
parser.add_argument('--add_dup_tag', '-a', dest='dup_tag', action='store_true', help='Set a tag to duplicate files.')
|
||||||
parser.add_argument('--clear_dup_tag', '-c', dest='clear_tag', action='store_true', help='Clear duplicates of duplicate tags.')
|
|
||||||
parser.add_argument('--del_tag_dup', '-d', dest='del_tag', action='store_true', help='Only delete scenes having DuplicateMarkForDeletion tag.')
|
parser.add_argument('--del_tag_dup', '-d', dest='del_tag', action='store_true', help='Only delete scenes having DuplicateMarkForDeletion tag.')
|
||||||
parser.add_argument('--remove_dup', '-r', dest='remove', action='store_true', help='Remove (delete) duplicate files.')
|
parser.add_argument('--remove_dup', '-r', dest='remove', action='store_true', help='Remove (delete) duplicate files.')
|
||||||
parse_args = parser.parse_args()
|
parse_args = parser.parse_args()
|
||||||
@@ -63,8 +60,6 @@ swapLongLength = stash.Setting('zSwapLongLength')
|
|||||||
significantTimeDiff = stash.Setting('significantTimeDiff')
|
significantTimeDiff = stash.Setting('significantTimeDiff')
|
||||||
toRecycleBeforeSwap = stash.Setting('toRecycleBeforeSwap')
|
toRecycleBeforeSwap = stash.Setting('toRecycleBeforeSwap')
|
||||||
cleanAfterDel = stash.Setting('zCleanAfterDel')
|
cleanAfterDel = stash.Setting('zCleanAfterDel')
|
||||||
favorLongerFileName = float(stash.Setting('favorLongerFileName'))
|
|
||||||
favorLargerFileSize = float(stash.Setting('favorLargerFileSize'))
|
|
||||||
duration_diff = float(stash.Setting('duration_diff'))
|
duration_diff = float(stash.Setting('duration_diff'))
|
||||||
if duration_diff > 10:
|
if duration_diff > 10:
|
||||||
duration_diff = 10
|
duration_diff = 10
|
||||||
@@ -187,16 +182,13 @@ def createTagId(tagName, tagName_descp, deleteIfExist = False):
|
|||||||
stash.Log(f"Dup-tagId={tagId['id']}")
|
stash.Log(f"Dup-tagId={tagId['id']}")
|
||||||
return tagId['id']
|
return tagId['id']
|
||||||
|
|
||||||
detailPrefix = "BaseDup="
|
|
||||||
detailPostfix = "<BaseDup>\n"
|
|
||||||
|
|
||||||
def setTagId(tagId, tagName, sceneDetails, DupFileToKeep):
|
def setTagId(tagId, tagName, sceneDetails, DupFileToKeep):
|
||||||
details = ""
|
details = ""
|
||||||
ORG_DATA_DICT = {'id' : sceneDetails['id']}
|
ORG_DATA_DICT = {'id' : sceneDetails['id']}
|
||||||
dataDict = ORG_DATA_DICT.copy()
|
dataDict = ORG_DATA_DICT.copy()
|
||||||
doAddTag = True
|
doAddTag = True
|
||||||
if addPrimaryDupPathToDetails:
|
if addPrimaryDupPathToDetails:
|
||||||
BaseDupStr = f"{detailPrefix}{DupFileToKeep['files'][0]['path']}\n{stash.STASH_URL}/scenes/{DupFileToKeep['id']}\n{detailPostfix}"
|
BaseDupStr = f"BaseDup={DupFileToKeep['files'][0]['path']}\n{stash.STASH_URL}/scenes/{DupFileToKeep['id']}\n"
|
||||||
if sceneDetails['details'] == "":
|
if sceneDetails['details'] == "":
|
||||||
details = BaseDupStr
|
details = BaseDupStr
|
||||||
elif not sceneDetails['details'].startswith(BaseDupStr):
|
elif not sceneDetails['details'].startswith(BaseDupStr):
|
||||||
@@ -223,20 +215,6 @@ def isInList(listToCk, pathToCk):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
NOT_IN_LIST = 65535
|
|
||||||
def indexInList(listToCk, pathToCk):
|
|
||||||
pathToCk = pathToCk.lower()
|
|
||||||
index = -1
|
|
||||||
lenItemMatch = 0
|
|
||||||
returnValue = NOT_IN_LIST
|
|
||||||
for item in listToCk:
|
|
||||||
index += 1
|
|
||||||
if pathToCk.startswith(item):
|
|
||||||
if len(item) > lenItemMatch: # Make sure the best match is selected by getting match with longest string.
|
|
||||||
lenItemMatch = len(item)
|
|
||||||
returnValue = index
|
|
||||||
return returnValue
|
|
||||||
|
|
||||||
def hasSameDir(path1, path2):
|
def hasSameDir(path1, path2):
|
||||||
if pathlib.Path(path1).resolve().parent == pathlib.Path(path2).resolve().parent:
|
if pathlib.Path(path1).resolve().parent == pathlib.Path(path2).resolve().parent:
|
||||||
return True
|
return True
|
||||||
@@ -280,21 +258,6 @@ def isSwapCandidate(DupFileToKeep, DupFile):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def isWorseKeepCandidate(DupFileToKeep, Scene):
|
|
||||||
if not isInList(whitelist, Scene['files'][0]['path']) and isInList(whitelist, DupFileToKeep['files'][0]['path']):
|
|
||||||
return True
|
|
||||||
if not isInList(graylist, Scene['files'][0]['path']) and isInList(graylist, DupFileToKeep['files'][0]['path']):
|
|
||||||
return True
|
|
||||||
if not isInList(blacklist, DupFileToKeep['files'][0]['path']) and isInList(blacklist, Scene['files'][0]['path']):
|
|
||||||
return True
|
|
||||||
|
|
||||||
if isInList(graylist, Scene['files'][0]['path']) and isInList(graylist, DupFileToKeep['files'][0]['path']) and indexInList(graylist, DupFileToKeep['files'][0]['path']) < indexInList(graylist, Scene['files'][0]['path']):
|
|
||||||
return True
|
|
||||||
if isInList(blacklist, DupFileToKeep['files'][0]['path']) and isInList(blacklist, Scene['files'][0]['path']) and indexInList(blacklist, DupFileToKeep['files'][0]['path']) < indexInList(blacklist, Scene['files'][0]['path']):
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def mangeDupFiles(merge=False, deleteDup=False, tagDuplicates=False):
|
def mangeDupFiles(merge=False, deleteDup=False, tagDuplicates=False):
|
||||||
duplicateMarkForDeletion_descp = 'Tag added to duplicate scenes so-as to tag them for deletion.'
|
duplicateMarkForDeletion_descp = 'Tag added to duplicate scenes so-as to tag them for deletion.'
|
||||||
stash.Trace(f"duplicateMarkForDeletion = {duplicateMarkForDeletion}")
|
stash.Trace(f"duplicateMarkForDeletion = {duplicateMarkForDeletion}")
|
||||||
@@ -349,37 +312,18 @@ def mangeDupFiles(merge=False, deleteDup=False, tagDuplicates=False):
|
|||||||
if significantLessTime(int(DupFileToKeep['files'][0]['duration']), int(Scene['files'][0]['duration'])):
|
if significantLessTime(int(DupFileToKeep['files'][0]['duration']), int(Scene['files'][0]['duration'])):
|
||||||
QtyRealTimeDiff += 1
|
QtyRealTimeDiff += 1
|
||||||
if int(DupFileToKeep['files'][0]['width']) < int(Scene['files'][0]['width']) or int(DupFileToKeep['files'][0]['height']) < int(Scene['files'][0]['height']):
|
if int(DupFileToKeep['files'][0]['width']) < int(Scene['files'][0]['width']) or int(DupFileToKeep['files'][0]['height']) < int(Scene['files'][0]['height']):
|
||||||
stash.Trace(f"Replacing {DupFileToKeep['files'][0]['path']} with {Scene['files'][0]['path']} for candidate to keep. Reason=resolution: {DupFileToKeep['files'][0]['width']}x{DupFileToKeep['files'][0]['height']} < {Scene['files'][0]['width']}x{Scene['files'][0]['height']}")
|
|
||||||
DupFileToKeep = Scene
|
DupFileToKeep = Scene
|
||||||
elif int(DupFileToKeep['files'][0]['duration']) < int(Scene['files'][0]['duration']):
|
elif int(DupFileToKeep['files'][0]['duration']) < int(Scene['files'][0]['duration']):
|
||||||
stash.Trace(f"Replacing {DupFileToKeep['files'][0]['path']} with {Scene['files'][0]['path']} for candidate to keep. Reason=duration: {DupFileToKeep['files'][0]['duration']} < {Scene['files'][0]['duration']}")
|
|
||||||
DupFileToKeep = Scene
|
DupFileToKeep = Scene
|
||||||
elif isInList(whitelist, Scene['files'][0]['path']) and not isInList(whitelist, DupFileToKeep['files'][0]['path']):
|
elif isInList(whitelist, Scene['files'][0]['path']) and not isInList(whitelist, DupFileToKeep['files'][0]['path']):
|
||||||
stash.Trace(f"Replacing {DupFileToKeep['files'][0]['path']} with {Scene['files'][0]['path']} for candidate to keep. Reason=not whitelist vs whitelist")
|
|
||||||
DupFileToKeep = Scene
|
DupFileToKeep = Scene
|
||||||
elif isInList(blacklist, DupFileToKeep['files'][0]['path']) and not isInList(blacklist, Scene['files'][0]['path']):
|
elif isInList(blacklist, DupFileToKeep['files'][0]['path']) and not isInList(blacklist, Scene['files'][0]['path']):
|
||||||
stash.Trace(f"Replacing {DupFileToKeep['files'][0]['path']} with {Scene['files'][0]['path']} for candidate to keep. Reason=blacklist vs not blacklist")
|
|
||||||
DupFileToKeep = Scene
|
|
||||||
elif isInList(blacklist, DupFileToKeep['files'][0]['path']) and isInList(blacklist, Scene['files'][0]['path']) and indexInList(blacklist, DupFileToKeep['files'][0]['path']) > indexInList(blacklist, Scene['files'][0]['path']):
|
|
||||||
stash.Trace(f"Replacing {DupFileToKeep['files'][0]['path']} with {Scene['files'][0]['path']} for candidate to keep. Reason=blacklist-index {indexInList(blacklist, DupFileToKeep['files'][0]['path'])} > {indexInList(blacklist, Scene['files'][0]['path'])}")
|
|
||||||
DupFileToKeep = Scene
|
DupFileToKeep = Scene
|
||||||
elif isInList(graylist, Scene['files'][0]['path']) and not isInList(graylist, DupFileToKeep['files'][0]['path']):
|
elif isInList(graylist, Scene['files'][0]['path']) and not isInList(graylist, DupFileToKeep['files'][0]['path']):
|
||||||
stash.Trace(f"Replacing {DupFileToKeep['files'][0]['path']} with {Scene['files'][0]['path']} for candidate to keep. Reason=not graylist vs graylist")
|
|
||||||
DupFileToKeep = Scene
|
DupFileToKeep = Scene
|
||||||
elif isInList(graylist, Scene['files'][0]['path']) and isInList(graylist, DupFileToKeep['files'][0]['path']) and indexInList(graylist, DupFileToKeep['files'][0]['path']) > indexInList(graylist, Scene['files'][0]['path']):
|
elif len(DupFileToKeep['files'][0]['path']) < len(Scene['files'][0]['path']):
|
||||||
stash.Trace(f"Replacing {DupFileToKeep['files'][0]['path']} with {Scene['files'][0]['path']} for candidate to keep. Reason=graylist-index {indexInList(graylist, DupFileToKeep['files'][0]['path'])} > {indexInList(graylist, Scene['files'][0]['path'])}")
|
|
||||||
DupFileToKeep = Scene
|
DupFileToKeep = Scene
|
||||||
elif favorLongerFileName and len(DupFileToKeep['files'][0]['path']) < len(Scene['files'][0]['path']) and not isWorseKeepCandidate(DupFileToKeep, Scene):
|
elif int(DupFileToKeep['files'][0]['size']) < int(Scene['files'][0]['size']):
|
||||||
stash.Trace(f"Replacing {DupFileToKeep['files'][0]['path']} with {Scene['files'][0]['path']} for candidate to keep. Reason=path-len {len(DupFileToKeep['files'][0]['path'])} < {len(Scene['files'][0]['path'])}")
|
|
||||||
DupFileToKeep = Scene
|
|
||||||
elif favorLargerFileSize and int(DupFileToKeep['files'][0]['size']) < int(Scene['files'][0]['size']) and not isWorseKeepCandidate(DupFileToKeep, Scene):
|
|
||||||
stash.Trace(f"Replacing {DupFileToKeep['files'][0]['path']} with {Scene['files'][0]['path']} for candidate to keep. Reason=size {DupFileToKeep['files'][0]['size']} < {Scene['files'][0]['size']}")
|
|
||||||
DupFileToKeep = Scene
|
|
||||||
elif not favorLongerFileName and len(DupFileToKeep['files'][0]['path']) > len(Scene['files'][0]['path']) and not isWorseKeepCandidate(DupFileToKeep, Scene):
|
|
||||||
stash.Trace(f"Replacing {DupFileToKeep['files'][0]['path']} with {Scene['files'][0]['path']} for candidate to keep. Reason=path-len {len(DupFileToKeep['files'][0]['path'])} > {len(Scene['files'][0]['path'])}")
|
|
||||||
DupFileToKeep = Scene
|
|
||||||
elif not favorLargerFileSize and int(DupFileToKeep['files'][0]['size']) > int(Scene['files'][0]['size']) and not isWorseKeepCandidate(DupFileToKeep, Scene):
|
|
||||||
stash.Trace(f"Replacing {DupFileToKeep['files'][0]['path']} with {Scene['files'][0]['path']} for candidate to keep. Reason=size {DupFileToKeep['files'][0]['size']} > {Scene['files'][0]['size']}")
|
|
||||||
DupFileToKeep = Scene
|
DupFileToKeep = Scene
|
||||||
else:
|
else:
|
||||||
DupFileToKeep = Scene
|
DupFileToKeep = Scene
|
||||||
@@ -440,7 +384,7 @@ def mangeDupFiles(merge=False, deleteDup=False, tagDuplicates=False):
|
|||||||
stash.metadata_clean_generated()
|
stash.metadata_clean_generated()
|
||||||
stash.optimise_database()
|
stash.optimise_database()
|
||||||
|
|
||||||
def manageTaggedDuplicates(deleteFiles=False):
|
def deleteTagggedDuplicates():
|
||||||
tagId = stash.find_tags(q=duplicateMarkForDeletion)
|
tagId = stash.find_tags(q=duplicateMarkForDeletion)
|
||||||
if len(tagId) > 0 and 'id' in tagId[0]:
|
if len(tagId) > 0 and 'id' in tagId[0]:
|
||||||
tagId = tagId[0]['id']
|
tagId = tagId[0]['id']
|
||||||
@@ -465,34 +409,19 @@ def manageTaggedDuplicates(deleteFiles=False):
|
|||||||
QtyFailedQuery += 1
|
QtyFailedQuery += 1
|
||||||
continue
|
continue
|
||||||
# stash.Log(f"scene={scene}")
|
# stash.Log(f"scene={scene}")
|
||||||
if deleteFiles:
|
DupFileName = scene['files'][0]['path']
|
||||||
DupFileName = scene['files'][0]['path']
|
DupFileNameOnly = pathlib.Path(DupFileName).stem
|
||||||
DupFileNameOnly = pathlib.Path(DupFileName).stem
|
stash.Warn(f"Deleting duplicate '{DupFileName}'", toAscii=True, printTo=LOG_STASH_N_PLUGIN)
|
||||||
stash.Warn(f"Deleting duplicate '{DupFileName}'", toAscii=True, printTo=LOG_STASH_N_PLUGIN)
|
if alternateTrashCanPath != "":
|
||||||
if alternateTrashCanPath != "":
|
destPath = f"{alternateTrashCanPath }{os.sep}{DupFileNameOnly}"
|
||||||
destPath = f"{alternateTrashCanPath }{os.sep}{DupFileNameOnly}"
|
if os.path.isfile(destPath):
|
||||||
if os.path.isfile(destPath):
|
destPath = f"{alternateTrashCanPath }{os.sep}_{time.time()}_{DupFileNameOnly}"
|
||||||
destPath = f"{alternateTrashCanPath }{os.sep}_{time.time()}_{DupFileNameOnly}"
|
shutil.move(DupFileName, destPath)
|
||||||
shutil.move(DupFileName, destPath)
|
elif moveToTrashCan:
|
||||||
elif moveToTrashCan:
|
sendToTrash(DupFileName)
|
||||||
sendToTrash(DupFileName)
|
result = stash.destroy_scene(scene['id'], delete_file=True)
|
||||||
result = stash.destroy_scene(scene['id'], delete_file=True)
|
stash.Trace(f"destroy_scene result={result} for file {DupFileName}", toAscii=True)
|
||||||
stash.Trace(f"destroy_scene result={result} for file {DupFileName}", toAscii=True)
|
QtyDeleted += 1
|
||||||
QtyDeleted += 1
|
|
||||||
else:
|
|
||||||
tags = [int(item['id']) for item in scene["tags"] if item['id'] != tagId]
|
|
||||||
stash.TraceOnce(f"tagId={tagId}, len={len(tags)}, tags = {tags}")
|
|
||||||
dataDict = {'id' : scene['id']}
|
|
||||||
if addPrimaryDupPathToDetails:
|
|
||||||
sceneDetails = scene['details']
|
|
||||||
if sceneDetails.find(detailPrefix) == 0 and sceneDetails.find(detailPostfix) > 1:
|
|
||||||
Pos1 = sceneDetails.find(detailPrefix)
|
|
||||||
Pos2 = sceneDetails.find(detailPostfix)
|
|
||||||
sceneDetails = sceneDetails[0:Pos1] + sceneDetails[Pos2 + len(detailPostfix):]
|
|
||||||
dataDict.update({'details' : sceneDetails})
|
|
||||||
dataDict.update({'tag_ids' : tags})
|
|
||||||
stash.Log(f"Updating scene with {dataDict}")
|
|
||||||
stash.update_scene(dataDict)
|
|
||||||
stash.Log(f"QtyDup={QtyDup}, QtyDeleted={QtyDeleted}, QtyFailedQuery={QtyFailedQuery}", printTo=LOG_STASH_N_PLUGIN)
|
stash.Log(f"QtyDup={QtyDup}, QtyDeleted={QtyDeleted}, QtyFailedQuery={QtyFailedQuery}", printTo=LOG_STASH_N_PLUGIN)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -510,10 +439,7 @@ if stash.PLUGIN_TASK_NAME == "tag_duplicates_task":
|
|||||||
mangeDupFiles(tagDuplicates=True, merge=mergeDupFilename)
|
mangeDupFiles(tagDuplicates=True, merge=mergeDupFilename)
|
||||||
stash.Trace(f"{stash.PLUGIN_TASK_NAME} EXIT")
|
stash.Trace(f"{stash.PLUGIN_TASK_NAME} EXIT")
|
||||||
elif stash.PLUGIN_TASK_NAME == "delete_tagged_duplicates_task":
|
elif stash.PLUGIN_TASK_NAME == "delete_tagged_duplicates_task":
|
||||||
manageTaggedDuplicates(deleteFiles=True)
|
deleteTagggedDuplicates()
|
||||||
stash.Trace(f"{stash.PLUGIN_TASK_NAME} EXIT")
|
|
||||||
elif stash.PLUGIN_TASK_NAME == "clear_duplicate_tags_task":
|
|
||||||
manageTaggedDuplicates(deleteFiles=False)
|
|
||||||
stash.Trace(f"{stash.PLUGIN_TASK_NAME} EXIT")
|
stash.Trace(f"{stash.PLUGIN_TASK_NAME} EXIT")
|
||||||
elif stash.PLUGIN_TASK_NAME == "delete_duplicates_task":
|
elif stash.PLUGIN_TASK_NAME == "delete_duplicates_task":
|
||||||
mangeDupFiles(deleteDup=True, merge=mergeDupFilename)
|
mangeDupFiles(deleteDup=True, merge=mergeDupFilename)
|
||||||
@@ -522,11 +448,8 @@ elif parse_args.dup_tag:
|
|||||||
mangeDupFiles(tagDuplicates=True, merge=mergeDupFilename)
|
mangeDupFiles(tagDuplicates=True, merge=mergeDupFilename)
|
||||||
stash.Trace(f"Tag duplicate EXIT")
|
stash.Trace(f"Tag duplicate EXIT")
|
||||||
elif parse_args.del_tag:
|
elif parse_args.del_tag:
|
||||||
manageTaggedDuplicates(deleteFiles=True)
|
deleteTagggedDuplicates()
|
||||||
stash.Trace(f"Delete tagged duplicates EXIT")
|
stash.Trace(f"Delete Tagged duplicates EXIT")
|
||||||
elif parse_args.clear_tag:
|
|
||||||
manageTaggedDuplicates(deleteFiles=False)
|
|
||||||
stash.Trace(f"Clear duplicate tags EXIT")
|
|
||||||
elif parse_args.remove:
|
elif parse_args.remove:
|
||||||
mangeDupFiles(deleteDup=True, merge=mergeDupFilename)
|
mangeDupFiles(deleteDup=True, merge=mergeDupFilename)
|
||||||
stash.Trace(f"Delete duplicate EXIT")
|
stash.Trace(f"Delete duplicate EXIT")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
name: DupFileManager
|
name: DupFileManager
|
||||||
description: Manages duplicate files.
|
description: Manages duplicate files.
|
||||||
version: 0.1.3
|
version: 0.1.2
|
||||||
url: https://github.com/David-Maisonave/Axter-Stash/tree/main/plugins/DupFileManager
|
url: https://github.com/David-Maisonave/Axter-Stash/tree/main/plugins/DupFileManager
|
||||||
settings:
|
settings:
|
||||||
mergeDupFilename:
|
mergeDupFilename:
|
||||||
@@ -37,11 +37,11 @@ settings:
|
|||||||
type: STRING
|
type: STRING
|
||||||
zxGraylist:
|
zxGraylist:
|
||||||
displayName: Gray List
|
displayName: Gray List
|
||||||
description: Preferential paths to determine which duplicate should be kept. E.g. C:\2nd_Fav,C:\3rd_Fav,C:\4th_Fav,H:\ShouldKeep
|
description: List of preferential paths to determine which duplicate should be the primary. E.g. C:\2nd_Favorite\,H:\ShouldKeep\
|
||||||
type: STRING
|
type: STRING
|
||||||
zyBlacklist:
|
zyBlacklist:
|
||||||
displayName: Black List
|
displayName: Black List
|
||||||
description: Least preferential paths; Determine primary deletion candidates. E.g. C:\Downloads,C:\DelMe-3rd,C:\DelMe-2nd,C:\DeleteMeFirst
|
description: List of LEAST preferential paths to determine primary candidates for deletion. E.g. C:\Downloads\,F:\DeleteMeFirst\
|
||||||
type: STRING
|
type: STRING
|
||||||
zyMaxDupToProcess:
|
zyMaxDupToProcess:
|
||||||
displayName: Max Dup Process
|
displayName: Max Dup Process
|
||||||
@@ -49,7 +49,7 @@ settings:
|
|||||||
type: NUMBER
|
type: NUMBER
|
||||||
zzdebugTracing:
|
zzdebugTracing:
|
||||||
displayName: Debug Tracing
|
displayName: Debug Tracing
|
||||||
description: Enable debug tracing so-as to add additional debug logging in Stash\plugins\DupFileManager\DupFileManager.log
|
description: (Default=false) [***For Advanced Users***] Enable debug tracing. When enabled, additional tracing logging is added to Stash\plugins\DupFileManager\DupFileManager.log
|
||||||
type: BOOLEAN
|
type: BOOLEAN
|
||||||
exec:
|
exec:
|
||||||
- python
|
- python
|
||||||
@@ -68,7 +68,3 @@ tasks:
|
|||||||
description: Delete duplicate scenes. Performs deletion without first tagging.
|
description: Delete duplicate scenes. Performs deletion without first tagging.
|
||||||
defaultArgs:
|
defaultArgs:
|
||||||
mode: delete_duplicates_task
|
mode: delete_duplicates_task
|
||||||
- name: Clear Duplicate Tags
|
|
||||||
description: Removes DuplicateMarkForDeletion tag from all files.
|
|
||||||
defaultArgs:
|
|
||||||
mode: clear_duplicate_tags_task
|
|
||||||
|
|||||||
@@ -18,10 +18,6 @@ config = {
|
|||||||
"DupFileTag" : "DuplicateMarkForDeletion",
|
"DupFileTag" : "DuplicateMarkForDeletion",
|
||||||
# Tag name used to tag duplicates in the whitelist. E.g. DuplicateWhitelistFile
|
# Tag name used to tag duplicates in the whitelist. E.g. DuplicateWhitelistFile
|
||||||
"DupWhiteListTag" : "DuplicateWhitelistFile",
|
"DupWhiteListTag" : "DuplicateWhitelistFile",
|
||||||
# If enabled, favor longer file name over shorter. If disabled, favor shorter file name.
|
|
||||||
"favorLongerFileName" : True,
|
|
||||||
# If enabled, favor larger file size over smaller. If disabled, favor smaller file size.
|
|
||||||
"favorLargerFileSize" : True,
|
|
||||||
|
|
||||||
# The following fields are ONLY used when running DupFileManager in script mode
|
# The following fields are ONLY used when running DupFileManager in script mode
|
||||||
"endpoint_Scheme" : "http", # Define endpoint to use when contacting the Stash server
|
"endpoint_Scheme" : "http", # Define endpoint to use when contacting the Stash server
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
# DupFileManager: Ver 0.1.3 (By David Maisonave)
|
# DupFileManager: Ver 0.1.2 (By David Maisonave)
|
||||||
|
|
||||||
DupFileManager is a [Stash](https://github.com/stashapp/stash) plugin which manages duplicate file in the Stash system.
|
DupFileManager is a [Stash](https://github.com/stashapp/stash) plugin which manages duplicate file in the Stash system.
|
||||||
|
|
||||||
<img width="500" alt="DupFileManager_Task_UI" src="https://github.com/user-attachments/assets/d25fd76b-4624-401b-bb12-091a4dadbe0c">
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
|
- Can merge potential source in the duplicate file names for tag names, performers, and studios.
|
||||||
|
- Normally when Stash searches the file name for tag names, performers, and studios, it only does so using the primary file.
|
||||||
- Delete duplicate file task with the following options:
|
- Delete duplicate file task with the following options:
|
||||||
- Tasks (Settings->Task->[Plugin Tasks]->DupFileManager)
|
- Tasks (Settings->Task->[Plugin Tasks]->DupFileManager)
|
||||||
- **Tag Duplicates** - Set tag DuplicateMarkForDeletion to the duplicates with lower resolution, duration, file name length, and/or black list path.
|
- **Tag Duplicates** - Set tag DuplicateMarkForDeletion to the duplicates with lower resolution, duration, file name length, and/or black list path.
|
||||||
@@ -14,21 +15,19 @@ DupFileManager is a [Stash](https://github.com/stashapp/stash) plugin which mana
|
|||||||
- Has a 3 tier path selection to determine which duplicates to keep, and which should be candidates for deletions.
|
- Has a 3 tier path selection to determine which duplicates to keep, and which should be candidates for deletions.
|
||||||
- **Whitelist** - List of paths NOT to be deleted.
|
- **Whitelist** - List of paths NOT to be deleted.
|
||||||
- E.g. C:\Favorite\,E:\MustKeep\
|
- E.g. C:\Favorite\,E:\MustKeep\
|
||||||
- **Gray-List** - List of preferential paths to determine which duplicate should be the primary. The list should be in order of most preferable in the beginning of the list.
|
- **Gray-List** - List of preferential paths to determine which duplicate should be the primary.
|
||||||
- E.g. C:\2nd_Fav,C:\3rd_Fav,C:\4th_Fav,H:\ShouldKeep
|
- E.g. C:\2nd_Favorite\,H:\ShouldKeep\
|
||||||
- **Blacklist** - List of LEAST preferential paths to determine primary candidates for deletion. The list should be in order of least preferable at the end.
|
- **Blacklist** - List of LEAST preferential paths to determine primary candidates for deletion.
|
||||||
- E.g. C:\Downloads,C:\DeleteMe-3rd,C:\DeleteMe-2nd,C:\DeleteMeFirst
|
- E.g. C:\Downloads\,F:\DeleteMeFirst\
|
||||||
- **Permanent Delete** - Enable to permanently delete files, instead of moving files to trash can.
|
- **Permanent Delete** - Enable to permanently delete files, instead of moving files to trash can.
|
||||||
- **Max Dup Process** - Use to limit the maximum files to process. Can be used to do a limited test run.
|
- **Max Dup Process** - Use to limit the maximum files to process. Can be used to do a limited test run.
|
||||||
galleries, rating, details, etc...
|
- **Merge Duplicate Tags** - Before deletion, merge metadata from duplicate. E.g. Tag names, performers, studios, title, galleries, rating, details, etc...
|
||||||
- **Swap High Resolution** - When enabled, swaps higher resolution files between whitelist and blacklist/graylist files.
|
- **Swap High Resolution** - When enabled, swaps higher resolution files between whitelist and blacklist/graylist files.
|
||||||
- **Swap Longer Duration** - When enabled, swaps scene with longer duration.
|
- **Swap Longer Duration** - When enabled, swaps scene with longer duration.
|
||||||
- Options available via DupFileManager_config.py
|
- Options available via DupFileManager_config.py
|
||||||
- **dup_path** - Alternate path to move deleted files to. Example: "C:\TempDeleteFolder"
|
- **dup_path** - Alternate path to move deleted files to. Example: "C:\TempDeleteFolder"
|
||||||
- **toRecycleBeforeSwap** - When enabled, moves destination file to recycle bin before swapping files.
|
- **toRecycleBeforeSwap** - When enabled, moves destination file to recycle bin before swapping files.
|
||||||
- **addPrimaryDupPathToDetails** - If enabled, adds the primary duplicate path to the scene detail.
|
- **addPrimaryDupPathToDetails** - If enabled, adds the primary duplicate path to the scene detail.
|
||||||
- Optionally merge metadata between duplicates before file deletion. (tag names, performers, studios, etc...)
|
|
||||||
- <img width="500" alt="MergeMetadataOption" src="https://github.com/user-attachments/assets/73ca7775-37e4-4409-8dac-1ddc7f31415d">
|
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,29 @@
|
|||||||
"""
|
|
||||||
StashPluginHelper (By David Maisonave aka Axter)
|
|
||||||
See end of this file for example usage
|
|
||||||
Log Features:
|
|
||||||
Can optionally log out to multiple outputs for each Log or Trace call.
|
|
||||||
Logging includes source code line number
|
|
||||||
Sets a maximum plugin log file size
|
|
||||||
Stash Interface Features:
|
|
||||||
Gets STASH_URL value from command line argument and/or from STDIN_READ
|
|
||||||
Sets FRAGMENT_SERVER based on command line arguments or STDIN_READ
|
|
||||||
Sets PLUGIN_ID based on the main script file name (in lower case)
|
|
||||||
Gets PLUGIN_TASK_NAME value
|
|
||||||
Sets pluginSettings (The plugin UI settings)
|
|
||||||
Misc Features:
|
|
||||||
Gets DRY_RUN value from command line argument and/or from UI and/or from config file
|
|
||||||
Gets DEBUG_TRACING value from command line argument and/or from UI and/or from config file
|
|
||||||
Sets RUNNING_IN_COMMAND_LINE_MODE to True if detects multiple arguments
|
|
||||||
Sets CALLED_AS_STASH_PLUGIN to True if it's able to read from STDIN_READ
|
|
||||||
"""
|
|
||||||
from stashapi.stashapp import StashInterface
|
from stashapi.stashapp import StashInterface
|
||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler
|
||||||
import re, inspect, sys, os, pathlib, logging, json, ctypes
|
import re, inspect, sys, os, pathlib, logging, json
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
from stashapi.stash_types import PhashDistance
|
from stashapi.stash_types import PhashDistance
|
||||||
import __main__
|
import __main__
|
||||||
|
|
||||||
_ARGUMENT_UNSPECIFIED_ = "_ARGUMENT_UNSPECIFIED_"
|
_ARGUMENT_UNSPECIFIED_ = "_ARGUMENT_UNSPECIFIED_"
|
||||||
|
|
||||||
|
# StashPluginHelper (By David Maisonave aka Axter)
|
||||||
|
# See end of this file for example usage
|
||||||
|
# Log Features:
|
||||||
|
# Can optionally log out to multiple outputs for each Log or Trace call.
|
||||||
|
# Logging includes source code line number
|
||||||
|
# Sets a maximum plugin log file size
|
||||||
|
# Stash Interface Features:
|
||||||
|
# Gets STASH_URL value from command line argument and/or from STDIN_READ
|
||||||
|
# Sets FRAGMENT_SERVER based on command line arguments or STDIN_READ
|
||||||
|
# Sets PLUGIN_ID based on the main script file name (in lower case)
|
||||||
|
# Gets PLUGIN_TASK_NAME value
|
||||||
|
# Sets pluginSettings (The plugin UI settings)
|
||||||
|
# Misc Features:
|
||||||
|
# Gets DRY_RUN value from command line argument and/or from UI and/or from config file
|
||||||
|
# Gets DEBUG_TRACING value from command line argument and/or from UI and/or from config file
|
||||||
|
# Sets RUNNING_IN_COMMAND_LINE_MODE to True if detects multiple arguments
|
||||||
|
# Sets CALLED_AS_STASH_PLUGIN to True if it's able to read from STDIN_READ
|
||||||
class StashPluginHelper(StashInterface):
|
class StashPluginHelper(StashInterface):
|
||||||
# Primary Members for external reference
|
# Primary Members for external reference
|
||||||
PLUGIN_TASK_NAME = None
|
PLUGIN_TASK_NAME = None
|
||||||
@@ -400,7 +398,20 @@ class StashPluginHelper(StashInterface):
|
|||||||
return result['findDuplicateScenes']
|
return result['findDuplicateScenes']
|
||||||
|
|
||||||
# #################################################################################################
|
# #################################################################################################
|
||||||
# The below functions extends class StashInterface with functions which are not yet in the class
|
# The below functions extends class StashInterface with functions which are not yet in the class or
|
||||||
|
# fixes for functions which have not yet made it into official class.
|
||||||
|
def metadata_scan(self, paths:list=[], flags={}):
|
||||||
|
query = "mutation MetadataScan($input:ScanMetadataInput!) { metadataScan(input: $input) }"
|
||||||
|
scan_metadata_input = {"paths": paths}
|
||||||
|
if flags:
|
||||||
|
scan_metadata_input.update(flags)
|
||||||
|
else:
|
||||||
|
scanData = self.get_configuration_defaults("scan { ...ScanMetadataOptions }")
|
||||||
|
if scanData['scan'] != None:
|
||||||
|
scan_metadata_input.update(scanData.get("scan",{}))
|
||||||
|
result = self.call_GQL(query, {"input": scan_metadata_input})
|
||||||
|
return result["metadataScan"]
|
||||||
|
|
||||||
def get_all_scenes(self):
|
def get_all_scenes(self):
|
||||||
query_all_scenes = """
|
query_all_scenes = """
|
||||||
query AllScenes {
|
query AllScenes {
|
||||||
|
|||||||
@@ -1,31 +1,29 @@
|
|||||||
"""
|
|
||||||
StashPluginHelper (By David Maisonave aka Axter)
|
|
||||||
See end of this file for example usage
|
|
||||||
Log Features:
|
|
||||||
Can optionally log out to multiple outputs for each Log or Trace call.
|
|
||||||
Logging includes source code line number
|
|
||||||
Sets a maximum plugin log file size
|
|
||||||
Stash Interface Features:
|
|
||||||
Gets STASH_URL value from command line argument and/or from STDIN_READ
|
|
||||||
Sets FRAGMENT_SERVER based on command line arguments or STDIN_READ
|
|
||||||
Sets PLUGIN_ID based on the main script file name (in lower case)
|
|
||||||
Gets PLUGIN_TASK_NAME value
|
|
||||||
Sets pluginSettings (The plugin UI settings)
|
|
||||||
Misc Features:
|
|
||||||
Gets DRY_RUN value from command line argument and/or from UI and/or from config file
|
|
||||||
Gets DEBUG_TRACING value from command line argument and/or from UI and/or from config file
|
|
||||||
Sets RUNNING_IN_COMMAND_LINE_MODE to True if detects multiple arguments
|
|
||||||
Sets CALLED_AS_STASH_PLUGIN to True if it's able to read from STDIN_READ
|
|
||||||
"""
|
|
||||||
from stashapi.stashapp import StashInterface
|
from stashapi.stashapp import StashInterface
|
||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler
|
||||||
import re, inspect, sys, os, pathlib, logging, json, ctypes
|
import re, inspect, sys, os, pathlib, logging, json
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
from stashapi.stash_types import PhashDistance
|
from stashapi.stash_types import PhashDistance
|
||||||
import __main__
|
import __main__
|
||||||
|
|
||||||
_ARGUMENT_UNSPECIFIED_ = "_ARGUMENT_UNSPECIFIED_"
|
_ARGUMENT_UNSPECIFIED_ = "_ARGUMENT_UNSPECIFIED_"
|
||||||
|
|
||||||
|
# StashPluginHelper (By David Maisonave aka Axter)
|
||||||
|
# See end of this file for example usage
|
||||||
|
# Log Features:
|
||||||
|
# Can optionally log out to multiple outputs for each Log or Trace call.
|
||||||
|
# Logging includes source code line number
|
||||||
|
# Sets a maximum plugin log file size
|
||||||
|
# Stash Interface Features:
|
||||||
|
# Gets STASH_URL value from command line argument and/or from STDIN_READ
|
||||||
|
# Sets FRAGMENT_SERVER based on command line arguments or STDIN_READ
|
||||||
|
# Sets PLUGIN_ID based on the main script file name (in lower case)
|
||||||
|
# Gets PLUGIN_TASK_NAME value
|
||||||
|
# Sets pluginSettings (The plugin UI settings)
|
||||||
|
# Misc Features:
|
||||||
|
# Gets DRY_RUN value from command line argument and/or from UI and/or from config file
|
||||||
|
# Gets DEBUG_TRACING value from command line argument and/or from UI and/or from config file
|
||||||
|
# Sets RUNNING_IN_COMMAND_LINE_MODE to True if detects multiple arguments
|
||||||
|
# Sets CALLED_AS_STASH_PLUGIN to True if it's able to read from STDIN_READ
|
||||||
class StashPluginHelper(StashInterface):
|
class StashPluginHelper(StashInterface):
|
||||||
# Primary Members for external reference
|
# Primary Members for external reference
|
||||||
PLUGIN_TASK_NAME = None
|
PLUGIN_TASK_NAME = None
|
||||||
@@ -400,7 +398,20 @@ class StashPluginHelper(StashInterface):
|
|||||||
return result['findDuplicateScenes']
|
return result['findDuplicateScenes']
|
||||||
|
|
||||||
# #################################################################################################
|
# #################################################################################################
|
||||||
# The below functions extends class StashInterface with functions which are not yet in the class
|
# The below functions extends class StashInterface with functions which are not yet in the class or
|
||||||
|
# fixes for functions which have not yet made it into official class.
|
||||||
|
def metadata_scan(self, paths:list=[], flags={}):
|
||||||
|
query = "mutation MetadataScan($input:ScanMetadataInput!) { metadataScan(input: $input) }"
|
||||||
|
scan_metadata_input = {"paths": paths}
|
||||||
|
if flags:
|
||||||
|
scan_metadata_input.update(flags)
|
||||||
|
else:
|
||||||
|
scanData = self.get_configuration_defaults("scan { ...ScanMetadataOptions }")
|
||||||
|
if scanData['scan'] != None:
|
||||||
|
scan_metadata_input.update(scanData.get("scan",{}))
|
||||||
|
result = self.call_GQL(query, {"input": scan_metadata_input})
|
||||||
|
return result["metadataScan"]
|
||||||
|
|
||||||
def get_all_scenes(self):
|
def get_all_scenes(self):
|
||||||
query_all_scenes = """
|
query_all_scenes = """
|
||||||
query AllScenes {
|
query AllScenes {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# RenameFile: Ver 0.5.0 (By David Maisonave)
|
# RenameFile: Ver 0.5.1 (By David Maisonave)
|
||||||
RenameFile is a [Stash](https://github.com/stashapp/stash) plugin which performs the following tasks.
|
RenameFile is a [Stash](https://github.com/stashapp/stash) plugin which performs the following tasks.
|
||||||
- **Rename Scene File Name** (On-The-Fly)
|
- **Rename Scene File Name** (On-The-Fly)
|
||||||
- **Append tag names** to file name
|
- **Append tag names** to file name
|
||||||
|
|||||||
@@ -1,31 +1,29 @@
|
|||||||
"""
|
|
||||||
StashPluginHelper (By David Maisonave aka Axter)
|
|
||||||
See end of this file for example usage
|
|
||||||
Log Features:
|
|
||||||
Can optionally log out to multiple outputs for each Log or Trace call.
|
|
||||||
Logging includes source code line number
|
|
||||||
Sets a maximum plugin log file size
|
|
||||||
Stash Interface Features:
|
|
||||||
Gets STASH_URL value from command line argument and/or from STDIN_READ
|
|
||||||
Sets FRAGMENT_SERVER based on command line arguments or STDIN_READ
|
|
||||||
Sets PLUGIN_ID based on the main script file name (in lower case)
|
|
||||||
Gets PLUGIN_TASK_NAME value
|
|
||||||
Sets pluginSettings (The plugin UI settings)
|
|
||||||
Misc Features:
|
|
||||||
Gets DRY_RUN value from command line argument and/or from UI and/or from config file
|
|
||||||
Gets DEBUG_TRACING value from command line argument and/or from UI and/or from config file
|
|
||||||
Sets RUNNING_IN_COMMAND_LINE_MODE to True if detects multiple arguments
|
|
||||||
Sets CALLED_AS_STASH_PLUGIN to True if it's able to read from STDIN_READ
|
|
||||||
"""
|
|
||||||
from stashapi.stashapp import StashInterface
|
from stashapi.stashapp import StashInterface
|
||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler
|
||||||
import re, inspect, sys, os, pathlib, logging, json, ctypes
|
import re, inspect, sys, os, pathlib, logging, json
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
from stashapi.stash_types import PhashDistance
|
from stashapi.stash_types import PhashDistance
|
||||||
import __main__
|
import __main__
|
||||||
|
|
||||||
_ARGUMENT_UNSPECIFIED_ = "_ARGUMENT_UNSPECIFIED_"
|
_ARGUMENT_UNSPECIFIED_ = "_ARGUMENT_UNSPECIFIED_"
|
||||||
|
|
||||||
|
# StashPluginHelper (By David Maisonave aka Axter)
|
||||||
|
# See end of this file for example usage
|
||||||
|
# Log Features:
|
||||||
|
# Can optionally log out to multiple outputs for each Log or Trace call.
|
||||||
|
# Logging includes source code line number
|
||||||
|
# Sets a maximum plugin log file size
|
||||||
|
# Stash Interface Features:
|
||||||
|
# Gets STASH_URL value from command line argument and/or from STDIN_READ
|
||||||
|
# Sets FRAGMENT_SERVER based on command line arguments or STDIN_READ
|
||||||
|
# Sets PLUGIN_ID based on the main script file name (in lower case)
|
||||||
|
# Gets PLUGIN_TASK_NAME value
|
||||||
|
# Sets pluginSettings (The plugin UI settings)
|
||||||
|
# Misc Features:
|
||||||
|
# Gets DRY_RUN value from command line argument and/or from UI and/or from config file
|
||||||
|
# Gets DEBUG_TRACING value from command line argument and/or from UI and/or from config file
|
||||||
|
# Sets RUNNING_IN_COMMAND_LINE_MODE to True if detects multiple arguments
|
||||||
|
# Sets CALLED_AS_STASH_PLUGIN to True if it's able to read from STDIN_READ
|
||||||
class StashPluginHelper(StashInterface):
|
class StashPluginHelper(StashInterface):
|
||||||
# Primary Members for external reference
|
# Primary Members for external reference
|
||||||
PLUGIN_TASK_NAME = None
|
PLUGIN_TASK_NAME = None
|
||||||
@@ -400,7 +398,20 @@ class StashPluginHelper(StashInterface):
|
|||||||
return result['findDuplicateScenes']
|
return result['findDuplicateScenes']
|
||||||
|
|
||||||
# #################################################################################################
|
# #################################################################################################
|
||||||
# The below functions extends class StashInterface with functions which are not yet in the class
|
# The below functions extends class StashInterface with functions which are not yet in the class or
|
||||||
|
# fixes for functions which have not yet made it into official class.
|
||||||
|
def metadata_scan(self, paths:list=[], flags={}):
|
||||||
|
query = "mutation MetadataScan($input:ScanMetadataInput!) { metadataScan(input: $input) }"
|
||||||
|
scan_metadata_input = {"paths": paths}
|
||||||
|
if flags:
|
||||||
|
scan_metadata_input.update(flags)
|
||||||
|
else:
|
||||||
|
scanData = self.get_configuration_defaults("scan { ...ScanMetadataOptions }")
|
||||||
|
if scanData['scan'] != None:
|
||||||
|
scan_metadata_input.update(scanData.get("scan",{}))
|
||||||
|
result = self.call_GQL(query, {"input": scan_metadata_input})
|
||||||
|
return result["metadataScan"]
|
||||||
|
|
||||||
def get_all_scenes(self):
|
def get_all_scenes(self):
|
||||||
query_all_scenes = """
|
query_all_scenes = """
|
||||||
query AllScenes {
|
query AllScenes {
|
||||||
|
|||||||
@@ -313,6 +313,7 @@ def rename_scene(scene_id):
|
|||||||
shutil.move(original_file_path, new_file_path)
|
shutil.move(original_file_path, new_file_path)
|
||||||
exitMsg = f"{dry_run_prefix}Moved file to '{new_file_path}' from '{original_file_path}'"
|
exitMsg = f"{dry_run_prefix}Moved file to '{new_file_path}' from '{original_file_path}'"
|
||||||
else:
|
else:
|
||||||
|
stash.Trace(f"Rename('{original_file_path}', '{new_file_path}')")
|
||||||
if not dry_run:
|
if not dry_run:
|
||||||
os.rename(original_file_path, new_file_path)
|
os.rename(original_file_path, new_file_path)
|
||||||
exitMsg = f"{dry_run_prefix}Renamed file to '{new_file_path}' from '{original_file_path}'"
|
exitMsg = f"{dry_run_prefix}Renamed file to '{new_file_path}' from '{original_file_path}'"
|
||||||
@@ -326,6 +327,7 @@ def rename_scene(scene_id):
|
|||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
stash.Trace(f"scan path={original_parent_directory.resolve().as_posix()}")
|
||||||
stash.metadata_scan(paths=[original_parent_directory.resolve().as_posix()])
|
stash.metadata_scan(paths=[original_parent_directory.resolve().as_posix()])
|
||||||
stash.Log(exitMsg)
|
stash.Log(exitMsg)
|
||||||
return new_filename
|
return new_filename
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
name: RenameFile
|
name: RenameFile
|
||||||
description: Renames video (scene) file names when the user edits the [Title] field located in the scene [Edit] tab.
|
description: Renames video (scene) file names when the user edits the [Title] field located in the scene [Edit] tab.
|
||||||
version: 0.5.0
|
version: 0.5.1
|
||||||
url: https://github.com/David-Maisonave/Axter-Stash/tree/main/plugins/RenameFile
|
url: https://github.com/David-Maisonave/Axter-Stash/tree/main/plugins/RenameFile
|
||||||
settings:
|
settings:
|
||||||
performerAppend:
|
performerAppend:
|
||||||
|
|||||||
Reference in New Issue
Block a user