sticker-convert 2.7.12__py3-none-any.whl → 2.8.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.
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env python3
2
2
  import os
3
- from decimal import ROUND_HALF_UP, Decimal
4
3
  from fractions import Fraction
5
4
  from io import BytesIO
6
5
  from math import ceil, floor
@@ -15,12 +14,12 @@ from PIL import __version__ as PillowVersion
15
14
  from sticker_convert.job_option import CompOption
16
15
  from sticker_convert.utils.callback import Callback, CallbackReturn, CbQueueItemType
17
16
  from sticker_convert.utils.files.cache_store import CacheStore
18
- from sticker_convert.utils.media.codec_info import CodecInfo
17
+ from sticker_convert.utils.media.codec_info import CodecInfo, rounding
19
18
  from sticker_convert.utils.media.format_verify import FormatVerify
20
19
 
21
20
  if TYPE_CHECKING:
22
- from av.video.frame import VideoFrame # type: ignore
23
- from av.video.plane import VideoPlane # type: ignore
21
+ from av.video.frame import VideoFrame
22
+ from av.video.plane import VideoPlane
24
23
 
25
24
  MSG_START_COMP = "[I] Start compressing {} -> {}"
26
25
  MSG_SKIP_COMP = "[S] Compatible file found, skip compress and just copy {} -> {}"
@@ -44,10 +43,6 @@ YUV_RGB_MATRIX = np.array(
44
43
  )
45
44
 
46
45
 
