stashapp-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stashapp_cli/__init__.py +2 -0
- stashapp_cli/cli.py +149 -0
- stashapp_cli/commands/__init__.py +39 -0
- stashapp_cli/commands/_common.py +440 -0
- stashapp_cli/commands/backup.py +70 -0
- stashapp_cli/commands/generate.py +88 -0
- stashapp_cli/commands/graphql.py +212 -0
- stashapp_cli/commands/group.py +137 -0
- stashapp_cli/commands/introspect.py +35 -0
- stashapp_cli/commands/job.py +122 -0
- stashapp_cli/commands/performer.py +368 -0
- stashapp_cli/commands/scan.py +92 -0
- stashapp_cli/commands/scene.py +405 -0
- stashapp_cli/commands/scrape.py +133 -0
- stashapp_cli/commands/stashbox.py +173 -0
- stashapp_cli/commands/studio.py +227 -0
- stashapp_cli/commands/tag.py +694 -0
- stashapp_cli/core.py +165 -0
- stashapp_cli/images.py +72 -0
- stashapp_cli/introspect.py +343 -0
- stashapp_cli/py.typed +0 -0
- stashapp_cli/queries/__init__.py +40 -0
- stashapp_cli/queries/_get.py +27 -0
- stashapp_cli/queries/_image_flags.py +30 -0
- stashapp_cli/queries/backup.py +20 -0
- stashapp_cli/queries/group.py +117 -0
- stashapp_cli/queries/job.py +29 -0
- stashapp_cli/queries/metadata.py +42 -0
- stashapp_cli/queries/performer.py +179 -0
- stashapp_cli/queries/scene.py +139 -0
- stashapp_cli/queries/scrape.py +183 -0
- stashapp_cli/queries/sources.py +76 -0
- stashapp_cli/queries/stashbox.py +67 -0
- stashapp_cli/queries/studio.py +145 -0
- stashapp_cli/queries/tag.py +244 -0
- stashapp_cli/sources.py +116 -0
- stashapp_cli-0.1.0.dist-info/METADATA +208 -0
- stashapp_cli-0.1.0.dist-info/RECORD +41 -0
- stashapp_cli-0.1.0.dist-info/WHEEL +4 -0
- stashapp_cli-0.1.0.dist-info/entry_points.txt +3 -0
- stashapp_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"""performer commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
from .. import core, queries
|
|
8
|
+
from ._common import (
|
|
9
|
+
ALIASES,
|
|
10
|
+
TAG_IDS,
|
|
11
|
+
URLS,
|
|
12
|
+
CommandKind,
|
|
13
|
+
FindSpec,
|
|
14
|
+
add_find_options,
|
|
15
|
+
add_get_command,
|
|
16
|
+
add_leaf,
|
|
17
|
+
add_list_options,
|
|
18
|
+
add_update_target,
|
|
19
|
+
bulk_update_input,
|
|
20
|
+
find_filter,
|
|
21
|
+
mutation_result,
|
|
22
|
+
read_json_arg,
|
|
23
|
+
require_force,
|
|
24
|
+
require_list_target,
|
|
25
|
+
resolve_update_input,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
FIND = FindSpec("performers", "name", match_aliases=True, sort="name")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def cmd_performer_find(args: argparse.Namespace) -> object:
|
|
32
|
+
return queries.performer.find_performers(
|
|
33
|
+
find_filter(args, FIND), args.page, args.per_page
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def cmd_performer_create(args: argparse.Namespace) -> object:
|
|
38
|
+
urls = args.url or []
|
|
39
|
+
input_data = {"name": args.name, "gender": args.gender, "urls": urls}
|
|
40
|
+
return mutation_result(
|
|
41
|
+
"performerCreate",
|
|
42
|
+
args.apply,
|
|
43
|
+
input_data,
|
|
44
|
+
lambda: queries.performer.create_performer(args.name, args.gender, urls),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
LIST_FIELDS = (TAG_IDS, URLS, ALIASES)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def cmd_performer_update(args: argparse.Namespace) -> object:
|
|
52
|
+
bulk_input = bulk_update_input(args, LIST_FIELDS)
|
|
53
|
+
if bulk_input is not None:
|
|
54
|
+
require_list_target(args)
|
|
55
|
+
return mutation_result(
|
|
56
|
+
"bulkPerformerUpdate",
|
|
57
|
+
args.apply,
|
|
58
|
+
bulk_input,
|
|
59
|
+
lambda: queries.performer.bulk_update_performer(bulk_input),
|
|
60
|
+
)
|
|
61
|
+
input_data = resolve_update_input(args)
|
|
62
|
+
return mutation_result(
|
|
63
|
+
"performerUpdate",
|
|
64
|
+
args.apply,
|
|
65
|
+
input_data,
|
|
66
|
+
lambda: queries.performer.update_performer(input_data),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _alias_key(value: str) -> str:
|
|
71
|
+
return value.strip().casefold()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _stash_id_key(stash_id: dict) -> tuple[str, str]:
|
|
75
|
+
return (stash_id["endpoint"], stash_id["stash_id"])
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _kept(destination: dict, values: dict, field: str) -> list:
|
|
79
|
+
"""The destination's value for `field` after the merge's partial update."""
|
|
80
|
+
value = values[field] if field in values else destination.get(field)
|
|
81
|
+
if value is None:
|
|
82
|
+
return []
|
|
83
|
+
return [value] if field == "name" else list(value)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def lost_values(destination: dict, sources: list[dict], values: dict) -> list[dict]:
|
|
87
|
+
"""Report, per source, the aliases, URLs, and stash IDs the merge would drop.
|
|
88
|
+
|
|
89
|
+
Kept values come from `values` when it sets a field, because a list in
|
|
90
|
+
`values` replaces the destination's list, and from `destination` otherwise.
|
|
91
|
+
"""
|
|
92
|
+
kept_aliases = {
|
|
93
|
+
_alias_key(alias)
|
|
94
|
+
for alias in _kept(destination, values, "name")
|
|
95
|
+
+ _kept(destination, values, "alias_list")
|
|
96
|
+
}
|
|
97
|
+
kept_urls = set(_kept(destination, values, "urls"))
|
|
98
|
+
kept_stash_ids = {
|
|
99
|
+
_stash_id_key(stash_id) for stash_id in _kept(destination, values, "stash_ids")
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
report = []
|
|
103
|
+
for source in sources:
|
|
104
|
+
aliases: list[str] = []
|
|
105
|
+
seen_aliases: set[str] = set()
|
|
106
|
+
for alias in [source.get("name"), *(source.get("alias_list") or [])]:
|
|
107
|
+
if not alias:
|
|
108
|
+
continue
|
|
109
|
+
key = _alias_key(alias)
|
|
110
|
+
if key in kept_aliases or key in seen_aliases:
|
|
111
|
+
continue
|
|
112
|
+
seen_aliases.add(key)
|
|
113
|
+
aliases.append(alias)
|
|
114
|
+
|
|
115
|
+
urls: list[str] = []
|
|
116
|
+
for url in source.get("urls") or []:
|
|
117
|
+
if url not in kept_urls and url not in urls:
|
|
118
|
+
urls.append(url)
|
|
119
|
+
|
|
120
|
+
stash_ids: list[dict] = []
|
|
121
|
+
seen_stash_ids: set[tuple[str, str]] = set()
|
|
122
|
+
for stash_id in source.get("stash_ids") or []:
|
|
123
|
+
key = _stash_id_key(stash_id)
|
|
124
|
+
if key in kept_stash_ids or key in seen_stash_ids:
|
|
125
|
+
continue
|
|
126
|
+
seen_stash_ids.add(key)
|
|
127
|
+
stash_ids.append({"endpoint": key[0], "stash_id": key[1]})
|
|
128
|
+
|
|
129
|
+
report.append(
|
|
130
|
+
{
|
|
131
|
+
"id": source["id"],
|
|
132
|
+
"name": source.get("name"),
|
|
133
|
+
"lost": {"aliases": aliases, "urls": urls, "stash_ids": stash_ids},
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
return report
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _performer_id(value: object, option: str) -> str:
|
|
140
|
+
"""`value` as Stash writes a performer ID, so `03` and `3` compare equal."""
|
|
141
|
+
text = str(value).strip()
|
|
142
|
+
if isinstance(value, bool) or not text.isdecimal():
|
|
143
|
+
raise core.CliError(
|
|
144
|
+
f"{option} must be a performer ID (a non-negative integer), got {value!r}.",
|
|
145
|
+
2,
|
|
146
|
+
"usage_error",
|
|
147
|
+
)
|
|
148
|
+
return str(int(text))
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _is_str_list(value: object) -> bool:
|
|
152
|
+
return isinstance(value, list) and all(isinstance(item, str) for item in value)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _is_stash_id_list(value: object) -> bool:
|
|
156
|
+
return isinstance(value, list) and all(
|
|
157
|
+
isinstance(item, dict)
|
|
158
|
+
and isinstance(item.get("endpoint"), str)
|
|
159
|
+
and isinstance(item.get("stash_id"), str)
|
|
160
|
+
for item in value
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# The `--values` fields the loss report reads, with the shape each must have.
|
|
165
|
+
_VALUES_FIELD_CHECKS = {
|
|
166
|
+
"name": (lambda value: isinstance(value, str), "a string"),
|
|
167
|
+
"alias_list": (_is_str_list, "a list of strings"),
|
|
168
|
+
"urls": (_is_str_list, "a list of strings"),
|
|
169
|
+
"stash_ids": (
|
|
170
|
+
_is_stash_id_list,
|
|
171
|
+
"a list of objects with string endpoint and stash_id",
|
|
172
|
+
),
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _check_values_fields(values: dict) -> None:
|
|
177
|
+
for field, (is_valid, expected) in _VALUES_FIELD_CHECKS.items():
|
|
178
|
+
if (
|
|
179
|
+
field in values
|
|
180
|
+
and values[field] is not None
|
|
181
|
+
and not is_valid(values[field])
|
|
182
|
+
):
|
|
183
|
+
raise core.CliError(
|
|
184
|
+
f"--values field {field} must be {expected}.", 2, "usage_error"
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _merge_values(raw: str | None, destination_id: str) -> dict | None:
|
|
189
|
+
"""Parse and check `--values`, filling its required id with the destination."""
|
|
190
|
+
if raw is None:
|
|
191
|
+
return None
|
|
192
|
+
values = read_json_arg(raw)
|
|
193
|
+
if not isinstance(values, dict):
|
|
194
|
+
raise core.CliError(
|
|
195
|
+
"--values must be a PerformerUpdateInput JSON object.", 2, "usage_error"
|
|
196
|
+
)
|
|
197
|
+
if "url" in values:
|
|
198
|
+
raise core.CliError(
|
|
199
|
+
"--values must not set the legacy url field; performerMerge rejects "
|
|
200
|
+
"it. Use urls.",
|
|
201
|
+
2,
|
|
202
|
+
"usage_error",
|
|
203
|
+
)
|
|
204
|
+
_check_values_fields(values)
|
|
205
|
+
if "id" in values:
|
|
206
|
+
if _performer_id(values["id"], "--values id") != destination_id:
|
|
207
|
+
raise core.CliError(
|
|
208
|
+
f"Destination {destination_id} does not match id {values['id']} "
|
|
209
|
+
"in --values.",
|
|
210
|
+
2,
|
|
211
|
+
"usage_error",
|
|
212
|
+
)
|
|
213
|
+
else:
|
|
214
|
+
values["id"] = destination_id
|
|
215
|
+
return values
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def cmd_performer_merge(args: argparse.Namespace) -> object:
|
|
219
|
+
destination_id = _performer_id(args.destination, "--destination")
|
|
220
|
+
source_ids = list(
|
|
221
|
+
dict.fromkeys(_performer_id(source, "--source") for source in args.source)
|
|
222
|
+
)
|
|
223
|
+
if destination_id in source_ids:
|
|
224
|
+
raise core.CliError(
|
|
225
|
+
f"Destination {destination_id} is also a --source; a performer cannot "
|
|
226
|
+
"be merged into itself.",
|
|
227
|
+
2,
|
|
228
|
+
"usage_error",
|
|
229
|
+
)
|
|
230
|
+
values = _merge_values(args.values, destination_id)
|
|
231
|
+
|
|
232
|
+
performers = {
|
|
233
|
+
performer["id"]: performer
|
|
234
|
+
for performer in queries.performer.find_performers_by_ids(
|
|
235
|
+
[destination_id, *source_ids]
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
destination = performers[destination_id]
|
|
239
|
+
sources = lost_values(
|
|
240
|
+
destination,
|
|
241
|
+
[performers[source_id] for source_id in source_ids],
|
|
242
|
+
values or {},
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
input_data: dict = {"source": source_ids, "destination": destination_id}
|
|
246
|
+
if values is not None:
|
|
247
|
+
input_data["values"] = values
|
|
248
|
+
if not args.apply:
|
|
249
|
+
return {
|
|
250
|
+
"applied": False,
|
|
251
|
+
"operation": "performerMerge",
|
|
252
|
+
"input": input_data,
|
|
253
|
+
"destination": {"id": destination["id"], "name": destination.get("name")},
|
|
254
|
+
"sources": sources,
|
|
255
|
+
}
|
|
256
|
+
require_force(args, ["performerMerge"])
|
|
257
|
+
return {
|
|
258
|
+
"applied": True,
|
|
259
|
+
"operation": "performerMerge",
|
|
260
|
+
"id": queries.performer.merge_performers(input_data),
|
|
261
|
+
"sources": sources,
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def register(subparsers: argparse._SubParsersAction) -> None:
|
|
266
|
+
performer = subparsers.add_parser("performer", help="Performer lookups and writes.")
|
|
267
|
+
performer_sub = performer.add_subparsers(dest="performer_command", required=True)
|
|
268
|
+
|
|
269
|
+
add_get_command(
|
|
270
|
+
performer_sub, "performer", queries.performer.get_performer, example_id=8
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
find = add_leaf(
|
|
274
|
+
performer_sub,
|
|
275
|
+
"find",
|
|
276
|
+
kind=CommandKind.READ,
|
|
277
|
+
func=cmd_performer_find,
|
|
278
|
+
help="Find performers by exact name or alias, or by a PerformerFilterType.",
|
|
279
|
+
examples=(
|
|
280
|
+
"stashapp-cli performer find --name 'Jane Doe'",
|
|
281
|
+
'stashapp-cli performer find --filter-json \'{"gender": {"value": "FEMALE", "modifier": "EQUALS"}}\'',
|
|
282
|
+
"stashapp-cli performer find --name 'Jane Doe' --filter-json '{\"favorite\": true}'",
|
|
283
|
+
"stashapp-cli performer find --page 2 --per-page 100",
|
|
284
|
+
),
|
|
285
|
+
)
|
|
286
|
+
add_find_options(find, FIND)
|
|
287
|
+
|
|
288
|
+
create = add_leaf(
|
|
289
|
+
performer_sub,
|
|
290
|
+
"create",
|
|
291
|
+
kind=CommandKind.WRITE,
|
|
292
|
+
func=cmd_performer_create,
|
|
293
|
+
help="Create a performer (dry-run by default; see global --apply).",
|
|
294
|
+
examples=(
|
|
295
|
+
"stashapp-cli performer create 'Jane Doe' --gender FEMALE",
|
|
296
|
+
"stashapp-cli performer create 'Jane Doe' --apply",
|
|
297
|
+
),
|
|
298
|
+
)
|
|
299
|
+
create.add_argument("name")
|
|
300
|
+
create.add_argument("--gender", default=None)
|
|
301
|
+
create.add_argument(
|
|
302
|
+
"--url", action="append", default=None, help="May be given more than once."
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
update = add_leaf(
|
|
306
|
+
performer_sub,
|
|
307
|
+
"update",
|
|
308
|
+
kind=CommandKind.WRITE,
|
|
309
|
+
func=cmd_performer_update,
|
|
310
|
+
help="Update a performer (dry-run by default; see global --apply).",
|
|
311
|
+
examples=(
|
|
312
|
+
"stashapp-cli performer update 1 --input '{\"urls\": []}'",
|
|
313
|
+
'stashapp-cli performer update --input \'{"id": "1", "urls": []}\'',
|
|
314
|
+
"stashapp-cli --apply performer update --input @performer.json",
|
|
315
|
+
"stashapp-cli performer update 1 --add-alias 'Jay D' --add-tag-id 55",
|
|
316
|
+
"stashapp-cli performer update 1 --remove-url https://old.example/jane --apply",
|
|
317
|
+
),
|
|
318
|
+
)
|
|
319
|
+
add_update_target(update, "PerformerUpdateInput")
|
|
320
|
+
add_list_options(update, LIST_FIELDS)
|
|
321
|
+
|
|
322
|
+
merge = add_leaf(
|
|
323
|
+
performer_sub,
|
|
324
|
+
"merge",
|
|
325
|
+
kind=CommandKind.DESTRUCTIVE,
|
|
326
|
+
func=cmd_performer_merge,
|
|
327
|
+
help=(
|
|
328
|
+
"Merge source performers into a destination and destroy the sources "
|
|
329
|
+
"(dry-run by default; needs --apply --force)."
|
|
330
|
+
),
|
|
331
|
+
examples=(
|
|
332
|
+
"stashapp-cli performer merge --destination 3 --source 7 --source 9",
|
|
333
|
+
(
|
|
334
|
+
"stashapp-cli performer merge --destination 3 --source 7 "
|
|
335
|
+
'--values \'{"alias_list": ["JD", "Jane D"]}\''
|
|
336
|
+
),
|
|
337
|
+
(
|
|
338
|
+
"stashapp-cli performer merge --destination 3 --source 7 "
|
|
339
|
+
"--values @values.json --apply --force"
|
|
340
|
+
),
|
|
341
|
+
),
|
|
342
|
+
)
|
|
343
|
+
merge.add_argument(
|
|
344
|
+
"--destination", required=True, metavar="ID", help="Performer to keep."
|
|
345
|
+
)
|
|
346
|
+
merge.add_argument(
|
|
347
|
+
"--source",
|
|
348
|
+
action="append",
|
|
349
|
+
required=True,
|
|
350
|
+
metavar="ID",
|
|
351
|
+
help="Performer to merge and destroy; may be given more than once.",
|
|
352
|
+
)
|
|
353
|
+
merge.add_argument(
|
|
354
|
+
"--values",
|
|
355
|
+
default=None,
|
|
356
|
+
help=(
|
|
357
|
+
"PerformerUpdateInput as JSON, @file, or @- whose fields override "
|
|
358
|
+
"the destination; its id defaults to the destination."
|
|
359
|
+
),
|
|
360
|
+
)
|
|
361
|
+
merge.epilog = f"{merge.epilog}" + (
|
|
362
|
+
"\n\nThe dry run reads every performer and lists, per source, the "
|
|
363
|
+
"aliases (including\nits name), URLs, and stash IDs that the destination "
|
|
364
|
+
"would not keep, so the\nmerge would lose them. List fields in --values "
|
|
365
|
+
"(alias_list, urls, stash_ids)\nreplace the destination's list, so pass "
|
|
366
|
+
"the full list. To keep a lost value,\nadd it to --values and re-run "
|
|
367
|
+
"the dry run; the lost lists are empty when\nnothing would be lost."
|
|
368
|
+
)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""scan commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
from .. import core, queries
|
|
8
|
+
from ._common import CommandKind, add_leaf, job_result, read_input_object
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def scan_input(args: argparse.Namespace) -> dict:
|
|
12
|
+
"""Build a ScanMetadataInput: `--input`, then the options over their fields."""
|
|
13
|
+
input_data = read_input_object(args.input)
|
|
14
|
+
if args.paths:
|
|
15
|
+
input_data["paths"] = args.paths
|
|
16
|
+
if args.phashes:
|
|
17
|
+
input_data["scanGeneratePhashes"] = True
|
|
18
|
+
if args.covers:
|
|
19
|
+
input_data["scanGenerateCovers"] = True
|
|
20
|
+
if args.rescan:
|
|
21
|
+
input_data["rescan"] = True
|
|
22
|
+
if not input_data.get("paths"):
|
|
23
|
+
raise core.CliError(
|
|
24
|
+
"Pass at least one PATH, or paths in --input.", 2, "usage_error"
|
|
25
|
+
)
|
|
26
|
+
return input_data
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def cmd_scan_run(args: argparse.Namespace) -> object:
|
|
30
|
+
input_data = scan_input(args)
|
|
31
|
+
return job_result(
|
|
32
|
+
"metadataScan",
|
|
33
|
+
args.apply,
|
|
34
|
+
input_data,
|
|
35
|
+
lambda: queries.metadata.start_scan(input_data),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def register(subparsers: argparse._SubParsersAction) -> None:
|
|
40
|
+
scan = subparsers.add_parser("scan", help="Start Stash scan jobs.")
|
|
41
|
+
scan_sub = scan.add_subparsers(dest="scan_command", required=True)
|
|
42
|
+
|
|
43
|
+
run = add_leaf(
|
|
44
|
+
scan_sub,
|
|
45
|
+
"run",
|
|
46
|
+
kind=CommandKind.WRITE,
|
|
47
|
+
func=cmd_scan_run,
|
|
48
|
+
help="Start a scan job for library paths and print its job id.",
|
|
49
|
+
examples=(
|
|
50
|
+
"stashapp-cli scan run /media/new --phashes",
|
|
51
|
+
"stashapp-cli scan run /media/new --phashes --covers --apply",
|
|
52
|
+
(
|
|
53
|
+
'stashapp-cli scan run --input \'{"paths": ["/media/new"], '
|
|
54
|
+
'"scanGenerateSprites": true}\''
|
|
55
|
+
),
|
|
56
|
+
),
|
|
57
|
+
)
|
|
58
|
+
run.description = (
|
|
59
|
+
"Start a Stash scan job (metadataScan) and print its job_id. Wait for\n"
|
|
60
|
+
"it with `stashapp-cli job wait JOB_ID`.\n"
|
|
61
|
+
"\n"
|
|
62
|
+
"Scan options that are left out are off: a scan generates phashes or\n"
|
|
63
|
+
"covers only when --phashes or --covers is given. A scan does not add\n"
|
|
64
|
+
"phashes to files that Stash already knows; use\n"
|
|
65
|
+
"`stashapp-cli generate run --phashes` for them."
|
|
66
|
+
)
|
|
67
|
+
run.add_argument(
|
|
68
|
+
"paths",
|
|
69
|
+
nargs="*",
|
|
70
|
+
metavar="PATH",
|
|
71
|
+
help="Paths to scan; replace paths in --input. At least one is required.",
|
|
72
|
+
)
|
|
73
|
+
run.add_argument(
|
|
74
|
+
"--phashes",
|
|
75
|
+
action="store_true",
|
|
76
|
+
help="Generate phashes for new files (sets scanGeneratePhashes).",
|
|
77
|
+
)
|
|
78
|
+
run.add_argument(
|
|
79
|
+
"--covers",
|
|
80
|
+
action="store_true",
|
|
81
|
+
help="Generate covers for new files (sets scanGenerateCovers).",
|
|
82
|
+
)
|
|
83
|
+
run.add_argument(
|
|
84
|
+
"--rescan",
|
|
85
|
+
action="store_true",
|
|
86
|
+
help="Rescan files even if they have not changed (sets rescan).",
|
|
87
|
+
)
|
|
88
|
+
run.add_argument(
|
|
89
|
+
"--input",
|
|
90
|
+
default=None,
|
|
91
|
+
help="ScanMetadataInput as JSON, @file, or @-; options override its fields.",
|
|
92
|
+
)
|