sticker-convert 2.11.9__tar.gz → 2.12.1__tar.gz

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 (125) hide show
  1. {sticker_convert-2.11.9/src/sticker_convert.egg-info → sticker_convert-2.12.1}/PKG-INFO +1 -1
  2. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/converter.py +72 -0
  3. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/definitions.py +7 -0
  4. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/downloaders/__init__.py +0 -0
  5. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/downloaders/download_discord.py +0 -0
  6. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/downloaders/download_kakao.py +10 -4
  7. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/downloaders/download_viber.py +20 -6
  8. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/gui_components/__init__.py +0 -0
  9. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/gui_components/frames/__init__.py +0 -0
  10. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/gui_components/frames/comp_frame.py +0 -0
  11. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/gui_components/frames/config_frame.py +0 -0
  12. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/gui_components/frames/control_frame.py +0 -0
  13. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/gui_components/frames/cred_frame.py +0 -0
  14. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/gui_components/frames/input_frame.py +0 -0
  15. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/gui_components/frames/output_frame.py +0 -0
  16. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/gui_components/frames/progress_frame.py +0 -0
  17. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/gui_components/frames/right_clicker.py +0 -0
  18. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/gui_components/gui_utils.py +0 -0
  19. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/gui_components/windows/__init__.py +0 -0
  20. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/gui_components/windows/advanced_compression_window.py +0 -0
  21. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/gui_components/windows/base_window.py +0 -0
  22. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/gui_components/windows/discord_get_auth_window.py +0 -0
  23. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/gui_components/windows/kakao_get_auth_window.py +0 -0
  24. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/gui_components/windows/line_get_auth_window.py +0 -0
  25. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/gui_components/windows/signal_get_auth_window.py +0 -0
  26. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/gui_components/windows/viber_get_auth_window.py +0 -0
  27. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/.github/FUNDING.yml +0 -0
  28. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/.gitignore +0 -0
  29. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/README.md +0 -0
  30. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers/Info.plist +0 -0
  31. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Info.plist +0 -0
  32. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Contents.json +0 -0
  33. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Contents.json +0 -0
  34. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Contents.json +0 -0
  35. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Sticker 1.png +0 -0
  36. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 2.sticker/Contents.json +0 -0
  37. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 2.sticker/Sticker 2.png +0 -0
  38. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 3.sticker/Contents.json +0 -0
  39. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 3.sticker/Sticker 3.png +0 -0
  40. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/App-Store-1024x1024pt.png +0 -0
  41. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Contents.json +0 -0
  42. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-App-Store-1024x768pt.png +0 -0
  43. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPad-67x50pt@2x.png +0 -0
  44. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPad-Pro-74x55pt@2x.png +0 -0
  45. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPhone-60x45pt@2x.png +0 -0
  46. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPhone-60x45pt@3x.png +0 -0
  47. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages27x20pt@2x.png +0 -0
  48. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages27x20pt@3x.png +0 -0
  49. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages32x24pt@2x.png +0 -0
  50. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages32x24pt@3x.png +0 -0
  51. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPad-Settings-29pt@2x.png +0 -0
  52. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPhone-Settings-29pt@3x.png +0 -0
  53. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPhone-settings-29pt@2x.png +0 -0
  54. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.pbxproj +0 -0
  55. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -0
  56. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -0
  57. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcuserdata/niklaspeterson.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  58. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/ios-message-stickers-template/stickers.xcodeproj/xcuserdata/niklaspeterson.xcuserdatad/xcschemes/xcschememanagement.plist +0 -0
  59. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/resources/emoji.json +0 -0
  60. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/resources/help.json +0 -0
  61. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/resources/input.json +0 -0
  62. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/resources/memdump_windows.ps1 +0 -0
  63. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/resources/output.json +0 -0
  64. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/uploaders/__init__.py +0 -0
  65. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/uploaders/upload_viber.py +19 -19
  66. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/utils/auth/get_discord_auth.py +0 -0
  67. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/utils/auth/telegram_api.py +0 -0
  68. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/utils/auth/telethon_setup.py +0 -0
  69. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/utils/callback.py +0 -0
  70. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/utils/chrome_remotedebug.py +75 -15
  71. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/utils/emoji.py +0 -0
  72. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/utils/files/json_resources_loader.py +0 -0
  73. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/utils/files/metadata_handler.py +1 -0
  74. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/utils/media/codec_info.py +53 -6
  75. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/utils/media/decrypt_kakao.py +0 -0
  76. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/utils/process.py +0 -0
  77. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/version.py +1 -1
  78. {sticker_convert-2.11.9 → sticker_convert-2.12.1/src/sticker_convert.egg-info}/PKG-INFO +1 -1
  79. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/tests/test_download.py +28 -0
  80. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/LICENSE +0 -0
  81. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/MANIFEST.in +0 -0
  82. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/README.md +0 -0
  83. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/pyproject.toml +0 -0
  84. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/requirements.txt +0 -0
  85. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/setup.cfg +0 -0
  86. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/__init__.py +0 -0
  87. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/__main__.py +0 -0
  88. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/cli.py +0 -0
  89. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/downloaders/download_base.py +0 -0
  90. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/downloaders/download_line.py +0 -0
  91. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/downloaders/download_signal.py +0 -0
  92. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/downloaders/download_telegram.py +0 -0
  93. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/gui.py +0 -0
  94. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/job.py +0 -0
  95. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/job_option.py +0 -0
  96. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/resources/NotoColorEmoji.ttf +0 -0
  97. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/resources/appicon.icns +0 -0
  98. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/resources/appicon.ico +0 -0
  99. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/resources/appicon.png +0 -0
  100. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/resources/compression.json +0 -0
  101. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/resources/memdump_linux.sh +0 -0
  102. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/uploaders/compress_wastickers.py +0 -0
  103. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/uploaders/upload_base.py +0 -0
  104. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/uploaders/upload_signal.py +0 -0
  105. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/uploaders/upload_telegram.py +0 -0
  106. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/uploaders/xcode_imessage.py +0 -0
  107. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/utils/auth/get_kakao_auth.py +0 -0
  108. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/utils/auth/get_kakao_desktop_auth.py +0 -0
  109. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/utils/auth/get_line_auth.py +0 -0
  110. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/utils/auth/get_signal_auth.py +0 -0
  111. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/utils/auth/get_viber_auth.py +0 -0
  112. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/utils/files/cache_store.py +0 -0
  113. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/utils/files/json_manager.py +0 -0
  114. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/utils/files/run_bin.py +0 -0
  115. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/utils/files/sanitize_filename.py +0 -0
  116. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/utils/media/apple_png_normalize.py +0 -0
  117. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/utils/media/format_verify.py +0 -0
  118. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert/utils/url_detect.py +0 -0
  119. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert.egg-info/SOURCES.txt +0 -0
  120. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert.egg-info/dependency_links.txt +0 -0
  121. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert.egg-info/entry_points.txt +0 -0
  122. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert.egg-info/requires.txt +0 -0
  123. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/src/sticker_convert.egg-info/top_level.txt +0 -0
  124. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/tests/test_compression.py +0 -0
  125. {sticker_convert-2.11.9 → sticker_convert-2.12.1}/tests/test_export.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sticker-convert
