sticker-convert 2.10.7__py3-none-any.whl → 2.10.9__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.
@@ -702,9 +702,9 @@ class StickerConvert:
702
702
  self._frames_export_apng()
703
703
  else:
704
704
  self._frames_export_png()
705
- elif self.out_f.suffix == ".gif":
705
+ elif self.out_f.suffix in (".gif", ".webp"):
706
706
  self._frames_export_pil_anim()
707
- elif self.out_f.suffix in (".webm", ".mp4", ".mkv", ".webp") or is_animated:
707
+ elif self.out_f.suffix in (".webm", ".mp4", ".mkv") or is_animated:
708
708
  self._frames_export_pyav()
709
709
  else:
710
710
  self._frames_export_pil()
@@ -811,8 +811,9 @@ class StickerConvert:
811
811
  elif self.out_f.suffix == ".webp":
812
812
  im_out = [Image.fromarray(i) for i in self.frames_processed] # type: ignore
813
813
  extra_kwargs["format"] = "WebP"
814
- extra_kwargs["minimize_size"] = True
815
- extra_kwargs["method"] = 6
814
+ # Enabling below is too slow
815
+ # extra_kwargs["minimize_size"] = True
816
+ # extra_kwargs["method"] = 6
816
817
  else:
817
818
  raise RuntimeError(f"Invalid format {self.out_f.suffix}")
818
819
 
@@ -34,18 +34,26 @@ class DownloadBase:
34
34
  retries: int = 3,
35
35
  headers: Optional[dict[Any, Any]] = None,
36
36
  **kwargs: Any,
37
- ) -> None:
37
+ ) -> dict[str, bool]:
38
+ results: dict[str, bool] = {}
38
39
  anyio.run(
39
40
  partial(
40
- self.download_multiple_files_async, targets, retries, headers, **kwargs
41
+ self.download_multiple_files_async,
42
+ targets,
43
+ retries,
44
+ headers,
45
+ results,
46
+ **kwargs,
41
47
  )
42
48
  )
49
+ return results
43
50
 
44
51
  async def download_multiple_files_async(
45
52
  self,
46
53
  targets: List[Tuple[str, Path]],
47
54
  retries: int = 3,
48
55
  headers: Optional[dict[Any, Any]] = None,
56
+ results: Optional[dict[str, bool]] = None,
49
57
  **kwargs: Any,
50
58
  ) -> None:
51
59
  # targets format: [(url1, dest2), (url2, dest2), ...]
@@ -53,44 +61,56 @@ class DownloadBase:
53
61
  ("bar", None, {"set_progress_mode": "determinate", "steps": len(targets)})
54
62
  )
55
63
 
64
+ semaphore = anyio.Semaphore(4)
65
+
56
66
  async with httpx.AsyncClient() as client:
57
67
  async with anyio.create_task_group() as tg:
58
68
  for url, dest in targets:
59
69
  tg.start_soon(
60
70
  self.download_file_async,
71
+ semaphore,
61
72
  client,
62
73
  url,
63
74
  dest,
64
75
  retries,
65
76
  headers,
77
+ results,
66
78
  **kwargs,
67
79
  )
68
80
 
69
81
  async def download_file_async(
70
82
  self,
83
+ semaphore: anyio.Semaphore,
71
84
  client: httpx.AsyncClient,
72
85
  url: str,
73
86
  dest: Path,
74
87
  retries: int = 3,
75
88
  headers: Optional[dict[Any, Any]] = None,
89
+ results: Optional[dict[str, bool]] = None,
76
90
  **kwargs: Any,
77
91
  ) -> None:
