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.
- sticker_convert/__main__.py +24 -24
- sticker_convert/auth/__init__.py +0 -0
- sticker_convert/auth/auth_base.py +19 -0
- sticker_convert/auth/auth_discord.py +149 -0
- sticker_convert/{utils/auth/get_kakao_auth.py → auth/auth_kakao_android_login.py} +331 -300
- sticker_convert/auth/auth_kakao_desktop_login.py +327 -0
- sticker_convert/auth/auth_kakao_desktop_memdump.py +281 -0
- sticker_convert/{utils/auth/get_line_auth.py → auth/auth_line.py} +98 -80
- sticker_convert/auth/auth_signal.py +139 -0
- sticker_convert/auth/auth_telethon.py +161 -0
- sticker_convert/auth/auth_viber.py +250 -0
- sticker_convert/auth/telegram_api.py +736 -0
- sticker_convert/cli.py +623 -509
- sticker_convert/converter.py +1093 -962
- sticker_convert/definitions.py +11 -0
- sticker_convert/downloaders/download_band.py +111 -0
- sticker_convert/downloaders/download_base.py +171 -130
- sticker_convert/downloaders/download_discord.py +92 -0
- sticker_convert/downloaders/download_kakao.py +417 -255
- sticker_convert/downloaders/download_line.py +484 -472
- sticker_convert/downloaders/download_ogq.py +80 -0
- sticker_convert/downloaders/download_signal.py +108 -92
- sticker_convert/downloaders/download_telegram.py +56 -130
- sticker_convert/downloaders/download_viber.py +121 -95
- sticker_convert/gui.py +788 -795
- sticker_convert/gui_components/frames/comp_frame.py +180 -165
- sticker_convert/gui_components/frames/config_frame.py +156 -113
- sticker_convert/gui_components/frames/control_frame.py +32 -30
- sticker_convert/gui_components/frames/cred_frame.py +232 -162
- sticker_convert/gui_components/frames/input_frame.py +139 -137
- sticker_convert/gui_components/frames/output_frame.py +112 -110
- sticker_convert/gui_components/frames/right_clicker.py +25 -23
- sticker_convert/gui_components/windows/advanced_compression_window.py +757 -715
- sticker_convert/gui_components/windows/base_window.py +7 -2
- sticker_convert/gui_components/windows/discord_get_auth_window.py +79 -0
- sticker_convert/gui_components/windows/kakao_get_auth_window.py +511 -186
- sticker_convert/gui_components/windows/line_get_auth_window.py +94 -102
- sticker_convert/gui_components/windows/signal_get_auth_window.py +84 -135
- sticker_convert/gui_components/windows/viber_get_auth_window.py +168 -0
- sticker_convert/ios-message-stickers-template/.github/FUNDING.yml +3 -3
- sticker_convert/ios-message-stickers-template/.gitignore +0 -0
- sticker_convert/ios-message-stickers-template/README.md +10 -10
- sticker_convert/ios-message-stickers-template/stickers/Info.plist +43 -43
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Info.plist +31 -31
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Contents.json +6 -6
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Contents.json +20 -20
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Contents.json +9 -9
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Sticker 1.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 2.sticker/Contents.json +9 -9
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 2.sticker/Sticker 2.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 3.sticker/Contents.json +9 -9
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 3.sticker/Sticker 3.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/App-Store-1024x1024pt.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Contents.json +91 -91
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-App-Store-1024x768pt.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPad-67x50pt@2x.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPad-Pro-74x55pt@2x.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPhone-60x45pt@2x.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPhone-60x45pt@3x.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages27x20pt@2x.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages27x20pt@3x.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages32x24pt@2x.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages32x24pt@3x.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPad-Settings-29pt@2x.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPhone-Settings-29pt@3x.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPhone-settings-29pt@2x.png +0 -0
- sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.pbxproj +364 -364
- sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -7
- sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -8
- sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcuserdata/niklaspeterson.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- sticker_convert/ios-message-stickers-template/stickers.xcodeproj/xcuserdata/niklaspeterson.xcuserdatad/xcschemes/xcschememanagement.plist +14 -14
- sticker_convert/job.py +279 -179
- sticker_convert/job_option.py +15 -2
- sticker_convert/locales/en_US/LC_MESSAGES/base.mo +0 -0
- sticker_convert/locales/ja_JP/LC_MESSAGES/base.mo +0 -0
- sticker_convert/locales/zh_CN/LC_MESSAGES/base.mo +0 -0
- sticker_convert/locales/zh_TW/LC_MESSAGES/base.mo +0 -0
- sticker_convert/py.typed +0 -0
- sticker_convert/resources/NotoColorEmoji.ttf +0 -0
- sticker_convert/resources/compression.json +220 -16
- sticker_convert/resources/emoji.json +527 -77
- sticker_convert/resources/help.ja_JP.json +88 -0
- sticker_convert/resources/help.json +24 -10
- sticker_convert/resources/help.zh_CN.json +88 -0
- sticker_convert/resources/help.zh_TW.json +88 -0
- sticker_convert/resources/input.ja_JP.json +74 -0
- sticker_convert/resources/input.json +121 -71
- sticker_convert/resources/input.zh_CN.json +74 -0
- sticker_convert/resources/input.zh_TW.json +74 -0
- sticker_convert/resources/memdump_linux.sh +25 -0
- sticker_convert/resources/memdump_windows.ps1 +8 -0
- sticker_convert/resources/output.ja_JP.json +38 -0
- sticker_convert/resources/output.json +24 -0
- sticker_convert/resources/output.zh_CN.json +38 -0
- sticker_convert/resources/output.zh_TW.json +38 -0
- sticker_convert/uploaders/compress_wastickers.py +186 -156
- sticker_convert/uploaders/upload_base.py +44 -35
- sticker_convert/uploaders/upload_signal.py +218 -173
- sticker_convert/uploaders/upload_telegram.py +353 -388
- sticker_convert/uploaders/upload_viber.py +178 -0
- sticker_convert/uploaders/xcode_imessage.py +295 -285
- sticker_convert/utils/callback.py +238 -6
- sticker_convert/utils/chrome_remotedebug.py +219 -0
- sticker_convert/utils/chromiums/linux.py +52 -0
- sticker_convert/utils/chromiums/osx.py +68 -0
- sticker_convert/utils/chromiums/windows.py +45 -0
- sticker_convert/utils/emoji.py +28 -0
- sticker_convert/utils/files/json_resources_loader.py +24 -19
- sticker_convert/utils/files/metadata_handler.py +8 -7
- sticker_convert/utils/files/run_bin.py +1 -1
- sticker_convert/utils/media/codec_info.py +99 -67
- sticker_convert/utils/media/format_verify.py +33 -20
- sticker_convert/utils/process.py +231 -0
- sticker_convert/utils/translate.py +108 -0
- sticker_convert/utils/url_detect.py +40 -33
- sticker_convert/version.py +1 -1
- {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info}/METADATA +189 -96
- sticker_convert-2.17.0.0.dist-info/RECORD +138 -0
- {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info}/WHEEL +1 -1
- sticker_convert/utils/auth/get_signal_auth.py +0 -129
- sticker_convert-2.8.12.dist-info/RECORD +0 -101
- {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info}/entry_points.txt +0 -0
- {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info/licenses}/LICENSE +0 -0
- {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
|
-
|
|
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
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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'
|
|
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'
|
|
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'
|
|
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
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
91
|
-
fps, frames = CodecInfo.
|
|
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
|
|
118
|
-
return CodecInfo.
|
|
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
|
|
145
|
-
return CodecInfo.
|
|
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
|
|
172
|
-
fps, frames = CodecInfo.
|
|
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
|
|
188
|
-
|
|
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
|
-
|
|
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
|
|
203
|
-
|
|
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
|
-
|
|
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
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
-
|
|
69
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|