sticker-convert 2.8.0__py3-none-any.whl → 2.8.2__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 (27) hide show
  1. sticker_convert/cli.py +4 -0
  2. sticker_convert/converter.py +8 -8
  3. sticker_convert/downloaders/download_base.py +3 -7
  4. sticker_convert/downloaders/download_kakao.py +3 -4
  5. sticker_convert/downloaders/download_line.py +3 -4
  6. sticker_convert/downloaders/download_signal.py +3 -4
  7. sticker_convert/downloaders/download_telegram.py +3 -4
  8. sticker_convert/gui.py +3 -1
  9. sticker_convert/gui_components/windows/advanced_compression_window.py +117 -101
  10. sticker_convert/job.py +81 -89
  11. sticker_convert/job_option.py +3 -0
  12. sticker_convert/resources/compression.json +11 -0
  13. sticker_convert/resources/help.json +1 -0
  14. sticker_convert/uploaders/compress_wastickers.py +3 -4
  15. sticker_convert/uploaders/upload_base.py +2 -8
  16. sticker_convert/uploaders/upload_signal.py +3 -4
  17. sticker_convert/uploaders/upload_telegram.py +3 -4
  18. sticker_convert/uploaders/xcode_imessage.py +3 -4
  19. sticker_convert/utils/callback.py +33 -21
  20. sticker_convert/utils/media/codec_info.py +3 -7
  21. sticker_convert/version.py +1 -1
  22. {sticker_convert-2.8.0.dist-info → sticker_convert-2.8.2.dist-info}/METADATA +18 -15
  23. {sticker_convert-2.8.0.dist-info → sticker_convert-2.8.2.dist-info}/RECORD +27 -27
  24. {sticker_convert-2.8.0.dist-info → sticker_convert-2.8.2.dist-info}/LICENSE +0 -0
  25. {sticker_convert-2.8.0.dist-info → sticker_convert-2.8.2.dist-info}/WHEEL +0 -0
  26. {sticker_convert-2.8.0.dist-info → sticker_convert-2.8.2.dist-info}/entry_points.txt +0 -0
  27. {sticker_convert-2.8.0.dist-info → sticker_convert-2.8.2.dist-info}/top_level.txt +0 -0
sticker_convert/job.py CHANGED
@@ -5,12 +5,10 @@ import os
5
5
  import shutil
6
6
  import traceback
7
7
  from datetime import datetime
8
- from multiprocessing import Process, Value
9
- from multiprocessing.managers import ListProxy, SyncManager
8
+ from multiprocessing import Manager, Process, Value
10
9
  from pathlib import Path
11
- from queue import Queue
12
10
  from threading import Thread
13
- from typing import TYPE_CHECKING, Any, Callable, Dict, Generator, List, Optional, Tuple
11
+ from typing import Any, Callable, Dict, List, Optional, Tuple
14
12
  from urllib.parse import urlparse
15
13
 
16
14
  from sticker_convert.converter import StickerConvert
@@ -23,18 +21,11 @@ from sticker_convert.uploaders.compress_wastickers import CompressWastickers
23
21
  from sticker_convert.uploaders.upload_signal import UploadSignal
24
22
  from sticker_convert.uploaders.upload_telegram import UploadTelegram
25
23
  from sticker_convert.uploaders.xcode_imessage import XcodeImessage
26
- from sticker_convert.utils.callback import CallbackReturn, CbQueueItemType
24
+ from sticker_convert.utils.callback import CallbackReturn, CbQueueType, ResultsListType, WorkQueueType
27
25
  from sticker_convert.utils.files.json_resources_loader import OUTPUT_JSON
28
26
  from sticker_convert.utils.files.metadata_handler import MetadataHandler
29
27
  from sticker_convert.utils.media.codec_info import CodecInfo
30
28
 
31
- WorkListItemType = Optional[Tuple[Callable[..., Any], Tuple[Any, ...]]]
32
- if TYPE_CHECKING:
33
- # mypy complains about this
34
- WorkListType = ListProxy[WorkListItemType] # type: ignore
35
- else:
36
- WorkListType = List[WorkListItemType]
37
-
38
29
 
39
30
  class Executor:
