sticker-convert 2.7.2__py3-none-any.whl → 2.7.4__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.
- sticker_convert/__init__.py +1 -0
- sticker_convert/__main__.py +3 -1
- sticker_convert/cli.py +20 -24
- sticker_convert/converter.py +108 -119
- sticker_convert/definitions.py +8 -12
- sticker_convert/downloaders/download_base.py +14 -31
- sticker_convert/downloaders/download_kakao.py +25 -39
- sticker_convert/downloaders/download_line.py +24 -33
- sticker_convert/downloaders/download_signal.py +7 -16
- sticker_convert/downloaders/download_telegram.py +6 -15
- sticker_convert/gui.py +53 -61
- sticker_convert/gui_components/frames/comp_frame.py +11 -20
- sticker_convert/gui_components/frames/config_frame.py +9 -9
- sticker_convert/gui_components/frames/control_frame.py +3 -3
- sticker_convert/gui_components/frames/cred_frame.py +12 -18
- sticker_convert/gui_components/frames/input_frame.py +9 -15
- sticker_convert/gui_components/frames/output_frame.py +9 -15
- sticker_convert/gui_components/frames/progress_frame.py +8 -8
- sticker_convert/gui_components/frames/right_clicker.py +2 -2
- sticker_convert/gui_components/gui_utils.py +6 -8
- sticker_convert/gui_components/windows/advanced_compression_window.py +23 -32
- sticker_convert/gui_components/windows/base_window.py +6 -6
- sticker_convert/gui_components/windows/kakao_get_auth_window.py +5 -11
- sticker_convert/gui_components/windows/line_get_auth_window.py +5 -5
- sticker_convert/gui_components/windows/signal_get_auth_window.py +6 -6
- sticker_convert/job.py +84 -90
- sticker_convert/job_option.py +36 -32
- sticker_convert/resources/emoji.json +334 -70
- sticker_convert/resources/help.json +1 -1
- sticker_convert/uploaders/compress_wastickers.py +19 -30
- sticker_convert/uploaders/upload_base.py +19 -13
- sticker_convert/uploaders/upload_signal.py +20 -33
- sticker_convert/uploaders/upload_telegram.py +21 -28
- sticker_convert/uploaders/xcode_imessage.py +30 -95
- sticker_convert/utils/auth/get_kakao_auth.py +7 -8
- sticker_convert/utils/auth/get_line_auth.py +5 -6
- sticker_convert/utils/auth/get_signal_auth.py +7 -7
- sticker_convert/utils/callback.py +31 -23
- sticker_convert/utils/files/cache_store.py +6 -8
- sticker_convert/utils/files/json_manager.py +6 -7
- sticker_convert/utils/files/json_resources_loader.py +12 -0
- sticker_convert/utils/files/metadata_handler.py +93 -84
- sticker_convert/utils/files/run_bin.py +11 -10
- sticker_convert/utils/files/sanitize_filename.py +30 -28
- sticker_convert/utils/media/apple_png_normalize.py +3 -2
- sticker_convert/utils/media/codec_info.py +41 -44
- sticker_convert/utils/media/decrypt_kakao.py +7 -7
- sticker_convert/utils/media/format_verify.py +14 -14
- sticker_convert/utils/url_detect.py +4 -5
- sticker_convert/version.py +2 -1
- {sticker_convert-2.7.2.dist-info → sticker_convert-2.7.4.dist-info}/METADATA +19 -17
- {sticker_convert-2.7.2.dist-info → sticker_convert-2.7.4.dist-info}/RECORD +56 -55
- {sticker_convert-2.7.2.dist-info → sticker_convert-2.7.4.dist-info}/WHEEL +1 -1
- {sticker_convert-2.7.2.dist-info → sticker_convert-2.7.4.dist-info}/LICENSE +0 -0
- {sticker_convert-2.7.2.dist-info → sticker_convert-2.7.4.dist-info}/entry_points.txt +0 -0
- {sticker_convert-2.7.2.dist-info → sticker_convert-2.7.4.dist-info}/top_level.txt +0 -0
sticker_convert/job.py
CHANGED
@@ -7,15 +7,14 @@ import shutil
|
|
7
7
|
import traceback
|
8
8
|
from datetime import datetime
|
9
9
|
from multiprocessing import Process, Value
|
10
|
-
from multiprocessing.managers import SyncManager
|
10
|
+
from multiprocessing.managers import ListProxy, SyncManager
|
11
11
|
from pathlib import Path
|
12
12
|
from queue import Queue
|
13
13
|
from threading import Thread
|
14
|
-
from typing import Any, Callable, Generator, Optional,
|
14
|
+
from typing import TYPE_CHECKING, Any, Callable, Dict, Generator, Iterator, List, Optional, Tuple
|
15
15
|
from urllib.parse import urlparse
|
16
16
|
|
17
17
|
from sticker_convert.converter import StickerConvert
|
18
|
-
from sticker_convert.definitions import ROOT_DIR
|
19
18
|
from sticker_convert.downloaders.download_kakao import DownloadKakao
|
20
19
|
from sticker_convert.downloaders.download_line import DownloadLine
|
21
20
|
from sticker_convert.downloaders.download_signal import DownloadSignal
|
@@ -25,11 +24,19 @@ from sticker_convert.uploaders.compress_wastickers import CompressWastickers
|
|
25
24
|
from sticker_convert.uploaders.upload_signal import UploadSignal
|
26
25
|
from sticker_convert.uploaders.upload_telegram import UploadTelegram
|
27
26
|
from sticker_convert.uploaders.xcode_imessage import XcodeImessage
|
28
|
-
from sticker_convert.utils.callback import CallbackReturn
|
29
|
-
from sticker_convert.utils.files.
|
27
|
+
from sticker_convert.utils.callback import CallbackReturn, CbQueueItemType
|
28
|
+
from sticker_convert.utils.files.json_resources_loader import OUTPUT_JSON
|
30
29
|
from sticker_convert.utils.files.metadata_handler import MetadataHandler
|
31
30
|
from sticker_convert.utils.media.codec_info import CodecInfo
|
32
31
|
|
32
|
+
CbQueueType = Queue[CbQueueItemType]
|
33
|
+
WorkListItemType = Optional[Tuple[Callable[..., Any], Tuple[Any, ...]]]
|
34
|
+
if TYPE_CHECKING:
|
35
|
+
# mypy complains about this
|
36
|
+
WorkListType = ListProxy[WorkListItemType] # type: ignore
|
37
|
+
else:
|
38
|
+
WorkListType = List[WorkListItemType]
|
39
|
+
|
33
40
|
|
34
41
|
class Executor:
|
35
42
|
def __init__(
|
@@ -39,7 +46,7 @@ class Executor:
|
|
39
46
|
cb_bar: Callable[..., None],
|
40
47
|
cb_ask_bool: Callable[..., bool],
|
41
48
|
cb_ask_str: Callable[..., str],
|
42
|
-
):
|
49
|
+
) -> None:
|
43
50
|
self.cb_msg = cb_msg
|
44
51
|
self.cb_msg_block = cb_msg_block
|
45
52
|
self.cb_bar = cb_bar
|
@@ -48,21 +55,13 @@ class Executor:
|
|
48
55
|
|
49
56
|
self.manager = SyncManager()
|
50
57
|
self.manager.start()
|
51
|
-
|
52
|
-
|
53
|
-
|
58
|
+
# Using list instead of queue for work_list as it can cause random deadlocks
|
59
|
+
# Especially when using scale_filter=nearest
|
60
|
+
self.work_list: WorkListType = self.manager.list()
|
54
61
|
self.results_queue: Queue[Any] = self.manager.Queue()
|
55
|
-
self.cb_queue: Queue
|
56
|
-
Union[
|
57
|
-
tuple[
|
58
|
-
Optional[str], Optional[tuple[Any, ...]], Optional[dict[str, str]]
|
59
|
-
],
|
60
|
-
str,
|
61
|
-
None,
|
62
|
-
]
|
63
|
-
] = self.manager.Queue()
|
62
|
+
self.cb_queue: CbQueueType = self.manager.Queue()
|
64
63
|
self.cb_return = CallbackReturn()
|
65
|
-
self.processes:
|
64
|
+
self.processes: List[Process] = []
|
66
65
|
|
67
66
|
self.is_cancel_job = Value("i", 0)
|
68
67
|
|
@@ -77,31 +76,24 @@ class Executor:
|
|
77
76
|
|
78
77
|
def cb_thread(
|
79
78
|
self,
|
80
|
-
cb_queue:
|
81
|
-
Union[
|
82
|
-
tuple[
|
83
|
-
Optional[str], Optional[tuple[Any, ...]], Optional[dict[str, str]]
|
84
|
-
],
|
85
|
-
str,
|
86
|
-
]
|
87
|
-
],
|
79
|
+
cb_queue: CbQueueType,
|
88
80
|
cb_return: CallbackReturn,
|
89
|
-
):
|
81
|
+
) -> None:
|
90
82
|
for i in iter(cb_queue.get, None):
|
91
83
|
if isinstance(i, tuple):
|
92
84
|
action = i[0]
|
93
85
|
if len(i) >= 2:
|
94
|
-
args:
|
86
|
+
args: Tuple[str, ...] = i[1] if i[1] else tuple()
|
95
87
|
else:
|
96
88
|
args = tuple()
|
97
89
|
if len(i) >= 3:
|
98
|
-
kwargs:
|
90
|
+
kwargs: Dict[str, str] = i[2] if i[2] else {}
|
99
91
|
else:
|
100
|
-
kwargs =
|
92
|
+
kwargs = {}
|
101
93
|
else:
|
102
94
|
action = i
|
103
95
|
args = tuple()
|
104
|
-
kwargs =
|
96
|
+
kwargs = {}
|
105
97
|
if action == "msg":
|
106
98
|
self.cb_msg(*args, **kwargs)
|
107
99
|
elif action == "bar":
|
@@ -119,24 +111,27 @@ class Executor:
|
|
119
111
|
|
120
112
|
@staticmethod
|
121
113
|
def worker(
|
122
|
-
|
114
|
+
work_list: WorkListType,
|
123
115
|
results_queue: Queue[Any],
|
124
|
-
cb_queue:
|
125
|
-
Union[
|
126
|
-
tuple[
|
127
|
-
Optional[str], Optional[tuple[Any, ...]], Optional[dict[str, str]]
|
128
|
-
],
|
129
|
-
str,
|
130
|
-
]
|
131
|
-
],
|
116
|
+
cb_queue: CbQueueType,
|
132
117
|
cb_return: CallbackReturn,
|
133
|
-
):
|
134
|
-
|
118
|
+
) -> None:
|
119
|
+
while True:
|
120
|
+
try:
|
121
|
+
work = work_list.pop(0)
|
122
|
+
except IndexError:
|
123
|
+
break
|
124
|
+
|
125
|
+
if work is None:
|
126
|
+
break
|
127
|
+
else:
|
128
|
+
work_func, work_args = work
|
129
|
+
|
135
130
|
try:
|
136
131
|
results = work_func(*work_args, cb_queue, cb_return)
|
137
132
|
results_queue.put(results)
|
138
133
|
except Exception:
|
139
|
-
arg_dump:
|
134
|
+
arg_dump: List[Any] = []
|
140
135
|
for i in work_args:
|
141
136
|
if isinstance(i, CredOption):
|
142
137
|
arg_dump.append("CredOption(REDACTED)")
|
@@ -148,18 +143,14 @@ class Executor:
|
|
148
143
|
e += traceback.format_exc()
|
149
144
|
e += "#####################"
|
150
145
|
cb_queue.put(e)
|
151
|
-
|
152
|
-
|
153
|
-
def start_workers(self, processes: int = 1):
|
154
|
-
# Would contain None from previous run
|
155
|
-
while not self.work_queue.empty():
|
156
|
-
self.work_queue.get()
|
146
|
+
work_list.append(None)
|
157
147
|
|
148
|
+
def start_workers(self, processes: int = 1) -> None:
|
158
149
|
for _ in range(processes):
|
159
150
|
process = Process(
|
160
151
|
target=Executor.worker,
|
161
152
|
args=(
|
162
|
-
self.
|
153
|
+
self.work_list,
|
163
154
|
self.results_queue,
|
164
155
|
self.cb_queue,
|
165
156
|
self.cb_return,
|
@@ -170,11 +161,13 @@ class Executor:
|
|
170
161
|
process.start()
|
171
162
|
self.processes.append(process)
|
172
163
|
|
173
|
-
def add_work(
|
174
|
-
self
|
164
|
+
def add_work(
|
165
|
+
self, work_func: Callable[..., Any], work_args: Tuple[Any, ...]
|
166
|
+
) -> None:
|
167
|
+
self.work_list.append((work_func, work_args))
|
175
168
|
|
176
|
-
def join_workers(self):
|
177
|
-
self.
|
169
|
+
def join_workers(self) -> None:
|
170
|
+
self.work_list.append(None)
|
178
171
|
try:
|
179
172
|
for process in self.processes:
|
180
173
|
process.join()
|
@@ -182,13 +175,12 @@ class Executor:
|
|
182
175
|
pass
|
183
176
|
|
184
177
|
self.results_queue.put(None)
|
178
|
+
self.work_list = self.manager.list()
|
179
|
+
self.processes.clear()
|
185
180
|
|
186
|
-
|
187
|
-
|
188
|
-
def kill_workers(self, *args: Any, **kwargs: Any):
|
181
|
+
def kill_workers(self, *_: Any, **__: Any) -> None:
|
189
182
|
self.is_cancel_job.value = 1 # type: ignore
|
190
|
-
|
191
|
-
self.work_queue.get()
|
183
|
+
self.work_list = self.manager.list()
|
192
184
|
|
193
185
|
for process in self.processes:
|
194
186
|
if platform.system() == "Windows":
|
@@ -199,20 +191,21 @@ class Executor:
|
|
199
191
|
|
200
192
|
self.cleanup()
|
201
193
|
|
202
|
-
def cleanup(self):
|
194
|
+
def cleanup(self) -> None:
|
203
195
|
self.cb_queue.put(None)
|
204
196
|
self.cb_thread_instance.join()
|
197
|
+
self.work_list = self.manager.list()
|
205
198
|
|
206
199
|
def get_result(self) -> Generator[Any, None, None]:
|
207
|
-
|
208
|
-
|
200
|
+
gen: Iterator[Any] = iter(self.results_queue.get, None)
|
201
|
+
yield from gen
|
209
202
|
|
210
203
|
def cb(
|
211
204
|
self,
|
212
205
|
action: Optional[str],
|
213
|
-
args: Optional[
|
214
|
-
kwargs: Optional[
|
215
|
-
):
|
206
|
+
args: Optional[Tuple[str, ...]] = None,
|
207
|
+
kwargs: Optional[Dict[str, Any]] = None,
|
208
|
+
) -> None:
|
216
209
|
self.cb_queue.put((action, args, kwargs))
|
217
210
|
|
218
211
|
|
@@ -228,7 +221,7 @@ class Job:
|
|
228
221
|
cb_bar: Callable[..., None],
|
229
222
|
cb_ask_bool: Callable[..., bool],
|
230
223
|
cb_ask_str: Callable[..., str],
|
231
|
-
):
|
224
|
+
) -> None:
|
232
225
|
self.opt_input = opt_input
|
233
226
|
self.opt_comp = opt_comp
|
234
227
|
self.opt_output = opt_output
|
@@ -239,8 +232,8 @@ class Job:
|
|
239
232
|
self.cb_ask_bool = cb_ask_bool
|
240
233
|
self.cb_ask_str = cb_ask_str
|
241
234
|
|
242
|
-
self.compress_fails:
|
243
|
-
self.out_urls:
|
235
|
+
self.compress_fails: List[str] = []
|
236
|
+
self.out_urls: List[str] = []
|
244
237
|
|
245
238
|
self.executor = Executor(
|
246
239
|
self.cb_msg,
|
@@ -277,7 +270,7 @@ class Job:
|
|
277
270
|
code = 2
|
278
271
|
self.executor.cb("Job cancelled.")
|
279
272
|
break
|
280
|
-
|
273
|
+
if not success:
|
281
274
|
code = 1
|
282
275
|
self.executor.cb("An error occured during this run.")
|
283
276
|
break
|
@@ -287,7 +280,7 @@ class Job:
|
|
287
280
|
|
288
281
|
return code
|
289
282
|
|
290
|
-
def cancel(self, *
|
283
|
+
def cancel(self, *_: Any, **_kwargs: Any) -> None:
|
291
284
|
self.executor.kill_workers()
|
292
285
|
|
293
286
|
def verify_input(self) -> bool:
|
@@ -332,7 +325,7 @@ class Job:
|
|
332
325
|
error_msg += "[X] Uploading to signal requires uuid and password.\n"
|
333
326
|
error_msg += save_to_local_tip
|
334
327
|
|
335
|
-
output_presets =
|
328
|
+
output_presets = OUTPUT_JSON
|
336
329
|
|
337
330
|
input_option = self.opt_input.option
|
338
331
|
output_option = self.opt_output.option
|
@@ -401,7 +394,9 @@ class Job:
|
|
401
394
|
|
402
395
|
if self.opt_comp.scale_filter not in (
|
403
396
|
"nearest",
|
397
|
+
"box",
|
404
398
|
"bilinear",
|
399
|
+
"hamming",
|
405
400
|
"bicubic",
|
406
401
|
"lanczos",
|
407
402
|
):
|
@@ -409,7 +404,9 @@ class Job:
|
|
409
404
|
error_msg += (
|
410
405
|
f"[X] scale_filter {self.opt_comp.scale_filter} is not valid option"
|
411
406
|
)
|
412
|
-
error_msg +=
|
407
|
+
error_msg += (
|
408
|
+
" Valid options: nearest, box, bilinear, hamming, bicubic, lanczos"
|
409
|
+
)
|
413
410
|
|
414
411
|
if self.opt_comp.quantize_method not in ("imagequant", "fastoctree", "none"):
|
415
412
|
error_msg += "\n"
|
@@ -448,7 +445,7 @@ class Job:
|
|
448
445
|
or not any(path.iterdir())
|
449
446
|
):
|
450
447
|
continue
|
451
|
-
|
448
|
+
if path_type == "Output" and (
|
452
449
|
path.name == "stickers_output"
|
453
450
|
or self.opt_comp.no_compress
|
454
451
|
or not any(path.iterdir())
|
@@ -456,7 +453,7 @@ class Job:
|
|
456
453
|
continue
|
457
454
|
|
458
455
|
related_files = MetadataHandler.get_files_related_to_sticker_convert(path)
|
459
|
-
if any(
|
456
|
+
if any(i for i in path.iterdir() if i not in related_files):
|
460
457
|
msg = "WARNING: {} directory is set to {}.\n"
|
461
458
|
msg += 'It does not have default name of "{}",\n'
|
462
459
|
msg += "and It seems like it contains PERSONAL DATA.\n"
|
@@ -528,7 +525,7 @@ class Job:
|
|
528
525
|
return True
|
529
526
|
|
530
527
|
def download(self) -> bool:
|
531
|
-
downloaders:
|
528
|
+
downloaders: List[Callable[..., bool]] = []
|
532
529
|
|
533
530
|
if self.opt_input.option == "signal":
|
534
531
|
downloaders.append(DownloadSignal.start)
|
@@ -548,14 +545,13 @@ class Job:
|
|
548
545
|
self.executor.cb("Nothing to download")
|
549
546
|
return True
|
550
547
|
|
551
|
-
self.executor.start_workers(processes=1)
|
552
|
-
|
553
548
|
for downloader in downloaders:
|
554
549
|
self.executor.add_work(
|
555
550
|
work_func=downloader,
|
556
551
|
work_args=(self.opt_input.url, self.opt_input.dir, self.opt_cred),
|
557
552
|
)
|
558
553
|
|
554
|
+
self.executor.start_workers(processes=1)
|
559
555
|
self.executor.join_workers()
|
560
556
|
|
561
557
|
# Return False if any of the job returns failure
|
@@ -600,7 +596,7 @@ class Job:
|
|
600
596
|
input_dir = Path(self.opt_input.dir)
|
601
597
|
output_dir = Path(self.opt_output.dir)
|
602
598
|
|
603
|
-
in_fs:
|
599
|
+
in_fs: List[Path] = []
|
604
600
|
|
605
601
|
# .txt: emoji.txt, title.txt
|
606
602
|
# .m4a: line sticker sound effects
|
@@ -609,9 +605,7 @@ class Job:
|
|
609
605
|
|
610
606
|
if not in_f.is_file():
|
611
607
|
continue
|
612
|
-
|
613
|
-
CodecInfo.get_file_ext(i) in (".txt", ".m4a") or Path(i).stem == "cover"
|
614
|
-
):
|
608
|
+
if CodecInfo.get_file_ext(i) in (".txt", ".m4a") or Path(i).stem == "cover":
|
615
609
|
shutil.copy(in_f, output_dir / i.name)
|
616
610
|
else:
|
617
611
|
in_fs.append(i)
|
@@ -623,8 +617,6 @@ class Job:
|
|
623
617
|
"bar", kwargs={"set_progress_mode": "determinate", "steps": in_fs_count}
|
624
618
|
)
|
625
619
|
|
626
|
-
self.executor.start_workers(processes=min(self.opt_comp.processes, in_fs_count))
|
627
|
-
|
628
620
|
for i in in_fs:
|
629
621
|
in_f = input_dir / i.name
|
630
622
|
out_f = output_dir / Path(i).stem
|
@@ -633,6 +625,7 @@ class Job:
|
|
633
625
|
work_func=StickerConvert.convert, work_args=(in_f, out_f, self.opt_comp)
|
634
626
|
)
|
635
627
|
|
628
|
+
self.executor.start_workers(processes=min(self.opt_comp.processes, in_fs_count))
|
636
629
|
self.executor.join_workers()
|
637
630
|
|
638
631
|
# Return False if any of the job returns failure
|
@@ -649,7 +642,7 @@ class Job:
|
|
649
642
|
|
650
643
|
self.executor.cb("Exporting...")
|
651
644
|
|
652
|
-
exporters:
|
645
|
+
exporters: List[Callable[..., List[str]]] = []
|
653
646
|
|
654
647
|
if self.opt_output.option == "whatsapp":
|
655
648
|
exporters.append(CompressWastickers.start)
|
@@ -666,21 +659,22 @@ class Job:
|
|
666
659
|
if self.opt_output.option == "imessage":
|
667
660
|
exporters.append(XcodeImessage.start)
|
668
661
|
|
669
|
-
self.executor.start_workers(processes=1)
|
670
|
-
|
671
662
|
for exporter in exporters:
|
672
663
|
self.executor.add_work(
|
673
664
|
work_func=exporter,
|
674
665
|
work_args=(self.opt_output, self.opt_comp, self.opt_cred),
|
675
666
|
)
|
676
667
|
|
668
|
+
self.executor.start_workers(processes=1)
|
677
669
|
self.executor.join_workers()
|
678
670
|
|
679
671
|
for result in self.executor.get_result():
|
680
672
|
self.out_urls.extend(result)
|
681
673
|
|
682
674
|
if self.out_urls:
|
683
|
-
with open(
|
675
|
+
with open(
|
676
|
+
Path(self.opt_output.dir, "export-result.txt"), "w+", encoding="utf-8"
|
677
|
+
) as f:
|
684
678
|
f.write("\n".join(self.out_urls))
|
685
679
|
else:
|
686
680
|
self.executor.cb("An error occured while exporting stickers")
|
@@ -694,14 +688,14 @@ class Job:
|
|
694
688
|
msg += "##########\n"
|
695
689
|
msg += "\n"
|
696
690
|
|
697
|
-
if self.compress_fails
|
691
|
+
if self.compress_fails:
|
698
692
|
msg += f'Warning: Could not compress the following {len(self.compress_fails)} file{"s" if len(self.compress_fails) > 1 else ""}:\n'
|
699
693
|
msg += "\n".join(self.compress_fails)
|
700
694
|
msg += "\n"
|
701
695
|
msg += "\nConsider adjusting compression parameters"
|
702
696
|
msg += "\n"
|
703
697
|
|
704
|
-
if self.out_urls
|
698
|
+
if self.out_urls:
|
705
699
|
msg += "Export results:\n"
|
706
700
|
msg += "\n".join(self.out_urls)
|
707
701
|
else:
|
sticker_convert/job_option.py
CHANGED
@@ -6,7 +6,7 @@ from dataclasses import dataclass
|
|
6
6
|
from math import ceil
|
7
7
|
from multiprocessing import cpu_count
|
8
8
|
from pathlib import Path
|
9
|
-
from typing import Any, Optional, Union
|
9
|
+
from typing import Any, Dict, Optional, Tuple, Union
|
10
10
|
|
11
11
|
|
12
12
|
def to_int(i: Union[float, str, None]) -> Optional[int]:
|
@@ -15,7 +15,7 @@ def to_int(i: Union[float, str, None]) -> Optional[int]:
|
|
15
15
|
|
16
16
|
@dataclass
|
17
17
|
class BaseOption:
|
18
|
-
def merge(self, config: "BaseOption"):
|
18
|
+
def merge(self, config: "BaseOption") -> None:
|
19
19
|
for k, v in vars(config).items():
|
20
20
|
if v is not None:
|
21
21
|
setattr(self, k, v)
|
@@ -23,8 +23,8 @@ class BaseOption:
|
|
23
23
|
def __repr__(self) -> str:
|
24
24
|
return json.dumps(self.to_dict(), indent=2)
|
25
25
|
|
26
|
-
def to_dict(self) ->
|
27
|
-
return
|
26
|
+
def to_dict(self) -> Dict[str, str]:
|
27
|
+
return {}
|
28
28
|
|
29
29
|
|
30
30
|
@dataclass
|
@@ -33,7 +33,7 @@ class InputOption(BaseOption):
|
|
33
33
|
url: str = ""
|
34
34
|
dir: Path = Path()
|
35
35
|
|
36
|
-
def to_dict(self) ->
|
36
|
+
def to_dict(self) -> Dict[Any, Any]:
|
37
37
|
return {"option": self.option, "url": self.url, "dir": self.dir.as_posix()}
|
38
38
|
|
39
39
|
|
@@ -43,8 +43,8 @@ class CompOption(BaseOption):
|
|
43
43
|
size_max_img: Optional[int] = None
|
44
44
|
size_max_vid: Optional[int] = None
|
45
45
|
|
46
|
-
format_img:
|
47
|
-
format_vid:
|
46
|
+
format_img: Tuple[str, ...] = tuple()
|
47
|
+
format_vid: Tuple[str, ...] = tuple()
|
48
48
|
|
49
49
|
fps_min: Optional[int] = None
|
50
50
|
fps_max: Optional[int] = None
|
@@ -78,7 +78,7 @@ class CompOption(BaseOption):
|
|
78
78
|
animated: Optional[bool] = None
|
79
79
|
square: Optional[bool] = None
|
80
80
|
|
81
|
-
def to_dict(self) ->
|
81
|
+
def to_dict(self) -> Dict[Any, Any]:
|
82
82
|
return {
|
83
83
|
"preset": self.preset,
|
84
84
|
"size_max": {"img": self.size_max_img, "vid": self.size_max_vid},
|
@@ -111,75 +111,79 @@ class CompOption(BaseOption):
|
|
111
111
|
"square": self.square,
|
112
112
|
}
|
113
113
|
|
114
|
-
def get_size_max(self) ->
|
114
|
+
def get_size_max(self) -> Tuple[Optional[int], Optional[int]]:
|
115
115
|
return (self.size_max_img, self.size_max_vid)
|
116
116
|
|
117
|
-
def set_size_max(self, value: Optional[int]):
|
117
|
+
def set_size_max(self, value: Optional[int]) -> None:
|
118
118
|
self.size_max_img, self.size_max_vid = to_int(value), to_int(value)
|
119
119
|
|
120
|
-
def get_format(self) ->
|
120
|
+
def get_format(self) -> Tuple[Tuple[str, ...], Tuple[str, ...]]:
|
121
121
|
return (self.format_img, self.format_vid)
|
122
122
|
|
123
|
-
def set_format(self, value:
|
123
|
+
def set_format(self, value: Tuple[str, ...]) -> None:
|
124
124
|
self.format_img, self.format_vid = value, value
|
125
125
|
|
126
|
-
def get_fps(self) ->
|
126
|
+
def get_fps(self) -> Tuple[Optional[int], Optional[int]]:
|
127
127
|
return (self.fps_min, self.fps_max)
|
128
128
|
|
129
|
-
def set_fps(self, value: Optional[int]):
|
129
|
+
def set_fps(self, value: Optional[int]) -> None:
|
130
130
|
self.fps_min, self.fps_max = to_int(value), to_int(value)
|
131
131
|
|
132
|
-
def get_res(
|
132
|
+
def get_res(
|
133
|
+
self,
|
134
|
+
) -> Tuple[
|
135
|
+
Tuple[Optional[int], Optional[int]], Tuple[Optional[int], Optional[int]]
|
136
|
+
]:
|
133
137
|
return (self.get_res_w(), self.get_res_h())
|
134
138
|
|
135
|
-
def set_res(self, value: Optional[int]):
|
139
|
+
def set_res(self, value: Optional[int]) -> None:
|
136
140
|
self.res_w_min = to_int(value)
|
137
141
|
self.res_w_max = to_int(value)
|
138
142
|
self.res_h_min = to_int(value)
|
139
143
|
self.res_h_max = to_int(value)
|
140
144
|
|
141
|
-
def get_res_max(self) ->
|
145
|
+
def get_res_max(self) -> Tuple[Optional[int], Optional[int]]:
|
142
146
|
return (self.res_w_max, self.res_h_max)
|
143
147
|
|
144
|
-
def set_res_max(self, value: Optional[int]):
|
148
|
+
def set_res_max(self, value: Optional[int]) -> None:
|
145
149
|
self.res_w_max = to_int(value)
|
146
150
|
self.res_h_max = to_int(value)
|
147
151
|
|
148
|
-
def get_res_min(self) ->
|
152
|
+
def get_res_min(self) -> Tuple[Optional[int], Optional[int]]:
|
149
153
|
return (self.res_w_min, self.res_h_min)
|
150
154
|
|
151
|
-
def set_res_min(self, value: Optional[int]):
|
155
|
+
def set_res_min(self, value: Optional[int]) -> None:
|
152
156
|
self.res_w_min = to_int(value)
|
153
157
|
self.res_h_min = to_int(value)
|
154
158
|
|
155
|
-
def get_res_w(self) ->
|
159
|
+
def get_res_w(self) -> Tuple[Optional[int], Optional[int]]:
|
156
160
|
return (self.res_w_min, self.res_w_max)
|
157
161
|
|
158
|
-
def set_res_w(self, value: Optional[int]):
|
162
|
+
def set_res_w(self, value: Optional[int]) -> None:
|
159
163
|
self.res_w_min, self.res_w_max = to_int(value), to_int(value)
|
160
164
|
|
161
|
-
def get_res_h(self) ->
|
165
|
+
def get_res_h(self) -> Tuple[Optional[int], Optional[int]]:
|
162
166
|
return (self.res_h_min, self.res_h_max)
|
163
167
|
|
164
|
-
def set_res_h(self, value: Optional[int]):
|
168
|
+
def set_res_h(self, value: Optional[int]) -> None:
|
165
169
|
self.res_h_min, self.res_h_max = to_int(value), to_int(value)
|
166
170
|
|
167
|
-
def get_quality(self) ->
|
171
|
+
def get_quality(self) -> Tuple[Optional[int], Optional[int]]:
|
168
172
|
return (self.quality_min, self.quality_max)
|
169
173
|
|
170
|
-
def set_quality(self, value: Optional[int]):
|
174
|
+
def set_quality(self, value: Optional[int]) -> None:
|
171
175
|
self.quality_min, self.quality_max = to_int(value), to_int(value)
|
172
176
|
|
173
|
-
def get_color(self) ->
|
177
|
+
def get_color(self) -> Tuple[Optional[int], Optional[int]]:
|
174
178
|
return (self.color_min, self.color_max)
|
175
179
|
|
176
|
-
def set_color(self, value: Optional[int]):
|
180
|
+
def set_color(self, value: Optional[int]) -> None:
|
177
181
|
self.color_min, self.color_max = to_int(value), to_int(value)
|
178
182
|
|
179
|
-
def get_duration(self) ->
|
183
|
+
def get_duration(self) -> Tuple[Optional[int], Optional[int]]:
|
180
184
|
return (self.duration_min, self.duration_max)
|
181
185
|
|
182
|
-
def set_duration(self, value: Optional[int]):
|
186
|
+
def set_duration(self, value: Optional[int]) -> None:
|
183
187
|
self.duration_min, self.duration_max = to_int(value), to_int(value)
|
184
188
|
|
185
189
|
|
@@ -190,7 +194,7 @@ class OutputOption(BaseOption):
|
|
190
194
|
title: str = ""
|
191
195
|
author: str = ""
|
192
196
|
|
193
|
-
def to_dict(self) ->
|
197
|
+
def to_dict(self) -> Dict[Any, Any]:
|
194
198
|
return {
|
195
199
|
"option": self.option,
|
196
200
|
"dir": self.dir.as_posix(),
|
@@ -212,7 +216,7 @@ class CredOption(BaseOption):
|
|
212
216
|
kakao_phone_number: str = ""
|
213
217
|
line_cookies: str = ""
|
214
218
|
|
215
|
-
def to_dict(self) ->
|
219
|
+
def to_dict(self) -> Dict[Any, Any]:
|
216
220
|
return {
|
217
221
|
"signal": {"uuid": self.signal_uuid, "password": self.signal_password},
|
218
222
|
"telegram": {"token": self.telegram_token, "userid": self.telegram_userid},
|