sticker-convert 2.7.13__tar.gz → 2.8.1__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. {sticker-convert-2.7.13/src/sticker_convert.egg-info → sticker-convert-2.8.1}/PKG-INFO +1 -1
  2. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/pyproject.toml +1 -1
  3. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/converter.py +65 -40
  4. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/downloaders/download_base.py +3 -7
  5. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/downloaders/download_kakao.py +3 -4
  6. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/downloaders/download_line.py +3 -4
  7. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/downloaders/download_signal.py +3 -4
  8. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/downloaders/download_telegram.py +2 -3
  9. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/job.py +78 -87
  10. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/uploaders/compress_wastickers.py +3 -4
  11. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/uploaders/upload_base.py +2 -8
  12. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/uploaders/upload_signal.py +3 -4
  13. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/uploaders/upload_telegram.py +2 -3
  14. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/uploaders/xcode_imessage.py +3 -4
  15. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/utils/callback.py +33 -21
  16. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/utils/media/codec_info.py +113 -37
  17. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/version.py +1 -1
  18. {sticker-convert-2.7.13 → sticker-convert-2.8.1/src/sticker_convert.egg-info}/PKG-INFO +1 -1
  19. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/LICENSE +0 -0
  20. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/MANIFEST.in +0 -0
  21. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/README.md +0 -0
  22. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/requirements.txt +0 -0
  23. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/setup.cfg +0 -0
  24. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/__init__.py +0 -0
  25. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/__main__.py +0 -0
  26. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/cli.py +0 -0
  27. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/definitions.py +0 -0
  28. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/downloaders/__init__.py +0 -0
  29. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/gui.py +0 -0
  30. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/gui_components/__init__.py +0 -0
  31. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/gui_components/frames/__init__.py +0 -0
  32. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/gui_components/frames/comp_frame.py +0 -0
  33. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/gui_components/frames/config_frame.py +0 -0
  34. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/gui_components/frames/control_frame.py +0 -0
  35. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/gui_components/frames/cred_frame.py +0 -0
  36. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/gui_components/frames/input_frame.py +0 -0
  37. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/gui_components/frames/output_frame.py +0 -0
  38. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/gui_components/frames/progress_frame.py +0 -0
  39. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/gui_components/frames/right_clicker.py +0 -0
  40. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/gui_components/gui_utils.py +0 -0
  41. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/gui_components/windows/__init__.py +0 -0
  42. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/gui_components/windows/advanced_compression_window.py +0 -0
  43. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/gui_components/windows/base_window.py +0 -0
  44. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/gui_components/windows/kakao_get_auth_window.py +0 -0
  45. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/gui_components/windows/line_get_auth_window.py +0 -0
  46. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/gui_components/windows/signal_get_auth_window.py +0 -0
  47. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/.github/FUNDING.yml +0 -0
  48. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/.gitignore +0 -0
  49. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/README.md +0 -0
  50. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers/Info.plist +0 -0
  51. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Info.plist +0 -0
  52. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Contents.json +0 -0
  53. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Contents.json +0 -0
  54. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Contents.json +0 -0
  55. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Sticker 1.png +0 -0
  56. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 2.sticker/Contents.json +0 -0
  57. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 2.sticker/Sticker 2.png +0 -0
  58. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 3.sticker/Contents.json +0 -0
  59. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 3.sticker/Sticker 3.png +0 -0
  60. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/App-Store-1024x1024pt.png +0 -0
  61. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Contents.json +0 -0
  62. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-App-Store-1024x768pt.png +0 -0
  63. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPad-67x50pt@2x.png +0 -0
  64. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPad-Pro-74x55pt@2x.png +0 -0
  65. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPhone-60x45pt@2x.png +0 -0
  66. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPhone-60x45pt@3x.png +0 -0
  67. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages27x20pt@2x.png +0 -0
  68. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages27x20pt@3x.png +0 -0
  69. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages32x24pt@2x.png +0 -0
  70. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages32x24pt@3x.png +0 -0
  71. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPad-Settings-29pt@2x.png +0 -0
  72. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPhone-Settings-29pt@3x.png +0 -0
  73. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPhone-settings-29pt@2x.png +0 -0
  74. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.pbxproj +0 -0
  75. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -0
  76. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -0
  77. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcuserdata/niklaspeterson.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  78. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/ios-message-stickers-template/stickers.xcodeproj/xcuserdata/niklaspeterson.xcuserdatad/xcschemes/xcschememanagement.plist +0 -0
  79. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/job_option.py +0 -0
  80. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/resources/NotoColorEmoji.ttf +0 -0
  81. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/resources/appicon.icns +0 -0
  82. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/resources/appicon.ico +0 -0
  83. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/resources/appicon.png +0 -0
  84. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/resources/compression.json +0 -0
  85. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/resources/emoji.json +0 -0
  86. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/resources/help.json +0 -0
  87. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/resources/input.json +0 -0
  88. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/resources/output.json +0 -0
  89. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/uploaders/__init__.py +0 -0
  90. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/utils/auth/get_kakao_auth.py +0 -0
  91. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/utils/auth/get_line_auth.py +0 -0
  92. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/utils/auth/get_signal_auth.py +0 -0
  93. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/utils/files/cache_store.py +0 -0
  94. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/utils/files/json_manager.py +0 -0
  95. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/utils/files/json_resources_loader.py +0 -0
  96. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/utils/files/metadata_handler.py +0 -0
  97. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/utils/files/run_bin.py +0 -0
  98. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/utils/files/sanitize_filename.py +0 -0
  99. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/utils/media/apple_png_normalize.py +0 -0
  100. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/utils/media/decrypt_kakao.py +0 -0
  101. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/utils/media/format_verify.py +0 -0
  102. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert/utils/url_detect.py +0 -0
  103. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert.egg-info/SOURCES.txt +0 -0
  104. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert.egg-info/dependency_links.txt +0 -0
  105. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert.egg-info/entry_points.txt +0 -0
  106. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert.egg-info/requires.txt +0 -0
  107. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/src/sticker_convert.egg-info/top_level.txt +0 -0
  108. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/tests/test_compression.py +0 -0
  109. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/tests/test_download.py +0 -0
  110. {sticker-convert-2.7.13 → sticker-convert-2.8.1}/tests/test_export.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sticker-convert
