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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. sticker_convert/__main__.py +24 -24
  2. sticker_convert/auth/__init__.py +0 -0
  3. sticker_convert/auth/auth_base.py +19 -0
  4. sticker_convert/auth/auth_discord.py +149 -0
  5. sticker_convert/{utils/auth/get_kakao_auth.py → auth/auth_kakao_android_login.py} +331 -300
  6. sticker_convert/auth/auth_kakao_desktop_login.py +327 -0
  7. sticker_convert/auth/auth_kakao_desktop_memdump.py +281 -0
  8. sticker_convert/{utils/auth/get_line_auth.py → auth/auth_line.py} +98 -80
  9. sticker_convert/auth/auth_signal.py +139 -0
  10. sticker_convert/auth/auth_telethon.py +161 -0
  11. sticker_convert/auth/auth_viber.py +250 -0
  12. sticker_convert/auth/telegram_api.py +736 -0
  13. sticker_convert/cli.py +623 -509
  14. sticker_convert/converter.py +1093 -962
  15. sticker_convert/definitions.py +11 -0
  16. sticker_convert/downloaders/download_band.py +111 -0
  17. sticker_convert/downloaders/download_base.py +171 -130
  18. sticker_convert/downloaders/download_discord.py +92 -0
  19. sticker_convert/downloaders/download_kakao.py +417 -255
  20. sticker_convert/downloaders/download_line.py +484 -472
  21. sticker_convert/downloaders/download_ogq.py +80 -0
  22. sticker_convert/downloaders/download_signal.py +108 -92
  23. sticker_convert/downloaders/download_telegram.py +56 -130
  24. sticker_convert/downloaders/download_viber.py +121 -95
  25. sticker_convert/gui.py +788 -795
  26. sticker_convert/gui_components/frames/comp_frame.py +180 -165
  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 -162
  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 -715
  34. sticker_convert/gui_components/windows/base_window.py +7 -2
  35. sticker_convert/gui_components/windows/discord_get_auth_window.py +79 -0
  36. sticker_convert/gui_components/windows/kakao_get_auth_window.py +511 -186
  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 -135
  39. sticker_convert/gui_components/windows/viber_get_auth_window.py +168 -0
  40. sticker_convert/ios-message-stickers-template/.github/FUNDING.yml +3 -3
  41. sticker_convert/ios-message-stickers-template/.gitignore +0 -0
  42. sticker_convert/ios-message-stickers-template/README.md +10 -10
  43. sticker_convert/ios-message-stickers-template/stickers/Info.plist +43 -43
  44. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Info.plist +31 -31
  45. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Contents.json +6 -6
  46. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Contents.json +20 -20
  47. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Contents.json +9 -9
  48. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 1.sticker/Sticker 1.png +0 -0
  49. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 2.sticker/Contents.json +9 -9
  50. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 2.sticker/Sticker 2.png +0 -0
  51. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 3.sticker/Contents.json +9 -9
  52. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/Sticker Pack.stickerpack/Sticker 3.sticker/Sticker 3.png +0 -0
  53. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/App-Store-1024x1024pt.png +0 -0
  54. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Contents.json +91 -91
  55. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-App-Store-1024x768pt.png +0 -0
  56. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPad-67x50pt@2x.png +0 -0
  57. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPad-Pro-74x55pt@2x.png +0 -0
  58. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPhone-60x45pt@2x.png +0 -0
  59. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages-iPhone-60x45pt@3x.png +0 -0
  60. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages27x20pt@2x.png +0 -0
  61. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages27x20pt@3x.png +0 -0
  62. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages32x24pt@2x.png +0 -0
  63. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/Messages32x24pt@3x.png +0 -0
  64. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPad-Settings-29pt@2x.png +0 -0
  65. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPhone-Settings-29pt@3x.png +0 -0
  66. sticker_convert/ios-message-stickers-template/stickers StickerPackExtension/Stickers.xcstickers/iMessage App Icon.stickersiconset/iPhone-settings-29pt@2x.png +0 -0
  67. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.pbxproj +364 -364
  68. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -7
  69. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -8
  70. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/project.xcworkspace/xcuserdata/niklaspeterson.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  71. sticker_convert/ios-message-stickers-template/stickers.xcodeproj/xcuserdata/niklaspeterson.xcuserdatad/xcschemes/xcschememanagement.plist +14 -14
  72. sticker_convert/job.py +279 -179
  73. sticker_convert/job_option.py +15 -2
  74. sticker_convert/locales/en_US/LC_MESSAGES/base.mo +0 -0
  75. sticker_convert/locales/ja_JP/LC_MESSAGES/base.mo +0 -0
  76. sticker_convert/locales/zh_CN/LC_MESSAGES/base.mo +0 -0
  77. sticker_convert/locales/zh_TW/LC_MESSAGES/base.mo +0 -0
  78. sticker_convert/py.typed +0 -0
  79. sticker_convert/resources/NotoColorEmoji.ttf +0 -0
  80. sticker_convert/resources/compression.json +220 -16
  81. sticker_convert/resources/emoji.json +527 -77
  82. sticker_convert/resources/help.ja_JP.json +88 -0
  83. sticker_convert/resources/help.json +24 -10
  84. sticker_convert/resources/help.zh_CN.json +88 -0
  85. sticker_convert/resources/help.zh_TW.json +88 -0
  86. sticker_convert/resources/input.ja_JP.json +74 -0
  87. sticker_convert/resources/input.json +121 -71
  88. sticker_convert/resources/input.zh_CN.json +74 -0
  89. sticker_convert/resources/input.zh_TW.json +74 -0
  90. sticker_convert/resources/memdump_linux.sh +25 -0
  91. sticker_convert/resources/memdump_windows.ps1 +8 -0
  92. sticker_convert/resources/output.ja_JP.json +38 -0
  93. sticker_convert/resources/output.json +24 -0
  94. sticker_convert/resources/output.zh_CN.json +38 -0
  95. sticker_convert/resources/output.zh_TW.json +38 -0
  96. sticker_convert/uploaders/compress_wastickers.py +186 -156
  97. sticker_convert/uploaders/upload_base.py +44 -35
  98. sticker_convert/uploaders/upload_signal.py +218 -173
  99. sticker_convert/uploaders/upload_telegram.py +353 -388
  100. sticker_convert/uploaders/upload_viber.py +178 -0
  101. sticker_convert/uploaders/xcode_imessage.py +295 -285
  102. sticker_convert/utils/callback.py +238 -6
  103. sticker_convert/utils/chrome_remotedebug.py +219 -0
  104. sticker_convert/utils/chromiums/linux.py +52 -0
  105. sticker_convert/utils/chromiums/osx.py +68 -0
  106. sticker_convert/utils/chromiums/windows.py +45 -0
  107. sticker_convert/utils/emoji.py +28 -0
  108. sticker_convert/utils/files/json_resources_loader.py +24 -19
  109. sticker_convert/utils/files/metadata_handler.py +8 -7
  110. sticker_convert/utils/files/run_bin.py +1 -1
  111. sticker_convert/utils/media/codec_info.py +99 -67
  112. sticker_convert/utils/media/format_verify.py +33 -20
  113. sticker_convert/utils/process.py +231 -0
  114. sticker_convert/utils/translate.py +108 -0
  115. sticker_convert/utils/url_detect.py +40 -33
  116. sticker_convert/version.py +1 -1
  117. {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info}/METADATA +189 -96
  118. sticker_convert-2.17.0.0.dist-info/RECORD +138 -0
  119. {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info}/WHEEL +1 -1
  120. sticker_convert/utils/auth/get_signal_auth.py +0 -129
  121. sticker_convert-2.8.12.dist-info/RECORD +0 -101
  122. {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info}/entry_points.txt +0 -0
  123. {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info/licenses}/LICENSE +0 -0
  124. {sticker_convert-2.8.12.dist-info → sticker_convert-2.17.0.0.dist-info}/top_level.txt +0 -0