40
31
  def __init__(
@@ -51,33 +42,29 @@ class Executor:
51
42
  self.cb_ask_bool = cb_ask_bool
52
43
  self.cb_ask_str = cb_ask_str
53
44
 
54
- self.manager = SyncManager()
55
- self.manager.start()
56
- # Using list instead of queue for work_list as it can cause random deadlocks
57
- # Especially when using scale_filter=nearest
58
- self.work_list: WorkListType = self.manager.list()
59
- self.results_queue: Queue[Any] = self.manager.Queue()
60
- self.cb_queue: Queue[CbQueueItemType] = self.manager.Queue()
61
- self.cb_return = CallbackReturn()
45
+ self.manager = Manager()
46
+ self.work_queue: WorkQueueType = self.manager.Queue()
47
+ self.cb_queue: CbQueueType = self.manager.Queue()
48
+ self.results_list: ResultsListType = self.manager.list()
49
+ self.cb_return = CallbackReturn(self.manager)
62
50
  self.processes: List[Process] = []
63
51
 
64
52
  self.is_cancel_job = Value("i", 0)
65
53
 
66
- self.cb_thread_instance = Thread(
67
- target=self.cb_thread,
68
- args=(
69
- self.cb_queue,
70
- self.cb_return,
71
- ),
72
- )
73
- self.cb_thread_instance.start()
54
+ self.cb_thread_instance: Optional[Thread] = None
74
55
 
75
56
  def cb_thread(
76
57
  self,
77
- cb_queue: Queue[CbQueueItemType],
78
- cb_return: CallbackReturn,
58
+ cb_queue: CbQueueType,
59
+ processes: int,
79
60
  ) -> None:
61
+ processes_done = 0
80
62
  for i in iter(cb_queue.get, None):
63
+ if i == "__PROCESS_DONE__":
64
+ processes_done += 1
65
+ if processes_done == processes:
66
+ cb_queue.put(None)
67
+ continue
81
68
  if isinstance(i, tuple):
82
69
  action = i[0]
83
70
  if len(i) >= 2:
@@ -92,42 +79,44 @@ class Executor:
92
79
  action = i
93
80
  args = tuple()
94
81
  kwargs = {}
95
- if action == "msg":
96
- self.cb_msg(*args, **kwargs)
97
- elif action == "bar":
98
- self.cb_bar(*args, **kwargs)
99
- elif action == "update_bar":
100
- self.cb_bar(update_bar=1)
101
- elif action == "msg_block":
102
- cb_return.set_response(self.cb_msg_block(*args, **kwargs))
103
- elif action == "ask_bool":
104
- cb_return.set_response(self.cb_ask_bool(*args, **kwargs))
105
- elif action == "ask_str":
106
- cb_return.set_response(self.cb_ask_str(*args, **kwargs))
107
- else:
108
- self.cb_msg(action)
82
+ self.cb(action, args, kwargs)
83
+
84
+ def cb(
85
+ self,
86
+ action: Optional[str],
87
+ args: Optional[Tuple[str, ...]] = None,
88
+ kwargs: Optional[Dict[str, Any]] = None,
89
+ ) -> None:
90
+ if args is None:
91
+ args = tuple()
92
+ if kwargs is None:
93
+ kwargs = {}
94
+ if action == "msg":
95
+ self.cb_msg(*args, **kwargs)
96
+ elif action == "bar":
97
+ self.cb_bar(*args, **kwargs)
98
+ elif action == "update_bar":
99
+ self.cb_bar(update_bar=1)
100
+ elif action == "msg_block":
101
+ self.cb_return.set_response(self.cb_msg_block(*args, **kwargs))
102
+ elif action == "ask_bool":
103
+ self.cb_return.set_response(self.cb_ask_bool(*args, **kwargs))
104
+ elif action == "ask_str":
105
+ self.cb_return.set_response(self.cb_ask_str(*args, **kwargs))
106
+ else:
107
+ self.cb_msg(action)
109
108
 
110
109
  @staticmethod
111
110
  def worker(
112
- work_list: WorkListType,
113
- results_queue: Queue[Any],
114
- cb_queue: Queue[CbQueueItemType],
111
+ work_queue: WorkQueueType,
112
+ results_list: ResultsListType,
113
+ cb_queue: CbQueueType,
115
114
  cb_return: CallbackReturn,
116
115
  ) -> None:
117
- while True:
118
- try:
119
- work = work_list.pop(0)
120
- except IndexError:
121
- break
122
-
123
- if work is None:
124
- break
125
- else:
126
- work_func, work_args = work
127
-
116
+ for work_func, work_args in iter(work_queue.get, None):
128
117
  try:
129
118
  results = work_func(*work_args, cb_queue, cb_return)
130
- results_queue.put(results)
119
+ results_list.append(results)
131
120
  except Exception:
132
121
  arg_dump: List[Any] = []
133
122
  for i in work_args:
@@ -142,15 +131,25 @@ class Executor:
142
131
  e += "#####################"
143
132
  cb_queue.put(e)
144
133
 
145
- work_list.append(None)
134
+ work_queue.put(None)
135
+ cb_queue.put("__PROCESS_DONE__")
146
136
 
147
137
  def start_workers(self, processes: int = 1) -> None:
138
+ self.cb_thread_instance = Thread(
139
+ target=self.cb_thread,
140
+ args=(self.cb_queue, processes),
141
+ )
142
+ self.cb_thread_instance.start()
143
+
144
+ self.results_list[:] = []
145
+ while not self.work_queue.empty():
146
+ self.work_queue.get()
148
147
  for _ in range(processes):
149
148
  process = Process(
150
149
  target=Executor.worker,
151
150
  args=(
152
- self.work_list,
153
- self.results_queue,
151
+ self.work_queue,
152
+ self.results_list,
154
153
  self.cb_queue,
155
154
  self.cb_return,
156
155
  ),
@@ -163,18 +162,16 @@ class Executor:
163
162
  def add_work(
164
163
  self, work_func: Callable[..., Any], work_args: Tuple[Any, ...]
165
164
  ) -> None:
166
- self.work_list.append((work_func, work_args))
165
+ self.work_queue.put((work_func, work_args))
167
166
 
168
167
  def join_workers(self) -> None:
169
- self.work_list.append(None)
168
+ self.work_queue.put(None)
170
169
  try:
171
170
  for process in self.processes:
172
171
  process.join()
173
172
  except KeyboardInterrupt:
174
173
  pass
175
174
 
176
- self.results_queue.put(None)
177
- self.work_list[:] = []
178
175
  self.processes.clear()
179
176
 
180
177
  def kill_workers(self, *_: Any, **__: Any) -> None:
@@ -183,25 +180,14 @@ class Executor:
183
180
  for process in self.processes:
184
181
  process.terminate()
185
182
 
186
- self.cleanup(killed=True)
183
+ self.cb_msg("Job cancelled.")
184
+ self.cleanup()
187
185
 
188
- def cleanup(self, killed: bool = False) -> None:
189
- if killed:
190
- self.cb_queue.put("Job cancelled.")
191
- self.cb_queue.put(("bar", None, {"set_progress_mode": "clear"}))
186
+ def cleanup(self) -> None:
187
+ self.cb_bar("clear")
192
188
  self.cb_queue.put(None)
193
- self.cb_thread_instance.join()
194
-
195
- def get_result(self) -> Generator[Any, None, None]:
196
- yield from iter(self.results_queue.get, None)
197
-
198
- def cb(
199
- self,
200
- action: Optional[str],
201
- args: Optional[Tuple[str, ...]] = None,
202
- kwargs: Optional[Dict[str, Any]] = None,
203
- ) -> None:
204
- self.cb_queue.put((action, args, kwargs))
189
+ if self.cb_thread_instance:
190
+ self.cb_thread_instance.join()
205
191
 
206
192
 
207
193
  class Job:
@@ -547,17 +533,18 @@ class Job:
547
533
  self.executor.cb("Nothing to download")
548
534
  return True
549
535
 
536
+ self.executor.start_workers(processes=1)
537
+
550
538
  for downloader in downloaders:
551
539
  self.executor.add_work(
552
540
  work_func=downloader,
553
541
  work_args=(self.opt_input.url, self.opt_input.dir, self.opt_cred),
554
542
  )
555
543
 
556
- self.executor.start_workers(processes=1)
557
544
  self.executor.join_workers()
558
545
 
559
546
  # Return False if any of the job returns failure
560
- for result in self.executor.get_result():
547
+ for result in self.executor.results_list:
561
548
  if result is False:
562
549
  return False
563
550
 
@@ -613,12 +600,17 @@ class Job:
613
600
  in_fs.append(i)
614
601
 
615
602
  in_fs_count = len(in_fs)
603
+ if in_fs_count == 0:
604
+ self.executor.cb("No files to compress")
605
+ return True
616
606
 
617
607
  self.executor.cb(msg)
618
608
  self.executor.cb(
619
609
  "bar", kwargs={"set_progress_mode": "determinate", "steps": in_fs_count}
620
610
  )
621
611
 
612
+ self.executor.start_workers(processes=min(self.opt_comp.processes, in_fs_count))
613
+
622
614
  for i in in_fs:
623
615
  in_f = input_dir / i.name
624
616
  out_f = output_dir / Path(i).stem
@@ -627,11 +619,10 @@ class Job:
627
619
  work_func=StickerConvert.convert, work_args=(in_f, out_f, self.opt_comp)
628
620
  )
629
621
 
630
- self.executor.start_workers(processes=min(self.opt_comp.processes, in_fs_count))
631
622
  self.executor.join_workers()
632
623
 
633
624
  # Return False if any of the job returns failure
634
- for result in self.executor.get_result():
625
+ for result in self.executor.results_list:
635
626
  if result[0] is False:
636
627
  return False
637
628
 
@@ -661,16 +652,17 @@ class Job:
661
652
  if self.opt_output.option == "imessage":
662
653
  exporters.append(XcodeImessage.start)
663
654
 
655
+ self.executor.start_workers(processes=1)
656
+
664
657
  for exporter in exporters:
665
658
  self.executor.add_work(
666
659
  work_func=exporter,
667
660
  work_args=(self.opt_output, self.opt_comp, self.opt_cred),
668
661
  )
669
662
 
670
- self.executor.start_workers(processes=1)
671
663
  self.executor.join_workers()
672
664
 
673
- for result in self.executor.get_result():
665
+ for result in self.executor.results_list:
674
666
  self.out_urls.extend(result)
675
667
 
676
668
  if self.out_urls:
@@ -69,6 +69,8 @@ class CompOption(BaseOption):
69
69
 
70
70
  bg_color: Optional[str] = None
71
71
 
72
+ padding_percent: int = 0
73
+
72
74
  steps: int = 1
73
75
  fake_vid: Optional[bool] = None
74
76
  quantize_method: Optional[str] = None
@@ -104,6 +106,7 @@ class CompOption(BaseOption):
104
106
  "duration": {"min": self.duration_min, "max": self.duration_max},
105
107
  "steps": self.steps,
106
108
  "bg_color": self.bg_color,
109
+ "padding_percent": self.padding_percent,
107
110
  "fake_vid": self.fake_vid,
108
111
  "quantize_method": self.quantize_method,
109
112
  "scale_filter": self.scale_filter,
@@ -38,6 +38,7 @@
38
38
  "min": 0,
39
39
  "max": 10000
40
40
  },