3
- Version: 2.7.13
3
+ Version: 2.8.1
4
4
  Summary: Convert (animated) stickers to/from WhatsApp, Telegram, Signal, Line, Kakao, iMessage. Written in Python.
5
5
  Author-email: laggykiller <chaudominic2@gmail.com>
6
6
  Maintainer-email: laggykiller <chaudominic2@gmail.com>
@@ -40,7 +40,7 @@ Repository = "https://github.com/laggykiller/sticker-convert"
40
40
  sticker-convert = "sticker_convert.__main__:main"
41
41
 
42
42
  [build-system]
43
- requires = ["setuptools>=61.0.0", "wheel"]
43
+ requires = ["setuptools>=61.0.0"]
44
44
  build-backend = "setuptools.build_meta"
45
45
 
46
46
  [tool.setuptools.packages.find]
@@ -1,11 +1,9 @@
1
1
  #!/usr/bin/env python3
2
2
  import os
3
- from decimal import ROUND_HALF_UP, Decimal
4
3
  from fractions import Fraction
5
4
  from io import BytesIO
6
5
  from math import ceil, floor
7
6
  from pathlib import Path
8
- from queue import Queue
9
7
  from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Tuple, Union, cast
10
8
 
11
9
  import numpy as np
@@ -13,9 +11,9 @@ from PIL import Image
13
11
  from PIL import __version__ as PillowVersion
14
12
 
15
13
  from sticker_convert.job_option import CompOption
16
- from sticker_convert.utils.callback import Callback, CallbackReturn, CbQueueItemType
14
+ from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
17
15
  from sticker_convert.utils.files.cache_store import CacheStore
18
- from sticker_convert.utils.media.codec_info import CodecInfo
16
+ from sticker_convert.utils.media.codec_info import CodecInfo, rounding
19
17
  from sticker_convert.utils.media.format_verify import FormatVerify
20
18
 
21
19
  if TYPE_CHECKING:
@@ -44,10 +42,6 @@ YUV_RGB_MATRIX = np.array(
44
42
  )
45
43
 
46
44
 
47
- def rounding(value: float) -> Decimal:
48
- return Decimal(value).quantize(0, ROUND_HALF_UP)
49
-
50
-
51
45
  def get_step_value(
52
46
  max_step: Optional[int],
53
47
  min_step: Optional[int],
@@ -125,7 +119,7 @@ class StickerConvert:
125
119
  in_f: Union[Path, Tuple[Path, bytes]],
126
120
  out_f: Path,
127
121
  opt_comp: CompOption,
128
- cb: "Union[Queue[CbQueueItemType], Callback]",
122
+ cb: CallbackProtocol,
129
123
  # cb_return: CallbackReturn
130
124
  ) -> None:
131
125
  self.in_f: Union[bytes, Path]
@@ -191,7 +185,7 @@ class StickerConvert:
191
185
  in_f: Union[Path, Tuple[Path, bytes]],
192
186
  out_f: Path,
193
187
  opt_comp: CompOption,
194
- cb: "Union[Queue[CbQueueItemType], Callback]",
188
+ cb: CallbackProtocol,
195
189
  _cb_return: CallbackReturn,
196
190
  ) -> Tuple[bool, Path, Union[None, bytes, Path], int]:
197
191
  sticker = StickerConvert(in_f, out_f, opt_comp, cb)
@@ -426,10 +420,24 @@ class StickerConvert:
426
420
  def _frames_import_pillow(self) -> None:
427
421
  with Image.open(self.in_f) as im:
428
422
  # Note: im.convert("RGBA") would return rgba image of current frame only
429
- if "n_frames" in dir(im):
430
- for i in range(im.n_frames):
431
- im.seek(i)
423
+ if (
424
+ "n_frames" in dir(im)
425
+ and im.n_frames != 0
426
+ and self.codec_info_orig.fps != 0.0
427
+ ):
428
+ duration_ptr = 0.0
429
+ duration_inc = 1 / self.codec_info_orig.fps * 1000
430
+ next_frame_start_duration = im.info.get("duration", 1000)
431
+ frame = 0
432
+ while True:
432
433
  self.frames_raw.append(np.asarray(im.convert("RGBA")))
434
+ duration_ptr += duration_inc
435
+ if duration_ptr >= next_frame_start_duration:
436
+ if frame == im.n_frames:
437
+ break
438
+ im.seek(frame)
439
+ next_frame_start_duration += im.info.get("duration", 1000)
440
+ frame += 1
433
441
  else:
434
442
  self.frames_raw.append(np.asarray(im.convert("RGBA")))
435
443
 
@@ -480,10 +488,12 @@ class StickerConvert:
480
488
  graph.configure()
481
489
 
482
490
  graph.push(frame)
483
- frame = cast(VideoFrame, graph.pull())
491
+ frame_resized = cast(VideoFrame, graph.pull())
492
+ else:
493
+ frame_resized = frame
484
494
 
