sticker-convert 2.13.3.0__py3-none-any.whl → 2.17.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. sticker_convert/__main__.py +24 -27
  2. sticker_convert/auth/__init__.py +0 -0
  3. sticker_convert/auth/auth_base.py +19 -0
  4. sticker_convert/{utils/auth/get_discord_auth.py → auth/auth_discord.py} +149 -118
  5. sticker_convert/{utils/auth/get_kakao_auth.py → auth/auth_kakao_android_login.py} +331 -330
  6. sticker_convert/auth/auth_kakao_desktop_login.py +327 -0
  7. sticker_convert/{utils/auth/get_kakao_desktop_auth.py → auth/auth_kakao_desktop_memdump.py} +281 -263
  8. sticker_convert/{utils/auth/get_line_auth.py → auth/auth_line.py} +98 -80
  9. sticker_convert/{utils/auth/get_signal_auth.py → auth/auth_signal.py} +139 -135
  10. sticker_convert/auth/auth_telethon.py +161 -0
  11. sticker_convert/{utils/auth/get_viber_auth.py → auth/auth_viber.py} +250 -235
  12. sticker_convert/{utils/auth → auth}/telegram_api.py +736 -675
  13. sticker_convert/cli.py +623 -608
  14. sticker_convert/converter.py +1093 -1084
  15. sticker_convert/definitions.py +4 -0
  16. sticker_convert/downloaders/download_band.py +111 -110
  17. sticker_convert/downloaders/download_base.py +171 -166
  18. sticker_convert/downloaders/download_discord.py +92 -91
  19. sticker_convert/downloaders/download_kakao.py +417 -404
  20. sticker_convert/downloaders/download_line.py +484 -475
  21. sticker_convert/downloaders/download_ogq.py +80 -79
  22. sticker_convert/downloaders/download_signal.py +108 -105
  23. sticker_convert/downloaders/download_telegram.py +56 -55
  24. sticker_convert/downloaders/download_viber.py +121 -120
  25. sticker_convert/gui.py +788 -873
  26. sticker_convert/gui_components/frames/comp_frame.py +180 -166
  27. sticker_convert/gui_components/frames/config_frame.py +156 -113
  28. sticker_convert/gui_components/frames/control_frame.py +32 -30
  29. sticker_convert/gui_components/frames/cred_frame.py +232 -233
  30. sticker_convert/gui_components/frames/input_frame.py +139 -137
  31. sticker_convert/gui_components/frames/output_frame.py +112 -110
  32. sticker_convert/gui_components/frames/right_clicker.py +25 -23
  33. sticker_convert/gui_components/windows/advanced_compression_window.py +757 -757
  34. sticker_convert/gui_components/windows/base_window.py +7 -2
  35. sticker_convert/gui_components/windows/discord_get_auth_window.py +79 -82
  36. sticker_convert/gui_components/windows/kakao_get_auth_window.py +511 -321
  37. sticker_convert/gui_components/windows/line_get_auth_window.py +94 -102
  38. sticker_convert/gui_components/windows/signal_get_auth_window.py +84 -89
  39. sticker_convert/gui_components/windows/viber_get_auth_window.py +168 -168
  40. sticker_convert/ios-message-stickers-template/.github/FUNDING.yml +3 -3
  41. sticker_convert/ios-message-stickers-template/README.md +10 -10
  42. sticker_convert/ios-message-stickers-template/stickers/Info.plist +43 -43
  43. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Info.plist +31 -31
  44. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Contents.json +6 -6
  45. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Contents.json +20 -20
  46. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Contents.json +9 -9
  47. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 2.sticker/Contents.json +9 -9
  48. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 3.sticker/Contents.json +9 -9
  49. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Contents.json +91 -91
  50. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.pbxproj +364 -364
  51. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -7
  52. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -8
  53. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/xcuserdata/niklaspeterson.xcuserdatad/xcschemes/xcschememanagement.plist +14 -14
  54. sticker_convert/job.py +166 -130
  55. sticker_convert/job_option.py +1 -0
  56. sticker_convert/locales/en_US/LC_MESSAGES/base.mo +0 -0
  57. sticker_convert/locales/ja_JP/LC_MESSAGES/base.mo +0 -0
  58. sticker_convert/locales/zh_CN/LC_MESSAGES/base.mo +0 -0
  59. sticker_convert/locales/zh_TW/LC_MESSAGES/base.mo +0 -0
  60. sticker_convert/py.typed +0 -0
  61. sticker_convert/resources/NotoColorEmoji.ttf +0 -0
  62. sticker_convert/resources/help.ja_JP.json +88 -0
  63. sticker_convert/resources/help.json +10 -7
  64. sticker_convert/resources/help.zh_CN.json +88 -0
  65. sticker_convert/resources/help.zh_TW.json +88 -0
  66. sticker_convert/resources/input.ja_JP.json +74 -0
  67. sticker_convert/resources/input.json +121 -121
  68. sticker_convert/resources/input.zh_CN.json +74 -0
  69. sticker_convert/resources/input.zh_TW.json +74 -0
  70. sticker_convert/resources/output.ja_JP.json +38 -0
  71. sticker_convert/resources/output.zh_CN.json +38 -0
  72. sticker_convert/resources/output.zh_TW.json +38 -0
  73. sticker_convert/uploaders/compress_wastickers.py +186 -177
  74. sticker_convert/uploaders/upload_base.py +44 -35
  75. sticker_convert/uploaders/upload_signal.py +218 -203
  76. sticker_convert/uploaders/upload_telegram.py +353 -338
  77. sticker_convert/uploaders/upload_viber.py +178 -169
  78. sticker_convert/uploaders/xcode_imessage.py +295 -286
  79. sticker_convert/utils/callback.py +238 -6
  80. sticker_convert/utils/emoji.py +16 -4
  81. sticker_convert/utils/files/json_resources_loader.py +24 -19
  82. sticker_convert/utils/files/metadata_handler.py +3 -3
  83. sticker_convert/utils/translate.py +108 -0
  84. sticker_convert/utils/url_detect.py +40 -37
  85. sticker_convert/version.py +1 -1
  86. {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/METADATA +89 -74
  87. {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/RECORD +91 -74
  88. sticker_convert/utils/auth/telethon_setup.py +0 -97
  89. sticker_convert/utils/singletons.py +0 -18
  90. {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/WHEEL +0 -0
  91. {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/entry_points.txt +0 -0
  92. {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/licenses/LICENSE +0 -0
  93. {sticker_convert-2.13.3.0.dist-info → sticker_convert-2.17.0.0.dist-info}/top_level.txt +0 -0
@@ -1,338 +1,353 @@
1
- #!/usr/bin/env python3
2
- import copy
3
- from pathlib import Path
4
- from typing import Any, Dict, List, Optional, Tuple, Union, cast
5
-
6
- import anyio
7
- from telegram import Sticker
8
-
9
- from sticker_convert.converter import StickerConvert
10
- from sticker_convert.job_option import CompOption, CredOption, OutputOption
11
- from sticker_convert.uploaders.upload_base import UploadBase
12
- from sticker_convert.utils.auth.telegram_api import BotAPI, TelegramAPI, TelegramSticker, TelethonAPI
13
- from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
14
- from sticker_convert.utils.emoji import extract_emojis
15
- from sticker_convert.utils.files.metadata_handler import MetadataHandler
16
- from sticker_convert.utils.media.codec_info import CodecInfo
17
- from sticker_convert.utils.media.format_verify import FormatVerify
18
-
19
-
20
- class UploadTelegram(UploadBase):
21
- def __init__(self, *args: Any, **kwargs: Any) -> None:
22
- super().__init__(*args, **kwargs)
23
-
24
- self.base_spec.size_max_img = 512000
25
- self.base_spec.size_max_vid = 256000
26
- self.base_spec.duration_max = 3000
27
- self.base_spec.set_res(512)
28
-
29
- self.png_spec = copy.deepcopy(self.base_spec)
30
- self.png_spec.set_format((".png",))
31
- self.png_spec.animated = False
32
-
33
- self.tgs_spec = copy.deepcopy(self.base_spec)
34
- self.tgs_spec.set_format((".tgs",))
35
- self.tgs_spec.fps_min = 60
36
- self.tgs_spec.fps_max = 60
37
- self.tgs_spec.size_max_img = 64000
38
- self.tgs_spec.size_max_vid = 64000
39
-
40
- self.webm_spec = copy.deepcopy(self.base_spec)
41
- self.webm_spec.set_format((".webm",))
42
- self.webm_spec.fps_max = 30
43
- self.webm_spec.animated = None if self.opt_comp.fake_vid else True
44
-
45
- self.opt_comp_merged = copy.deepcopy(self.opt_comp)
46
- self.opt_comp_merged.merge(self.base_spec)
47
-
48
- base_cover_spec = CompOption(
49
- size_max_img=128000, size_max_vid=32000, duration_max=3000
50
- )
51
- base_cover_spec.set_res(100)
52
-
53
- self.png_cover_spec = copy.deepcopy(base_cover_spec)
54
- self.png_cover_spec.set_format((".png",))
55
- self.png_cover_spec.animated = False
56
-
57
- self.tgs_cover_spec = copy.deepcopy(base_cover_spec)
58
- self.tgs_cover_spec.set_format((".tgs",))
59
- self.tgs_cover_spec.fps_min = 60
60
- self.tgs_cover_spec.fps_max = 60
61
-
62
- self.webm_cover_spec = copy.deepcopy(base_cover_spec)
63
- self.webm_cover_spec.set_format((".webm",))
64
- self.webm_cover_spec.fps_max = 30
65
- self.webm_cover_spec.animated = True
66
-
67
- self.opt_comp_cover_merged = copy.deepcopy(self.opt_comp)
68
- self.opt_comp_cover_merged.merge(self.base_spec)
69
-
70
- async def upload_pack(
71
- self, pack_title: str, stickers: List[Path], emoji_dict: Dict[str, str]
72
- ) -> Tuple[Optional[str], int, int]:
73
- tg_api: TelegramAPI
74
- if self.opt_output.option.endswith("telethon"):
75
- tg_api = TelethonAPI()
76
- else:
77
- tg_api = BotAPI()
78
-
79
- is_emoji = False
80
- if "emoji" in self.opt_output.option:
81
- is_emoji = True
82
-
83
- success = await tg_api.setup(self.opt_cred, True, self.cb, self.cb_return)
84
- if success is False:
85
- self.cb.put("Download failed: Invalid credentials")
86
- return None, len(stickers), 0
87
-
88
- pack_short_name = await tg_api.set_upload_pack_short_name(pack_title)
89
- await tg_api.set_upload_pack_type(is_emoji)
90
- pack_exist = await tg_api.check_pack_exist()
91
- if pack_exist:
92
- question = f"Warning: Pack {pack_short_name} already exists.\n"
93
- question += "Delete all stickers in pack?\n"
94
- question += "Note: After recreating set, please wait for about 3 minutes for the set to reappear."
95
-
96
- self.cb.put(
97
- (
98
- "ask_bool",
99
- (question,),
100
- None,
101
- )
102
- )
103
- if self.cb_return:
104
- response = self.cb_return.get_response()
105
- else:
106
- response = False
107
-
108
- if response is True:
109
- self.cb.put(f"Deleting all stickers from pack {pack_short_name}")
110
- await tg_api.pack_del()
111
- pack_exist = False
112
- else:
113
- self.cb.put(f"Not deleting existing pack {pack_short_name}")
114
-
115
- if self.opt_output.option == "telegram_emoji":
116
- sticker_type = Sticker.CUSTOM_EMOJI
117
- else:
118
- sticker_type = Sticker.REGULAR
119
-
120
- stickers_list: List[TelegramSticker] = []
121
- sticker_format = None
122
- for src in stickers:
123
- self.cb.put(f"Verifying {src} for uploading to telegram")
124
-
125
- emoji = extract_emojis(emoji_dict.get(Path(src).stem, ""))
126
- if emoji == "":
127
- self.cb.put(
128
- f"Warning: Cannot find emoji for file {Path(src).name}, using default emoji..."
129
- )
130
- emoji_list = [self.opt_comp.default_emoji]
131
-
132
- if len(emoji) > 20:
133
- self.cb.put(
134
- f"Warning: {len(emoji)} emoji for file {Path(src).name}, exceeding limit of 20, keep first 20 only..."
135
- )
136
- emoji_list = [*emoji][:20]
137
-
138
- ext = Path(src).suffix
139
- if ext == ".tgs":
140
- spec_choice = self.tgs_spec
141
- sticker_format = "animated"
142
- elif ext == ".webm":
143
- spec_choice = self.webm_spec
144
- sticker_format = "video"
145
- else:
146
- ext = ".png"
147
- spec_choice = self.png_spec
148
- sticker_format = "static"
149
-
150
- if self.opt_output.option == "telegram_emoji":
151
- spec_choice.set_res(100)
152
-
153
- file_info = CodecInfo(src)
154
- check_file_result = (
155
- FormatVerify.check_file_fps(
156
- src, fps=spec_choice.get_fps(), file_info=file_info
157
- )
158
- and FormatVerify.check_file_duration(
159
- src, duration=spec_choice.get_duration(), file_info=file_info
160
- )
161
- and FormatVerify.check_file_size(
162
- src, size=spec_choice.get_size_max(), file_info=file_info
163
- )
164
- and FormatVerify.check_format(
165
- src, fmt=spec_choice.get_format(), file_info=file_info
166
- )
167
- )
168
- if self.opt_output.option == "telegram":
169
- if sticker_format == "animated":
170
- check_file_result = (
171
- check_file_result
172
- and file_info.res[0] == 512
173
- and file_info.res[1] == 512
174
- )
175
- else:
176
- # For video and static stickers (Not animated)
177
- # Allow file with one of the dimension = 512 but another <512
178
- # https://core.telegram.org/stickers#video-requirements
179
- check_file_result = check_file_result and (
180
- file_info.res[0] == 512 or file_info.res[1] == 512
181
- )
182
- check_file_result = check_file_result and (
183
- file_info.res[0] <= 512 and file_info.res[1] <= 512
184
- )
185
- else:
186
- # telegram_emoji
187
- check_file_result = (
188
- check_file_result
189
- and file_info.res[0] == 100
190
- and file_info.res[1] == 100
191
- )
192
-
193
- if sticker_format == "static":
194
- # It is important to check if webp and png are static only
195
- check_file_result = check_file_result and FormatVerify.check_animated(
196
- src, animated=spec_choice.animated, file_info=file_info
197
- )
198
-
199
- if check_file_result:
200
- with open(src, "rb") as f:
201
- sticker_bytes = f.read()
202
- else:
203
- _, _, convert_result, _ = StickerConvert.convert(
204
- Path(src),
205
- Path(f"bytes{ext}"),
206
- self.opt_comp_merged,
207
- self.cb,
208
- self.cb_return,
209
- )
210
- sticker_bytes = cast(bytes, convert_result)
211
-
212
- stickers_list.append((src, sticker_bytes, emoji_list, sticker_format))
213
-
214
- if pack_exist is False:
215
- stickers_total, stickers_ok = await tg_api.pack_new(
216
- stickers_list, sticker_type
217
- )
218
- pack_exist = True
219
- else:
220
- stickers_total, stickers_ok = await tg_api.pack_add(
221
- stickers_list, sticker_type
222
- )
223
-
224
- cover_path = MetadataHandler.get_cover(self.opt_output.dir)
225
- if cover_path:
226
- thumbnail_bytes: Union[None, bytes, Path] = None
227
- cover_ext = Path(cover_path).suffix
228
-
229
- if cover_ext == ".tgs":
230
- thumbnail_format = "animated"
231
- cover_spec_choice = self.tgs_cover_spec
232
- elif cover_ext == ".webm":
233
- thumbnail_format = "video"
234
- cover_spec_choice = self.webm_cover_spec
235
- else:
236
- cover_ext = ".png"
237
- thumbnail_format = "static"
238
- cover_spec_choice = self.png_cover_spec
239
-
240
- if FormatVerify.check_file(cover_path, spec=cover_spec_choice):
241
- with open(cover_path, "rb") as f:
242
- thumbnail_bytes = f.read()
243
- else:
244
- _, _, thumbnail_bytes, _ = cast(
245
- Tuple[Any, Any, bytes, Any],
246
- StickerConvert.convert(
247
- cover_path,
248
- Path(f"bytes{cover_ext}"),
249
- self.opt_comp_cover_merged,
250
- self.cb,
251
- self.cb_return,
252
- ),
253
- )
254
-
255
- await tg_api.pack_thumbnail(
256
- (cover_path, thumbnail_bytes, [], thumbnail_format)
257
- )
258
-
259
- self.cb.put(f"Finish uploading {pack_short_name}")
260
- await tg_api.exit()
261
- return await tg_api.get_pack_url(), stickers_total, stickers_ok
262
-
263
- def upload_stickers_telegram(self) -> Tuple[int, int, List[str]]:
264
- urls: List[str] = []
265
-
266
- title, _, emoji_dict = MetadataHandler.get_metadata(
267
- self.opt_output.dir,
268
- title=self.opt_output.title,
269
- author=self.opt_output.author,
270
- )
271
- if title is None:
272
- raise TypeError("title cannot be", title)
273
- if emoji_dict is None:
274
- msg_block = "emoji.txt is required for uploading signal stickers\n"
275
- msg_block += f"emoji.txt generated for you in {self.opt_output.dir}\n"
276
- msg_block += f"Default emoji is set to {self.opt_comp.default_emoji}.\n"
277
- msg_block += "Please edit emoji.txt now, then continue"
278
- MetadataHandler.generate_emoji_file(
279
- directory=self.opt_output.dir, default_emoji=self.opt_comp.default_emoji
280
- )
281
-
282
- self.cb.put(("msg_block", (msg_block,), None))
283
- if self.cb_return:
284
- self.cb_return.get_response()
285
-
286
- title, _, emoji_dict = MetadataHandler.get_metadata(
287
- self.opt_output.dir,
288
- title=self.opt_output.title,
289
- author=self.opt_output.author,
290
- )
291
-
292
- assert title is not None
293
- assert emoji_dict is not None
294
-
295
- if self.opt_output.option == "telegram_emoji":
296
- file_per_pack = 200
297
- else:
298
- file_per_pack = 120
299
-
300
- packs = MetadataHandler.split_sticker_packs(
301
- self.opt_output.dir,
302
- title=title,
303
- file_per_anim_pack=file_per_pack,
304
- file_per_image_pack=file_per_pack,
305
- separate_image_anim=not self.opt_comp.fake_vid,
306
- )
307
-
308
- stickers_total = 0
309
- stickers_ok = 0
310
- for pack_title, stickers in packs.items():
311
- self.cb.put(f"Uploading pack {pack_title}")
312
- result, stickers_total_pack, stickers_ok_pack = anyio.run(
313
- self.upload_pack, pack_title, stickers, emoji_dict
314
- )
315
- if result:
316
- self.cb.put((result))
317
- urls.append(result)
318
- stickers_total += stickers_total_pack
319
- stickers_ok += stickers_ok_pack
320
-
321
- return stickers_ok, stickers_total, urls
322
-
323
- @staticmethod
324
- def start(
325
- opt_output: OutputOption,
326
- opt_comp: CompOption,
327
- opt_cred: CredOption,
328
- cb: CallbackProtocol,
329
- cb_return: CallbackReturn,
330
- ) -> Tuple[int, int, List[str]]:
331
- exporter = UploadTelegram(
332
- opt_output,
333
- opt_comp,
334
- opt_cred,
335
- cb,
336
- cb_return,
337
- )
338
- return exporter.upload_stickers_telegram()
1
+ #!/usr/bin/env python3
2
+ import copy
3
+ from pathlib import Path
4
+ from typing import Any, Dict, List, Optional, Tuple, Union, cast
5
+
6
+ import anyio
7
+ from telegram import Sticker
8
+
9
+ from sticker_convert.auth.telegram_api import BotAPI, TelegramAPI, TelegramSticker, TelethonAPI
10
+ from sticker_convert.converter import StickerConvert
11
+ from sticker_convert.job_option import CompOption, CredOption, OutputOption
12
+ from sticker_convert.uploaders.upload_base import UploadBase, get_msg_emoji_txt_required
13
+ from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
14
+ from sticker_convert.utils.emoji import extract_emojis
15
+ from sticker_convert.utils.files.metadata_handler import MetadataHandler
16
+ from sticker_convert.utils.media.codec_info import CodecInfo
17
+ from sticker_convert.utils.media.format_verify import FormatVerify
18
+ from sticker_convert.utils.translate import I
19
+
20
+
21
+ class UploadTelegram(UploadBase):
22
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
23
+ self.MSG_ASK_DEL_EXIST_PACK = I(
24
+ "Warning: Pack {} already exists.\n"
25
+ "Delete all stickers in pack?\n"
26
+ "Note: After recreating set, please wait for about 3 minutes for the set to reappear."
27
+ )
28
+
29
+ super().__init__(*args, **kwargs)
30
+
31
+ self.base_spec.size_max_img = 512000
32
+ self.base_spec.size_max_vid = 256000
33
+ self.base_spec.duration_max = 3000
34
+ self.base_spec.set_res(512)
35
+
36
+ self.png_spec = copy.deepcopy(self.base_spec)
37
+ self.png_spec.set_format((".png",))
38
+ self.png_spec.animated = False
39
+
40
+ self.tgs_spec = copy.deepcopy(self.base_spec)
41
+ self.tgs_spec.set_format((".tgs",))
42
+ self.tgs_spec.fps_min = 60
43
+ self.tgs_spec.fps_max = 60
44
+ self.tgs_spec.size_max_img = 64000
45
+ self.tgs_spec.size_max_vid = 64000
46
+
47
+ self.webm_spec = copy.deepcopy(self.base_spec)
48
+ self.webm_spec.set_format((".webm",))
49
+ self.webm_spec.fps_max = 30
50
+ self.webm_spec.animated = None if self.opt_comp.fake_vid else True
51
+
52
+ self.opt_comp_merged = copy.deepcopy(self.opt_comp)
53
+ self.opt_comp_merged.merge(self.base_spec)
54
+
55
+ base_cover_spec = CompOption(
56
+ size_max_img=128000, size_max_vid=32000, duration_max=3000
57
+ )
58
+ base_cover_spec.set_res(100)
59
+
60
+ self.png_cover_spec = copy.deepcopy(base_cover_spec)
61
+ self.png_cover_spec.set_format((".png",))
62
+ self.png_cover_spec.animated = False
63
+
64
+ self.tgs_cover_spec = copy.deepcopy(base_cover_spec)
65
+ self.tgs_cover_spec.set_format((".tgs",))
66
+ self.tgs_cover_spec.fps_min = 60
67
+ self.tgs_cover_spec.fps_max = 60
68
+
69
+ self.webm_cover_spec = copy.deepcopy(base_cover_spec)
70
+ self.webm_cover_spec.set_format((".webm",))
71
+ self.webm_cover_spec.fps_max = 30
72
+ self.webm_cover_spec.animated = True
73
+
74
+ self.opt_comp_cover_merged = copy.deepcopy(self.opt_comp)
75
+ self.opt_comp_cover_merged.merge(self.base_spec)
76
+
77
+ async def upload_pack(
78
+ self, pack_title: str, stickers: List[Path], emoji_dict: Dict[str, str]
79
+ ) -> Tuple[Optional[str], int, int]:
80
+ tg_api: TelegramAPI
81
+ if self.opt_output.option.endswith("telethon"):
82
+ tg_api = TelethonAPI()
83
+ else:
84
+ tg_api = BotAPI()
85
+
86
+ is_emoji = False
87
+ if "emoji" in self.opt_output.option:
88
+ is_emoji = True
89
+
90
+ success = await tg_api.setup(self.opt_cred, True, self.cb, self.cb_return)
91
+ if success is False:
92
+ self.cb.put(I("Download failed: Invalid credentials"))
93
+ return None, len(stickers), 0
94
+
95
+ pack_short_name = await tg_api.set_upload_pack_short_name(pack_title)
96
+ await tg_api.set_upload_pack_type(is_emoji)
97
+ pack_exist = await tg_api.check_pack_exist()
98
+ if pack_exist:
99
+ self.cb.put(
100
+ (
101
+ "ask_bool",
102
+ (self.MSG_ASK_DEL_EXIST_PACK.format(pack_short_name),),
103
+ None,
104
+ )
105
+ )
106
+ if self.cb_return:
107
+ response = self.cb_return.get_response()
108
+ else:
109
+ response = False
110
+
111
+ if response is True:
112
+ self.cb.put(
113
+ I("Deleting all stickers from pack {}").format(pack_short_name)
114
+ )
115
+ await tg_api.pack_del()
116
+ pack_exist = False
117
+ else:
118
+ self.cb.put(I("Not deleting existing pack {}").format(pack_short_name))
119
+
120
+ if self.opt_output.option == "telegram_emoji":
121
+ sticker_type = Sticker.CUSTOM_EMOJI
122
+ else:
123
+ sticker_type = Sticker.REGULAR
124
+
125
+ stickers_list: List[TelegramSticker] = []
126
+ sticker_format = None
127
+ for src in stickers:
128
+ self.cb.put(I("Verifying {} for uploading to telegram").format(src))
129
+
130
+ emoji = extract_emojis(emoji_dict.get(Path(src).stem, ""))
131
+ if emoji == "":
132
+ self.cb.put(
133
+ I(
134
+ "Warning: Cannot find emoji for file {}, using default emoji..."
135
+ ).format(Path(src).name)
136
+ )
137
+ emoji = self.opt_comp.default_emoji
138
+
139
+ if len(emoji) > 20:
140
+ self.cb.put(
141
+ I(
142
+ "Warning: {emoji} emoji for file {file}, exceeding limit of 20, keep first 20 only..."
143
+ ).format(emoji=len(emoji), file=Path(src).name)
144
+ )
145
+ emoji_list = [*emoji][:20]
146
+
147
+ ext = Path(src).suffix
148
+ if ext == ".tgs":
149
+ spec_choice = self.tgs_spec
150
+ sticker_format = "animated"
151
+ elif ext == ".webm":
152
+ spec_choice = self.webm_spec
153
+ sticker_format = "video"
154
+ else:
155
+ ext = ".png"
156
+ spec_choice = self.png_spec
157
+ sticker_format = "static"
158
+
159
+ if self.opt_output.option == "telegram_emoji":
160
+ spec_choice.set_res(100)
161
+
162
+ file_info = CodecInfo(src)
163
+ check_file_result = (
164
+ FormatVerify.check_file_fps(
165
+ src, fps=spec_choice.get_fps(), file_info=file_info
166
+ )
167
+ and FormatVerify.check_file_duration(
168
+ src, duration=spec_choice.get_duration(), file_info=file_info
169
+ )
170
+ and FormatVerify.check_file_size(
171
+ src, size=spec_choice.get_size_max(), file_info=file_info
172
+ )
173
+ and FormatVerify.check_format(
174
+ src, fmt=spec_choice.get_format(), file_info=file_info
175
+ )
176
+ )
177
+ if self.opt_output.option == "telegram":
178
+ if sticker_format == "animated":
179
+ check_file_result = (
180
+ check_file_result
181
+ and file_info.res[0] == 512
182
+ and file_info.res[1] == 512
183
+ )
184
+ else:
185
+ # For video and static stickers (Not animated)
186
+ # Allow file with one of the dimension = 512 but another <512
187
+ # https://core.telegram.org/stickers#video-requirements
188
+ check_file_result = check_file_result and (
189
+ file_info.res[0] == 512 or file_info.res[1] == 512
190
+ )
191
+ check_file_result = check_file_result and (
192
+ file_info.res[0] <= 512 and file_info.res[1] <= 512
193
+ )
194
+ else:
195
+ # telegram_emoji
196
+ check_file_result = (
197
+ check_file_result
198
+ and file_info.res[0] == 100
199
+ and file_info.res[1] == 100
200
+ )
201
+
202
+ if sticker_format == "static":
203
+ # It is important to check if webp and png are static only
204
+ check_file_result = check_file_result and FormatVerify.check_animated(
205
+ src, animated=spec_choice.animated, file_info=file_info
206
+ )
207
+
208
+ if check_file_result:
209
+ with open(src, "rb") as f:
210
+ sticker_bytes = f.read()
211
+ else:
212
+ _, _, convert_result, _ = StickerConvert.convert(
213
+ Path(src),
214
+ Path(f"bytes{ext}"),
215
+ self.opt_comp_merged,
216
+ self.cb,
217
+ self.cb_return,
218
+ )
219
+ sticker_bytes = cast(bytes, convert_result)
220
+
221
+ stickers_list.append((src, sticker_bytes, emoji_list, sticker_format))
222
+
223
+ if pack_exist is False:
224
+ stickers_total, stickers_ok = await tg_api.pack_new(
225
+ stickers_list, sticker_type
226
+ )
227
+ pack_exist = True
228
+ else:
229
+ stickers_total, stickers_ok = await tg_api.pack_add(
230
+ stickers_list, sticker_type
231
+ )
232
+
233
+ cover_path = MetadataHandler.get_cover(self.opt_output.dir)
234
+ if cover_path:
235
+ thumbnail_bytes: Union[None, bytes, Path] = None
236
+ cover_ext = Path(cover_path).suffix
237
+
238
+ if cover_ext == ".tgs":
239
+ thumbnail_format = "animated"
240
+ cover_spec_choice = self.tgs_cover_spec
241
+ elif cover_ext == ".webm":
242
+ thumbnail_format = "video"
243
+ cover_spec_choice = self.webm_cover_spec
244
+ else:
245
+ cover_ext = ".png"
246
+ thumbnail_format = "static"
247
+ cover_spec_choice = self.png_cover_spec
248
+
249
+ if FormatVerify.check_file(cover_path, spec=cover_spec_choice):
250
+ with open(cover_path, "rb") as f:
251
+ thumbnail_bytes = f.read()
252
+ else:
253
+ _, _, thumbnail_bytes, _ = cast(
254
+ Tuple[Any, Any, bytes, Any],
255
+ StickerConvert.convert(
256
+ cover_path,
257
+ Path(f"bytes{cover_ext}"),
258
+ self.opt_comp_cover_merged,
259
+ self.cb,
260
+ self.cb_return,
261
+ ),
262
+ )
263
+
264
+ await tg_api.pack_thumbnail(
265
+ (cover_path, thumbnail_bytes, [], thumbnail_format)
266
+ )
267
+
268
+ self.cb.put(I("Finish uploading {}").format(pack_short_name))
269
+ await tg_api.exit()
270
+ return await tg_api.get_pack_url(), stickers_total, stickers_ok
271
+
272
+ def upload_stickers_telegram(self) -> Tuple[int, int, List[str]]:
273
+ urls: List[str] = []
274
+
275
+ title, _, emoji_dict = MetadataHandler.get_metadata(
276
+ self.opt_output.dir,
277
+ title=self.opt_output.title,
278
+ author=self.opt_output.author,
279
+ )
280
+ if title is None:
281
+ raise TypeError(I("title cannot be {}").format(title))
282
+ if emoji_dict is None:
283
+ MetadataHandler.generate_emoji_file(
284
+ directory=self.opt_output.dir, default_emoji=self.opt_comp.default_emoji
285
+ )
286
+
287
+ self.cb.put(
288
+ (
289
+ "msg_block",
290
+ (
291
+ get_msg_emoji_txt_required().format(
292
+ self.opt_output.dir, self.opt_comp.default_emoji
293
+ ),
294
+ ),
295
+ None,
296
+ )
297
+ )
298
+ if self.cb_return:
299
+ self.cb_return.get_response()
300
+
301
+ title, _, emoji_dict = MetadataHandler.get_metadata(
302
+ self.opt_output.dir,
303
+ title=self.opt_output.title,
304
+ author=self.opt_output.author,
305
+ )
306
+
307
+ assert title is not None
308
+ assert emoji_dict is not None
309
+
310
+ if self.opt_output.option == "telegram_emoji":
311
+ file_per_pack = 200
312
+ else:
313
+ file_per_pack = 120
314
+
315
+ packs = MetadataHandler.split_sticker_packs(
316
+ self.opt_output.dir,
317
+ title=title,
318
+ file_per_anim_pack=file_per_pack,
319
+ file_per_image_pack=file_per_pack,
320
+ separate_image_anim=not self.opt_comp.fake_vid,
321
+ )
322
+
323
+ stickers_total = 0
324
+ stickers_ok = 0
325
+ for pack_title, stickers in packs.items():
326
+ self.cb.put(I("Uploading pack {}").format(pack_title))
327
+ result, stickers_total_pack, stickers_ok_pack = anyio.run(
328
+ self.upload_pack, pack_title, stickers, emoji_dict
329
+ )
330
+ if result:
331
+ self.cb.put((result))
332
+ urls.append(result)
333
+ stickers_total += stickers_total_pack
334
+ stickers_ok += stickers_ok_pack
335
+
336
+ return stickers_ok, stickers_total, urls
337
+
338
+ @staticmethod
339
+ def start(
340
+ opt_output: OutputOption,
341
+ opt_comp: CompOption,
342
+ opt_cred: CredOption,
343
+ cb: CallbackProtocol,
344
+ cb_return: CallbackReturn,
345
+ ) -> Tuple[int, int, List[str]]:
346
+ exporter = UploadTelegram(
347
+ opt_output,
348
+ opt_comp,
349
+ opt_cred,
350
+ cb,
351
+ cb_return,
352
+ )
353
+ return exporter.upload_stickers_telegram()