41
+ "padding_percent": 0,
41
42
  "bg_color": "",
42
43
  "steps": 16,
43
44
  "fake_vid": false,
@@ -84,6 +85,7 @@
84
85
  "min": 0,
85
86
  "max": 3000
86
87
  },
88
+ "padding_percent": 0,
87
89
  "bg_color": "",
88
90
  "steps": 16,
89
91
  "fake_vid": false,
@@ -130,6 +132,7 @@
130
132
  "min": 0,
131
133
  "max": 3000
132
134
  },
135
+ "padding_percent": 0,
133
136
  "bg_color": "",
134
137
  "steps": 16,
135
138
  "fake_vid": false,
@@ -176,6 +179,7 @@
176
179
  "min": 0,
177
180
  "max": 3000
178
181
  },
182
+ "padding_percent": 0,
179
183
  "bg_color": "",
180
184
  "steps": 16,
181
185
  "fake_vid": false,
@@ -222,6 +226,7 @@
222
226
  "min": 8,
223
227
  "max": 10000
224
228
  },
229
+ "padding_percent": 0,
225
230
  "bg_color": "",
226
231
  "steps": 16,
227
232
  "fake_vid": true,
@@ -268,6 +273,7 @@
268
273
  "min": 83,
269
274
  "max": 4000