3
- Version: 2.11.9
3
+ Version: 2.12.1
4
4
  Summary: Convert (animated) stickers to/from WhatsApp, Telegram, Signal, Line, Kakao, Viber, Discord, iMessage. Written in Python.
5
5
  Author-email: laggykiller <chaudominic2@gmail.com>
6
6
  Maintainer-email: laggykiller <chaudominic2@gmail.com>
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env python3
2
+ import json
2
3
  import os
3
4
  from fractions import Fraction
4
5
  from io import BytesIO
@@ -7,12 +8,14 @@ from pathlib import Path
7
8
  from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Tuple, Union, cast
8
9
 
9
10
  import numpy as np
11
+ from bs4 import BeautifulSoup
10
12
  from PIL import Image
11
13
  from PIL import __version__ as PillowVersion
12
14
  from PIL import features
13
15
 
14
16
  from sticker_convert.job_option import CompOption
15
17
  from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
18
+ from sticker_convert.utils.chrome_remotedebug import CRD
16
19
  from sticker_convert.utils.files.cache_store import CacheStore
17
20
  from sticker_convert.utils.media.codec_info import CodecInfo, rounding
18
21
  from sticker_convert.utils.media.format_verify import FormatVerify
@@ -423,9 +426,78 @@ class StickerConvert:
423
426
  # ffmpeg do not support webp decoding (yet)