78
- self.cb.put(f"Downloading {url}")
79
- for retry in range(retries):
80
- response = await client.get(
81
- url, follow_redirects=True, headers=headers, **kwargs
82
- )
83
-
84
- if response.is_success:
85
- async with await anyio.open_file(dest, "wb+") as f:
86
- await f.write(response.content)
87
- self.cb.put(f"Downloaded {url}")
88
- else:
89
- self.cb.put(
90
- f"Error {response.status_code}: {url} (tried {retry+1}/{retries} times)"
92
+ async with semaphore:
93
+ self.cb.put(f"Downloading {url}")
94
+ success = False
95
+ for retry in range(retries):
96
+ response = await client.get(
97
+ url, follow_redirects=True, headers=headers, **kwargs
91
98
  )
99
+ success = response.is_success
100
+
101
+ if success:
102
+ async with await anyio.open_file(dest, "wb+") as f:
103
+ await f.write(response.content)
104
+ self.cb.put(f"Downloaded {url}")
105
+ else:
106
+ self.cb.put(
107
+ f"Error {response.status_code}: {url} (tried {retry+1}/{retries} times)"
108
+ )
109
+
110
+ if results is not None:
111
+ results[url] = success
92
112
 
93
- self.cb.put("update_bar")
113
+ self.cb.put("update_bar")
94
114
 
95
115
  def download_file(
96
116
  self,
@@ -21,10 +21,10 @@ class DownloadDiscord(DownloadBase):
21
21
  # def __init__(self, *args: Any, **kwargs: Any) -> None:
22
22
  # super().__init__(*args, **kwargs)
23
23
 
24
- def download_stickers_discord(self) -> bool:
24
+ def download_stickers_discord(self) -> Tuple[int, int]:
25
25
  if self.opt_cred is None or self.opt_cred.discord_token == "":
26
26
  self.cb.put("Error: Downloading from Discord requires token")
27
- return False
27
+ return 0, 0
28
28
 
29
29
  gid: Optional[str] = None
30
30
  if self.url.isnumeric():
@@ -36,7 +36,7 @@ class DownloadDiscord(DownloadBase):
36
36
 
37
37
  if gid is None or gid.isnumeric() is False:
38
38
  self.cb.put("Error: Invalid url")
39
- return False
39
+ return 0, 0
40
40
 
41
41
  headers = {
42
42
  "Authorization": self.opt_cred.discord_token,
@@ -68,7 +68,7 @@ class DownloadDiscord(DownloadBase):
68
68
  f_path = Path(self.out_dir, f_name)
69
69
  targets.append((sticker_url, f_path))
70
70
 
71
- self.download_multiple_files(targets)
71
+ results = self.download_multiple_files(targets)
72
72
 
73
73
  server_name = r_json["name"]
74
74
  MetadataHandler.set_metadata(
@@ -78,7 +78,7 @@ class DownloadDiscord(DownloadBase):
78
78
  emoji_dict=emoji_dict if self.input_option == "discord" else None,
79
79
  )
80
80
 
81
- return True
81
+ return sum(results.values()), len(targets)
82
82
 
83
83
  @staticmethod
84
84
  def start(
@@ -86,6 +86,6 @@ class DownloadDiscord(DownloadBase):
86
86
  opt_cred: Optional[CredOption],
87
87
  cb: CallbackProtocol,
88
88
  cb_return: CallbackReturn,
89
- ) -> bool:
89
+ ) -> Tuple[int, int]:
90
90
  downloader = DownloadDiscord(opt_input, opt_cred, cb, cb_return)
91
91
  return downloader.download_stickers_discord()
@@ -34,6 +34,11 @@ class daumtools {
34
34
  return dataDict['urlScheme'];
35
35
  }
36
36
  }
37
+ class document {
38
+ static querySelectorAll(selectors) {
39
+ return [];
40
+ }
41
+ }
37
42
  """
38
43
 
39
44
 
@@ -133,9 +138,21 @@ class DownloadKakao(DownloadBase):
133
138
  kakao_url = cast(str, ctx.eval(js))
134
139
  item_code = urlparse(kakao_url).path.split("/")[2]
135
140
 
141
+ # Share link redirect to preview link if use desktop headers
142
+ # This allows us to find pack author
143
+ headers_desktop = {"User-Agent": "Chrome"}
144
+
145
+ response = requests.get(url, headers=headers_desktop, allow_redirects=True)
146
+ response.url
147
+
148
+ pack_title_url = urlparse(response.url).path.split("/")[-1]
149
+ pack_info_unauthed = MetadataKakao.get_pack_info_unauthed(pack_title_url)
150
+ if pack_info_unauthed:
151
+ self.author = pack_info_unauthed["result"]["artist"]
152
+
136
153
  return pack_title, item_code
137
154
 
138
- def download_stickers_kakao(self) -> bool:
155
+ def download_stickers_kakao(self) -> Tuple[int, int]:
139
156
  self.auth_token = None
140
157
  if self.opt_cred:
141
158
  self.auth_token = self.opt_cred.kakao_auth_token
@@ -146,7 +163,7 @@ class DownloadKakao(DownloadBase):
146
163
  if item_code:
147
164
  return self.download_animated(item_code)
148
165
  self.cb.put("Download failed: Cannot download metadata for sticker pack")
149
- return False
166
+ return 0, 0
150
167
 
151
168
  if self.url.isnumeric() or self.url.startswith("kakaotalk://store/emoticon/"):
152
169
  item_code = self.url.replace("kakaotalk://store/emoticon/", "")
@@ -177,7 +194,7 @@ class DownloadKakao(DownloadBase):
177
194
  self.cb.put(
178
195
  "Download failed: Cannot download metadata for sticker pack"
179
196
  )
180
- return False
197
+ return 0, 0
181
198
 
182
199
  self.author = self.pack_info_unauthed["result"]["artist"]
183
200
  title_ko = self.pack_info_unauthed["result"]["title"]
@@ -197,14 +214,15 @@ class DownloadKakao(DownloadBase):
197
214
  response = False
198
215
 
199
216
  if response is False:
200
- return False
217
+ return 0, 0
201
218
 
202
219
  return self.download_static(thumbnail_urls)
203
220
 
204
221
  self.cb.put("Download failed: Unrecognized URL")
205
- return False
222
+ return 0, 0
206
223
 
207
- def download_static(self, thumbnail_urls: str) -> bool:
224
+ def download_static(self, thumbnail_urls: str) -> Tuple[int, int]:
225
+ headers = {"User-Agent": "Android"}
208
226
  MetadataHandler.set_metadata(
209
227
  self.out_dir, title=self.pack_title, author=self.author
210
228
  )
@@ -215,11 +233,11 @@ class DownloadKakao(DownloadBase):
215
233
  dest = Path(self.out_dir, str(num).zfill(3) + ".png")
216
234
  targets.append((url, dest))
217
235
 
218
- self.download_multiple_files(targets)
236
+ results = self.download_multiple_files(targets, headers=headers)
219
237
 
220
- return True
238
+ return sum(results.values()), len(targets)
221
239
 
222
- def download_animated(self, item_code: str) -> bool:
240
+ def download_animated(self, item_code: str) -> Tuple[int, int]:
223
241
  MetadataHandler.set_metadata(
224
242
  self.out_dir, title=self.pack_title, author=self.author
225
243
  )
@@ -272,7 +290,7 @@ class DownloadKakao(DownloadBase):
272
290
  break
273
291
  if play_ext == "":
274
292
  self.cb.put(f"Failed to determine extension of {item_code}")
275
- return False
293
+ return 0, 0
276
294
  else:
277
295
  play_path_format = f"dw/{item_code}.{play_type}_0##{play_ext}"
278
296
  else:
@@ -308,7 +326,7 @@ class DownloadKakao(DownloadBase):
308
326
  sound_dl_path = Path(self.out_dir, str(num).zfill(3) + sound_ext)
309
327
  targets.append((sound_url, sound_dl_path))
310
328
 
311
- self.download_multiple_files(targets, headers=headers)
329
+ results = self.download_multiple_files(targets, headers=headers)
312
330
 
313
331
  for target in targets:
314
332
  f_path = target[1]
@@ -326,7 +344,7 @@ class DownloadKakao(DownloadBase):
326
344
 
327
345
  self.cb.put(f"Finished getting {item_code}")
328
346
 
329
- return True
347
+ return sum(results.values()), len(targets)
330
348
 
331
349
  @staticmethod
332
350
  def start(
@@ -334,6 +352,6 @@ class DownloadKakao(DownloadBase):
334
352
  opt_cred: Optional[CredOption],
335
353
  cb: CallbackProtocol,
336
354
  cb_return: CallbackReturn,
337
- ) -> bool:
355
+ ) -> Tuple[int, int]:
338
356
  downloader = DownloadKakao(opt_input, opt_cred, cb, cb_return)
339
357
  return downloader.download_stickers_kakao()
@@ -400,13 +400,13 @@ class DownloadLine(DownloadBase):
400
400
 
401
401
  self.cb.put(f"Combined {i.name.replace('-text.png', '.png')}")
402
402
 
403
- def download_stickers_line(self) -> bool:
403
+ def download_stickers_line(self) -> Tuple[int, int]:
404
404
  url_data = MetadataLine.analyze_url(self.url)
405
405
  if url_data:
406
406
  self.pack_id, self.region, self.is_emoji = url_data
407
407
  else:
408
408
  self.cb.put("Download failed: Unsupported URL format")
409
- return False
409
+ return 0, 0
410
410
 
411
411
  if self.is_emoji:
412
412
  metadata = MetadataLine.get_metadata_sticon(self.pack_id, self.region)
@@ -423,7 +423,7 @@ class DownloadLine(DownloadBase):
423
423
  ) = metadata
424
424
  else:
425
425
  self.cb.put("Download failed: Failed to get metadata")
426
- return False
426
+ return 0, 0
427
427
 
428
428
  MetadataHandler.set_metadata(self.out_dir, title=self.title, author=self.author)
429
429
 
@@ -434,7 +434,7 @@ class DownloadLine(DownloadBase):
434
434
  self.cb.put(f"Downloaded {pack_url}")
435
435
  else:
436
436
  self.cb.put(f"Cannot download {pack_url}")
437
- return False
437
+ return 0, 0
438
438
 
439
439
  if self.is_emoji:
440
440
  self.decompress_emoticon(zip_file)
@@ -458,7 +458,7 @@ class DownloadLine(DownloadBase):
458
458
  self.download_multiple_files(custom_sticker_text_urls, headers=self.headers)
459
459
  self.combine_custom_text()
460
460
 
461
- return True
461
+ return len(self.pack_files), len(self.pack_files)
462
462
 
463
463
  @staticmethod
464
464
  def start(
@@ -466,6 +466,6 @@ class DownloadLine(DownloadBase):
466
466
  opt_cred: Optional[CredOption],
467
467
  cb: CallbackProtocol,
468
468
  cb_return: CallbackReturn,
469
- ) -> bool:
469
+ ) -> Tuple[int, int]:
470
470
  downloader = DownloadLine(opt_input, opt_cred, cb, cb_return)
471
471
  return downloader.download_stickers_line()
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  from pathlib import Path
3
- from typing import Dict, Optional
3
+ from typing import Dict, Optional, Tuple
4
4
 
5
5
  import anyio
6
6
  from signalstickers_client.errors import SignalException
@@ -62,10 +62,10 @@ class DownloadSignal(DownloadBase):
62
62
  self.out_dir, title=pack.title, author=pack.author, emoji_dict=emoji_dict
63
63
  )
64
64
 
65
- def download_stickers_signal(self) -> bool:
65
+ def download_stickers_signal(self) -> Tuple[int, int]:
66
66
  if "signal.art" not in self.url:
67
67
  self.cb.put("Download failed: Unrecognized URL format")
68
- return False
68
+ return 0, 0
69
69
 
70
70
  pack_id = self.url.split("#pack_id=")[1].split("&pack_key=")[0]
71
71
  pack_key = self.url.split("&pack_key=")[1]
@@ -74,11 +74,11 @@ class DownloadSignal(DownloadBase):
74
74
  pack = anyio.run(DownloadSignal.get_pack, pack_id, pack_key)
75
75
  except SignalException as e:
76
76
  self.cb.put(f"Failed to download pack due to {repr(e)}")
77
- return False
77
+ return 0, 0
78
78
 
79
79
  self.save_stickers(pack)
80
80
 
81
- return True
81
+ return len(pack.stickers), len(pack.stickers)
82
82
 
83
83
  @staticmethod
84
84
  def start(
@@ -86,6 +86,6 @@ class DownloadSignal(DownloadBase):
86
86
  opt_cred: Optional[CredOption],
87
87
  cb: CallbackProtocol,
88
88
  cb_return: CallbackReturn,
89
- ) -> bool:
89
+ ) -> Tuple[int, int]:
90
90
  downloader = DownloadSignal(opt_input, opt_cred, cb, cb_return)
91
91
  return downloader.download_stickers_signal()
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  from pathlib import Path
3
- from typing import Dict, Optional, Union
3
+ from typing import Dict, Optional, Tuple, Union
4
4
  from urllib.parse import urlparse
5
5
 
6
6
  import anyio
@@ -18,18 +18,18 @@ class DownloadTelegram(DownloadBase):
18
18
  # def __init__(self, *args: Any, **kwargs: Any) -> None:
19
19
  # super().__init__(*args, **kwargs)
20
20
 
21
- def download_stickers_telegram(self) -> bool:
21
+ def download_stickers_telegram(self) -> Tuple[int, int]:
22
22
  self.token = ""
23
23
 
24
24
  if self.opt_cred:
25
25
  self.token = self.opt_cred.telegram_token.strip()
26
26
  if not self.token:
27
27
  self.cb.put("Download failed: Token required for downloading from telegram")
28
- return False
28
+ return 0, 0
29
29
 
30
30
  if not ("telegram.me" in self.url or "t.me" in self.url):
31
31
  self.cb.put("Download failed: Unrecognized URL format")
32
- return False
32
+ return 0, 0
33
33
 
34
34
  self.title = Path(urlparse(self.url).path).name
35
35
 
@@ -37,7 +37,8 @@ class DownloadTelegram(DownloadBase):
37
37
 
38
38
  return anyio.run(self.save_stickers)
39
39
 
40
- async def save_stickers(self) -> bool:
40
+ async def save_stickers(self) -> Tuple[int, int]:
41
+ results: dict[str, bool] = {}
41
42
  timeout = 30
42
43
  application = ( # type: ignore
43
44
  ApplicationBuilder()
@@ -65,7 +66,7 @@ class DownloadTelegram(DownloadBase):
65
66
  self.cb.put(
66
67
  f"Failed to download telegram sticker set {self.title} due to: {e}"
67
68
  )
68
- return False
69
+ return 0, 0
69
70
 
70
71
  self.cb.put(
71
72
  (
@@ -79,14 +80,19 @@ class DownloadTelegram(DownloadBase):
79
80
  )
80
81
 
81
82
  async def download_sticker(
82
- sticker: Union[PhotoSize, Sticker], f_id: str
83
+ sticker: Union[PhotoSize, Sticker], f_id: str, results: dict[str, bool]
83
84
  ) -> None:
84
- sticker_file = await sticker.get_file(
85
- read_timeout=timeout,
86
- write_timeout=timeout,
87
- connect_timeout=timeout,
88
- pool_timeout=timeout,
89
- )
85
+ try:
86
+ sticker_file = await sticker.get_file(
87
+ read_timeout=timeout,
88
+ write_timeout=timeout,
89
+ connect_timeout=timeout,
90
+ pool_timeout=timeout,
91
+ )
92
+ except TelegramError as e:
93
+ self.cb.put(f"Failed to download {f_id}: {str(e)}")
94
+ results[f_id] = False
95
+ return
90
96
  fpath = sticker_file.file_path
91
97
  assert fpath is not None
92
98
  ext = Path(fpath).suffix
@@ -102,21 +108,25 @@ class DownloadTelegram(DownloadBase):
102
108
  if isinstance(sticker, Sticker) and sticker.emoji is not None:
103
109
  self.emoji_dict[f_id] = sticker.emoji
104
110
  self.cb.put(f"Downloaded {f_name}")
111
+ results[f_id] = True
105
112
  if f_id != "cover":
106
113
  self.cb.put("update_bar")
107
114
 
108
115
  async with anyio.create_task_group() as tg:
109
116
  for num, sticker in enumerate(sticker_set.stickers):
110
117
  f_id = str(num).zfill(3)
111
- tg.start_soon(download_sticker, sticker, f_id)
118
+ tg.start_soon(download_sticker, sticker, f_id, results)
112
119
 
113
120
  if sticker_set.thumbnail is not None:
114
- tg.start_soon(download_sticker, sticker_set.thumbnail, "cover")
121
+ results_thumb: dict[str, bool] = {}
122
+ tg.start_soon(
123
+ download_sticker, sticker_set.thumbnail, "cover", results_thumb
124
+ )
115
125
 
116
126
  MetadataHandler.set_metadata(
117
127
  self.out_dir, title=self.title, emoji_dict=self.emoji_dict
118
128
  )
119
- return True
129
+ return sum(results.values()), len(sticker_set.stickers)
120
130
 
121
131
  @staticmethod
122
132
  def start(
@@ -124,6 +134,6 @@ class DownloadTelegram(DownloadBase):
124
134
  opt_cred: Optional[CredOption],
125
135
  cb: CallbackProtocol,
126
136
  cb_return: CallbackReturn,
127
- ) -> bool:
137
+ ) -> Tuple[int, int]:
128
138
  downloader = DownloadTelegram(opt_input, opt_cred, cb, cb_return)
129
139
  return downloader.download_stickers_telegram()
@@ -44,7 +44,7 @@ class DownloadViber(DownloadBase):
44
44
 
45
45
  return title, zip_url
46
46
 
47
- def decompress(self, zip_file: bytes) -> None:
47
+ def decompress(self, zip_file: bytes) -> int:
48
48
  with zipfile.ZipFile(BytesIO(zip_file)) as zf:
49
49
  self.cb.put("Unzipping...")
50
50
 
@@ -69,19 +69,21 @@ class DownloadViber(DownloadBase):
69
69
 
70
70
  self.cb.put("update_bar")
71
71
 
72
- def download_stickers_viber(self) -> bool:
72
+ return len(zf_files)
73
+
74
+ def download_stickers_viber(self) -> Tuple[int, int]:
73
75
  pack_info = self.get_pack_info(self.url)
74
76
  if pack_info is None:
75
77
  self.cb.put("Download failed: Cannot get pack info")
76
- return False
78
+ return 0, 0
77
79
  title, zip_url = pack_info
78
80
 
79
81
  zip_file = self.download_file(zip_url)
80
- self.decompress(zip_file)
82
+ count = self.decompress(zip_file)
81
83
 
82
84
  MetadataHandler.set_metadata(self.out_dir, title=title)
83
85
 
84
- return True
86
+ return count, count
85
87
 
86
88
  @staticmethod
87
89
  def start(
@@ -89,6 +91,6 @@ class DownloadViber(DownloadBase):
89
91
  opt_cred: Optional[CredOption],
90
92
  cb: CallbackProtocol,
91
93
  cb_return: CallbackReturn,
92
- ) -> bool:
94
+ ) -> Tuple[int, int]:
93
95
  downloader = DownloadViber(opt_input, opt_cred, cb, cb_return)
94
96
  return downloader.download_stickers_viber()