270
275
  },
276
+ "padding_percent": 0,
271
277
  "bg_color": "",
272
278
  "steps": 16,
273
279
  "fake_vid": false,
@@ -314,6 +320,7 @@
314
320
  "min": 0,
315
321
  "max": 5000
316
322
  },
323
+ "padding_percent": 0,
317
324
  "bg_color": "",
318
325
  "steps": 16,
319
326
  "fake_vid": false,
@@ -360,6 +367,7 @@
360
367
  "min": 0,
361
368
  "max": 2000
362
369
  },
370
+ "padding_percent": 0,
363
371
  "bg_color": "",
364
372
  "steps": 16,
365
373
  "fake_vid": false,
@@ -406,6 +414,7 @@
406
414
  "min": 0,
407
415
  "max": 2000
408
416
  },
417
+ "padding_percent": 0,
409
418
  "bg_color": "",
410
419
  "steps": 16,
411
420
  "fake_vid": false,
@@ -452,6 +461,7 @@
452
461
  "min": 0,
453
462
  "max": 2000
454
463
  },
464
+ "padding_percent": 0,
455
465
  "bg_color": "",
456
466
  "steps": 16,
457
467
  "fake_vid": false,
@@ -498,6 +508,7 @@
498
508
  "min": 0,
499
509
  "max": 10000