47
- def rounding(value: float) -> Decimal:
48
- return Decimal(value).quantize(0, ROUND_HALF_UP)
49
-
50
-
51
46
  def get_step_value(
52
47
  max_step: Optional[int],
53
48
  min_step: Optional[int],
@@ -264,14 +259,21 @@ class StickerConvert:
264
259
  self.result_size = self.size
265
260
  self.result_step = step_current
266
261
 
267
- if step_upper - step_lower > 1 and self.size_max:
262
+ if (
263
+ step_upper - step_lower > 0
264
+ and step_current != step_lower
265
+ and self.size_max
266
+ ):
268
267
  if self.size <= self.size_max:
269
268
  sign = "<"
270
269
  step_upper = step_current
271
270
  else:
272
271
  sign = ">"
273
272
  step_lower = step_current
274
- step_current = int(rounding((step_lower + step_upper) / 2))
273
+ if step_current == step_lower + 1:
274
+ step_current = step_lower
275
+ else:
276
+ step_current = int(rounding((step_lower + step_upper) / 2))
275
277
  self.recompress(sign)
276
278
  elif self.result:
277
279
  return self.compress_done(self.result, self.result_step)
@@ -409,7 +411,7 @@ class StickerConvert:
409
411
 
410
412
  if suffix in (".tgs", ".lottie", ".json"):
411
413
  self._frames_import_lottie()
412
- elif suffix in (".webp", ".apng", "png"):
414
+ elif suffix in (".webp", ".apng", ".png", ".gif"):
413
415
  # ffmpeg do not support webp decoding (yet)
414
416
  # ffmpeg could fail to decode apng if file is buggy
415
417
  self._frames_import_pillow()
@@ -419,10 +421,24 @@ class StickerConvert:
419
421
  def _frames_import_pillow(self) -> None:
420
422
  with Image.open(self.in_f) as im:
421
423
  # Note: im.convert("RGBA") would return rgba image of current frame only
422
- if "n_frames" in dir(im):
423
- for i in range(im.n_frames):
424
- im.seek(i)
424
+ if (
425
+ "n_frames" in dir(im)
426
+ and im.n_frames != 0
427
+ and self.codec_info_orig.fps != 0.0
428
+ ):
429
+ duration_ptr = 0.0
430
+ duration_inc = 1 / self.codec_info_orig.fps * 1000
431
+ next_frame_start_duration = im.info.get("duration", 1000)
432
+ frame = 0
433
+ while True:
425
434
  self.frames_raw.append(np.asarray(im.convert("RGBA")))
435
+ duration_ptr += duration_inc
436
+ if duration_ptr >= next_frame_start_duration:
437
+ if frame == im.n_frames:
438
+ break
439
+ im.seek(frame)
440
+ next_frame_start_duration += im.info.get("duration", 1000)
441
+ frame += 1
426
442
  else:
427
443
  self.frames_raw.append(np.asarray(im.convert("RGBA")))
428
444
 
@@ -431,6 +447,7 @@ class StickerConvert:
431
447
  from av.codec.context import CodecContext
432
448
  from av.container.input import InputContainer
433
449
  from av.video.codeccontext import VideoCodecContext
450
+ from av.video.frame import VideoFrame
434
451
 
435
452
  # Crashes when handling some webm in yuv420p and convert to rgba
436
453
  # https://github.com/PyAV-Org/PyAV/issues/1166
@@ -451,19 +468,33 @@ class StickerConvert:
451
468
 
452
469
  for packet in container.demux(container.streams.video):
453
470
  for frame in context.decode(packet):
454
- if frame.width % 2 != 0:
455
- width = frame.width - 1
456
- else:
457
- width = frame.width
471
+ width_orig = frame.width
472
+ height_orig = frame.height
473
+
474
+ # Need to pad frame to even dimension first
475
+ if width_orig % 2 == 1 or height_orig % 2 == 1:
476
+ from av.filter import Graph
477
+
478
+ width_new = width_orig + width_orig % 2
479
+ height_new = height_orig + height_orig % 2
480
+
481
+ graph = Graph()
482
+ in_src = graph.add_buffer(template=container.streams.video[0])
483
+ pad = graph.add(
484
+ "pad", f"{width_new}:{height_new}:0:0:color=#00000000"
485
+ )
486
+ in_src.link_to(pad)
487
+ sink = graph.add("buffersink")
488
+ pad.link_to(sink)
489
+ graph.configure()
458
490
 
459
- if frame.height % 2 != 0:
460
- height = frame.height - 1
491
+ graph.push(frame)
492
+ frame_resized = cast(VideoFrame, graph.pull())
461
493
  else:
462
- height = frame.height
494
+ frame_resized = frame
463
495
 
464
- if frame.format.name == "yuv420p":
465
- rgb_array = frame.to_ndarray(format="rgb24")
466
- cast("np.ndarray[Any, Any]", rgb_array)
496
+ if frame_resized.format.name == "yuv420p":
497
+ rgb_array = frame_resized.to_ndarray(format="rgb24")
467
498
  rgba_array = np.dstack(
468
499
  (
469
500
  rgb_array,
@@ -474,14 +505,14 @@ class StickerConvert:
474
505
  # yuva420p may cause crash
475
506
  # Not safe to directly call frame.to_ndarray(format="rgba")
476
507
  # https://github.com/laggykiller/sticker-convert/issues/114
477
- frame = frame.reformat(
478
- width=width,
479
- height=height,
508
+ frame_resized = frame_resized.reformat(
480
509
  format="yuva420p",
481
510
  dst_colorspace=1,
482
511
  )
483
- rgba_array = yuva_to_rgba(frame)
512
+ rgba_array = yuva_to_rgba(frame_resized)
484
513
 
514
+ # Remove pixels that was added to make dimensions even
515
+ rgba_array = rgba_array[0:width_orig, 0:height_orig]
485
516
  self.frames_raw.append(rgba_array)
486
517
 
487
518
  def _frames_import_lottie(self) -> None:
@@ -514,25 +545,25 @@ class StickerConvert:
514
545
  anim.lottie_animation_destroy()
515
546
 
516
547
  def determine_bg_color(self) -> Tuple[int, int, int, int]:
548
+ mean_total = 0.0
517
549
  # Calculate average color of all frames for selecting background color
518
- frames_raw_np = np.array(self.frames_raw)
519
- s = frames_raw_np.shape
520
- colors = frames_raw_np.reshape((-1, s[3])) # type: ignore
521
- # Do not count in alpha=0
522
- # If alpha > 0, use alpha as weight
523
- colors = colors[colors[:, 3] != 0]
524
- if colors.shape[0] == 0:
525
- return (0, 0, 0, 0)
550
+ for frame in self.frames_raw:
551
+ s = frame.shape
552
+ colors = frame.reshape((-1, s[2])) # type: ignore
553
+ # Do not count in alpha=0
554
+ # If alpha > 0, use alpha as weight
555
+ colors = colors[colors[:, 3] != 0]
556
+ if colors.shape[0] != 0:
557
+ alphas = colors[:, 3] / 255
558
+ r_mean = np.mean(colors[:, 0] * alphas)
559
+ g_mean = np.mean(colors[:, 1] * alphas)
560
+ b_mean = np.mean(colors[:, 2] * alphas)
561
+ mean_total += (r_mean + g_mean + b_mean) / 3
562
+
563
+ if mean_total / len(self.frames_raw) < 128:
564
+ return (255, 255, 255, 0)
526
565
  else:
527
- alphas = colors[:, 3] / 255
528
- r_mean = np.mean(colors[:, 0] * alphas)
529
- g_mean = np.mean(colors[:, 1] * alphas)
530
- b_mean = np.mean(colors[:, 2] * alphas)
531
- mean = (r_mean + g_mean + b_mean) / 3
532
- if mean < 128:
533
- return (255, 255, 255, 0)
534
- else:
535
- return (0, 0, 0, 0)
566
+ return (0, 0, 0, 0)
536
567
 
537
568
  def frames_resize(
538
569
  self, frames_in: "List[np.ndarray[Any, Any]]"
@@ -627,8 +658,6 @@ class StickerConvert:
627
658
  frame_current = 0
628
659
  frame_current_float = 0.0
629
660
  while True:
630
- frame_current_float += frame_increment
631
- frame_current = int(rounding(frame_current_float))
632
661
  if frame_current <= len(frames_in) - 1 and not (
633
662
  frames_out_max and len(frames_out) == frames_out_max
634
663
  ):
@@ -639,6 +668,8 @@ class StickerConvert:
639
668
  ):
640
669
  frames_out.append(frames_in[-1])
641
670
  return frames_out
671
+ frame_current_float += frame_increment
672
+ frame_current = int(rounding(frame_current_float))
642
673
 
643
674
  def frames_export(self) -> None:
644
675
  is_animated = len(self.frames_processed) > 1 and self.fps
@@ -727,10 +758,10 @@ class StickerConvert:
727
758
  if 0 in alpha:
728
759
  extra_kwargs["transparency"] = 0
729
760
  extra_kwargs["disposal"] = 2
730
- im_out = [self.quantize(Image.fromarray(i)) for i in frames_processed]
761
+ im_out = [self.quantize(Image.fromarray(i)) for i in frames_processed] # type: ignore
731
762
  else:
732
763
  im_out = [
733
- self.quantize(Image.fromarray(i).convert("RGB")).convert("RGB")
764
+ self.quantize(Image.fromarray(i).convert("RGB")).convert("RGB") # type: ignore
734
765
  for i in frames_processed
735
766
  ]
736
767
 
@@ -755,10 +786,25 @@ class StickerConvert:
755
786
  config = webp.WebPConfig.new(quality=self.quality) # type: ignore
756
787
  enc = webp.WebPAnimEncoder.new(self.res_w, self.res_h) # type: ignore
757
788
  timestamp_ms = 0
758
- for frame in self.frames_processed:
759
- pic = webp.WebPPicture.from_numpy(frame) # type: ignore
789
+ timestamp_inc = int(1000 / self.fps)
790
+
791
+ pic = webp.WebPPicture.from_numpy(self.frames_processed[0]) # type: ignore
792
+ enc.encode_frame(pic, 0, config=config) # type: ignore
793
+
794
+ frame_num = 1
795
+ frame_num_prev = 1
796
+ frame_total = len(self.frames_processed)
797
+ while frame_num < frame_total - 1:
798
+ while frame_num < frame_total - 1 and np.array_equal(
799
+ self.frames_processed[frame_num_prev],
800
+ self.frames_processed[frame_num],
801
+ ):
802
+ timestamp_ms += timestamp_inc
803
+ frame_num += 1
804
+ pic = webp.WebPPicture.from_numpy(self.frames_processed[frame_num]) # type: ignore
760
805
  enc.encode_frame(pic, timestamp_ms, config=config) # type: ignore
761
- timestamp_ms += int(1000 / self.fps)
806
+ frame_num_prev = frame_num
807
+
762
808
  anim_data = enc.assemble(timestamp_ms) # type: ignore
763
809
  self.tmp_f.write(anim_data.buffer()) # type: ignore
764
810
 
@@ -3,13 +3,64 @@ from __future__ import annotations
3
3
 
4
4
  import mmap
5
5
  from decimal import ROUND_HALF_UP, Decimal
6
+ from fractions import Fraction
6
7
  from io import BytesIO
8
+ from math import gcd
7
9
  from pathlib import Path
8
- from typing import BinaryIO, Optional, Tuple, Union, cast
10
+ from typing import BinaryIO, List, Optional, Tuple, Union, cast
9
11
 
10
12
  from PIL import Image, UnidentifiedImageError
11
13
 
12
14
 
15
+ def lcm(a: int, b: int):
16
+ return abs(a * b) // gcd(a, b)
17
+
18
+
19
+ def rounding(value: float) -> Decimal:
20
+ return Decimal(value).quantize(0, ROUND_HALF_UP)
21
+
22
+
23
+ def fraction_gcd(x: Fraction, y: Fraction) -> Fraction:
24
+ a = x.numerator
25
+ b = x.denominator
26
+ c = y.numerator
27
+ d = y.denominator
28
+ return Fraction(gcd(a, c), lcm(b, d))
29
+
30
+
31
+ def fractions_gcd(*fractions: Fraction) -> Fraction:
32
+ fractions_list = list(fractions)
33
+ gcd = fractions_list.pop(0)
34
+ for fraction in fractions_list:
35
+ gcd = fraction_gcd(gcd, fraction)
36
+
37
+ return gcd
38
+
39
+
40
+ def get_five_dec_place(value: float) -> str:
41
+ return str(value).split(".")[1][:5].ljust(5, "0")
42
+
43
+
44
+ def likely_int(value: float) -> bool:
45
+ if isinstance(value, int):
46
+ return True
47
+ return True if get_five_dec_place(value) in ("99999", "00000") else False
48
+
49
+
50
+ def durations_gcd(*durations: Union[int, float]) -> Union[float, Fraction]:
51
+ if any(i for i in durations if isinstance(i, float)):
52
+ if all(i for i in durations if likely_int(i)):
53
+ return Fraction(gcd(*(int(rounding(i)) for i in durations)), 1)
54
+ # Test for denominators that can produce recurring decimal
55
+ for x in (3, 6, 7, 9, 11, 13):
56
+ if all(likely_int(i * x) for i in durations):
57
+ return Fraction(gcd(*(int(rounding(i * x)) for i in durations)), x)
58
+ else:
59
+ return min(durations)
60
+ else:
61
+ return Fraction(gcd(*durations), 1) # type: ignore
62
+
63
+
13
64
  class CodecInfo:
14
65
  def __init__(
15
66
  self, file: Union[Path, bytes], file_ext: Optional[str] = None
@@ -29,8 +80,9 @@ class CodecInfo:
29
80
  @staticmethod
30
81
  def get_file_fps_frames_duration(
31
82
  file: Union[Path, bytes], file_ext: Optional[str] = None
32
- ) -> Tuple[float, int, int]:
83
+ ) -> Tuple[float, int, float]:
33
84
  fps: float
85
+ duration: float
34
86
 
35
87
  if not file_ext and isinstance(file, Path):
36
88
  file_ext = CodecInfo.get_file_ext(file)
@@ -41,13 +93,12 @@ class CodecInfo:
41
93
  duration = int(frames / fps * 1000)
42
94
  else:
43
95
  duration = 0
96
+ elif file_ext == ".webp":
97
+ fps, frames, duration = CodecInfo._get_file_fps_frames_duration_webp(file)
98
+ elif file_ext in (".gif", ".apng", ".png"):
99
+ fps, frames, duration = CodecInfo._get_file_fps_frames_duration_pillow(file)
44
100
  else:
45
- if file_ext == ".webp":
46
- frames, duration = CodecInfo._get_file_frames_duration_webp(file)
47
- elif file_ext in (".gif", ".apng", ".png"):
48
- frames, duration = CodecInfo._get_file_frames_duration_pillow(file)
49
- else:
50
- frames, duration = CodecInfo._get_file_frames_duration_av(file)
101
+ frames, duration = CodecInfo._get_file_frames_duration_av(file)
51
102
 
52
103
  if duration > 0:
53
104
  fps = frames / duration * 1000
@@ -63,14 +114,16 @@ class CodecInfo:
63
114
 
64
115
  if file_ext == ".tgs":
65
116
  return CodecInfo._get_file_fps_tgs(file)
66
- if file_ext == ".webp":
67
- frames, duration = CodecInfo._get_file_frames_duration_webp(file)
117
+ elif file_ext == ".webp":
118
+ fps, _, _ = CodecInfo._get_file_fps_frames_duration_webp(file)
119
+ return fps
68
120
  elif file_ext in (".gif", ".apng", ".png"):
69
- frames, duration = CodecInfo._get_file_frames_duration_pillow(file)
70
- else:
71
- frames, duration = CodecInfo._get_file_frames_duration_av(
72
- file, frames_to_iterate=10
73
- )
121
+ fps, _, _ = CodecInfo._get_file_fps_frames_duration_pillow(file)
122
+ return fps
123
+
124
+ frames, duration = CodecInfo._get_file_frames_duration_av(
125
+ file, frames_to_iterate=10
126
+ )
74
127
 
75
128
  if duration > 0:
76
129
  return frames / duration * 1000
@@ -89,7 +142,7 @@ class CodecInfo:
89
142
  if file_ext == ".tgs":
90
143
  return CodecInfo._get_file_frames_tgs(file)
91
144
  if file_ext in (".gif", ".webp", ".png", ".apng"):
92
- frames, _ = CodecInfo._get_file_frames_duration_pillow(
145
+ _, frames, _ = CodecInfo._get_file_fps_frames_duration_pillow(
93
146
  file, frames_only=True
94
147
  )
95
148
  else:
@@ -106,7 +159,9 @@ class CodecInfo:
106
159
  @staticmethod
107
160
  def get_file_duration(
108
161
  file: Union[Path, bytes], file_ext: Optional[str] = None
109
- ) -> int:
162
+ ) -> float:
163
+ duration: float
164
+
110
165
  # Return duration in miliseconds
111
166
  if not file_ext and isinstance(file, Path):
112
167
  file_ext = CodecInfo.get_file_ext(file)
@@ -118,9 +173,9 @@ class CodecInfo:
118
173
  else:
119
174
  duration = 0
120
175
  elif file_ext == ".webp":
121
- _, duration = CodecInfo._get_file_frames_duration_webp(file)
176
+ _, _, duration = CodecInfo._get_file_fps_frames_duration_webp(file)
122
177
  elif file_ext in (".gif", ".png", ".apng"):
123
- _, duration = CodecInfo._get_file_frames_duration_pillow(file)
178
+ _, _, duration = CodecInfo._get_file_fps_frames_duration_pillow(file)
124
179
  else:
125
180
  _, duration = CodecInfo._get_file_frames_duration_av(file)
126
181
 
@@ -176,29 +231,46 @@ class CodecInfo:
176
231
  return fps, frames
177
232
 
178
233
  @staticmethod
179
- def _get_file_frames_duration_pillow(
234
+ def _get_file_fps_frames_duration_pillow(
180
235
  file: Union[Path, bytes], frames_only: bool = False
181
- ) -> Tuple[int, int]:
182
- total_duration = 0
236
+ ) -> Tuple[float, int, float]:
237
+ total_duration = 0.0
238
+ durations: List[float] = []
183
239
 
184
240
  with Image.open(file) as im:
185
241
  if "n_frames" in dir(im):
186
242
  frames = im.n_frames
187
243
  if frames_only is True:
188
- return frames, 1
244
+ return 0.0, frames, 1
189
245
  for i in range(im.n_frames):
190
246
  im.seek(i)
191
- total_duration += im.info.get("duration", 1000)
192
- return frames, total_duration
193
-
194
- return 1, 0
247
+ frame_duration = cast(float, im.info.get("duration", 1000))
248
+ if frame_duration not in durations and frame_duration != 0:
249
+ durations.append(frame_duration)
250
+ total_duration += frame_duration
251
+ if im.n_frames == 0 or total_duration == 0:
252
+ fps = 0.0
253
+ elif len(durations) == 1:
254
+ fps = frames / total_duration * 1000
255
+ else:
256
+ duration_gcd = durations_gcd(*durations)
257
+ frames_apparent = total_duration / duration_gcd
258
+ fps = frames_apparent / total_duration * 1000
259
+ return fps, frames, total_duration
260
+
261
+ return (
262
+ 0.0,
263
+ 1,
264
+ 0,
265
+ )
195
266
 
196
267
  @staticmethod
197
- def _get_file_frames_duration_webp(
268
+ def _get_file_fps_frames_duration_webp(
198
269
  file: Union[Path, bytes],
199
- ) -> Tuple[int, int]:
270
+ ) -> Tuple[float, int, int]:
200
271
  total_duration = 0
201
272
  frames = 0
273
+ durations: List[int] = []
202
274
 
203
275
  def _open_f(file: Union[Path, bytes]) -> BinaryIO:
204
276
  if isinstance(file, Path):
@@ -213,16 +285,26 @@ class CodecInfo:
213
285
  break
214
286
  mm.seek(anmf_pos + 20)
215
287
  frame_duration_32 = mm.read(4)
216
- frame_duration = frame_duration_32[:-1] + bytes(
288
+ frame_duration_bytes = frame_duration_32[:-1] + bytes(
217
289
  int(frame_duration_32[-1]) & 0b11111100
218
290
  )
219
- total_duration += int.from_bytes(frame_duration, "little")
291
+ frame_duration = int.from_bytes(frame_duration_bytes, "little")
292
+ if frame_duration not in durations and frame_duration != 0:
293
+ durations.append(frame_duration)
294
+ total_duration += frame_duration
220
295
  frames += 1
221
296
 
222
297
  if frames == 0:
223
- return 1, 0
298
+ return 0.0, 0, 0
299
+
300
+ if len(durations) == 1:
301
+ fps = frames / total_duration * 1000
302
+ else:
303
+ duration_gcd = durations_gcd(*durations)
304
+ frames_apparent = total_duration / duration_gcd
305
+ fps = float(frames_apparent / total_duration * 1000)
224
306
 
225
- return frames, total_duration
307
+ return fps, frames, total_duration
226
308
 
227
309
  @staticmethod
228
310
  def _get_file_frames_duration_av(
@@ -246,9 +328,7 @@ class CodecInfo:
246
328
  container = cast(InputContainer, container)
247
329
  stream = container.streams.video[0]
248
330
  if container.duration:
249
- duration_metadata = int(
250
- Decimal(container.duration / 1000).quantize(0, ROUND_HALF_UP)
251
- )
331
+ duration_metadata = int(rounding(container.duration / 1000))
252
332
  else:
253
333
  duration_metadata = 0
254
334
 
@@ -273,7 +353,7 @@ class CodecInfo:
273
353
  duration_n_minus_one = last_frame.pts * time_base_ms
274
354
  ms_per_frame = duration_n_minus_one / (frame_count - 1)
275
355
  duration = frame_count * ms_per_frame
276
- return frame_count, int(Decimal(duration).quantize(0, ROUND_HALF_UP))
356
+ return frame_count, int(rounding(duration))
277
357
 
278
358
  return 0, 0
279
359
 
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
- __version__ = "2.7.12"
3
+ __version__ = "2.8.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sticker-convert
3
- Version: 2.7.12
3
+ Version: 2.8.0
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>
@@ -1,12 +1,12 @@
1
1
  sticker_convert/__init__.py,sha256=iQnv6UOOA69c3soAn7ZOnAIubTIQSUxtq1Uhh8xRWvU,102
2
2
  sticker_convert/__main__.py,sha256=6RJauR-SCSSTT3TU7FFB6B6PVwsCxO2xZXtmZ3jc2Is,463
3
3
  sticker_convert/cli.py,sha256=kyfhvMoHq42uLZsYYLrr6b30xsNE93GVZlbGYPFjC2I,18385
4
- sticker_convert/converter.py,sha256=-BxUT-VCw-VbfbG7ZZxNTI4FIJcU3Pq2rzw0VuitiVI,32904
4
+ sticker_convert/converter.py,sha256=Y0vVZhAxlTe_SDTbQq2_Z9vM8oY6VAJPToYji-S6J-E,35033
5
5
  sticker_convert/definitions.py,sha256=ZhP2ALCEud-w9ZZD4c3TDG9eHGPZyaAL7zPUsJAbjtE,2073
6
6
  sticker_convert/gui.py,sha256=TqdgFbHBRYgcXWWrsfxLUJ8Zu9WeE11vYyZMX6nalik,30599
7
7
  sticker_convert/job.py,sha256=J4e7dZ48t5EhM3fG-xF1BEQ10WYljx3wWamKXAJjCa8,26000
8
8
  sticker_convert/job_option.py,sha256=1YVhyTfu2cWz3qpAKbdIM11jbL0CJz0ksOYAeg8v6dc,7649
9
- sticker_convert/version.py,sha256=itLv7GvtbPZ961xSemMjepArEGEJR3yg6PpGH0MuovQ,47
9
+ sticker_convert/version.py,sha256=M_a1EApD3NUMpL5LvuHWnryb8nvQLMKOHwpDKPiWPX4,46
10
10
  sticker_convert/downloaders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  sticker_convert/downloaders/download_base.py,sha256=5R7c8kwahAflOOYrtSQPnBVQ4T-DsprPUMP7G9wcJX4,2824
12
12
  sticker_convert/downloaders/download_kakao.py,sha256=RYrebTxEjKjXAr9xw18r0dMW0dFjSqZxzi8dpaq56TY,8581
@@ -89,12 +89,12 @@ sticker_convert/utils/files/metadata_handler.py,sha256=TJpQ-7KdnqQh09hwR6xB_scRL
89
89
  sticker_convert/utils/files/run_bin.py,sha256=QalA9je6liHxiOtxsjsFsIkc2t59quhcJCVpP1X3p50,1743
90
90
  sticker_convert/utils/files/sanitize_filename.py,sha256=HBklPGsHRJjFQUIC5rYTQsUrsuTtezZXIEA8CPhLP8A,2156
91
91
  sticker_convert/utils/media/apple_png_normalize.py,sha256=LbrQhc7LlYX4I9ek4XJsZE4l0MygBA1jB-PFiYLEkzk,3657
92
- sticker_convert/utils/media/codec_info.py,sha256=JHHlfCLSpepYFv6TSL1XqwUJoVOpOPFxurRtduplPGY,12856
92
+ sticker_convert/utils/media/codec_info.py,sha256=yXjgGN3AwwFiwPHTlM9mqEtH3fH8VegZpKkgj3TvUNA,15611
93
93
  sticker_convert/utils/media/decrypt_kakao.py,sha256=4wq9ZDRnFkx1WmFZnyEogBofiLGsWQM_X69HlA36578,1947
94
94
  sticker_convert/utils/media/format_verify.py,sha256=Xf94jyqk_6M9IlFGMy0wYIgQKn_yg00nD4XW0CgAbew,5732
95
- sticker_convert-2.7.12.dist-info/LICENSE,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
96
- sticker_convert-2.7.12.dist-info/METADATA,sha256=tJ1IQvfsDyRiDDQSkHech1YhfhlB9U9HPeUEk5a15eY,49133
97
- sticker_convert-2.7.12.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
98
- sticker_convert-2.7.12.dist-info/entry_points.txt,sha256=MNJ7XyC--ugxi5jS1nzjDLGnxCyLuaGdsVLnJhDHCqs,66
99
- sticker_convert-2.7.12.dist-info/top_level.txt,sha256=r9vfnB0l1ZnH5pTH5RvkobnK3Ow9m0RsncaOMAtiAtk,16
100
- sticker_convert-2.7.12.dist-info/RECORD,,
95
+ sticker_convert-2.8.0.dist-info/LICENSE,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
96
+ sticker_convert-2.8.0.dist-info/METADATA,sha256=M1OIHNyHntzmytdfECLmPnQ3r_veJG2y5W3JcoE845o,49132
97
+ sticker_convert-2.8.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
98
+ sticker_convert-2.8.0.dist-info/entry_points.txt,sha256=MNJ7XyC--ugxi5jS1nzjDLGnxCyLuaGdsVLnJhDHCqs,66
99
+ sticker_convert-2.8.0.dist-info/top_level.txt,sha256=r9vfnB0l1ZnH5pTH5RvkobnK3Ow9m0RsncaOMAtiAtk,16
100
+ sticker_convert-2.8.0.dist-info/RECORD,,