424
427
  # ffmpeg could fail to decode apng if file is buggy
425
428
  self._frames_import_pillow()
429
+ elif suffix == ".svg":
430
+ self._frames_import_svg()
426
431
  else:
427
432
  self._frames_import_pyav()
428
433
 
434
+ def _frames_import_svg(self) -> None:
435
+ width = self.codec_info_orig.res[0]
436
+ height = self.codec_info_orig.res[1]
437
+
438
+ chrome_path = CRD.get_chrome_path()
439
+ args = [
440
+ "--headless",
441
+ "--disable-extensions",
442
+ "--disable-infobars",
443
+ "--disable-gpu",
444
+ "--disable-gpu-rasterization",
445
+ "--hide-scrollbars",
446
+ f"--window-size={width + 100},{height + 100}",
447
+ "about:blank",
448
+ ]
449
+ if chrome_path is None:
450
+ raise RuntimeError("[F] Chrome/Chromium required for importing svg")
451
+ self.cb.put("[W] Importing SVG takes long time")
452
+
453
+ if isinstance(self.in_f, bytes):
454
+ svg = self.in_f.decode()
455
+ else:
456
+ with open(self.in_f) as f:
457
+ svg = f.read()
458
+ soup = BeautifulSoup(svg, "html.parser")
459
+ svg_tag = soup.find_all("svg")[0]
460
+
461
+ if svg_tag.get("width") is None:
462
+ svg_tag["width"] = width
463
+ if svg_tag.get("height") is None:
464
+ svg_tag["height"] = height
465
+ svg = str(soup)
466
+
467
+ crd = None
468
+ try:
469
+ crd = CRD(chrome_path, args=args)
470
+ crd.connect(-1)
471
+ crd.open_html_str(svg)
472
+ crd.set_transparent_bg()
473
+ crd.exec_js('svg = document.getElementsByTagName("svg")[0]')
474
+ x = json.loads(crd.exec_js("svg.getBoundingClientRect().x"))["result"][
475
+ "result"
476
+ ]["value"]
477
+ y = json.loads(crd.exec_js("svg.getBoundingClientRect().y"))["result"][
478
+ "result"
479
+ ]["value"]
480
+ clip = {"x": x, "y": y, "width": width, "height": height, "scale": 1}
481
+
482
+ if self.codec_info_orig.fps > 0:
483
+ crd.exec_js("svg.pauseAnimations()")
484
+ for i in range(self.codec_info_orig.frames):
485
+ curr_time = (
486
+ i
487
+ / self.codec_info_orig.frames
488
+ * self.codec_info_orig.duration
489
+ / 1000
490
+ )
491
+ crd.exec_js(f"svg.setCurrentTime({curr_time})")
492
+ self.frames_raw.append(
493
+ np.asarray(crd.screenshot(clip).convert("RGBA"))
494
+ )
495
+ else:
496
+ self.frames_raw.append(np.asarray(crd.screenshot(clip).convert("RGBA")))
497
+ finally:
498
+ if crd is not None:
499
+ crd.close()
500
+
429
501
  def _frames_import_pillow(self) -> None:
430
502
  with Image.open(self.in_f) as im:
431
503
  # Note: im.convert("RGBA") would return rgba image of current frame only
@@ -82,3 +82,10 @@ def get_config_dir() -> Path:
82
82
 
83
83
  # Directory for saving configs
84
84
  CONFIG_DIR = get_config_dir()
