Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
mikf
GitHub Repository: mikf/gallery-dl
Path: blob/master/gallery_dl/option.py
5457 views
1
# -*- coding: utf-8 -*-
2
3
# Copyright 2017-2025 Mike Fährmann
4
#
5
# This program is free software; you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License version 2 as
7
# published by the Free Software Foundation.
8
9
"""Command line option parsing"""
10
11
import argparse
12
import logging
13
import os.path
14
import sys
15
from . import job, util, version
16
17
18
class ConfigAction(argparse.Action):
19
"""Set argparse results as config values"""
20
def __call__(self, parser, namespace, values, option_string=None):
21
namespace.options.append(((), self.dest, values))
22
23
24
class ConfigConstAction(argparse.Action):
25
"""Set argparse const values as config values"""
26
def __call__(self, parser, namespace, values, option_string=None):
27
namespace.options.append(((), self.dest, self.const))
28
29
30
class AppendCommandAction(argparse.Action):
31
def __call__(self, parser, namespace, values, option_string=None):
32
items = getattr(namespace, self.dest, None) or []
33
val = self.const.copy()
34
val["command"] = values
35
items.append(val)
36
setattr(namespace, self.dest, items)
37
38
39
class DeprecatedConfigConstAction(argparse.Action):
40
"""Set argparse const values as config values + deprecation warning"""
41
def __call__(self, parser, namespace, values, option_string=None):
42
sys.stderr.write(
43
f"Warning: {'/'.join(self.option_strings)} is deprecated. "
44
f"Use {self.choices} instead.\n")
45
namespace.options.append(((), self.dest, self.const))
46
47
48
class ConfigParseAction(argparse.Action):
49
"""Parse KEY=VALUE config options"""
50
def __call__(self, parser, namespace, values, option_string=None):
51
key, value = _parse_option(values)
52
key = key.split(".") # splitting an empty string becomes [""]
53
namespace.options.append((key[:-1], key[-1], value))
54
55
56
class PPParseAction(argparse.Action):
57
"""Parse KEY=VALUE post processor options"""
58
def __call__(self, parser, namespace, values, option_string=None):
59
key, value = _parse_option(values)
60
namespace.options_pp[key] = value
61
62
63
class InputfileAction(argparse.Action):
64
"""Collect input files"""
65
def __call__(self, parser, namespace, value, option_string=None):
66
namespace.input_files.append((value, self.const))
67
68
69
class MtimeAction(argparse.Action):
70
"""Configure mtime post processors"""
71
def __call__(self, parser, namespace, value, option_string=None):
72
namespace.postprocessors.append({
73
"name": "mtime",
74
"value": f"{{{self.const or value}}}",
75
})
76
77
78
class RenameAction(argparse.Action):
79
"""Configure rename post processors"""
80
def __call__(self, parser, namespace, value, option_string=None):
81
if self.const:
82
namespace.postprocessors.append({
83
"name": "rename",
84
"to" : value,
85
})
86
else:
87
namespace.postprocessors.append({
88
"name": "rename",
89
"from": value,
90
})
91
92
93
class UgoiraAction(argparse.Action):
94
"""Configure ugoira post processors"""
95
def __call__(self, parser, namespace, value, option_string=None):
96
if self.const:
97
value = self.const
98
else:
99
value = value.strip().lower()
100
101
if value in ("webm", "vp9"):
102
pp = {
103
"extension" : "webm",
104
"ffmpeg-args" : ("-c:v", "libvpx-vp9",
105
"-crf", "12",
106
"-b:v", "0", "-an"),
107
}
108
elif value == "vp9-lossless":
109
pp = {
110
"extension" : "webm",
111
"ffmpeg-args" : ("-c:v", "libvpx-vp9",
112
"-lossless", "1",
113
"-pix_fmt", "yuv420p", "-an"),
114
}
115
elif value == "vp8":
116
pp = {
117
"extension" : "webm",
118
"ffmpeg-args" : ("-c:v", "libvpx",
119
"-crf", "4",
120
"-b:v", "5000k", "-an"),
121
}
122
elif value == "mp4":
123
pp = {
124
"extension" : "mp4",
125
"ffmpeg-args" : ("-c:v", "libx264", "-an", "-b:v", "5M"),
126
"libx264-prevent-odd": True,
127
}
128
elif value == "gif":
129
pp = {
130
"extension" : "gif",
131
"ffmpeg-args" : ("-filter_complex", "[0:v] split [a][b];"
132
"[a] palettegen [p];[b][p] paletteuse"),
133
"repeat-last-frame": False,
134
}
135
elif value == "mkv" or value == "copy":
136
pp = {
137
"extension" : "mkv",
138
"ffmpeg-args" : ("-c:v", "copy"),
139
"repeat-last-frame": False,
140
}
141
elif value == "zip" or value == "archive":
142
pp = {
143
"mode" : "archive",
144
}
145
namespace.options.append(((), "ugoira", "original"))
146
else:
147
parser.error(f"Unsupported Ugoira format '{value}'")
148
149
pp["name"] = "ugoira"
150
pp["whitelist"] = ("pixiv", "danbooru")
151
152
namespace.options.append((("extractor",), "ugoira", True))
153
namespace.postprocessors.append(pp)
154
155
156
class PrintAction(argparse.Action):
157
def __call__(self, parser, namespace, value, option_string=None):
158
if self.const:
159
if self.const == "-":
160
namespace.options.append(((), "skip", False))
161
namespace.options.append(((), "download", False))
162
namespace.options.append((("output",), "mode", False))
163
filename = "-"
164
base = None
165
mode = "w"
166
else:
167
if self.const is None:
168
namespace.options.append(((), "skip", False))
169
namespace.options.append(((), "download", False))
170
value, path = value
171
base, filename = os.path.split(path)
172
mode = "a"
173
174
event, sep, format_string = value.partition(":")
175
if not sep:
176
format_string = event
177
event = ("prepare",)
178
else:
179
event = event.strip().lower()
180
if event not in {"init", "file", "after", "skip", "error",
181
"prepare", "prepare-after", "post", "post-after",
182
"finalize", "finalize-success", "finalize-error"}:
183
format_string = value
184
event = ("prepare",)
185
186
if not format_string:
187
return
188
189
if format_string.startswith("\\f"):
190
format_string = "\f" + format_string[2:]
191
192
if format_string[0] == "\f":
193
if format_string[1] == "F" and format_string[-1] != "\n":
194
format_string += "\n"
195
elif "{" not in format_string and " " not in format_string:
196
format_string = f"{{{format_string}}}\n"
197
elif format_string[-1] != "\n":
198
format_string += "\n"
199
200
namespace.postprocessors.append({
201
"name" : "metadata",
202
"event" : event,
203
"filename" : filename,
204
"base-directory": base or ".",
205
"content-format": format_string,
206
"open" : mode,
207
})
208
209
210
class Formatter(argparse.HelpFormatter):
211
"""Custom HelpFormatter class to customize help output"""
212
def __init__(self, prog):
213
argparse.HelpFormatter.__init__(self, prog, max_help_position=30)
214
215
def _format_action_invocation(self, action):
216
opts = action.option_strings
217
if action.metavar:
218
opts = opts.copy()
219
opts[-1] = f"{opts[-1]} {action.metavar}"
220
return ", ".join(opts)
221
222
def _format_usage(self, usage, actions, groups, prefix):
223
return f"Usage: {self._prog} [OPTIONS] URL [URL...]\n"
224
225
def format_help(self):
226
return self._long_break_matcher.sub(
227
"\n\n", self._root_section.format_help())
228
229
230
def _parse_option(opt):
231
key, _, value = opt.partition("=")
232
try:
233
value = util.json_loads(value)
234
except ValueError:
235
pass
236
return key, value
237
238
239
def build_parser():
240
"""Build and configure an ArgumentParser object"""
241
parser = argparse.ArgumentParser(
242
formatter_class=Formatter,
243
add_help=False,
244
)
245
246
general = parser.add_argument_group("General Options")
247
general.add_argument(
248
"-h", "--help",
249
action="help",
250
help="Print this help message and exit",
251
)
252
general.add_argument(
253
"--version",
254
action="version", version=version.__version__,
255
help="Print program version and exit",
256
)
257
general.add_argument(
258
"-f", "--filename",
259
dest="filename", metavar="FORMAT",
260
help=("Filename format string for downloaded files "
261
"('/O' for \"original\" filenames)"),
262
)
263
general.add_argument(
264
"-d", "--destination",
265
dest="base-directory", metavar="PATH", action=ConfigAction,
266
help="Target location for file downloads",
267
)
268
general.add_argument(
269
"-D", "--directory",
270
dest="directory", metavar="PATH",
271
help="Exact location for file downloads",
272
)
273
general.add_argument(
274
"-X", "--extractors",
275
dest="extractor_sources", metavar="PATH", action="append",
276
help="Load external extractors from PATH",
277
)
278
general.add_argument(
279
"-a", "--user-agent",
280
dest="user-agent", metavar="UA", action=ConfigAction,
281
help="User-Agent request header",
282
)
283
general.add_argument(
284
"--clear-cache",
285
dest="clear_cache", metavar="MODULE",
286
help="Delete cached login sessions, cookies, etc. for MODULE "
287
"(ALL to delete everything)",
288
)
289
general.add_argument(
290
"--compat",
291
dest="category-map", nargs=0, action=ConfigConstAction, const="compat",
292
help="Restore legacy 'category' names",
293
)
294
295
update = parser.add_argument_group("Update Options")
296
if util.EXECUTABLE:
297
update.add_argument(
298
"-U", "--update",
299
dest="update", action="store_const", const="latest",
300
help="Update to the latest version",
301
)
302
update.add_argument(
303
"--update-to",
304
dest="update", metavar="CHANNEL[@TAG]",
305
help=("Switch to a dfferent release channel (stable or dev) "
306
"or upgrade/downgrade to a specific version"),
307
)
308
update.add_argument(
309
"--update-check",
310
dest="update", action="store_const", const="check",
311
help="Check if a newer version is available",
312
)
313
else:
314
update.add_argument(
315
"-U", "--update-check",
316
dest="update", action="store_const", const="check",
317
help="Check if a newer version is available",
318
)
319
320
input = parser.add_argument_group("Input Options")
321
input.add_argument(
322
"urls",
323
metavar="URL", nargs="*",
324
help=argparse.SUPPRESS,
325
)
326
input.add_argument(
327
"-i", "--input-file",
328
dest="input_files", metavar="FILE", action=InputfileAction, const=None,
329
default=[],
330
help=("Download URLs found in FILE ('-' for stdin). "
331
"More than one --input-file can be specified"),
332
)
333
input.add_argument(
334
"-I", "--input-file-comment",
335
dest="input_files", metavar="FILE", action=InputfileAction, const="c",
336
help=("Download URLs found in FILE. "
337
"Comment them out after they were downloaded successfully."),
338
)
339
input.add_argument(
340
"-x", "--input-file-delete",
341
dest="input_files", metavar="FILE", action=InputfileAction, const="d",
342
help=("Download URLs found in FILE. "
343
"Delete them after they were downloaded successfully."),
344
)
345
input.add_argument(
346
"--no-input",
347
dest="input", nargs=0, action=ConfigConstAction, const=False,
348
help="Do not prompt for passwords/tokens",
349
)
350
351
output = parser.add_argument_group("Output Options")
352
output.add_argument(
353
"-q", "--quiet",
354
dest="loglevel", default=logging.INFO,
355
action="store_const", const=logging.ERROR,
356
help="Activate quiet mode",
357
)
358
output.add_argument(
359
"-w", "--warning",
360
dest="loglevel",
361
action="store_const", const=logging.WARNING,
362
help="Print only warnings and errors",
363
)
364
output.add_argument(
365
"-v", "--verbose",
366
dest="loglevel",
367
action="store_const", const=logging.DEBUG,
368
help="Print various debugging information",
369
)
370
output.add_argument(
371
"-g", "--get-urls",
372
dest="list_urls", action="count",
373
help="Print URLs instead of downloading",
374
)
375
output.add_argument(
376
"-G", "--resolve-urls",
377
dest="list_urls", action="store_const", const=128,
378
help="Print URLs instead of downloading; resolve intermediary URLs",
379
)
380
output.add_argument(
381
"-j", "--dump-json",
382
dest="dump_json", action="count",
383
help="Print JSON information",
384
)
385
output.add_argument(
386
"-J", "--resolve-json",
387
dest="dump_json", action="store_const", const=128,
388
help="Print JSON information; resolve intermediary URLs",
389
)
390
output.add_argument(
391
"-s", "--simulate",
392
dest="jobtype", action="store_const", const=job.SimulationJob,
393
help="Simulate data extraction; do not download anything",
394
)
395
output.add_argument(
396
"-E", "--extractor-info",
397
dest="jobtype", action="store_const", const=job.InfoJob,
398
help="Print extractor defaults and settings",
399
)
400
output.add_argument(
401
"-K", "--list-keywords",
402
dest="jobtype", action="store_const", const=job.KeywordJob,
403
help=("Print a list of available keywords and example values "
404
"for the given URLs"),
405
)
406
output.add_argument(
407
"-e", "--error-file",
408
dest="errorfile", metavar="FILE", action=ConfigAction,
409
help="Add input URLs which returned an error to FILE",
410
)
411
output.add_argument(
412
"-N", "--print",
413
dest="postprocessors", metavar="[EVENT:]FORMAT",
414
action=PrintAction, const="-", default=[],
415
help=("Write FORMAT during EVENT (default 'prepare') to standard "
416
"output instead of downloading files. "
417
"Can be used multiple times. "
418
"Examples: 'id' or 'post:{md5[:8]}'"),
419
)
420
output.add_argument(
421
"--Print",
422
dest="postprocessors", metavar="[EVENT:]FORMAT",
423
action=PrintAction, const="+",
424
help="Like --print, but downloads files as well",
425
)
426
output.add_argument(
427
"--print-to-file",
428
dest="postprocessors", metavar="[EVENT:]FORMAT FILE",
429
action=PrintAction, const=None, nargs=2,
430
help=("Append FORMAT during EVENT to FILE instead of downloading "
431
"files. Can be used multiple times"),
432
)
433
output.add_argument(
434
"--Print-to-file",
435
dest="postprocessors", metavar="[EVENT:]FORMAT FILE",
436
action=PrintAction, const=False, nargs=2,
437
help="Like --print-to-file, but downloads files as well",
438
)
439
output.add_argument(
440
"--list-modules",
441
dest="list_modules", action="store_true",
442
help="Print a list of available extractor modules",
443
)
444
output.add_argument(
445
"--list-extractors",
446
dest="list_extractors", metavar="[CATEGORIES]", nargs="*",
447
help=("Print a list of extractor classes "
448
"with description, (sub)category and example URL"),
449
)
450
output.add_argument(
451
"--write-log",
452
dest="logfile", metavar="FILE", action=ConfigAction,
453
help="Write logging output to FILE",
454
)
455
output.add_argument(
456
"--write-unsupported",
457
dest="unsupportedfile", metavar="FILE", action=ConfigAction,
458
help=("Write URLs, which get emitted by other extractors but cannot "
459
"be handled, to FILE"),
460
)
461
output.add_argument(
462
"--write-pages",
463
dest="write-pages", nargs=0, action=ConfigConstAction, const=True,
464
help=("Write downloaded intermediary pages to files "
465
"in the current directory to debug problems"),
466
)
467
output.add_argument(
468
"--print-traffic",
469
dest="print_traffic", action="store_true",
470
help="Display sent and read HTTP traffic",
471
)
472
output.add_argument(
473
"--no-colors",
474
dest="colors", action="store_false",
475
help="Do not emit ANSI color codes in output",
476
)
477
478
networking = parser.add_argument_group("Networking Options")
479
networking.add_argument(
480
"-R", "--retries",
481
dest="retries", metavar="N", type=int, action=ConfigAction,
482
help=("Maximum number of retries for failed HTTP requests "
483
"or -1 for infinite retries (default: 4)"),
484
)
485
networking.add_argument(
486
"--http-timeout",
487
dest="timeout", metavar="SECONDS", type=float, action=ConfigAction,
488
help="Timeout for HTTP connections (default: 30.0)",
489
)
490
networking.add_argument(
491
"--proxy",
492
dest="proxy", metavar="URL", action=ConfigAction,
493
help="Use the specified proxy",
494
)
495
networking.add_argument(
496
"--source-address",
497
dest="source-address", metavar="IP", action=ConfigAction,
498
help="Client-side IP address to bind to",
499
)
500
networking.add_argument(
501
"-4", "--force-ipv4",
502
dest="source-address", nargs=0, action=ConfigConstAction,
503
const="0.0.0.0",
504
help="Make all connections via IPv4",
505
)
506
networking.add_argument(
507
"-6", "--force-ipv6",
508
dest="source-address", nargs=0, action=ConfigConstAction, const="::",
509
help="Make all connections via IPv6",
510
)
511
networking.add_argument(
512
"--no-check-certificate",
513
dest="verify", nargs=0, action=ConfigConstAction, const=False,
514
help="Disable HTTPS certificate validation",
515
)
516
517
downloader = parser.add_argument_group("Downloader Options")
518
downloader.add_argument(
519
"-r", "--limit-rate",
520
dest="rate", metavar="RATE", action=ConfigAction,
521
help="Maximum download rate (e.g. 500k, 2.5M, or 800k-2M)",
522
)
523
downloader.add_argument(
524
"--chunk-size",
525
dest="chunk-size", metavar="SIZE", action=ConfigAction,
526
help="Size of in-memory data chunks (default: 32k)",
527
)
528
downloader.add_argument(
529
"--sleep",
530
dest="sleep", metavar="SECONDS", action=ConfigAction,
531
help=("Number of seconds to wait before each download. "
532
"This can be either a constant value or a range "
533
"(e.g. 2.7 or 2.0-3.5)"),
534
)
535
downloader.add_argument(
536
"--sleep-request",
537
dest="sleep-request", metavar="SECONDS", action=ConfigAction,
538
help=("Number of seconds to wait between HTTP requests "
539
"during data extraction"),
540
)
541
downloader.add_argument(
542
"--sleep-429",
543
dest="sleep-429", metavar="SECONDS", action=ConfigAction,
544
help=("Number of seconds to wait when receiving a "
545
"'429 Too Many Requests' response"),
546
)
547
downloader.add_argument(
548
"--sleep-extractor",
549
dest="sleep-extractor", metavar="SECONDS", action=ConfigAction,
550
help=("Number of seconds to wait before starting data extraction "
551
"for an input URL"),
552
)
553
downloader.add_argument(
554
"--no-part",
555
dest="part", nargs=0, action=ConfigConstAction, const=False,
556
help="Do not use .part files",
557
)
558
downloader.add_argument(
559
"--no-skip",
560
dest="skip", nargs=0, action=ConfigConstAction, const=False,
561
help="Do not skip downloads; overwrite existing files",
562
)
563
downloader.add_argument(
564
"--no-mtime",
565
dest="mtime", nargs=0, action=ConfigConstAction, const=False,
566
help=("Do not set file modification times according to "
567
"Last-Modified HTTP response headers")
568
)
569
downloader.add_argument(
570
"--no-download",
571
dest="download", nargs=0, action=ConfigConstAction, const=False,
572
help=("Do not download any files")
573
)
574
575
configuration = parser.add_argument_group("Configuration Options")
576
configuration.add_argument(
577
"-o", "--option",
578
dest="options", metavar="KEY=VALUE",
579
action=ConfigParseAction, default=[],
580
help=("Additional options. "
581
"Example: -o browser=firefox") ,
582
)
583
configuration.add_argument(
584
"-c", "--config",
585
dest="configs_json", metavar="FILE", action="append",
586
help="Additional configuration files",
587
)
588
configuration.add_argument(
589
"--config-yaml",
590
dest="configs_yaml", metavar="FILE", action="append",
591
help="Additional configuration files in YAML format",
592
)
593
configuration.add_argument(
594
"--config-toml",
595
dest="configs_toml", metavar="FILE", action="append",
596
help="Additional configuration files in TOML format",
597
)
598
configuration.add_argument(
599
"--config-create",
600
dest="config", action="store_const", const="init",
601
help="Create a basic configuration file",
602
)
603
configuration.add_argument(
604
"--config-status",
605
dest="config", action="store_const", const="status",
606
help="Show configuration file status",
607
)
608
configuration.add_argument(
609
"--config-open",
610
dest="config", action="store_const", const="open",
611
help="Open configuration file in external application",
612
)
613
configuration.add_argument(
614
"--config-ignore",
615
dest="config_load", action="store_false",
616
help="Do not read default configuration files",
617
)
618
configuration.add_argument(
619
"--ignore-config",
620
dest="config_load", action="store_false",
621
help=argparse.SUPPRESS,
622
)
623
624
authentication = parser.add_argument_group("Authentication Options")
625
authentication.add_argument(
626
"-u", "--username",
627
dest="username", metavar="USER", action=ConfigAction,
628
help="Username to login with",
629
)
630
authentication.add_argument(
631
"-p", "--password",
632
dest="password", metavar="PASS", action=ConfigAction,
633
help="Password belonging to the given username",
634
)
635
authentication.add_argument(
636
"--netrc",
637
dest="netrc", nargs=0, action=ConfigConstAction, const=True,
638
help="Enable .netrc authentication data",
639
)
640
641
cookies = parser.add_argument_group("Cookie Options")
642
cookies.add_argument(
643
"-C", "--cookies",
644
dest="cookies", metavar="FILE", action=ConfigAction,
645
help="File to load additional cookies from",
646
)
647
cookies.add_argument(
648
"--cookies-export",
649
dest="cookies-update", metavar="FILE", action=ConfigAction,
650
help="Export session cookies to FILE",
651
)
652
cookies.add_argument(
653
"--cookies-from-browser",
654
dest="cookies_from_browser",
655
metavar="BROWSER[/DOMAIN][+KEYRING][:PROFILE][::CONTAINER]",
656
help=("Name of the browser to load cookies from, with optional "
657
"domain prefixed with '/', "
658
"keyring name prefixed with '+', "
659
"profile prefixed with ':', and "
660
"container prefixed with '::' "
661
"('none' for no container (default), 'all' for all containers)"),
662
)
663
664
selection = parser.add_argument_group("Selection Options")
665
selection.add_argument(
666
"-A", "--abort",
667
dest="abort", metavar="N[:TARGET]",
668
help=("Stop current extractor(s) "
669
"after N consecutive file downloads were skipped. "
670
"Specify a TARGET to set how many levels to ascend or "
671
"to which subcategory to jump to. "
672
"Examples: '-A 3', '-A 3:2', '-A 3:manga'"),
673
)
674
selection.add_argument(
675
"-T", "--terminate",
676
dest="terminate", metavar="N",
677
help=("Stop current & parent extractors "
678
"and proceed with the next input URL "
679
"after N consecutive file downloads were skipped"),
680
)
681
selection.add_argument(
682
"--filesize-min",
683
dest="filesize-min", metavar="SIZE", action=ConfigAction,
684
help="Do not download files smaller than SIZE (e.g. 500k or 2.5M)",
685
)
686
selection.add_argument(
687
"--filesize-max",
688
dest="filesize-max", metavar="SIZE", action=ConfigAction,
689
help="Do not download files larger than SIZE (e.g. 500k or 2.5M)",
690
)
691
selection.add_argument(
692
"--download-archive",
693
dest="archive", metavar="FILE", action=ConfigAction,
694
help=("Record successfully downloaded files in FILE and "
695
"skip downloading any file already in it"),
696
)
697
selection.add_argument(
698
"--range",
699
dest="image-range", metavar="RANGE", action=ConfigAction,
700
help=("Index range(s) specifying which files to download. "
701
"These can be either a constant value, range, or slice "
702
"(e.g. '5', '8-20', or '1:24:3')"),
703
)
704
selection.add_argument(
705
"--chapter-range",
706
dest="chapter-range", metavar="RANGE", action=ConfigAction,
707
help=("Like '--range', but applies to manga chapters "
708
"and other delegated URLs"),
709
)
710
selection.add_argument(
711
"--filter",
712
dest="image-filter", metavar="EXPR", action=ConfigAction,
713
help=("Python expression controlling which files to download. "
714
"Files for which the expression evaluates to False are ignored. "
715
"Available keys are the filename-specific ones listed by '-K'. "
716
"Example: --filter \"image_width >= 1000 and "
717
"rating in ('s', 'q')\""),
718
)
719
selection.add_argument(
720
"--chapter-filter",
721
dest="chapter-filter", metavar="EXPR", action=ConfigAction,
722
help=("Like '--filter', but applies to manga chapters "
723
"and other delegated URLs"),
724
)
725
726
infojson = {
727
"name" : "metadata",
728
"event" : "init",
729
"filename": "info.json",
730
}
731
postprocessor = parser.add_argument_group("Post-processing Options")
732
postprocessor.add_argument(
733
"-P", "--postprocessor",
734
dest="postprocessors", metavar="NAME", action="append",
735
help="Activate the specified post processor",
736
)
737
postprocessor.add_argument(
738
"--no-postprocessors",
739
dest="postprocess", nargs=0, action=ConfigConstAction, const=False,
740
help=("Do not run any post processors")
741
)
742
postprocessor.add_argument(
743
"-O", "--postprocessor-option",
744
dest="options_pp", metavar="KEY=VALUE",
745
action=PPParseAction, default={},
746
help="Additional post processor options",
747
)
748
postprocessor.add_argument(
749
"--write-metadata",
750
dest="postprocessors",
751
action="append_const", const="metadata",
752
help="Write metadata to separate JSON files",
753
)
754
postprocessor.add_argument(
755
"--write-info-json",
756
dest="postprocessors",
757
action="append_const", const=infojson,
758
help="Write gallery metadata to a info.json file",
759
)
760
postprocessor.add_argument(
761
"--write-infojson",
762
dest="postprocessors",
763
action="append_const", const=infojson,
764
help=argparse.SUPPRESS,
765
)
766
postprocessor.add_argument(
767
"--write-tags",
768
dest="postprocessors",
769
action="append_const", const={"name": "metadata", "mode": "tags"},
770
help="Write image tags to separate text files",
771
)
772
postprocessor.add_argument(
773
"--zip",
774
dest="postprocessors",
775
action="append_const", const="zip",
776
help="Store downloaded files in a ZIP archive",
777
)
778
postprocessor.add_argument(
779
"--cbz",
780
dest="postprocessors",
781
action="append_const", const={
782
"name" : "zip",
783
"extension": "cbz",
784
},
785
help="Store downloaded files in a CBZ archive",
786
)
787
postprocessor.add_argument(
788
"--mtime",
789
dest="postprocessors", metavar="NAME", action=MtimeAction,
790
help=("Set file modification times according to metadata "
791
"selected by NAME. Examples: 'date' or 'status[date]'"),
792
)
793
postprocessor.add_argument(
794
"--mtime-from-date",
795
dest="postprocessors", nargs=0, action=MtimeAction,
796
const="date|status[date]",
797
help=argparse.SUPPRESS,
798
)
799
postprocessor.add_argument(
800
"--rename",
801
dest="postprocessors", metavar="FORMAT", action=RenameAction, const=0,
802
help=("Rename previously downloaded files from FORMAT "
803
"to the current filename format"),
804
)
805
postprocessor.add_argument(
806
"--rename-to",
807
dest="postprocessors", metavar="FORMAT", action=RenameAction, const=1,
808
help=("Rename previously downloaded files from the current filename "
809
"format to FORMAT"),
810
)
811
postprocessor.add_argument(
812
"--ugoira",
813
dest="postprocessors", metavar="FMT", action=UgoiraAction,
814
help=("Convert Pixiv Ugoira to FMT using FFmpeg. "
815
"Supported formats are 'webm', 'mp4', 'gif', "
816
"'vp8', 'vp9', 'vp9-lossless', 'copy', 'zip'."),
817
)
818
postprocessor.add_argument(
819
"--ugoira-conv",
820
dest="postprocessors", nargs=0, action=UgoiraAction, const="vp8",
821
help=argparse.SUPPRESS,
822
)
823
postprocessor.add_argument(
824
"--ugoira-conv-lossless",
825
dest="postprocessors", nargs=0, action=UgoiraAction,
826
const="vp9-lossless",
827
help=argparse.SUPPRESS,
828
)
829
postprocessor.add_argument(
830
"--ugoira-conv-copy",
831
dest="postprocessors", nargs=0, action=UgoiraAction, const="copy",
832
help=argparse.SUPPRESS,
833
)
834
postprocessor.add_argument(
835
"--exec",
836
dest="postprocessors", metavar="CMD",
837
action=AppendCommandAction, const={"name": "exec"},
838
help=("Execute CMD for each downloaded file. "
839
"Supported replacement fields are "
840
"{} or {_path}, {_directory}, {_filename}. "
841
"Example: --exec \"convert {} {}.png && rm {}\""),
842
)
843
postprocessor.add_argument(
844
"--exec-after",
845
dest="postprocessors", metavar="CMD",
846
action=AppendCommandAction, const={
847
"name": "exec", "event": "finalize"},
848
help=("Execute CMD after all files were downloaded. "
849
"Example: --exec-after \"cd {_directory} "
850
"&& convert * ../doc.pdf\""),
851
)
852
853
try:
854
# restore normal behavior when adding '-4' or '-6' as arguments
855
parser._has_negative_number_optionals.clear()
856
except Exception:
857
pass
858
859
return parser
860
861