500
510
  },
511
+ "padding_percent": 0,
501
512
  "bg_color": "",
502
513
  "steps": 16,
503
514
  "fake_vid": false,
@@ -39,6 +39,7 @@
39
39
  "duration": "Change playback speed if outside of duration limit.\nDuration set in miliseconds.\n0 will disable limit.",
40
40
  "duration_min": "Set minimum output duration in miliseconds.",
41
41
  "duration_max": "Set maximum output duration in miliseconds.",
42
+ "padding_percent": "Set percentage of space used as padding.",
42
43
  "bg_color": "Set custom background color.\nExample: 00ff00 for green.\nIf this is not set, background color would be auto set to black if image is bright, or white if image is dark.\nNote: The color should not be visible if output format supports transparency.",
43
44
  "size": "Set maximum file size in bytes for video and image.",
44
45
  "vid_size_max": "Set maximum file size limit for animated stickers.",
@@ -3,13 +3,12 @@ import copy
3
3
  import shutil
4
4
  import zipfile
5
5
  from pathlib import Path
6
- from queue import Queue
7
- from typing import Any, List, Union
6
+ from typing import Any, List
8
7
 
9
8
  from sticker_convert.converter import StickerConvert
10
9
  from sticker_convert.job_option import CompOption, CredOption, OutputOption
11
10
  from sticker_convert.uploaders.upload_base import UploadBase
12
- from sticker_convert.utils.callback import Callback, CallbackReturn, CbQueueItemType
11
+ from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
13
12
  from sticker_convert.utils.files.cache_store import CacheStore
14
13
  from sticker_convert.utils.files.metadata_handler import MetadataHandler
15
14
  from sticker_convert.utils.files.sanitize_filename import sanitize_filename
@@ -150,7 +149,7 @@ class CompressWastickers(UploadBase):
150
149
  opt_output: OutputOption,
151
150
  opt_comp: CompOption,
152
151
  opt_cred: CredOption,
153
- cb: "Union[Queue[CbQueueItemType], Callback]",
152
+ cb: CallbackProtocol,
154
153
  cb_return: CallbackReturn,
155
154
  ) -> List[str]:
156
155
  exporter = CompressWastickers(opt_output, opt_comp, opt_cred, cb, cb_return)
@@ -1,9 +1,7 @@
1
1
  #!/usr/bin/env python3
2
- from queue import Queue
3
- from typing import Union
4
2
 
5
3
  from sticker_convert.job_option import CompOption, CredOption, OutputOption
6
- from sticker_convert.utils.callback import Callback, CallbackReturn, CbQueueItemType
4
+ from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
7
5
 
8
6
 
9
7
  class UploadBase:
@@ -12,13 +10,9 @@ class UploadBase:
12
10
  opt_output: OutputOption,
13
11
  opt_comp: CompOption,
14
12
  opt_cred: CredOption,
15
- cb: "Union[Queue[CbQueueItemType], Callback]",
13
+ cb: CallbackProtocol,
16
14
  cb_return: CallbackReturn,
17
15
  ) -> None:
18
- if not cb:
19
- cb = Callback(silent=True)
20
- cb_return = CallbackReturn()
21
-
22
16
  self.opt_output = opt_output
23
17
  self.opt_comp = opt_comp
24
18
  self.opt_cred = opt_cred
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  import copy
3
3
  from pathlib import Path
4
- from queue import Queue
5
- from typing import Any, Dict, List, Union
4
+ from typing import Any, Dict, List
6
5
 
7
6
  import anyio
8
7
  from signalstickers_client import StickersClient # type: ignore