85
+
86
+ # When importing SVG, import at this fps
87
+ SVG_SAMPLE_FPS = 30
88
+
89
+ # If width and height not set in SVG tag, import at this dimension
90
+ SVG_DEFAULT_WIDTH = 1024
91
+ SVG_DEFAULT_HEIGHT = 1024
@@ -128,7 +128,9 @@ class DownloadKakao(DownloadBase):
128
128
  self.pack_title = urlparse(r.url).path.split("/")[-1]
129
129
  pack_info_unauthed = MetadataKakao.get_pack_info_unauthed(self.pack_title)
130
130
  if pack_info_unauthed is None:
131
- self.cb.put("Download failed: Cannot download metadata for sticker pack")
131
+ self.cb.put(
132
+ "Download failed: Cannot download metadata for sticker pack"
133
+ )
132
134
  return 0, 0
133
135
 
134
136
  self.author = pack_info_unauthed["result"]["artist"]
@@ -136,11 +138,15 @@ class DownloadKakao(DownloadBase):
136
138
 
137
139
  if item_code is None:
138
140
  if self.auth_token is None:
139
- self.cb.put("Warning: Downloading animated sticker requires auth_token")
141
+ self.cb.put(
142
+ "Warning: Downloading animated sticker requires auth_token"
143
+ )
140
144
  else:
141
- self.cb.put("Warning: auth_token invalid, cannot download animated sticker")
145
+ self.cb.put(
146
+ "Warning: auth_token invalid, cannot download animated sticker"
147
+ )
142
148
  self.cb.put("Downloading static stickers...")
143
- self.download_static(thumbnail_urls)
149
+ return self.download_static(thumbnail_urls)
144
150
  else:
145
151
  return self.download_animated(item_code)
146
152
 
@@ -19,7 +19,7 @@ class DownloadViber(DownloadBase):
19
19
  # def __init__(self, *args: Any, **kwargs: Any) -> None:
20
20
  # super().__init__(*args, **kwargs)
21
21
 
22
- def get_pack_info(self, url: str) -> Optional[Tuple[str, str]]:
22
+ def get_pack_info(self, url: str) -> Optional[Tuple[str, str, str]]:
23
23
  r = requests.get(url, allow_redirects=True)
24
24
  soup = BeautifulSoup(r.text, "html.parser")
25
25
 
@@ -41,10 +41,13 @@ class DownloadViber(DownloadBase):
41
41
  title = pack_dict["title"]
42
42
  first_sticker_url = cast(str, pack_dict["stickerFirstItemUrl"])
43
43
  zip_url = "/".join(first_sticker_url.split("/")[:-1]) + ".zip"
44
+ pack_id = pack_dict["id"].split(".")[-1]
44
45
 
45
- return title, zip_url
46
+ return title, zip_url, pack_id
46
47
 
47
- def decompress(self, zip_file: bytes) -> int:
48
+ def decompress(
49
+ self, zip_file: bytes, exts: Optional[Tuple[str, ...]] = None
50
+ ) -> int:
48
51
  with zipfile.ZipFile(BytesIO(zip_file)) as zf:
49
52
  self.cb.put("Unzipping...")
50
53
 
@@ -58,9 +61,14 @@ class DownloadViber(DownloadBase):
58
61
  )
59
62
 
60
63
  for sticker in zf_files:
64
+ ext = Path(sticker).suffix
65
+ if "frame" in sticker or ".db" in sticker or sticker.endswith("/"):
66
+ continue
67
+ if exts is not None and ext not in exts:
68
+ continue
61
69
  num = sticker.split(".")[0][-2:].zfill(3)
62
70
  data = zf.read(sticker)
63
- ext = Path(sticker).suffix
71
+
64
72
  self.cb.put(f"Read {sticker}")
65
73
 
66
74
  out_path = Path(self.out_dir, num + ext)
@@ -76,10 +84,16 @@ class DownloadViber(DownloadBase):
76
84
  if pack_info is None:
77
85
  self.cb.put("Download failed: Cannot get pack info")
78
86
  return 0, 0
79
- title, zip_url = pack_info
87
+ title, zip_url, pack_id = pack_info
80
88
 
89
+ anim_url = f"https://content.cdn.viber.com/stickers/ASVG/{pack_id}.zip"
90
+ anim_file = self.download_file(anim_url)
81
91
  zip_file = self.download_file(zip_url)