485
- if frame.format.name == "yuv420p":
486
- rgb_array = frame.to_ndarray(format="rgb24")
495
+ if frame_resized.format.name == "yuv420p":
496
+ rgb_array = frame_resized.to_ndarray(format="rgb24")
487
497
  rgba_array = np.dstack(
488
498
  (
489
499
  rgb_array,
@@ -494,11 +504,11 @@ class StickerConvert:
494
504
  # yuva420p may cause crash
495
505
  # Not safe to directly call frame.to_ndarray(format="rgba")
496
506
  # https://github.com/laggykiller/sticker-convert/issues/114
497
- frame = frame.reformat(
507
+ frame_resized = frame_resized.reformat(
498
508
  format="yuva420p",
499
509
  dst_colorspace=1,
500
510
  )
501
- rgba_array = yuva_to_rgba(frame)
511
+ rgba_array = yuva_to_rgba(frame_resized)
502
512
 
503
513
  # Remove pixels that was added to make dimensions even
504
514
  rgba_array = rgba_array[0:width_orig, 0:height_orig]
@@ -534,25 +544,25 @@ class StickerConvert:
534
544
  anim.lottie_animation_destroy()
535
545
 
536
546
  def determine_bg_color(self) -> Tuple[int, int, int, int]:
547
+ mean_total = 0.0
537
548
  # Calculate average color of all frames for selecting background color
538
- frames_raw_np = np.array(self.frames_raw)
539
- s = frames_raw_np.shape
540
- colors = frames_raw_np.reshape((-1, s[3])) # type: ignore
541
- # Do not count in alpha=0
542
- # If alpha > 0, use alpha as weight
543
- colors = colors[colors[:, 3] != 0]
544
- if colors.shape[0] == 0:
545
- return (0, 0, 0, 0)
549
+ for frame in self.frames_raw:
550
+ s = frame.shape
551
+ colors = frame.reshape((-1, s[2])) # type: ignore
552
+ # Do not count in alpha=0
553
+ # If alpha > 0, use alpha as weight
554
+ colors = colors[colors[:, 3] != 0]
555
+ if colors.shape[0] != 0:
556
+ alphas = colors[:, 3] / 255
557
+ r_mean = np.mean(colors[:, 0] * alphas)
558
+ g_mean = np.mean(colors[:, 1] * alphas)
559
+ b_mean = np.mean(colors[:, 2] * alphas)
560
+ mean_total += (r_mean + g_mean + b_mean) / 3
561
+
562
+ if mean_total / len(self.frames_raw) < 128:
563
+ return (255, 255, 255, 0)
546
564
  else:
547
- alphas = colors[:, 3] / 255
548
- r_mean = np.mean(colors[:, 0] * alphas)
549
- g_mean = np.mean(colors[:, 1] * alphas)
550
- b_mean = np.mean(colors[:, 2] * alphas)
551
- mean = (r_mean + g_mean + b_mean) / 3
552
- if mean < 128:
553
- return (255, 255, 255, 0)
554
- else:
555
- return (0, 0, 0, 0)
565
+ return (0, 0, 0, 0)
556
566
 
557
567
  def frames_resize(
558
568
  self, frames_in: "List[np.ndarray[Any, Any]]"
@@ -647,8 +657,6 @@ class StickerConvert:
647
657
  frame_current = 0
648
658
  frame_current_float = 0.0
649
659
  while True:
650
- frame_current_float += frame_increment
651
- frame_current = int(rounding(frame_current_float))
652
660
  if frame_current <= len(frames_in) - 1 and not (
653
661
  frames_out_max and len(frames_out) == frames_out_max
654
662
  ):
@@ -659,6 +667,8 @@ class StickerConvert:
659
667
  ):
660
668
  frames_out.append(frames_in[-1])
661
669
  return frames_out
670
+ frame_current_float += frame_increment
671
+ frame_current = int(rounding(frame_current_float))
662
672
 
663
673
  def frames_export(self) -> None:
664
674
  is_animated = len(self.frames_processed) > 1 and self.fps
@@ -775,10 +785,25 @@ class StickerConvert:
775
785
  config = webp.WebPConfig.new(quality=self.quality) # type: ignore
776
786
  enc = webp.WebPAnimEncoder.new(self.res_w, self.res_h) # type: ignore
777
787
  timestamp_ms = 0
778
- for frame in self.frames_processed:
779
- pic = webp.WebPPicture.from_numpy(frame) # type: ignore
788
+ timestamp_inc = int(1000 / self.fps)
789
+
790
+ pic = webp.WebPPicture.from_numpy(self.frames_processed[0]) # type: ignore
791
+ enc.encode_frame(pic, 0, config=config) # type: ignore
792
+
793
+ frame_num = 1
794
+ frame_num_prev = 1
795
+ frame_total = len(self.frames_processed)
796
+ while frame_num < frame_total - 1:
797
+ while frame_num < frame_total - 1 and np.array_equal(
798
+ self.frames_processed[frame_num_prev],
799
+ self.frames_processed[frame_num],
800
+ ):
801
+ timestamp_ms += timestamp_inc
802
+ frame_num += 1
803
+ pic = webp.WebPPicture.from_numpy(self.frames_processed[frame_num]) # type: ignore
780
804
  enc.encode_frame(pic, timestamp_ms, config=config) # type: ignore
781
- timestamp_ms += int(1000 / self.fps)
805
+ frame_num_prev = frame_num
806
+
782
807
  anim_data = enc.assemble(timestamp_ms) # type: ignore
783
808
  self.tmp_f.write(anim_data.buffer()) # type: ignore
784
809
 
@@ -2,13 +2,12 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  from pathlib import Path
5
- from queue import Queue
6
- from typing import Any, List, Optional, Tuple, Union
5
+ from typing import Any, List, Optional, Tuple
7
6
 
8
7
  import requests
9
8
 
10
9
  from sticker_convert.job_option import CredOption
11
- from sticker_convert.utils.callback import Callback, CallbackReturn, CbQueueItemType
10
+ from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
12
11
 
13
12
 
14
13
  class DownloadBase:
@@ -17,10 +16,7 @@ class DownloadBase:
17
16
  url: str,
18
17
  out_dir: Path,
19
18
  opt_cred: Optional[CredOption],
20
- cb: Union[
21
- Queue[CbQueueItemType],
22
- Callback,
23
- ],
19
+ cb: CallbackProtocol,
24
20
  cb_return: CallbackReturn,
25
21
  ) -> None:
26
22
  self.url = url
@@ -5,8 +5,7 @@ import json
5
5
  import zipfile
6
6
  from io import BytesIO
7
7
  from pathlib import Path
8
- from queue import Queue
9
- from typing import Any, List, Optional, Tuple, Union
8
+ from typing import Any, List, Optional, Tuple
10
9
  from urllib.parse import urlparse
11
10
 
12
11
  import requests
@@ -15,7 +14,7 @@ from bs4.element import Tag
15
14
 
16
15
  from sticker_convert.downloaders.download_base import DownloadBase
17
16
  from sticker_convert.job_option import CredOption
18
- from sticker_convert.utils.callback import Callback, CallbackReturn, CbQueueItemType
17
+ from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
19
18
  from sticker_convert.utils.files.metadata_handler import MetadataHandler
20
19
  from sticker_convert.utils.media.decrypt_kakao import DecryptKakao
21
20
 
@@ -249,7 +248,7 @@ class DownloadKakao(DownloadBase):
249
248
  url: str,
250
249
  out_dir: Path,
251
250
  opt_cred: Optional[CredOption],
252
- cb: "Union[Queue[CbQueueItemType], Callback]",
251
+ cb: CallbackProtocol,
253
252
  cb_return: CallbackReturn,
254
253
  ) -> bool:
255
254
  downloader = DownloadKakao(url, out_dir, opt_cred, cb, cb_return)
@@ -7,8 +7,7 @@ import string
7
7
  import zipfile
8
8
  from io import BytesIO
9
9
  from pathlib import Path
10
- from queue import Queue
11
- from typing import Any, Dict, List, Optional, Tuple, Union
10
+ from typing import Any, Dict, List, Optional, Tuple
12
11
  from urllib import parse
13
12
 
14
13
  import requests
@@ -18,7 +17,7 @@ from PIL import Image
18
17
  from sticker_convert.downloaders.download_base import DownloadBase
19
18
  from sticker_convert.job_option import CredOption
20
19
  from sticker_convert.utils.auth.get_line_auth import GetLineAuth
21
- from sticker_convert.utils.callback import Callback, CallbackReturn, CbQueueItemType
20
+ from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
22
21
  from sticker_convert.utils.files.metadata_handler import MetadataHandler
23
22
  from sticker_convert.utils.media.apple_png_normalize import ApplePngNormalize
24
23
 
@@ -466,7 +465,7 @@ class DownloadLine(DownloadBase):
466
465
  url: str,
467
466
  out_dir: Path,
468
467
  opt_cred: Optional[CredOption],
469
- cb: "Union[Queue[CbQueueItemType], Callback]",
468
+ cb: CallbackProtocol,
470
469
  cb_return: CallbackReturn,
471
470
  ) -> bool:
