deffcode 0.2.7__py3-none-any.whl → 0.2.8__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.
deffcode/ffdecoder.py CHANGED
@@ -30,7 +30,7 @@ import threading
30
30
  from collections import OrderedDict
31
31
  from collections.abc import Generator
32
32
  from types import TracebackType
33
- from typing import Any
33
+ from typing import Any, Self
34
34
 
35
35
  import numpy as np
36
36
 
@@ -226,6 +226,9 @@ class FFdecoder:
226
226
  if {"-vf", "-filter_complex"}.intersection(self.__extra_params.keys()):
227
227
  key = "-vf" if "-vf" in self.__extra_params else "-filter_complex"
228
228
  sourcer_params[key] = self.__extra_params[key]
229
+ # multi-input output streams are selected via `-map`, so probe with it too
230
+ if self.__is_multi and "-map" in self.__extra_params:
231
+ sourcer_params["-map"] = self.__extra_params["-map"]
229
232
 
230
233
  # define dict to store user-defined parameters
231
234
  self.__user_metadata = {}
@@ -364,7 +367,7 @@ class FFdecoder:
364
367
  # reset improper values
365
368
  self.__custom_resolution = None
366
369
 
367
- def formulate(self) -> FFdecoder:
370
+ def formulate(self) -> Self:
368
371
  """
369
372
  This method formulates all necessary FFmpeg pipeline arguments and executes it inside the FFmpeg `subprocess` pipe.
370
373
 
@@ -662,7 +665,9 @@ class FFdecoder:
662
665
  if "-frames:v" in input_params:
663
666
  self.__raw_frame_num = input_params["-frames:v"]
664
667
  elif (
665
- self.__sourcer_metadata["approx_video_nframes"] is not None
668
+ # composite output length is unrelated to the primary source's length
669
+ not self.__is_multi
670
+ and self.__sourcer_metadata["approx_video_nframes"] is not None
666
671
  and self.__sourcer_metadata["approx_video_nframes"] > 0
667
672
  ):
668
673
  self.__raw_frame_num = self.__sourcer_metadata["approx_video_nframes"]
@@ -956,7 +961,7 @@ class FFdecoder:
956
961
  )
957
962
  if _src_meta.get("source_demuxer"):
958
963
  cmd += ["-f", _src_meta["source_demuxer"]]
959
- cmd += ["-i", _src]
964
+ cmd += ["-i", _src_meta.get("source", _src)]
960
965
  else:
961
966
  cmd += self.__ffmpeg_prefixes
962
967
  cmd += input_parameters
deffcode/ffhelper.py CHANGED
@@ -42,8 +42,14 @@ logger.propagate = False
42
42
  logger.addHandler(logger_handler())
43
43
  logger.setLevel(logging.DEBUG)
44
44
 
45
+ # set default timeout for subprocesses
46
+ MAX_TIMEOUT_SUBPROCESS: float = float(os.getenv("MAX_TIMEOUT_SUBPROCESS", 10.0))
47
+ # set grace period (seconds) between SIGTERM and SIGKILL when terminating
48
+ # a timed-out subprocess. Allows FFmpeg to flush buffers and release hardware
49
+ # resources before being force-killed.
50
+ TERMINATE_TIMEOUT_SUBPROCESS: float = float(os.getenv("TERMINATE_TIMEOUT_SUBPROCESS", 2.0))
45
51
  # set default timer for download requests
46
- DEFAULT_TIMEOUT = 3
52
+ DEFAULT_TIMEOUT_REQUESTS: float = float(os.getenv("DEFAULT_TIMEOUT_REQUESTS", 3.0))
47
53
 
48
54
 
49
55
  class TimeoutHTTPAdapter(HTTPAdapter):
@@ -52,7 +58,7 @@ class TimeoutHTTPAdapter(HTTPAdapter):
52
58
  """
53
59
 
54
60
  def __init__(self, *args: Any, **kwargs: Any) -> None:
