sticker-convert 2.1.5__py3-none-any.whl → 2.1.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sticker_convert/__init__.py +1 -1
- sticker_convert/__main__.py +7 -4
- sticker_convert/cli.py +42 -32
- sticker_convert/converter.py +432 -0
- sticker_convert/downloaders/download_base.py +40 -16
- sticker_convert/downloaders/download_kakao.py +103 -136
- sticker_convert/downloaders/download_line.py +30 -12
- sticker_convert/downloaders/download_signal.py +48 -32
- sticker_convert/downloaders/download_telegram.py +71 -26
- sticker_convert/gui.py +79 -130
- sticker_convert/{gui_frames → gui_components/frames}/comp_frame.py +2 -3
- sticker_convert/{gui_frames → gui_components/frames}/config_frame.py +3 -4
- sticker_convert/{gui_frames → gui_components/frames}/control_frame.py +2 -2
- sticker_convert/{gui_frames → gui_components/frames}/cred_frame.py +4 -4
- sticker_convert/{gui_frames → gui_components/frames}/input_frame.py +4 -4
- sticker_convert/{gui_frames → gui_components/frames}/output_frame.py +3 -3
- sticker_convert/{gui_frames → gui_components/frames}/progress_frame.py +1 -1
- sticker_convert/{utils → gui_components}/gui_utils.py +38 -21
- sticker_convert/{gui_windows → gui_components/windows}/advanced_compression_window.py +3 -2
- sticker_convert/{gui_windows → gui_components/windows}/base_window.py +3 -2
- sticker_convert/{gui_windows → gui_components/windows}/kakao_get_auth_window.py +3 -3
- sticker_convert/{gui_windows → gui_components/windows}/line_get_auth_window.py +2 -2
- sticker_convert/{gui_windows → gui_components/windows}/signal_get_auth_window.py +2 -2
- sticker_convert/{flow.py → job.py} +91 -102
- sticker_convert/job_option.py +301 -0
- sticker_convert/resources/compression.json +1 -1
- sticker_convert/uploaders/compress_wastickers.py +95 -74
- sticker_convert/uploaders/upload_base.py +16 -4
- sticker_convert/uploaders/upload_signal.py +100 -62
- sticker_convert/uploaders/upload_telegram.py +168 -128
- sticker_convert/uploaders/xcode_imessage.py +202 -132
- sticker_convert/{auth → utils/auth}/get_kakao_auth.py +7 -5
- sticker_convert/{auth → utils/auth}/get_line_auth.py +6 -5
- sticker_convert/{auth → utils/auth}/get_signal_auth.py +1 -1
- sticker_convert/utils/fake_cb_msg.py +5 -2
- sticker_convert/utils/{cache_store.py → files/cache_store.py} +7 -3
- sticker_convert/utils/files/dir_utils.py +64 -0
- sticker_convert/utils/{json_manager.py → files/json_manager.py} +5 -4
- sticker_convert/utils/files/metadata_handler.py +226 -0
- sticker_convert/utils/files/run_bin.py +58 -0
- sticker_convert/utils/{apple_png_normalize.py → media/apple_png_normalize.py} +23 -20
- sticker_convert/utils/{codec_info.py → media/codec_info.py} +41 -35
- sticker_convert/utils/media/decrypt_kakao.py +68 -0
- sticker_convert/utils/media/format_verify.py +184 -0
- sticker_convert/utils/url_detect.py +16 -14
- {sticker_convert-2.1.5.dist-info → sticker_convert-2.1.7.dist-info}/METADATA +11 -11
- {sticker_convert-2.1.5.dist-info → sticker_convert-2.1.7.dist-info}/RECORD +52 -50
- {sticker_convert-2.1.5.dist-info → sticker_convert-2.1.7.dist-info}/WHEEL +1 -1
- sticker_convert/utils/converter.py +0 -399
- sticker_convert/utils/curr_dir.py +0 -70
- sticker_convert/utils/format_verify.py +0 -188
- sticker_convert/utils/metadata_handler.py +0 -190
- sticker_convert/utils/run_bin.py +0 -46
- /sticker_convert/{gui_frames → gui_components/frames}/right_clicker.py +0 -0
- {sticker_convert-2.1.5.dist-info → sticker_convert-2.1.7.dist-info}/LICENSE +0 -0
- {sticker_convert-2.1.5.dist-info → sticker_convert-2.1.7.dist-info}/entry_points.txt +0 -0
- {sticker_convert-2.1.5.dist-info → sticker_convert-2.1.7.dist-info}/top_level.txt +0 -0
@@ -1,47 +1,64 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
from __future__ import annotations
|
1
3
|
import platform
|
2
4
|
from typing import Union, TYPE_CHECKING
|
3
5
|
|
4
6
|
from ttkbootstrap import Frame, Canvas, Scrollbar, PhotoImage
|
7
|
+
|
5
8
|
if TYPE_CHECKING:
|
6
|
-
from
|
7
|
-
from ..gui import GUI
|
9
|
+
from .windows.base_window import BaseWindow # type: ignore
|
10
|
+
from ..gui import GUI # type: ignore
|
11
|
+
|
8
12
|
|
9
13
|
class GUIUtils:
|
10
14
|
@staticmethod
|
11
15
|
def set_icon(window: Union["BaseWindow", "GUI"]):
|
12
|
-
window.icon = PhotoImage(file=
|
16
|
+
window.icon = PhotoImage(file="resources/appicon.png")
|
13
17
|
window.iconphoto(1, window.icon)
|
14
|
-
if platform.system() ==
|
15
|
-
window.iconbitmap(bitmap=
|
16
|
-
elif platform.system() ==
|
17
|
-
window.iconbitmap(bitmap=
|
18
|
-
window.tk.call(
|
18
|
+
if platform.system() == "Darwin":
|
19
|
+
window.iconbitmap(bitmap="resources/appicon.icns")
|
20
|
+
elif platform.system() == "Windows":
|
21
|
+
window.iconbitmap(bitmap="resources/appicon.ico")
|
22
|
+
window.tk.call("wm", "iconphoto", window._w, window.icon)
|
19
23
|
|
20
24
|
@staticmethod
|
21
|
-
def create_scrollable_frame(
|
25
|
+
def create_scrollable_frame(
|
26
|
+
window: Union["BaseWindow", "GUI"]
|
27
|
+
) -> tuple[Frame, Frame, Canvas, Scrollbar, Scrollbar, Frame]:
|
22
28
|
main_frame = Frame(window)
|
23
|
-
main_frame.pack(fill=
|
29
|
+
main_frame.pack(fill="both", expand=1)
|
24
30
|
|
25
31
|
horizontal_scrollbar_frame = Frame(main_frame)
|
26
|
-
horizontal_scrollbar_frame.pack(fill=
|
32
|
+
horizontal_scrollbar_frame.pack(fill="x", side="bottom")
|
27
33
|
|
28
34
|
canvas = Canvas(main_frame)
|
29
|
-
canvas.pack(side=
|
35
|
+
canvas.pack(side="left", fill="both", expand=1)
|
30
36
|
|
31
|
-
x_scrollbar = Scrollbar(
|
32
|
-
|
37
|
+
x_scrollbar = Scrollbar(
|
38
|
+
horizontal_scrollbar_frame, orient="horizontal", command=canvas.xview
|
39
|
+
)
|
40
|
+
x_scrollbar.pack(side="bottom", fill="x")
|
33
41
|
|
34
|
-
y_scrollbar = Scrollbar(main_frame, orient=
|
35
|
-
y_scrollbar.pack(side=
|
42
|
+
y_scrollbar = Scrollbar(main_frame, orient="vertical", command=canvas.yview)
|
43
|
+
y_scrollbar.pack(side="right", fill="y")
|
36
44
|
|
37
45
|
canvas.configure(xscrollcommand=x_scrollbar.set)
|
38
46
|
canvas.configure(yscrollcommand=y_scrollbar.set)
|
39
|
-
canvas.bind(
|
47
|
+
canvas.bind(
|
48
|
+
"<Configure>", lambda e: canvas.config(scrollregion=canvas.bbox("all"))
|
49
|
+
)
|
40
50
|
|
41
51
|
scrollable_frame = Frame(canvas)
|
42
|
-
canvas.create_window((0,0), window=scrollable_frame, anchor="nw")
|
52
|
+
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
|
43
53
|
|
44
|
-
return
|
54
|
+
return (
|
55
|
+
main_frame,
|
56
|
+
horizontal_scrollbar_frame,
|
57
|
+
canvas,
|
58
|
+
x_scrollbar,
|
59
|
+
y_scrollbar,
|
60
|
+
scrollable_frame,
|
61
|
+
)
|
45
62
|
|
46
63
|
@staticmethod
|
47
64
|
def finalize_window(window: Union["GUI", "BaseWindow"]):
|
@@ -62,7 +79,7 @@ class GUIUtils:
|
|
62
79
|
window_width = screen_width
|
63
80
|
if window_height > screen_height:
|
64
81
|
window_height = screen_height
|
65
|
-
|
82
|
+
|
66
83
|
frame_width = window_width - window.y_scrollbar.winfo_width()
|
67
84
|
frame_height = window_height - window.x_scrollbar.winfo_height()
|
68
85
|
|
@@ -73,4 +90,4 @@ class GUIUtils:
|
|
73
90
|
|
74
91
|
window.attributes("-alpha", 1)
|
75
92
|
|
76
|
-
window.focus_force()
|
93
|
+
window.focus_force()
|
@@ -1,13 +1,14 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
|
+
from __future__ import annotations
|
2
3
|
from functools import partial
|
3
4
|
|
4
5
|
from PIL import Image, ImageTk, ImageDraw
|
5
6
|
from ttkbootstrap import LabelFrame, Frame, OptionMenu, Button, Entry, Label, Checkbutton, Scrollbar, Canvas, StringVar # type: ignore
|
6
7
|
from tkinter import Event
|
7
8
|
|
8
|
-
from ..
|
9
|
+
from ..frames.right_clicker import RightClicker # type: ignore
|
9
10
|
from .base_window import BaseWindow # type: ignore
|
10
|
-
from ..
|
11
|
+
from ..gui_utils import GUIUtils # type: ignore
|
11
12
|
|
12
13
|
class AdvancedCompressionWindow(BaseWindow):
|
13
14
|
emoji_column_per_row = 10
|
@@ -1,11 +1,12 @@
|
|
1
|
+
#!/usr/bin/env python3
|
1
2
|
import platform
|
2
3
|
from typing import TYPE_CHECKING
|
3
4
|
|
4
5
|
from ttkbootstrap import Toplevel # type: ignore
|
5
6
|
|
6
7
|
if TYPE_CHECKING:
|
7
|
-
from
|
8
|
-
from ..
|
8
|
+
from ...gui import GUI # type: ignore
|
9
|
+
from ..gui_utils import GUIUtils # type: ignore
|
9
10
|
|
10
11
|
class BaseWindow(Toplevel):
|
11
12
|
def __init__(self, gui: "GUI"):
|
@@ -4,10 +4,10 @@ from threading import Thread
|
|
4
4
|
|
5
5
|
from ttkbootstrap import LabelFrame, Frame, Button, Entry, Label # type: ignore
|
6
6
|
|
7
|
-
from
|
8
|
-
from ..
|
7
|
+
from ...utils.auth.get_kakao_auth import GetKakaoAuth # type: ignore
|
8
|
+
from ..frames.right_clicker import RightClicker # type: ignore
|
9
9
|
from .base_window import BaseWindow # type: ignore
|
10
|
-
from ..
|
10
|
+
from ..gui_utils import GUIUtils # type: ignore
|
11
11
|
|
12
12
|
class KakaoGetAuthWindow(BaseWindow):
|
13
13
|
def __init__(self, *args, **kwargs):
|
@@ -5,9 +5,9 @@ from threading import Thread
|
|
5
5
|
|
6
6
|
from ttkbootstrap import Frame, Button, Label # type: ignore
|
7
7
|
|
8
|
-
from
|
8
|
+
from ...utils.auth.get_line_auth import GetLineAuth # type: ignore
|
9
9
|
from .base_window import BaseWindow # type: ignore
|
10
|
-
from ..
|
10
|
+
from ..gui_utils import GUIUtils # type: ignore
|
11
11
|
|
12
12
|
class LineGetAuthWindow(BaseWindow):
|
13
13
|
def __init__(self, *args, **kwargs):
|
@@ -4,9 +4,9 @@ from threading import Thread
|
|
4
4
|
|
5
5
|
from ttkbootstrap import Toplevel, Frame, Button, Label # type: ignore
|
6
6
|
|
7
|
-
from
|
7
|
+
from ...utils.auth.get_signal_auth import GetSignalAuth # type: ignore
|
8
8
|
from .base_window import BaseWindow # type: ignore
|
9
|
-
from ..
|
9
|
+
from ..gui_utils import GUIUtils # type: ignore
|
10
10
|
|
11
11
|
class SignalGetAuthWindow(BaseWindow):
|
12
12
|
def __init__(self, *args, **kwargs):
|
@@ -1,12 +1,17 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
|
+
from __future__ import annotations
|
3
|
+
|
2
4
|
import os
|
3
5
|
import shutil
|
4
6
|
from datetime import datetime
|
5
7
|
from multiprocessing import Process, Queue, Value
|
8
|
+
from multiprocessing.queues import Queue as QueueType
|
6
9
|
from threading import Thread
|
7
10
|
from urllib.parse import urlparse
|
8
11
|
from typing import Optional
|
9
12
|
|
13
|
+
from .job_option import InputOption, CompOption, OutputOption, CredOption # type: ignore
|
14
|
+
|
10
15
|
from .downloaders.download_line import DownloadLine # type: ignore
|
11
16
|
from .downloaders.download_signal import DownloadSignal # type: ignore
|
12
17
|
from .downloaders.download_telegram import DownloadTelegram # type: ignore
|
@@ -18,22 +23,21 @@ from .uploaders.upload_telegram import UploadTelegram # type: ignore
|
|
18
23
|
from .uploaders.compress_wastickers import CompressWastickers # type: ignore
|
19
24
|
from .uploaders.xcode_imessage import XcodeImessage # type: ignore
|
20
25
|
|
21
|
-
from .
|
22
|
-
from .utils.codec_info import CodecInfo # type: ignore
|
23
|
-
from .utils.json_manager import JsonManager # type: ignore
|
24
|
-
from .utils.metadata_handler import MetadataHandler # type: ignore
|
26
|
+
from .converter import StickerConvert # type: ignore
|
27
|
+
from .utils.media.codec_info import CodecInfo # type: ignore
|
28
|
+
from .utils.files.json_manager import JsonManager # type: ignore
|
29
|
+
from .utils.files.metadata_handler import MetadataHandler # type: ignore
|
25
30
|
|
26
|
-
class
|
31
|
+
class Job:
|
27
32
|
def __init__(self,
|
28
|
-
opt_input:
|
29
|
-
|
33
|
+
opt_input: InputOption, opt_comp: CompOption,
|
34
|
+
opt_output: OutputOption, opt_cred: CredOption,
|
35
|
+
cb_msg, cb_msg_block, cb_bar, cb_ask_bool):
|
30
36
|
|
31
37
|
self.opt_input = opt_input
|
32
38
|
self.opt_comp = opt_comp
|
33
39
|
self.opt_output = opt_output
|
34
40
|
self.opt_cred = opt_cred
|
35
|
-
self.input_presets = input_presets
|
36
|
-
self.output_presets = output_presets
|
37
41
|
self.cb_msg = cb_msg
|
38
42
|
self.cb_msg_block = cb_msg_block
|
39
43
|
self.cb_bar = cb_bar
|
@@ -42,25 +46,24 @@ class Flow:
|
|
42
46
|
self.compress_fails: list[str] = []
|
43
47
|
self.out_urls: list[str] = []
|
44
48
|
|
45
|
-
self.jobs_queue:
|
46
|
-
self.results_queue:
|
47
|
-
self.cb_msg_queue:
|
49
|
+
self.jobs_queue: QueueType[Optional[tuple[str, str, CompOption]]] = Queue()
|
50
|
+
self.results_queue: QueueType[Optional[tuple[bool, str, str, int]]] = Queue()
|
51
|
+
self.cb_msg_queue: QueueType[Optional[str]] = Queue()
|
48
52
|
self.processes: list[Process] = []
|
49
53
|
|
50
54
|
self.is_cancel_job = Value('i', 0)
|
51
55
|
|
52
|
-
if os.path.isdir(self.opt_input
|
53
|
-
os.makedirs(self.opt_input
|
56
|
+
if os.path.isdir(self.opt_input.dir) == False:
|
57
|
+
os.makedirs(self.opt_input.dir)
|
54
58
|
|
55
|
-
if os.path.isdir(self.opt_output
|
56
|
-
os.makedirs(self.opt_output
|
59
|
+
if os.path.isdir(self.opt_output.dir) == False:
|
60
|
+
os.makedirs(self.opt_output.dir)
|
57
61
|
|
58
62
|
def start(self) -> bool:
|
59
63
|
self.cb_bar(set_progress_mode='indeterminate')
|
60
64
|
self.cb_msg(cls=True)
|
61
65
|
|
62
66
|
tasks = (
|
63
|
-
self.sanitize,
|
64
67
|
self.verify_input,
|
65
68
|
self.cleanup,
|
66
69
|
self.download,
|
@@ -77,33 +80,6 @@ class Flow:
|
|
77
80
|
return 1
|
78
81
|
|
79
82
|
return 0
|
80
|
-
|
81
|
-
def sanitize(self) -> bool:
|
82
|
-
def to_int(i):
|
83
|
-
return i if i != None else None
|
84
|
-
|
85
|
-
try:
|
86
|
-
self.opt_comp['size_max']['img'] = to_int(self.opt_comp['size_max']['img'])
|
87
|
-
self.opt_comp['size_max']['vid'] = to_int(self.opt_comp['size_max']['vid'])
|
88
|
-
self.opt_comp['fps']['min'] = to_int(self.opt_comp['fps']['min'])
|
89
|
-
self.opt_comp['fps']['max'] = to_int(self.opt_comp['fps']['max'])
|
90
|
-
self.opt_comp['res']['w']['min'] = to_int(self.opt_comp['res']['w']['min'])
|
91
|
-
self.opt_comp['res']['w']['max'] = to_int(self.opt_comp['res']['w']['max'])
|
92
|
-
self.opt_comp['res']['h']['min'] = to_int(self.opt_comp['res']['h']['min'])
|
93
|
-
self.opt_comp['res']['h']['max'] = to_int(self.opt_comp['res']['h']['max'])
|
94
|
-
self.opt_comp['quality']['min'] = to_int(self.opt_comp['quality']['min'])
|
95
|
-
self.opt_comp['quality']['max'] = to_int(self.opt_comp['quality']['max'])
|
96
|
-
self.opt_comp['color']['min'] = to_int(self.opt_comp['color']['min'])
|
97
|
-
self.opt_comp['color']['max'] = to_int(self.opt_comp['color']['max'])
|
98
|
-
self.opt_comp['duration']['min'] = to_int(self.opt_comp['duration']['min'])
|
99
|
-
self.opt_comp['duration']['max'] = to_int(self.opt_comp['duration']['max'])
|
100
|
-
self.opt_comp['steps'] = to_int(self.opt_comp['steps'])
|
101
|
-
self.opt_comp['processes'] = to_int(self.opt_comp['processes'])
|
102
|
-
except ValueError:
|
103
|
-
self.cb_msg('Non-numbers found in field(s). Check your input and try again.')
|
104
|
-
return False
|
105
|
-
|
106
|
-
return True
|
107
83
|
|
108
84
|
def verify_input(self) -> bool:
|
109
85
|
info_msg = ''
|
@@ -113,12 +89,12 @@ class Flow:
|
|
113
89
|
save_to_local_tip += ' If you want to upload the results by yourself,\n'
|
114
90
|
save_to_local_tip += ' select "Save to local directory only" for output\n'
|
115
91
|
|
116
|
-
if self.opt_input
|
92
|
+
if self.opt_input.option == 'auto':
|
117
93
|
error_msg += '\n'
|
118
94
|
error_msg += '[X] Unrecognized URL input source\n'
|
119
95
|
|
120
|
-
if (self.opt_input
|
121
|
-
not self.opt_input.
|
96
|
+
if (self.opt_input.option != 'local' and
|
97
|
+
not self.opt_input.url):
|
122
98
|
|
123
99
|
error_msg += '\n'
|
124
100
|
error_msg += '[X] URL address cannot be empty.\n'
|
@@ -127,37 +103,37 @@ class Flow:
|
|
127
103
|
error_msg += ' in "Input source"\n'
|
128
104
|
|
129
105
|
|
130
|
-
if ((self.opt_input.
|
131
|
-
self.opt_output.
|
132
|
-
not self.opt_cred.
|
106
|
+
if ((self.opt_input.option == 'telegram' or
|
107
|
+
self.opt_output.option == 'telegram') and
|
108
|
+
not self.opt_cred.telegram_token):
|
133
109
|
|
134
110
|
error_msg += '[X] Downloading from and uploading to telegram requires bot token.\n'
|
135
111
|
error_msg += save_to_local_tip
|
136
112
|
|
137
|
-
if (self.opt_output.
|
138
|
-
not self.opt_cred.
|
113
|
+
if (self.opt_output.option == 'telegram' and
|
114
|
+
not self.opt_cred.telegram_userid):
|
139
115
|
|
140
116
|
error_msg += '[X] Uploading to telegram requires user_id \n'
|
141
117
|
error_msg += ' (From real account, not bot account).\n'
|
142
118
|
error_msg += save_to_local_tip
|
143
119
|
|
144
120
|
|
145
|
-
if (self.opt_output.
|
146
|
-
not (self.opt_cred.
|
121
|
+
if (self.opt_output.option == 'signal' and
|
122
|
+
not (self.opt_cred.signal_uuid and self.opt_cred.signal_password)):
|
147
123
|
|
148
124
|
error_msg += '[X] Uploading to signal requires uuid and password.\n'
|
149
125
|
error_msg += save_to_local_tip
|
150
126
|
|
151
127
|
output_presets = JsonManager.load_json('resources/output.json')
|
152
128
|
|
153
|
-
input_option = self.opt_input.
|
154
|
-
output_option = self.opt_output.
|
129
|
+
input_option = self.opt_input.option
|
130
|
+
output_option = self.opt_output.option
|
155
131
|
|
156
132
|
for metadata in ('title', 'author'):
|
157
|
-
if MetadataHandler.check_metadata_required(output_option, metadata) and not self.opt_output
|
158
|
-
if not MetadataHandler.check_metadata_provided(self.opt_input
|
133
|
+
if MetadataHandler.check_metadata_required(output_option, metadata) and not getattr(self.opt_output, metadata):
|
134
|
+
if not MetadataHandler.check_metadata_provided(self.opt_input.dir, input_option, metadata):
|
159
135
|
error_msg += f'[X] {output_presets[output_option]["full_name"]} requires {metadata}\n'
|
160
|
-
if self.opt_input.
|
136
|
+
if self.opt_input.option == 'local':
|
161
137
|
error_msg += f' {metadata} was not supplied and {metadata}.txt is absent\n'
|
162
138
|
else:
|
163
139
|
error_msg += f' {metadata} was not supplied and input source will not provide {metadata}\n'
|
@@ -165,7 +141,7 @@ class Flow:
|
|
165
141
|
error_msg += f' Create {metadata}.txt with the {metadata} name\n'
|
166
142
|
else:
|
167
143
|
info_msg += f'[!] {output_presets[output_option]["full_name"]} requires {metadata}\n'
|
168
|
-
if self.opt_input.
|
144
|
+
if self.opt_input.option == 'local':
|
169
145
|
info_msg += f' {metadata} was not supplied but {metadata}.txt is present\n'
|
170
146
|
info_msg += f' Using {metadata} name in {metadata}.txt\n'
|
171
147
|
else:
|
@@ -183,10 +159,10 @@ class Flow:
|
|
183
159
|
# Only warn if the compression option is available in export preset
|
184
160
|
# Only warn if export option is not local or custom
|
185
161
|
# Do not warn if no_compress is true
|
186
|
-
if (not self.opt_comp
|
187
|
-
self.opt_output
|
188
|
-
self.opt_comp
|
189
|
-
self.opt_output
|
162
|
+
if (not self.opt_comp.no_compress and
|
163
|
+
self.opt_output.option != 'local' and
|
164
|
+
self.opt_comp.preset != 'custom' and
|
165
|
+
self.opt_output.option not in self.opt_comp.preset):
|
190
166
|
|
191
167
|
msg = 'Compression preset does not match export option\n'
|
192
168
|
msg += 'You may continue, but the files will need to be compressed again before export\n'
|
@@ -198,9 +174,9 @@ class Flow:
|
|
198
174
|
return False
|
199
175
|
|
200
176
|
# Warn about unable to download animated Kakao stickers with such link
|
201
|
-
if (self.opt_output.
|
202
|
-
urlparse(self.opt_input.
|
203
|
-
not self.opt_cred.
|
177
|
+
if (self.opt_output.option == 'kakao' and
|
178
|
+
urlparse(self.opt_input.url).netloc == 'e.kakao.com' and
|
179
|
+
not self.opt_cred.kakao_auth_token):
|
204
180
|
|
205
181
|
msg = 'To download ANIMATED stickers from e.kakao.com,\n'
|
206
182
|
msg += 'you need to generate auth_token.\n'
|
@@ -229,32 +205,32 @@ class Flow:
|
|
229
205
|
timestamp = datetime.now().strftime('%Y-%d-%m_%H-%M-%S')
|
230
206
|
dir_name = 'archive_' + timestamp
|
231
207
|
|
232
|
-
in_dir_files = [i for i in os.listdir(self.opt_input
|
233
|
-
out_dir_files = [i for i in os.listdir(self.opt_output
|
208
|
+
in_dir_files = [i for i in os.listdir(self.opt_input.dir) if not i.startswith('archive_')]
|
209
|
+
out_dir_files = [i for i in os.listdir(self.opt_output.dir) if not i.startswith('archive_')]
|
234
210
|
|
235
|
-
if self.opt_input
|
211
|
+
if self.opt_input.option == 'local':
|
236
212
|
self.cb_msg('Skip moving old files in input directory as input source is local')
|
237
213
|
elif len(in_dir_files) == 0:
|
238
214
|
self.cb_msg('Skip moving old files in input directory as input source is empty')
|
239
215
|
else:
|
240
|
-
archive_dir = os.path.join(self.opt_input
|
216
|
+
archive_dir = os.path.join(self.opt_input.dir, dir_name)
|
241
217
|
self.cb_msg(f"Moving old files in input directory to {archive_dir} as input source is not local")
|
242
218
|
os.makedirs(archive_dir)
|
243
219
|
for i in in_dir_files:
|
244
|
-
old_path = os.path.join(self.opt_input
|
220
|
+
old_path = os.path.join(self.opt_input.dir, i)
|
245
221
|
new_path = os.path.join(archive_dir, i)
|
246
222
|
shutil.move(old_path, new_path)
|
247
223
|
|
248
|
-
if self.opt_comp
|
224
|
+
if self.opt_comp.no_compress:
|
249
225
|
self.cb_msg('Skip moving old files in output directory as no_compress is True')
|
250
226
|
elif len(out_dir_files) == 0:
|
251
227
|
self.cb_msg('Skip moving old files in output directory as output source is empty')
|
252
228
|
else:
|
253
|
-
archive_dir = os.path.join(self.opt_output
|
229
|
+
archive_dir = os.path.join(self.opt_output.dir, dir_name)
|
254
230
|
self.cb_msg(f"Moving old files in output directory to {archive_dir}")
|
255
231
|
os.makedirs(archive_dir)
|
256
232
|
for i in out_dir_files:
|
257
|
-
old_path = os.path.join(self.opt_output
|
233
|
+
old_path = os.path.join(self.opt_output.dir, i)
|
258
234
|
new_path = os.path.join(archive_dir, i)
|
259
235
|
shutil.move(old_path, new_path)
|
260
236
|
|
@@ -263,16 +239,16 @@ class Flow:
|
|
263
239
|
def download(self) -> bool:
|
264
240
|
downloaders = []
|
265
241
|
|
266
|
-
if self.opt_input
|
242
|
+
if self.opt_input.option == 'signal':
|
267
243
|
downloaders.append(DownloadSignal.start)
|
268
244
|
|
269
|
-
if self.opt_input
|
245
|
+
if self.opt_input.option == 'line':
|
270
246
|
downloaders.append(DownloadLine.start)
|
271
247
|
|
272
|
-
if self.opt_input
|
248
|
+
if self.opt_input.option == 'telegram':
|
273
249
|
downloaders.append(DownloadTelegram.start)
|
274
250
|
|
275
|
-
if self.opt_input
|
251
|
+
if self.opt_input.option == 'kakao':
|
276
252
|
downloaders.append(DownloadKakao.start)
|
277
253
|
|
278
254
|
if len(downloaders) > 0:
|
@@ -283,8 +259,8 @@ class Flow:
|
|
283
259
|
|
284
260
|
for downloader in downloaders:
|
285
261
|
success = downloader(
|
286
|
-
url=self.opt_input
|
287
|
-
out_dir=self.opt_input
|
262
|
+
url=self.opt_input.url,
|
263
|
+
out_dir=self.opt_input.dir,
|
288
264
|
opt_cred=self.opt_cred,
|
289
265
|
cb_msg=self.cb_msg, cb_msg_block=self.cb_msg_block, cb_bar=self.cb_bar)
|
290
266
|
self.cb_bar(set_progress_mode='indeterminate')
|
@@ -294,10 +270,10 @@ class Flow:
|
|
294
270
|
return True
|
295
271
|
|
296
272
|
def compress(self) -> bool:
|
297
|
-
if self.opt_comp
|
273
|
+
if self.opt_comp.no_compress == True:
|
298
274
|
self.cb_msg('no_compress is set to True, skip compression')
|
299
|
-
in_dir_files = [i for i in sorted(os.listdir(self.opt_input
|
300
|
-
out_dir_files = [i for i in sorted(os.listdir(self.opt_output
|
275
|
+
in_dir_files = [i for i in sorted(os.listdir(self.opt_input.dir)) if os.path.isfile(os.path.join(self.opt_input.dir, i))]
|
276
|
+
out_dir_files = [i for i in sorted(os.listdir(self.opt_output.dir)) if os.path.isfile(os.path.join(self.opt_output.dir, i))]
|
301
277
|
if len(in_dir_files) == 0:
|
302
278
|
self.cb_msg('Input directory is empty, nothing to copy to output directory')
|
303
279
|
elif len(out_dir_files) != 0:
|
@@ -305,14 +281,14 @@ class Flow:
|
|
305
281
|
else:
|
306
282
|
self.cb_msg('Output directory is empty, copying files from input directory')
|
307
283
|
for i in in_dir_files:
|
308
|
-
src_f = os.path.join(self.opt_input
|
309
|
-
dst_f = os.path.join(self.opt_output
|
284
|
+
src_f = os.path.join(self.opt_input.dir, i)
|
285
|
+
dst_f = os.path.join(self.opt_output.dir, i)
|
310
286
|
shutil.copy(src_f, dst_f)
|
311
287
|
return True
|
312
288
|
msg = 'Compressing...'
|
313
289
|
|
314
|
-
input_dir = self.opt_input
|
315
|
-
output_dir = self.opt_output
|
290
|
+
input_dir = self.opt_input.dir
|
291
|
+
output_dir = self.opt_output.dir
|
316
292
|
|
317
293
|
in_fs = []
|
318
294
|
|
@@ -338,9 +314,9 @@ class Flow:
|
|
338
314
|
Thread(target=self.cb_msg_thread, args=(self.cb_msg_queue,)).start()
|
339
315
|
Thread(target=self.processes_watcher_thread, args=(self.results_queue,)).start()
|
340
316
|
|
341
|
-
for i in range(min(self.opt_comp
|
317
|
+
for i in range(min(self.opt_comp.processes, in_fs_count)):
|
342
318
|
process = Process(
|
343
|
-
target=
|
319
|
+
target=Job.compress_worker,
|
344
320
|
args=(self.jobs_queue, self.results_queue, self.cb_msg_queue),
|
345
321
|
daemon=True
|
346
322
|
)
|
@@ -351,10 +327,10 @@ class Flow:
|
|
351
327
|
for i in in_fs:
|
352
328
|
in_f = os.path.join(input_dir, i)
|
353
329
|
|
354
|
-
if CodecInfo.is_anim(in_f) or self.opt_comp
|
355
|
-
extension = self.opt_comp
|
330
|
+
if CodecInfo.is_anim(in_f) or self.opt_comp.fake_vid:
|
331
|
+
extension = self.opt_comp.format_vid
|
356
332
|
else:
|
357
|
-
extension = self.opt_comp
|
333
|
+
extension = self.opt_comp.format_img
|
358
334
|
|
359
335
|
out_f = os.path.join(output_dir, os.path.splitext(i)[0] + extension)
|
360
336
|
|
@@ -370,19 +346,32 @@ class Flow:
|
|
370
346
|
|
371
347
|
return True
|
372
348
|
|
373
|
-
def processes_watcher_thread(
|
349
|
+
def processes_watcher_thread(
|
350
|
+
self,
|
351
|
+
results_queue: QueueType[Optional[tuple[bool, str, str, int]]]
|
352
|
+
):
|
353
|
+
|
374
354
|
for (success, in_f, out_f, size) in iter(results_queue.get, None): # type: ignore[misc]
|
375
355
|
if success == False: # type: ignore
|
376
356
|
self.compress_fails.append(in_f) # type: ignore[has-type]
|
377
357
|
|
378
358
|
self.cb_bar(update_bar=True)
|
379
359
|
|
380
|
-
def cb_msg_thread(
|
360
|
+
def cb_msg_thread(
|
361
|
+
self,
|
362
|
+
cb_msg_queue: QueueType[Optional[str]]
|
363
|
+
):
|
364
|
+
|
381
365
|
for msg in iter(cb_msg_queue.get, None): # type: ignore
|
382
366
|
self.cb_msg(msg)
|
383
367
|
|
384
368
|
@staticmethod
|
385
|
-
def compress_worker(
|
369
|
+
def compress_worker(
|
370
|
+
jobs_queue: QueueType[Optional[tuple[str, str, CompOption]]],
|
371
|
+
results_queue: QueueType[Optional[tuple[bool, str, str, int]]],
|
372
|
+
cb_msg_queue: QueueType[Optional[str]]
|
373
|
+
):
|
374
|
+
|
386
375
|
for (in_f, out_f, opt_comp) in iter(jobs_queue.get, None): # type: ignore[misc]
|
387
376
|
sticker = StickerConvert(in_f, out_f, opt_comp, cb_msg_queue) # type: ignore
|
388
377
|
success, in_f, out_f, size = sticker.convert()
|
@@ -394,7 +383,7 @@ class Flow:
|
|
394
383
|
def export(self) -> bool:
|
395
384
|
self.cb_bar(set_progress_mode='indeterminate')
|
396
385
|
|
397
|
-
if self.opt_output
|
386
|
+
if self.opt_output.option == 'local':
|
398
387
|
self.cb_msg('Saving to local directory only, nothing to export')
|
399
388
|
return True
|
400
389
|
|
@@ -402,16 +391,16 @@ class Flow:
|
|
402
391
|
|
403
392
|
exporters: list[UploadBase] = []
|
404
393
|
|
405
|
-
if self.opt_output
|
394
|
+
if self.opt_output.option == 'whatsapp':
|
406
395
|
exporters.append(CompressWastickers.start)
|
407
396
|
|
408
|
-
if self.opt_output
|
397
|
+
if self.opt_output.option == 'signal':
|
409
398
|
exporters.append(UploadSignal.start)
|
410
399
|
|
411
|
-
if self.opt_output
|
400
|
+
if self.opt_output.option == 'telegram':
|
412
401
|
exporters.append(UploadTelegram.start)
|
413
402
|
|
414
|
-
if self.opt_output
|
403
|
+
if self.opt_output.option == 'imessage':
|
415
404
|
exporters.append(XcodeImessage.start)
|
416
405
|
|
417
406
|
for exporter in exporters:
|
@@ -420,7 +409,7 @@ class Flow:
|
|
420
409
|
cb_msg=self.cb_msg, cb_msg_block=self.cb_msg_block, cb_ask_bool=self.cb_ask_bool, cb_bar=self.cb_bar)
|
421
410
|
|
422
411
|
if self.out_urls:
|
423
|
-
with open(os.path.join(self.opt_output
|
412
|
+
with open(os.path.join(self.opt_output.dir, 'export-result.txt'), 'w+') as f:
|
424
413
|
f.writelines(self.out_urls)
|
425
414
|
else:
|
426
415
|
self.cb_msg('An error occured while exporting stickers')
|