82
- count = self.decompress(zip_file)
92
+ if anim_file:
93
+ count = self.decompress(anim_file, (".svg",))
94
+ count += self.decompress(zip_file, (".mp3",))
95
+ else:
96
+ count = self.decompress(zip_file, (".mp3", ".png"))
83
97
 
84
98
  MetadataHandler.set_metadata(self.out_dir, title=title)
85
99
 
@@ -25,13 +25,14 @@ class UploadViber(UploadBase):
25
25
  self.png_spec = copy.deepcopy(self.base_spec)
26
26
  self.png_spec.set_res_max(490)
27
27
  self.png_spec.set_format((".png",))
28
+ self.opt_comp_merged = copy.deepcopy(self.opt_comp)
29
+ self.opt_comp_merged.merge(self.png_spec)
28
30
 
29
31
  self.png_cover_spec = copy.deepcopy(self.base_spec)
30
32
  self.png_cover_spec.set_res_max(120)
31
- self.png_spec.set_format((".png",))
32
-
33
- self.opt_comp_merged = copy.deepcopy(self.opt_comp)
34
- self.opt_comp_merged.merge(self.png_spec)
33
+ self.png_cover_spec.set_format((".png",))
34
+ self.opt_comp_cover_merged = copy.deepcopy(self.opt_comp)
35
+ self.opt_comp_cover_merged.merge(self.png_cover_spec)
35
36
 
36
37
  def upload_stickers_viber(self) -> Tuple[int, int, List[str]]:
37
38
  urls: List[str] = []
@@ -72,23 +73,22 @@ class UploadViber(UploadBase):
72
73
  separate_image_anim=False,
73
74
  )
74
75
 
75
- cover_path_old = MetadataHandler.get_cover(self.opt_output.dir)
76
- if cover_path_old:
77
- cover_path = cover_path_old
76
+ cover_path = MetadataHandler.get_cover(self.opt_output.dir)
77
+ if cover_path is None:
78
+ cover_path = MetadataHandler.get_stickers_present(self.opt_output.dir)[0]
79
+
80
+ if FormatVerify.check_file(cover_path, spec=self.png_cover_spec):
81
+ with open(cover_path, "rb") as f:
82
+ cover_bytes = f.read()
78
83
  else:
