forked from Github/Axter-Stash
In the report, made icon colors for tags, performers, galleries, and groups with different colors if they don't match.
### 1.0.0.2 - In the report, made icon colors for tags, performers, galleries, and groups with different colors if they don't match. In other words, use different color icons if **candidate to delete** doesn't match **duplicate to keep** associated icon data. - If data for associated icon are the same, then both icons are black or blue (the default color). - If [**duplicate to keep**] is missing data that is in [**candidate to delete**], than [**candidate to delete**] gets a yellow icon. - If [**candidate to delete**] is missing data that is in [**duplicate to keep**], than [**duplicate to keep**] gets a pink icon.
This commit is contained in:
@@ -557,6 +557,39 @@ def getSceneID(scene):
|
|||||||
def fileNameClassID(scene):
|
def fileNameClassID(scene):
|
||||||
return f" class=\"FN_ID_{scene['id']}\" "
|
return f" class=\"FN_ID_{scene['id']}\" "
|
||||||
|
|
||||||
|
def doesDelCandidateHaveMetadataNotInDupToKeep(DupFile, DupFileToKeep, listName, itemName = 'name'):
|
||||||
|
DelCandidateItems = []
|
||||||
|
DupToKeepItems = []
|
||||||
|
DupToKeepMissingItem = False
|
||||||
|
DelCandidateMissingItem = False
|
||||||
|
for item in DupFileToKeep[listName]:
|
||||||
|
if listName != 'tags' or not item['ignore_auto_tag']:
|
||||||
|
if listName == "groups":
|
||||||
|
DupToKeepItems += [item['group']['name']]
|
||||||
|
elif listName == "galleries":
|
||||||
|
item = stash.getGalleryName(item['id'])
|
||||||
|
DupToKeepItems += [item[itemName]]
|
||||||
|
else:
|
||||||
|
DupToKeepItems += [item[itemName]]
|
||||||
|
for item in DupFile[listName]:
|
||||||
|
if listName != 'tags' or not item['ignore_auto_tag']:
|
||||||
|
if listName == "groups":
|
||||||
|
name = item['group'][itemName]
|
||||||
|
elif listName == "galleries":
|
||||||
|
item = stash.getGalleryName(item['id'])
|
||||||
|
name = item[itemName]
|
||||||
|
else:
|
||||||
|
name = item[itemName]
|
||||||
|
DelCandidateItems += [name]
|
||||||
|
if name not in DupToKeepItems:
|
||||||
|
DupToKeepMissingItem = True
|
||||||
|
for name in DupToKeepItems:
|
||||||
|
if name not in DelCandidateItems:
|
||||||
|
DelCandidateMissingItem = True
|
||||||
|
break
|
||||||
|
return DupToKeepMissingItem, DelCandidateMissingItem
|
||||||
|
|
||||||
|
|
||||||
htmlReportNameFolder = f"{stash.PLUGINS_PATH}{os.sep}DupFileManager{os.sep}report"
|
htmlReportNameFolder = f"{stash.PLUGINS_PATH}{os.sep}DupFileManager{os.sep}report"
|
||||||
htmlReportName = f"{htmlReportNameFolder}{os.sep}{stash.Setting('htmlReportName')}"
|
htmlReportName = f"{htmlReportNameFolder}{os.sep}{stash.Setting('htmlReportName')}"
|
||||||
htmlReportTableRow = stash.Setting('htmlReportTableRow')
|
htmlReportTableRow = stash.Setting('htmlReportTableRow')
|
||||||
@@ -568,10 +601,14 @@ htmlPreviewOrStream = "stream" if stash.Setting('streamOverPreview') els
|
|||||||
|
|
||||||
def writeRowToHtmlReport(fileHtmlReport, DupFile, DupFileToKeep, QtyTagForDel = "?", tagDuplicates = False):
|
def writeRowToHtmlReport(fileHtmlReport, DupFile, DupFileToKeep, QtyTagForDel = "?", tagDuplicates = False):
|
||||||
fileDoesNotExistStr = "<b style='color:red;background-color:yellow;font-size:10px;'>[File NOT Exist]<b>"
|
fileDoesNotExistStr = "<b style='color:red;background-color:yellow;font-size:10px;'>[File NOT Exist]<b>"
|
||||||
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">'
|
defaultColorTag = "BlueTag.png"
|
||||||
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">'
|
defaultColorPerformer = "Headshot.png"
|
||||||
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">'
|
defaultColorGalleries = "Galleries.png"
|
||||||
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">'
|
defaultColorGroup = "Group.png"
|
||||||
|
htmlTagPrefix = '<div class="dropdown_icon"><img src="https://www.axter.com/images/stash/' + defaultColorTag + '" 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/' + defaultColorPerformer + '" 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/' + defaultColorGalleries + '" 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/' + defaultColorGroup + '" 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
|
dupFileExist = True if os.path.isfile(DupFile['files'][0]['path']) else False
|
||||||
toKeepFileExist = True if os.path.isfile(DupFileToKeep['files'][0]['path']) else False
|
toKeepFileExist = True if os.path.isfile(DupFileToKeep['files'][0]['path']) else False
|
||||||
fileHtmlReport.write(f"{htmlReportTableRow}")
|
fileHtmlReport.write(f"{htmlReportTableRow}")
|
||||||
@@ -640,25 +677,41 @@ def writeRowToHtmlReport(fileHtmlReport, DupFile, DupFileToKeep, QtyTagForDel =
|
|||||||
fileHtmlReport.write("</div></div>")
|
fileHtmlReport.write("</div></div>")
|
||||||
else:
|
else:
|
||||||
fileHtmlReport.write(fileDoesNotExistStr)
|
fileHtmlReport.write(fileDoesNotExistStr)
|
||||||
|
DupToKeepMissingTag, DelCandidateMissingTag = doesDelCandidateHaveMetadataNotInDupToKeep(DupFile, DupFileToKeep, 'tags')
|
||||||
if len(DupFile['tags']) > 0:
|
if len(DupFile['tags']) > 0:
|
||||||
fileHtmlReport.write(htmlTagPrefix)
|
if DupToKeepMissingTag:
|
||||||
|
fileHtmlReport.write(htmlTagPrefix.replace(defaultColorTag, "YellowTag.png"))
|
||||||
|
else:
|
||||||
|
fileHtmlReport.write(htmlTagPrefix)
|
||||||
for tag in DupFile['tags']:
|
for tag in DupFile['tags']:
|
||||||
# if not tag['ignore_auto_tag']:
|
# if not tag['ignore_auto_tag']:
|
||||||
fileHtmlReport.write(f"<div style='color:black;font-size: 12px;'>{tag['name']}</div>")
|
fileHtmlReport.write(f"<div style='color:black;font-size: 12px;'>{tag['name']}</div>")
|
||||||
fileHtmlReport.write("</div></div>")
|
fileHtmlReport.write("</div></div>")
|
||||||
|
DupToKeepMissingPerformer, DelCandidateMissingPerformer = doesDelCandidateHaveMetadataNotInDupToKeep(DupFile, DupFileToKeep, 'performers')
|
||||||
if len(DupFile['performers']) > 0:
|
if len(DupFile['performers']) > 0:
|
||||||
fileHtmlReport.write(htmlPerformerPrefix)
|
if DupToKeepMissingPerformer:
|
||||||
|
fileHtmlReport.write(htmlPerformerPrefix.replace(defaultColorPerformer, "YellowHeadshot.png"))
|
||||||
|
else:
|
||||||
|
fileHtmlReport.write(htmlPerformerPrefix)
|
||||||
for performer in DupFile['performers']:
|
for performer in DupFile['performers']:
|
||||||
fileHtmlReport.write(f"<div style='color:black;font-size: 12px;'>{performer['name']}</div>")
|
fileHtmlReport.write(f"<div style='color:black;font-size: 12px;'>{performer['name']}</div>")
|
||||||
fileHtmlReport.write("</div></div>")
|
fileHtmlReport.write("</div></div>")
|
||||||
|
DupToKeepMissingGallery, DelCandidateMissingGallery = doesDelCandidateHaveMetadataNotInDupToKeep(DupFile, DupFileToKeep, 'galleries', 'title')
|
||||||
if len(DupFile['galleries']) > 0:
|
if len(DupFile['galleries']) > 0:
|
||||||
fileHtmlReport.write(htmlGalleryPrefix)
|
if DupToKeepMissingGallery:
|
||||||
|
fileHtmlReport.write(htmlGalleryPrefix.replace(defaultColorGalleries, "YellowGalleries.png"))
|
||||||
|
else:
|
||||||
|
fileHtmlReport.write(htmlGalleryPrefix)
|
||||||
for gallery in DupFile['galleries']:
|
for gallery in DupFile['galleries']:
|
||||||
gallery = stash.find_gallery(gallery['id'])
|
gallery = stash.getGalleryName(gallery['id'])
|
||||||
fileHtmlReport.write(f"<div style='color:black;font-size: 12px;'>{gallery['title']}</div>")
|
fileHtmlReport.write(f"<div style='color:black;font-size: 12px;'>{gallery['title']}</div>")
|
||||||
fileHtmlReport.write("</div></div>")
|
fileHtmlReport.write("</div></div>")
|
||||||
|
DupToKeepMissingGroup, DelCandidateMissingGroup = doesDelCandidateHaveMetadataNotInDupToKeep(DupFile, DupFileToKeep, 'groups')
|
||||||
if len(DupFile['groups']) > 0:
|
if len(DupFile['groups']) > 0:
|
||||||
fileHtmlReport.write(htmlGroupPrefix)
|
if DupToKeepMissingGroup:
|
||||||
|
fileHtmlReport.write(htmlGroupPrefix.replace(defaultColorGroup, "YellowGroup.png"))
|
||||||
|
else:
|
||||||
|
fileHtmlReport.write(htmlGroupPrefix)
|
||||||
for group in DupFile['groups']:
|
for group in DupFile['groups']:
|
||||||
fileHtmlReport.write(f"<div style='color:black;font-size: 12px;'>{group['group']['name']}</div>")
|
fileHtmlReport.write(f"<div style='color:black;font-size: 12px;'>{group['group']['name']}</div>")
|
||||||
fileHtmlReport.write("</div></div>")
|
fileHtmlReport.write("</div></div>")
|
||||||
@@ -706,24 +759,36 @@ def writeRowToHtmlReport(fileHtmlReport, DupFile, DupFileToKeep, QtyTagForDel =
|
|||||||
fileHtmlReport.write(fileDoesNotExistStr)
|
fileHtmlReport.write(fileDoesNotExistStr)
|
||||||
|
|
||||||
if len(DupFileToKeep['tags']) > 0:
|
if len(DupFileToKeep['tags']) > 0:
|
||||||
fileHtmlReport.write(htmlTagPrefix)
|
if DelCandidateMissingTag:
|
||||||
|
fileHtmlReport.write(htmlTagPrefix.replace(defaultColorTag, "RedTag.png"))
|
||||||
|
else:
|
||||||
|
fileHtmlReport.write(htmlTagPrefix)
|
||||||
for tag in DupFileToKeep['tags']:
|
for tag in DupFileToKeep['tags']:
|
||||||
# if not tag['ignore_auto_tag']:
|
# if not tag['ignore_auto_tag']:
|
||||||
fileHtmlReport.write(f"<div style='color:black;font-size: 12px;'>{tag['name']}</div>")
|
fileHtmlReport.write(f"<div style='color:black;font-size: 12px;'>{tag['name']}</div>")
|
||||||
fileHtmlReport.write("</div></div>")
|
fileHtmlReport.write("</div></div>")
|
||||||
if len(DupFileToKeep['performers']) > 0:
|
if len(DupFileToKeep['performers']) > 0:
|
||||||
fileHtmlReport.write(htmlPerformerPrefix)
|
if DelCandidateMissingPerformer:
|
||||||
|
fileHtmlReport.write(htmlPerformerPrefix.replace(defaultColorPerformer, "PinkHeadshot.png"))
|
||||||
|
else:
|
||||||
|
fileHtmlReport.write(htmlPerformerPrefix)
|
||||||
for performer in DupFileToKeep['performers']:
|
for performer in DupFileToKeep['performers']:
|
||||||
fileHtmlReport.write(f"<div style='color:black;font-size: 12px;'>{performer['name']}</div>")
|
fileHtmlReport.write(f"<div style='color:black;font-size: 12px;'>{performer['name']}</div>")
|
||||||
fileHtmlReport.write("</div></div>")
|
fileHtmlReport.write("</div></div>")
|
||||||
if len(DupFileToKeep['galleries']) > 0:
|
if len(DupFileToKeep['galleries']) > 0:
|
||||||
fileHtmlReport.write(htmlGalleryPrefix)
|
if DelCandidateMissingGallery:
|
||||||
|
fileHtmlReport.write(htmlGalleryPrefix.replace(defaultColorGalleries, "PinkGalleries.png"))
|
||||||
|
else:
|
||||||
|
fileHtmlReport.write(htmlGalleryPrefix)
|
||||||
for gallery in DupFileToKeep['galleries']:
|
for gallery in DupFileToKeep['galleries']:
|
||||||
gallery = stash.find_gallery(gallery['id'])
|
gallery = stash.getGalleryName(gallery['id'])
|
||||||
fileHtmlReport.write(f"<div style='color:black;font-size: 12px;'>{gallery['title']}</div>")
|
fileHtmlReport.write(f"<div style='color:black;font-size: 12px;'>{gallery['title']}</div>")
|
||||||
fileHtmlReport.write("</div></div>")
|
fileHtmlReport.write("</div></div>")
|
||||||
if len(DupFileToKeep['groups']) > 0:
|
if len(DupFileToKeep['groups']) > 0:
|
||||||
fileHtmlReport.write(htmlGroupPrefix)
|
if DelCandidateMissingGroup:
|
||||||
|
fileHtmlReport.write(htmlGroupPrefix.replace(defaultColorGroup, "PinkGroup.png"))
|
||||||
|
else:
|
||||||
|
fileHtmlReport.write(htmlGroupPrefix)
|
||||||
for group in DupFileToKeep['groups']:
|
for group in DupFileToKeep['groups']:
|
||||||
fileHtmlReport.write(f"<div style='color:black;font-size: 12px;'>{group['group']['name']}</div>")
|
fileHtmlReport.write(f"<div style='color:black;font-size: 12px;'>{group['group']['name']}</div>")
|
||||||
fileHtmlReport.write("</div></div>")
|
fileHtmlReport.write("</div></div>")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
name: DupFileManager
|
name: DupFileManager
|
||||||
description: Manages duplicate files.
|
description: Manages duplicate files.
|
||||||
version: 1.0.0.1
|
version: 1.0.0.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
|
||||||
ui:
|
ui:
|
||||||
javascript:
|
javascript:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# DupFileManager: Ver 1.0.0.1 (By David Maisonave)
|
# DupFileManager: Ver 1.0.0.2 (By David Maisonave)
|
||||||
|
|
||||||
DupFileManager is a [Stash](https://github.com/stashapp/stash) plugin which manages duplicate files in the Stash system.
|
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.
|
It has both **task** and **tools-UI** components.
|
||||||
@@ -102,8 +102,8 @@ That's it!!!
|
|||||||
- 
|
- 
|
||||||
|
|
||||||
### Future Planned Features
|
### Future Planned Features
|
||||||
- Add logic to merge performers and galaries seperatly from tag merging on report. Planned for 1.1.0 Version.
|
- Add logic to merge performers and galaries seperatly from tag merging on report. Planned for 1.5.0 Version.
|
||||||
- Add logic to merge group metadata when selecting merge option on report. Planned for 1.2.0 Version.
|
- Add logic to merge group metadata when selecting merge option on report. Planned for 2.0.0 Version.
|
||||||
- Add advanced menu directly to the Settings->Tools menu. Planned for 2.0.0 Version.
|
- Add advanced menu directly to the Settings->Tools menu. Planned for 2.0.0 Version.
|
||||||
- Add report directly to the Settings->Tools menu. Planned for 2.0.0 Version.
|
- Add report directly to the Settings->Tools menu. Planned for 2.0.0 Version.
|
||||||
|
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ class StashPluginHelper(StashInterface):
|
|||||||
stopProcessBarSpin = True
|
stopProcessBarSpin = True
|
||||||
updateProgressbarOnIter = 0
|
updateProgressbarOnIter = 0
|
||||||
currentProgressbarIteration = 0
|
currentProgressbarIteration = 0
|
||||||
|
galleryNamesCache = {}
|
||||||
|
|
||||||
class OS_Type(IntEnum):
|
class OS_Type(IntEnum):
|
||||||
WINDOWS = 1
|
WINDOWS = 1
|
||||||
@@ -773,6 +774,14 @@ class StashPluginHelper(StashInterface):
|
|||||||
errMsg = f"Exception calling [updateScene]. Will retry; count({i}); Error: {e}\nTraceBack={tb}"
|
errMsg = f"Exception calling [updateScene]. Will retry; count({i}); Error: {e}\nTraceBack={tb}"
|
||||||
time.sleep(sleepSecondsBetweenRetry)
|
time.sleep(sleepSecondsBetweenRetry)
|
||||||
|
|
||||||
|
# getGalleryName uses a cache so it doesn't have to hit the server for the same ID.
|
||||||
|
def getGalleryName(self, gallery_id, refreshCache=False):
|
||||||
|
if refreshCache:
|
||||||
|
self.galleryNamesCache = {}
|
||||||
|
if gallery_id not in self.galleryNamesCache:
|
||||||
|
self.galleryNamesCache[gallery_id] = self.find_gallery(gallery_id)
|
||||||
|
return self.galleryNamesCache[gallery_id]
|
||||||
|
|
||||||
def runPlugin(self, plugin_id, task_mode=None, args:dict={}, asyn=False):
|
def runPlugin(self, plugin_id, task_mode=None, args:dict={}, asyn=False):
|
||||||
"""Runs a plugin operation.
|
"""Runs a plugin operation.
|
||||||
The operation is run immediately and does not use the job queue.
|
The operation is run immediately and does not use the job queue.
|
||||||
@@ -986,6 +995,8 @@ class mergeMetadata: # A class to merge scene metadata from source scene to dest
|
|||||||
self.mergeItems('tags', 'tag_ids', [], excludeName=self.excludeMergeTags)
|
self.mergeItems('tags', 'tag_ids', [], excludeName=self.excludeMergeTags)
|
||||||
self.mergeItems('performers', 'performer_ids', [])
|
self.mergeItems('performers', 'performer_ids', [])
|
||||||
self.mergeItems('galleries', 'gallery_ids', [])
|
self.mergeItems('galleries', 'gallery_ids', [])
|
||||||
|
# ToDo: Firgure out how to merge groups
|
||||||
|
# self.mergeItems('groups', 'group_ids')
|
||||||
# Looks like movies has been removed from new Stash version
|
# Looks like movies has been removed from new Stash version
|
||||||
# self.mergeItems('movies', 'movies', [])
|
# self.mergeItems('movies', 'movies', [])
|
||||||
self.mergeItems('urls', listToAdd=self.destData['urls'], NotStartWith=self.stash.STASH_URL)
|
self.mergeItems('urls', listToAdd=self.destData['urls'], NotStartWith=self.stash.STASH_URL)
|
||||||
@@ -1020,9 +1031,13 @@ class mergeMetadata: # A class to merge scene metadata from source scene to dest
|
|||||||
if item not in self.destData[fieldName]:
|
if item not in self.destData[fieldName]:
|
||||||
if NotStartWith == None or not item.startswith(NotStartWith):
|
if NotStartWith == None or not item.startswith(NotStartWith):
|
||||||
if excludeName == None or item['name'] not in excludeName:
|
if excludeName == None or item['name'] not in excludeName:
|
||||||
if fieldName == 'movies':
|
if fieldName == 'groups':
|
||||||
listToAdd += [{"movie_id" : item['movie']['id'], "scene_index" : item['scene_index']}]
|
# listToAdd += [{"group_id" : item['group']['id'], "group_name" : item['group']['name']}]
|
||||||
dataAdded += f"{item['movie']['id']} "
|
listToAdd += [item['group']['id']]
|
||||||
|
dataAdded += f"{item['group']['id']} "
|
||||||
|
# elif fieldName == 'movies':
|
||||||
|
# listToAdd += [{"movie_id" : item['movie']['id'], "scene_index" : item['scene_index']}]
|
||||||
|
# dataAdded += f"{item['movie']['id']} "
|
||||||
elif updateFieldName == None:
|
elif updateFieldName == None:
|
||||||
listToAdd += [item]
|
listToAdd += [item]
|
||||||
dataAdded += f"{item} "
|
dataAdded += f"{item} "
|
||||||
|
|||||||
@@ -31,3 +31,8 @@
|
|||||||
- Added report option to delete by flags set on the report.
|
- Added report option to delete by flags set on the report.
|
||||||
### 1.0.0.1
|
### 1.0.0.1
|
||||||
- Fixed bug with report delete scene request.
|
- Fixed bug with report delete scene request.
|
||||||
|
### 1.0.0.2
|
||||||
|
- In the report, made icon colors for tags, performers, galleries, and groups with different colors if they don't match. In other words, use different color icons if **candidate to delete** doesn't match **duplicate to keep** associated icon data.
|
||||||
|
- If data for associated icon are the same, then both icons are black or blue (the default color).
|
||||||
|
- If [**duplicate to keep**] is missing data that is in [**candidate to delete**], than [**candidate to delete**] gets a yellow icon.
|
||||||
|
- If [**candidate to delete**] is missing data that is in [**duplicate to keep**], than [**duplicate to keep**] gets a pink icon.
|
||||||
|
|||||||
Reference in New Issue
Block a user