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.
Files changed (56) hide show
  1. sticker_convert/__init__.py +1 -0
  2. sticker_convert/__main__.py +3 -1
  3. sticker_convert/cli.py +20 -24
  4. sticker_convert/converter.py +108 -119
  5. sticker_convert/definitions.py +8 -12
  6. sticker_convert/downloaders/download_base.py +14 -31
  7. sticker_convert/downloaders/download_kakao.py +25 -39
  8. sticker_convert/downloaders/download_line.py +24 -33
  9. sticker_convert/downloaders/download_signal.py +7 -16
  10. sticker_convert/downloaders/download_telegram.py +6 -15
  11. sticker_convert/gui.py +53 -61
  12. sticker_convert/gui_components/frames/comp_frame.py +11 -20
  13. sticker_convert/gui_components/frames/config_frame.py +9 -9
  14. sticker_convert/gui_components/frames/control_frame.py +3 -3
  15. sticker_convert/gui_components/frames/cred_frame.py +12 -18
  16. sticker_convert/gui_components/frames/input_frame.py +9 -15
  17. sticker_convert/gui_components/frames/output_frame.py +9 -15
  18. sticker_convert/gui_components/frames/progress_frame.py +8 -8
  19. sticker_convert/gui_components/frames/right_clicker.py +2 -2
  20. sticker_convert/gui_components/gui_utils.py +6 -8
  21. sticker_convert/gui_components/windows/advanced_compression_window.py +23 -32
  22. sticker_convert/gui_components/windows/base_window.py +6 -6
  23. sticker_convert/gui_components/windows/kakao_get_auth_window.py +5 -11
  24. sticker_convert/gui_components/windows/line_get_auth_window.py +5 -5
  25. sticker_convert/gui_components/windows/signal_get_auth_window.py +6 -6
  26. sticker_convert/job.py +84 -90
  27. sticker_convert/job_option.py +36 -32
  28. sticker_convert/resources/emoji.json +334 -70
  29. sticker_convert/resources/help.json +1 -1
  30. sticker_convert/uploaders/compress_wastickers.py +19 -30
  31. sticker_convert/uploaders/upload_base.py +19 -13
  32. sticker_convert/uploaders/upload_signal.py +20 -33
  33. sticker_convert/uploaders/upload_telegram.py +21 -28
  34. sticker_convert/uploaders/xcode_imessage.py +30 -95
  35. sticker_convert/utils/auth/get_kakao_auth.py +7 -8
  36. sticker_convert/utils/auth/get_line_auth.py +5 -6
  37. sticker_convert/utils/auth/get_signal_auth.py +7 -7
  38. sticker_convert/utils/callback.py +31 -23
  39. sticker_convert/utils/files/cache_store.py +6 -8
  40. sticker_convert/utils/files/json_manager.py +6 -7
  41. sticker_convert/utils/files/json_resources_loader.py +12 -0
  42. sticker_convert/utils/files/metadata_handler.py +93 -84
  43. sticker_convert/utils/files/run_bin.py +11 -10
  44. sticker_convert/utils/files/sanitize_filename.py +30 -28
  45. sticker_convert/utils/media/apple_png_normalize.py +3 -2
  46. sticker_convert/utils/media/codec_info.py +41 -44
  47. sticker_convert/utils/media/decrypt_kakao.py +7 -7
  48. sticker_convert/utils/media/format_verify.py +14 -14
  49. sticker_convert/utils/url_detect.py +4 -5
  50. sticker_convert/version.py +2 -1
  51. {sticker_convert-2.7.2.dist-info → sticker_convert-2.7.4.dist-info}/METADATA +19 -17
  52. {sticker_convert-2.7.2.dist-info → sticker_convert-2.7.4.dist-info}/RECORD +56 -55
  53. {sticker_convert-2.7.2.dist-info → sticker_convert-2.7.4.dist-info}/WHEEL +1 -1
  54. {sticker_convert-2.7.2.dist-info → sticker_convert-2.7.4.dist-info}/LICENSE +0 -0
  55. {sticker_convert-2.7.2.dist-info → sticker_convert-2.7.4.dist-info}/entry_points.txt +0 -0
  56. {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, Union
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.json_manager import JsonManager
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
- self.work_queue: Queue[
52
- Optional[tuple[Callable[..., Any], tuple[Any, ...]]]
53
- ] = self.manager.Queue()
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: list[Process] = []
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: 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: tuple[str, ...] = i[1] if i[1] else tuple()
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: dict[str, str] = i[2] if i[2] else dict()
90
+ kwargs: Dict[str, str] = i[2] if i[2] else {}
99
91
  else:
100
- kwargs = dict()
92
+ kwargs = {}
101
93
  else:
102
94
  action = i
103
95
  args = tuple()
104
- kwargs = dict()
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
- work_queue: Queue[Optional[tuple[Callable[..., Any], tuple[Any, ...]]]],
114
+ work_list: WorkListType,
123
115
  results_queue: Queue[Any],
124
- cb_queue: 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
- for work_func, work_args in iter(work_queue.get, None):
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: list[Any] = []
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
- work_queue.put(None)
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.work_queue,
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(self, work_func: Callable[..., Any], work_args: tuple[Any, ...]):
174
- self.work_queue.put((work_func, work_args))
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.work_queue.put(None)
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
- self.process = []
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
- while not self.work_queue.empty():
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
- for result in iter(self.results_queue.get, None):
208
- yield result
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[tuple[str, ...]] = None,
214
- kwargs: Optional[dict[str, Any]] = None,
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: list[str] = []
243
- self.out_urls: list[str] = []
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
- elif not success:
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, *args: Any, **kwargs: Any):
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 = JsonManager.load_json(ROOT_DIR / "resources/output.json")
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 += " Valid options: nearest, bilinear, bicubic, lanczos"
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
- elif path_type == "Output" and (
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([i for i in path.iterdir() if i not in related_files]):
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: list[Callable[..., bool]] = []
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: list[Path] = []
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
- elif (
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: list[Callable[..., list[str]]] = []
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(Path(self.opt_output.dir, "export-result.txt"), "w+") as f:
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:
@@ -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) -> dict[str, str]:
27
- return dict()
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) -> dict[Any, Any]:
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: tuple[str, ...] = tuple()
47
- format_vid: tuple[str, ...] = tuple()
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) -> dict[Any, Any]:
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) -> tuple[Optional[int], Optional[int]]:
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) -> tuple[tuple[str, ...], tuple[str, ...]]:
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: tuple[str, ...]):
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) -> tuple[Optional[int], Optional[int]]:
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(self) -> tuple[tuple[Optional[int], Optional[int]], tuple[Optional[int], Optional[int]]]:
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) -> tuple[Optional[int], Optional[int]]:
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) -> tuple[Optional[int], Optional[int]]:
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) -> tuple[Optional[int], Optional[int]]:
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) -> tuple[Optional[int], Optional[int]]:
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) -> tuple[Optional[int], Optional[int]]:
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) -> tuple[Optional[int], Optional[int]]:
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) -> tuple[Optional[int], Optional[int]]:
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) -> dict[Any, Any]:
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) -> dict[Any, Any]:
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},