Added UI interface for DupFileManager

This commit is contained in:
David Maisonave
2024-10-28 06:15:03 -04:00
parent 174b489bc8
commit be8e632699
12 changed files with 377 additions and 95 deletions

View File

@@ -0,0 +1,36 @@
.scene-card__date {
color: #bfccd6;
font-size: 0.85em;
}
.scene-card__performer {
display: inline-block;
font-weight: 500;
margin-right: 0.5em;
}
.scene-card__performer a {
color: #137cbd;
}
.scene-card__performers,
.scene-card__tags {
-webkit-box-orient: vertical;
display: -webkit-box;
-webkit-line-clamp: 1;
overflow: hidden;
}
.scene-card__performers:hover,
.scene-card__tags:hover {
-webkit-line-clamp: unset;
overflow: visible;
}
.scene-card__tags .tag-item {
margin-left: 0;
}
.scene-performer-popover .image-thumbnail {
margin: 1em;
}
/*# sourceMappingURL=DupFileManager.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sourceRoot":"","sources":["../src/DupFileManager.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;;;AAIJ;AAAA;EAEE;EACA;EACA;EACA;;AAEA;AAAA;EACE;EACA;;;AAIJ;EACE;;;AAGF;EACE","file":"DupFileManager.css"}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"version":3,"file":"DupFileManager.js","sourceRoot":"","sources":["../src/DupFileManager.tsx"],"names":[],"mappings":"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"}

View File

@@ -4,7 +4,7 @@
# Note: To call this script outside of Stash, pass argument --url
# Example: python DupFileManager.py --url http://localhost:9999 -a
import ModulesValidate
ModulesValidate.modulesInstalled(["send2trash", "requests"])
ModulesValidate.modulesInstalled(["send2trash", "requests"], silent=True)
import os, sys, time, pathlib, argparse, platform, shutil, traceback, logging, requests
from datetime import datetime
from StashPluginHelper import StashPluginHelper
@@ -46,11 +46,17 @@ stash = StashPluginHelper(
DebugFieldName="zzDebug",
)
stash.convertToAscii = True
doJsonReturnModeTypes = ["tag_duplicates_task", "removeDupTag", "addExcludeTag", "removeExcludeTag", "mergeTags", "getLocalDupReportPath", "createDuplicateReportWithoutTagging", "deleteLocalDupReportHtmlFiles"]
doJsonReturn = False
if stash.PLUGIN_TASK_NAME in doJsonReturnModeTypes:
doJsonReturn = True
stash.log_to_norm = stash.LogTo.FILE
stash.Log("******************* Starting *******************")
if len(sys.argv) > 1:
stash.Log(f"argv = {sys.argv}")
else:
stash.Trace(f"No command line arguments. JSON_INPUT['args'] = {stash.JSON_INPUT['args']}")
stash.Trace(f"No command line arguments. JSON_INPUT['args'] = {stash.JSON_INPUT['args']}; PLUGIN_TASK_NAME = {stash.PLUGIN_TASK_NAME}")
stash.status(logLevel=logging.DEBUG)
@@ -482,6 +488,8 @@ def logReason(DupFileToKeep, Scene, reason):
reasonDict[DupFileToKeep['id']] = reason
stash.Debug(f"Replacing {DupFileToKeep['files'][0]['path']} with {Scene['files'][0]['path']} for candidate to keep. Reason={reason}")
htmlReportName = f"{stash.PLUGINS_PATH}{os.sep}{stash.Setting('htmlReportName')}"
def mangeDupFiles(merge=False, deleteDup=False, tagDuplicates=False):
global reasonDict
duplicateMarkForDeletion_descp = 'Tag added to duplicate scenes so-as to tag them for deletion.'
@@ -490,7 +498,6 @@ def mangeDupFiles(merge=False, deleteDup=False, tagDuplicates=False):
stash.Trace(f"dupTagId={dupTagId} name={duplicateMarkForDeletion}")
createHtmlReport = stash.Setting('createHtmlReport')
previewOrStream = "stream" if stash.Setting('streamOverPreview') else "preview"
htmlReportName = f"{stash.PLUGINS_PATH}{os.sep}{stash.Setting('htmlReportName')}"
htmlReportNameHomePage = htmlReportName
htmlReportTableRow = stash.Setting('htmlReportTableRow')
htmlReportTableData = stash.Setting('htmlReportTableData')
@@ -661,10 +668,12 @@ def mangeDupFiles(merge=False, deleteDup=False, tagDuplicates=False):
elif moveToTrashCan:
sendToTrash(DupFileName)
stash.destroyScene(DupFile['id'], delete_file=True)
elif tagDuplicates:
elif tagDuplicates or fileHtmlReport != None:
QtyTagForDel+=1
QtyTagForDelPaginate+=1
didAddTag = setTagId_withRetry(duplicateMarkForDeletion, DupFile, DupFileToKeep, ignoreAutoTag=True)
didAddTag = False
if tagDuplicates:
didAddTag = setTagId_withRetry(duplicateMarkForDeletion, DupFile, DupFileToKeep, ignoreAutoTag=True)
if fileHtmlReport != None:
stash.Debug(f"Adding scene {DupFile['id']} to HTML report.")
dupFileExist = True if os.path.isfile(DupFile['files'][0]['path']) else False
@@ -698,9 +707,9 @@ def mangeDupFiles(merge=False, deleteDup=False, tagDuplicates=False):
fileHtmlReport.write("</table>")
fileHtmlReport.write(f"<button class=\"link-button\" title=\"Delete file and remove scene from stash\" value=\"Duplicate\" id=\"{DupFile['id']}\">[Delete]</button>")
if dupFileExist:
fileHtmlReport.write(f"<button class=\"link-button\" title=\"Remove duplicate tag from scene\" value=\"RemoveDupTag\" id=\"{DupFile['id']}\">[Remove Tag]</button>")
fileHtmlReport.write(f"<button class=\"link-button\" title=\"Add exclude scene from deletion tag\" value=\"AddExcludeTag\" id=\"{DupFile['id']}\">[Add Exclude Tag]</button>")
if dupFileExist and tagDuplicates:
fileHtmlReport.write(f"<button class=\"link-button\" title=\"Remove duplicate tag from scene\" value=\"removeDupTag\" id=\"{DupFile['id']}\">[Remove Tag]</button>")
fileHtmlReport.write(f"<button class=\"link-button\" title=\"Add exclude scene from deletion tag\" value=\"addExcludeTag\" id=\"{DupFile['id']}\">[Add Exclude Tag]</button>")
fileHtmlReport.write(f"<button class=\"link-button\" title=\"Merge duplicate scene tags with ToKeep scene tags\" value=\"mergeTags\" id=\"{DupFile['id']}:{DupFileToKeep['id']}\">[Merge Tags]</button>")
if dupFileExist:
fileHtmlReport.write(f"<a class=\"link-items\" title=\"Open folder\" href=\"file://{getPath(DupFile, True)}\">[Folder]</a>")
@@ -718,7 +727,7 @@ def mangeDupFiles(merge=False, deleteDup=False, tagDuplicates=False):
fileHtmlReport.write(f"<tr class=\"scene-details\"><td>{DupFileToKeep['files'][0]['width']}x{DupFileToKeep['files'][0]['height']}</td><td>{DupFileToKeep['files'][0]['duration']}</td><td>{DupFileToKeep['files'][0]['bit_rate']}</td><td>{DupFileToKeep['files'][0]['video_codec']}</td><td>{DupFileToKeep['files'][0]['frame_rate']}</td><td>{DupFileToKeep['files'][0]['size']}</td><td>{DupFileToKeep['id']}</td></tr></table>")
fileHtmlReport.write(f"<button class=\"link-button\" title=\"Delete [DupFileToKeep] and remove scene from stash\" value=\"ToKeep\" id=\"{DupFileToKeep['id']}\">[Delete]</button>")
if isTaggedExcluded(DupFileToKeep):
fileHtmlReport.write(f"<button class=\"link-button\" title=\"Remove exclude scene from deletion tag\" value=\"RemoveExcludeTag\" id=\"{DupFileToKeep['id']}\">[Remove Exclude Tag]</button>")
fileHtmlReport.write(f"<button class=\"link-button\" title=\"Remove exclude scene from deletion tag\" value=\"removeExcludeTag\" id=\"{DupFileToKeep['id']}\">[Remove Exclude Tag]</button>")
fileHtmlReport.write(f"<a class=\"link-items\" title=\"Open folder\" href=\"file://{getPath(DupFileToKeep, True)}\">[Folder]</a>")
if toKeepFileExist:
fileHtmlReport.write(f"<a class=\"link-items\" title=\"Play file locally\" href=\"file://{getPath(DupFileToKeep)}\">[Play]</a>")
@@ -762,7 +771,7 @@ def mangeDupFiles(merge=False, deleteDup=False, tagDuplicates=False):
fileHtmlReport.write(f"{stash.Setting('htmlReportTable')}\n")
fileHtmlReport.write(f"{htmlReportTableRow}{htmlReportTableHeader}Scene</th>{htmlReportTableHeader}Duplicate to Delete</th>{htmlReportTableHeader}Scene-ToKeep</th>{htmlReportTableHeader}Duplicate to Keep</th></tr>\n")
if graylistTagging and stash.startsWithInList(graylist, DupFile['files'][0]['path']):
if tagDuplicates and graylistTagging and stash.startsWithInList(graylist, DupFile['files'][0]['path']):
stash.addTag(DupFile, graylistMarkForDeletion, ignoreAutoTag=True)
if didAddTag:
QtyNewlyTag+=1
@@ -831,6 +840,7 @@ def mangeDupFiles(merge=False, deleteDup=False, tagDuplicates=False):
stash.optimise_database()
if doGeneratePhash:
stash.metadata_generate({"phashes": True})
sys.stdout.write("Report complete")
def findCurrentTagId(tagNames):
tagNames = [i for n, i in enumerate(tagNames) if i not in tagNames[:n]]
@@ -932,34 +942,42 @@ def manageTagggedDuplicates(deleteScenes=False, clearTag=False, setGrayListTag=F
stash.metadata_generate({"phashes": True})
def removeDupTag():
if 'removeDupTag' not in stash.JSON_INPUT['args']:
stash.Error(f"Could not find removeDupTag in JSON_INPUT ({stash.JSON_INPUT['args']})")
if 'Target' not in stash.JSON_INPUT['args']:
stash.Error(f"Could not find Target in JSON_INPUT ({stash.JSON_INPUT['args']})")
return
sceneToRemoveTag = stash.JSON_INPUT['args']['removeDupTag']
stash.removeTag(sceneToRemoveTag, duplicateMarkForDeletion)
stash.Log(f"Done removing tag from scene {sceneToRemoveTag}.")
scene = stash.JSON_INPUT['args']['Target']
stash.Log(f"Processing scene ID# {scene}")
stash.removeTag(scene, duplicateMarkForDeletion)
stash.Log(f"Done removing tag from scene {scene}.")
jsonReturn = "{'removeDupTag' : 'complete', 'id': '" + f"{scene}" + "'}"
stash.Log(f"Sending json value {jsonReturn}")
sys.stdout.write(jsonReturn)
def addExcludeForDelTag():
if 'addExcludeForDelTag' not in stash.JSON_INPUT['args']:
stash.Error(f"Could not find addExcludeForDelTag in JSON_INPUT ({stash.JSON_INPUT['args']})")
def addExcludeTag():
if 'Target' not in stash.JSON_INPUT['args']:
stash.Error(f"Could not find Target in JSON_INPUT ({stash.JSON_INPUT['args']})")
return
scene = stash.JSON_INPUT['args']['addExcludeForDelTag']
scene = stash.JSON_INPUT['args']['Target']
stash.Log(f"Processing scene ID# {scene}")
stash.addTag(scene, excludeDupFileDeleteTag)
stash.Log(f"Done adding exclude tag to scene {scene}.")
sys.stdout.write("{" + f"addExcludeTag : 'complete', id: '{scene}'" + "}")
def removeExcludeForDelTag():
if 'removeExcludeForDelTag' not in stash.JSON_INPUT['args']:
stash.Error(f"Could not find removeExcludeForDelTag in JSON_INPUT ({stash.JSON_INPUT['args']})")
def removeExcludeTag():
if 'Target' not in stash.JSON_INPUT['args']:
stash.Error(f"Could not find Target in JSON_INPUT ({stash.JSON_INPUT['args']})")
return
scene = stash.JSON_INPUT['args']['removeExcludeForDelTag']
scene = stash.JSON_INPUT['args']['Target']
stash.Log(f"Processing scene ID# {scene}")
stash.removeTag(scene, excludeDupFileDeleteTag)
stash.Log(f"Done removing exclude tag from scene {scene}.")
sys.stdout.write("{" + f"removeExcludeTag : 'complete', id: '{scene}'" + "}")
def mergeTags():
if 'mergeScenes' not in stash.JSON_INPUT['args']:
stash.Error(f"Could not find mergeScenes in JSON_INPUT ({stash.JSON_INPUT['args']})")
if 'Target' not in stash.JSON_INPUT['args']:
stash.Error(f"Could not find Target in JSON_INPUT ({stash.JSON_INPUT['args']})")
return
mergeScenes = stash.JSON_INPUT['args']['mergeScenes']
mergeScenes = stash.JSON_INPUT['args']['Target']
scenes = mergeScenes.split(":")
if len(scenes) < 2:
stash.Error(f"Could not get both scenes from string {mergeScenes}")
@@ -969,6 +987,55 @@ def mergeTags():
scene2 = stash.find_scene(int(scenes[1]))
stash.mergeMetadata(scene1, scene2)
stash.Log(f"Done merging scenes for scene {scenes[0]} and scene {scenes[1]}")
sys.stdout.write("{" + f"mergeTags : 'complete', id1: '{scene1}', id2: '{scene2}'" + "}")
def openWebpage():
htmlReportName = f"{stash.PLUGINS_PATH}{os.sep}{stash.Setting('htmlReportName')}"
stash.Log(f"Opening web page {htmlReportName}")
# chromePath = r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
chromePath = r"C:\PROGRA~2\Google\Chrome\Application\chrome.exe"
firefox = r"C:\PROGRA~1\MOZILL~1\firefox.exe"
browserPath = chromePath
if os.path.isfile(browserPath):
stash.Log(f"Opening web page {htmlReportName} with user {os.getlogin()} using {browserPath}")
# pw_record = pwd.getpwnam(os.getlogin())
# uid = pw_record.pw_uid
# gid = pw_record.pw_gid
# os.setgid(gid)
# os.setuid(uid)
# stash.Log(f"Using uid={uid} and gid={gid}")
# stash.executeProcess(f"\"{browserPath}\" -incognito --new-window \"file://{htmlReportName}\"", ExecDetach=True)
os.system(f"{browserPath} -incognito --new-window \"file://{htmlReportName}\"")
# else:
import webbrowser
webbrowser.open(htmlReportName, new=2, autoraise=True)
os.system(f"start file:///{htmlReportName}")
def getLocalDupReportPath():
htmlReportExist = "true" if os.path.isfile(htmlReportName) else "false"
# htmlReportExist = "false"
localPath = htmlReportName.replace("\\", "\\\\")
jsonReturn = "{'LocalDupReportExist' : " + f"{htmlReportExist}" + ", 'Path': '" + f"{localPath}" + "'}"
stash.Log(f"Sending json value {jsonReturn}")
sys.stdout.write(jsonReturn)
def deleteLocalDupReportHtmlFiles():
htmlReportExist = "true" if os.path.isfile(htmlReportName) else "false"
if os.path.isfile(htmlReportName):
stash.Log(f"Deleting file {htmlReportName}")
os.remove(htmlReportName)
for x in range(2, 9999):
fileName = htmlReportName.replace(".html", f"_{x-1}.html")
stash.Debug(f"Checking if file '{fileName}' exist.")
if not os.path.isfile(fileName):
break
stash.Log(f"Deleting file {fileName}")
os.remove(fileName)
else:
stash.Log(f"Report file does not exist: {htmlReportName}")
jsonReturn = "{'LocalDupReportExist' : " + f"{htmlReportExist}" + ", 'Path': '" + f"{htmlReportName}" + "', 'qty': '" + f"{x}" + "'}"
stash.Log(f"Sending json value {jsonReturn}")
sys.stdout.write(jsonReturn)
# ToDo: Add additional menu items option only for bottom of report:
# Remove from stash all files no longer part of stash library
@@ -993,18 +1060,27 @@ try:
elif stash.PLUGIN_TASK_NAME == "generate_phash_task":
stash.metadata_generate({"phashes": True})
stash.Debug(f"{stash.PLUGIN_TASK_NAME} EXIT")
elif stash.PLUGIN_TASK_NAME == "remove_a_duplicate_tag":
elif stash.PLUGIN_TASK_NAME == "removeDupTag":
removeDupTag()
stash.Debug(f"{stash.PLUGIN_TASK_NAME} EXIT")
elif stash.PLUGIN_TASK_NAME == "add_an_exclude_tag":
addExcludeForDelTag()
elif stash.PLUGIN_TASK_NAME == "addExcludeTag":
addExcludeTag()
stash.Debug(f"{stash.PLUGIN_TASK_NAME} EXIT")
elif stash.PLUGIN_TASK_NAME == "remove_an_exclude_tag":
removeExcludeForDelTag()
elif stash.PLUGIN_TASK_NAME == "removeExcludeTag":
removeExcludeTag()
stash.Debug(f"{stash.PLUGIN_TASK_NAME} EXIT")
elif stash.PLUGIN_TASK_NAME == "merge_tags":
elif stash.PLUGIN_TASK_NAME == "mergeTags":
mergeTags()
stash.Debug(f"{stash.PLUGIN_TASK_NAME} EXIT")
elif stash.PLUGIN_TASK_NAME == "getLocalDupReportPath":
getLocalDupReportPath()
stash.Debug(f"{stash.PLUGIN_TASK_NAME} EXIT")
elif stash.PLUGIN_TASK_NAME == "deleteLocalDupReportHtmlFiles":
deleteLocalDupReportHtmlFiles()
stash.Debug(f"{stash.PLUGIN_TASK_NAME} EXIT")
elif stash.PLUGIN_TASK_NAME == "createDuplicateReportWithoutTagging":
mangeDupFiles(tagDuplicates=False, merge=mergeDupFilename)
stash.Debug(f"{stash.PLUGIN_TASK_NAME} EXIT")
elif parse_args.dup_tag:
stash.PLUGIN_TASK_NAME = "dup_tag"
mangeDupFiles(tagDuplicates=True, merge=mergeDupFilename)
@@ -1030,5 +1106,7 @@ except Exception as e:
killScanningJobs()
stash.convertToAscii = False
stash.Error(f"Error: {e}\nTraceBack={tb}")
if doJsonReturn:
sys.stdout.write("{" + f"Exception : '{e}; See log file for TraceBack' " + "}")
stash.Log("\n*********************************\nEXITING ***********************\n*********************************")

View File

@@ -2,6 +2,13 @@ name: DupFileManager
description: Manages duplicate files.
version: 0.1.6
url: https://github.com/David-Maisonave/Axter-Stash/tree/main/plugins/DupFileManager
ui:
javascript:
- DupFileManager.js
css:
- DupFileManager.css
- DupFileManager.css.map
- DupFileManager.js.map
settings:
mergeDupFilename:
displayName: Merge Duplicate Tags

View File

@@ -118,7 +118,17 @@ table, th, td {border:1px solid black;}
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script>
var superQuery = `{ mutation SceneDestroy($input:SceneDestroyInput!) {sceneDestroy(input: $input)} }`
function RunPluginDupFileManager(Mode, ActionID, chkBxRemoveValid, button) {
$.ajax({method: "POST", url: "http://127.0.0.1:9999/graphql", contentType: "application/json", dataType: "text",
data: JSON.stringify({
query: `mutation RunPluginOperation($plugin_id:ID!,$args:Map!){runPluginOperation(plugin_id:$plugin_id,args:$args)}`,
variables: {"plugin_id": "DupFileManager", "args": { "Target" : ActionID, "mode":Mode}},
}), success: function(result){
console.log(result);
button.style.visibility = 'hidden';
if (!chkBxRemoveValid.checked) alert("Action " + Mode + " for scene(s) ID# " + ActionID + " complete.")
}});
}
$(document).ready(function(){
$("button").click(function(){
chkBxRemoveValid = document.getElementById("RemoveValidatePrompt");
@@ -128,64 +138,19 @@ $(document).ready(function(){
if (!chkBxDisableDeleteConfirm.checked && !confirm("Are you sure you want to delete this file and remove scene from stash?")) {
return
}
const SceneId = this.id;
$.ajax({method: "POST", url: "http://127.0.0.1:9999/graphql", contentType: "application/json",
data: JSON.stringify({
query: `mutation SceneDestroy($input:SceneDestroyInput!) {sceneDestroy(input: $input)}`,
variables: {"input":{"delete_file":true,"id":this.id}},
variables: {"input":{"delete_file":true,"id":SceneId}},
}), success: function(result){
$("#div1").html(result);
console.log(result);
if (!chkBxRemoveValid.checked) alert("Delete request received for scene ID# " + SceneId)
}});
this.style.visibility = 'hidden';
if (!chkBxRemoveValid.checked) alert("Sent delete request for scene ID# " + this.id)
}
else if (this.value === "RemoveDupTag")
{
$.ajax({method: "POST", url: "http://127.0.0.1:9999/graphql", contentType: "application/json",
data: JSON.stringify({
query: `mutation RunPluginOperation($plugin_id:ID!,$args:Map!){runPluginOperation(plugin_id:$plugin_id,args:$args)}`,
variables: {"plugin_id": "DupFileManager", "args": {"removeDupTag":this.id, "mode":"remove_a_duplicate_tag"}},
}), success: function(result){
$("#div1").html(result);
}});
this.style.visibility = 'hidden';
if (!chkBxRemoveValid.checked) alert("Sent remove duplicate tag request for scene ID# " + this.id)
}
else if (this.value === "AddExcludeTag")
{
$.ajax({method: "POST", url: "http://127.0.0.1:9999/graphql", contentType: "application/json",
data: JSON.stringify({
query: `mutation RunPluginOperation($plugin_id:ID!,$args:Map!){runPluginOperation(plugin_id:$plugin_id,args:$args)}`,
variables: {"plugin_id": "DupFileManager", "args": {"addExcludeForDelTag":this.id, "mode":"add_an_exclude_tag"}},
}), success: function(result){
$("#div1").html(result);
}});
this.style.visibility = 'hidden';
if (!chkBxRemoveValid.checked) alert("Sent add exclude tag request for scene ID# " + this.id)
}
else if (this.value === "RemoveExcludeTag")
{
$.ajax({method: "POST", url: "http://127.0.0.1:9999/graphql", contentType: "application/json",
data: JSON.stringify({
query: `mutation RunPluginOperation($plugin_id:ID!,$args:Map!){runPluginOperation(plugin_id:$plugin_id,args:$args)}`,
variables: {"plugin_id": "DupFileManager", "args": {"removeExcludeForDelTag":this.id, "mode":"remove_an_exclude_tag"}},
}), success: function(result){
$("#div1").html(result);
}});
this.style.visibility = 'hidden';
if (!chkBxRemoveValid.checked) alert("Sent remove exclude tag request for scene ID# " + this.id)
}
else if (this.value === "mergeTags")
{
$.ajax({method: "POST", url: "http://127.0.0.1:9999/graphql", contentType: "application/json",
data: JSON.stringify({
query: `mutation RunPluginOperation($plugin_id:ID!,$args:Map!){runPluginOperation(plugin_id:$plugin_id,args:$args)}`,
variables: {"plugin_id": "DupFileManager", "args": {"mergeScenes":this.id, "mode":"merge_tags"}},
}), success: function(result){
$("#div1").html(result);
}});
this.style.visibility = 'hidden';
if (!chkBxRemoveValid.checked) alert("Sent merge scene request for scenes " + this.id)
}
else
RunPluginDupFileManager(this.value, this.id, chkBxRemoveValid, this)
});
});
</script>

View File

@@ -627,7 +627,7 @@ class StashPluginHelper(StashInterface):
def removeTag(self, scene, tagName): # scene can be scene ID or scene metadata
scene_details = scene
if 'id' not in scene:
if isinstance(scene, int) or 'id' not in scene:
scene_details = self.find_scene(scene)
tagIds = []
doesHaveTagName = False
@@ -649,7 +649,7 @@ class StashPluginHelper(StashInterface):
if errMsg != None:
self.Warn(errMsg)
scene_details = scene
if 'id' not in scene:
if isinstance(scene, int) or 'id' not in scene:
scene_details = self.find_scene(scene)
tagIds = [self.createTagId(tagName, tagName_descp=tagName_descp, ignoreAutoTag=ignoreAutoTag)]
for tag in scene_details['tags']:
@@ -884,7 +884,8 @@ class mergeMetadata: # A class to merge scene metadata from source scene to dest
self.mergeItems('tags', 'tag_ids', [], excludeName=self.excludeMergeTags)
self.mergeItems('performers', 'performer_ids', [])
self.mergeItems('galleries', 'gallery_ids', [])
self.mergeItems('movies', 'movies', [])
# ToDo: Need to find out why the following line no longer works in new Stash version
# self.mergeItems('movies', 'movies', [])
self.mergeItems('urls', listToAdd=self.destData['urls'], NotStartWith=self.stash.STASH_URL)
self.mergeItem('studio', 'studio_id', 'id')
self.mergeItem('title')

View File

@@ -627,7 +627,7 @@ class StashPluginHelper(StashInterface):
def removeTag(self, scene, tagName): # scene can be scene ID or scene metadata
scene_details = scene
if 'id' not in scene:
if isinstance(scene, int) or 'id' not in scene:
scene_details = self.find_scene(scene)
tagIds = []
doesHaveTagName = False
@@ -649,7 +649,7 @@ class StashPluginHelper(StashInterface):
if errMsg != None:
self.Warn(errMsg)
scene_details = scene
if 'id' not in scene:
if isinstance(scene, int) or 'id' not in scene:
scene_details = self.find_scene(scene)
tagIds = [self.createTagId(tagName, tagName_descp=tagName_descp, ignoreAutoTag=ignoreAutoTag)]
for tag in scene_details['tags']:
@@ -884,7 +884,8 @@ class mergeMetadata: # A class to merge scene metadata from source scene to dest
self.mergeItems('tags', 'tag_ids', [], excludeName=self.excludeMergeTags)
self.mergeItems('performers', 'performer_ids', [])
self.mergeItems('galleries', 'gallery_ids', [])
self.mergeItems('movies', 'movies', [])
# ToDo: Need to find out why the following line no longer works in new Stash version
# self.mergeItems('movies', 'movies', [])
self.mergeItems('urls', listToAdd=self.destData['urls'], NotStartWith=self.stash.STASH_URL)
self.mergeItem('studio', 'studio_id', 'id')
self.mergeItem('title')

View File

@@ -4,7 +4,7 @@
# Note: To call this script outside of Stash, pass argument --url and the Stash URL.
# Example: python filemonitor.py --url http://localhost:9999
import ModulesValidate
ModulesValidate.modulesInstalled(["watchdog", "schedule", "requests"])
ModulesValidate.modulesInstalled(["stashapp-tools", "watchdog", "schedule", "requests"])
from StashPluginHelper import StashPluginHelper
import os, sys, time, pathlib, argparse, platform, traceback, logging
from StashPluginHelper import taskQueue

View File

@@ -627,7 +627,7 @@ class StashPluginHelper(StashInterface):
def removeTag(self, scene, tagName): # scene can be scene ID or scene metadata
scene_details = scene
if 'id' not in scene:
if isinstance(scene, int) or 'id' not in scene:
scene_details = self.find_scene(scene)
tagIds = []
doesHaveTagName = False
@@ -649,7 +649,7 @@ class StashPluginHelper(StashInterface):
if errMsg != None:
self.Warn(errMsg)
scene_details = scene
if 'id' not in scene:
if isinstance(scene, int) or 'id' not in scene:
scene_details = self.find_scene(scene)
tagIds = [self.createTagId(tagName, tagName_descp=tagName_descp, ignoreAutoTag=ignoreAutoTag)]
for tag in scene_details['tags']:
@@ -884,7 +884,8 @@ class mergeMetadata: # A class to merge scene metadata from source scene to dest
self.mergeItems('tags', 'tag_ids', [], excludeName=self.excludeMergeTags)
self.mergeItems('performers', 'performer_ids', [])
self.mergeItems('galleries', 'gallery_ids', [])
self.mergeItems('movies', 'movies', [])
# ToDo: Need to find out why the following line no longer works in new Stash version
# self.mergeItems('movies', 'movies', [])
self.mergeItems('urls', listToAdd=self.destData['urls'], NotStartWith=self.stash.STASH_URL)
self.mergeItem('studio', 'studio_id', 'id')
self.mergeItem('title')

View File

@@ -3,7 +3,7 @@
# Get the latest developers version from following link: https://github.com/David-Maisonave/Axter-Stash/tree/main/plugins/RenameFile
# Based on source code from https://github.com/Serechops/Serechops-Stash/tree/main/plugins/Renamer
import ModulesValidate
ModulesValidate.modulesInstalled(["requests"])
ModulesValidate.modulesInstalled(["stashapp-tools", "requests"])
import os, sys, shutil, json, hashlib, pathlib, logging, time, traceback
from pathlib import Path
import stashapi.log as log # Importing stashapi.log as log for critical events ONLY