@@ -12,7 +11,7 @@ from signalstickers_client.models import LocalStickerPack, Sticker # type: igno
12
11
  from sticker_convert.converter import StickerConvert
13
12
  from sticker_convert.job_option import CompOption, CredOption, OutputOption
14
13
  from sticker_convert.uploaders.upload_base import UploadBase
15
- from sticker_convert.utils.callback import Callback, CallbackReturn, CbQueueItemType
14
+ from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
16
15
  from sticker_convert.utils.files.metadata_handler import MetadataHandler
17
16
  from sticker_convert.utils.media.codec_info import CodecInfo
18
17
  from sticker_convert.utils.media.format_verify import FormatVerify
@@ -167,7 +166,7 @@ class UploadSignal(UploadBase):
167
166
  opt_output: OutputOption,
168
167
  opt_comp: CompOption,
169
168
  opt_cred: CredOption,
170
- cb: "Union[Queue[CbQueueItemType], Callback]",
169
+ cb: CallbackProtocol,
171
170
  cb_return: CallbackReturn,
172
171
  ) -> List[str]:
173
172
  exporter = UploadSignal(opt_output, opt_comp, opt_cred, cb, cb_return)
@@ -2,7 +2,6 @@
2
2
  import copy
3
3
  import re
4
4
  from pathlib import Path
5
- from queue import Queue
6
5
  from typing import Any, Dict, List, Optional, Tuple, Union, cast
7
6
 
8
7
  import anyio
@@ -13,7 +12,7 @@ from telegram.ext import AIORateLimiter, ApplicationBuilder
13
12
  from sticker_convert.converter import StickerConvert
14
13
  from sticker_convert.job_option import CompOption, CredOption, OutputOption
15
14
  from sticker_convert.uploaders.upload_base import UploadBase
16
- from sticker_convert.utils.callback import Callback, CallbackReturn, CbQueueItemType
15
+ from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
17
16
  from sticker_convert.utils.files.metadata_handler import MetadataHandler
18
17
  from sticker_convert.utils.media.format_verify import FormatVerify
19
18
 
@@ -79,7 +78,7 @@ class UploadTelegram(UploadBase):
79
78
  application = ( # type: ignore
80
79
  ApplicationBuilder()
81
80
  .token(self.opt_cred.telegram_token.strip())
82
- .rate_limiter(AIORateLimiter())
81
+ .rate_limiter(AIORateLimiter(max_retries=3))
83
82
  .connect_timeout(timeout)
84
83
  .pool_timeout(timeout)
85
84
  .read_timeout(timeout)
@@ -350,7 +349,7 @@ class UploadTelegram(UploadBase):
350
349
  opt_output: OutputOption,
351
350
  opt_comp: CompOption,
352
351
  opt_cred: CredOption,
353
- cb: "Union[Queue[CbQueueItemType], Callback]",
352
+ cb: CallbackProtocol,
354
353
  cb_return: CallbackReturn,
355
354
  ) -> List[str]:
356
355
  exporter = UploadTelegram(
@@ -6,14 +6,13 @@ import plistlib
6
6
  import shutil
7
7
  import zipfile
8
8
  from pathlib import Path
9
- from queue import Queue
10
- from typing import Any, Dict, List, Union
9
+ from typing import Any, Dict, List
11
10
 
12
11
  from sticker_convert.converter import StickerConvert
13
12
  from sticker_convert.definitions import ROOT_DIR
14
13
  from sticker_convert.job_option import CompOption, CredOption, OutputOption
15
14
  from sticker_convert.uploaders.upload_base import UploadBase
16
- from sticker_convert.utils.callback import Callback, CallbackReturn, CbQueueItemType
15
+ from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
17
16
  from sticker_convert.utils.files.metadata_handler import XCODE_IMESSAGE_ICONSET, MetadataHandler
18
17
  from sticker_convert.utils.files.sanitize_filename import sanitize_filename
19
18
  from sticker_convert.utils.media.codec_info import CodecInfo
@@ -279,7 +278,7 @@ class XcodeImessage(UploadBase):
279
278
  opt_output: OutputOption,
280
279
  opt_comp: CompOption,
281
280
  opt_cred: CredOption,
282
- cb: "Union[Queue[CbQueueItemType], Callback]",
281
+ cb: CallbackProtocol,
283
282
  cb_return: CallbackReturn,
284
283
  ) -> List[str]:
285
284
  exporter = XcodeImessage(opt_output, opt_comp, opt_cred, cb, cb_return)