472
471
  downloader = DownloadLine(url, out_dir, opt_cred, cb, cb_return)
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  from pathlib import Path
3
- from queue import Queue
4
- from typing import Dict, Optional, Union
3
+ from typing import Dict, Optional
5
4
 
6
5
  import anyio
7
6
  from signalstickers_client import StickersClient # type: ignore
@@ -10,7 +9,7 @@ from signalstickers_client.models import StickerPack # type: ignore
10
9
 
11
10
  from sticker_convert.downloaders.download_base import DownloadBase
12
11
  from sticker_convert.job_option import CredOption
13
- from sticker_convert.utils.callback import Callback, CallbackReturn, CbQueueItemType
12
+ from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
14
13
  from sticker_convert.utils.files.metadata_handler import MetadataHandler
15
14
  from sticker_convert.utils.media.codec_info import CodecInfo
16
15
 
@@ -86,7 +85,7 @@ class DownloadSignal(DownloadBase):
86
85
  url: str,
87
86
  out_dir: Path,
88
87
  opt_cred: Optional[CredOption],
89
- cb: "Union[Queue[CbQueueItemType], Callback]",
88
+ cb: CallbackProtocol,
90
89
  cb_return: CallbackReturn,
91
90
  ) -> bool:
92
91
  downloader = DownloadSignal(url, out_dir, opt_cred, cb, cb_return)
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env python3
2
2
  from pathlib import Path