79
- cover_path_old = MetadataHandler.get_stickers_present(self.opt_output.dir)[
80
- 0
81
- ]
82
- cover_path = self.opt_output.dir / "cover.png"
83
-
84
- if not FormatVerify.check_file(cover_path_old, spec=self.png_cover_spec):
85
- StickerConvert.convert(
86
- cover_path_old,
84
+ _, _, cover_bytes, _ = StickerConvert.convert( # type: ignore
87
85
  cover_path,
88
- self.opt_comp_merged,
86
+ Path("bytes.png"),
87
+ self.opt_comp_cover_merged,
89
88
  self.cb,
90
89
  self.cb_return,
91
90
  )
91
+ assert isinstance(cover_bytes, bytes)
92
92
 
93
93
  stickers_total = 0
94
94
  stickers_ok = 0
@@ -126,12 +126,12 @@ class UploadViber(UploadBase):
126
126
  upload_data["description"] = author
127
127
  upload_data["shareable"] = "1"
128
128
 
129
- with open(out_f, "rb") as f, open(cover_path, "rb") as g:
129
+ with open(out_f, "rb") as f:
130
130
  r = requests.post(
131
131
  "https://market.api.viber.com/2/users/custom-sticker-packs/create",
132
132
  files={
133
- "file": ("upload.zip", f),
134
- "file_icon": ("color_icon.png", g),
133
+ "file": ("upload.zip", f.read()),
134
+ "file_icon": ("color_icon.png", cover_bytes),
135
135
  },
136
136
  data=upload_data,
137
137
  )
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env python3
2
+ import base64
3
+ import io
2
4
  import json
3
5
  import os
4
6
  import platform
@@ -6,10 +8,11 @@ import shutil
6
8
  import socket
7
9
  import subprocess
8
10
  import time
9
- from typing import Any, Dict, Optional, Union, cast
11
+ from typing import Any, Dict, List, Optional, Union, cast
10
12
 
11
13
  import requests
12
14
  import websocket
15
+ from PIL import Image
13
16
 
14
17
  # References
15
18
  # https://github.com/yeongbin-jo/python-chromedriver-autoinstaller/blob/master/chromedriver_autoinstaller/utils.py
@@ -24,15 +27,31 @@ def get_free_port() -> int:
24
27
 
25
28
 
26
29
  class CRD:
27
- def __init__(self, chrome_bin: str, port: Optional[int] = None):
30
+ def __init__(
31
+ self,
32
+ chrome_bin: str,
33
+ port: Optional[int] = None,
34
+ args: Optional[List[str]] = None,
35
+ ):
28
36
  if port is None:
29
37
  port = get_free_port()
30
38
  self.port = port
31
- launch_cmd = [
39
+
40
+ launch_cmd: List[str] = []
41
+ if (
42
+ platform.system() == "Linux"
43
+ and os.environ.get("DISPLAY", False) is False
44
+ and shutil.which("xvfb-run")
45
+ ):
46
+ launch_cmd += ["xvfb-run", "--server-args='-screen 0, 1024x768x24'"]
47
+
48
+ launch_cmd += [
32
49
  chrome_bin,
33
50
  f"--remote-debugging-port={port}",
34
- f"--remote-allow-origins=http://localhost:{port}",
51
+ f"--remote-allow-origins=http://127.0.0.1:{port}",
35
52
  ]
53
+ if args:
54
+ launch_cmd += args
36
55
 
37
56
  # Adding --no-sandbox in Windows may cause Signal fail to launch
38
57
  # https://github.com/laggykiller/sticker-convert/issues/274
@@ -40,7 +59,7 @@ class CRD:
40
59
  platform.system() != "Windows"
41
60
  and "geteuid" in dir(os)
42
61
  and os.geteuid() == 0
43
- ):
62
+ ) or os.path.isfile("/.dockerenv"):
44
63
  launch_cmd.append("--no-sandbox")
45
64
 
46
65
  self.chrome_proc = subprocess.Popen(launch_cmd)
@@ -89,12 +108,17 @@ class CRD:
89
108
  return chrome_bin
90
109
  return None
91
110
 
92
- def connect(self):
111
+ def connect(self, target_id: int = 0):
93
112
  self.cmd_id = 1
94
113
  r = None
114
+ targets: List[Any] = []
95
115
  for _ in range(30):
96
116
  try:
97
- r = requests.get(f"http://localhost:{self.port}/json")
117
+ r = requests.get(f"http://127.0.0.1:{self.port}/json")
118
+ targets = json.loads(r.text)
119
+ if len(targets) == 0:
120
+ time.sleep(1)
121
+ continue
98
122
  break
99
123
  except requests.exceptions.ConnectionError:
100
124
  time.sleep(1)
@@ -102,17 +126,12 @@ class CRD:
102
126
  if r is None:
103
127
  raise RuntimeError("Cannot connect to chrome debugging port")
104
128
 
105
- targets = json.loads(r.text)
106
- for _ in range(30):
107
- if len(targets) == 0:
108
- time.sleep(1)
109
- else:
110
- break
111
-
112
129
  if len(targets) == 0:
113
130
  raise RuntimeError("Cannot create websocket connection with debugger")
114
131
 
115
- self.ws = websocket.create_connection(targets[0]["webSocketDebuggerUrl"]) # type: ignore
132
+ self.ws = websocket.create_connection( # type: ignore
133
+ targets[target_id]["webSocketDebuggerUrl"]
134
+ )
116
135
 
117
136
  def send_cmd(self, command: Dict[Any, Any]) -> Union[str, bytes]:
118
137
  if command.get("id") is None:
@@ -138,6 +157,27 @@ class CRD:
138
157
  command["params"]["contextId"] = context_id
139
158
  return self.send_cmd(command)
140
159
 