@@ -1,388 +1,353 @@
1
- #!/usr/bin/env python3
2
- import copy
3
- import re
4
- from pathlib import Path
5
- from typing import Any, Dict, List, Optional, Union, cast
6
-
7
- import anyio
8
- from telegram import InputSticker, Sticker
9
- from telegram.error import BadRequest, TelegramError
10
- from telegram.ext import AIORateLimiter, ApplicationBuilder
11
-
12
- from sticker_convert.converter import StickerConvert
13
- from sticker_convert.job_option import CompOption, CredOption, OutputOption
14
- from sticker_convert.uploaders.upload_base import UploadBase
15
- from sticker_convert.utils.callback import CallbackProtocol, CallbackReturn
16
- from sticker_convert.utils.files.metadata_handler import MetadataHandler
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.square = True
27
- self.base_spec.duration_max = 3000
28
- self.base_spec.set_res(512)
29
-
30
- self.png_spec = copy.deepcopy(self.base_spec)
31
- self.png_spec.set_format((".png",))
32
- self.png_spec.animated = False
33
-
34
- self.tgs_spec = copy.deepcopy(self.base_spec)
35
- self.tgs_spec.set_format((".tgs",))
36
- self.tgs_spec.fps_min = 60
37
- self.tgs_spec.fps_max = 60
38
- self.tgs_spec.size_max_img = 64000
39
- self.tgs_spec.size_max_vid = 64000
40
-
41
- self.webm_spec = copy.deepcopy(self.base_spec)
42
- self.webm_spec.set_format((".webm",))
43
- self.webm_spec.fps_max = 30
44
- self.webm_spec.animated = None if self.opt_comp.fake_vid else True
45
-
46
- self.opt_comp_merged = copy.deepcopy(self.opt_comp)
47
- self.opt_comp_merged.merge(self.base_spec)
48
-
49
- base_cover_spec = CompOption(
50
- size_max_img=128000, size_max_vid=32000, square=True, duration_max=3000
51
- )
52
- base_cover_spec.set_res(100)
53
-
54
- self.png_cover_spec = copy.deepcopy(base_cover_spec)
55
- self.png_cover_spec.set_format((".png",))
56
- self.png_cover_spec.animated = False
57
-
58
- self.tgs_cover_spec = copy.deepcopy(base_cover_spec)
59
- self.tgs_cover_spec.set_format((".tgs",))
60
- self.tgs_cover_spec.fps_min = 60
61
- self.tgs_cover_spec.fps_max = 60
62
-
63
- self.webm_cover_spec = copy.deepcopy(base_cover_spec)
64
- self.webm_cover_spec.set_format((".webm",))
65
- self.webm_cover_spec.fps_max = 30
66
- self.webm_cover_spec.animated = True
67
-
68
- self.opt_comp_cover_merged = copy.deepcopy(self.opt_comp)
69
- self.opt_comp_cover_merged.merge(self.base_spec)
70
-
71
- async def upload_pack(
72
- self, pack_title: str, stickers: List[Path], emoji_dict: Dict[str, str]
73
- ) -> Optional[str]:
74
- token = self.opt_cred.telegram_token.strip()
75
- assert token
76
- timeout = 10 * len(stickers)
77
-
78
- application = ( # type: ignore
79
- ApplicationBuilder()
80
- .token(self.opt_cred.telegram_token.strip())
81
- .rate_limiter(AIORateLimiter(max_retries=3))
82
- .connect_timeout(timeout)
83
- .pool_timeout(timeout)
84
- .read_timeout(timeout)
85
- .write_timeout(timeout)
86
- .connection_pool_size(len(stickers))
87
- .build()
88
- )
89
-
90
- async with application:
91
- bot = application.bot
92
- pack_short_name = (
93
- pack_title.replace(" ", "_") + "_by_" + bot.name.replace("@", "")
94
- )
95
- pack_short_name = re.sub(
96
- "[^0-9a-zA-Z]+", "_", pack_short_name
97
- ) # name used in url, only alphanum and underscore only
98
-
99
- sticker_set: Any = None
100
- try:
101
- sticker_set = await bot.get_sticker_set(
102
- pack_short_name,
103
- read_timeout=30,
104
- write_timeout=30,
105
- connect_timeout=30,
106
- pool_timeout=30,
107
- )
108
- except TelegramError:
109
- pass
110
-
111
- if sticker_set is not None:
112
- question = f"Warning: Pack {pack_short_name} already exists.\n"
113
- question += "Delete all stickers in pack?\n"
114
- question += "Note: After recreating set, please wait for about 3 minutes for the set to reappear."
115
-
116
- self.cb.put(
117
- (
118
- "ask_bool",
119
- (question,),
120
- None,
121
- )
122
- )
123
- if self.cb_return:
124
- response = self.cb_return.get_response()
125
- else:
126
- response = False
127
-
128
- if response is True:
129
- self.cb.put(f"Deleting all stickers from pack {pack_short_name}")
130
- try:
131
- await bot.delete_sticker_set(pack_short_name)
132
- except BadRequest as e:
133
- self.cb.put(
134
- f"Cannot delete sticker set {pack_short_name} due to {e}"
135
- )
136
- if str(e) == "Stickerpack_not_found":
137
- self.cb.put(
138
- "Hint: You might had deleted and recreated pack too quickly. Wait about 3 minutes and try again."
139
- )
140
- return None
141
- except TelegramError as e:
142
- self.cb.put(
143
- f"Cannot delete sticker set {pack_short_name} due to {e}"
144
- )
145
- return None
146
- sticker_set = None
147
- else:
148
- self.cb.put(f"Not deleting existing pack {pack_short_name}")
149
-
150
- if self.opt_output.option == "telegram_emoji":
151
- sticker_type = Sticker.CUSTOM_EMOJI
152
- else:
153
- sticker_type = Sticker.REGULAR
154
-
155
- init_input_stickers: List[InputSticker] = []
156
- sticker_format = None
157
- sticker_format_prev = None
158
- for count, src in enumerate(stickers):
159
- self.cb.put(f"Verifying {src} for uploading to telegram")
160
-
161
- emoji = emoji_dict.get(Path(src).stem, None)
162
- if emoji:
163
- if len(emoji) > 20:
164
- self.cb.put(
165
- f"Warning: {len(emoji)} emoji for file {Path(src).name}, exceeding limit of 20, keep first 20 only..."
166
- )
167
- emoji_list = [*emoji][:20]
168
- else:
169
- self.cb.put(
170
- f"Warning: Cannot find emoji for file {Path(src).name}, skip uploading this file..."
171
- )
172
- continue
173
-
174
- ext = Path(src).suffix
175
- if ext == ".tgs":
176
- spec_choice = self.tgs_spec
177
- sticker_format = "animated"
178
- elif ext == ".webm":
179
- spec_choice = self.webm_spec
180
- sticker_format = "video"
181
- else:
182
- ext = ".png"
183
- spec_choice = self.png_spec
184
- sticker_format = "static"
185
-
186
- if self.opt_output.option == "telegram_emoji":
187
- spec_choice.set_res(100)
188
-
189
- if FormatVerify.check_file(src, spec=spec_choice):
190
- with open(src, "rb") as f:
191
- sticker_bytes = f.read()
192
- else:
193
- _, _, convert_result, _ = StickerConvert.convert(
194
- Path(src),
195
- Path(f"bytes{ext}"),
196
- self.opt_comp_merged,
197
- self.cb,
198
- self.cb_return,
199
- )
200
- sticker_bytes = cast(bytes, convert_result)
201
-
202
- input_sticker = InputSticker(
203
- sticker=sticker_bytes,
204
- emoji_list=emoji_list,
205
- format=sticker_format,
206
- )
207
-
208
- if sticker_set is None:
209
- if count < 50 and (
210
- sticker_format_prev is None
211
- or sticker_format_prev == sticker_format
212
- ):
213
- init_input_stickers.append(input_sticker)
214
- else:
215
- start_msg = f"Creating pack and bulk uploading {count} stickers with same format of {pack_short_name}"
216
- finish_msg = f"Created pack and bulk uploaded {count} stickers with same format of {pack_short_name}"
217
- error_msg = f"Cannot create pack and bulk upload {count} stickers with same format of {pack_short_name} due to"
218
- self.cb.put(start_msg)
219
- try:
220
- await bot.create_new_sticker_set(
221
- user_id=self.telegram_userid,
222
- name=pack_short_name,
223
- title=pack_title,
224
- stickers=init_input_stickers,
225
- sticker_type=sticker_type,
226
- )
227
- sticker_set = True
228
- self.cb.put(finish_msg)
229
- except TelegramError as e:
230
- self.cb.put(f"{error_msg} {e}")
231
- return None
232
- else:
233
- try:
234
- # We could use tg.start_soon() here
235
- # But this would disrupt the order of stickers
236
- await bot.add_sticker_to_set(
237
- user_id=self.telegram_userid,
238
- name=pack_short_name,
239
- sticker=input_sticker,
240
- )
241
- self.cb.put(f"Uploaded sticker {src} of {pack_short_name}")
242
- except BadRequest as e:
243
- self.cb.put(
244
- f"Cannot upload sticker {src} of {pack_short_name} due to {e}"
245
- )
246
- if str(e) == "Stickerpack_not_found":
247
- self.cb.put(
248
- "Hint: You might had deleted and recreated pack too quickly. Wait about 3 minutes and try again."
249
- )
250
- except TelegramError as e:
251
- self.cb.put(
252
- f"Cannot upload sticker {src} of {pack_short_name} due to {e}"
253
- )
254
-
255
- sticker_format_prev = sticker_format
256
-
257
- cover_path = MetadataHandler.get_cover(self.opt_output.dir)
258
- if cover_path:
259
- thumbnail_bytes: Union[None, bytes, Path] = None
260
- cover_ext = Path(cover_path).suffix
261
-
262
- if cover_ext == ".tgs":
263
- thumbnail_format = "animated"
264
- cover_spec_choice = self.tgs_cover_spec
265
- elif cover_ext == ".webm":
266
- thumbnail_format = "video"
267
- cover_spec_choice = self.webm_cover_spec
268
- else:
269
- cover_ext = ".png"
270
- thumbnail_format = "static"
271
- cover_spec_choice = self.png_cover_spec
272
-
273
- if FormatVerify.check_file(cover_path, spec=cover_spec_choice):
274
- with open(cover_path, "rb") as f:
275
- thumbnail_bytes = f.read()
276
- else:
277
- _, _, thumbnail_bytes, _ = StickerConvert.convert(
278
- cover_path,
279
- Path(f"bytes{cover_ext}"),
280
- self.opt_comp_cover_merged,
281
- self.cb,
282
- self.cb_return,
283
- )
284
-
285
- try:
286
- self.cb.put(
287
- f"Uploading cover (thumbnail) of pack {pack_short_name}"
288
- )
289
- await bot.set_sticker_set_thumbnail(
290
- name=pack_short_name,
291
- user_id=self.telegram_userid,
292
- thumbnail=thumbnail_bytes,
293
- format=thumbnail_format,
294
- )
295
- self.cb.put(f"Uploaded cover (thumbnail) of pack {pack_short_name}")
296
- except TelegramError as e:
297
- self.cb.put(
298
- f"Cannot upload cover (thumbnail) of pack {pack_short_name} due to {e}"
299
- )
300
-
301
- self.cb.put(f"Finish uploading {pack_short_name}")
302
-
303
- if self.opt_output.option == "telegram_emoji":
304
- result = f"https://t.me/addemoji/{pack_short_name}"
305
- else:
306
- result = f"https://t.me/addstickers/{pack_short_name}"
307
- return result
308
-
309
- def upload_stickers_telegram(self) -> List[str]:
310
- urls: List[str] = []
311
-
312
- if not (self.opt_cred.telegram_token and self.opt_cred.telegram_userid):
313
- self.cb.put("Token and userid required for uploading to telegram")
314
- return urls
315
-
316
- if self.opt_cred.telegram_userid.isnumeric():
317
- self.telegram_userid = int(self.opt_cred.telegram_userid)
318
- else:
319
- self.cb.put("Invalid userid, should contain numbers only")
320
- return urls
321
-
322
- title, _, emoji_dict = MetadataHandler.get_metadata(
323
- self.opt_output.dir,
324
- title=self.opt_output.title,
325
- author=self.opt_output.author,
326
- )
327
- if title is None:
328
- raise TypeError("title cannot be", title)
329
- if emoji_dict is None:
330
- msg_block = "emoji.txt is required for uploading signal stickers\n"
331
- msg_block += f"emoji.txt generated for you in {self.opt_output.dir}\n"
332
- msg_block += f"Default emoji is set to {self.opt_comp.default_emoji}.\n"
333
- msg_block += "Please edit emoji.txt now, then continue"
334
- MetadataHandler.generate_emoji_file(
335
- directory=self.opt_output.dir, default_emoji=self.opt_comp.default_emoji
336
- )
337
-
338
- self.cb.put(("msg_block", (msg_block,), None))
339
- if self.cb_return:
340
- self.cb_return.get_response()
341
-
342
- title, _, emoji_dict = MetadataHandler.get_metadata(
343
- self.opt_output.dir,
344
- title=self.opt_output.title,
345
- author=self.opt_output.author,
346
- )
347
-
348
- assert title is not None
349
- assert emoji_dict is not None
350
-
351
- if self.opt_output.option == "telegram_emoji":
352
- file_per_pack = 200
353
- else:
354
- file_per_pack = 120
355
-
356
- packs = MetadataHandler.split_sticker_packs(
357
- self.opt_output.dir,
358
- title=title,
359
- file_per_anim_pack=file_per_pack,
360
- file_per_image_pack=file_per_pack,
361
- separate_image_anim=not self.opt_comp.fake_vid,
362
- )
363
-
364
- for pack_title, stickers in packs.items():
365
- self.cb.put(f"Uploading pack {pack_title}")
366
- result = anyio.run(self.upload_pack, pack_title, stickers, emoji_dict)
367
- if result:
368
- self.cb.put((result))
369
- urls.append(result)
370
-
371
- return urls
372
-
373
- @staticmethod
374
- def start(
375
- opt_output: OutputOption,
376
- opt_comp: CompOption,
377
- opt_cred: CredOption,
378
- cb: CallbackProtocol,
379
- cb_return: CallbackReturn,
380
- ) -> List[str]:
381
- exporter = UploadTelegram(
382
- opt_output,
383
- opt_comp,
384
- opt_cred,
385
- cb,
386
- cb_return,
387
- )
388
- 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()