@@ -1,39 +1,57 @@
1
1
  #!/usr/bin/env python3
2
- from multiprocessing import Event, Queue
3
- from typing import Any, Callable, Dict, Optional, Tuple, Union
2
+ from multiprocessing import Event, Manager
3
+ from multiprocessing.managers import ListProxy, SyncManager
4
+ from queue import Queue
5
+ from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Protocol, Tuple, Union
4
6
 
5
7
  from tqdm import tqdm
6
8
 
7
9
  CbQueueTupleType = Tuple[
8
10
  Optional[str], Optional[Tuple[Any, ...]], Optional[Dict[str, Any]]
9
11
  ]
10
- CbQueueItemType = Union[
11
- CbQueueTupleType,
12
- str,
13
- None,
14
- ]
12
+ CbQueueItemType = Union[CbQueueTupleType, str, None]
13
+ WorkQueueItemType = Optional[Tuple[Callable[..., Any], Tuple[Any, ...]]]
14
+ ResponseItemType = Union[bool, str, None]
15
+
16
+ if TYPE_CHECKING:
17
+ # mypy complains about this
18
+ ResultsListType = ListProxy[Any] # type: ignore
19
+ ResponseListType = ListProxy[ResponseItemType] # type: ignore
20
+ CbQueueType = Queue[CbQueueItemType] # type: ignore
21
+ WorkQueueType = Queue[WorkQueueItemType] # type: ignore
22
+ else:
23
+ ResultsListType = List[Any]
24
+ ResponseListType = List[ResponseItemType]
25
+ CbQueueType = Queue
26
+ WorkQueueType = Queue
15
27
 
16
28
 
17
29
  class CallbackReturn:
18
- def __init__(self) -> None:
30
+ def __init__(self, manager: Optional[SyncManager] = None) -> None:
19
31
  self.response_event = Event()
20
- self.response_queue: Queue[Union[bool, str, None]] = Queue()
32
+ if manager is None:
33
+ manager = Manager()
34
+ self.response_queue: ResponseListType = manager.list()
21
35
 
22
- def set_response(self, response: Union[bool, str, None]) -> None:
23
- self.response_queue.put(response)
36
+ def set_response(self, response: ResponseItemType) -> None:
37
+ self.response_queue.append(response)
24
38
  self.response_event.set()
25
39
 
26
- def get_response(self) -> Union[bool, str, None]:
40
+ def get_response(self) -> ResponseItemType:
27
41
  self.response_event.wait()
28
42
 
29
- response = self.response_queue.get()
43
+ response = self.response_queue.pop()
30
44
 
31
45
  self.response_event.clear()
32
46
 
33
47
  return response
34
48
 
35
49
 
36
- class Callback:
50
+ class CallbackProtocol(Protocol):
51
+ def put(self, i: Union[CbQueueItemType, str]) -> Any: ...
52
+
53
+
54
+ class Callback(CallbackProtocol):
37
55
  def __init__(
38
56
  self,
39
57
  msg: Optional[Callable[..., None]] = None,
@@ -155,13 +173,7 @@ class Callback:
155
173
 
156
174
  return response
157
175
 
158
- def put(
159
- self,
160
- i: Union[
161
- CbQueueItemType,
162
- str,
163
- ],
164
- ) -> Union[str, bool, None]:
176
+ def put(self, i: Union[CbQueueItemType, str]) -> Union[str, bool, None]:
165
177
  if isinstance(i, tuple):
166
178
  action = i[0]
167
179
  if len(i) >= 2:
@@ -258,11 +258,7 @@ class CodecInfo:
258
258
  fps = frames_apparent / total_duration * 1000
259
259
  return fps, frames, total_duration
260
260
 
261
- return (
262
- 0.0,
263
- 1,
264
- 0,
265
- )
261
+ return 0.0, 1, 0
266
262
 
267
263
  @staticmethod
268
264
  def _get_file_fps_frames_duration_webp(
@@ -294,8 +290,8 @@ class CodecInfo:
294
290
  total_duration += frame_duration
295
291
  frames += 1
296
292
 
297
- if frames == 0:
298
- return 0.0, 0, 0
293
+ if frames <= 1:
294
+ return 0.0, 1, 0
299
295
 
300
296
  if len(durations) == 1:
301
297
  fps = frames / total_duration * 1000
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
- __version__ = "2.8.0"
3
+ __version__ = "2.8.2"