160
+ def set_transparent_bg(self) -> Union[str, bytes]:
161
+ command: Dict[str, Any] = {
162
+ "id": self.cmd_id,
163
+ "method": "Emulation.setDefaultBackgroundColorOverride",
164
+ "params": {"color": {"r": 0, "g": 0, "b": 0, "a": 0}},
165
+ }
166
+ return self.send_cmd(command)
167
+
168
+ def screenshot(self, clip: Optional[Dict[str, int]] = None):
169
+ command: Dict[str, Any] = {
170
+ "id": self.cmd_id,
171
+ "method": "Page.captureScreenshot",
172
+ "params": {},
173
+ }
174
+ if clip:
175
+ command["params"]["clip"] = clip
176
+ result = self.send_cmd(command)
177
+ return Image.open(
178
+ io.BytesIO(base64.b64decode(json.loads(result)["result"]["data"]))
179
+ )
180
+
141
181
  def get_curr_url(self) -> str:
142
182
  r = self.exec_js("window.location.href")
143
183
  return cast(
@@ -148,6 +188,26 @@ class CRD:
148
188
  command = {"id": self.cmd_id, "method": "Page.navigate", "params": {"url": url}}
149
189
  self.send_cmd(command)
150
190
 
191
+ def open_html_str(self, html: str):
192
+ command: Dict[str, Any] = {
193
+ "id": self.cmd_id,
194
+ "method": "Page.navigate",
195
+ "params": {"url": "about:blank"},
196
+ }
197
+ result = cast(str, self.send_cmd(command))
198
+ frame_id = json.loads(result).get("result", {}).get("frameId", None)
199
+ if frame_id is None:
200
+ raise RuntimeError(f"Cannot navigate to about:blank ({result})")
201
+
202
+ self.exec_js('document.getElementsByTagName("html")[0].remove()')
203
+
204
+ command = {
205
+ "id": self.cmd_id,
206
+ "method": "Page.setDocumentContent",
207
+ "params": {"frameId": frame_id, "html": html},
208
+ }
209
+ self.send_cmd(command)
210
+
151
211
  def runtime_enable(self):
152
212
  command = {
153
213
  "method": "Runtime.enable",
@@ -17,6 +17,7 @@ RELATED_EXTENSIONS = (
17
17
  ".tgs",
18
18
  ".lottie",
19
19
  ".json",
20
+ ".svg",
20
21
  ".mp4",
21
22
  ".mkv",
22
23
  ".mov",
@@ -3,16 +3,22 @@ from __future__ import annotations
3
3
 
4
4
  import json
5
5
  import mmap
6
+ import warnings
6
7
  from decimal import ROUND_HALF_UP, Decimal
7
8
  from fractions import Fraction
8
9
  from io import BytesIO
9
- from math import gcd
10
+ from math import ceil, gcd
10
11
  from pathlib import Path
11
12
  from typing import BinaryIO, List, Optional, Tuple, Union, cast
12
13
 
14
+ from bs4 import BeautifulSoup, XMLParsedAsHTMLWarning
13
15
  from PIL import Image, UnidentifiedImageError
14
16
  from rlottie_python.rlottie_wrapper import LottieAnimation
15
17
 
18
+ from sticker_convert.definitions import SVG_DEFAULT_HEIGHT, SVG_DEFAULT_WIDTH, SVG_SAMPLE_FPS
19
+
20
+ warnings.filterwarnings("ignore", category=XMLParsedAsHTMLWarning)
21
+
16
22
 
17
23
  def lcm(a: int, b: int):
18
24
  return abs(a * b) // gcd(a, b)
@@ -90,11 +96,17 @@ class CodecInfo:
90
96
  self.file_ext = CodecInfo.get_file_ext(file)
91
97
  else:
92
98
  self.file_ext = file_ext
93
- self.fps, self.frames, self.duration = CodecInfo.get_file_fps_frames_duration(
94
- file
95
- )
96
- self.codec = CodecInfo.get_file_codec(file)
97
- self.res = CodecInfo.get_file_res(file)
99
+ if self.file_ext == ".svg":
100
+ self.fps, self.frames, self.duration, self.res = CodecInfo.get_svg_info(
101
+ file
102
+ )
103
+ self.codec = "svg"
104
+ else:
105
+ self.fps, self.frames, self.duration = (
106
+ CodecInfo.get_file_fps_frames_duration(file)
107
+ )
108
+ self.codec = CodecInfo.get_file_codec(file)
109
+ self.res = CodecInfo.get_file_res(file)
98
110
  self.is_animated = self.fps > 1
99
111
 
100
112
  @staticmethod
@@ -435,3 +447,38 @@ class CodecInfo:
435
447
  if CodecInfo.get_file_frames(file, check_anim=True) > 1:
436
448
  return True
437
449
  return False
450
+
451
+ @staticmethod
452
+ def get_svg_info(
453
+ file: Union[Path, bytes],
454
+ ) -> Tuple[float, int, int, Tuple[int, int]]:
455
+ if isinstance(file, Path):
456
+ with open(file) as f:
457
+ svg = f.read()
458
+ else:
459
+ svg = file.decode()
460
+
461
+ soup = BeautifulSoup(svg, "html.parser")
462
+ svg_tag = soup.find_all("svg")[0]
463
+ width = int(svg_tag.get("width", SVG_DEFAULT_WIDTH))
464
+ height = int(svg_tag.get("height", SVG_DEFAULT_HEIGHT))
465
+
466
+ animate_elements = [*soup.find_all("animate")] + [
467
+ *soup.find_all("animateTransform")
468
+ ]
469
+ duration = 0
470
+ for element in animate_elements:
471
+ dur = cast(str, element.get("dur"))
472
+ if dur.endswith("s"):
473
+ duration = int(max(duration, float(dur[:-1]) * 1000))
474
+ elif dur.endswith("ms"):
475
+ duration = int(max(duration, float(dur[:-2])))
476
+
477
+ if duration != 0:
478
+ fps = SVG_SAMPLE_FPS
479
+ frames = ceil(fps * duration / 1000)
480
+ else:
481
+ fps = 0
482
+ frames = 1
483
+
484
+ return fps, frames, duration, (width, height)
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
- __version__ = "2.11.9"
3
+ __version__ = "2.12.1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sticker-convert
3
- Version: 2.11.9
3
+ Version: 2.12.1
4
4
  Summary: Convert (animated) stickers to/from WhatsApp, Telegram, Signal, Line, Kakao, Viber, Discord, iMessage. Written in Python.
5
5
  Author-email: laggykiller <chaudominic2@gmail.com>
6
6
  Maintainer-email: laggykiller <chaudominic2@gmail.com>
@@ -473,6 +473,34 @@ def test_download_viber_official_sticker_packs(tmp_path: LocalPath) -> None:
473
473
  )
474
474
 
475
475
 
476
+ @pytest.mark.skipif(not TEST_DOWNLOAD, reason="TEST_DOWNLOAD not set")
477
+ def test_download_viber_official_sticker_packs_sound(tmp_path: LocalPath) -> None:
478
+ _run_sticker_convert(
479
+ tmp_path=tmp_path,
480
+ source="viber",
481
+ url="https://stickers.viber.com/pages/onenationunderloveUA",
482
+ expected_file_count=24,
483
+ expected_file_formats=[".png", ".mp3"],
484
+ with_title=True,
485
+ with_author=False,
486
+ with_emoji=False,
487
+ )
488
+
489
+
490
+ @pytest.mark.skipif(not TEST_DOWNLOAD, reason="TEST_DOWNLOAD not set")
491
+ def test_download_viber_official_sticker_packs_animated(tmp_path: LocalPath) -> None:
492
+ _run_sticker_convert(
493
+ tmp_path=tmp_path,
494
+ source="viber",
495
+ url="https://stickers.viber.com/pages/earthday",
496
+ expected_file_count=32,
497
+ expected_file_formats=[".svg"],
498
+ with_title=True,
499
+ with_author=False,
500
+ with_emoji=False,
501
+ )
502
+
503
+
476
504
  @pytest.mark.skipif(not TEST_DOWNLOAD, reason="TEST_DOWNLOAD not set")
477
505
  def test_download_discord_stickers(tmp_path: LocalPath) -> None:
478
506
  _run_sticker_convert(