sticker-convert 2.11.9__py3-none-any.whl → 2.12.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/converter.py +72 -0
- sticker_convert/definitions.py +7 -0
- sticker_convert/downloaders/download_kakao.py +9 -3
- sticker_convert/downloaders/download_viber.py +20 -6
- sticker_convert/ios-message-stickers-template/.github/FUNDING.yml +0 -0
- sticker_convert/ios-message-stickers-template/.gitignore +0 -0
- sticker_convert/ios-message-stickers-template/README.md +0 -0
- sticker_convert/ios-message-stickers-template/stickers/Info.plist +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Info.plist +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Contents.json +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Contents.json +0 -0
- sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Contents.json +0 -0
- 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 +0 -0
- 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 +0 -0
- 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 +0 -0
- 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 +0 -0
- sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -0
- sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -0
- 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 +0 -0
- sticker_convert/resources/emoji.json +0 -0
- sticker_convert/resources/help.json +0 -0
- sticker_convert/resources/input.json +0 -0
- sticker_convert/resources/memdump_windows.ps1 +0 -0
- sticker_convert/resources/output.json +0 -0
- sticker_convert/uploaders/upload_viber.py +13 -14
- sticker_convert/utils/chrome_remotedebug.py +78 -15
- sticker_convert/utils/files/metadata_handler.py +1 -0
- sticker_convert/utils/media/codec_info.py +53 -6
- sticker_convert/version.py +1 -1
- {sticker_convert-2.11.9.dist-info → sticker_convert-2.12.0.dist-info}/METADATA +1 -1
- {sticker_convert-2.11.9.dist-info → sticker_convert-2.12.0.dist-info}/RECORD +15 -15
- {sticker_convert-2.11.9.dist-info → sticker_convert-2.12.0.dist-info}/WHEEL +0 -0
- {sticker_convert-2.11.9.dist-info → sticker_convert-2.12.0.dist-info}/entry_points.txt +0 -0
- {sticker_convert-2.11.9.dist-info → sticker_convert-2.12.0.dist-info}/licenses/LICENSE +0 -0
- {sticker_convert-2.11.9.dist-info → sticker_convert-2.12.0.dist-info}/top_level.txt +0 -0
sticker_convert/converter.py
CHANGED
@@ -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
|
sticker_convert/definitions.py
CHANGED
@@ -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(
|
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,9 +138,13 @@ class DownloadKakao(DownloadBase):
|
|
136
138
|
|
137
139
|
if item_code is None:
|
138
140
|
if self.auth_token is None:
|
139
|
-
self.cb.put(
|
141
|
+
self.cb.put(
|
142
|
+
"Warning: Downloading animated sticker requires auth_token"
|
143
|
+
)
|
140
144
|
else:
|
141
|
-
self.cb.put(
|
145
|
+
self.cb.put(
|
146
|
+
"Warning: auth_token invalid, cannot download animated sticker"
|
147
|
+
)
|
142
148
|
self.cb.put("Downloading static stickers...")
|
143
149
|
self.download_static(thumbnail_urls)
|
144
150
|
else:
|
@@ -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(
|
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
|
-
|
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
|
-
|
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
|
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -72,23 +72,22 @@ class UploadViber(UploadBase):
|
|
72
72
|
separate_image_anim=False,
|
73
73
|
)
|
74
74
|
|
75
|
-
|
76
|
-
if
|
77
|
-
cover_path =
|
75
|
+
cover_path = MetadataHandler.get_cover(self.opt_output.dir)
|
76
|
+
if cover_path is None:
|
77
|
+
cover_path = MetadataHandler.get_stickers_present(self.opt_output.dir)[0]
|
78
|
+
|
79
|
+
if FormatVerify.check_file(cover_path, spec=self.png_cover_spec):
|
80
|
+
with open(cover_path, "rb") as f:
|
81
|
+
cover_bytes = f.read()
|
78
82
|
else:
|
79
|
-
|
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,
|
83
|
+
_, _, cover_bytes, _ = StickerConvert.convert( # type: ignore
|
87
84
|
cover_path,
|
85
|
+
Path("bytes.png"),
|
88
86
|
self.opt_comp_merged,
|
89
87
|
self.cb,
|
90
88
|
self.cb_return,
|
91
89
|
)
|
90
|
+
assert isinstance(cover_bytes, bytes)
|
92
91
|
|
93
92
|
stickers_total = 0
|
94
93
|
stickers_ok = 0
|
@@ -126,12 +125,12 @@ class UploadViber(UploadBase):
|
|
126
125
|
upload_data["description"] = author
|
127
126
|
upload_data["shareable"] = "1"
|
128
127
|
|
129
|
-
with open(out_f, "rb") as f
|
128
|
+
with open(out_f, "rb") as f:
|
130
129
|
r = requests.post(
|
131
130
|
"https://market.api.viber.com/2/users/custom-sticker-packs/create",
|
132
131
|
files={
|
133
|
-
"file": ("upload.zip", f),
|
134
|
-
"file_icon": ("color_icon.png",
|
132
|
+
"file": ("upload.zip", f.read()),
|
133
|
+
"file_icon": ("color_icon.png", cover_bytes),
|
135
134
|
},
|
136
135
|
data=upload_data,
|
137
136
|
)
|
@@ -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,32 @@ def get_free_port() -> int:
|
|
24
27
|
|
25
28
|
|
26
29
|
class CRD:
|
27
|
-
def __init__(
|
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
|
-
|
39
|
+
|
40
|
+
launch_cmd: List[str] = []
|
41
|
+
self.display = None
|
42
|
+
if (
|
43
|
+
platform.system() == "Linux"
|
44
|
+
and os.environ.get("DISPLAY", False) is False
|
45
|
+
and shutil.which("xvfb-run")
|
46
|
+
):
|
47
|
+
launch_cmd += ["xvfb-run", "--server-args='-screen 0, 1024x768x24'"]
|
48
|
+
|
49
|
+
launch_cmd += [
|
32
50
|
chrome_bin,
|
33
51
|
f"--remote-debugging-port={port}",
|
34
|
-
f"--remote-allow-origins=http://
|
52
|
+
f"--remote-allow-origins=http://127.0.0.1:{port}",
|
35
53
|
]
|
54
|
+
if args:
|
55
|
+
launch_cmd += args
|
36
56
|
|
37
57
|
# Adding --no-sandbox in Windows may cause Signal fail to launch
|
38
58
|
# https://github.com/laggykiller/sticker-convert/issues/274
|
@@ -40,7 +60,7 @@ class CRD:
|
|
40
60
|
platform.system() != "Windows"
|
41
61
|
and "geteuid" in dir(os)
|
42
62
|
and os.geteuid() == 0
|
43
|
-
):
|
63
|
+
) or os.path.isfile("/.dockerenv"):
|
44
64
|
launch_cmd.append("--no-sandbox")
|
45
65
|
|
46
66
|
self.chrome_proc = subprocess.Popen(launch_cmd)
|
@@ -89,12 +109,17 @@ class CRD:
|
|
89
109
|
return chrome_bin
|
90
110
|
return None
|
91
111
|
|
92
|
-
def connect(self):
|
112
|
+
def connect(self, target_id: int = 0):
|
93
113
|
self.cmd_id = 1
|
94
114
|
r = None
|
115
|
+
targets: List[Any] = []
|
95
116
|
for _ in range(30):
|
96
117
|
try:
|
97
|
-
r = requests.get(f"http://
|
118
|
+
r = requests.get(f"http://127.0.0.1:{self.port}/json")
|
119
|
+
targets = json.loads(r.text)
|
120
|
+
if len(targets) == 0:
|
121
|
+
time.sleep(1)
|
122
|
+
continue
|
98
123
|
break
|
99
124
|
except requests.exceptions.ConnectionError:
|
100
125
|
time.sleep(1)
|
@@ -102,17 +127,12 @@ class CRD:
|
|
102
127
|
if r is None:
|
103
128
|
raise RuntimeError("Cannot connect to chrome debugging port")
|
104
129
|
|
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
130
|
if len(targets) == 0:
|
113
131
|
raise RuntimeError("Cannot create websocket connection with debugger")
|
114
132
|
|
115
|
-
self.ws = websocket.create_connection(
|
133
|
+
self.ws = websocket.create_connection( # type: ignore
|
134
|
+
targets[target_id]["webSocketDebuggerUrl"]
|
135
|
+
)
|
116
136
|
|
117
137
|
def send_cmd(self, command: Dict[Any, Any]) -> Union[str, bytes]:
|
118
138
|
if command.get("id") is None:
|
@@ -138,6 +158,27 @@ class CRD:
|
|
138
158
|
command["params"]["contextId"] = context_id
|
139
159
|
return self.send_cmd(command)
|
140
160
|
|
161
|
+
def set_transparent_bg(self) -> Union[str, bytes]:
|
162
|
+
command: Dict[str, Any] = {
|
163
|
+
"id": self.cmd_id,
|
164
|
+
"method": "Emulation.setDefaultBackgroundColorOverride",
|
165
|
+
"params": {"color": {"r": 0, "g": 0, "b": 0, "a": 0}},
|
166
|
+
}
|
167
|
+
return self.send_cmd(command)
|
168
|
+
|
169
|
+
def screenshot(self, clip: Optional[Dict[str, int]] = None):
|
170
|
+
command: Dict[str, Any] = {
|
171
|
+
"id": self.cmd_id,
|
172
|
+
"method": "Page.captureScreenshot",
|
173
|
+
"params": {},
|
174
|
+
}
|
175
|
+
if clip:
|
176
|
+
command["params"]["clip"] = clip
|
177
|
+
result = self.send_cmd(command)
|
178
|
+
return Image.open(
|
179
|
+
io.BytesIO(base64.b64decode(json.loads(result)["result"]["data"]))
|
180
|
+
)
|
181
|
+
|
141
182
|
def get_curr_url(self) -> str:
|
142
183
|
r = self.exec_js("window.location.href")
|
143
184
|
return cast(
|
@@ -148,6 +189,26 @@ class CRD:
|
|
148
189
|
command = {"id": self.cmd_id, "method": "Page.navigate", "params": {"url": url}}
|
149
190
|
self.send_cmd(command)
|
150
191
|
|
192
|
+
def open_html_str(self, html: str):
|
193
|
+
command: Dict[str, Any] = {
|
194
|
+
"id": self.cmd_id,
|
195
|
+
"method": "Page.navigate",
|
196
|
+
"params": {"url": "about:blank"},
|
197
|
+
}
|
198
|
+
result = cast(str, self.send_cmd(command))
|
199
|
+
frame_id = json.loads(result).get("result", {}).get("frameId", None)
|
200
|
+
if frame_id is None:
|
201
|
+
raise RuntimeError(f"Cannot navigate to about:blank ({result})")
|
202
|
+
|
203
|
+
self.exec_js('document.getElementsByTagName("html")[0].remove()')
|
204
|
+
|
205
|
+
command = {
|
206
|
+
"id": self.cmd_id,
|
207
|
+
"method": "Page.setDocumentContent",
|
208
|
+
"params": {"frameId": frame_id, "html": html},
|
209
|
+
}
|
210
|
+
self.send_cmd(command)
|
211
|
+
|
151
212
|
def runtime_enable(self):
|
152
213
|
command = {
|
153
214
|
"method": "Runtime.enable",
|
@@ -169,3 +230,5 @@ class CRD:
|
|
169
230
|
def close(self):
|
170
231
|
self.ws.close()
|
171
232
|
self.chrome_proc.kill()
|
233
|
+
if self.display:
|
234
|
+
self.display.stop()
|
@@ -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
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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)
|
sticker_convert/version.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: sticker-convert
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.12.0
|
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,20 +1,20 @@
|
|
1
1
|
sticker_convert/__init__.py,sha256=iQnv6UOOA69c3soAn7ZOnAIubTIQSUxtq1Uhh8xRWvU,102
|
2
2
|
sticker_convert/__main__.py,sha256=elDCMvU27letiYs8jPUpxaCq5puURnvcDuqGsAAb6_w,592
|
3
3
|
sticker_convert/cli.py,sha256=MWzq9QJJ7HYBD73DmSgRIPo-u58mTGEIoUUpJKNjI0Q,22358
|
4
|
-
sticker_convert/converter.py,sha256=
|
5
|
-
sticker_convert/definitions.py,sha256=
|
4
|
+
sticker_convert/converter.py,sha256=Dnl5nkvSgIvfbcKp0n4ytoW6ZeHim5dNnrhAs3MZXx8,39423
|
5
|
+
sticker_convert/definitions.py,sha256=BqROmOvqIqw8ANaqZXIxJAXGD0HgjAvCfFouQ4SaWHc,2254
|
6
6
|
sticker_convert/gui.py,sha256=D9L-lFif4P4MANglis_wpjkzlUuGPuq8AybWGyPVP8E,33824
|
7
7
|
sticker_convert/job.py,sha256=gEd6yF8WPUR1_7q9SVhnwdD5usrDxwowNnR5q9uS8Yc,28011
|
8
8
|
sticker_convert/job_option.py,sha256=NysZtKVVG7EzRkLnVaeYJY0uNlYKFmjoststB1cvVrY,8020
|
9
|
-
sticker_convert/version.py,sha256=
|
9
|
+
sticker_convert/version.py,sha256=5PwAcytiNCTA-jXWUYxsOwxsKuNWAlGiQx6_QL36xdI,47
|
10
10
|
sticker_convert/downloaders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
11
|
sticker_convert/downloaders/download_base.py,sha256=MI5pCT_tkfoaFlrD1oNynDj1Rv1CK0APuNVElTEAEis,5110
|
12
12
|
sticker_convert/downloaders/download_discord.py,sha256=6AFpLAYL2hRvVcsqUtzDUC31U66U02ZcnKXDnZRi2jk,3496
|
13
|
-
sticker_convert/downloaders/download_kakao.py,sha256=
|
13
|
+
sticker_convert/downloaders/download_kakao.py,sha256=kcWwyid76Db7QR8MqhHJuYONnIY9zOHRUBcNcae1VYI,12465
|
14
14
|
sticker_convert/downloaders/download_line.py,sha256=c-hTzEarGQP0b-pnPl29NuSKcaZWUeRo_94YpJzz72M,17911
|
15
15
|
sticker_convert/downloaders/download_signal.py,sha256=3wv-BLd4qggly4AdtwV8f3vUpCVZ-8GnoPLoWngY3Pk,3728
|
16
16
|
sticker_convert/downloaders/download_telegram.py,sha256=iGmOcVjU2Ai19t1lyDY2JhQIc--O5XMyNCGXZAA4DVY,2028
|
17
|
-
sticker_convert/downloaders/download_viber.py,sha256=
|
17
|
+
sticker_convert/downloaders/download_viber.py,sha256=ApHy4sDUOmVwplHfLbso_gyOKn9zccqvRnHENzR_UUM,3790
|
18
18
|
sticker_convert/gui_components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
19
|
sticker_convert/gui_components/gui_utils.py,sha256=okho2cA1Scem_m6rPiYifreFzpFrM21-yUkiAv64EUI,3431
|
20
20
|
sticker_convert/gui_components/frames/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -82,10 +82,10 @@ sticker_convert/uploaders/compress_wastickers.py,sha256=Rgl3WzIDJn7oa00nNqeBFDqI
|
|
82
82
|
sticker_convert/uploaders/upload_base.py,sha256=uQupPn6r4zrlAzpKzzX7CgvZb69ATyrwPKahWOQj0ds,1203
|
83
83
|
sticker_convert/uploaders/upload_signal.py,sha256=k9XS6oU0gYCXEph-LzWvUP3IDINAVaQzzzNgDRp_YNM,7443
|
84
84
|
sticker_convert/uploaders/upload_telegram.py,sha256=WlUyLJlW83XZz6RhA76jHMXA6TNUIEVbPwhi14RTnds,12482
|
85
|
-
sticker_convert/uploaders/upload_viber.py,sha256=
|
85
|
+
sticker_convert/uploaders/upload_viber.py,sha256=E5gXxPF22TyfSyOEe1uxnbonCMgH8ma3WTm0ecJ3yxE,6444
|
86
86
|
sticker_convert/uploaders/xcode_imessage.py,sha256=iTTT8gDYOTNkKqXeSWUBuWfxu7xeE418t2Z1YQFR5L0,11365
|
87
87
|
sticker_convert/utils/callback.py,sha256=spYUGlklOs1yPZAxoqwOWgR1sdimpfM8a27if3TaVYk,6155
|
88
|
-
sticker_convert/utils/chrome_remotedebug.py,sha256=
|
88
|
+
sticker_convert/utils/chrome_remotedebug.py,sha256=jxqH2LktA9UVJW055QeHecVRkc8Hl7KlUcxi_srhDXk,7143
|
89
89
|
sticker_convert/utils/emoji.py,sha256=AqB26JY-PkYzNwPLReSnqLiQKe-bR9UXnLclAbgubJ8,367
|
90
90
|
sticker_convert/utils/process.py,sha256=EAQZ9WpiKmkvToIv8G1HNY4V7m0jXyyePTmeP2XOZzE,4688
|
91
91
|
sticker_convert/utils/url_detect.py,sha256=vCbhQbcW1X_UtdfQlICGY8pMX34KQ6sCtDJZbp0NrSg,876
|
@@ -100,16 +100,16 @@ sticker_convert/utils/auth/telethon_setup.py,sha256=3hH0KglsotFBxT3gwFjHiDuREyah
|
|
100
100
|
sticker_convert/utils/files/cache_store.py,sha256=etfe614OAhAyrnM5fGeESKq6R88YLNqkqkxSzEmZ0V0,1047
|
101
101
|
sticker_convert/utils/files/json_manager.py,sha256=Vr6pZJdLMkrJJWN99210aduVHb0ILyf0SSTaw4TZqgc,541
|
102
102
|
sticker_convert/utils/files/json_resources_loader.py,sha256=flZFixUXRTrOAhvRQpuSQgmJ69yXL94sxukcowLT1JQ,1049
|
103
|
-
sticker_convert/utils/files/metadata_handler.py,sha256=
|
103
|
+
sticker_convert/utils/files/metadata_handler.py,sha256=TUseSpKsFLoqqFf7YFENzkwDYSDKPiMCoEp04ebnVr8,10120
|
104
104
|
sticker_convert/utils/files/run_bin.py,sha256=C_KKGtMUTajJnKo4Ia9e6WuWCXSQRdGsVPG-r5GXT_I,1784
|
105
105
|
sticker_convert/utils/files/sanitize_filename.py,sha256=HBklPGsHRJjFQUIC5rYTQsUrsuTtezZXIEA8CPhLP8A,2156
|
106
106
|
sticker_convert/utils/media/apple_png_normalize.py,sha256=LbrQhc7LlYX4I9ek4XJsZE4l0MygBA1jB-PFiYLEkzk,3657
|
107
|
-
sticker_convert/utils/media/codec_info.py,sha256=
|
107
|
+
sticker_convert/utils/media/codec_info.py,sha256=_F_iV3k2GYtb8vYZUSKrWl9E7XEW6LDywbnJ-DM6kbk,16476
|
108
108
|
sticker_convert/utils/media/decrypt_kakao.py,sha256=4wq9ZDRnFkx1WmFZnyEogBofiLGsWQM_X69HlA36578,1947
|
109
109
|
sticker_convert/utils/media/format_verify.py,sha256=oM32P186tWe9YxvBQRPr8D3FEmBN3b2rEe_2S_MwxyQ,6236
|
110
|
-
sticker_convert-2.
|
111
|
-
sticker_convert-2.
|
112
|
-
sticker_convert-2.
|
113
|
-
sticker_convert-2.
|
114
|
-
sticker_convert-2.
|
115
|
-
sticker_convert-2.
|
110
|
+
sticker_convert-2.12.0.dist-info/licenses/LICENSE,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
|
111
|
+
sticker_convert-2.12.0.dist-info/METADATA,sha256=KBvqYSm9HFUCkPrFOqZ3RDVKCI6916VpywSKT1JvwsE,53484
|
112
|
+
sticker_convert-2.12.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
113
|
+
sticker_convert-2.12.0.dist-info/entry_points.txt,sha256=MNJ7XyC--ugxi5jS1nzjDLGnxCyLuaGdsVLnJhDHCqs,66
|
114
|
+
sticker_convert-2.12.0.dist-info/top_level.txt,sha256=r9vfnB0l1ZnH5pTH5RvkobnK3Ow9m0RsncaOMAtiAtk,16
|
115
|
+
sticker_convert-2.12.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|