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
sticker_convert/gui.py CHANGED
@@ -1,795 +1,788 @@
1
- #!/usr/bin/env python3
2
- import os
3
- import platform
4
- import signal
5
- import sys
6
- from functools import partial
7
- from json.decoder import JSONDecodeError
8
- from math import ceil
9
- from multiprocessing import Event, cpu_count
10
- from pathlib import Path
11
- from threading import Thread
12
- from typing import Any, Callable, Dict, Optional, Union
13
- from urllib.parse import urlparse
14
-
15
- from mergedeep import merge # type: ignore
16
- from PIL import ImageFont
17
- from ttkbootstrap import BooleanVar, DoubleVar, IntVar, StringVar, Toplevel, Window # type: ignore
18
- from ttkbootstrap.dialogs import Messagebox, Querybox # type: ignore
19
-
20
- from sticker_convert.definitions import CONFIG_DIR, DEFAULT_DIR, ROOT_DIR
21
- from sticker_convert.gui_components.frames.comp_frame import CompFrame
22
- from sticker_convert.gui_components.frames.config_frame import ConfigFrame
23
- from sticker_convert.gui_components.frames.control_frame import ControlFrame
24
- from sticker_convert.gui_components.frames.cred_frame import CredFrame
25
- from sticker_convert.gui_components.frames.input_frame import InputFrame
26
- from sticker_convert.gui_components.frames.output_frame import OutputFrame
27
- from sticker_convert.gui_components.frames.progress_frame import ProgressFrame
28
- from sticker_convert.gui_components.gui_utils import GUIUtils
29
- from sticker_convert.job import Job
30
- from sticker_convert.job_option import CompOption, CredOption, InputOption, OutputOption
31
- from sticker_convert.utils.files.json_manager import JsonManager
32
- from sticker_convert.utils.files.metadata_handler import MetadataHandler
33
- from sticker_convert.utils.url_detect import UrlDetect
34
- from sticker_convert.version import __version__
35
-
36
-
37
- class GUI(Window):
38
- def __init__(self) -> None:
39
- super().__init__(themename="darkly", alpha=0) # type: ignore
40
- self.init_done = False
41
- self.load_jsons()
42
-
43
- font_path = ROOT_DIR / "resources/NotoColorEmoji.ttf"
44
- self.emoji_font = ImageFont.truetype(font_path.as_posix(), 109)
45
-
46
- GUIUtils.set_icon(self)
47
-
48
- self.title(f"sticker-convert {__version__}")
49
- self.protocol("WM_DELETE_WINDOW", self.quit)
50
-
51
- (
52
- self.main_frame,
53
- self.horizontal_scrollbar_frame,
54
- self.canvas,
55
- self.x_scrollbar,
56
- self.y_scrollbar,
57
- self.scrollable_frame,
58
- ) = GUIUtils.create_scrollable_frame(self)
59
-
60
- self.declare_variables()
61
- self.apply_config()
62
- self.apply_creds()
63
- self.init_frames()
64
- self.pack_frames()
65
- self.warn_tkinter_bug()
66
- GUIUtils.finalize_window(self)
67
-
68
- self.bind("<<exec_in_main>>", self.exec_in_main) # type: ignore
69
-
70
- def __enter__(self) -> "GUI":
71
- return self
72
-
73
- def gui(self) -> None:
74
- self.init_done = True
75
- self.highlight_fields()
76
- self.mainloop()
77
-
78
- def quit(self) -> None:
79
- if self.job:
80
- response = self.cb_ask_bool("Job is running, really quit?")
81
- if response is False:
82
- return
83
-
84
- self.cb_msg(msg="Quitting, please wait...")
85
-
86
- self.save_config()
87
- if self.settings_save_cred_var.get() is True:
88
- self.save_creds()
89
- else:
90
- self.delete_creds()
91
-
92
- if self.job:
93
- self.cancel_job()
94
- self.destroy()
95
-
96
- def declare_variables(self) -> None:
97
- # Input
98
- self.input_option_display_var = StringVar(self)
99
- self.input_option_true_var = StringVar(self)
100
- self.input_setdir_var = StringVar(self)
101
- self.input_address_var = StringVar(self)
102
-
103
- # Compression
104
- self.no_compress_var = BooleanVar()
105
- self.comp_preset_var = StringVar(self)
106
- self.fps_min_var = IntVar(self)
107
- self.fps_max_var = IntVar(self)
108
- self.fps_disable_var = BooleanVar()
109
- self.fps_power_var = DoubleVar()
110
- self.res_w_min_var = IntVar(self)
111
- self.res_w_max_var = IntVar(self)
112
- self.res_w_disable_var = BooleanVar()
113
- self.res_h_min_var = IntVar(self)
114
- self.res_h_max_var = IntVar(self)
115
- self.res_h_disable_var = BooleanVar()
116
- self.res_power_var = DoubleVar()
117
- self.quality_min_var = IntVar(self)
118
- self.quality_max_var = IntVar(self)
119
- self.quality_disable_var = BooleanVar()
120
- self.quality_power_var = DoubleVar()
121
- self.color_min_var = IntVar(self)
122
- self.color_max_var = IntVar(self)
123
- self.color_disable_var = BooleanVar()
124
- self.color_power_var = DoubleVar()
125
- self.duration_min_var = IntVar(self)
126
- self.duration_max_var = IntVar(self)
127
- self.duration_disable_var = BooleanVar()
128
- self.padding_percent_var = IntVar(self)
129
- self.img_size_max_var = IntVar(self)
130
- self.vid_size_max_var = IntVar(self)
131
- self.size_disable_var = BooleanVar()
132
- self.bg_color_var = StringVar()
133
- self.img_format_var = StringVar(self)
134
- self.vid_format_var = StringVar(self)
135
- self.fake_vid_var = BooleanVar()
136
- self.scale_filter_var = StringVar(self)
137
- self.quantize_method_var = StringVar(self)
138
- self.cache_dir_var = StringVar(self)
139
- self.default_emoji_var = StringVar(self)
140
- self.steps_var = IntVar(self)
141
- self.processes_var = IntVar(self)
142
-
143
- # Output
144
- self.output_option_display_var = StringVar(self)
145
- self.output_option_true_var = StringVar(self)
146
- self.output_setdir_var = StringVar(self)
147
- self.title_var = StringVar(self)
148
- self.author_var = StringVar(self)
149
-
150
- # Credentials
151
- self.signal_uuid_var = StringVar(self)
152
- self.signal_password_var = StringVar(self)
153
- self.signal_data_dir_var = StringVar(self)
154
- self.telegram_token_var = StringVar(self)
155
- self.telegram_userid_var = StringVar(self)
156
- self.kakao_auth_token_var = StringVar(self)
157
- self.kakao_username_var = StringVar(self)
158
- self.kakao_password_var = StringVar(self)
159
- self.kakao_country_code_var = StringVar(self)
160
- self.kakao_phone_number_var = StringVar(self)
161
- self.line_cookies_var = StringVar(self)
162
-
163
- # Config
164
- self.settings_save_cred_var = BooleanVar()
165
-
166
- # Other
167
- self.response_event = Event()
168
- self.response = None
169
- self.action: Optional[Callable[..., Any]] = None
170
- self.job: Optional[Job] = None
171
-
172
- def init_frames(self) -> None:
173
- self.input_frame = InputFrame(
174
- self, self.scrollable_frame, borderwidth=1, text="Input"
175
- )
176
- self.comp_frame = CompFrame(
177
- self, self.scrollable_frame, borderwidth=1, text="Compression options"
178
- )
179
- self.output_frame = OutputFrame(
180
- self, self.scrollable_frame, borderwidth=1, text="Output"
181
- )
182
- self.cred_frame = CredFrame(
183
- self, self.scrollable_frame, borderwidth=1, text="Credentials"
184
- )
185
- self.settings_frame = ConfigFrame(
186
- self, self.scrollable_frame, borderwidth=1, text="Config"
187
- )
188
- self.progress_frame = ProgressFrame(
189
- self, self.scrollable_frame, borderwidth=1, text="Progress"
190
- )
191
- self.control_frame = ControlFrame(self, self.scrollable_frame, borderwidth=1)
192
-
193
- def pack_frames(self) -> None:
194
- self.input_frame.grid(column=0, row=0, sticky="w", padx=5, pady=5)
195
- self.comp_frame.grid(column=1, row=0, sticky="news", padx=5, pady=5)
196
- self.output_frame.grid(column=0, row=1, sticky="w", padx=5, pady=5)
197
- self.cred_frame.grid(column=1, row=1, rowspan=2, sticky="w", padx=5, pady=5)
198
- self.settings_frame.grid(column=0, row=2, sticky="news", padx=5, pady=5)
199
- self.progress_frame.grid(
200
- column=0, row=3, columnspan=2, sticky="news", padx=5, pady=5
201
- )
202
- self.control_frame.grid(
203
- column=0, row=4, columnspan=2, sticky="news", padx=5, pady=5
204
- )
205
-
206
- def warn_tkinter_bug(self) -> None:
207
- if (
208
- platform.system() == "Darwin"
209
- and platform.mac_ver()[0].split(".")[0] == "14"
210
- and sys.version_info[0] == 3
211
- and sys.version_info[1] == 11
212
- and sys.version_info[2] <= 6
213
- ):
214
- msg = "NOTICE: If buttons are not responsive, try to press "
215
- msg += "on title bar or move mouse cursor away from window for a while."
216
- self.cb_msg(msg)
217
- msg = (
218
- "(This is due to a bug in tkinter specific to macOS 14 python <=3.11.6)"
219
- )
220
- self.cb_msg(msg)
221
- msg = "(https://github.com/python/cpython/issues/110218)"
222
- self.cb_msg(msg)
223
-
224
- def load_jsons(self) -> None:
225
- try:
226
- from sticker_convert.utils.files.json_resources_loader import COMPRESSION_JSON, EMOJI_JSON, HELP_JSON, INPUT_JSON, OUTPUT_JSON
227
- except RuntimeError as e:
228
- self.cb_msg(str(e))
229
- return
230
-
231
- self.help = HELP_JSON
232
- self.input_presets = INPUT_JSON
233
- self.compression_presets = COMPRESSION_JSON
234
- self.output_presets = OUTPUT_JSON
235
- self.emoji_list = EMOJI_JSON
236
-
237
- if not (
238
- self.compression_presets and self.input_presets and self.output_presets
239
- ):
240
- Messagebox.show_error( # type: ignore
241
- message='Warning: json(s) under "resources" directory cannot be found',
242
- title="sticker-convert",
243
- )
244
- sys.exit()
245
-
246
- self.settings_path = CONFIG_DIR / "config.json"
247
- if self.settings_path.is_file():
248
- try:
249
- self.settings: Dict[Any, Any] = JsonManager.load_json(
250
- self.settings_path
251
- )
252
- except JSONDecodeError:
253
- self.cb_msg("Warning: config.json content is corrupted")
254
- self.settings = {}
255
- else:
256
- self.settings = {}
257
-
258
- self.creds_path = CONFIG_DIR / "creds.json"
259
- if self.creds_path.is_file():
260
- try:
261
- self.creds = JsonManager.load_json(self.creds_path)
262
- except JSONDecodeError:
263
- self.cb_msg("Warning: creds.json content is corrupted")
264
- self.creds = {}
265
- else:
266
- self.creds = {}
267
-
268
- def save_config(self) -> None:
269
- # Only update comp_custom if custom preset is selected
270
- if self.comp_preset_var.get() == "custom":
271
- comp_custom: Dict[Any, Any] = merge( # type: ignore
272
- self.compression_presets.get("custom"), # type: ignore
273
- self.get_opt_comp().to_dict(),
274
- )
275
- comp_custom["format"]["img"] = comp_custom["format"]["img"][0]
276
- comp_custom["format"]["vid"] = comp_custom["format"]["vid"][0]
277
- del comp_custom["preset"]
278
- del comp_custom["no_compress"]
279
- else:
280
- compression_presets_custom = self.compression_presets.get("custom")
281
- if compression_presets_custom is None:
282
- comp_custom = {}
283
- else:
284
- comp_custom = compression_presets_custom
285
-
286
- self.settings = {
287
- "input": self.get_opt_input().to_dict(),
288
- "comp": {
289
- "no_compress": self.no_compress_var.get(),
290
- "preset": self.comp_preset_var.get(),
291
- "cache_dir": self.cache_dir_var.get(),
292
- "processes": self.processes_var.get(),
293
- },
294
- "comp_custom": comp_custom,
295
- "output": self.get_opt_output().to_dict(),
296
- "creds": {"save_cred": self.settings_save_cred_var.get()},
297
- }
298
-
299
- JsonManager.save_json(self.settings_path, self.settings)
300
-
301
- def save_creds(self) -> None:
302
- self.creds = self.get_opt_cred().to_dict()
303
-
304
- JsonManager.save_json(self.creds_path, self.creds)
305
-
306
- def delete_creds(self) -> None:
307
- if self.creds_path.is_file():
308
- os.remove(self.creds_path)
309
-
310
- def delete_config(self) -> None:
311
- if self.settings_path.is_file():
312
- os.remove(self.settings_path)
313
-
314
- def apply_config(self) -> None:
315
- # Input
316
- self.default_input_mode: str = self.settings.get("input", {}).get(
317
- "option", "auto"
318
- )
319
- self.input_address_var.set(self.settings.get("input", {}).get("url", ""))
320
- default_stickers_input_dir = str(DEFAULT_DIR / "stickers_input")
321
- self.input_setdir_var.set(
322
- self.settings.get("input", {}).get("dir", default_stickers_input_dir)
323
- )
324
- if not Path(self.input_setdir_var.get()).is_dir():
325
- self.input_setdir_var.set(default_stickers_input_dir)
326
- self.input_option_display_var.set(
327
- self.input_presets[self.default_input_mode]["full_name"]
328
- )
329
- self.input_option_true_var.set(
330
- self.input_presets[self.default_input_mode]["full_name"]
331
- )
332
-
333
- # Compression
334
- self.no_compress_var.set(
335
- self.settings.get("comp", {}).get("no_compress", False)
336
- )
337
- default_comp_preset = list(self.compression_presets.keys())[0]
338
- self.comp_preset_var.set(
339
- self.settings.get("comp", {}).get("preset", default_comp_preset)
340
- )
341
- comp_custom = self.settings.get("comp_custom")
342
- if comp_custom:
343
- self.compression_presets["custom"] = merge(
344
- self.compression_presets["custom"], comp_custom
345
- )
346
- self.cache_dir_var.set(self.settings.get("comp", {}).get("cache_dir", ""))
347
- self.processes_var.set(
348
- self.settings.get("comp", {}).get("processes", ceil(cpu_count() / 2))
349
- )
350
- self.default_output_mode: str = self.settings.get("output", {}).get(
351
- "option", "signal"
352
- )
353
-
354
- # Output
355
- default_stickers_output_dir = str(DEFAULT_DIR / "stickers_output")
356
- self.output_setdir_var.set(
357
- self.settings.get("output", {}).get("dir", default_stickers_output_dir)
358
- )
359
- if not Path(self.output_setdir_var.get()).is_dir():
360
- self.output_setdir_var.set(default_stickers_output_dir)
361
- self.title_var.set(self.settings.get("output", {}).get("title", ""))
362
- self.author_var.set(self.settings.get("output", {}).get("author", ""))
363
- self.settings_save_cred_var.set(
364
- self.settings.get("creds", {}).get("save_cred", True)
365
- )
366
- self.output_option_display_var.set(
367
- self.output_presets[self.default_output_mode]["full_name"]
368
- )
369
- self.output_option_true_var.set(
370
- self.output_presets[self.default_output_mode]["full_name"]
371
- )
372
-
373
- def apply_creds(self) -> None:
374
- self.signal_uuid_var.set(self.creds.get("signal", {}).get("uuid", ""))
375
- self.signal_password_var.set(self.creds.get("signal", {}).get("password", ""))
376
- self.telegram_token_var.set(self.creds.get("telegram", {}).get("token", ""))
377
- self.telegram_userid_var.set(self.creds.get("telegram", {}).get("userid", ""))
378
- self.kakao_auth_token_var.set(self.creds.get("kakao", {}).get("auth_token", ""))
379
- self.kakao_username_var.set(self.creds.get("kakao", {}).get("username", ""))
380
- self.kakao_password_var.set(self.creds.get("kakao", {}).get("password", ""))
381
- self.kakao_country_code_var.set(
382
- self.creds.get("kakao", {}).get("country_code", "")
383
- )
384
- self.kakao_phone_number_var.set(
385
- self.creds.get("kakao", {}).get("phone_number", "")
386
- )
387
- self.line_cookies_var.set(self.creds.get("line", {}).get("cookies", ""))
388
-
389
- def get_input_name(self) -> str:
390
- return [
391
- k
392
- for k, v in self.input_presets.items()
393
- if v["full_name"] == self.input_option_true_var.get()
394
- ][0]
395
-
396
- def get_input_display_name(self) -> str:
397
- return [
398
- k
399
- for k, v in self.input_presets.items()
400
- if v["full_name"] == self.input_option_display_var.get()
401
- ][0]
402
-
403
- def get_output_name(self) -> str:
404
- return [
405
- k
406
- for k, v in self.output_presets.items()
407
- if v["full_name"] == self.output_option_true_var.get()
408
- ][0]
409
-
410
- # def get_output_display_name(self) -> str:
411
- # return [
412
- # k
413
- # for k, v in self.output_presets.items()
414
- # if v["full_name"] == self.output_option_display_var.get()
415
- # ][0]
416
-
417
- def get_preset(self) -> str:
418
- selection = self.comp_preset_var.get()
419
- if selection == "auto":
420
- output_option = self.get_output_name()
421
- if output_option == "imessage":
422
- return "imessage_small"
423
- if output_option == "local":
424
- return selection
425
- return output_option
426
-
427
- return selection
428
-
429
- def start_job(self) -> None:
430
- self.save_config()
431
- if self.settings_save_cred_var.get() is True:
432
- self.save_creds()
433
- else:
434
- self.delete_creds()
435
-
436
- self.control_frame.start_btn.config(text="Cancel", bootstyle="danger") # type: ignore
437
- self.set_inputs("disabled")
438
-
439
- opt_input = self.get_opt_input()
440
- opt_output = self.get_opt_output()
441
- opt_comp = self.get_opt_comp()
442
- opt_cred = self.get_opt_cred()
443
-
444
- self.job = Job(
445
- opt_input,
446
- opt_comp,
447
- opt_output,
448
- opt_cred,
449
- self.cb_msg,
450
- self.cb_msg_block,
451
- self.cb_bar,
452
- self.cb_ask_bool,
453
- self.cb_ask_str,
454
- )
455
-
456
- signal.signal(signal.SIGINT, self.job.cancel)
457
-
458
- Thread(target=self.start_process, daemon=True).start()
459
-
460
- def get_opt_input(self) -> InputOption:
461
- return InputOption(
462
- option=self.get_input_name(),
463
- url=self.input_address_var.get(),
464
- dir=Path(self.input_setdir_var.get()),
465
- )
466
-
467
- def get_opt_output(self) -> OutputOption:
468
- return OutputOption(
469
- option=self.get_output_name(),
470
- dir=Path(self.output_setdir_var.get()),
471
- title=self.title_var.get(),
472
- author=self.author_var.get(),
473
- )
474
-
475
- def get_opt_comp(self) -> CompOption:
476
- return CompOption(
477
- preset=self.get_preset(),
478
- size_max_img=self.img_size_max_var.get()
479
- if not self.size_disable_var.get()
480
- else None,
481
- size_max_vid=self.vid_size_max_var.get()
482
- if not self.size_disable_var.get()
483
- else None,
484
- format_img=(self.img_format_var.get(),),
485
- format_vid=(self.vid_format_var.get(),),
486
- fps_min=self.fps_min_var.get() if not self.fps_disable_var.get() else None,
487
- fps_max=self.fps_max_var.get() if not self.fps_disable_var.get() else None,
488
- fps_power=self.fps_power_var.get(),
489
- res_w_min=self.res_w_min_var.get()
490
- if not self.res_w_disable_var.get()
491
- else None,
492
- res_w_max=self.res_w_max_var.get()
493
- if not self.res_w_disable_var.get()
494
- else None,
495
- res_h_min=self.res_h_min_var.get()
496
- if not self.res_h_disable_var.get()
497
- else None,
498
- res_h_max=self.res_h_max_var.get()
499
- if not self.res_h_disable_var.get()
500
- else None,
501
- res_power=self.res_power_var.get(),
502
- quality_min=self.quality_min_var.get()
503
- if not self.quality_disable_var.get()
504
- else None,
505
- quality_max=self.quality_max_var.get()
506
- if not self.quality_disable_var.get()
507
- else None,
508
- quality_power=self.quality_power_var.get(),
509
- color_min=self.color_min_var.get()
510
- if not self.color_disable_var.get()
511
- else None,
512
- color_max=self.color_max_var.get()
513
- if not self.color_disable_var.get()
514
- else None,
515
- color_power=self.color_power_var.get(),
516
- duration_min=self.duration_min_var.get()
517
- if not self.duration_disable_var.get()
518
- else None,
519
- duration_max=self.duration_max_var.get()
520
- if not self.duration_disable_var.get()
521
- else None,
522
- bg_color=self.bg_color_var.get(),
523
- padding_percent=self.padding_percent_var.get(),
524
- steps=self.steps_var.get(),
525
- fake_vid=self.fake_vid_var.get(),
526
- scale_filter=self.scale_filter_var.get(),
527
- quantize_method=self.quantize_method_var.get(),
528
- cache_dir=self.cache_dir_var.get()
529
- if self.cache_dir_var.get() != ""
530
- else None,
531
- default_emoji=self.default_emoji_var.get(),
532
- no_compress=self.no_compress_var.get(),
533
- processes=self.processes_var.get(),
534
- )
535
-
536
- def get_opt_cred(self) -> CredOption:
537
- return CredOption(
538
- signal_uuid=self.signal_uuid_var.get(),
539
- signal_password=self.signal_password_var.get(),
540
- telegram_token=self.telegram_token_var.get(),
541
- telegram_userid=self.telegram_userid_var.get(),
542
- kakao_auth_token=self.kakao_auth_token_var.get(),
543
- kakao_username=self.kakao_username_var.get(),
544
- kakao_password=self.kakao_password_var.get(),
545
- kakao_country_code=self.kakao_country_code_var.get(),
546
- kakao_phone_number=self.kakao_phone_number_var.get(),
547
- line_cookies=self.line_cookies_var.get(),
548
- )
549
-
550
- def start_process(self) -> None:
551
- if self.job:
552
- self.job.start()
553
- self.job = None
554
-
555
- self.stop_job()
556
-
557
- def stop_job(self) -> None:
558
- self.set_inputs("normal")
559
- self.control_frame.start_btn.config(text="Start", bootstyle="default") # type: ignore
560
-
561
- def cancel_job(self) -> None:
562
- if self.job:
563
- # Need to start new thread or else GUI may freeze
564
- Thread(target=self.job.cancel, daemon=True).start()
565
-
566
- def set_inputs(self, state: str) -> None:
567
- # state: 'normal', 'disabled'
568
-
569
- self.input_frame.set_states(state=state)
570
- self.comp_frame.set_states(state=state)
571
- self.output_frame.set_states(state=state)
572
- self.cred_frame.set_states(state=state)
573
- self.settings_frame.set_states(state=state)
574
-
575
- if state == "normal":
576
- self.input_frame.cb_input_option()
577
- self.comp_frame.cb_no_compress()
578
-
579
- def exec_in_main(self, _evt: Any) -> Any:
580
- if self.action:
581
- self.response = self.action()
582
- self.response_event.set()
583
-
584
- def cb_ask_str(
585
- self,
586
- question: str,
587
- initialvalue: Optional[str] = None,
588
- cli_show_initialvalue: bool = True,
589
- parent: Optional[object] = None,
590
- ) -> Any:
591
- self.action = partial(
592
- Querybox.get_string, # type: ignore
593
- question,
594
- title="sticker-convert",
595
- initialvalue=initialvalue,
596
- parent=parent,
597
- )
598
- self.event_generate("<<exec_in_main>>")
599
- self.response_event.wait()
600
- self.response_event.clear()
601
-
602
- return self.response
603
-
604
- def cb_ask_bool(
605
- self, question: str, parent: Union[Window, Toplevel, None] = None
606
- ) -> bool:
607
- self.action = partial(
608
- Messagebox.yesno, # type: ignore
609
- question,
610
- title="sticker-convert",
611
- parent=parent,
612
- )
613
- self.event_generate("<<exec_in_main>>")
614
- self.response_event.wait()
615
- self.response_event.clear()
616
-
617
- if self.response == "Yes":
618
- return True
619
- return False
620
-
621
- def cb_msg(self, *args: Any, **kwargs: Any) -> None:
622
- self.progress_frame.update_message_box(*args, **kwargs)
623
-
624
- def cb_msg_block(
625
- self,
626
- *args: Any,
627
- message: Optional[str] = None,
628
- parent: Optional[object] = None,
629
- **_kwargs: Any,
630
- ) -> Any:
631
- if message is None and len(args) > 0:
632
- message = " ".join(str(i) for i in args)
633
- self.action = partial(
634
- Messagebox.show_info, # type: ignore
635
- message,
636
- title="sticker-convert",
637
- parent=parent,
638
- )
639
- self.event_generate("<<exec_in_main>>")
640
- self.response_event.wait()
641
- self.response_event.clear()
642
-
643
- return self.response
644
-
645
- def cb_bar(
646
- self,
647
- set_progress_mode: Optional[str] = None,
648
- steps: int = 0,
649
- update_bar: int = 0,
650
- *args: Any,
651
- **kwargs: Any,
652
- ) -> None:
653
- self.progress_frame.update_progress_bar(
654
- set_progress_mode, steps, update_bar, *args, **kwargs
655
- )
656
-
657
- def highlight_fields(self) -> bool:
658
- if not self.init_done:
659
- return True
660
-
661
- input_option = self.get_input_name()
662
- input_option_display = self.get_input_display_name()
663
- output_option = self.get_output_name()
664
- # output_option_display = self.get_output_display_name()
665
- url = self.input_address_var.get()
666
-
667
- in_out_dir_same = (
668
- Path(self.input_setdir_var.get()).absolute()
669
- == Path(self.output_setdir_var.get()).absolute()
670
- )
671
-
672
- # Input
673
- if in_out_dir_same is True:
674
- self.input_frame.input_setdir_entry.config(bootstyle="danger") # type: ignore
675
- elif not Path(self.input_setdir_var.get()).is_dir():
676
- self.input_frame.input_setdir_entry.config(bootstyle="warning") # type: ignore
677
- else:
678
- self.input_frame.input_setdir_entry.config(bootstyle="default") # type: ignore
679
-
680
- self.input_frame.address_lbl.config(
681
- text=self.input_presets[input_option_display]["address_lbls"]
682
- )
683
- self.input_frame.address_entry.config(bootstyle="default") # type: ignore
684
-
685
- if input_option == "local":
686
- self.input_frame.address_entry.config(state="disabled")
687
- self.input_frame.address_tip.config(
688
- text=self.input_presets[input_option_display]["example"]
689
- )
690
-
691
- else:
692
- self.input_frame.address_entry.config(state="normal")
693
- self.input_frame.address_tip.config(
694
- text=self.input_presets[input_option_display]["example"]
695
- )
696
- download_option = UrlDetect.detect(url)
697
-
698
- if not url:
699
- self.input_frame.address_entry.config(bootstyle="warning") # type: ignore
700
-
701
- elif download_option != input_option and not (
702
- input_option in ("kakao", "line") and url.isnumeric()
703
- ):
704
- self.input_frame.address_entry.config(bootstyle="danger") # type: ignore
705
- self.input_frame.address_tip.config(
706
- text=f"Invalid URL. {self.input_presets[input_option_display]['example']}"
707
- )
708
-
709
- elif input_option_display == "auto" and download_option:
710
- self.input_frame.address_tip.config(
711
- text=f"Detected URL: {download_option}"
712
- )
713
-
714
- # Output
715
- if in_out_dir_same is True:
716
- self.output_frame.output_setdir_entry.config(bootstyle="danger") # type: ignore
717
- elif not Path(self.output_setdir_var.get()).is_dir():
718
- self.output_frame.output_setdir_entry.config(bootstyle="warning") # type: ignore
719
- else:
720
- self.output_frame.output_setdir_entry.config(bootstyle="default") # type: ignore
721
-
722
- if (
723
- MetadataHandler.check_metadata_required(output_option, "title")
724
- and not MetadataHandler.check_metadata_provided(
725
- Path(self.input_setdir_var.get()), input_option, "title"
726
- )
727
- and not self.title_var.get()
728
- ):
729
- self.output_frame.title_entry.config(bootstyle="warning") # type: ignore
730
- else:
731
- self.output_frame.title_entry.config(bootstyle="default") # type: ignore
732
-
733
- if (
734
- MetadataHandler.check_metadata_required(output_option, "author")
735
- and not MetadataHandler.check_metadata_provided(
736
- Path(self.input_setdir_var.get()), input_option, "author"
737
- )
738
- and not self.author_var.get()
739
- ):
740
- self.output_frame.author_entry.config(bootstyle="warning") # type: ignore
741
- else:
742
- self.output_frame.author_entry.config(bootstyle="default") # type: ignore
743
-
744
- if self.comp_preset_var.get() == "auto":
745
- if output_option == "local":
746
- self.no_compress_var.set(True)
747
- else:
748
- self.no_compress_var.set(False)
749
- self.comp_frame.cb_no_compress()
750
-
751
- # Credentials
752
- if output_option == "signal" and not self.signal_uuid_var.get():
753
- self.cred_frame.signal_uuid_entry.config(bootstyle="warning") # type: ignore
754
- else:
755
- self.cred_frame.signal_uuid_entry.config(bootstyle="default") # type: ignore
756
-
757
- if output_option == "signal" and not self.signal_password_var.get():
758
- self.cred_frame.signal_password_entry.config(bootstyle="warning") # type: ignore
759
- else:
760
- self.cred_frame.signal_password_entry.config(bootstyle="default") # type: ignore
761
-
762
- if (
763
- input_option == "telegram" or output_option == "telegram"
764
- ) and not self.telegram_token_var.get():
765
- self.cred_frame.telegram_token_entry.config(bootstyle="warning") # type: ignore
766
- else:
767
- self.cred_frame.telegram_token_entry.config(bootstyle="default") # type: ignore
768
-
769
- if output_option == "telegram" and not self.telegram_userid_var.get():
770
- self.cred_frame.telegram_userid_entry.config(bootstyle="warning") # type: ignore
771
- else:
772
- self.cred_frame.telegram_userid_entry.config(bootstyle="default") # type: ignore
773
-
774
- if (
775
- urlparse(url).netloc == "e.kakao.com"
776
- and not self.kakao_auth_token_var.get()
777
- ):
778
- self.cred_frame.kakao_auth_token_entry.config(bootstyle="warning") # type: ignore
779
- else:
780
- self.cred_frame.kakao_auth_token_entry.config(bootstyle="default") # type: ignore
781
-
782
- # Check for Input and Compression mismatch
783
- if (
784
- not self.no_compress_var.get()
785
- and self.get_output_name() != "local"
786
- and self.comp_preset_var.get() not in ("auto", "custom")
787
- and self.get_output_name() not in self.comp_preset_var.get()
788
- ):
789
- self.comp_frame.comp_preset_opt.config(bootstyle="warning") # type: ignore
790
- self.output_frame.output_option_opt.config(bootstyle="warning") # type: ignore
791
- else:
792
- self.comp_frame.comp_preset_opt.config(bootstyle="secondary") # type: ignore
793
- self.output_frame.output_option_opt.config(bootstyle="secondary") # type: ignore
794
-
795
- return True
1
+ #!/usr/bin/env python3
2
+ import os
3
+ import platform
4
+ import signal
5
+ import sys
6
+ from json.decoder import JSONDecodeError
7
+ from math import ceil
8
+ from multiprocessing import Event, cpu_count
9
+ from pathlib import Path
10
+ from threading import Thread
11
+ from typing import Any, Callable, Dict, Optional
12
+ from urllib.parse import urlparse
13
+
14
+ from mergedeep import merge # type: ignore
15
+ from PIL import ImageFont
16
+ from ttkbootstrap import BooleanVar, DoubleVar, IntVar, StringVar, Window # type: ignore
17
+
18
+ from sticker_convert.definitions import CONFIG_DIR, DEFAULT_DIR, ROOT_DIR, RUNTIME_STATE
19
+ from sticker_convert.gui_components.frames.comp_frame import CompFrame
20
+ from sticker_convert.gui_components.frames.config_frame import ConfigFrame
21
+ from sticker_convert.gui_components.frames.control_frame import ControlFrame
22
+ from sticker_convert.gui_components.frames.cred_frame import CredFrame
23
+ from sticker_convert.gui_components.frames.input_frame import InputFrame
24
+ from sticker_convert.gui_components.frames.output_frame import OutputFrame
25
+ from sticker_convert.gui_components.frames.progress_frame import ProgressFrame
26
+ from sticker_convert.gui_components.gui_utils import GUIUtils
27
+ from sticker_convert.job import Job
28
+ from sticker_convert.job_option import CompOption, CredOption, InputOption, OutputOption
29
+ from sticker_convert.utils.callback import CallbackGui
30
+ from sticker_convert.utils.files.json_manager import JsonManager
31
+ from sticker_convert.utils.files.json_resources_loader import load_resource_json
32
+ from sticker_convert.utils.files.metadata_handler import MetadataHandler
33
+ from sticker_convert.utils.translate import SUPPORTED_LANG, I
34
+ from sticker_convert.utils.url_detect import UrlDetect
35
+ from sticker_convert.version import __version__
36
+
37
+
38
+ class GUI(Window):
39
+ def __init__(self) -> None:
40
+ self.MSG_MORAL = I(
41
+ "sticker-convert is Free and Opensource software by laggykiller\n"
42
+ "{project_url}\n"
43
+ "Please use the stickers with your friends only.\n"
44
+ "It is illegal and immoral to sell stickers downloaded from this program."
45
+ )
46
+ self.PROJECT_URL = "https://github.com/laggykiller/sticker-convert"
47
+
48
+ self.MSG_MAC14_TK_BUG = I(
49
+ "NOTICE: If buttons are not responsive, try to press\n"
50
+ "on title bar or move mouse cursor away from window for a while.\n"
51
+ "(This is a bug in tkinter specific to macOS 14 python <=3.11.6)\n"
52
+ "({mac14_tk_bug_url})"
53
+ )
54
+ self.MAC14_TK_BUG_URL = "https://github.com/python/cpython/issues/110218"
55
+
56
+ super().__init__(themename="darkly", alpha=0) # type: ignore
57
+ self.cb = CallbackGui(self)
58
+ self.init_done = False
59
+ self.load_jsons()
60
+
61
+ font_path = ROOT_DIR / "resources/NotoColorEmoji.ttf"
62
+ self.emoji_font = ImageFont.truetype(font_path.as_posix(), 109)
63
+
64
+ GUIUtils.set_icon(self)
65
+
66
+ self.title(f"sticker-convert {__version__}")
67
+ self.protocol("WM_DELETE_WINDOW", self.quit)
68
+
69
+ (
70
+ self.main_frame,
71
+ self.horizontal_scrollbar_frame,
72
+ self.canvas,
73
+ self.x_scrollbar,
74
+ self.y_scrollbar,
75
+ self.scrollable_frame,
76
+ ) = GUIUtils.create_scrollable_frame(self)
77
+
78
+ self.declare_variables()
79
+ self.apply_config()
80
+ self.apply_creds()
81
+ self.init_frames()
82
+ self.pack_frames()
83
+ self.author_info()
84
+ self.warn_tkinter_bug()
85
+ GUIUtils.finalize_window(self)
86
+
87
+ self.bind("<<exec_in_main>>", self.exec_in_main) # type: ignore
88
+
89
+ def __enter__(self) -> "GUI":
90
+ return self
91
+
92
+ def gui(self) -> None:
93
+ self.init_done = True
94
+ self.highlight_fields()
95
+ self.mainloop()
96
+
97
+ def quit(self) -> None:
98
+ if self.job:
99
+ response = self.cb.put(
100
+ ("ask_bool", (I("Job is running, really quit?"),), None)
101
+ )
102
+ if response is False:
103
+ return
104
+
105
+ self.cb.put(I("Quitting, please wait..."))
106
+
107
+ self.save_config()
108
+ if self.settings_save_cred_var.get() is True:
109
+ self.save_creds()
110
+ else:
111
+ self.delete_creds()
112
+
113
+ if self.job:
114
+ self.cancel_job()
115
+ self.destroy()
116
+
117
+ def reset(self) -> None:
118
+ self.init_done = False
119
+
120
+ # These json have translated version
121
+ RUNTIME_STATE["help_json"] = None
122
+ RUNTIME_STATE["input_json"] = None
123
+ RUNTIME_STATE["output_json"] = None
124
+ self.load_jsons()
125
+ self.apply_config()
126
+
127
+ self.input_frame.destroy()
128
+ self.comp_frame.destroy()
129
+ self.output_frame.destroy()
130
+ self.cred_frame.destroy()
131
+ self.settings_frame.destroy()
132
+ self.progress_frame.destroy()
133
+ self.control_frame.destroy()
134
+
135
+ self.init_frames()
136
+ self.pack_frames()
137
+ self.init_done = True
138
+ self.highlight_fields()
139
+
140
+ def declare_variables(self) -> None:
141
+ # Input
142
+ self.input_option_display_var = StringVar(self)
143
+ self.input_option_true_var = StringVar(self)
144
+ self.input_setdir_var = StringVar(self)
145
+ self.input_address_var = StringVar(self)
146
+
147
+ # Compression
148
+ self.no_compress_var = BooleanVar()
149
+ self.comp_preset_var = StringVar(self)
150
+ self.fps_min_var = IntVar(self)
151
+ self.fps_max_var = IntVar(self)
152
+ self.fps_disable_var = BooleanVar()
153
+ self.fps_power_var = DoubleVar()
154
+ self.res_w_min_var = IntVar(self)
155
+ self.res_w_max_var = IntVar(self)
156
+ self.res_w_disable_var = BooleanVar()
157
+ self.res_h_min_var = IntVar(self)
158
+ self.res_h_max_var = IntVar(self)
159
+ self.res_h_disable_var = BooleanVar()
160
+ self.res_power_var = DoubleVar()
161
+ self.res_snap_pow2_var = BooleanVar()
162
+ self.quality_min_var = IntVar(self)
163
+ self.quality_max_var = IntVar(self)
164
+ self.quality_disable_var = BooleanVar()
165
+ self.quality_power_var = DoubleVar()
166
+ self.color_min_var = IntVar(self)
167
+ self.color_max_var = IntVar(self)
168
+ self.color_disable_var = BooleanVar()
169
+ self.color_power_var = DoubleVar()
170
+ self.duration_min_var = IntVar(self)
171
+ self.duration_max_var = IntVar(self)
172
+ self.duration_disable_var = BooleanVar()
173
+ self.padding_percent_var = IntVar(self)
174
+ self.img_size_max_var = IntVar(self)
175
+ self.vid_size_max_var = IntVar(self)
176
+ self.size_disable_var = BooleanVar()
177
+ self.bg_color_var = StringVar()
178
+ self.img_format_var = StringVar(self)
179
+ self.vid_format_var = StringVar(self)
180
+ self.fake_vid_var = BooleanVar()
181
+ self.scale_filter_var = StringVar(self)
182
+ self.quantize_method_var = StringVar(self)
183
+ self.chromium_path_var = StringVar(self)
184
+ self.cache_dir_var = StringVar(self)
185
+ self.default_emoji_var = StringVar(self)
186
+ self.steps_var = IntVar(self)
187
+ self.processes_var = IntVar(self)
188
+
189
+ # Output
190
+ self.output_option_display_var = StringVar(self)
191
+ self.output_option_true_var = StringVar(self)
192
+ self.output_setdir_var = StringVar(self)
193
+ self.title_var = StringVar(self)
194
+ self.author_var = StringVar(self)
195
+
196
+ # Credentials
197
+ self.signal_uuid_var = StringVar(self)
198
+ self.signal_password_var = StringVar(self)
199
+ self.telegram_token_var = StringVar(self)
200
+ self.telegram_userid_var = StringVar(self)
201
+ self.telethon_api_id_var = IntVar(self)
202
+ self.telethon_api_hash_var = StringVar(self)
203
+ self.kakao_auth_token_var = StringVar(self)
204
+ self.kakao_username_var = StringVar(self)
205
+ self.kakao_password_var = StringVar(self)
206
+ self.kakao_country_code_var = StringVar(self)
207
+ self.kakao_phone_number_var = StringVar(self)
208
+ self.kakao_device_uuid_var = StringVar(self)
209
+ self.kakao_bin_path_var = StringVar(self)
210
+ self.line_cookies_var = StringVar(self)
211
+ self.viber_auth_var = StringVar(self)
212
+ self.viber_bin_path_var = StringVar(self)
213
+ self.discord_token_var = StringVar(self)
214
+
215
+ # Config
216
+ self.settings_save_cred_var = BooleanVar()
217
+
218
+ # Other
219
+ self.lang_display_var = StringVar(self)
220
+ self.lang_true_var = StringVar(self)
221
+ self.response_event = Event()
222
+ self.response = None
223
+ self.action: Optional[Callable[..., Any]] = None
224
+ self.job: Optional[Job] = None
225
+
226
+ def init_frames(self) -> None:
227
+ self.input_frame = InputFrame(
228
+ self, self.scrollable_frame, borderwidth=1, text=I("Input")
229
+ )
230
+ self.comp_frame = CompFrame(
231
+ self, self.scrollable_frame, borderwidth=1, text=I("Compression options")
232
+ )
233
+ self.output_frame = OutputFrame(
234
+ self, self.scrollable_frame, borderwidth=1, text=I("Output")
235
+ )
236
+ self.cred_frame = CredFrame(
237
+ self, self.scrollable_frame, borderwidth=1, text=I("Credentials")
238
+ )
239
+ self.settings_frame = ConfigFrame(
240
+ self, self.scrollable_frame, borderwidth=1, text=I("Config")
241
+ )
242
+ self.progress_frame = ProgressFrame(
243
+ self, self.scrollable_frame, borderwidth=1, text=I("Progress")
244
+ )
245
+ self.control_frame = ControlFrame(self, self.scrollable_frame, borderwidth=1)
246
+
247
+ def pack_frames(self) -> None:
248
+ self.input_frame.grid(column=0, row=0, sticky="w", padx=5, pady=5)
249
+ self.comp_frame.grid(column=1, row=0, sticky="news", padx=5, pady=5)
250
+ self.output_frame.grid(column=0, row=1, sticky="w", padx=5, pady=5)
251
+ self.cred_frame.grid(column=1, row=1, rowspan=2, sticky="w", padx=5, pady=5)
252
+ self.settings_frame.grid(column=0, row=2, sticky="news", padx=5, pady=5)
253
+ self.progress_frame.grid(
254
+ column=0, row=3, columnspan=2, sticky="news", padx=5, pady=5
255
+ )
256
+ self.control_frame.grid(
257
+ column=0, row=4, columnspan=2, sticky="news", padx=5, pady=5
258
+ )
259
+
260
+ def author_info(self) -> None:
261
+ self.cb.put(self.MSG_MORAL.format(project_url=self.PROJECT_URL))
262
+
263
+ def warn_tkinter_bug(self) -> None:
264
+ if (
265
+ platform.system() == "Darwin"
266
+ and platform.mac_ver()[0].split(".")[0] == "14"
267
+ and sys.version_info[0] == 3
268
+ and sys.version_info[1] == 11
269
+ and sys.version_info[2] <= 6
270
+ ):
271
+ self.cb.put(
272
+ self.MSG_MAC14_TK_BUG.format(mac14_tk_bug_url=self.MAC14_TK_BUG_URL)
273
+ )
274
+
275
+ def load_jsons(self) -> None:
276
+ self.help = load_resource_json("help")
277
+ self.input_presets = load_resource_json("input")
278
+ self.compression_presets = load_resource_json("compression")
279
+ self.output_presets = load_resource_json("output")
280
+ self.emoji_list = load_resource_json("emoji")
281
+
282
+ self.settings_path = CONFIG_DIR / "config.json"
283
+ if self.settings_path.is_file():
284
+ try:
285
+ self.settings: Dict[Any, Any] = JsonManager.load_json(
286
+ self.settings_path
287
+ )
288
+ except JSONDecodeError:
289
+ self.cb.put(I("Warning: config.json content is corrupted"))
290
+ self.settings = {}
291
+ else:
292
+ self.settings = {}
293
+
294
+ self.creds_path = CONFIG_DIR / "creds.json"
295
+ if self.creds_path.is_file():
296
+ try:
297
+ self.creds = JsonManager.load_json(self.creds_path)
298
+ except JSONDecodeError:
299
+ self.cb.put(I("Warning: creds.json content is corrupted"))
300
+ self.creds = {}
301
+ else:
302
+ self.creds = {}
303
+
304
+ def save_config(self) -> None:
305
+ # Only update comp_custom if custom preset is selected
306
+ if self.comp_preset_var.get() == "custom":
307
+ comp_custom: Dict[Any, Any] = merge( # type: ignore
308
+ self.compression_presets.get("custom"), # type: ignore
309
+ self.get_opt_comp().to_dict(),
310
+ )
311
+ comp_custom["format"]["img"] = comp_custom["format"]["img"][0]
312
+ comp_custom["format"]["vid"] = comp_custom["format"]["vid"][0]
313
+ del comp_custom["preset"]
314
+ del comp_custom["no_compress"]
315
+ else:
316
+ compression_presets_custom = self.compression_presets.get("custom")
317
+ if compression_presets_custom is None:
318
+ comp_custom = {}
319
+ else:
320
+ comp_custom = compression_presets_custom
321
+
322
+ self.settings = {
323
+ "input": self.get_opt_input().to_dict(),
324
+ "comp": {
325
+ "no_compress": self.no_compress_var.get(),
326
+ "preset": self.comp_preset_var.get(),
327
+ "chromium_path": self.chromium_path_var.get(),
328
+ "cache_dir": self.cache_dir_var.get(),
329
+ "processes": self.processes_var.get(),
330
+ },
331
+ "comp_custom": comp_custom,
332
+ "output": self.get_opt_output().to_dict(),
333
+ "creds": {"save_cred": self.settings_save_cred_var.get()},
334
+ "lang": self.lang_true_var.get(),
335
+ }
336
+
337
+ JsonManager.save_json(self.settings_path, self.settings)
338
+
339
+ def save_creds(self) -> None:
340
+ self.creds = self.get_opt_cred().to_dict()
341
+
342
+ JsonManager.save_json(self.creds_path, self.creds)
343
+
344
+ def delete_creds(self) -> None:
345
+ if self.creds_path.is_file():
346
+ os.remove(self.creds_path)
347
+
348
+ def delete_config(self) -> None:
349
+ if self.settings_path.is_file():
350
+ os.remove(self.settings_path)
351
+
352
+ def apply_config(self) -> None:
353
+ # Input
354
+ self.default_input_mode: str = self.settings.get("input", {}).get(
355
+ "option", "auto"
356
+ )
357
+ self.input_address_var.set(self.settings.get("input", {}).get("url", ""))
358
+ default_stickers_input_dir = str(DEFAULT_DIR / "stickers_input")
359
+ self.input_setdir_var.set(
360
+ self.settings.get("input", {}).get("dir", default_stickers_input_dir)
361
+ )
362
+ if not Path(self.input_setdir_var.get()).is_dir():
363
+ self.input_setdir_var.set(default_stickers_input_dir)
364
+ self.input_option_display_var.set(
365
+ self.input_presets[self.default_input_mode]["full_name"]
366
+ )
367
+ self.input_option_true_var.set(
368
+ self.input_presets[self.default_input_mode]["full_name"]
369
+ )
370
+
371
+ # Compression
372
+ self.no_compress_var.set(
373
+ self.settings.get("comp", {}).get("no_compress", False)
374
+ )
375
+ default_comp_preset = list(self.compression_presets.keys())[0]
376
+ self.comp_preset_var.set(
377
+ self.settings.get("comp", {}).get("preset", default_comp_preset)
378
+ )
379
+ comp_custom = self.settings.get("comp_custom")
380
+ if comp_custom:
381
+ self.compression_presets["custom"] = merge(
382
+ self.compression_presets["custom"], comp_custom
383
+ )
384
+ self.cache_dir_var.set(self.settings.get("comp", {}).get("cache_dir", ""))
385
+ self.chromium_path_var.set(
386
+ self.settings.get("comp", {}).get("chromium_path", "")
387
+ )
388
+ self.processes_var.set(
389
+ self.settings.get("comp", {}).get("processes", ceil(cpu_count() / 2))
390
+ )
391
+ self.default_output_mode: str = self.settings.get("output", {}).get(
392
+ "option", "signal"
393
+ )
394
+
395
+ # Output
396
+ default_stickers_output_dir = str(DEFAULT_DIR / "stickers_output")
397
+ self.output_setdir_var.set(
398
+ self.settings.get("output", {}).get("dir", default_stickers_output_dir)
399
+ )
400
+ if not Path(self.output_setdir_var.get()).is_dir():
401
+ self.output_setdir_var.set(default_stickers_output_dir)
402
+ self.title_var.set(self.settings.get("output", {}).get("title", ""))
403
+ self.author_var.set(self.settings.get("output", {}).get("author", ""))
404
+ self.settings_save_cred_var.set(
405
+ self.settings.get("creds", {}).get("save_cred", True)
406
+ )
407
+ self.output_option_display_var.set(
408
+ self.output_presets[self.default_output_mode]["full_name"]
409
+ )
410
+ self.output_option_true_var.set(
411
+ self.output_presets[self.default_output_mode]["full_name"]
412
+ )
413
+
414
+ # Language
415
+ self.lang_true_var.set(self.settings.get("lang", "auto"))
416
+ self.lang_display_var.set(
417
+ [k for k, v in SUPPORTED_LANG.items() if v == self.lang_true_var.get()][0]
418
+ )
419
+
420
+ def apply_creds(self) -> None:
421
+ self.signal_uuid_var.set(self.creds.get("signal", {}).get("uuid", ""))
422
+ self.signal_password_var.set(self.creds.get("signal", {}).get("password", ""))
423
+ self.telegram_token_var.set(self.creds.get("telegram", {}).get("token", ""))
424
+ self.telegram_userid_var.set(self.creds.get("telegram", {}).get("userid", ""))
425
+ self.telethon_api_id_var.set(self.creds.get("telethon", {}).get("api_id", 0))
426
+ self.telethon_api_hash_var.set(
427
+ self.creds.get("telethon", {}).get("api_hash", "")
428
+ )
429
+ self.kakao_auth_token_var.set(self.creds.get("kakao", {}).get("auth_token", ""))
430
+ self.kakao_username_var.set(self.creds.get("kakao", {}).get("username", ""))
431
+ self.kakao_password_var.set(self.creds.get("kakao", {}).get("password", ""))
432
+ self.kakao_country_code_var.set(
433
+ self.creds.get("kakao", {}).get("country_code", "")
434
+ )
435
+ self.kakao_phone_number_var.set(
436
+ self.creds.get("kakao", {}).get("phone_number", "")
437
+ )
438
+ self.kakao_device_uuid_var.set(
439
+ self.creds.get("kakao", {}).get("device_uuid", "")
440
+ )
441
+ self.line_cookies_var.set(self.creds.get("line", {}).get("cookies", ""))
442
+ self.viber_auth_var.set(self.creds.get("viber", {}).get("auth", ""))
443
+ self.discord_token_var.set(self.creds.get("discord", {}).get("token", ""))
444
+
445
+ def get_input_name(self) -> str:
446
+ return [
447
+ k
448
+ for k, v in self.input_presets.items()
449
+ if v["full_name"] == self.input_option_true_var.get()
450
+ ][0]
451
+
452
+ def get_input_display_name(self) -> str:
453
+ return [
454
+ k
455
+ for k, v in self.input_presets.items()
456
+ if v["full_name"] == self.input_option_display_var.get()
457
+ ][0]
458
+
459
+ def get_output_name(self) -> str:
460
+ return [
461
+ k
462
+ for k, v in self.output_presets.items()
463
+ if v["full_name"] == self.output_option_true_var.get()
464
+ ][0]
465
+
466
+ # def get_output_display_name(self) -> str:
467
+ # return [
468
+ # k
469
+ # for k, v in self.output_presets.items()
470
+ # if v["full_name"] == self.output_option_display_var.get()
471
+ # ][0]
472
+
473
+ def get_preset(self) -> str:
474
+ selection = self.comp_preset_var.get()
475
+ if selection == "auto":
476
+ output_option = self.get_output_name()
477
+ if "telegram_emoji" in output_option:
478
+ return "telegram_emoji"
479
+ if "telegram" in output_option:
480
+ return "telegram"
481
+ if output_option == "imessage":
482
+ return "imessage_small"
483
+ if output_option == "local":
484
+ return selection
485
+ return output_option
486
+
487
+ return selection
488
+
489
+ def start_job(self) -> None:
490
+ self.save_config()
491
+ if self.settings_save_cred_var.get() is True:
492
+ self.save_creds()
493
+ else:
494
+ self.delete_creds()
495
+
496
+ self.control_frame.start_btn.config(text=I("Cancel"), bootstyle="danger") # type: ignore
497
+ self.set_inputs("disabled")
498
+
499
+ opt_input = self.get_opt_input()
500
+ opt_output = self.get_opt_output()
501
+ opt_comp = self.get_opt_comp()
502
+ opt_cred = self.get_opt_cred()
503
+
504
+ self.job = Job(opt_input, opt_comp, opt_output, opt_cred, self.cb)
505
+
506
+ signal.signal(signal.SIGINT, self.job.cancel)
507
+
508
+ Thread(target=self.start_process, daemon=True).start()
509
+
510
+ def get_opt_input(self) -> InputOption:
511
+ return InputOption(
512
+ option=self.get_input_name(),
513
+ url=self.input_address_var.get(),
514
+ dir=Path(self.input_setdir_var.get()),
515
+ )
516
+
517
+ def get_opt_output(self) -> OutputOption:
518
+ return OutputOption(
519
+ option=self.get_output_name(),
520
+ dir=Path(self.output_setdir_var.get()),
521
+ title=self.title_var.get(),
522
+ author=self.author_var.get(),
523
+ )
524
+
525
+ def get_opt_comp(self) -> CompOption:
526
+ return CompOption(
527
+ preset=self.get_preset(),
528
+ size_max_img=self.img_size_max_var.get()
529
+ if not self.size_disable_var.get()
530
+ else None,
531
+ size_max_vid=self.vid_size_max_var.get()
532
+ if not self.size_disable_var.get()
533
+ else None,
534
+ format_img=(self.img_format_var.get(),),
535
+ format_vid=(self.vid_format_var.get(),),
536
+ fps_min=self.fps_min_var.get() if not self.fps_disable_var.get() else None,
537
+ fps_max=self.fps_max_var.get() if not self.fps_disable_var.get() else None,
538
+ fps_power=self.fps_power_var.get(),
539
+ res_w_min=self.res_w_min_var.get()
540
+ if not self.res_w_disable_var.get()
541
+ else None,
542
+ res_w_max=self.res_w_max_var.get()
543
+ if not self.res_w_disable_var.get()
544
+ else None,
545
+ res_h_min=self.res_h_min_var.get()
546
+ if not self.res_h_disable_var.get()
547
+ else None,
548
+ res_h_max=self.res_h_max_var.get()
549
+ if not self.res_h_disable_var.get()
550
+ else None,
551
+ res_power=self.res_power_var.get(),
552
+ res_snap_pow2=self.res_snap_pow2_var.get(),
553
+ quality_min=self.quality_min_var.get()
554
+ if not self.quality_disable_var.get()
555
+ else None,
556
+ quality_max=self.quality_max_var.get()
557
+ if not self.quality_disable_var.get()
558
+ else None,
559
+ quality_power=self.quality_power_var.get(),
560
+ color_min=self.color_min_var.get()
561
+ if not self.color_disable_var.get()
562
+ else None,
563
+ color_max=self.color_max_var.get()
564
+ if not self.color_disable_var.get()
565
+ else None,
566
+ color_power=self.color_power_var.get(),
567
+ duration_min=self.duration_min_var.get()
568
+ if not self.duration_disable_var.get()
569
+ else None,
570
+ duration_max=self.duration_max_var.get()
571
+ if not self.duration_disable_var.get()
572
+ else None,
573
+ bg_color=self.bg_color_var.get(),
574
+ padding_percent=self.padding_percent_var.get(),
575
+ steps=self.steps_var.get(),
576
+ fake_vid=self.fake_vid_var.get(),
577
+ scale_filter=self.scale_filter_var.get(),
578
+ quantize_method=self.quantize_method_var.get(),
579
+ chromium_path=self.chromium_path_var.get()
580
+ if self.chromium_path_var.get() != ""
581
+ else None,
582
+ cache_dir=self.cache_dir_var.get()
583
+ if self.cache_dir_var.get() != ""
584
+ else None,
585
+ default_emoji=self.default_emoji_var.get(),
586
+ no_compress=self.no_compress_var.get(),
587
+ processes=self.processes_var.get(),
588
+ )
589
+
590
+ def get_opt_cred(self) -> CredOption:
591
+ return CredOption(
592
+ signal_uuid=self.signal_uuid_var.get(),
593
+ signal_password=self.signal_password_var.get(),
594
+ telegram_token=self.telegram_token_var.get(),
595
+ telegram_userid=self.telegram_userid_var.get(),
596
+ telethon_api_id=self.telethon_api_id_var.get(),
597
+ telethon_api_hash=self.telethon_api_hash_var.get(),
598
+ kakao_auth_token=self.kakao_auth_token_var.get(),
599
+ kakao_username=self.kakao_username_var.get(),
600
+ kakao_password=self.kakao_password_var.get(),
601
+ kakao_country_code=self.kakao_country_code_var.get(),
602
+ kakao_phone_number=self.kakao_phone_number_var.get(),
603
+ kakao_device_uuid=self.kakao_device_uuid_var.get(),
604
+ line_cookies=self.line_cookies_var.get(),
605
+ viber_auth=self.viber_auth_var.get(),
606
+ discord_token=self.discord_token_var.get(),
607
+ )
608
+
609
+ def start_process(self) -> None:
610
+ if self.job:
611
+ self.job.start()
612
+ self.job = None
613
+
614
+ self.stop_job()
615
+
616
+ def stop_job(self) -> None:
617
+ self.set_inputs("normal")
618
+ self.control_frame.start_btn.config(text=I("Start"), bootstyle="default") # type: ignore
619
+
620
+ def cancel_job(self) -> None:
621
+ if self.job:
622
+ # Need to start new thread or else GUI may freeze
623
+ Thread(target=self.job.cancel, daemon=True).start()
624
+
625
+ def set_inputs(self, state: str) -> None:
626
+ # state: 'normal', 'disabled'
627
+
628
+ self.input_frame.set_states(state=state)
629
+ self.comp_frame.set_states(state=state)
630
+ self.output_frame.set_states(state=state)
631
+ self.cred_frame.set_states(state=state)
632
+ self.settings_frame.set_states(state=state)
633
+
634
+ def exec_in_main(self, _evt: Any) -> Any:
635
+ if self.action:
636
+ self.response = self.action()
637
+ self.response_event.set()
638
+
639
+ def highlight_fields(self) -> bool:
640
+ if not self.init_done:
641
+ return True
642
+
643
+ input_option = self.get_input_name()
644
+ input_option_display = self.get_input_display_name()
645
+ output_option = self.get_output_name()
646
+ # output_option_display = self.get_output_display_name()
647
+ url = self.input_address_var.get()
648
+
649
+ in_out_dir_same = (
650
+ Path(self.input_setdir_var.get()).absolute()
651
+ == Path(self.output_setdir_var.get()).absolute()
652
+ )
653
+
654
+ # Input
655
+ if in_out_dir_same is True:
656
+ self.input_frame.input_setdir_entry.config(bootstyle="danger") # type: ignore
657
+ elif not Path(self.input_setdir_var.get()).is_dir():
658
+ self.input_frame.input_setdir_entry.config(bootstyle="warning") # type: ignore
659
+ else:
660
+ self.input_frame.input_setdir_entry.config(bootstyle="default") # type: ignore
661
+
662
+ self.input_frame.address_lbl.config(
663
+ text=self.input_presets[input_option_display]["address_lbls"]
664
+ )
665
+ self.input_frame.address_entry.config(bootstyle="default") # type: ignore
666
+
667
+ if input_option == "local":
668
+ self.input_frame.address_entry.config(state="disabled")
669
+ self.input_frame.address_tip.config(
670
+ text=self.input_presets[input_option_display]["example"]
671
+ )
672
+
673
+ else:
674
+ self.input_frame.address_entry.config(state="normal")
675
+ self.input_frame.address_tip.config(
676
+ text=self.input_presets[input_option_display]["example"]
677
+ )
678
+ download_option = UrlDetect.detect(url)
679
+
680
+ if not url:
681
+ self.input_frame.address_entry.config(bootstyle="warning") # type: ignore
682
+
683
+ elif (
684
+ download_option is None
685
+ or input_option.startswith(download_option) is False
686
+ and not (
687
+ input_option
688
+ in ("kakao", "band", "line", "discord", "discord_emoji")
689
+ and url.isnumeric()
690
+ )
691
+ ):
692
+ self.input_frame.address_entry.config(bootstyle="danger") # type: ignore
693
+ self.input_frame.address_tip.config(
694
+ text=I("Invalid URL: ")
695
+ + self.input_presets[input_option_display]["example"]
696
+ )
697
+
698
+ elif input_option_display == "auto" and download_option:
699
+ self.input_frame.address_tip.config(
700
+ text=I("Detected URL: ") + download_option
701
+ )
702
+
703
+ # Output
704
+ if in_out_dir_same is True:
705
+ self.output_frame.output_setdir_entry.config(bootstyle="danger") # type: ignore
706
+ elif not Path(self.output_setdir_var.get()).is_dir():
707
+ self.output_frame.output_setdir_entry.config(bootstyle="warning") # type: ignore
708
+ else:
709
+ self.output_frame.output_setdir_entry.config(bootstyle="default") # type: ignore
710
+
711
+ if (
712
+ MetadataHandler.check_metadata_required(output_option, "title")
713
+ and not MetadataHandler.check_metadata_provided(
714
+ Path(self.input_setdir_var.get()), input_option, "title"
715
+ )
716
+ and not self.title_var.get()
717
+ ):
718
+ self.output_frame.title_entry.config(bootstyle="warning") # type: ignore
719
+ else:
720
+ self.output_frame.title_entry.config(bootstyle="default") # type: ignore
721
+
722
+ if (
723
+ MetadataHandler.check_metadata_required(output_option, "author")
724
+ and not MetadataHandler.check_metadata_provided(
725
+ Path(self.input_setdir_var.get()), input_option, "author"
726
+ )
727
+ and not self.author_var.get()
728
+ ):
729
+ self.output_frame.author_entry.config(bootstyle="warning") # type: ignore
730
+ else:
731
+ self.output_frame.author_entry.config(bootstyle="default") # type: ignore
732
+
733
+ # Credentials
734
+ if output_option == "signal" and not self.signal_uuid_var.get():
735
+ self.cred_frame.signal_uuid_entry.config(bootstyle="warning") # type: ignore
736
+ else:
737
+ self.cred_frame.signal_uuid_entry.config(bootstyle="default") # type: ignore
738
+
739
+ if output_option == "signal" and not self.signal_password_var.get():
740
+ self.cred_frame.signal_password_entry.config(bootstyle="warning") # type: ignore
741
+ else:
742
+ self.cred_frame.signal_password_entry.config(bootstyle="default") # type: ignore
743
+
744
+ if (
745
+ input_option == "telegram" or output_option == "telegram"
746
+ ) and not self.telegram_token_var.get():
747
+ self.cred_frame.telegram_token_entry.config(bootstyle="warning") # type: ignore
748
+ else:
749
+ self.cred_frame.telegram_token_entry.config(bootstyle="default") # type: ignore
750
+
751
+ if output_option == "telegram" and not self.telegram_userid_var.get():
752
+ self.cred_frame.telegram_userid_entry.config(bootstyle="warning") # type: ignore
753
+ else:
754
+ self.cred_frame.telegram_userid_entry.config(bootstyle="default") # type: ignore
755
+
756
+ if output_option == "viber" and not self.viber_auth_var.get():
757
+ self.cred_frame.viber_auth_entry.config(bootstyle="warning") # type: ignore
758
+ else:
759
+ self.cred_frame.viber_auth_entry.config(bootstyle="default") # type: ignore
760
+
761
+ if (
762
+ urlparse(url).netloc == "e.kakao.com"
763
+ and not self.kakao_auth_token_var.get()
764
+ ):
765
+ self.cred_frame.kakao_auth_token_entry.config(bootstyle="warning") # type: ignore
766
+ else:
767
+ self.cred_frame.kakao_auth_token_entry.config(bootstyle="default") # type: ignore
768
+
769
+ if input_option.startswith("discord") and not self.discord_token_var.get():
770
+ self.cred_frame.discord_token_entry.config(bootstyle="warning") # type: ignore
771
+ else:
772
+ self.cred_frame.discord_token_entry.config(bootstyle="default") # type: ignore
773
+
774
+ # Check for Input and Compression mismatch
775
+ if (
776
+ not self.no_compress_var.get()
777
+ and self.get_output_name() != "local"
778
+ and self.comp_preset_var.get() not in ("auto", "custom")
779
+ and self.get_output_name().replace("_telethon", "")
780
+ not in self.comp_preset_var.get()
781
+ ):
782
+ self.comp_frame.comp_preset_opt.config(bootstyle="warning") # type: ignore
783
+ self.output_frame.output_option_opt.config(bootstyle="warning") # type: ignore
784
+ else:
785
+ self.comp_frame.comp_preset_opt.config(bootstyle="secondary") # type: ignore
786
+ self.output_frame.output_option_opt.config(bootstyle="secondary") # type: ignore
787
+
788
+ return True