sticker-convert 2.8.12__py3-none-any.whl → 2.17.0.0__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 (124) hide show
  1. sticker_convert/__main__.py +24 -24
  2. sticker_convert/auth/__init__.py +0 -0
  3. sticker_convert/auth/auth_base.py +19 -0
  4. sticker_convert/auth/auth_discord.py +149 -0
  5. sticker_convert/{utils/auth/get_kakao_auth.py → auth/auth_kakao_android_login.py} +331 -300
  6. sticker_convert/auth/auth_kakao_desktop_login.py +327 -0
  7. sticker_convert/auth/auth_kakao_desktop_memdump.py +281 -0
  8. sticker_convert/{utils/auth/get_line_auth.py → auth/auth_line.py} +98 -80
  9. sticker_convert/auth/auth_signal.py +139 -0
  10. sticker_convert/auth/auth_telethon.py +161 -0
  11. sticker_convert/auth/auth_viber.py +250 -0
  12. sticker_convert/auth/telegram_api.py +736 -0
  13. sticker_convert/cli.py +623 -509
  14. sticker_convert/converter.py +1093 -962
  15. sticker_convert/definitions.py +11 -0
  16. sticker_convert/downloaders/download_band.py +111 -0
  17. sticker_convert/downloaders/download_base.py +171 -130
  18. sticker_convert/downloaders/download_discord.py +92 -0
  19. sticker_convert/downloaders/download_kakao.py +417 -255
  20. sticker_convert/downloaders/download_line.py +484 -472
  21. sticker_convert/downloaders/download_ogq.py +80 -0
  22. sticker_convert/downloaders/download_signal.py +108 -92
  23. sticker_convert/downloaders/download_telegram.py +56 -130
  24. sticker_convert/downloaders/download_viber.py +121 -95
  25. sticker_convert/gui.py +788 -795
  26. sticker_convert/gui_components/frames/comp_frame.py +180 -165
  27. sticker_convert/gui_components/frames/config_frame.py +156 -113
  28. sticker_convert/gui_components/frames/control_frame.py +32 -30
  29. sticker_convert/gui_components/frames/cred_frame.py +232 -162
  30. sticker_convert/gui_components/frames/input_frame.py +139 -137
  31. sticker_convert/gui_components/frames/output_frame.py +112 -110
  32. sticker_convert/gui_components/frames/right_clicker.py +25 -23
  33. sticker_convert/gui_components/windows/advanced_compression_window.py +757 -715
  34. sticker_convert/gui_components/windows/base_window.py +7 -2
  35. sticker_convert/gui_components/windows/discord_get_auth_window.py +79 -0
  36. sticker_convert/gui_components/windows/kakao_get_auth_window.py +511 -186
  37. sticker_convert/gui_components/windows/line_get_auth_window.py +94 -102
  38. sticker_convert/gui_components/windows/signal_get_auth_window.py +84 -135
  39. sticker_convert/gui_components/windows/viber_get_auth_window.py +168 -0
  40. sticker_convert/ios-message-stickers-template/.github/FUNDING.yml +3 -3
  41. sticker_convert/ios-message-stickers-template/.gitignore +0 -0
  42. sticker_convert/ios-message-stickers-template/README.md +10 -10
  43. sticker_convert/ios-message-stickers-template/stickers/Info.plist +43 -43
  44. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Info.plist +31 -31
  45. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Contents.json +6 -6
  46. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Contents.json +20 -20
  47. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Contents.json +9 -9
  48. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Sticker 1.png +0 -0
  49. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 2.sticker/Contents.json +9 -9
  50. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 2.sticker/Sticker 2.png +0 -0
  51. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 3.sticker/Contents.json +9 -9
  52. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 3.sticker/Sticker 3.png +0 -0
  53. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/App-Store-1024x1024pt.png +0 -0
  54. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Contents.json +91 -91
  55. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-App-Store-1024x768pt.png +0 -0
  56. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPad-67x50pt@2x.png +0 -0
  57. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPad-Pro-74x55pt@2x.png +0 -0
  58. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPhone-60x45pt@2x.png +0 -0
  59. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPhone-60x45pt@3x.png +0 -0
  60. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages27x20pt@2x.png +0 -0
  61. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages27x20pt@3x.png +0 -0
  62. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages32x24pt@2x.png +0 -0
  63. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages32x24pt@3x.png +0 -0
  64. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPad-Settings-29pt@2x.png +0 -0
  65. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPhone-Settings-29pt@3x.png +0 -0
  66. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPhone-settings-29pt@2x.png +0 -0
  67. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.pbxproj +364 -364
  68. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -7
  69. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -8
  70. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcuserdata/niklaspeterson.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  71. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/xcuserdata/niklaspeterson.xcuserdatad/xcschemes/xcschememanagement.plist +14 -14
  72. sticker_convert/job.py +279 -179
  73. sticker_convert/job_option.py +15 -2
  74. sticker_convert/locales/en_US/LC_MESSAGES/base.mo +0 -0
  75. sticker_convert/locales/ja_JP/LC_MESSAGES/base.mo +0 -0
  76. sticker_convert/locales/zh_CN/LC_MESSAGES/base.mo +0 -0
  77. sticker_convert/locales/zh_TW/LC_MESSAGES/base.mo +0 -0
  78. sticker_convert/py.typed +0 -0
  79. sticker_convert/resources/NotoColorEmoji.ttf +0 -0
  80. sticker_convert/resources/compression.json +220 -16
  81. sticker_convert/resources/emoji.json +527 -77
  82. sticker_convert/resources/help.ja_JP.json +88 -0
  83. sticker_convert/resources/help.json +24 -10
  84. sticker_convert/resources/help.zh_CN.json +88 -0
  85. sticker_convert/resources/help.zh_TW.json +88 -0
  86. sticker_convert/resources/input.ja_JP.json +74 -0
  87. sticker_convert/resources/input.json +121 -71
  88. sticker_convert/resources/input.zh_CN.json +74 -0
  89. sticker_convert/resources/input.zh_TW.json +74 -0
  90. sticker_convert/resources/memdump_linux.sh +25 -0
  91. sticker_convert/resources/memdump_windows.ps1 +8 -0
  92. sticker_convert/resources/output.ja_JP.json +38 -0
  93. sticker_convert/resources/output.json +24 -0
  94. sticker_convert/resources/output.zh_CN.json +38 -0
  95. sticker_convert/resources/output.zh_TW.json +38 -0
  96. sticker_convert/uploaders/compress_wastickers.py +186 -156
  97. sticker_convert/uploaders/upload_base.py +44 -35
  98. sticker_convert/uploaders/upload_signal.py +218 -173
  99. sticker_convert/uploaders/upload_telegram.py +353 -388
  100. sticker_convert/uploaders/upload_viber.py +178 -0
  101. sticker_convert/uploaders/xcode_imessage.py +295 -285
  102. sticker_convert/utils/callback.py +238 -6
  103. sticker_convert/utils/chrome_remotedebug.py +219 -0
  104. sticker_convert/utils/chromiums/linux.py +52 -0
  105. sticker_convert/utils/chromiums/osx.py +68 -0
  106. sticker_convert/utils/chromiums/windows.py +45 -0
  107. sticker_convert/utils/emoji.py +28 -0
  108. sticker_convert/utils/files/json_resources_loader.py +24 -19
  109. sticker_convert/utils/files/metadata_handler.py +8 -7
  110. sticker_convert/utils/files/run_bin.py +1 -1
  111. sticker_convert/utils/media/codec_info.py +99 -67
  112. sticker_convert/utils/media/format_verify.py +33 -20
  113. sticker_convert/utils/process.py +231 -0
  114. sticker_convert/utils/translate.py +108 -0
  115. sticker_convert/utils/url_detect.py +40 -33
  116. sticker_convert/version.py +1 -1
  117. {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info}/METADATA +189 -96
  118. sticker_convert-2.17.0.0.dist-info/RECORD +138 -0
  119. {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info}/WHEEL +1 -1
  120. sticker_convert/utils/auth/get_signal_auth.py +0 -129
  121. sticker_convert-2.8.12.dist-info/RECORD +0 -101
  122. {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info}/entry_points.txt +0 -0
  123. {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info/licenses}/LICENSE +0 -0
  124. {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env python3
2
+ from typing import Callable, List, Sequence, Set
3
+
4
+ from ugrapheme import graphemes # type: ignore
5
+
6
+ from sticker_convert.utils.files.json_resources_loader import load_resource_json
7
+
8
+ graphemes: Callable[[str], Sequence[str]] # type: ignore
9
+
10
+
11
+ # https://stackoverflow.com/a/480227
12
+ # Return list of unique items of list while preserve order
13
+ def uniques(seq: List[str]):
14
+ seen: Set[str] = set()
15
+ seen_add = seen.add
16
+ return [x for x in seq if not (x in seen or seen_add(x))]
17
+
18
+
19
+ def get_emoji_list() -> List[str]:
20
+ return [i["emoji"] for i in load_resource_json("emoji")]
21
+
22
+
23
+ EMOJI_LIST = get_emoji_list()
24
+
25
+
26
+ # https://stackoverflow.com/a/43146653
27
+ def extract_emojis(s: str) -> str:
28
+ return "".join(uniques(list(c for c in graphemes(s) if c in EMOJI_LIST)))
@@ -1,27 +1,32 @@
1
- from typing import Any, Dict
1
+ #!/usr/bin/env python3
2
+ from typing import Any, Dict, cast
2
3
 
3
4
  from mergedeep import merge # type: ignore
4
5
 
5
- from sticker_convert.definitions import CONFIG_DIR, ROOT_DIR
6
+ from sticker_convert.definitions import CONFIG_DIR, ROOT_DIR, RUNTIME_STATE
6
7
  from sticker_convert.utils.files.json_manager import JsonManager
7
8
 
8
9
 
9
- def _load_compression() -> Dict[Any, Any]:
10
- compression_json = JsonManager.load_json(ROOT_DIR / "resources/compression.json")
11
- custom_preset_json_path = CONFIG_DIR / "custom_preset.json"
12
- if custom_preset_json_path.exists():
13
- custom_preset_json = JsonManager.load_json(custom_preset_json_path)
14
- compression_json: Dict[Any, Any] = merge( # type: ignore
15
- compression_json, # type: ignore
16
- custom_preset_json,
17
- )
18
- return compression_json
10
+ def load_resource_json(json_name: str) -> Dict[Any, Any]:
11
+ if RUNTIME_STATE.get(f"{json_name}_json") is not None:
12
+ return cast(Dict[Any, Any], RUNTIME_STATE[f"{json_name}_json"])
13
+ else:
14
+ loaded_json = JsonManager.load_json(ROOT_DIR / f"resources/{json_name}.json")
19
15
 
16
+ json_to_merge = None
17
+ lang = RUNTIME_STATE["LANG"]
18
+ if lang != "en_US":
19
+ # Translated json
20
+ json_to_merge = ROOT_DIR / f"resources/{json_name}.{lang}.json"
21
+ if json_name == "compression":
22
+ # Custom preset json
23
+ json_to_merge = CONFIG_DIR / "custom_preset.json"
20
24
 
21
- HELP_JSON: Dict[str, Dict[str, str]] = JsonManager.load_json(
22
- ROOT_DIR / "resources/help.json"
23
- )
24
- INPUT_JSON = JsonManager.load_json(ROOT_DIR / "resources/input.json")
25
- COMPRESSION_JSON = _load_compression()
26
- OUTPUT_JSON = JsonManager.load_json(ROOT_DIR / "resources/output.json")
27
- EMOJI_JSON = JsonManager.load_json(ROOT_DIR / "resources/emoji.json")
25
+ if json_to_merge and json_to_merge.exists():
26
+ custom_preset_json = JsonManager.load_json(json_to_merge)
27
+ loaded_json: Dict[Any, Any] = merge( # type: ignore
28
+ loaded_json, # type: ignore
29
+ custom_preset_json,
30
+ )
31
+ RUNTIME_STATE[f"{json_name}_json"] = loaded_json
32
+ return loaded_json
@@ -5,7 +5,7 @@ import json
5
5
  from pathlib import Path
6
6
  from typing import Dict, List, Optional, Tuple
7
7
 
8
- from sticker_convert.utils.files.json_resources_loader import INPUT_JSON, OUTPUT_JSON
8
+ from sticker_convert.utils.files.json_resources_loader import load_resource_json
9
9
  from sticker_convert.utils.media.codec_info import CodecInfo
10
10
 
11
11
  RELATED_EXTENSIONS = (
@@ -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",
@@ -37,7 +38,7 @@ RELATED_NAME = (
37
38
  )
38
39
 
39
40
  BLACKLIST_PREFIX = ("cover",)
40
- BLACKLIST_SUFFIX = (".txt", ".m4a", ".wastickers", ".DS_Store", "._.DS_Store")
41
+ BLACKLIST_SUFFIX = (".txt", ".m4a", ".wastickers", ".zip", ".DS_Store", "._.DS_Store")
41
42
 
42
43
  XCODE_IMESSAGE_ICONSET = {
43
44
  "App-Store-1024x1024pt.png": (1024, 1024),
@@ -165,7 +166,7 @@ class MetadataHandler:
165
166
  Does not check if metadata provided via user input in GUI or flag options
166
167
  metadata = 'title' or 'author'
167
168
  """
168
- input_presets = INPUT_JSON
169
+ input_presets = load_resource_json("input")
169
170
  assert input_presets
170
171
 
171
172
  if input_option == "local":
@@ -184,7 +185,7 @@ class MetadataHandler:
184
185
  @staticmethod
185
186
  def check_metadata_required(output_option: str, metadata: str) -> bool:
186
187
  # metadata = 'title' or 'author'
187
- output_presets = OUTPUT_JSON
188
+ output_presets = load_resource_json("output")
188
189
  assert output_presets
189
190
  return output_presets[output_option]["metadata_requirements"][metadata]
190
191
 
@@ -264,7 +265,7 @@ class MetadataHandler:
264
265
  if len(anim_stickers) == file_per_anim_pack or (
265
266
  finished_all and len(anim_stickers) > 0
266
267
  ):
267
- suffix = f'{"-anim" if image_present else ""}{"-" + str(anim_pack_count) if anim_pack_count > 0 else ""}'
268
+ suffix = f"{'-anim' if image_present else ''}{'-' + str(anim_pack_count) if anim_pack_count > 0 else ''}"
268
269
  title_current = str(title) + suffix
269
270
  packs[title_current] = anim_stickers.copy()
270
271
  anim_stickers = []
@@ -272,7 +273,7 @@ class MetadataHandler:
272
273
  if len(image_stickers) == file_per_image_pack or (
273
274
  finished_all and len(image_stickers) > 0
274
275
  ):
275
- suffix = f'{"-image" if anim_present else ""}{"-" + str(image_pack_count) if image_pack_count > 0 else ""}'
276
+ suffix = f"{'-image' if anim_present else ''}{'-' + str(image_pack_count) if image_pack_count > 0 else ''}"
276
277
  title_current = str(title) + suffix
277
278
  packs[title_current] = image_stickers.copy()
278
279
  image_stickers = []
@@ -292,7 +293,7 @@ class MetadataHandler:
292
293
  if len(stickers) == file_per_pack or (
293
294
  finished_all and len(stickers) > 0
294
295
  ):
295
- suffix = f'{"-" + str(pack_count) if pack_count > 0 else ""}'
296
+ suffix = f"{'-' + str(pack_count) if pack_count > 0 else ''}"
296
297
  title_current = str(title) + suffix
297
298
  packs[title_current] = stickers.copy()
298
299
  stickers = []
@@ -14,7 +14,7 @@ class RunBin:
14
14
  if Path(executable).is_file():
15
15
  return executable
16
16
 
17
- if platform.system() == "Windows":
17
+ if platform.system() == "Windows" and executable.endswith(".exe") is False:
18
18
  executable = executable + ".exe"
19
19
 
20
20
  which_result = shutil.which(executable)
@@ -1,18 +1,26 @@
1
1
  #!/usr/bin/env python3
2
2
  from __future__ import annotations
3
3
 
4
+ import json
4
5
  import mmap
6
+ import warnings
5
7
  from decimal import ROUND_HALF_UP, Decimal
6
8
  from fractions import Fraction
7
9
  from io import BytesIO
8
- from math import gcd
10
+ from math import ceil, gcd
9
11
  from pathlib import Path
10
12
  from typing import BinaryIO, List, Optional, Tuple, Union, cast
11
13
 
14
+ from bs4 import BeautifulSoup, XMLParsedAsHTMLWarning
12
15
  from PIL import Image, UnidentifiedImageError
16
+ from rlottie_python.rlottie_wrapper import LottieAnimation
13
17
 
18
+ from sticker_convert.definitions import SVG_DEFAULT_HEIGHT, SVG_DEFAULT_WIDTH, SVG_SAMPLE_FPS
14
19
 
15
- def lcm(a: int, b: int):
20
+ warnings.filterwarnings("ignore", category=XMLParsedAsHTMLWarning)
21
+
22
+
23
+ def lcm(a: int, b: int) -> int:
16
24
  return abs(a * b) // gcd(a, b)
17
25
 
18
26
 
@@ -61,6 +69,24 @@ def durations_gcd(*durations: Union[int, float]) -> Union[float, Fraction]:
61
69
  return Fraction(gcd(*durations), 1) # type: ignore
62
70
 
63
71
 
72
+ def open_lottie(file: Union[Path, bytes]) -> LottieAnimation:
73
+ if isinstance(file, Path):
74
+ if file.suffix == ".tgs":
75
+ return LottieAnimation.from_tgs(file.as_posix())
76
+ else:
77
+ return LottieAnimation.from_file(file.as_posix())
78
+ else:
79
+ import gzip
80
+
81
+ try:
82
+ with gzip.open(BytesIO(file)) as f:
83
+ data = f.read().decode(encoding="utf-8")
84
+ except gzip.BadGzipFile:
85
+ data = json.loads(file.decode())
86
+
87
+ return LottieAnimation.from_data(data)
88
+
89
+
64
90
  class CodecInfo:
65
91
  def __init__(
66
92
  self, file: Union[Path, bytes], file_ext: Optional[str] = None
@@ -70,11 +96,17 @@ class CodecInfo:
70
96
  self.file_ext = CodecInfo.get_file_ext(file)
71
97
  else:
72
98
  self.file_ext = file_ext
73
- self.fps, self.frames, self.duration = CodecInfo.get_file_fps_frames_duration(
74
- file
75
- )
76
- self.codec = CodecInfo.get_file_codec(file)
77
- 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)
78
110
  self.is_animated = self.fps > 1
79
111
 
80
112
  @staticmethod
@@ -87,8 +119,8 @@ class CodecInfo:
87
119
  if not file_ext and isinstance(file, Path):
88
120
  file_ext = CodecInfo.get_file_ext(file)
89
121
 
90
- if file_ext == ".tgs":
91
- fps, frames = CodecInfo._get_file_fps_frames_tgs(file)
122
+ if file_ext in (".tgs", ".json", ".lottie"):
123
+ fps, frames = CodecInfo._get_file_fps_frames_lottie(file)
92
124
  if fps > 0:
93
125
  duration = int(frames / fps * 1000)
94
126
  else:
@@ -114,8 +146,8 @@ class CodecInfo:
114
146
  if not file_ext and isinstance(file, Path):
115
147
  file_ext = CodecInfo.get_file_ext(file)
116
148
 
117
- if file_ext == ".tgs":
118
- return CodecInfo._get_file_fps_tgs(file)
149
+ if file_ext in (".tgs", ".json", ".lottie"):
150
+ return CodecInfo._get_file_fps_lottie(file)
119
151
  elif file_ext == ".webp":
120
152
  fps, _, _, _ = CodecInfo._get_file_fps_frames_duration_webp(file)
121
153
  return fps
@@ -141,8 +173,8 @@ class CodecInfo:
141
173
  if not file_ext and isinstance(file, Path):
142
174
  file_ext = CodecInfo.get_file_ext(file)
143
175
 
144
- if file_ext == ".tgs":
145
- return CodecInfo._get_file_frames_tgs(file)
176
+ if file_ext in (".tgs", ".json", ".lottie"):
177
+ return CodecInfo._get_file_frames_lottie(file)
146
178
  if file_ext in (".gif", ".webp", ".png", ".apng"):
147
179
  _, frames, _ = CodecInfo._get_file_fps_frames_duration_pillow(
148
180
  file, frames_only=True
@@ -168,8 +200,8 @@ class CodecInfo:
168
200
  if not file_ext and isinstance(file, Path):
169
201
  file_ext = CodecInfo.get_file_ext(file)
170
202
 
171
- if file_ext == ".tgs":
172
- fps, frames = CodecInfo._get_file_fps_frames_tgs(file)
203
+ if file_ext in (".tgs", ".json", ".lottie"):
204
+ fps, frames = CodecInfo._get_file_fps_frames_lottie(file)
173
205
  if fps > 0:
174
206
  duration = int(frames / fps * 1000)
175
207
  else:
@@ -184,51 +216,25 @@ class CodecInfo:
184
216
  return duration
185
217
 
186
218
  @staticmethod
187
- def _get_file_fps_tgs(file: Union[Path, bytes]) -> int:
188
- from rlottie_python.rlottie_wrapper import LottieAnimation
219
+ def _get_file_fps_lottie(file: Union[Path, bytes]) -> int:
220
+ anim = open_lottie(file)
221
+ fps = anim.lottie_animation_get_framerate()
189
222
 
190
- if isinstance(file, Path):
191
- with LottieAnimation.from_tgs(file.as_posix()) as anim:
192
- return anim.lottie_animation_get_framerate()
193
- else:
194
- import gzip
195
-
196
- with gzip.open(BytesIO(file)) as f:
197
- data = f.read().decode(encoding="utf-8")
198
- with LottieAnimation.from_data(data) as anim:
199
- return anim.lottie_animation_get_framerate()
223
+ return fps
200
224
 
201
225
  @staticmethod
202
- def _get_file_frames_tgs(file: Union[Path, bytes]) -> int:
203
- from rlottie_python.rlottie_wrapper import LottieAnimation
204
-
205
- if isinstance(file, Path):
206
- with LottieAnimation.from_tgs(file.as_posix()) as anim:
207
- return anim.lottie_animation_get_totalframe()
208
- else:
209
- import gzip
226
+ def _get_file_frames_lottie(file: Union[Path, bytes]) -> int:
227
+ anim = open_lottie(file)
228
+ frames = anim.lottie_animation_get_totalframe()
210
229
 
211
- with gzip.open(BytesIO(file)) as f:
212
- data = f.read().decode(encoding="utf-8")
213
- with LottieAnimation.from_data(data) as anim:
214
- return anim.lottie_animation_get_totalframe()
230
+ return frames
215
231
 
216
232
  @staticmethod
217
- def _get_file_fps_frames_tgs(file: Union[Path, bytes]) -> Tuple[int, int]:
218
- from rlottie_python.rlottie_wrapper import LottieAnimation
219
-
220
- if isinstance(file, Path):
221
- with LottieAnimation.from_tgs(file.as_posix()) as anim:
222
- fps = anim.lottie_animation_get_framerate()
223
- frames = anim.lottie_animation_get_totalframe()
224
- else:
225
- import gzip
226
-
227
- with gzip.open(BytesIO(file)) as f:
228
- data = f.read().decode(encoding="utf-8")
229
- with LottieAnimation.from_data(data) as anim:
230
- fps = anim.lottie_animation_get_framerate()
231
- frames = anim.lottie_animation_get_totalframe()
233
+ def _get_file_fps_frames_lottie(file: Union[Path, bytes]) -> Tuple[int, int]:
234
+ anim = open_lottie(file)
235
+ fps = anim.lottie_animation_get_framerate()
236
+ frames = anim.lottie_animation_get_totalframe()
237
+ anim.lottie_animation_destroy()
232
238
 
233
239
  return fps, frames
234
240
 
@@ -408,19 +414,10 @@ class CodecInfo:
408
414
  if not file_ext and isinstance(file, Path):
409
415
  file_ext = CodecInfo.get_file_ext(file)
410
416
 
411
- if file_ext == ".tgs":
412
- from rlottie_python.rlottie_wrapper import LottieAnimation
413
-
414
- if isinstance(file, Path):
415
- with LottieAnimation.from_tgs(file.as_posix()) as anim:
416
- width, height = anim.lottie_animation_get_size()
417
- else:
418
- import gzip
419
-
420
- with gzip.open(BytesIO(file)) as f:
421
- data = f.read().decode(encoding="utf-8")
422
- with LottieAnimation.from_data(data) as anim:
423
- width, height = anim.lottie_animation_get_size()
417
+ if file_ext in (".tgs", ".json", ".lottie"):
418
+ anim = open_lottie(file)
419
+ width, height = anim.lottie_animation_get_size()
420
+ anim.lottie_animation_destroy()
424
421
  elif file_ext in (".webp", ".png", ".apng"):
425
422
  with Image.open(file) as im:
426
423
  width = im.width
@@ -450,3 +447,38 @@ class CodecInfo:
450
447
  if CodecInfo.get_file_frames(file, check_anim=True) > 1:
451
448
  return True
452
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,5 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  import os
3
+ from fractions import Fraction
3
4
  from pathlib import Path
4
5
  from typing import Optional, Tuple, Union
5
6
 
@@ -16,9 +17,7 @@ class FormatVerify:
16
17
  file_info = CodecInfo(file)
17
18
 
18
19
  return (
19
- FormatVerify.check_file_res(
20
- file, res=spec.get_res(), square=spec.square, file_info=file_info
21
- )
20
+ FormatVerify.check_file_res(file, res=spec.get_res(), file_info=file_info)
22
21
  and FormatVerify.check_file_fps(
23
22
  file, fps=spec.get_fps(), file_info=file_info
24
23
  )
@@ -48,7 +47,6 @@ class FormatVerify:
48
47
  res: Tuple[
49
48
  Tuple[Optional[int], Optional[int]], Tuple[Optional[int], Optional[int]]
50
49
  ],
51
- square: Optional[bool] = None,
52
50
  file_info: Optional[CodecInfo] = None,
53
51
  ) -> bool:
54
52
  if file_info:
@@ -65,8 +63,27 @@ class FormatVerify:
65
63
  return False
66
64
  if res[1][1] and file_height > res[1][1]:
67
65
  return False
68
- if square and file_height != file_width:
69
- return False
66
+
67
+ min_ratio = None
68
+ if res[0][0] and res[1][0]:
69
+ min_ratio = Fraction(res[0][0], res[1][0])
70
+
71
+ max_ratio = None
72
+ if res[0][1] and res[1][1]:
73
+ max_ratio = Fraction(res[0][1], res[1][1])
74
+
75
+ file_ratio = Fraction(file_width, file_height)
76
+
77
+ if min_ratio is not None and max_ratio is not None:
78
+ if min_ratio == max_ratio:
79
+ return True if file_ratio == min_ratio else False
80
+ else:
81
+ return True
82
+
83
+ if min_ratio is not None:
84
+ return True if file_ratio == min_ratio else False
85
+ if max_ratio is not None:
86
+ return True if file_ratio == max_ratio else False
70
87
 
71
88
  return True
72
89
 
@@ -124,20 +141,16 @@ class FormatVerify:
124
141
  else:
125
142
  file_animated = CodecInfo.is_anim(file)
126
143
 
127
- if (
128
- file_animated is True
129
- and size
130
- and size[1] is not None
131
- and file_size > size[1]
132
- ):
133
- return False
134
- if (
135
- file_animated is False
136
- and size
137
- and size[0] is not None
138
- and file_size > size[0]
139
- ):
140
- return False
144
+ if file_animated is True:
145
+ if not size[1]:
146
+ return True
147
+ elif file_size > size[1]:
148
+ return False
149
+ else:
150
+ if not size[0]:
151
+ return True
152
+ elif file_size > size[0]:
153
+ return False
141
154
 
142
155
  return True
143
156