3
- from queue import Queue
4
3
  from typing import Dict, Optional, Union
5
4
  from urllib.parse import urlparse
6
5
 
@@ -11,7 +10,7 @@ from telegram.ext import AIORateLimiter, ApplicationBuilder
11
10
 
12
11
  from sticker_convert.downloaders.download_base import DownloadBase
13
12
  from sticker_convert.job_option import CredOption
14
- from sticker_convert.utils.callback import Callback, CallbackReturn, CbQueueItemType
13
+ from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
15
14
  from sticker_convert.utils.files.metadata_handler import MetadataHandler
16
15
 
17
16
 
@@ -124,7 +123,7 @@ class DownloadTelegram(DownloadBase):
124
123
  url: str,
125
124
  out_dir: Path,
126
125
  opt_cred: Optional[CredOption],
127
- cb: "Union[Queue[CbQueueItemType], Callback]",
126
+ cb: CallbackProtocol,
128
127
  cb_return: CallbackReturn,
129
128
  ) -> bool:
130
129
  downloader = DownloadTelegram(url, out_dir, opt_cred, cb, cb_return)
@@ -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,18 @@ 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[:] = []
175
+ if self.cb_thread_instance:
176
+ self.cb_thread_instance.join()
178
177
  self.processes.clear()
179
178
 
180
179
  def kill_workers(self, *_: Any, **__: Any) -> None:
@@ -187,21 +186,10 @@ class Executor:
187
186
 
188
187
  def cleanup(self, killed: bool = False) -> None:
189
188
  if killed:
190
- self.cb_queue.put("Job cancelled.")
191
- self.cb_queue.put(("bar", None, {"set_progress_mode": "clear"}))
192
- 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
+ self.cb_msg("Job cancelled.")
190
+ self.cb_bar("clear")
191
+ # self.cb_queue.put(None)
192
+ self.manager.shutdown()
205
193
 
206
194
 
207
195
  class Job:
@@ -547,17 +535,18 @@ class Job:
547
535
  self.executor.cb("Nothing to download")
548
536
  return True
549
537
 
538
+ self.executor.start_workers(processes=1)
539
+
550
540
  for downloader in downloaders:
551
541
  self.executor.add_work(
552
542
  work_func=downloader,
553
543
  work_args=(self.opt_input.url, self.opt_input.dir, self.opt_cred),
554
544
  )
555
545
 
556
- self.executor.start_workers(processes=1)
557
546
  self.executor.join_workers()
558
547
 
559
548
  # Return False if any of the job returns failure
560
- for result in self.executor.get_result():
549
+ for result in self.executor.results_list:
561
550
  if result is False:
562
551
  return False
563
552
 
@@ -619,6 +608,8 @@ class Job:
619
608
  "bar", kwargs={"set_progress_mode": "determinate", "steps": in_fs_count}
620
609
  )
621
610
 
611
+ self.executor.start_workers(processes=min(self.opt_comp.processes, in_fs_count))
612
+
622
613
  for i in in_fs:
623
614
  in_f = input_dir / i.name
624
615
  out_f = output_dir / Path(i).stem
@@ -627,11 +618,10 @@ class Job:
627
618
  work_func=StickerConvert.convert, work_args=(in_f, out_f, self.opt_comp)
628
619
  )
629
620
 
630
- self.executor.start_workers(processes=min(self.opt_comp.processes, in_fs_count))
631
621
  self.executor.join_workers()
632
622
 
633
623
  # Return False if any of the job returns failure
634
- for result in self.executor.get_result():
624
+ for result in self.executor.results_list:
635
625
  if result[0] is False:
636
626
  return False
637
627
 
@@ -661,16 +651,17 @@ class Job:
661
651
  if self.opt_output.option == "imessage":
662
652
  exporters.append(XcodeImessage.start)
663
653
 
654
+ self.executor.start_workers(processes=1)
655
+
664
656
  for exporter in exporters:
665
657
  self.executor.add_work(
666
658
  work_func=exporter,
667
659
  work_args=(self.opt_output, self.opt_comp, self.opt_cred),
668
660
  )
669
661
 
670
- self.executor.start_workers(processes=1)
671
662
  self.executor.join_workers()
672
663
 
673
- for result in self.executor.get_result():
664
+ for result in self.executor.results_list:
674
665
  self.out_urls.extend(result)
675
666
 
676
667
  if self.out_urls:
@@ -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)