sticker-convert 2.7.3__py3-none-any.whl → 2.7.4__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/cli.py +12 -16
- sticker_convert/converter.py +46 -50
- sticker_convert/definitions.py +8 -12
- sticker_convert/downloaders/download_base.py +13 -30
- sticker_convert/downloaders/download_kakao.py +19 -25
- sticker_convert/downloaders/download_line.py +7 -8
- sticker_convert/downloaders/download_signal.py +4 -5
- sticker_convert/downloaders/download_telegram.py +4 -5
- sticker_convert/gui.py +28 -29
- sticker_convert/gui_components/frames/comp_frame.py +7 -7
- sticker_convert/gui_components/frames/config_frame.py +7 -7
- sticker_convert/gui_components/frames/control_frame.py +1 -1
- sticker_convert/gui_components/frames/cred_frame.py +8 -8
- sticker_convert/gui_components/frames/input_frame.py +6 -6
- sticker_convert/gui_components/frames/output_frame.py +6 -6
- sticker_convert/gui_components/frames/progress_frame.py +6 -6
- sticker_convert/gui_components/gui_utils.py +2 -4
- sticker_convert/gui_components/windows/advanced_compression_window.py +13 -11
- sticker_convert/gui_components/windows/base_window.py +3 -3
- sticker_convert/gui_components/windows/kakao_get_auth_window.py +2 -2
- sticker_convert/gui_components/windows/line_get_auth_window.py +2 -2
- sticker_convert/gui_components/windows/signal_get_auth_window.py +1 -1
- sticker_convert/job.py +66 -75
- sticker_convert/job_option.py +1 -1
- sticker_convert/resources/help.json +1 -1
- sticker_convert/uploaders/compress_wastickers.py +3 -3
- sticker_convert/uploaders/upload_base.py +2 -3
- sticker_convert/uploaders/upload_signal.py +5 -5
- sticker_convert/uploaders/upload_telegram.py +5 -4
- sticker_convert/uploaders/xcode_imessage.py +14 -69
- sticker_convert/utils/auth/get_kakao_auth.py +4 -5
- sticker_convert/utils/auth/get_line_auth.py +1 -2
- sticker_convert/utils/auth/get_signal_auth.py +4 -4
- sticker_convert/utils/callback.py +25 -17
- sticker_convert/utils/files/cache_store.py +4 -6
- sticker_convert/utils/files/json_manager.py +3 -4
- sticker_convert/utils/files/json_resources_loader.py +12 -0
- sticker_convert/utils/files/metadata_handler.py +83 -74
- sticker_convert/utils/files/run_bin.py +8 -7
- sticker_convert/utils/files/sanitize_filename.py +30 -28
- sticker_convert/utils/media/apple_png_normalize.py +2 -2
- sticker_convert/utils/media/codec_info.py +22 -29
- sticker_convert/utils/media/decrypt_kakao.py +3 -5
- sticker_convert/utils/media/format_verify.py +6 -8
- sticker_convert/utils/url_detect.py +4 -5
- sticker_convert/version.py +1 -1
- {sticker_convert-2.7.3.dist-info → sticker_convert-2.7.4.dist-info}/METADATA +16 -14
- {sticker_convert-2.7.3.dist-info → sticker_convert-2.7.4.dist-info}/RECORD +52 -51
- {sticker_convert-2.7.3.dist-info → sticker_convert-2.7.4.dist-info}/WHEEL +1 -1
- {sticker_convert-2.7.3.dist-info → sticker_convert-2.7.4.dist-info}/LICENSE +0 -0
- {sticker_convert-2.7.3.dist-info → sticker_convert-2.7.4.dist-info}/entry_points.txt +0 -0
- {sticker_convert-2.7.3.dist-info → sticker_convert-2.7.4.dist-info}/top_level.txt +0 -0
sticker_convert/cli.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
import argparse
|
3
3
|
import signal
|
4
|
+
import sys
|
4
5
|
from argparse import Namespace
|
5
6
|
from json.decoder import JSONDecodeError
|
6
7
|
from math import ceil
|
@@ -8,7 +9,7 @@ from multiprocessing import cpu_count
|
|
8
9
|
from pathlib import Path
|
9
10
|
from typing import Any, Dict
|
10
11
|
|
11
|
-
from sticker_convert.definitions import CONFIG_DIR, DEFAULT_DIR
|
12
|
+
from sticker_convert.definitions import CONFIG_DIR, DEFAULT_DIR
|
12
13
|
from sticker_convert.job import Job
|
13
14
|
from sticker_convert.job_option import CompOption, CredOption, InputOption, OutputOption
|
14
15
|
from sticker_convert.utils.auth.get_kakao_auth import GetKakaoAuth
|
@@ -26,22 +27,17 @@ class CLI:
|
|
26
27
|
|
27
28
|
def cli(self) -> None:
|
28
29
|
try:
|
29
|
-
|
30
|
-
ROOT_DIR / "resources/help.json"
|
31
|
-
)
|
32
|
-
self.input_presets = JsonManager.load_json(
|
33
|
-
ROOT_DIR / "resources/input.json"
|
34
|
-
)
|
35
|
-
self.compression_presets = JsonManager.load_json(
|
36
|
-
ROOT_DIR / "resources/compression.json"
|
37
|
-
)
|
38
|
-
self.output_presets = JsonManager.load_json(
|
39
|
-
ROOT_DIR / "resources/output.json"
|
40
|
-
)
|
30
|
+
from sticker_convert.utils.files.json_resources_loader import COMPRESSION_JSON, EMOJI_JSON, HELP_JSON, INPUT_JSON, OUTPUT_JSON
|
41
31
|
except RuntimeError as e:
|
42
|
-
self.cb.msg(e
|
32
|
+
self.cb.msg(str(e))
|
43
33
|
return
|
44
34
|
|
35
|
+
self.help = HELP_JSON
|
36
|
+
self.input_presets = INPUT_JSON
|
37
|
+
self.compression_presets = COMPRESSION_JSON
|
38
|
+
self.output_presets = OUTPUT_JSON
|
39
|
+
self.emoji_list = EMOJI_JSON
|
40
|
+
|
45
41
|
parser = argparse.ArgumentParser(
|
46
42
|
description="CLI for stickers-convert",
|
47
43
|
formatter_class=argparse.RawTextHelpFormatter,
|
@@ -186,7 +182,7 @@ class CLI:
|
|
186
182
|
|
187
183
|
signal.signal(signal.SIGINT, job.cancel)
|
188
184
|
status = job.start()
|
189
|
-
exit(status)
|
185
|
+
sys.exit(status)
|
190
186
|
|
191
187
|
def get_opt_input(self, args: Namespace) -> InputOption:
|
192
188
|
download_options = {
|
@@ -212,7 +208,7 @@ class CLI:
|
|
212
208
|
self.cb.msg(f"Detected URL input source: {download_option}")
|
213
209
|
else:
|
214
210
|
self.cb.msg(f"Error: Unrecognied URL input source for url: {url}")
|
215
|
-
exit()
|
211
|
+
sys.exit()
|
216
212
|
|
217
213
|
opt_input = InputOption(
|
218
214
|
option=download_option,
|
sticker_convert/converter.py
CHANGED
@@ -6,25 +6,32 @@ from io import BytesIO
|
|
6
6
|
from math import ceil, floor
|
7
7
|
from pathlib import Path
|
8
8
|
from queue import Queue
|
9
|
-
from typing import TYPE_CHECKING, Any,
|
9
|
+
from typing import TYPE_CHECKING, Any, List, Literal, Optional, Tuple, Union, cast
|
10
10
|
|
11
11
|
import numpy as np
|
12
12
|
from PIL import Image
|
13
13
|
|
14
|
-
if TYPE_CHECKING:
|
15
|
-
from av.video.plane import VideoPlane # type: ignore
|
16
|
-
|
17
14
|
from sticker_convert.job_option import CompOption
|
18
|
-
from sticker_convert.utils.callback import Callback, CallbackReturn
|
15
|
+
from sticker_convert.utils.callback import Callback, CallbackReturn, CbQueueItemType
|
19
16
|
from sticker_convert.utils.files.cache_store import CacheStore
|
20
17
|
from sticker_convert.utils.media.codec_info import CodecInfo
|
21
18
|
from sticker_convert.utils.media.format_verify import FormatVerify
|
22
19
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
]
|
20
|
+
if TYPE_CHECKING:
|
21
|
+
from av.video.plane import VideoPlane # type: ignore
|
22
|
+
|
23
|
+
MSG_START_COMP = "[I] Start compressing {} -> {}"
|
24
|
+
MSG_SKIP_COMP = "[S] Compatible file found, skip compress and just copy {} -> {}"
|
25
|
+
MSG_COMP = (
|
26
|
+
"[C] Compressing {} -> {} res={}x{}, "
|
27
|
+
"quality={}, fps={}, color={} (step {}-{}-{})"
|
28
|
+
)
|
29
|
+
MSG_REDO_COMP = "[{}] Compressed {} -> {} but size {} {} limit {}, recompressing"
|
30
|
+
MSG_DONE_COMP = "[S] Successful compression {} -> {} size {} (step {})"
|
31
|
+
MSG_FAIL_COMP = (
|
32
|
+
"[F] Failed Compression {} -> {}, "
|
33
|
+
"cannot get below limit {} with lowest quality under current settings (Best size: {})"
|
34
|
+
)
|
28
35
|
|
29
36
|
|
30
37
|
def rounding(value: float) -> Decimal:
|
@@ -32,8 +39,8 @@ def rounding(value: float) -> Decimal:
|
|
32
39
|
|
33
40
|
|
34
41
|
def get_step_value(
|
35
|
-
|
36
|
-
|
42
|
+
max_step: Optional[int],
|
43
|
+
min_step: Optional[int],
|
37
44
|
step: int,
|
38
45
|
steps: int,
|
39
46
|
power: float = 1.0,
|
@@ -49,14 +56,12 @@ def get_step_value(
|
|
49
56
|
else:
|
50
57
|
factor = 0
|
51
58
|
|
52
|
-
if
|
53
|
-
v = round((
|
59
|
+
if max_step is not None and min_step is not None:
|
60
|
+
v = round((max_step - min_step) * step / steps * factor + min_step)
|
54
61
|
if even is True and v % 2 == 1:
|
55
62
|
return v + 1
|
56
|
-
|
57
|
-
|
58
|
-
else:
|
59
|
-
return None
|
63
|
+
return v
|
64
|
+
return None
|
60
65
|
|
61
66
|
|
62
67
|
def useful_array(
|
@@ -71,19 +76,6 @@ def useful_array(
|
|
71
76
|
|
72
77
|
|
73
78
|
class StickerConvert:
|
74
|
-
MSG_START_COMP = "[I] Start compressing {} -> {}"
|
75
|
-
MSG_SKIP_COMP = "[S] Compatible file found, skip compress and just copy {} -> {}"
|
76
|
-
MSG_COMP = (
|
77
|
-
"[C] Compressing {} -> {} res={}x{}, "
|
78
|
-
"quality={}, fps={}, color={} (step {}-{}-{})"
|
79
|
-
)
|
80
|
-
MSG_REDO_COMP = "[{}] Compressed {} -> {} but size {} {} limit {}, recompressing"
|
81
|
-
MSG_DONE_COMP = "[S] Successful compression {} -> {} size {} (step {})"
|
82
|
-
MSG_FAIL_COMP = (
|
83
|
-
"[F] Failed Compression {} -> {}, "
|
84
|
-
"cannot get below limit {} with lowest quality under current settings (Best size: {})"
|
85
|
-
)
|
86
|
-
|
87
79
|
def __init__(
|
88
80
|
self,
|
89
81
|
in_f: Union[Path, Tuple[Path, bytes]],
|
@@ -91,7 +83,7 @@ class StickerConvert:
|
|
91
83
|
opt_comp: CompOption,
|
92
84
|
cb: "Union[Queue[CbQueueItemType], Callback]",
|
93
85
|
# cb_return: CallbackReturn
|
94
|
-
):
|
86
|
+
) -> None:
|
95
87
|
self.in_f: Union[bytes, Path]
|
96
88
|
if isinstance(in_f, Path):
|
97
89
|
self.in_f = in_f
|
@@ -151,7 +143,7 @@ class StickerConvert:
|
|
151
143
|
out_f: Path,
|
152
144
|
opt_comp: CompOption,
|
153
145
|
cb: "Union[Queue[CbQueueItemType], Callback]",
|
154
|
-
|
146
|
+
_cb_return: CallbackReturn,
|
155
147
|
) -> Tuple[bool, Path, Union[None, bytes, Path], int]:
|
156
148
|
sticker = StickerConvert(in_f, out_f, opt_comp, cb)
|
157
149
|
result = sticker._convert()
|
@@ -163,7 +155,7 @@ class StickerConvert:
|
|
163
155
|
if result:
|
164
156
|
return self.compress_done(result)
|
165
157
|
|
166
|
-
self.cb.put((
|
158
|
+
self.cb.put((MSG_START_COMP.format(self.in_f_name, self.out_f_name)))
|
167
159
|
|
168
160
|
steps_list = self.generate_steps_list()
|
169
161
|
|
@@ -195,7 +187,7 @@ class StickerConvert:
|
|
195
187
|
self.color = param[4]
|
196
188
|
|
197
189
|
self.tmp_f = BytesIO()
|
198
|
-
msg =
|
190
|
+
msg = MSG_COMP.format(
|
199
191
|
self.in_f_name,
|
200
192
|
self.out_f_name,
|
201
193
|
self.res_w,
|
@@ -261,7 +253,7 @@ class StickerConvert:
|
|
261
253
|
file_info=self.codec_info_orig,
|
262
254
|
)
|
263
255
|
):
|
264
|
-
self.cb.put((
|
256
|
+
self.cb.put((MSG_SKIP_COMP.format(self.in_f_name, self.out_f_name)))
|
265
257
|
|
266
258
|
if isinstance(self.in_f, Path):
|
267
259
|
with open(self.in_f, "rb") as f:
|
@@ -272,8 +264,8 @@ class StickerConvert:
|
|
272
264
|
self.result_size = len(self.in_f)
|
273
265
|
|
274
266
|
return result
|
275
|
-
|
276
|
-
|
267
|
+
|
268
|
+
return None
|
277
269
|
|
278
270
|
def generate_steps_list(self) -> List[Tuple[Optional[int], ...]]:
|
279
271
|
steps_list: List[Tuple[Optional[int], ...]] = []
|
@@ -323,7 +315,7 @@ class StickerConvert:
|
|
323
315
|
return steps_list
|
324
316
|
|
325
317
|
def recompress(self, sign: str) -> None:
|
326
|
-
msg =
|
318
|
+
msg = MSG_REDO_COMP.format(
|
327
319
|
sign, self.in_f_name, self.out_f_name, self.size, sign, self.size_max
|
328
320
|
)
|
329
321
|
self.cb.put(msg)
|
@@ -331,7 +323,7 @@ class StickerConvert:
|
|
331
323
|
def compress_fail(
|
332
324
|
self,
|
333
325
|
) -> Tuple[bool, Path, Union[None, bytes, Path], int]:
|
334
|
-
msg =
|
326
|
+
msg = MSG_FAIL_COMP.format(
|
335
327
|
self.in_f_name, self.out_f_name, self.size_max, self.size
|
336
328
|
)
|
337
329
|
self.cb.put(msg)
|
@@ -353,7 +345,7 @@ class StickerConvert:
|
|
353
345
|
f.write(data)
|
354
346
|
|
355
347
|
if result_step:
|
356
|
-
msg =
|
348
|
+
msg = MSG_DONE_COMP.format(
|
357
349
|
self.in_f_name, self.out_f_name, self.result_size, result_step
|
358
350
|
)
|
359
351
|
self.cb.put(msg)
|
@@ -378,7 +370,7 @@ class StickerConvert:
|
|
378
370
|
def _frames_import_pillow(self) -> None:
|
379
371
|
with Image.open(self.in_f) as im:
|
380
372
|
# Note: im.convert("RGBA") would return rgba image of current frame only
|
381
|
-
if "n_frames" in im
|
373
|
+
if "n_frames" in dir(im):
|
382
374
|
for i in range(im.n_frames):
|
383
375
|
im.seek(i)
|
384
376
|
self.frames_raw.append(np.asarray(im.convert("RGBA")))
|
@@ -516,11 +508,15 @@ class StickerConvert:
|
|
516
508
|
) -> "List[np.ndarray[Any, Any]]":
|
517
509
|
frames_out: "List[np.ndarray[Any, Any]]" = []
|
518
510
|
|
519
|
-
resample: Literal[0, 1, 2, 3]
|
511
|
+
resample: Literal[0, 1, 2, 3, 4, 5]
|
520
512
|
if self.opt_comp.scale_filter == "nearest":
|
521
513
|
resample = Image.NEAREST
|
514
|
+
elif self.opt_comp.scale_filter == "box":
|
515
|
+
resample = Image.BOX
|
522
516
|
elif self.opt_comp.scale_filter == "bilinear":
|
523
517
|
resample = Image.BILINEAR
|
518
|
+
elif self.opt_comp.scale_filter == "hamming":
|
519
|
+
resample = Image.HAMMING
|
524
520
|
elif self.opt_comp.scale_filter == "bicubic":
|
525
521
|
resample = Image.BICUBIC
|
526
522
|
elif self.opt_comp.scale_filter == "lanczos":
|
@@ -764,10 +760,10 @@ class StickerConvert:
|
|
764
760
|
return image.copy()
|
765
761
|
if self.opt_comp.quantize_method == "imagequant":
|
766
762
|
return self._quantize_by_imagequant(image)
|
767
|
-
|
763
|
+
if self.opt_comp.quantize_method == "fastoctree":
|
768
764
|
return self._quantize_by_fastoctree(image)
|
769
|
-
|
770
|
-
|
765
|
+
|
766
|
+
return image
|
771
767
|
|
772
768
|
def _quantize_by_imagequant(self, image: Image.Image) -> Image.Image:
|
773
769
|
import imagequant # type: ignore
|
@@ -812,17 +808,17 @@ class StickerConvert:
|
|
812
808
|
#
|
813
809
|
# For GIF, we need to adjust fps such that delay is matching to hundreths of second
|
814
810
|
return self._fix_fps_duration(fps, 100)
|
815
|
-
|
811
|
+
if self.out_f.suffix in (".webp", ".apng", ".png"):
|
816
812
|
return self._fix_fps_duration(fps, 1000)
|
817
|
-
|
818
|
-
|
813
|
+
|
814
|
+
return self._fix_fps_pyav(fps)
|
819
815
|
|
820
816
|
def _fix_fps_duration(self, fps: float, denominator: int) -> Fraction:
|
821
817
|
delay = int(rounding(denominator / fps))
|
822
818
|
fps_fraction = Fraction(denominator, delay)
|
823
819
|
if self.opt_comp.fps_max and fps_fraction > self.opt_comp.fps_max:
|
824
820
|
return Fraction(denominator, (delay + 1))
|
825
|
-
|
821
|
+
if self.opt_comp.fps_min and fps_fraction < self.opt_comp.fps_min:
|
826
822
|
return Fraction(denominator, (delay - 1))
|
827
823
|
return fps_fraction
|
828
824
|
|
sticker_convert/definitions.py
CHANGED
@@ -48,8 +48,7 @@ def check_root_dir_exe_writable() -> bool:
|
|
48
48
|
or "site-packages" in ROOT_DIR_EXE.as_posix()
|
49
49
|
):
|
50
50
|
return False
|
51
|
-
|
52
|
-
return True
|
51
|
+
return True
|
53
52
|
|
54
53
|
|
55
54
|
ROOT_DIR_EXE_WRITABLE = check_root_dir_exe_writable()
|
@@ -58,13 +57,11 @@ ROOT_DIR_EXE_WRITABLE = check_root_dir_exe_writable()
|
|
58
57
|
def get_default_dir() -> Path:
|
59
58
|
if ROOT_DIR_EXE_WRITABLE:
|
60
59
|
return ROOT_DIR_EXE
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
else:
|
67
|
-
return home_dir
|
60
|
+
home_dir = Path.home()
|
61
|
+
desktop_dir = home_dir / "Desktop"
|
62
|
+
if desktop_dir.is_dir():
|
63
|
+
return desktop_dir
|
64
|
+
return home_dir
|
68
65
|
|
69
66
|
|
70
67
|
# Default directory for stickers_input and stickers_output
|
@@ -79,9 +76,8 @@ def get_config_dir() -> Path:
|
|
79
76
|
|
80
77
|
if ROOT_DIR_EXE_WRITABLE:
|
81
78
|
return ROOT_DIR_EXE
|
82
|
-
|
83
|
-
|
84
|
-
return fallback_dir
|
79
|
+
os.makedirs(fallback_dir, exist_ok=True)
|
80
|
+
return fallback_dir
|
85
81
|
|
86
82
|
|
87
83
|
# Directory for saving configs
|
@@ -3,12 +3,12 @@ from __future__ import annotations
|
|
3
3
|
|
4
4
|
from pathlib import Path
|
5
5
|
from queue import Queue
|
6
|
-
from typing import Any,
|
6
|
+
from typing import Any, List, Optional, Tuple, Union
|
7
7
|
|
8
8
|
import requests
|
9
9
|
|
10
10
|
from sticker_convert.job_option import CredOption
|
11
|
-
from sticker_convert.utils.callback import Callback, CallbackReturn
|
11
|
+
from sticker_convert.utils.callback import Callback, CallbackReturn, CbQueueItemType
|
12
12
|
|
13
13
|
|
14
14
|
class DownloadBase:
|
@@ -18,35 +18,20 @@ class DownloadBase:
|
|
18
18
|
out_dir: Path,
|
19
19
|
opt_cred: Optional[CredOption],
|
20
20
|
cb: Union[
|
21
|
-
Queue[
|
22
|
-
Union[
|
23
|
-
Tuple[str, Optional[Tuple[str]], Optional[Dict[str, Any]]],
|
24
|
-
str,
|
25
|
-
None,
|
26
|
-
]
|
27
|
-
],
|
21
|
+
Queue[CbQueueItemType],
|
28
22
|
Callback,
|
29
23
|
],
|
30
24
|
cb_return: CallbackReturn,
|
31
|
-
):
|
32
|
-
self.url
|
33
|
-
self.out_dir
|
34
|
-
self.opt_cred
|
35
|
-
self.cb
|
36
|
-
|
37
|
-
Union[
|
38
|
-
Tuple[str, Optional[Tuple[str]], Optional[Dict[str, Any]]],
|
39
|
-
str,
|
40
|
-
None,
|
41
|
-
]
|
42
|
-
],
|
43
|
-
Callback,
|
44
|
-
] = cb
|
45
|
-
self.cb_return: CallbackReturn = cb_return
|
25
|
+
) -> None:
|
26
|
+
self.url = url
|
27
|
+
self.out_dir = out_dir
|
28
|
+
self.opt_cred = opt_cred
|
29
|
+
self.cb = cb
|
30
|
+
self.cb_return = cb_return
|
46
31
|
|
47
32
|
def download_multiple_files(
|
48
33
|
self, targets: List[Tuple[str, Path]], retries: int = 3, **kwargs: Any
|
49
|
-
):
|
34
|
+
) -> None:
|
50
35
|
# targets format: [(url1, dest2), (url2, dest2), ...]
|
51
36
|
self.cb.put(
|
52
37
|
("bar", None, {"set_progress_mode": "determinate", "steps": len(targets)})
|
@@ -75,8 +60,7 @@ class DownloadBase:
|
|
75
60
|
|
76
61
|
if response.status_code != 200:
|
77
62
|
return b""
|
78
|
-
|
79
|
-
self.cb.put(f"Downloading {url}")
|
63
|
+
self.cb.put(f"Downloading {url}")
|
80
64
|
|
81
65
|
if show_progress:
|
82
66
|
steps = (total_length / chunk_size) + 1
|
@@ -101,11 +85,10 @@ class DownloadBase:
|
|
101
85
|
|
102
86
|
if not result:
|
103
87
|
return b""
|
104
|
-
|
88
|
+
if dest:
|
105
89
|
with open(dest, "wb+") as f:
|
106
90
|
f.write(result)
|
107
91
|
msg = f"Downloaded {url}"
|
108
92
|
self.cb.put(msg)
|
109
93
|
return b""
|
110
|
-
|
111
|
-
return result
|
94
|
+
return result
|
@@ -13,10 +13,9 @@ import requests
|
|
13
13
|
from bs4 import BeautifulSoup
|
14
14
|
from bs4.element import Tag
|
15
15
|
|
16
|
-
from sticker_convert.converter import CbQueueItemType
|
17
16
|
from sticker_convert.downloaders.download_base import DownloadBase
|
18
17
|
from sticker_convert.job_option import CredOption
|
19
|
-
from sticker_convert.utils.callback import Callback, CallbackReturn
|
18
|
+
from sticker_convert.utils.callback import Callback, CallbackReturn, CbQueueItemType
|
20
19
|
from sticker_convert.utils.files.metadata_handler import MetadataHandler
|
21
20
|
from sticker_convert.utils.media.decrypt_kakao import DecryptKakao
|
22
21
|
|
@@ -41,7 +40,7 @@ class MetadataKakao:
|
|
41
40
|
data_urls = app_scheme_link_tag.get("data-url")
|
42
41
|
if not data_urls:
|
43
42
|
return None, None
|
44
|
-
|
43
|
+
if isinstance(data_urls, list):
|
45
44
|
data_url = data_urls[0]
|
46
45
|
else:
|
47
46
|
data_url = data_urls
|
@@ -113,7 +112,7 @@ class MetadataKakao:
|
|
113
112
|
|
114
113
|
class DownloadKakao(DownloadBase):
|
115
114
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
116
|
-
super(
|
115
|
+
super().__init__(*args, **kwargs)
|
117
116
|
self.pack_title: Optional[str] = None
|
118
117
|
self.author: Optional[str] = None
|
119
118
|
|
@@ -129,13 +128,10 @@ class DownloadKakao(DownloadBase):
|
|
129
128
|
|
130
129
|
if item_code:
|
131
130
|
return self.download_animated(item_code)
|
132
|
-
|
133
|
-
|
134
|
-
"Download failed: Cannot download metadata for sticker pack"
|
135
|
-
)
|
136
|
-
return False
|
131
|
+
self.cb.put("Download failed: Cannot download metadata for sticker pack")
|
132
|
+
return False
|
137
133
|
|
138
|
-
|
134
|
+
if self.url.isnumeric() or self.url.startswith("kakaotalk://store/emoticon/"):
|
139
135
|
item_code = self.url.replace("kakaotalk://store/emoticon/", "")
|
140
136
|
|
141
137
|
self.pack_title = None
|
@@ -150,7 +146,7 @@ class DownloadKakao(DownloadBase):
|
|
150
146
|
|
151
147
|
return self.download_animated(item_code)
|
152
148
|
|
153
|
-
|
149
|
+
if urlparse(self.url).netloc == "e.kakao.com":
|
154
150
|
self.pack_title = self.url.replace("https://e.kakao.com/t/", "")
|
155
151
|
(
|
156
152
|
self.author,
|
@@ -172,24 +168,22 @@ class DownloadKakao(DownloadBase):
|
|
172
168
|
item_code = MetadataKakao.get_item_code(title_ko, auth_token)
|
173
169
|
if item_code:
|
174
170
|
return self.download_animated(item_code)
|
171
|
+
msg = "Warning: Cannot get item code.\n"
|
172
|
+
msg += "Is auth_token invalid / expired? Try to regenerate it.\n"
|
173
|
+
msg += "Continue to download static stickers instead?"
|
174
|
+
self.cb.put(("ask_bool", (msg,), None))
|
175
|
+
if self.cb_return:
|
176
|
+
response = self.cb_return.get_response()
|
175
177
|
else:
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
if self.cb_return:
|
181
|
-
response = self.cb_return.get_response()
|
182
|
-
else:
|
183
|
-
response = False
|
184
|
-
|
185
|
-
if response is False:
|
186
|
-
return False
|
178
|
+
response = False
|
179
|
+
|
180
|
+
if response is False:
|
181
|
+
return False
|
187
182
|
|
188
183
|
return self.download_static(thumbnail_urls)
|
189
184
|
|
190
|
-
|
191
|
-
|
192
|
-
return False
|
185
|
+
self.cb.put("Download failed: Unrecognized URL")
|
186
|
+
return False
|
193
187
|
|
194
188
|
def download_static(self, thumbnail_urls: str) -> bool:
|
195
189
|
MetadataHandler.set_metadata(
|
@@ -15,15 +15,14 @@ import requests
|
|
15
15
|
from bs4 import BeautifulSoup
|
16
16
|
from PIL import Image
|
17
17
|
|
18
|
-
from sticker_convert.converter import CbQueueItemType
|
19
18
|
from sticker_convert.downloaders.download_base import DownloadBase
|
20
19
|
from sticker_convert.job_option import CredOption
|
21
20
|
from sticker_convert.utils.auth.get_line_auth import GetLineAuth
|
22
|
-
from sticker_convert.utils.callback import Callback, CallbackReturn
|
21
|
+
from sticker_convert.utils.callback import Callback, CallbackReturn, CbQueueItemType
|
23
22
|
from sticker_convert.utils.files.metadata_handler import MetadataHandler
|
24
23
|
from sticker_convert.utils.media.apple_png_normalize import ApplePngNormalize
|
25
24
|
|
26
|
-
|
25
|
+
# Reference: https://github.com/doubleplusc/Line-sticker-downloader/blob/master/sticker_dl.py
|
27
26
|
|
28
27
|
|
29
28
|
class MetadataLine:
|
@@ -162,7 +161,7 @@ class MetadataLine:
|
|
162
161
|
|
163
162
|
class DownloadLine(DownloadBase):
|
164
163
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
165
|
-
super(
|
164
|
+
super().__init__(*args, **kwargs)
|
166
165
|
self.headers = {
|
167
166
|
"referer": "https://store.line.me",
|
168
167
|
"user-agent": "Android",
|
@@ -227,7 +226,7 @@ class DownloadLine(DownloadBase):
|
|
227
226
|
num: int,
|
228
227
|
prefix: str = "",
|
229
228
|
suffix: str = "",
|
230
|
-
):
|
229
|
+
) -> None:
|
231
230
|
data = zf.read(f_path)
|
232
231
|
ext = Path(f_path).suffix
|
233
232
|
if ext == ".png" and not self.is_emoji and int() < 775:
|
@@ -444,13 +443,13 @@ class DownloadLine(DownloadBase):
|
|
444
443
|
self.decompress_stickers(zip_file)
|
445
444
|
|
446
445
|
custom_sticker_text_urls: List[Tuple[str, Path]] = []
|
447
|
-
if self.sticker_text_dict
|
446
|
+
if self.sticker_text_dict and (
|
448
447
|
self.resource_type == "PER_STICKER_TEXT"
|
449
|
-
or (self.resource_type == "NAME_TEXT" and self.cookies
|
448
|
+
or (self.resource_type == "NAME_TEXT" and self.cookies)
|
450
449
|
):
|
451
450
|
self.edit_custom_sticker_text()
|
452
451
|
custom_sticker_text_urls = self.get_custom_sticker_text_urls()
|
453
|
-
elif self.resource_type == "NAME_TEXT" and self.cookies
|
452
|
+
elif self.resource_type == "NAME_TEXT" and not self.cookies:
|
454
453
|
self.cb.put('Warning: Line "Custom stickers" is supplied as input')
|
455
454
|
self.cb.put(
|
456
455
|
"However, adding custom message requires Line cookies, and it is not supplied"
|
@@ -1,24 +1,23 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
from pathlib import Path
|
3
3
|
from queue import Queue
|
4
|
-
from typing import
|
4
|
+
from typing import Dict, Optional, Union
|
5
5
|
|
6
6
|
import anyio
|
7
7
|
from signalstickers_client import StickersClient # type: ignore
|
8
8
|
from signalstickers_client.errors import SignalException # type: ignore
|
9
9
|
from signalstickers_client.models import StickerPack # type: ignore
|
10
10
|
|
11
|
-
from sticker_convert.converter import CbQueueItemType
|
12
11
|
from sticker_convert.downloaders.download_base import DownloadBase
|
13
12
|
from sticker_convert.job_option import CredOption
|
14
|
-
from sticker_convert.utils.callback import Callback, CallbackReturn
|
13
|
+
from sticker_convert.utils.callback import Callback, CallbackReturn, CbQueueItemType
|
15
14
|
from sticker_convert.utils.files.metadata_handler import MetadataHandler
|
16
15
|
from sticker_convert.utils.media.codec_info import CodecInfo
|
17
16
|
|
18
17
|
|
19
18
|
class DownloadSignal(DownloadBase):
|
20
|
-
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
21
|
-
|
19
|
+
# def __init__(self, *args: Any, **kwargs: Any) -> None:
|
20
|
+
# super().__init__(*args, **kwargs)
|
22
21
|
|
23
22
|
@staticmethod
|
24
23
|
async def get_pack(pack_id: str, pack_key: str) -> StickerPack:
|
@@ -1,23 +1,22 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
from pathlib import Path
|
3
3
|
from queue import Queue
|
4
|
-
from typing import
|
4
|
+
from typing import Dict, Optional, Union
|
5
5
|
from urllib.parse import urlparse
|
6
6
|
|
7
7
|
import anyio
|
8
8
|
from telegram import Bot
|
9
9
|
from telegram.error import TelegramError
|
10
10
|
|
11
|
-
from sticker_convert.converter import CbQueueItemType
|
12
11
|
from sticker_convert.downloaders.download_base import DownloadBase
|
13
12
|
from sticker_convert.job_option import CredOption
|
14
|
-
from sticker_convert.utils.callback import Callback, CallbackReturn
|
13
|
+
from sticker_convert.utils.callback import Callback, CallbackReturn, CbQueueItemType
|
15
14
|
from sticker_convert.utils.files.metadata_handler import MetadataHandler
|
16
15
|
|
17
16
|
|
18
17
|
class DownloadTelegram(DownloadBase):
|
19
|
-
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
20
|
-
|
18
|
+
# def __init__(self, *args: Any, **kwargs: Any) -> None:
|
19
|
+
# super().__init__(*args, **kwargs)
|
21
20
|
|
22
21
|
def download_stickers_telegram(self) -> bool:
|
23
22
|
self.token = ""
|