Skip to content

Commit eeb2d3f

Browse files
elkorolDogmaDragon
andauthored
Additional Files Deleter (#256)
Co-authored-by: DogmaDragon <[email protected]>
1 parent f82757d commit eeb2d3f

File tree

4 files changed

+266
-0
lines changed

4 files changed

+266
-0
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Addtional Files Deleter
2+
3+
This is a plugin that will scan your Stash for either scenes or images where the file count is above 1. It will then skip over the primary file for each scene or image object and delete these extra files. Usually scene that contain multiple files are identical Phash matches (Unless you have manually merged unidentical Phashed files). Image objects that contain multiple files are grouped together under identical checksums, not Phashes. (You can't manually merge images as of yet.)
4+
5+
## Usage
6+
7+
Copy repository into Stash plugins folder or add via the new plugins system and refresh your plugins from the Settings.
8+
9+
If on first run you may want to run the Create Tag task, which creates an ignore tag that you can apply to Scenes or Images, so that they are bypassed when any of the other tasks are run.
10+
11+
Other than Create Tag task you can run the following tasks.
12+
13+
Images - Delete
14+
Images - Delete & Record
15+
Scenes - Delete
16+
Scenes - Delete & Record
17+
18+
Tasks that just specify delete will just delete addtional files from their respective objects and Delete & Record will take the file paths of the files to be deleted, prefix them with "File: " (For latter easy searching) and it will append them to the current list of urls the object has and update the object. This is just a precaution to record perhaps usefull metadata an additional file path may hold for later use.
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import sys, json
2+
3+
import stashapi.log as log
4+
from stashapi.stashapp import StashInterface
5+
6+
SVG_IMAGE = (
7+
"data:image/svg+xml;base64,PCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIi"
8+
"AiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj4KDTwhLS0gVXBsb2FkZW"
9+
"QgdG86IFNWRyBSZXBvLCB3d3cuc3ZncmVwby5jb20sIFRyYW5zZm9ybWVkIGJ5OiBTVkcgUmVwbyBNaXhlciBUb2"
10+
"9scyAtLT4KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD"
11+
"0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KDTxnIGlkPSJTVkdSZXBvX2JnQ2Fycm"
12+
"llciIgc3Ryb2tlLXdpZHRoPSIwIi8+Cg08ZyBpZD0iU1ZHUmVwb190cmFjZXJDYXJyaWVyIiBzdHJva2UtbGluZW"
13+
"NhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KDTxnIGlkPSJTVkdSZXBvX2ljb25DYXJyaWVyIj"
14+
"4gPHBhdGggZD0iTTUuNjM2MDUgNS42MzYwNUwxOC4zNjQgMTguMzY0TTUuNjM2MDUgMTguMzY0TDE4LjM2NCA1Lj"
15+
"YzNjA1TTIxIDEyQzIxIDE2Ljk3MDYgMTYuOTcwNiAyMSAxMiAyMUM3LjAyOTQ0IDIxIDMgMTYuOTcwNiAzIDEyQz"
16+
"MgNy4wMjk0NCA3LjAyOTQ0IDMgMTIgM0MxNi45NzA2IDMgMjEgNy4wMjk0NCAyMSAxMloiIHN0cm9rZT0iI2ZmZm"
17+
"ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPiA8L2c+Cg08L3N2Zz4="
18+
)
19+
20+
21+
tag_exclude = {
22+
"name": "Addtional Files Deleter: Scenes/Images: Ignore",
23+
"description": "Addtional Files Deleter: Scene/Image Objects that contain addtional files "
24+
"will not be deleted",
25+
"image": SVG_IMAGE
26+
}
27+
28+
29+
def main():
30+
global stash
31+
32+
json_input = json.loads(sys.stdin.read())
33+
mode_arg = json_input["args"]["mode"]
34+
35+
stash = StashInterface(json_input["server_connection"])
36+
37+
if mode_arg == "create_tag":
38+
create_tag(tag_exclude)
39+
if mode_arg == "remove_tag":
40+
remove_tag()
41+
if mode_arg == "images_delete":
42+
images_delete()
43+
if mode_arg == "images_delete_record_paths":
44+
images_delete_record_paths()
45+
if mode_arg == "scenes_delete":
46+
scenes_delete()
47+
if mode_arg == "scenes_delete_record_paths":
48+
scenes_delete_record_paths()
49+
50+
def update_image(image_id, paths):
51+
update = stash.update_image(
52+
{'id': image_id, 'urls': paths})
53+
return update
54+
55+
def update_scene(scene_id, paths):
56+
update = stash.update_scene(
57+
{'id': scene_id, 'urls': paths})
58+
return update
59+
60+
def find_images(find_images_tag):
61+
image_count, images = stash.find_images(
62+
f={
63+
"file_count": {"modifier": "GREATER_THAN", "value": 1},
64+
"tags": {"modifier": "EXCLUDES", "value": find_images_tag},
65+
},
66+
filter={
67+
"per_page": "-1"
68+
},
69+
get_count=True,
70+
71+
)
72+
return image_count, images
73+
74+
def find_scenes(find_scenes_tag):
75+
scene_count, scenes = stash.find_scenes(
76+
f={
77+
"file_count": {"modifier": "GREATER_THAN", "value": 1},
78+
"tags": {"modifier": "EXCLUDES", "value": find_scenes_tag},
79+
},
80+
filter={
81+
"per_page": "-1"
82+
},
83+
get_count=True,
84+
)
85+
return scene_count, scenes
86+
87+
def find_tag(name, create=False):
88+
find_tag_tag = stash.find_tag(name, create)
89+
if find_tag_tag is None:
90+
log.error(f"Tag does not exist: {tag_exclude['name']}")
91+
else:
92+
log.info(f"Found Tag: ID:{find_tag_tag['id']} Name: {find_tag_tag['name']}")
93+
return find_tag_tag
94+
95+
def create_tag(obj):
96+
create_tag_tag = stash.create_tag(obj)
97+
98+
if create_tag_tag is None:
99+
log.error(f'Tag already exists: {tag_exclude["name"]}')
100+
else:
101+
log.info(f"Created Tag: ID:{create_tag_tag['id']} Name: {create_tag_tag['name']}")
102+
return create_tag_tag
103+
104+
def remove_tag():
105+
remove_tag_tag = find_tag(tag_exclude["name"])
106+
if remove_tag_tag is not None:
107+
stash.destroy_tag(remove_tag_tag['id'])
108+
log.info(f"Deleted Tag - ID:{remove_tag_tag['id']}: Name: {remove_tag_tag['name']}")
109+
110+
def images_delete():
111+
images_delete_tag = find_tag(tag_exclude)
112+
113+
if images_delete_tag is None:
114+
images_delete_tag = create_tag(tag_exclude)
115+
116+
image_count, images = find_images(images_delete_tag["id"])
117+
log.info(f"Deleting Addtional files of {image_count} image objects")
118+
119+
for j, image in enumerate(images):
120+
log.progress(j / image_count)
121+
122+
for i, file in enumerate(image["visual_files"]):
123+
if i == 0: # skip first ID
124+
continue
125+
delete = stash.destroy_files(file["id"])
126+
if delete is True:
127+
log.info(f"Image ID:{image['id']} - File ID:{file['id']} - Deleted: {file['path']}")
128+
else:
129+
log.error(f"Image ID:{image['id']} - File ID:{file['id']} - Could not be Deleted: {file['path']}")
130+
131+
def images_delete_record_paths():
132+
images_delete_record_tag = find_tag(tag_exclude)
133+
134+
if images_delete_record_tag is None:
135+
images_delete_record_tag = create_tag(tag_exclude)
136+
137+
image_count, images = find_images(images_delete_record_tag["id"])
138+
log.info(f"Deleting Addtional Images of {image_count} image objects and recording paths in URLs Field")
139+
140+
for j, image in enumerate(images):
141+
image_id = image["id"]
142+
paths = image["urls"]
143+
log.progress(j / image_count)
144+
145+
for i, file in enumerate(image["visual_files"]):
146+
if i == 0: # skip first ID
147+
continue
148+
path = file["path"]
149+
delete = stash.destroy_files(file["id"])
150+
if delete is True:
151+
log.info(f"Image ID:{image['id']} - File ID:{file['id']} - Deleted: {path}")
152+
paths.append("File: " + path)
153+
else:
154+
log.error(f"Image ID:{image['id']} - File ID:{file['id']} - Could not be Deleted: {path}")
155+
update = update_image(image_id, paths)
156+
if update is not None:
157+
log.info(f"Image ID:{image_id}: Updated with path(s) as URLs: {path}")
158+
else:
159+
log.error(f"Image ID:{image_id}: Could not be updated with path(s) as URLs: {path}")
160+
161+
def scenes_delete():
162+
scenes_delete_tag = find_tag(tag_exclude)
163+
164+
if scenes_delete_tag is None:
165+
scenes_delete_tag = create_tag(tag_exclude)
166+
167+
scene_count, scenes = find_scenes(scenes_delete_tag["id"])
168+
log.info(f"Deleting Addtional files of {scene_count} scene objects and recording paths in URLs Field")
169+
170+
for j, scene in enumerate(scenes):
171+
log.progress(j / scene_count)
172+
173+
for i, file in enumerate(scene["files"]):
174+
if i == 0: # skip first ID
175+
continue
176+
delete = stash.destroy_files(file["id"])
177+
if delete is True:
178+
log.info(f"Scene ID:{scene['id']} - File ID:{file['id']} - Deleted: {file['path']}")
179+
else:
180+
log.error(f"Scene ID:{scene['id']} - File ID:{file['id']} - Could not be Deleted: {file['path']}")
181+
182+
def scenes_delete_record_paths():
183+
scenes_delete_record_tag = find_tag(tag_exclude)
184+
185+
if scenes_delete_record_tag is None:
186+
scenes_delete_record_tag = create_tag(tag_exclude)
187+
188+
scene_count, scenes = find_scenes(scenes_delete_record_tag["id"])
189+
log.info(f"Deleting Addtional files of {scene_count} scene objects and recording paths in URLs Field")
190+
191+
for j, scene in enumerate(scenes):
192+
log.progress(j / scene_count)
193+
194+
scene_id = scene["id"]
195+
paths = scene["urls"]
196+
197+
for i, file in enumerate(scene["files"]):
198+
if i == 0: # skip first ID
199+
continue
200+
path = file["path"]
201+
delete = stash.destroy_files(file["id"])
202+
if delete is True:
203+
log.info(f"Scene ID:{scene['id']} - File ID:{file['id']} - Deleted: {path}")
204+
paths.append("File: " + path)
205+
else:
206+
log.error(f"Scene ID:{scene['id']} - File ID:{file['id']} - Could not be Deleted: {path}")
207+
update = update_scene(scene_id, paths)
208+
if update is not None:
209+
log.info(f"Scene ID:{scene_id}: Updated with path(s) as URLs: {path}")
210+
else:
211+
log.error(f"Scene ID:{scene_id}: Could not be updated with path(s) as URLs: "
212+
"{path}")
213+
214+
if __name__ == "__main__":
215+
main()
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Addtional Files Deleter
2+
description: Deletes addtional files assosiated with an image or scene object. Which will usually have identical PHashes for scenes or Checksum for images. Unless is a scene manually merged. Apply ignore tag to scene/image object for plugin to bypass.
3+
version: 0.1
4+
exec:
5+
- python
6+
- "{pluginDir}/deleter.py"
7+
interface: raw
8+
tasks:
9+
- name: Create Tag
10+
description: Create the plugin Ignore Tag
11+
defaultArgs:
12+
mode: create_tag
13+
- name: Remove Tag
14+
description: Remove the plugin Ignore Tag
15+
defaultArgs:
16+
mode: remove_tag
17+
- name: Images - Delete
18+
description: Image objects that contain addtional files will be deleted
19+
defaultArgs:
20+
mode: images_delete
21+
- name: Images - Delete & Record
22+
description: Addtional files will be deleted & old paths will be stored in Image object URLs field (Incase they contain future needed metadata)
23+
defaultArgs:
24+
mode: images_delete_record_paths
25+
- name: Scenes - Delete
26+
description: Scene objects that contain addtional files will be deleted
27+
defaultArgs:
28+
mode: scenes_delete
29+
- name: Scenes - Delete & Record
30+
description: Addtional files will be deleted & old paths will be stored in Scene object URLs field (Incase they contain future needed metadata)
31+
defaultArgs:
32+
mode: scenes_delete_record_paths
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
stashapp-tools==0.2.40

0 commit comments

Comments
 (0)