55
- self.timeout: float = DEFAULT_TIMEOUT
61
+ self.timeout: float = DEFAULT_TIMEOUT_REQUESTS
56
62
  if "timeout" in kwargs:
57
63
  self.timeout = kwargs["timeout"]
58
64
  del kwargs["timeout"]
@@ -210,7 +216,9 @@ def download_ffmpeg_binaries(path: str, os_windows: bool = False, os_bit: str =
210
216
  status_forcelist=[429, 500, 502, 503, 504],
211
217
  )
212
218
  # Mount it for https usage
213
- adapter = TimeoutHTTPAdapter(timeout=2.0, max_retries=retries)
219
+ adapter = TimeoutHTTPAdapter(
220
+ timeout=MAX_TIMEOUT_SUBPROCESS, max_retries=retries
221
+ )
214
222
  http.mount("https://", adapter)
215
223
  response = http.get(file_url, stream=True)
216
224
  response.raise_for_status()
@@ -410,6 +418,7 @@ def extract_device_n_demuxer(
410
418
  metadata = check_sp_output(
411
419
  [path, *default_ffcommand.split(" ")],
412
420
  force_retrieve_stderr=True,
421
+ timeout=MAX_TIMEOUT_SUBPROCESS,
413
422
  )
414
423
  # clean and split metadata
415
424
  splitted = [x.decode("utf-8").strip() for x in metadata.split(b"\n")]
@@ -469,6 +478,7 @@ def extract_device_n_demuxer(
469
478
  # search in path properties
470
479
  metadata_path = check_sp_output(
471
480
  ["v4l2-ctl", "--device={}".format(path), "--all"],
481
+ timeout=MAX_TIMEOUT_SUBPROCESS,
472
482
  )
473
483
  # decode path metadata
474
484
  decoded_path = metadata_path.decode("utf-8").strip()
@@ -488,6 +498,7 @@ def extract_device_n_demuxer(
488
498
  metadata = check_sp_output(
489
499
  [path, *default_ffcommand.split(" ")],
490
500
  force_retrieve_stderr=True,
501
+ timeout=MAX_TIMEOUT_SUBPROCESS,
491
502
  )
492
503
  # clean and split metadata
493
504
  splitted = [x.decode("utf-8").strip() for x in metadata.split(b"\n")]
@@ -641,44 +652,83 @@ def check_sp_output(*args: Any, **kwargs: Any) -> bytes:
641
652
  """
642
653
  ## check_sp_output
643
654
 
644
- Returns FFmpeg `stdout` output from subprocess module.
655
+ Executes a subprocess command and returns its `stdout` (or `stderr` when
656
+ requested). On timeout, performs a two-step graceful shutdown — sends
657
+ `SIGTERM` first to allow FFmpeg to flush buffers and release hardware
658
+ resources (decoders, capture devices), then escalates to `SIGKILL` if the
659
+ process fails to exit within a short grace period.
645
660
 
646
661
  Parameters:
647
662
  args (based on input): Non Keyword Arguments
648
663
  kwargs (based on input): Keyword Arguments
664
+ force_retrieve_stderr (bool): If True, returns stderr. Also
665
+ suppresses `CalledProcessError` on non-zero exit, since some
666
+ FFmpeg diagnostic commands (e.g. `-list_devices`) emit useful
667
+ output on stderr while exiting non-zero by design.
668
+ timeout (float): Seconds to wait before terminating the process.
649
669
 
650
- **Returns:** A string value.
670
+ **Returns:** A bytes value.
651
671
  """
652
672
  # workaround for python bug: https://bugs.python.org/issue37380
653
673
  if platform.system() == "Windows":
654
674
  # see comment https://bugs.python.org/msg370334
655
675
  sp._cleanup = lambda: None
676
+
656
677
  # handle additional params
657
678
  retrieve_stderr = kwargs.pop("force_retrieve_stderr", False)
679
+ timeout = kwargs.pop("timeout", None)
680
+
658
681
  # execute command in subprocess
659
682
  process = sp.Popen(
660
683
  *args,
661
684
  stdout=sp.PIPE,
662
- stderr=sp.DEVNULL if not (retrieve_stderr) else sp.PIPE,
685
+ stderr=sp.PIPE if retrieve_stderr else sp.DEVNULL,
663
686
  **kwargs,
664
687
  )
665
- # communicate and poll process
666
- output, stderr = process.communicate()
688
+
689
+ # communicate and poll process with two-step timeout handling
690
+ timeout_occurred = False
691
+ try:
692
+ output, stderr = process.communicate(timeout=timeout)
693
+ except sp.TimeoutExpired:
694
+ timeout_occurred = True
695
+ logger.warning(
696
+ f"[Pipeline-Warning] :: Process exceeded timeout of {timeout}s. "
697
+ "Attempting graceful termination..."
698
+ )
699
+ # Step 1: polite SIGTERM, give process a chance to clean up
700
+ process.terminate()
701
+ try:
702
+ output, stderr = process.communicate(timeout=TERMINATE_TIMEOUT_SUBPROCESS)
703
+ except sp.TimeoutExpired:
704
+ # Step 2: process ignored SIGTERM, force kill
705
+ logger.error("[Pipeline-Error] :: Process unresponsive to SIGTERM. Hard killing...")
706
+ process.kill()
707
+ output, stderr = process.communicate()
708
+
667
709
  retcode = process.poll()
710
+
668
711
  # handle return code
669
- if retcode and not (retrieve_stderr):
670
- logger.error("[Pipeline-Error] :: {}".format(output.decode("utf-8")))
712
+ # Bypass CalledProcessError if caller wants stderr (some FFmpeg commands
713
+ # exit non-zero by design) or if we killed the process via our timeout.
714
+ if retcode and not retrieve_stderr and not timeout_occurred:
715
+ logger.error(
716
+ "[Pipeline-Error] :: {}".format(output.decode("utf-8") if output else "No output")
717
+ )
671
718
  cmd = kwargs.get("args")
672
719
  if cmd is None:
673
720
  cmd = args[0]
674
721
  error = sp.CalledProcessError(retcode, cmd)
675
722
  error.output = output
676
723
  raise error
677
- # raise error if no output
678
- bool(output) or bool(stderr) or logger.error(
679
- "[Pipeline-Error] :: Pipeline failed to exact any data from command: {}!".format(
680
- args[0] if args else []
724
+
725
+ # warn if process emitted nothing on either stream
726
+ if not (bool(output) or bool(stderr)):
727
+ logger.error(
728
+ "[Pipeline-Error] :: Pipeline failed to extract any data from command: {}!".format(
729
+ args[0] if args else []
730
+ )
681
731
  )
682
- )
683
- # return output otherwise
732
+
733
+ # return stderr when explicitly requested (and present), else stdout
684
734
  return stderr if retrieve_stderr and stderr else output
deffcode/sourcer.py CHANGED
@@ -28,11 +28,12 @@ import os
28
28
  import platform
29
29
  import re
30
30
  import shutil
31
- from typing import Any
31
+ from typing import Any, Self
32
32
 
33
33
  import numpy as np
34
34
 
35
35
  from .ffhelper import (
36
+ MAX_TIMEOUT_SUBPROCESS,
36
37
  check_sp_output,
37
38
  extract_device_n_demuxer,
38
39
  get_supported_demuxers,
@@ -294,7 +295,7 @@ class Sourcer:
294
295
  # check whether metadata probed or not?
295
296
  self.__metadata_probed = False
296
297
 
297
- def probe_stream(self, default_stream_indexes: list[int] | tuple[int, int] = (0, 0)) -> Sourcer:
298
+ def probe_stream(self, default_stream_indexes: list[int] | tuple[int, int] = (0, 0)) -> Self:
298
299
  """
299
300
  This method Parses/Probes FFmpeg `subprocess` pipe's Standard Output for given input source and Populates the information in private class variables.
300
301
 
@@ -308,6 +309,40 @@ class Sourcer:
308
309
  and len(default_stream_indexes) == 2
309
310
  and all(isinstance(x, int) for x in default_stream_indexes)
310
311
  ), "Invalid default_stream_indexes value!"
312
+ # probe extra inputs first via independent single-source Sourcers, so
313
+ # their resolved source/demuxer (e.g. camera index -> device) can be
314
+ # reused by the combined filtergraph probe below.
315
+ extra_metadata = []
316
+ if self.__is_multi:
317
+ for idx in range(1, len(self.__source_list)):
318
+ _src = self.__source_list[idx]
319
+ _demux = self.__source_demuxer_list[idx]
320
+ _prefixes = self.__ffmpeg_prefixes_list[idx]
321
+ _params = self.__sourcer_params.copy()
322
+ _params["-ffprefixes"] = _prefixes
323
+ # output routing references all inputs; meaningless per-source
324
+ for _key in ("-vf", "-filter_complex", "-map"):
325
+ _params.pop(_key, None)
326
+ # Resolve to an absolute path: on Unix `self.__ffmpeg` may be
327
+ # the bare command "ffmpeg" found via PATH, which the nested
328
+ # `get_valid_ffmpeg_path()` would reject as "not a file".
329
+ _custom_ffmpeg = (
330
+ self.__ffmpeg
331
+ if self.__ffmpeg and os.path.isfile(self.__ffmpeg)
332
+ else (shutil.which(self.__ffmpeg) or "")
333
+ )
334
+ _s = Sourcer(
335
+ _src,
336
+ source_demuxer=_demux,
337
+ custom_ffmpeg=_custom_ffmpeg,
338
+ verbose=self.__verbose_logs,
339
+ **_params,
340
+ )
341
+ _s.probe_stream(default_stream_indexes)
342
+ extra_metadata.append(_s.retrieve_metadata(force_retrieve_missing=True))
343
+ self.__resolved_extra_inputs = [
344
+ (m[0]["source"], m[0].get("source_demuxer")) for m in extra_metadata
345
+ ]
311
346
  # validate source and extract metadata
312
347
  self.__ffsp_output = self.__validate_source(
313
348
  self.__source,
@@ -394,34 +429,7 @@ class Sourcer:
394
429
  # the guard inside retrieve_metadata (checks for non-empty
395
430
  # __multi_source_metadata) prevents `sources: []` self-pollution.
396
431
  self.__multi_source_metadata.append(self.retrieve_metadata(force_retrieve_missing=True))
397
- for idx in range(1, len(self.__source_list)):
398
- _src = self.__source_list[idx]
399
- _demux = self.__source_demuxer_list[idx]
400
- _prefixes = self.__ffmpeg_prefixes_list[idx]
401
- _params = self.__sourcer_params.copy()
402
- _params["-ffprefixes"] = _prefixes
403
- # spawn an independent single-source Sourcer per extra input;
404
- # this reuses the resolved ffmpeg path and isolates per-source
405
- # parsing state (which probe_stream otherwise clobbers).
406
- # Resolve to an absolute path: on Unix `self.__ffmpeg` may be
407
- # the bare command "ffmpeg" found via PATH, which the nested
408
- # `get_valid_ffmpeg_path()` would reject as "not a file".
409
- _custom_ffmpeg = (
410
- self.__ffmpeg
411
- if self.__ffmpeg and os.path.isfile(self.__ffmpeg)
412
- else (shutil.which(self.__ffmpeg) or "")
413
- )
414
- _s = Sourcer(
415
- _src,
416
- source_demuxer=_demux,
417
- custom_ffmpeg=_custom_ffmpeg,
418
- verbose=self.__verbose_logs,
419
- **_params,
420
- )
421
- _s.probe_stream(default_stream_indexes)
422
- self.__multi_source_metadata.append(
423
- _s.retrieve_metadata(force_retrieve_missing=True)
424
- )
432
+ self.__multi_source_metadata.extend(extra_metadata)
425
433
 
426
434
  # return reference to the instance object.
427
435
  return self
@@ -671,6 +679,20 @@ class Sourcer:
671
679
  + self.__ffmpeg_prefixes
672
680
  + (["-f", source_demuxer] if source_demuxer else [])
673
681
  + ["-i", source]
682
+ # multi-input filtergraphs reference every input (e.g. `[1:v]`),
683
+ # so the probe must ingest all of them to resolve output metadata.
684
+ + [
685
+ arg
686
+ for (src, demux), prefixes in zip(
687
+ self.__resolved_extra_inputs,
688
+ self.__ffmpeg_prefixes_list[1:],
689
+ strict=True,
690
+ )
691
+ for arg in ["-t", "0.0001"]
692
+ + prefixes
693
+ + (["-f", demux] if demux else [])
694
+ + ["-i", src]
695
+ ]
674
696
  + dict2Args(self.__sourcer_params)
675
697
  + ["-f", "null", "-"]
676
698
  )
@@ -687,13 +709,16 @@ class Sourcer:
687
709
  check_sp_output(
688
710
  meta_cmd,
689
711
  force_retrieve_stderr=True,
712
+ timeout=MAX_TIMEOUT_SUBPROCESS,
690
713
  )
691
714
  .decode("utf-8")
692
715
  .strip()
693
716
  )
694
717
  # separate input and output metadata (if available)
695
718
  if "Output #" in metadata:
696
- (metadata, self.__metadata_output) = metadata.split("Output #")
719
+ metadata, self.__metadata_output = metadata.split("Output #")
720
+ # primary `source_*` metadata must only describe input 0
721
+ metadata = re.split(r"^Input #1\b", metadata, maxsplit=1, flags=re.M)[0]
697
722
  # return metadata based on params
698
723
  return metadata
699
724
 
deffcode/version.py CHANGED
@@ -1 +1 @@
1
- __version__: str = "0.2.7"
1
+ __version__: str = "0.2.8"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deffcode
3
- Version: 0.2.7
3
+ Version: 0.2.8
4
4
  Summary: A cross-platform High-performance & Flexible Real-time Video Frames Decoder in Python.
5
5
  Author-email: Abhishek Thakur <abhi.una12@gmail.com>
6
6
  License: Apache License 2.0
@@ -22,11 +22,11 @@ Classifier: Intended Audience :: Developers
22
22
  Classifier: Intended Audience :: Science/Research
23
23
  Classifier: Intended Audience :: Education
24
24
  Classifier: License :: OSI Approved :: Apache Software License
25
- Classifier: Programming Language :: Python :: 3.10
26
25
  Classifier: Programming Language :: Python :: 3.11
27
26
  Classifier: Programming Language :: Python :: 3.12
28
27
  Classifier: Programming Language :: Python :: 3.13
29
- Requires-Python: >=3.10
28
+ Classifier: Programming Language :: Python :: 3.14
29
+ Requires-Python: >=3.11
30
30
  Description-Content-Type: text/markdown
31
31
  License-File: LICENSE
32
32
  Requires-Dist: cython
@@ -113,7 +113,7 @@ Here are some key features that stand out:
113
113
  - Effortless [**Metadata Extraction**][extracting-video-metadata] from all streams available in the source.
114
114
  - Maintains the standard easy to learn [**OpenCV-Python**](https://docs.opencv.org/4.x/d6/d00/tutorial_py_root.html) coding syntax.
115
115
  - Out-of-the-box support for all prominent Computer Vision libraries.
116
- - Cross-platform, runs on Python 3.10+, and easy to install.
116
+ - Cross-platform, runs on Python 3.11+, and easy to install.
117
117
 
118
118
  <!--
119
119
  - [x] Lossless Transcoding support with [WriteGear](https://abhitronix.github.io/deffcode/latest/gears/writegear/introduction/). #TODO
@@ -235,6 +235,8 @@ Once you have DeFFcode installed, checkout our Well-Documented **[Recipes 🍱][
235
235
  - [CUDA-NVENC-accelerated Video Transcoding with WriteGear API][cuda-nvenc-accelerated-video-transcoding-with-writegear-api]
236
236
  - [CUDA-NVENC-accelerated End-to-end Lossless Video Transcoding with WriteGear API][cuda-nvenc-accelerated-end-to-end-lossless-video-transcoding-with-writegear-api]
237
237
  - [Multi-Input Source Configurations][multi-input-source-configurations]
238
+ - [Decoding multiple inputs as 2x2 video grid _(mosaic)_][decoding-multiple-inputs-as-2x2-video-grid-mosaic]
239
+ - [Decoding crossfade transition between multiple inputs][decoding-crossfade-transition-between-multiple-inputs]
238
240
 
239
241
  </details>
240
242
 
@@ -271,9 +273,8 @@ decoder = FFdecoder("https://abhitronix.github.io/html/Big_Buck_Bunny_1080_10s_1
271
273
 
272
274
  # grab RGB24(default) 3D frames from decoder
273
275
  for frame in decoder.generateFrame():
274
-
275
276
  # lets print its shape
276
- print(frame.shape) # (1080, 1920, 3)
277
+ print(frame.shape) # (1080, 1920, 3)
277
278
 
278
279
  # terminate the decoder
279
280
  decoder.terminate()
@@ -288,7 +289,9 @@ The primary function of [**Sourcer API**][sourcer-api] is to **gather metadata i
288
289
  from deffcode import Sourcer
289
290
 
290
291
  # initialize and formulate the decoder using suitable source
291
- sourcer = Sourcer("https://abhitronix.github.io/html/Big_Buck_Bunny_1080_10s_1MB.mp4").probe_stream()
292
+ sourcer = Sourcer(
293
+ "https://abhitronix.github.io/html/Big_Buck_Bunny_1080_10s_1MB.mp4"
294
+ ).probe_stream()
292
295
 
293
296
  # print metadata as `json.dump`
294
297
  print(sourcer.retrieve_metadata(pretty_json=True))
@@ -354,18 +357,24 @@ It is something I am doing with my own free time. But so much more needs to be d
354
357
 
355
358
  Here is a Bibtex entry you can use to cite this project in a publication:
356
359
 
357
- [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.12689394.svg)](https://doi.org/10.5281/zenodo.12689394)
360
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.19658542.svg)](https://doi.org/10.5281/zenodo.19658542)
358
361
 
359
362
  ```BibTeX
360
363
  @software{deffcode,
361
364
  author = {Abhishek Thakur},
362
- title = {abhiTronix/deffcode: v0.2.6},
363
- month = jul,
364
- year = 2024,
365
+ title = {abhiTronix/deffcode: v0.2.7},
366
+ month = apr,
367
+ year = 2026,
365
368
  publisher = {Zenodo},
366
- version = {v0.2.6},
367
- doi = {10.5281/zenodo.12689394},
368
- url = {https://doi.org/10.5281/zenodo.12689394},
369
+ version = {v0.2.7},
370
+ doi = {10.5281/zenodo.19658542},
371
+ url = {https://doi.org/10.5281/zenodo.19658542},
372
+ swhid = {swh:1:dir:85e8dfd087aed870deb4043c532faaa9b156d1e1
373
+ ;origin=https://doi.org/10.5281/zenodo.6976947;vis
374
+ it=swh:1:snp:af1b478890801c02d56b95f563b86761980bf
375
+ 82c;anchor=swh:1:rel:85f9af4452f8030317ec2012acc10
376
+ 29e8a945e2d;path=abhiTronix-deffcode-5162649
377
+ },
369
378
  }
370
379
  ```
371
380
 
@@ -478,6 +487,8 @@ Advanced Recipes
478
487
  [smart-keyframe-only-decoding-for-heavy-ai-inference]: https://abhitronix.github.io/deffcode/latest/recipes/advanced/extract-frame-metadata/#smart-keyframe-only-decoding-for-heavy-ai-inference
479
488
  [variable-frame-rate-vfr-synchronization-via-pts_time]: https://abhitronix.github.io/deffcode/latest/recipes/advanced/extract-frame-metadata/#variable-frame-rate-vfr-synchronization-via-pts_time
480
489
  [multi-input-source-configurations]: https://abhitronix.github.io/deffcode/latest/recipes/advanced/multi_input/#multi-input-source-configurations
490
+ [decoding-multiple-inputs-as-2x2-video-grid-mosaic]: https://abhitronix.github.io/deffcode/latest/recipes/advanced/multi_input/#decoding-multiple-inputs-as-2x2-video-grid-mosaic
491
+ [decoding-crossfade-transition-between-multiple-inputs]: https://abhitronix.github.io/deffcode/latest/recipes/advanced/multi_input/#decoding-crossfade-transition-between-multiple-inputs
481
492
 
482
493
  <!--
483
494
  CI Apps URLs
@@ -0,0 +1,11 @@
1
+ deffcode/__init__.py,sha256=BQO_1Yv0E9PZCPtJ8-1z3X0VLP6VgBbdV5RahJU_RIk,286
2
+ deffcode/ffdecoder.py,sha256=0N1x1GstU7_RpqXe3RF-Uw4ZMGjBsRiDXlcFaycecxA,50640
3
+ deffcode/ffhelper.py,sha256=6DwWsCf22DQH2bXtkBvsh01APc26TFsDfWD5N00snJg,28911
4
+ deffcode/sourcer.py,sha256=k_Sau4vYLc8uGTJ5kX0Jorx0HC006bnA5Eplu_Ney7A,43617
5
+ deffcode/utils.py,sha256=bIR1Hu_icLs3Xe7njHR9YE09MYUbvUfhOUMSqSHh730,4729
6
+ deffcode/version.py,sha256=HUqwShoZiCc_WHZJmLh-SoGXaMatKowDyny2qENFLOU,27
7
+ deffcode-0.2.8.dist-info/licenses/LICENSE,sha256=pF-0xwtgiRjzW5P97fyEOCIqe7LV5Sd5cjIC4fBDwVQ,12259
8
+ deffcode-0.2.8.dist-info/METADATA,sha256=mZcgTQV75a8E4ytkFBgYiD5bpgRTY0jz6BqKd4wjhUw,34772
9
+ deffcode-0.2.8.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
10
+ deffcode-0.2.8.dist-info/top_level.txt,sha256=1C-kyZgIKFviemaQMmhP0VKR3wkzEZcrf1o5oUckvHI,9
11
+ deffcode-0.2.8.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (82.0.1)
2
+ Generator: setuptools (84.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,11 +0,0 @@
1
- deffcode/__init__.py,sha256=BQO_1Yv0E9PZCPtJ8-1z3X0VLP6VgBbdV5RahJU_RIk,286
2
- deffcode/ffdecoder.py,sha256=BXCGHDg56lZg08UEX16jGZNaREIweIjnOt3wAn50aGk,50278
3
- deffcode/ffhelper.py,sha256=AP9OJoKrHFonAFfiv19QCl5G_LIuTUzRjQ7yT0sTLUo,26409
4
- deffcode/sourcer.py,sha256=V5TxxSvT1kEWm6e2x2w7Nrgi40Bg9IoKyTuyNsY0Y6Y,42405
5
- deffcode/utils.py,sha256=bIR1Hu_icLs3Xe7njHR9YE09MYUbvUfhOUMSqSHh730,4729
6
- deffcode/version.py,sha256=dxcyVFoGmkMidzPt7T448p1Z-y0g7gXUtzvD5b-1rgg,27
7
- deffcode-0.2.7.dist-info/licenses/LICENSE,sha256=pF-0xwtgiRjzW5P97fyEOCIqe7LV5Sd5cjIC4fBDwVQ,12259
8
- deffcode-0.2.7.dist-info/METADATA,sha256=syRnP0R-4DZnflnfVaZg64wbb-CA9WdEif2sw6v5XJI,33812
9
- deffcode-0.2.7.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
10
- deffcode-0.2.7.dist-info/top_level.txt,sha256=1C-kyZgIKFviemaQMmhP0VKR3wkzEZcrf1o5oUckvHI,9
11
- deffcode-0.2.7.dist-info/RECORD,,