sticker-convert 2.2.7__py3-none-any.whl → 2.3.1__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/converter.py +60 -13
- sticker_convert/gui.py +15 -6
- sticker_convert/job.py +4 -0
- sticker_convert/utils/media/codec_info.py +27 -2
- {sticker_convert-2.2.7.dist-info → sticker_convert-2.3.1.dist-info}/METADATA +2 -2
- {sticker_convert-2.2.7.dist-info → sticker_convert-2.3.1.dist-info}/RECORD +11 -11
- {sticker_convert-2.2.7.dist-info → sticker_convert-2.3.1.dist-info}/LICENSE +0 -0
- {sticker_convert-2.2.7.dist-info → sticker_convert-2.3.1.dist-info}/WHEEL +0 -0
- {sticker_convert-2.2.7.dist-info → sticker_convert-2.3.1.dist-info}/entry_points.txt +0 -0
- {sticker_convert-2.2.7.dist-info → sticker_convert-2.3.1.dist-info}/top_level.txt +0 -0
sticker_convert/__init__.py
CHANGED
sticker_convert/converter.py
CHANGED
@@ -30,6 +30,14 @@ def get_step_value(
|
|
30
30
|
else:
|
31
31
|
return None
|
32
32
|
|
33
|
+
def useful_array(plane, bytes_per_pixel=1, dtype='uint8'):
|
34
|
+
total_line_size = abs(plane.line_size)
|
35
|
+
useful_line_size = plane.width * bytes_per_pixel
|
36
|
+
arr = np.frombuffer(plane, np.uint8)
|
37
|
+
if total_line_size != useful_line_size:
|
38
|
+
arr = arr.reshape(-1, total_line_size)[:, 0:useful_line_size].reshape(-1)
|
39
|
+
return arr.view(np.dtype(dtype))
|
40
|
+
|
33
41
|
class StickerConvert:
|
34
42
|
MSG_START_COMP = '[I] Start compressing {} -> {}'
|
35
43
|
MSG_SKIP_COMP = '[S] Compatible file found, skip compress and just copy {} -> {}'
|
@@ -225,30 +233,69 @@ class StickerConvert:
|
|
225
233
|
self.frames_raw.append(frame)
|
226
234
|
return
|
227
235
|
|
228
|
-
frame_format = 'rgba'
|
229
236
|
# Crashes when handling some webm in yuv420p and convert to rgba
|
230
237
|
# https://github.com/PyAV-Org/PyAV/issues/1166
|
231
238
|
metadata = iio.immeta(self.in_f, plugin='pyav', exclude_applied=False)
|
232
239
|
context = None
|
233
240
|
if metadata.get('video_format') == 'yuv420p':
|
234
|
-
if metadata.get('alpha_mode') != '1':
|
235
|
-
frame_format = 'rgb24'
|
236
241
|
if metadata.get('codec') == 'vp8':
|
237
|
-
context = CodecContext.create('
|
242
|
+
context = CodecContext.create('libvpx', 'r')
|
238
243
|
elif metadata.get('codec') == 'vp9':
|
239
244
|
context = CodecContext.create('libvpx-vp9', 'r')
|
240
245
|
|
241
246
|
with av.open(self.in_f) as container:
|
242
247
|
if not context:
|
243
248
|
context = container.streams.video[0].codec_context
|
244
|
-
|
249
|
+
|
250
|
+
for packet in container.demux(container.streams.video):
|
245
251
|
for frame in context.decode(packet):
|
246
|
-
frame
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
+
if frame.width % 2 != 0:
|
253
|
+
width = frame.width - 1
|
254
|
+
else:
|
255
|
+
width = frame.width
|
256
|
+
if frame.height % 2 != 0:
|
257
|
+
height = frame.height - 1
|
258
|
+
else:
|
259
|
+
height = frame.height
|
260
|
+
if frame.format.name == 'yuv420p':
|
261
|
+
rgb_array = frame.to_ndarray(format='rgb24')
|
262
|
+
rgba_array = np.dstack(
|
263
|
+
(rgb_array, np.zeros(frame.shape[:2], dtype=np.uint8) + 255)
|
264
|
+
)
|
265
|
+
else:
|
266
|
+
# yuva420p may cause crash
|
267
|
+
# https://github.com/laggykiller/sticker-convert/issues/114
|
268
|
+
frame = frame.reformat(width=width, height=height, format='yuva420p', dst_colorspace=1)
|
269
|
+
|
270
|
+
# https://stackoverflow.com/questions/72308308/converting-yuv-to-rgb-in-python-coefficients-work-with-array-dont-work-with-n
|
271
|
+
y = useful_array(frame.planes[0]).reshape(height, width)
|
272
|
+
u = useful_array(frame.planes[1]).reshape(height // 2, width // 2)
|
273
|
+
v = useful_array(frame.planes[2]).reshape(height // 2, width // 2)
|
274
|
+
a = useful_array(frame.planes[3]).reshape(height, width)
|
275
|
+
|
276
|
+
u = u.repeat(2, axis=0).repeat(2, axis=1)
|
277
|
+
v = v.repeat(2, axis=0).repeat(2, axis=1)
|
278
|
+
|
279
|
+
y = y.reshape((y.shape[0], y.shape[1], 1))
|
280
|
+
u = u.reshape((u.shape[0], u.shape[1], 1))
|
281
|
+
v = v.reshape((v.shape[0], v.shape[1], 1))
|
282
|
+
a = a.reshape((a.shape[0], a.shape[1], 1))
|
283
|
+
|
284
|
+
yuv_array = np.concatenate((y, u, v), axis=2)
|
285
|
+
|
286
|
+
yuv_array = yuv_array.astype(np.float32)
|
287
|
+
yuv_array[:, :, 0] = yuv_array[:, :, 0].clip(16, 235).astype(yuv_array.dtype) - 16
|
288
|
+
yuv_array[:, :, 1:] = yuv_array[:, :, 1:].clip(16, 240).astype(yuv_array.dtype) - 128
|
289
|
+
|
290
|
+
convert = np.array([
|
291
|
+
[1.164, 0.000, 1.793],
|
292
|
+
[1.164, -0.213, -0.533],
|
293
|
+
[1.164, 2.112, 0.000]
|
294
|
+
])
|
295
|
+
rgb_array = np.matmul(yuv_array, convert.T).clip(0,255).astype('uint8')
|
296
|
+
rgba_array = np.concatenate((rgb_array, a), axis=2)
|
297
|
+
|
298
|
+
self.frames_raw.append(rgba_array)
|
252
299
|
|
253
300
|
def frames_import_lottie(self):
|
254
301
|
if self.in_f_ext == '.tgs':
|
@@ -313,11 +360,11 @@ class StickerConvert:
|
|
313
360
|
# fps_ratio: 1 frame in new anim equal to how many frame in old anim
|
314
361
|
# speed_ratio: How much to speed up / slow down
|
315
362
|
fps_ratio = self.fps_orig / self.fps
|
316
|
-
if (self.opt_comp.duration_min and
|
363
|
+
if (self.opt_comp.duration_min != None and
|
317
364
|
self.duration_orig < self.opt_comp.duration_min):
|
318
365
|
|
319
366
|
speed_ratio = self.duration_orig / self.opt_comp.duration_min
|
320
|
-
elif (self.opt_comp.duration_max and
|
367
|
+
elif (self.opt_comp.duration_max != None and
|
321
368
|
self.duration_orig > self.opt_comp.duration_max):
|
322
369
|
|
323
370
|
speed_ratio = self.duration_orig / self.opt_comp.duration_max
|
sticker_convert/gui.py
CHANGED
@@ -543,11 +543,18 @@ class GUI(Window):
|
|
543
543
|
# output_option_display = self.get_output_display_name()
|
544
544
|
url = self.input_address_var.get()
|
545
545
|
|
546
|
-
|
547
|
-
|
548
|
-
self.input_frame.input_setdir_entry.config(bootstyle='default')
|
546
|
+
if os.path.abspath(self.input_setdir_var.get()) == os.path.abspath(self.output_setdir_var.get()):
|
547
|
+
in_out_dir_same = True
|
549
548
|
else:
|
549
|
+
in_out_dir_same = False
|
550
|
+
|
551
|
+
# Input
|
552
|
+
if in_out_dir_same == True:
|
553
|
+
self.input_frame.input_setdir_entry.config(bootstyle='danger')
|
554
|
+
elif not os.path.isdir(self.input_setdir_var.get()):
|
550
555
|
self.input_frame.input_setdir_entry.config(bootstyle='warning')
|
556
|
+
else:
|
557
|
+
self.input_frame.input_setdir_entry.config(bootstyle='default')
|
551
558
|
|
552
559
|
self.input_frame.address_lbl.config(text=self.input_presets[input_option_display]['address_lbls'])
|
553
560
|
self.input_frame.address_entry.config(bootstyle='default')
|
@@ -574,10 +581,12 @@ class GUI(Window):
|
|
574
581
|
self.input_frame.address_tip.config(text=f'Detected URL: {download_option}')
|
575
582
|
|
576
583
|
# Output
|
577
|
-
if
|
578
|
-
self.output_frame.output_setdir_entry.config(bootstyle='
|
579
|
-
|
584
|
+
if in_out_dir_same == True:
|
585
|
+
self.output_frame.output_setdir_entry.config(bootstyle='danger')
|
586
|
+
elif not os.path.isdir(self.output_setdir_var.get()):
|
580
587
|
self.output_frame.output_setdir_entry.config(bootstyle='warning')
|
588
|
+
else:
|
589
|
+
self.output_frame.output_setdir_entry.config(bootstyle='default')
|
581
590
|
|
582
591
|
if (MetadataHandler.check_metadata_required(output_option, 'title') and
|
583
592
|
not MetadataHandler.check_metadata_provided(self.input_setdir_var.get(), input_option, 'title') and
|
sticker_convert/job.py
CHANGED
@@ -89,6 +89,10 @@ class Job:
|
|
89
89
|
save_to_local_tip += ' If you want to upload the results by yourself,\n'
|
90
90
|
save_to_local_tip += ' select "Save to local directory only" for output\n'
|
91
91
|
|
92
|
+
if os.path.abspath(self.opt_input.dir) == os.path.abspath(self.opt_output.dir):
|
93
|
+
error_msg += '\n'
|
94
|
+
error_msg += '[X] Input and output directories cannot be the same\n'
|
95
|
+
|
92
96
|
if self.opt_input.option == 'auto':
|
93
97
|
error_msg += '\n'
|
94
98
|
error_msg += '[X] Unrecognized URL input source\n'
|
@@ -5,6 +5,8 @@ import mimetypes
|
|
5
5
|
from typing import Optional
|
6
6
|
|
7
7
|
import imageio.v3 as iio
|
8
|
+
import av # type: ignore
|
9
|
+
from av.codec.context import CodecContext # type: ignore
|
8
10
|
from rlottie_python import LottieAnimation # type: ignore
|
9
11
|
from PIL import Image, UnidentifiedImageError
|
10
12
|
import mmap
|
@@ -66,8 +68,31 @@ class CodecInfo:
|
|
66
68
|
else:
|
67
69
|
fps = frames / total_duration * 1000
|
68
70
|
else:
|
69
|
-
metadata
|
70
|
-
|
71
|
+
# Getting fps from metadata is not reliable
|
72
|
+
# Example: https://github.com/laggykiller/sticker-convert/issues/114
|
73
|
+
metadata = iio.immeta(file, plugin='pyav', exclude_applied=False)
|
74
|
+
context = None
|
75
|
+
if metadata.get('video_format') == 'yuv420p':
|
76
|
+
if metadata.get('codec') == 'vp8':
|
77
|
+
context = CodecContext.create('libvpx', 'r')
|
78
|
+
elif metadata.get('codec') == 'vp9':
|
79
|
+
context = CodecContext.create('libvpx-vp9', 'r')
|
80
|
+
|
81
|
+
with av.open(file) as container:
|
82
|
+
stream = container.streams.video[0]
|
83
|
+
if not context:
|
84
|
+
context = stream.codec_context
|
85
|
+
|
86
|
+
last_frame = None
|
87
|
+
for frame_count, frame in enumerate(container.decode(stream)):
|
88
|
+
last_frame = frame
|
89
|
+
if frame_count > 10:
|
90
|
+
break
|
91
|
+
|
92
|
+
if frame_count <= 1:
|
93
|
+
fps = 1
|
94
|
+
else:
|
95
|
+
fps = frame_count / (last_frame.pts * last_frame.time_base.numerator / last_frame.time_base.denominator)
|
71
96
|
|
72
97
|
return fps
|
73
98
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: sticker-convert
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.3.1
|
4
4
|
Summary: Convert (animated) stickers to/from WhatsApp, Telegram, Signal, Line, Kakao, iMessage. Written in Python.
|
5
5
|
Author-email: laggykiller <chaudominic2@gmail.com>
|
6
6
|
Maintainer-email: laggykiller <chaudominic2@gmail.com>
|
@@ -376,7 +376,7 @@ Requires-Dist: pyoxipng ~=9.0.0
|
|
376
376
|
Requires-Dist: python-telegram-bot ~=20.5
|
377
377
|
Requires-Dist: requests ~=2.31.0
|
378
378
|
Requires-Dist: rlottie-python ~=1.2.1
|
379
|
-
Requires-Dist: selenium ~=4.
|
379
|
+
Requires-Dist: selenium ~=4.17.2
|
380
380
|
Requires-Dist: signalstickers-client ~=3.3.0
|
381
381
|
Requires-Dist: tqdm ~=4.66.1
|
382
382
|
Requires-Dist: ttkbootstrap-fork-laggykiller ~=1.5.1
|
@@ -1,9 +1,9 @@
|
|
1
|
-
sticker_convert/__init__.py,sha256=
|
1
|
+
sticker_convert/__init__.py,sha256=fl39fVB4JgtiBbT7-wCNoXpljUvI7OAZBy3q531bmn4,66
|
2
2
|
sticker_convert/__main__.py,sha256=HHzFzRsxxhxkCL15_sE1rBAdtprT0NfVUNR591BMvl4,628
|
3
3
|
sticker_convert/cli.py,sha256=mDU18LdLD2yr1jvzuE8R9IpESd9ZiMTU1_DlcnY6WWw,16631
|
4
|
-
sticker_convert/converter.py,sha256=
|
5
|
-
sticker_convert/gui.py,sha256=
|
6
|
-
sticker_convert/job.py,sha256=
|
4
|
+
sticker_convert/converter.py,sha256=nK_gcUmxR4n61BUOlEDPDXNkNF8kEJaar5NIzPYflWQ,19905
|
5
|
+
sticker_convert/gui.py,sha256=vR1tkqMSkxkMt-GD-V5i8DYSQ-HUjkwgKl1y0iveE9g,28019
|
6
|
+
sticker_convert/job.py,sha256=gwqHy7k6fmhimmYnXYWCNg3IJJpvPcMUteu7otljeiY,17640
|
7
7
|
sticker_convert/job_option.py,sha256=MdBhyp4-cIHxno4SwBX2n45lkq3dK8Epy84nxvrT_PQ,10926
|
8
8
|
sticker_convert/downloaders/download_base.py,sha256=TYC6bod0IHUApPBLltsOg9zUOMJRScUp7RMwUG8nF78,2572
|
9
9
|
sticker_convert/downloaders/download_kakao.py,sha256=6NKUT5iHyvVX7og8kSyk80TXUM0upUQMnnDbPFCK0NQ,8034
|
@@ -81,12 +81,12 @@ sticker_convert/utils/files/json_manager.py,sha256=BBndjSY5kvjxurOpnDI2DRsKoLrNB
|
|
81
81
|
sticker_convert/utils/files/metadata_handler.py,sha256=KIy8j89DMBW7-gpL9jqhzz96GgBvPHsRYegHXemk_mY,8346
|
82
82
|
sticker_convert/utils/files/run_bin.py,sha256=4c8y5c_2wUfm0hbJSOGFcezpW5BVqM5_e9aliPbxvwo,1632
|
83
83
|
sticker_convert/utils/media/apple_png_normalize.py,sha256=YGrZ8pZvgfhw8dW-0gf7XcXk_R82lJTX-oTo6SsB7uY,3719
|
84
|
-
sticker_convert/utils/media/codec_info.py,sha256=
|
84
|
+
sticker_convert/utils/media/codec_info.py,sha256=vla8jbnx0w1Gp4UWj1n95qLVct8l0Th70mAEMFr0LQM,6061
|
85
85
|
sticker_convert/utils/media/decrypt_kakao.py,sha256=gMz64vIGEIPAl4FFWuUbPMHE7vExr46ZFpzMoukvwws,1918
|
86
86
|
sticker_convert/utils/media/format_verify.py,sha256=7WvvDSFDkyG1WlkXKqVbmPjKiyEedtqivZcCqSNziD8,6179
|
87
|
-
sticker_convert-2.
|
88
|
-
sticker_convert-2.
|
89
|
-
sticker_convert-2.
|
90
|
-
sticker_convert-2.
|
91
|
-
sticker_convert-2.
|
92
|
-
sticker_convert-2.
|
87
|
+
sticker_convert-2.3.1.dist-info/LICENSE,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
|
88
|
+
sticker_convert-2.3.1.dist-info/METADATA,sha256=tNM3JESS5RftQwEVQ4HJ0cCmwu_WBzyy2svIXDC5CEA,45184
|
89
|
+
sticker_convert-2.3.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
90
|
+
sticker_convert-2.3.1.dist-info/entry_points.txt,sha256=MNJ7XyC--ugxi5jS1nzjDLGnxCyLuaGdsVLnJhDHCqs,66
|
91
|
+
sticker_convert-2.3.1.dist-info/top_level.txt,sha256=r9vfnB0l1ZnH5pTH5RvkobnK3Ow9m0RsncaOMAtiAtk,16
|
92
|
+
sticker_convert-2.3.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|