Spaces:
Running
Running
Upload 29 files
Browse files- .gitattributes +3 -0
- MLPY/Lib/site-packages/torio/__init__.py +8 -0
- MLPY/Lib/site-packages/torio/__pycache__/__init__.cpython-39.pyc +0 -0
- MLPY/Lib/site-packages/torio/_extension/__init__.py +13 -0
- MLPY/Lib/site-packages/torio/_extension/__pycache__/__init__.cpython-39.pyc +0 -0
- MLPY/Lib/site-packages/torio/_extension/__pycache__/utils.cpython-39.pyc +0 -0
- MLPY/Lib/site-packages/torio/_extension/utils.py +147 -0
- MLPY/Lib/site-packages/torio/io/__init__.py +9 -0
- MLPY/Lib/site-packages/torio/io/__pycache__/__init__.cpython-39.pyc +0 -0
- MLPY/Lib/site-packages/torio/io/__pycache__/_streaming_media_decoder.cpython-39.pyc +0 -0
- MLPY/Lib/site-packages/torio/io/__pycache__/_streaming_media_encoder.cpython-39.pyc +0 -0
- MLPY/Lib/site-packages/torio/io/_streaming_media_decoder.py +978 -0
- MLPY/Lib/site-packages/torio/io/_streaming_media_encoder.py +502 -0
- MLPY/Lib/site-packages/torio/lib/__init__.py +0 -0
- MLPY/Lib/site-packages/torio/lib/__pycache__/__init__.cpython-39.pyc +0 -0
- MLPY/Lib/site-packages/torio/lib/_torio_ffmpeg4.pyd +3 -0
- MLPY/Lib/site-packages/torio/lib/_torio_ffmpeg5.pyd +3 -0
- MLPY/Lib/site-packages/torio/lib/_torio_ffmpeg6.pyd +3 -0
- MLPY/Lib/site-packages/torio/lib/libtorio_ffmpeg4.pyd +0 -0
- MLPY/Lib/site-packages/torio/lib/libtorio_ffmpeg5.pyd +0 -0
- MLPY/Lib/site-packages/torio/lib/libtorio_ffmpeg6.pyd +0 -0
- MLPY/Lib/site-packages/torio/utils/__init__.py +4 -0
- MLPY/Lib/site-packages/torio/utils/__pycache__/__init__.cpython-39.pyc +0 -0
- MLPY/Lib/site-packages/torio/utils/__pycache__/ffmpeg_utils.cpython-39.pyc +0 -0
- MLPY/Lib/site-packages/torio/utils/ffmpeg_utils.py +247 -0
- MLPY/Lib/site-packages/typing_extensions-4.12.2.dist-info/INSTALLER +1 -0
- MLPY/Lib/site-packages/typing_extensions-4.12.2.dist-info/LICENSE +279 -0
- MLPY/Lib/site-packages/typing_extensions-4.12.2.dist-info/METADATA +67 -0
- MLPY/Lib/site-packages/typing_extensions-4.12.2.dist-info/RECORD +7 -0
- MLPY/Lib/site-packages/typing_extensions-4.12.2.dist-info/WHEEL +4 -0
.gitattributes
CHANGED
@@ -102,3 +102,6 @@ MLPY/Lib/site-packages/torch/lib/torch_cpu.lib filter=lfs diff=lfs merge=lfs -te
|
|
102 |
MLPY/Lib/site-packages/torch/lib/torch_python.dll filter=lfs diff=lfs merge=lfs -text
|
103 |
MLPY/Lib/site-packages/torch/lib/XNNPACK.lib filter=lfs diff=lfs merge=lfs -text
|
104 |
MLPY/Lib/site-packages/torchaudio/lib/libtorchaudio.pyd filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
102 |
MLPY/Lib/site-packages/torch/lib/torch_python.dll filter=lfs diff=lfs merge=lfs -text
|
103 |
MLPY/Lib/site-packages/torch/lib/XNNPACK.lib filter=lfs diff=lfs merge=lfs -text
|
104 |
MLPY/Lib/site-packages/torchaudio/lib/libtorchaudio.pyd filter=lfs diff=lfs merge=lfs -text
|
105 |
+
MLPY/Lib/site-packages/torio/lib/_torio_ffmpeg4.pyd filter=lfs diff=lfs merge=lfs -text
|
106 |
+
MLPY/Lib/site-packages/torio/lib/_torio_ffmpeg5.pyd filter=lfs diff=lfs merge=lfs -text
|
107 |
+
MLPY/Lib/site-packages/torio/lib/_torio_ffmpeg6.pyd filter=lfs diff=lfs merge=lfs -text
|
MLPY/Lib/site-packages/torio/__init__.py
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from . import _extension # noqa # usort: skip
|
2 |
+
from . import io, utils
|
3 |
+
|
4 |
+
|
5 |
+
__all__ = [
|
6 |
+
"io",
|
7 |
+
"utils",
|
8 |
+
]
|
MLPY/Lib/site-packages/torio/__pycache__/__init__.cpython-39.pyc
ADDED
Binary file (251 Bytes). View file
|
|
MLPY/Lib/site-packages/torio/_extension/__init__.py
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from .utils import _init_ffmpeg, _LazyImporter
|
2 |
+
|
3 |
+
|
4 |
+
_FFMPEG_EXT = None
|
5 |
+
|
6 |
+
|
7 |
+
def lazy_import_ffmpeg_ext():
|
8 |
+
"""Load FFmpeg integration based on availability in lazy manner"""
|
9 |
+
|
10 |
+
global _FFMPEG_EXT
|
11 |
+
if _FFMPEG_EXT is None:
|
12 |
+
_FFMPEG_EXT = _LazyImporter("_torio_ffmpeg", _init_ffmpeg)
|
13 |
+
return _FFMPEG_EXT
|
MLPY/Lib/site-packages/torio/_extension/__pycache__/__init__.cpython-39.pyc
ADDED
Binary file (466 Bytes). View file
|
|
MLPY/Lib/site-packages/torio/_extension/__pycache__/utils.cpython-39.pyc
ADDED
Binary file (5.2 kB). View file
|
|
MLPY/Lib/site-packages/torio/_extension/utils.py
ADDED
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import importlib
|
2 |
+
import logging
|
3 |
+
import os
|
4 |
+
import types
|
5 |
+
from pathlib import Path
|
6 |
+
|
7 |
+
import torch
|
8 |
+
|
9 |
+
_LG = logging.getLogger(__name__)
|
10 |
+
_LIB_DIR = Path(__file__).parent.parent / "lib"
|
11 |
+
|
12 |
+
|
13 |
+
class _LazyImporter(types.ModuleType):
|
14 |
+
"""Lazily import module/extension."""
|
15 |
+
|
16 |
+
def __init__(self, name, import_func):
|
17 |
+
super().__init__(name)
|
18 |
+
self.import_func = import_func
|
19 |
+
self.module = None
|
20 |
+
|
21 |
+
# Note:
|
22 |
+
# Python caches what was retrieved with `__getattr__`, so this method will not be
|
23 |
+
# called again for the same item.
|
24 |
+
def __getattr__(self, item):
|
25 |
+
self._import_once()
|
26 |
+
return getattr(self.module, item)
|
27 |
+
|
28 |
+
def __repr__(self):
|
29 |
+
if self.module is None:
|
30 |
+
return f"<module '{self.__module__}.{self.__class__.__name__}(\"{self.name}\")'>"
|
31 |
+
return repr(self.module)
|
32 |
+
|
33 |
+
def __dir__(self):
|
34 |
+
self._import_once()
|
35 |
+
return dir(self.module)
|
36 |
+
|
37 |
+
def _import_once(self):
|
38 |
+
if self.module is None:
|
39 |
+
self.module = self.import_func()
|
40 |
+
# Note:
|
41 |
+
# By attaching the module attributes to self,
|
42 |
+
# module attributes are directly accessible.
|
43 |
+
# This allows to avoid calling __getattr__ for every attribute access.
|
44 |
+
self.__dict__.update(self.module.__dict__)
|
45 |
+
|
46 |
+
def is_available(self):
|
47 |
+
try:
|
48 |
+
self._import_once()
|
49 |
+
except Exception:
|
50 |
+
return False
|
51 |
+
return True
|
52 |
+
|
53 |
+
|
54 |
+
def _get_lib_path(lib: str):
|
55 |
+
suffix = "pyd" if os.name == "nt" else "so"
|
56 |
+
path = _LIB_DIR / f"{lib}.{suffix}"
|
57 |
+
return path
|
58 |
+
|
59 |
+
|
60 |
+
def _load_lib(lib: str) -> bool:
|
61 |
+
"""Load extension module
|
62 |
+
|
63 |
+
Note:
|
64 |
+
In case `torio` is deployed with `pex` format, the library file
|
65 |
+
is not in a standard location.
|
66 |
+
In this case, we expect that `libtorio` is available somewhere
|
67 |
+
in the search path of dynamic loading mechanism, so that importing
|
68 |
+
`_torio` will have library loader find and load `libtorio`.
|
69 |
+
This is the reason why the function should not raising an error when the library
|
70 |
+
file is not found.
|
71 |
+
|
72 |
+
Returns:
|
73 |
+
bool:
|
74 |
+
True if the library file is found AND the library loaded without failure.
|
75 |
+
False if the library file is not found (like in the case where torio
|
76 |
+
is deployed with pex format, thus the shared library file is
|
77 |
+
in a non-standard location.).
|
78 |
+
If the library file is found but there is an issue loading the library,
|
79 |
+
(such as missing dependency) then this function raises the exception as-is.
|
80 |
+
|
81 |
+
Raises:
|
82 |
+
Exception:
|
83 |
+
If the library file is found, but there is an issue loading the library file,
|
84 |
+
(when underlying `ctype.DLL` throws an exception), this function will pass
|
85 |
+
the exception as-is, instead of catching it and returning bool.
|
86 |
+
The expected case is `OSError` thrown by `ctype.DLL` when a dynamic dependency
|
87 |
+
is not found.
|
88 |
+
This behavior was chosen because the expected failure case is not recoverable.
|
89 |
+
If a dependency is missing, then users have to install it.
|
90 |
+
"""
|
91 |
+
path = _get_lib_path(lib)
|
92 |
+
if not path.exists():
|
93 |
+
return False
|
94 |
+
torch.ops.load_library(path)
|
95 |
+
return True
|
96 |
+
|
97 |
+
|
98 |
+
_FFMPEG_VERS = ["6", "5", "4", ""]
|
99 |
+
|
100 |
+
|
101 |
+
def _find_versionsed_ffmpeg_extension(version: str):
|
102 |
+
ext = f"torio.lib._torio_ffmpeg{version}"
|
103 |
+
lib = f"libtorio_ffmpeg{version}"
|
104 |
+
|
105 |
+
if not importlib.util.find_spec(ext):
|
106 |
+
raise RuntimeError(f"FFmpeg{version} extension is not available.")
|
107 |
+
|
108 |
+
_load_lib(lib)
|
109 |
+
return importlib.import_module(ext)
|
110 |
+
|
111 |
+
|
112 |
+
def _find_ffmpeg_extension(ffmpeg_vers):
|
113 |
+
for ffmpeg_ver in ffmpeg_vers:
|
114 |
+
_LG.debug("Loading FFmpeg%s", ffmpeg_ver)
|
115 |
+
try:
|
116 |
+
ext = _find_versionsed_ffmpeg_extension(ffmpeg_ver)
|
117 |
+
_LG.debug("Successfully loaded FFmpeg%s", ffmpeg_ver)
|
118 |
+
return ext
|
119 |
+
except Exception:
|
120 |
+
_LG.debug("Failed to load FFmpeg%s extension.", ffmpeg_ver, exc_info=True)
|
121 |
+
continue
|
122 |
+
raise ImportError(
|
123 |
+
f"Failed to intialize FFmpeg extension. Tried versions: {ffmpeg_vers}. "
|
124 |
+
"Enable DEBUG logging to see more details about the error."
|
125 |
+
)
|
126 |
+
|
127 |
+
|
128 |
+
def _get_ffmpeg_versions():
|
129 |
+
ffmpeg_vers = _FFMPEG_VERS
|
130 |
+
# User override
|
131 |
+
if (ffmpeg_ver := os.environ.get("TORIO_USE_FFMPEG_VERSION")) is not None:
|
132 |
+
if ffmpeg_ver not in ffmpeg_vers:
|
133 |
+
raise ValueError(
|
134 |
+
f"The FFmpeg version '{ffmpeg_ver}' (read from TORIO_USE_FFMPEG_VERSION) "
|
135 |
+
f"is not one of supported values. Possible values are {ffmpeg_vers}"
|
136 |
+
)
|
137 |
+
ffmpeg_vers = [ffmpeg_ver]
|
138 |
+
return ffmpeg_vers
|
139 |
+
|
140 |
+
|
141 |
+
def _init_ffmpeg():
|
142 |
+
ffmpeg_vers = _get_ffmpeg_versions()
|
143 |
+
ext = _find_ffmpeg_extension(ffmpeg_vers)
|
144 |
+
ext.init()
|
145 |
+
if ext.get_log_level() > 8:
|
146 |
+
ext.set_log_level(8)
|
147 |
+
return ext
|
MLPY/Lib/site-packages/torio/io/__init__.py
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from ._streaming_media_decoder import StreamingMediaDecoder
|
2 |
+
from ._streaming_media_encoder import CodecConfig, StreamingMediaEncoder
|
3 |
+
|
4 |
+
|
5 |
+
__all__ = [
|
6 |
+
"StreamingMediaDecoder",
|
7 |
+
"CodecConfig",
|
8 |
+
"StreamingMediaEncoder",
|
9 |
+
]
|
MLPY/Lib/site-packages/torio/io/__pycache__/__init__.cpython-39.pyc
ADDED
Binary file (343 Bytes). View file
|
|
MLPY/Lib/site-packages/torio/io/__pycache__/_streaming_media_decoder.cpython-39.pyc
ADDED
Binary file (31.2 kB). View file
|
|
MLPY/Lib/site-packages/torio/io/__pycache__/_streaming_media_encoder.cpython-39.pyc
ADDED
Binary file (19.6 kB). View file
|
|
MLPY/Lib/site-packages/torio/io/_streaming_media_decoder.py
ADDED
@@ -0,0 +1,978 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from __future__ import annotations
|
2 |
+
|
3 |
+
import os
|
4 |
+
from dataclasses import dataclass
|
5 |
+
from pathlib import Path
|
6 |
+
from typing import BinaryIO, Dict, Iterator, Optional, Tuple, TypeVar, Union
|
7 |
+
|
8 |
+
import torch
|
9 |
+
import torio
|
10 |
+
from torch.utils._pytree import tree_map
|
11 |
+
|
12 |
+
ffmpeg_ext = torio._extension.lazy_import_ffmpeg_ext()
|
13 |
+
|
14 |
+
__all__ = [
|
15 |
+
"StreamingMediaDecoder",
|
16 |
+
]
|
17 |
+
|
18 |
+
|
19 |
+
@dataclass
|
20 |
+
class SourceStream:
|
21 |
+
"""The metadata of a source stream, returned by :meth:`~torio.io.StreamingMediaDecoder.get_src_stream_info`.
|
22 |
+
|
23 |
+
This class is used when representing streams of media type other than `audio` or `video`.
|
24 |
+
|
25 |
+
When source stream is `audio` or `video` type, :class:`SourceAudioStream` and
|
26 |
+
:class:`SourceVideoStream`, which reports additional media-specific attributes,
|
27 |
+
are used respectively.
|
28 |
+
"""
|
29 |
+
|
30 |
+
media_type: str
|
31 |
+
"""The type of the stream.
|
32 |
+
One of ``"audio"``, ``"video"``, ``"data"``, ``"subtitle"``, ``"attachment"`` and empty string.
|
33 |
+
|
34 |
+
.. note::
|
35 |
+
Only audio and video streams are supported for output.
|
36 |
+
.. note::
|
37 |
+
Still images, such as PNG and JPEG formats are reported as video.
|
38 |
+
"""
|
39 |
+
codec: str
|
40 |
+
"""Short name of the codec. Such as ``"pcm_s16le"`` and ``"h264"``."""
|
41 |
+
codec_long_name: str
|
42 |
+
"""Detailed name of the codec.
|
43 |
+
|
44 |
+
Such as "`PCM signed 16-bit little-endian`" and "`H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10`".
|
45 |
+
"""
|
46 |
+
format: Optional[str]
|
47 |
+
"""Media format. Such as ``"s16"`` and ``"yuv420p"``.
|
48 |
+
|
49 |
+
Commonly found audio values are;
|
50 |
+
|
51 |
+
- ``"u8"``, ``"u8p"``: Unsigned 8-bit unsigned interger.
|
52 |
+
- ``"s16"``, ``"s16p"``: 16-bit signed integer.
|
53 |
+
- ``"s32"``, ``"s32p"``: 32-bit signed integer.
|
54 |
+
- ``"flt"``, ``"fltp"``: 32-bit floating-point.
|
55 |
+
|
56 |
+
.. note::
|
57 |
+
|
58 |
+
`p` at the end indicates the format is `planar`.
|
59 |
+
Channels are grouped together instead of interspersed in memory.
|
60 |
+
"""
|
61 |
+
bit_rate: Optional[int]
|
62 |
+
"""Bit rate of the stream in bits-per-second.
|
63 |
+
This is an estimated values based on the initial few frames of the stream.
|
64 |
+
For container formats and variable bit rate, it can be 0.
|
65 |
+
"""
|
66 |
+
num_frames: Optional[int]
|
67 |
+
"""The number of frames in the stream"""
|
68 |
+
bits_per_sample: Optional[int]
|
69 |
+
"""This is the number of valid bits in each output sample.
|
70 |
+
For compressed format, it can be 0.
|
71 |
+
"""
|
72 |
+
metadata: Dict[str, str]
|
73 |
+
"""Metadata attached to the source stream."""
|
74 |
+
|
75 |
+
|
76 |
+
@dataclass
|
77 |
+
class SourceAudioStream(SourceStream):
|
78 |
+
"""The metadata of an audio source stream, returned by :meth:`~torio.io.StreamingMediaDecoder.get_src_stream_info`.
|
79 |
+
|
80 |
+
This class is used when representing audio stream.
|
81 |
+
|
82 |
+
In addition to the attributes reported by :class:`SourceStream`,
|
83 |
+
the following attributes are reported.
|
84 |
+
"""
|
85 |
+
|
86 |
+
sample_rate: float
|
87 |
+
"""Sample rate of the audio."""
|
88 |
+
num_channels: int
|
89 |
+
"""Number of channels."""
|
90 |
+
|
91 |
+
|
92 |
+
@dataclass
|
93 |
+
class SourceVideoStream(SourceStream):
|
94 |
+
"""The metadata of a video source stream, returned by :meth:`~torio.io.StreamingMediaDecoder.get_src_stream_info`.
|
95 |
+
|
96 |
+
This class is used when representing video stream.
|
97 |
+
|
98 |
+
In addition to the attributes reported by :class:`SourceStream`,
|
99 |
+
the following attributes are reported.
|
100 |
+
"""
|
101 |
+
|
102 |
+
width: int
|
103 |
+
"""Width of the video frame in pixel."""
|
104 |
+
height: int
|
105 |
+
"""Height of the video frame in pixel."""
|
106 |
+
frame_rate: float
|
107 |
+
"""Frame rate."""
|
108 |
+
|
109 |
+
|
110 |
+
def _parse_si(i):
|
111 |
+
media_type = i.media_type
|
112 |
+
if media_type == "audio":
|
113 |
+
return SourceAudioStream(
|
114 |
+
media_type=i.media_type,
|
115 |
+
codec=i.codec_name,
|
116 |
+
codec_long_name=i.codec_long_name,
|
117 |
+
format=i.format,
|
118 |
+
bit_rate=i.bit_rate,
|
119 |
+
num_frames=i.num_frames,
|
120 |
+
bits_per_sample=i.bits_per_sample,
|
121 |
+
metadata=i.metadata,
|
122 |
+
sample_rate=i.sample_rate,
|
123 |
+
num_channels=i.num_channels,
|
124 |
+
)
|
125 |
+
if media_type == "video":
|
126 |
+
return SourceVideoStream(
|
127 |
+
media_type=i.media_type,
|
128 |
+
codec=i.codec_name,
|
129 |
+
codec_long_name=i.codec_long_name,
|
130 |
+
format=i.format,
|
131 |
+
bit_rate=i.bit_rate,
|
132 |
+
num_frames=i.num_frames,
|
133 |
+
bits_per_sample=i.bits_per_sample,
|
134 |
+
metadata=i.metadata,
|
135 |
+
width=i.width,
|
136 |
+
height=i.height,
|
137 |
+
frame_rate=i.frame_rate,
|
138 |
+
)
|
139 |
+
return SourceStream(
|
140 |
+
media_type=i.media_type,
|
141 |
+
codec=i.codec_name,
|
142 |
+
codec_long_name=i.codec_long_name,
|
143 |
+
format=None,
|
144 |
+
bit_rate=None,
|
145 |
+
num_frames=None,
|
146 |
+
bits_per_sample=None,
|
147 |
+
metadata=i.metadata,
|
148 |
+
)
|
149 |
+
|
150 |
+
|
151 |
+
@dataclass
|
152 |
+
class OutputStream:
|
153 |
+
"""Output stream configured on :class:`StreamingMediaDecoder`,
|
154 |
+
returned by :meth:`~torio.io.StreamingMediaDecoder.get_out_stream_info`.
|
155 |
+
"""
|
156 |
+
|
157 |
+
source_index: int
|
158 |
+
"""Index of the source stream that this output stream is connected."""
|
159 |
+
filter_description: str
|
160 |
+
"""Description of filter graph applied to the source stream."""
|
161 |
+
media_type: str
|
162 |
+
"""The type of the stream. ``"audio"`` or ``"video"``."""
|
163 |
+
format: str
|
164 |
+
"""Media format. Such as ``"s16"`` and ``"yuv420p"``.
|
165 |
+
|
166 |
+
Commonly found audio values are;
|
167 |
+
|
168 |
+
- ``"u8"``, ``"u8p"``: Unsigned 8-bit unsigned interger.
|
169 |
+
- ``"s16"``, ``"s16p"``: 16-bit signed integer.
|
170 |
+
- ``"s32"``, ``"s32p"``: 32-bit signed integer.
|
171 |
+
- ``"flt"``, ``"fltp"``: 32-bit floating-point.
|
172 |
+
|
173 |
+
.. note::
|
174 |
+
|
175 |
+
`p` at the end indicates the format is `planar`.
|
176 |
+
Channels are grouped together instead of interspersed in memory."""
|
177 |
+
|
178 |
+
|
179 |
+
@dataclass
|
180 |
+
class OutputAudioStream(OutputStream):
|
181 |
+
"""Information about an audio output stream configured with
|
182 |
+
:meth:`~torio.io.StreamingMediaDecoder.add_audio_stream` or
|
183 |
+
:meth:`~torio.io.StreamingMediaDecoder.add_basic_audio_stream`.
|
184 |
+
|
185 |
+
In addition to the attributes reported by :class:`OutputStream`,
|
186 |
+
the following attributes are reported.
|
187 |
+
"""
|
188 |
+
|
189 |
+
sample_rate: float
|
190 |
+
"""Sample rate of the audio."""
|
191 |
+
num_channels: int
|
192 |
+
"""Number of channels."""
|
193 |
+
|
194 |
+
|
195 |
+
@dataclass
|
196 |
+
class OutputVideoStream(OutputStream):
|
197 |
+
"""Information about a video output stream configured with
|
198 |
+
:meth:`~torio.io.StreamingMediaDecoder.add_video_stream` or
|
199 |
+
:meth:`~torio.io.StreamingMediaDecoder.add_basic_video_stream`.
|
200 |
+
|
201 |
+
In addition to the attributes reported by :class:`OutputStream`,
|
202 |
+
the following attributes are reported.
|
203 |
+
"""
|
204 |
+
|
205 |
+
width: int
|
206 |
+
"""Width of the video frame in pixel."""
|
207 |
+
height: int
|
208 |
+
"""Height of the video frame in pixel."""
|
209 |
+
frame_rate: float
|
210 |
+
"""Frame rate."""
|
211 |
+
|
212 |
+
|
213 |
+
def _parse_oi(i):
|
214 |
+
media_type = i.media_type
|
215 |
+
if media_type == "audio":
|
216 |
+
return OutputAudioStream(
|
217 |
+
source_index=i.source_index,
|
218 |
+
filter_description=i.filter_description,
|
219 |
+
media_type=i.media_type,
|
220 |
+
format=i.format,
|
221 |
+
sample_rate=i.sample_rate,
|
222 |
+
num_channels=i.num_channels,
|
223 |
+
)
|
224 |
+
if media_type == "video":
|
225 |
+
return OutputVideoStream(
|
226 |
+
source_index=i.source_index,
|
227 |
+
filter_description=i.filter_description,
|
228 |
+
media_type=i.media_type,
|
229 |
+
format=i.format,
|
230 |
+
width=i.width,
|
231 |
+
height=i.height,
|
232 |
+
frame_rate=i.frame_rate,
|
233 |
+
)
|
234 |
+
raise ValueError(f"Unexpected media_type: {i.media_type}({i})")
|
235 |
+
|
236 |
+
|
237 |
+
def _get_afilter_desc(sample_rate: Optional[int], fmt: Optional[str], num_channels: Optional[int]):
|
238 |
+
descs = []
|
239 |
+
if sample_rate is not None:
|
240 |
+
descs.append(f"aresample={sample_rate}")
|
241 |
+
if fmt is not None or num_channels is not None:
|
242 |
+
parts = []
|
243 |
+
if fmt is not None:
|
244 |
+
parts.append(f"sample_fmts={fmt}")
|
245 |
+
if num_channels is not None:
|
246 |
+
parts.append(f"channel_layouts={num_channels}c")
|
247 |
+
descs.append(f"aformat={':'.join(parts)}")
|
248 |
+
return ",".join(descs) if descs else None
|
249 |
+
|
250 |
+
|
251 |
+
def _get_vfilter_desc(frame_rate: Optional[float], width: Optional[int], height: Optional[int], fmt: Optional[str]):
|
252 |
+
descs = []
|
253 |
+
if frame_rate is not None:
|
254 |
+
descs.append(f"fps={frame_rate}")
|
255 |
+
scales = []
|
256 |
+
if width is not None:
|
257 |
+
scales.append(f"width={width}")
|
258 |
+
if height is not None:
|
259 |
+
scales.append(f"height={height}")
|
260 |
+
if scales:
|
261 |
+
descs.append(f"scale={':'.join(scales)}")
|
262 |
+
if fmt is not None:
|
263 |
+
descs.append(f"format=pix_fmts={fmt}")
|
264 |
+
return ",".join(descs) if descs else None
|
265 |
+
|
266 |
+
|
267 |
+
# Base class for ChunkTensor
|
268 |
+
# Based off of TrivialTensorViaComposition
|
269 |
+
# https://github.com/albanD/subclass_zoo/blob/0eeb1d68fb59879029c610bc407f2997ae43ba0a/trivial_tensors.py#L83
|
270 |
+
class ChunkTensorBase(torch.Tensor):
|
271 |
+
__torch_function__ = torch._C._disabled_torch_function_impl
|
272 |
+
|
273 |
+
@staticmethod
|
274 |
+
def __new__(cls, _elem, *_):
|
275 |
+
return super().__new__(cls, _elem)
|
276 |
+
|
277 |
+
@classmethod
|
278 |
+
def __torch_dispatch__(cls, func, _, args=(), kwargs=None):
|
279 |
+
def unwrap(t):
|
280 |
+
return t._elem if isinstance(t, cls) else t
|
281 |
+
|
282 |
+
return func(*tree_map(unwrap, args), **tree_map(unwrap, kwargs))
|
283 |
+
|
284 |
+
|
285 |
+
@dataclass
|
286 |
+
class ChunkTensor(ChunkTensorBase):
|
287 |
+
"""Decoded media frames with metadata.
|
288 |
+
|
289 |
+
The instance of this class represents the decoded video/audio frames with
|
290 |
+
metadata, and the instance itself behave like :py:class:`~torch.Tensor`.
|
291 |
+
|
292 |
+
Client codes can pass instance of this class as-if it's
|
293 |
+
:py:class:`~torch.Tensor` class, or call the methods defined on
|
294 |
+
:py:class:`~torch.Tensor` class.
|
295 |
+
|
296 |
+
Example:
|
297 |
+
>>> # Define input streams
|
298 |
+
>>> reader = StreamingMediaDecoder(...)
|
299 |
+
>>> reader.add_audio_stream(frames_per_chunk=4000, sample_rate=8000)
|
300 |
+
>>> reader.add_video_stream(frames_per_chunk=7, frame_rate=28)
|
301 |
+
>>> # Decode the streams and fetch frames
|
302 |
+
>>> reader.fill_buffer()
|
303 |
+
>>> audio_chunk, video_chunk = reader.pop_chunks()
|
304 |
+
|
305 |
+
>>> # Access metadata
|
306 |
+
>>> (audio_chunk.pts, video_chunks.pts)
|
307 |
+
(0.0, 0.0)
|
308 |
+
>>>
|
309 |
+
>>> # The second time the PTS is different
|
310 |
+
>>> reader.fill_buffer()
|
311 |
+
>>> audio_chunk, video_chunk = reader.pop_chunks()
|
312 |
+
>>> (audio_chunk.pts, video_chunks.pts)
|
313 |
+
(0.5, 0.25)
|
314 |
+
|
315 |
+
>>> # Call PyTorch ops on chunk
|
316 |
+
>>> audio_chunk.shape
|
317 |
+
torch.Size([4000, 2]
|
318 |
+
>>> power = torch.pow(video_chunk, 2)
|
319 |
+
>>>
|
320 |
+
>>> # the result is a plain torch.Tensor class
|
321 |
+
>>> type(power)
|
322 |
+
<class 'torch.Tensor'>
|
323 |
+
>>>
|
324 |
+
>>> # Metadata is not available on the result
|
325 |
+
>>> power.pts
|
326 |
+
AttributeError: 'Tensor' object has no attribute 'pts'
|
327 |
+
"""
|
328 |
+
|
329 |
+
# Keep it private for now
|
330 |
+
_elem: torch.Tensor
|
331 |
+
|
332 |
+
pts: float
|
333 |
+
"""Presentation time stamp of the first frame in the chunk.
|
334 |
+
|
335 |
+
Unit: second.
|
336 |
+
"""
|
337 |
+
|
338 |
+
|
339 |
+
def _format_doc(**kwargs):
|
340 |
+
def decorator(obj):
|
341 |
+
obj.__doc__ = obj.__doc__.format(**kwargs)
|
342 |
+
return obj
|
343 |
+
|
344 |
+
return decorator
|
345 |
+
|
346 |
+
|
347 |
+
_frames_per_chunk = """Number of frames returned as one chunk.
|
348 |
+
If the source stream is exhausted before enough frames are buffered,
|
349 |
+
then the chunk is returned as-is.
|
350 |
+
|
351 |
+
Providing ``-1`` disables chunking and :py:func:`pop_chunks` method
|
352 |
+
will concatenate all the buffered frames and return it."""
|
353 |
+
|
354 |
+
_buffer_chunk_size = """Internal buffer size.
|
355 |
+
When the number of chunks buffered exceeds this number, old frames are
|
356 |
+
dropped. For example, if ``frames_per_chunk`` is 5 and ``buffer_chunk_size`` is
|
357 |
+
3, then frames older than ``15`` are dropped.
|
358 |
+
Providing ``-1`` disables this behavior.
|
359 |
+
|
360 |
+
Default: ``3``."""
|
361 |
+
|
362 |
+
_audio_stream_index = """The source audio stream index.
|
363 |
+
If omitted, :py:attr:`default_audio_stream` is used."""
|
364 |
+
|
365 |
+
|
366 |
+
_video_stream_index = """The source video stream index.
|
367 |
+
If omitted, :py:attr:`default_video_stream` is used."""
|
368 |
+
|
369 |
+
_decoder = """The name of the decoder to be used.
|
370 |
+
When provided, use the specified decoder instead of the default one.
|
371 |
+
|
372 |
+
To list the available decoders, please use
|
373 |
+
:py:func:`~torio.utils.ffmpeg_utils.get_audio_decoders` for audio, and
|
374 |
+
:py:func:`~torio.utils.ffmpeg_utils.get_video_decoders` for video.
|
375 |
+
|
376 |
+
Default: ``None``."""
|
377 |
+
|
378 |
+
_decoder_option = """Options passed to decoder.
|
379 |
+
Mapping from str to str. (Default: ``None``)
|
380 |
+
|
381 |
+
To list decoder options for a decoder, you can use
|
382 |
+
``ffmpeg -h decoder=<DECODER>`` command.
|
383 |
+
|
384 |
+
|
|
385 |
+
|
386 |
+
In addition to decoder-specific options, you can also pass options related
|
387 |
+
to multithreading. They are effective only if the decoder support them.
|
388 |
+
If neither of them are provided, StreamingMediaDecoder defaults to single thread.
|
389 |
+
|
390 |
+
``"threads"``: The number of threads (in str).
|
391 |
+
Providing the value ``"0"`` will let FFmpeg decides based on its heuristics.
|
392 |
+
|
393 |
+
``"thread_type"``: Which multithreading method to use.
|
394 |
+
The valid values are ``"frame"`` or ``"slice"``.
|
395 |
+
Note that each decoder supports different set of methods.
|
396 |
+
If not provided, a default value is used.
|
397 |
+
|
398 |
+
- ``"frame"``: Decode more than one frame at once.
|
399 |
+
Each thread handles one frame.
|
400 |
+
This will increase decoding delay by one frame per thread
|
401 |
+
- ``"slice"``: Decode more than one part of a single frame at once.
|
402 |
+
|
403 |
+
|
|
404 |
+
"""
|
405 |
+
|
406 |
+
|
407 |
+
_hw_accel = """Enable hardware acceleration.
|
408 |
+
|
409 |
+
When video is decoded on CUDA hardware, for example
|
410 |
+
`decoder="h264_cuvid"`, passing CUDA device indicator to `hw_accel`
|
411 |
+
(i.e. `hw_accel="cuda:0"`) will make StreamingMediaDecoder place the resulting
|
412 |
+
frames directly on the specified CUDA device as CUDA tensor.
|
413 |
+
|
414 |
+
If `None`, the frame will be moved to CPU memory.
|
415 |
+
Default: ``None``."""
|
416 |
+
|
417 |
+
|
418 |
+
_format_audio_args = _format_doc(
|
419 |
+
frames_per_chunk=_frames_per_chunk,
|
420 |
+
buffer_chunk_size=_buffer_chunk_size,
|
421 |
+
stream_index=_audio_stream_index,
|
422 |
+
decoder=_decoder,
|
423 |
+
decoder_option=_decoder_option,
|
424 |
+
)
|
425 |
+
|
426 |
+
|
427 |
+
_format_video_args = _format_doc(
|
428 |
+
frames_per_chunk=_frames_per_chunk,
|
429 |
+
buffer_chunk_size=_buffer_chunk_size,
|
430 |
+
stream_index=_video_stream_index,
|
431 |
+
decoder=_decoder,
|
432 |
+
decoder_option=_decoder_option,
|
433 |
+
hw_accel=_hw_accel,
|
434 |
+
)
|
435 |
+
|
436 |
+
|
437 |
+
InputStreamTypes = TypeVar("InputStream", bound=SourceStream)
|
438 |
+
OutputStreamTypes = TypeVar("OutputStream", bound=OutputStream)
|
439 |
+
|
440 |
+
|
441 |
+
class StreamingMediaDecoder:
|
442 |
+
"""Fetch and decode audio/video streams chunk by chunk.
|
443 |
+
|
444 |
+
For the detailed usage of this class, please refer to the tutorial.
|
445 |
+
|
446 |
+
Args:
|
447 |
+
src (str, path-like, bytes or file-like object): The media source.
|
448 |
+
If string-type, it must be a resource indicator that FFmpeg can
|
449 |
+
handle. This includes a file path, URL, device identifier or
|
450 |
+
filter expression. The supported value depends on the FFmpeg found
|
451 |
+
in the system.
|
452 |
+
|
453 |
+
If bytes, it must be an encoded media data in contiguous memory.
|
454 |
+
|
455 |
+
If file-like object, it must support `read` method with the signature
|
456 |
+
`read(size: int) -> bytes`.
|
457 |
+
Additionally, if the file-like object has `seek` method, it uses
|
458 |
+
the method when parsing media metadata. This improves the reliability
|
459 |
+
of codec detection. The signagure of `seek` method must be
|
460 |
+
`seek(offset: int, whence: int) -> int`.
|
461 |
+
|
462 |
+
Please refer to the following for the expected signature and behavior
|
463 |
+
of `read` and `seek` method.
|
464 |
+
|
465 |
+
- https://docs.python.org/3/library/io.html#io.BufferedIOBase.read
|
466 |
+
- https://docs.python.org/3/library/io.html#io.IOBase.seek
|
467 |
+
|
468 |
+
format (str or None, optional):
|
469 |
+
Override the input format, or specify the source sound device.
|
470 |
+
Default: ``None`` (no override nor device input).
|
471 |
+
|
472 |
+
This argument serves two different usecases.
|
473 |
+
|
474 |
+
1) Override the source format.
|
475 |
+
This is useful when the input data do not contain a header.
|
476 |
+
|
477 |
+
2) Specify the input source device.
|
478 |
+
This allows to load media stream from hardware devices,
|
479 |
+
such as microphone, camera and screen, or a virtual device.
|
480 |
+
|
481 |
+
|
482 |
+
.. note::
|
483 |
+
|
484 |
+
This option roughly corresponds to ``-f`` option of ``ffmpeg`` command.
|
485 |
+
Please refer to the ffmpeg documentations for the possible values.
|
486 |
+
|
487 |
+
https://ffmpeg.org/ffmpeg-formats.html#Demuxers
|
488 |
+
|
489 |
+
Please use :py:func:`~torio.utils.ffmpeg_utils.get_demuxers` to list the
|
490 |
+
demultiplexers available in the current environment.
|
491 |
+
|
492 |
+
For device access, the available values vary based on hardware (AV device) and
|
493 |
+
software configuration (ffmpeg build).
|
494 |
+
|
495 |
+
https://ffmpeg.org/ffmpeg-devices.html#Input-Devices
|
496 |
+
|
497 |
+
Please use :py:func:`~torio.utils.ffmpeg_utils.get_input_devices` to list
|
498 |
+
the input devices available in the current environment.
|
499 |
+
|
500 |
+
option (dict of str to str, optional):
|
501 |
+
Custom option passed when initializing format context (opening source).
|
502 |
+
|
503 |
+
You can use this argument to change the input source before it is passed to decoder.
|
504 |
+
|
505 |
+
Default: ``None``.
|
506 |
+
|
507 |
+
buffer_size (int):
|
508 |
+
The internal buffer size in byte. Used only when `src` is file-like object.
|
509 |
+
|
510 |
+
Default: `4096`.
|
511 |
+
"""
|
512 |
+
|
513 |
+
def __init__(
|
514 |
+
self,
|
515 |
+
src: Union[str, Path, BinaryIO],
|
516 |
+
format: Optional[str] = None,
|
517 |
+
option: Optional[Dict[str, str]] = None,
|
518 |
+
buffer_size: int = 4096,
|
519 |
+
):
|
520 |
+
self.src = src
|
521 |
+
if isinstance(src, bytes):
|
522 |
+
self._be = ffmpeg_ext.StreamingMediaDecoderBytes(src, format, option, buffer_size)
|
523 |
+
elif hasattr(src, "read"):
|
524 |
+
self._be = ffmpeg_ext.StreamingMediaDecoderFileObj(src, format, option, buffer_size)
|
525 |
+
else:
|
526 |
+
self._be = ffmpeg_ext.StreamingMediaDecoder(os.path.normpath(src), format, option)
|
527 |
+
|
528 |
+
i = self._be.find_best_audio_stream()
|
529 |
+
self._default_audio_stream = None if i < 0 else i
|
530 |
+
i = self._be.find_best_video_stream()
|
531 |
+
self._default_video_stream = None if i < 0 else i
|
532 |
+
|
533 |
+
@property
|
534 |
+
def num_src_streams(self):
|
535 |
+
"""Number of streams found in the provided media source.
|
536 |
+
|
537 |
+
:type: int
|
538 |
+
"""
|
539 |
+
return self._be.num_src_streams()
|
540 |
+
|
541 |
+
@property
|
542 |
+
def num_out_streams(self):
|
543 |
+
"""Number of output streams configured by client code.
|
544 |
+
|
545 |
+
:type: int
|
546 |
+
"""
|
547 |
+
return self._be.num_out_streams()
|
548 |
+
|
549 |
+
@property
|
550 |
+
def default_audio_stream(self):
|
551 |
+
"""The index of default audio stream. ``None`` if there is no audio stream
|
552 |
+
|
553 |
+
:type: Optional[int]
|
554 |
+
"""
|
555 |
+
return self._default_audio_stream
|
556 |
+
|
557 |
+
@property
|
558 |
+
def default_video_stream(self):
|
559 |
+
"""The index of default video stream. ``None`` if there is no video stream
|
560 |
+
|
561 |
+
:type: Optional[int]
|
562 |
+
"""
|
563 |
+
return self._default_video_stream
|
564 |
+
|
565 |
+
def get_metadata(self) -> Dict[str, str]:
|
566 |
+
"""Get the metadata of the source media.
|
567 |
+
|
568 |
+
Returns:
|
569 |
+
dict
|
570 |
+
"""
|
571 |
+
return self._be.get_metadata()
|
572 |
+
|
573 |
+
def get_src_stream_info(self, i: int) -> InputStreamTypes:
|
574 |
+
"""Get the metadata of source stream
|
575 |
+
|
576 |
+
Args:
|
577 |
+
i (int): Stream index.
|
578 |
+
Returns:
|
579 |
+
InputStreamTypes:
|
580 |
+
Information about the source stream.
|
581 |
+
If the source stream is audio type, then
|
582 |
+
:class:`~torio.io._stream_reader.SourceAudioStream` is returned.
|
583 |
+
If it is video type, then
|
584 |
+
:class:`~torio.io._stream_reader.SourceVideoStream` is returned.
|
585 |
+
Otherwise :class:`~torio.io._stream_reader.SourceStream` class is returned.
|
586 |
+
"""
|
587 |
+
return _parse_si(self._be.get_src_stream_info(i))
|
588 |
+
|
589 |
+
def get_out_stream_info(self, i: int) -> OutputStreamTypes:
|
590 |
+
"""Get the metadata of output stream
|
591 |
+
|
592 |
+
Args:
|
593 |
+
i (int): Stream index.
|
594 |
+
Returns:
|
595 |
+
OutputStreamTypes
|
596 |
+
Information about the output stream.
|
597 |
+
If the output stream is audio type, then
|
598 |
+
:class:`~torio.io._stream_reader.OutputAudioStream` is returned.
|
599 |
+
If it is video type, then
|
600 |
+
:class:`~torio.io._stream_reader.OutputVideoStream` is returned.
|
601 |
+
"""
|
602 |
+
info = self._be.get_out_stream_info(i)
|
603 |
+
return _parse_oi(info)
|
604 |
+
|
605 |
+
def seek(self, timestamp: float, mode: str = "precise"):
|
606 |
+
"""Seek the stream to the given timestamp [second]
|
607 |
+
|
608 |
+
Args:
|
609 |
+
timestamp (float): Target time in second.
|
610 |
+
mode (str): Controls how seek is done.
|
611 |
+
Valid choices are;
|
612 |
+
|
613 |
+
* "key": Seek into the nearest key frame before the given timestamp.
|
614 |
+
* "any": Seek into any frame (including non-key frames) before the given timestamp.
|
615 |
+
* "precise": First seek into the nearest key frame before the given timestamp, then
|
616 |
+
decode frames until it reaches the closes frame to the given timestamp.
|
617 |
+
|
618 |
+
Note:
|
619 |
+
All the modes invalidate and reset the internal state of decoder.
|
620 |
+
When using "any" mode and if it ends up seeking into non-key frame,
|
621 |
+
the image decoded may be invalid due to lack of key frame.
|
622 |
+
Using "precise" will workaround this issue by decoding frames from previous
|
623 |
+
key frame, but will be slower.
|
624 |
+
"""
|
625 |
+
modes = {
|
626 |
+
"key": 0,
|
627 |
+
"any": 1,
|
628 |
+
"precise": 2,
|
629 |
+
}
|
630 |
+
if mode not in modes:
|
631 |
+
raise ValueError(f"The value of mode must be one of {list(modes.keys())}. Found: {mode}")
|
632 |
+
self._be.seek(timestamp, modes[mode])
|
633 |
+
|
634 |
+
@_format_audio_args
|
635 |
+
def add_basic_audio_stream(
|
636 |
+
self,
|
637 |
+
frames_per_chunk: int,
|
638 |
+
buffer_chunk_size: int = 3,
|
639 |
+
*,
|
640 |
+
stream_index: Optional[int] = None,
|
641 |
+
decoder: Optional[str] = None,
|
642 |
+
decoder_option: Optional[Dict[str, str]] = None,
|
643 |
+
format: Optional[str] = "fltp",
|
644 |
+
sample_rate: Optional[int] = None,
|
645 |
+
num_channels: Optional[int] = None,
|
646 |
+
):
|
647 |
+
"""Add output audio stream
|
648 |
+
|
649 |
+
Args:
|
650 |
+
frames_per_chunk (int): {frames_per_chunk}
|
651 |
+
|
652 |
+
buffer_chunk_size (int, optional): {buffer_chunk_size}
|
653 |
+
|
654 |
+
stream_index (int or None, optional): {stream_index}
|
655 |
+
|
656 |
+
decoder (str or None, optional): {decoder}
|
657 |
+
|
658 |
+
decoder_option (dict or None, optional): {decoder_option}
|
659 |
+
|
660 |
+
format (str, optional): Output sample format (precision).
|
661 |
+
|
662 |
+
If ``None``, the output chunk has dtype corresponding to
|
663 |
+
the precision of the source audio.
|
664 |
+
|
665 |
+
Otherwise, the sample is converted and the output dtype is changed
|
666 |
+
as following.
|
667 |
+
|
668 |
+
- ``"u8p"``: The output is ``torch.uint8`` type.
|
669 |
+
- ``"s16p"``: The output is ``torch.int16`` type.
|
670 |
+
- ``"s32p"``: The output is ``torch.int32`` type.
|
671 |
+
- ``"s64p"``: The output is ``torch.int64`` type.
|
672 |
+
- ``"fltp"``: The output is ``torch.float32`` type.
|
673 |
+
- ``"dblp"``: The output is ``torch.float64`` type.
|
674 |
+
|
675 |
+
Default: ``"fltp"``.
|
676 |
+
|
677 |
+
sample_rate (int or None, optional): If provided, resample the audio.
|
678 |
+
|
679 |
+
num_channels (int, or None, optional): If provided, change the number of channels.
|
680 |
+
"""
|
681 |
+
self.add_audio_stream(
|
682 |
+
frames_per_chunk,
|
683 |
+
buffer_chunk_size,
|
684 |
+
stream_index=stream_index,
|
685 |
+
decoder=decoder,
|
686 |
+
decoder_option=decoder_option,
|
687 |
+
filter_desc=_get_afilter_desc(sample_rate, format, num_channels),
|
688 |
+
)
|
689 |
+
|
690 |
+
@_format_video_args
|
691 |
+
def add_basic_video_stream(
|
692 |
+
self,
|
693 |
+
frames_per_chunk: int,
|
694 |
+
buffer_chunk_size: int = 3,
|
695 |
+
*,
|
696 |
+
stream_index: Optional[int] = None,
|
697 |
+
decoder: Optional[str] = None,
|
698 |
+
decoder_option: Optional[Dict[str, str]] = None,
|
699 |
+
format: Optional[str] = "rgb24",
|
700 |
+
frame_rate: Optional[int] = None,
|
701 |
+
width: Optional[int] = None,
|
702 |
+
height: Optional[int] = None,
|
703 |
+
hw_accel: Optional[str] = None,
|
704 |
+
):
|
705 |
+
"""Add output video stream
|
706 |
+
|
707 |
+
Args:
|
708 |
+
frames_per_chunk (int): {frames_per_chunk}
|
709 |
+
|
710 |
+
buffer_chunk_size (int, optional): {buffer_chunk_size}
|
711 |
+
|
712 |
+
stream_index (int or None, optional): {stream_index}
|
713 |
+
|
714 |
+
decoder (str or None, optional): {decoder}
|
715 |
+
|
716 |
+
decoder_option (dict or None, optional): {decoder_option}
|
717 |
+
|
718 |
+
format (str, optional): Change the format of image channels. Valid values are,
|
719 |
+
|
720 |
+
- ``"rgb24"``: 8 bits * 3 channels (R, G, B)
|
721 |
+
- ``"bgr24"``: 8 bits * 3 channels (B, G, R)
|
722 |
+
- ``"yuv420p"``: 8 bits * 3 channels (Y, U, V)
|
723 |
+
- ``"gray"``: 8 bits * 1 channels
|
724 |
+
|
725 |
+
Default: ``"rgb24"``.
|
726 |
+
|
727 |
+
frame_rate (int or None, optional): If provided, change the frame rate.
|
728 |
+
|
729 |
+
width (int or None, optional): If provided, change the image width. Unit: Pixel.
|
730 |
+
|
731 |
+
height (int or None, optional): If provided, change the image height. Unit: Pixel.
|
732 |
+
|
733 |
+
hw_accel (str or None, optional): {hw_accel}
|
734 |
+
"""
|
735 |
+
self.add_video_stream(
|
736 |
+
frames_per_chunk,
|
737 |
+
buffer_chunk_size,
|
738 |
+
stream_index=stream_index,
|
739 |
+
decoder=decoder,
|
740 |
+
decoder_option=decoder_option,
|
741 |
+
filter_desc=_get_vfilter_desc(frame_rate, width, height, format),
|
742 |
+
hw_accel=hw_accel,
|
743 |
+
)
|
744 |
+
|
745 |
+
@_format_audio_args
|
746 |
+
def add_audio_stream(
|
747 |
+
self,
|
748 |
+
frames_per_chunk: int,
|
749 |
+
buffer_chunk_size: int = 3,
|
750 |
+
*,
|
751 |
+
stream_index: Optional[int] = None,
|
752 |
+
decoder: Optional[str] = None,
|
753 |
+
decoder_option: Optional[Dict[str, str]] = None,
|
754 |
+
filter_desc: Optional[str] = None,
|
755 |
+
):
|
756 |
+
"""Add output audio stream
|
757 |
+
|
758 |
+
Args:
|
759 |
+
frames_per_chunk (int): {frames_per_chunk}
|
760 |
+
|
761 |
+
buffer_chunk_size (int, optional): {buffer_chunk_size}
|
762 |
+
|
763 |
+
stream_index (int or None, optional): {stream_index}
|
764 |
+
|
765 |
+
decoder (str or None, optional): {decoder}
|
766 |
+
|
767 |
+
decoder_option (dict or None, optional): {decoder_option}
|
768 |
+
|
769 |
+
filter_desc (str or None, optional): Filter description.
|
770 |
+
The list of available filters can be found at
|
771 |
+
https://ffmpeg.org/ffmpeg-filters.html
|
772 |
+
Note that complex filters are not supported.
|
773 |
+
|
774 |
+
"""
|
775 |
+
i = self.default_audio_stream if stream_index is None else stream_index
|
776 |
+
if i is None:
|
777 |
+
raise RuntimeError("There is no audio stream.")
|
778 |
+
self._be.add_audio_stream(
|
779 |
+
i,
|
780 |
+
frames_per_chunk,
|
781 |
+
buffer_chunk_size,
|
782 |
+
filter_desc,
|
783 |
+
decoder,
|
784 |
+
decoder_option or {},
|
785 |
+
)
|
786 |
+
|
787 |
+
@_format_video_args
|
788 |
+
def add_video_stream(
|
789 |
+
self,
|
790 |
+
frames_per_chunk: int,
|
791 |
+
buffer_chunk_size: int = 3,
|
792 |
+
*,
|
793 |
+
stream_index: Optional[int] = None,
|
794 |
+
decoder: Optional[str] = None,
|
795 |
+
decoder_option: Optional[Dict[str, str]] = None,
|
796 |
+
filter_desc: Optional[str] = None,
|
797 |
+
hw_accel: Optional[str] = None,
|
798 |
+
):
|
799 |
+
"""Add output video stream
|
800 |
+
|
801 |
+
Args:
|
802 |
+
frames_per_chunk (int): {frames_per_chunk}
|
803 |
+
|
804 |
+
buffer_chunk_size (int, optional): {buffer_chunk_size}
|
805 |
+
|
806 |
+
stream_index (int or None, optional): {stream_index}
|
807 |
+
|
808 |
+
decoder (str or None, optional): {decoder}
|
809 |
+
|
810 |
+
decoder_option (dict or None, optional): {decoder_option}
|
811 |
+
|
812 |
+
hw_accel (str or None, optional): {hw_accel}
|
813 |
+
|
814 |
+
filter_desc (str or None, optional): Filter description.
|
815 |
+
The list of available filters can be found at
|
816 |
+
https://ffmpeg.org/ffmpeg-filters.html
|
817 |
+
Note that complex filters are not supported.
|
818 |
+
"""
|
819 |
+
i = self.default_video_stream if stream_index is None else stream_index
|
820 |
+
if i is None:
|
821 |
+
raise RuntimeError("There is no video stream.")
|
822 |
+
self._be.add_video_stream(
|
823 |
+
i,
|
824 |
+
frames_per_chunk,
|
825 |
+
buffer_chunk_size,
|
826 |
+
filter_desc,
|
827 |
+
decoder,
|
828 |
+
decoder_option or {},
|
829 |
+
hw_accel,
|
830 |
+
)
|
831 |
+
|
832 |
+
def remove_stream(self, i: int):
|
833 |
+
"""Remove an output stream.
|
834 |
+
|
835 |
+
Args:
|
836 |
+
i (int): Index of the output stream to be removed.
|
837 |
+
"""
|
838 |
+
self._be.remove_stream(i)
|
839 |
+
|
840 |
+
def process_packet(self, timeout: Optional[float] = None, backoff: float = 10.0) -> int:
|
841 |
+
"""Read the source media and process one packet.
|
842 |
+
|
843 |
+
If a packet is read successfully, then the data in the packet will
|
844 |
+
be decoded and passed to corresponding output stream processors.
|
845 |
+
|
846 |
+
If the packet belongs to a source stream that is not connected to
|
847 |
+
an output stream, then the data are discarded.
|
848 |
+
|
849 |
+
When the source reaches EOF, then it triggers all the output stream
|
850 |
+
processors to enter drain mode. All the output stream processors
|
851 |
+
flush the pending frames.
|
852 |
+
|
853 |
+
Args:
|
854 |
+
timeout (float or None, optional): Timeout in milli seconds.
|
855 |
+
|
856 |
+
This argument changes the retry behavior when it failed to
|
857 |
+
process a packet due to the underlying media resource being
|
858 |
+
temporarily unavailable.
|
859 |
+
|
860 |
+
When using a media device such as a microphone, there are cases
|
861 |
+
where the underlying buffer is not ready.
|
862 |
+
Calling this function in such case would cause the system to report
|
863 |
+
`EAGAIN (resource temporarily unavailable)`.
|
864 |
+
|
865 |
+
* ``>=0``: Keep retrying until the given time passes.
|
866 |
+
|
867 |
+
* ``0<``: Keep retrying forever.
|
868 |
+
|
869 |
+
* ``None`` : No retrying and raise an exception immediately.
|
870 |
+
|
871 |
+
Default: ``None``.
|
872 |
+
|
873 |
+
Note:
|
874 |
+
|
875 |
+
The retry behavior is applicable only when the reason is the
|
876 |
+
unavailable resource. It is not invoked if the reason of failure is
|
877 |
+
other.
|
878 |
+
|
879 |
+
backoff (float, optional): Time to wait before retrying in milli seconds.
|
880 |
+
|
881 |
+
This option is effective only when `timeout` is effective. (not ``None``)
|
882 |
+
|
883 |
+
When `timeout` is effective, this `backoff` controls how long the function
|
884 |
+
should wait before retrying. Default: ``10.0``.
|
885 |
+
|
886 |
+
Returns:
|
887 |
+
int:
|
888 |
+
``0``
|
889 |
+
A packet was processed properly. The caller can keep
|
890 |
+
calling this function to buffer more frames.
|
891 |
+
|
892 |
+
``1``
|
893 |
+
The streamer reached EOF. All the output stream processors
|
894 |
+
flushed the pending frames. The caller should stop calling
|
895 |
+
this method.
|
896 |
+
"""
|
897 |
+
return self._be.process_packet(timeout, backoff)
|
898 |
+
|
899 |
+
def process_all_packets(self):
|
900 |
+
"""Process packets until it reaches EOF."""
|
901 |
+
self._be.process_all_packets()
|
902 |
+
|
903 |
+
def is_buffer_ready(self) -> bool:
|
904 |
+
"""Returns true if all the output streams have at least one chunk filled."""
|
905 |
+
return self._be.is_buffer_ready()
|
906 |
+
|
907 |
+
def pop_chunks(self) -> Tuple[Optional[ChunkTensor]]:
|
908 |
+
"""Pop one chunk from all the output stream buffers.
|
909 |
+
|
910 |
+
Returns:
|
911 |
+
Tuple[Optional[ChunkTensor]]:
|
912 |
+
Buffer contents.
|
913 |
+
If a buffer does not contain any frame, then `None` is returned instead.
|
914 |
+
"""
|
915 |
+
ret = []
|
916 |
+
for chunk in self._be.pop_chunks():
|
917 |
+
if chunk is None:
|
918 |
+
ret.append(None)
|
919 |
+
else:
|
920 |
+
ret.append(ChunkTensor(chunk.frames, chunk.pts))
|
921 |
+
return ret
|
922 |
+
|
923 |
+
def fill_buffer(self, timeout: Optional[float] = None, backoff: float = 10.0) -> int:
|
924 |
+
"""Keep processing packets until all buffers have at least one chunk
|
925 |
+
|
926 |
+
Arguments:
|
927 |
+
timeout (float or None, optional): See
|
928 |
+
:py:func:`~StreamingMediaDecoder.process_packet`. (Default: ``None``)
|
929 |
+
|
930 |
+
backoff (float, optional): See
|
931 |
+
:py:func:`~StreamingMediaDecoder.process_packet`. (Default: ``10.0``)
|
932 |
+
|
933 |
+
Returns:
|
934 |
+
int:
|
935 |
+
``0``
|
936 |
+
Packets are processed properly and buffers are
|
937 |
+
ready to be popped once.
|
938 |
+
|
939 |
+
``1``
|
940 |
+
The streamer reached EOF. All the output stream processors
|
941 |
+
flushed the pending frames. The caller should stop calling
|
942 |
+
this method.
|
943 |
+
"""
|
944 |
+
return self._be.fill_buffer(timeout, backoff)
|
945 |
+
|
946 |
+
def stream(
|
947 |
+
self, timeout: Optional[float] = None, backoff: float = 10.0
|
948 |
+
) -> Iterator[Tuple[Optional[ChunkTensor], ...]]:
|
949 |
+
"""Return an iterator that generates output tensors
|
950 |
+
|
951 |
+
Arguments:
|
952 |
+
timeout (float or None, optional): See
|
953 |
+
:py:func:`~StreamingMediaDecoder.process_packet`. (Default: ``None``)
|
954 |
+
|
955 |
+
backoff (float, optional): See
|
956 |
+
:py:func:`~StreamingMediaDecoder.process_packet`. (Default: ``10.0``)
|
957 |
+
|
958 |
+
Returns:
|
959 |
+
Iterator[Tuple[Optional[ChunkTensor], ...]]:
|
960 |
+
Iterator that yields a tuple of chunks that correspond to the output
|
961 |
+
streams defined by client code.
|
962 |
+
If an output stream is exhausted, then the chunk Tensor is substituted
|
963 |
+
with ``None``.
|
964 |
+
The iterator stops if all the output streams are exhausted.
|
965 |
+
"""
|
966 |
+
if self.num_out_streams == 0:
|
967 |
+
raise RuntimeError("No output stream is configured.")
|
968 |
+
|
969 |
+
while True:
|
970 |
+
if self.fill_buffer(timeout, backoff):
|
971 |
+
break
|
972 |
+
yield self.pop_chunks()
|
973 |
+
|
974 |
+
while True:
|
975 |
+
chunks = self.pop_chunks()
|
976 |
+
if all(c is None for c in chunks):
|
977 |
+
return
|
978 |
+
yield chunks
|
MLPY/Lib/site-packages/torio/io/_streaming_media_encoder.py
ADDED
@@ -0,0 +1,502 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from dataclasses import dataclass
|
2 |
+
from pathlib import Path
|
3 |
+
from typing import BinaryIO, Dict, Optional, Union
|
4 |
+
|
5 |
+
import torch
|
6 |
+
import torio
|
7 |
+
|
8 |
+
ffmpeg_ext = torio._extension.lazy_import_ffmpeg_ext()
|
9 |
+
|
10 |
+
|
11 |
+
@dataclass
|
12 |
+
class CodecConfig:
|
13 |
+
"""Codec configuration."""
|
14 |
+
|
15 |
+
bit_rate: int = -1
|
16 |
+
"""Bit rate"""
|
17 |
+
|
18 |
+
compression_level: int = -1
|
19 |
+
"""Compression level"""
|
20 |
+
|
21 |
+
qscale: Optional[int] = None
|
22 |
+
"""Global quality factor. Enables variable bit rate. Valid values depend on encoder.
|
23 |
+
|
24 |
+
For example: MP3 takes ``0`` - ``9`` (https://trac.ffmpeg.org/wiki/Encode/MP3) while
|
25 |
+
libvorbis takes ``-1`` - ``10``.
|
26 |
+
"""
|
27 |
+
|
28 |
+
gop_size: int = -1
|
29 |
+
"""The number of pictures in a group of pictures, or 0 for intra_only"""
|
30 |
+
|
31 |
+
max_b_frames: int = -1
|
32 |
+
"""maximum number of B-frames between non-B-frames."""
|
33 |
+
|
34 |
+
|
35 |
+
def _convert_config(cfg: CodecConfig):
|
36 |
+
if cfg is None:
|
37 |
+
return None
|
38 |
+
# Convert the codecconfig to C++ compatible type.
|
39 |
+
# omitting the return type annotation so as not to access ffmpeg_ext here.
|
40 |
+
return ffmpeg_ext.CodecConfig(
|
41 |
+
cfg.bit_rate,
|
42 |
+
cfg.compression_level,
|
43 |
+
cfg.qscale,
|
44 |
+
cfg.gop_size,
|
45 |
+
cfg.max_b_frames,
|
46 |
+
)
|
47 |
+
|
48 |
+
|
49 |
+
def _format_doc(**kwargs):
|
50 |
+
def decorator(obj):
|
51 |
+
obj.__doc__ = obj.__doc__.format(**kwargs)
|
52 |
+
return obj
|
53 |
+
|
54 |
+
return decorator
|
55 |
+
|
56 |
+
|
57 |
+
_encoder = """The name of the encoder to be used.
|
58 |
+
When provided, use the specified encoder instead of the default one.
|
59 |
+
|
60 |
+
To list the available encoders, please use
|
61 |
+
:py:func:`~torio.utils.ffmpeg_utils.get_audio_encoders` for audio, and
|
62 |
+
:py:func:`~torio.utils.ffmpeg_utils.get_video_encoders` for video.
|
63 |
+
|
64 |
+
Default: ``None``."""
|
65 |
+
|
66 |
+
|
67 |
+
_encoder_option = """Options passed to encoder.
|
68 |
+
Mapping from str to str.
|
69 |
+
|
70 |
+
To list encoder options for a encoder, you can use
|
71 |
+
``ffmpeg -h encoder=<ENCODER>`` command.
|
72 |
+
|
73 |
+
Default: ``None``.
|
74 |
+
|
75 |
+
|
|
76 |
+
|
77 |
+
In addition to encoder-specific options, you can also pass options related
|
78 |
+
to multithreading. They are effective only if the encoder support them.
|
79 |
+
If neither of them are provided, StreamReader defaults to single thread.
|
80 |
+
|
81 |
+
``"threads"``: The number of threads (in str).
|
82 |
+
Providing the value ``"0"`` will let FFmpeg decides based on its heuristics.
|
83 |
+
|
84 |
+
``"thread_type"``: Which multithreading method to use.
|
85 |
+
The valid values are ``"frame"`` or ``"slice"``.
|
86 |
+
Note that each encoder supports different set of methods.
|
87 |
+
If not provided, a default value is used.
|
88 |
+
|
89 |
+
- ``"frame"``: Encode more than one frame at once.
|
90 |
+
Each thread handles one frame.
|
91 |
+
This will increase decoding delay by one frame per thread
|
92 |
+
- ``"slice"``: Encode more than one part of a single frame at once.
|
93 |
+
|
94 |
+
|
|
95 |
+
"""
|
96 |
+
|
97 |
+
|
98 |
+
_encoder_format = """Format used to encode media.
|
99 |
+
When encoder supports multiple formats, passing this argument will override
|
100 |
+
the format used for encoding.
|
101 |
+
|
102 |
+
To list supported formats for the encoder, you can use
|
103 |
+
``ffmpeg -h encoder=<ENCODER>`` command.
|
104 |
+
|
105 |
+
Default: ``None``.
|
106 |
+
|
107 |
+
Note:
|
108 |
+
When ``encoder_format`` option is not provided, encoder uses its default format.
|
109 |
+
|
110 |
+
For example, when encoding audio into wav format, 16-bit signed integer is used,
|
111 |
+
and when encoding video into mp4 format (h264 encoder), one of YUV format is used.
|
112 |
+
|
113 |
+
This is because typically, 32-bit or 16-bit floating point is used in audio models but
|
114 |
+
they are not commonly used in audio formats. Similarly, RGB24 is commonly used in vision
|
115 |
+
models, but video formats usually (and better) support YUV formats.
|
116 |
+
"""
|
117 |
+
|
118 |
+
_codec_config = """Codec configuration. Please refer to :py:class:`CodecConfig` for
|
119 |
+
configuration options.
|
120 |
+
|
121 |
+
Default: ``None``."""
|
122 |
+
|
123 |
+
|
124 |
+
_filter_desc = """Additional processing to apply before encoding the input media.
|
125 |
+
"""
|
126 |
+
|
127 |
+
_format_common_args = _format_doc(
|
128 |
+
encoder=_encoder,
|
129 |
+
encoder_option=_encoder_option,
|
130 |
+
encoder_format=_encoder_format,
|
131 |
+
codec_config=_codec_config,
|
132 |
+
filter_desc=_filter_desc,
|
133 |
+
)
|
134 |
+
|
135 |
+
|
136 |
+
class StreamingMediaEncoder:
|
137 |
+
"""Encode and write audio/video streams chunk by chunk
|
138 |
+
|
139 |
+
Args:
|
140 |
+
dst (str, path-like or file-like object): The destination where the encoded data are written.
|
141 |
+
If string-type, it must be a resource indicator that FFmpeg can
|
142 |
+
handle. The supported value depends on the FFmpeg found in the system.
|
143 |
+
|
144 |
+
If file-like object, it must support `write` method with the signature
|
145 |
+
`write(data: bytes) -> int`.
|
146 |
+
|
147 |
+
Please refer to the following for the expected signature and behavior of
|
148 |
+
`write` method.
|
149 |
+
|
150 |
+
- https://docs.python.org/3/library/io.html#io.BufferedIOBase.write
|
151 |
+
|
152 |
+
format (str or None, optional):
|
153 |
+
Override the output format, or specify the output media device.
|
154 |
+
Default: ``None`` (no override nor device output).
|
155 |
+
|
156 |
+
This argument serves two different use cases.
|
157 |
+
|
158 |
+
1) Override the output format.
|
159 |
+
This is useful when writing raw data or in a format different from the extension.
|
160 |
+
|
161 |
+
2) Specify the output device.
|
162 |
+
This allows to output media streams to hardware devices,
|
163 |
+
such as speaker and video screen.
|
164 |
+
|
165 |
+
.. note::
|
166 |
+
|
167 |
+
This option roughly corresponds to ``-f`` option of ``ffmpeg`` command.
|
168 |
+
Please refer to the ffmpeg documentations for possible values.
|
169 |
+
|
170 |
+
https://ffmpeg.org/ffmpeg-formats.html#Muxers
|
171 |
+
|
172 |
+
Please use :py:func:`~torio.utils.ffmpeg_utils.get_muxers` to list the
|
173 |
+
multiplexers available in the current environment.
|
174 |
+
|
175 |
+
For device access, the available values vary based on hardware (AV device) and
|
176 |
+
software configuration (ffmpeg build).
|
177 |
+
Please refer to the ffmpeg documentations for possible values.
|
178 |
+
|
179 |
+
https://ffmpeg.org/ffmpeg-devices.html#Output-Devices
|
180 |
+
|
181 |
+
Please use :py:func:`~torio.utils.ffmpeg_utils.get_output_devices` to list
|
182 |
+
the output devices available in the current environment.
|
183 |
+
|
184 |
+
buffer_size (int):
|
185 |
+
The internal buffer size in byte. Used only when `dst` is a file-like object.
|
186 |
+
|
187 |
+
Default: `4096`.
|
188 |
+
"""
|
189 |
+
|
190 |
+
def __init__(
|
191 |
+
self,
|
192 |
+
dst: Union[str, Path, BinaryIO],
|
193 |
+
format: Optional[str] = None,
|
194 |
+
buffer_size: int = 4096,
|
195 |
+
):
|
196 |
+
if hasattr(dst, "write"):
|
197 |
+
self._s = ffmpeg_ext.StreamingMediaEncoderFileObj(dst, format, buffer_size)
|
198 |
+
else:
|
199 |
+
self._s = ffmpeg_ext.StreamingMediaEncoder(str(dst), format)
|
200 |
+
self._is_open = False
|
201 |
+
|
202 |
+
@_format_common_args
|
203 |
+
def add_audio_stream(
|
204 |
+
self,
|
205 |
+
sample_rate: int,
|
206 |
+
num_channels: int,
|
207 |
+
format: str = "flt",
|
208 |
+
*,
|
209 |
+
encoder: Optional[str] = None,
|
210 |
+
encoder_option: Optional[Dict[str, str]] = None,
|
211 |
+
encoder_sample_rate: Optional[int] = None,
|
212 |
+
encoder_num_channels: Optional[int] = None,
|
213 |
+
encoder_format: Optional[str] = None,
|
214 |
+
codec_config: Optional[CodecConfig] = None,
|
215 |
+
filter_desc: Optional[str] = None,
|
216 |
+
):
|
217 |
+
"""Add an output audio stream.
|
218 |
+
|
219 |
+
Args:
|
220 |
+
sample_rate (int): The sample rate.
|
221 |
+
|
222 |
+
num_channels (int): The number of channels.
|
223 |
+
|
224 |
+
format (str, optional): Input sample format, which determines the dtype
|
225 |
+
of the input tensor.
|
226 |
+
|
227 |
+
- ``"u8"``: The input tensor must be ``torch.uint8`` type.
|
228 |
+
- ``"s16"``: The input tensor must be ``torch.int16`` type.
|
229 |
+
- ``"s32"``: The input tensor must be ``torch.int32`` type.
|
230 |
+
- ``"s64"``: The input tensor must be ``torch.int64`` type.
|
231 |
+
- ``"flt"``: The input tensor must be ``torch.float32`` type.
|
232 |
+
- ``"dbl"``: The input tensor must be ``torch.float64`` type.
|
233 |
+
|
234 |
+
Default: ``"flt"``.
|
235 |
+
|
236 |
+
encoder (str or None, optional): {encoder}
|
237 |
+
|
238 |
+
encoder_option (dict or None, optional): {encoder_option}
|
239 |
+
|
240 |
+
encoder_sample_rate (int or None, optional): Override the sample rate used for encoding time.
|
241 |
+
Some encoders pose restriction on the sample rate used for encoding.
|
242 |
+
If the source sample rate is not supported by the encoder, the source sample rate is used,
|
243 |
+
otherwise a default one is picked.
|
244 |
+
|
245 |
+
For example, ``"opus"`` encoder only supports 48k Hz, so, when encoding a
|
246 |
+
waveform with ``"opus"`` encoder, it is always encoded as 48k Hz.
|
247 |
+
Meanwhile ``"mp3"`` (``"libmp3lame"``) supports 44.1k, 48k, 32k, 22.05k,
|
248 |
+
24k, 16k, 11.025k, 12k and 8k Hz.
|
249 |
+
If the original sample rate is one of these, then the original sample rate
|
250 |
+
is used, otherwise it will be resampled to a default one (44.1k).
|
251 |
+
When encoding into WAV format, there is no restriction on sample rate,
|
252 |
+
so the original sample rate will be used.
|
253 |
+
|
254 |
+
Providing ``encoder_sample_rate`` will override this behavior and
|
255 |
+
make encoder attempt to use the provided sample rate.
|
256 |
+
The provided value must be one support by the encoder.
|
257 |
+
|
258 |
+
encoder_num_channels (int or None, optional): Override the number of channels used for encoding.
|
259 |
+
|
260 |
+
Similar to sample rate, some encoders (such as ``"opus"``,
|
261 |
+
``"vorbis"`` and ``"g722"``) pose restriction on
|
262 |
+
the numbe of channels that can be used for encoding.
|
263 |
+
|
264 |
+
If the original number of channels is supported by encoder,
|
265 |
+
then it will be used, otherwise, the encoder attempts to
|
266 |
+
remix the channel to one of the supported ones.
|
267 |
+
|
268 |
+
Providing ``encoder_num_channels`` will override this behavior and
|
269 |
+
make encoder attempt to use the provided number of channels.
|
270 |
+
The provided value must be one support by the encoder.
|
271 |
+
|
272 |
+
encoder_format (str or None, optional): {encoder_format}
|
273 |
+
|
274 |
+
codec_config (CodecConfig or None, optional): {codec_config}
|
275 |
+
|
276 |
+
filter_desc (str or None, optional): {filter_desc}
|
277 |
+
"""
|
278 |
+
self._s.add_audio_stream(
|
279 |
+
sample_rate,
|
280 |
+
num_channels,
|
281 |
+
format,
|
282 |
+
encoder,
|
283 |
+
encoder_option,
|
284 |
+
encoder_format,
|
285 |
+
encoder_sample_rate,
|
286 |
+
encoder_num_channels,
|
287 |
+
_convert_config(codec_config),
|
288 |
+
filter_desc,
|
289 |
+
)
|
290 |
+
|
291 |
+
@_format_common_args
|
292 |
+
def add_video_stream(
|
293 |
+
self,
|
294 |
+
frame_rate: float,
|
295 |
+
width: int,
|
296 |
+
height: int,
|
297 |
+
format: str = "rgb24",
|
298 |
+
*,
|
299 |
+
encoder: Optional[str] = None,
|
300 |
+
encoder_option: Optional[Dict[str, str]] = None,
|
301 |
+
encoder_frame_rate: Optional[float] = None,
|
302 |
+
encoder_width: Optional[int] = None,
|
303 |
+
encoder_height: Optional[int] = None,
|
304 |
+
encoder_format: Optional[str] = None,
|
305 |
+
codec_config: Optional[CodecConfig] = None,
|
306 |
+
filter_desc: Optional[str] = None,
|
307 |
+
hw_accel: Optional[str] = None,
|
308 |
+
):
|
309 |
+
"""Add an output video stream.
|
310 |
+
|
311 |
+
This method has to be called before `open` is called.
|
312 |
+
|
313 |
+
Args:
|
314 |
+
frame_rate (float): Frame rate of the video.
|
315 |
+
|
316 |
+
width (int): Width of the video frame.
|
317 |
+
|
318 |
+
height (int): Height of the video frame.
|
319 |
+
|
320 |
+
format (str, optional): Input pixel format, which determines the
|
321 |
+
color channel order of the input tensor.
|
322 |
+
|
323 |
+
- ``"gray8"``: One channel, grayscale.
|
324 |
+
- ``"rgb24"``: Three channels in the order of RGB.
|
325 |
+
- ``"bgr24"``: Three channels in the order of BGR.
|
326 |
+
- ``"yuv444p"``: Three channels in the order of YUV.
|
327 |
+
|
328 |
+
Default: ``"rgb24"``.
|
329 |
+
|
330 |
+
In either case, the input tensor has to be ``torch.uint8`` type and
|
331 |
+
the shape must be (frame, channel, height, width).
|
332 |
+
|
333 |
+
encoder (str or None, optional): {encoder}
|
334 |
+
|
335 |
+
encoder_option (dict or None, optional): {encoder_option}
|
336 |
+
|
337 |
+
encoder_frame_rate (float or None, optional): Override the frame rate used for encoding.
|
338 |
+
|
339 |
+
Some encoders, (such as ``"mpeg1"`` and ``"mpeg2"``) pose restriction on the
|
340 |
+
frame rate that can be used for encoding.
|
341 |
+
If such case, if the source frame rate (provided as ``frame_rate``) is not
|
342 |
+
one of the supported frame rate, then a default one is picked, and the frame rate
|
343 |
+
is changed on-the-fly. Otherwise the source frame rate is used.
|
344 |
+
|
345 |
+
Providing ``encoder_frame_rate`` will override this behavior and
|
346 |
+
make encoder attempts to use the provided sample rate.
|
347 |
+
The provided value must be one support by the encoder.
|
348 |
+
|
349 |
+
encoder_width (int or None, optional): Width of the image used for encoding.
|
350 |
+
This allows to change the image size during encoding.
|
351 |
+
|
352 |
+
encoder_height (int or None, optional): Height of the image used for encoding.
|
353 |
+
This allows to change the image size during encoding.
|
354 |
+
|
355 |
+
encoder_format (str or None, optional): {encoder_format}
|
356 |
+
|
357 |
+
codec_config (CodecConfig or None, optional): {codec_config}
|
358 |
+
|
359 |
+
filter_desc (str or None, optional): {filter_desc}
|
360 |
+
|
361 |
+
hw_accel (str or None, optional): Enable hardware acceleration.
|
362 |
+
|
363 |
+
When video is encoded on CUDA hardware, for example
|
364 |
+
`encoder="h264_nvenc"`, passing CUDA device indicator to `hw_accel`
|
365 |
+
(i.e. `hw_accel="cuda:0"`) will make StreamingMediaEncoder expect video
|
366 |
+
chunk to be CUDA Tensor. Passing CPU Tensor will result in an error.
|
367 |
+
|
368 |
+
If `None`, the video chunk Tensor has to be CPU Tensor.
|
369 |
+
Default: ``None``.
|
370 |
+
"""
|
371 |
+
self._s.add_video_stream(
|
372 |
+
frame_rate,
|
373 |
+
width,
|
374 |
+
height,
|
375 |
+
format,
|
376 |
+
encoder,
|
377 |
+
encoder_option,
|
378 |
+
encoder_format,
|
379 |
+
encoder_frame_rate,
|
380 |
+
encoder_width,
|
381 |
+
encoder_height,
|
382 |
+
hw_accel,
|
383 |
+
_convert_config(codec_config),
|
384 |
+
filter_desc,
|
385 |
+
)
|
386 |
+
|
387 |
+
def set_metadata(self, metadata: Dict[str, str]):
|
388 |
+
"""Set file-level metadata
|
389 |
+
|
390 |
+
Args:
|
391 |
+
metadata (dict or None, optional): File-level metadata.
|
392 |
+
"""
|
393 |
+
self._s.set_metadata(metadata)
|
394 |
+
|
395 |
+
def _print_output_stream(self, i: int):
|
396 |
+
"""[debug] Print the registered stream information to stdout."""
|
397 |
+
self._s.dump_format(i)
|
398 |
+
|
399 |
+
def open(self, option: Optional[Dict[str, str]] = None) -> "StreamingMediaEncoder":
|
400 |
+
"""Open the output file / device and write the header.
|
401 |
+
|
402 |
+
:py:class:`StreamingMediaEncoder` is also a context manager and therefore supports the
|
403 |
+
``with`` statement.
|
404 |
+
This method returns the instance on which the method is called (i.e. `self`),
|
405 |
+
so that it can be used in `with` statement.
|
406 |
+
It is recommended to use context manager, as the file is closed automatically
|
407 |
+
when exiting from ``with`` clause.
|
408 |
+
|
409 |
+
Args:
|
410 |
+
option (dict or None, optional): Private options for protocol, device and muxer. See example.
|
411 |
+
|
412 |
+
Example - Protocol option
|
413 |
+
>>> s = StreamingMediaEncoder(dst="rtmp://localhost:1234/live/app", format="flv")
|
414 |
+
>>> s.add_video_stream(...)
|
415 |
+
>>> # Passing protocol option `listen=1` makes StreamingMediaEncoder act as RTMP server.
|
416 |
+
>>> with s.open(option={"listen": "1"}) as f:
|
417 |
+
>>> f.write_video_chunk(...)
|
418 |
+
|
419 |
+
Example - Device option
|
420 |
+
>>> s = StreamingMediaEncoder("-", format="sdl")
|
421 |
+
>>> s.add_video_stream(..., encoder_format="rgb24")
|
422 |
+
>>> # Open SDL video player with fullscreen
|
423 |
+
>>> with s.open(option={"window_fullscreen": "1"}):
|
424 |
+
>>> f.write_video_chunk(...)
|
425 |
+
|
426 |
+
Example - Muxer option
|
427 |
+
>>> s = StreamingMediaEncoder("foo.flac")
|
428 |
+
>>> s.add_audio_stream(...)
|
429 |
+
>>> s.set_metadata({"artist": "torio contributors"})
|
430 |
+
>>> # FLAC muxer has a private option to not write the header.
|
431 |
+
>>> # The resulting file does not contain the above metadata.
|
432 |
+
>>> with s.open(option={"write_header": "false"}) as f:
|
433 |
+
>>> f.write_audio_chunk(...)
|
434 |
+
"""
|
435 |
+
if not self._is_open:
|
436 |
+
self._s.open(option)
|
437 |
+
self._is_open = True
|
438 |
+
return self
|
439 |
+
|
440 |
+
def close(self):
|
441 |
+
"""Close the output
|
442 |
+
|
443 |
+
:py:class:`StreamingMediaEncoder` is also a context manager and therefore supports the
|
444 |
+
``with`` statement.
|
445 |
+
It is recommended to use context manager, as the file is closed automatically
|
446 |
+
when exiting from ``with`` clause.
|
447 |
+
|
448 |
+
See :py:meth:`StreamingMediaEncoder.open` for more detail.
|
449 |
+
"""
|
450 |
+
if self._is_open:
|
451 |
+
self._s.close()
|
452 |
+
self._is_open = False
|
453 |
+
|
454 |
+
def write_audio_chunk(self, i: int, chunk: torch.Tensor, pts: Optional[float] = None):
|
455 |
+
"""Write audio data
|
456 |
+
|
457 |
+
Args:
|
458 |
+
i (int): Stream index.
|
459 |
+
chunk (Tensor): Waveform tensor. Shape: `(frame, channel)`.
|
460 |
+
The ``dtype`` must match what was passed to :py:meth:`add_audio_stream` method.
|
461 |
+
pts (float, optional, or None): If provided, overwrite the presentation timestamp.
|
462 |
+
|
463 |
+
.. note::
|
464 |
+
|
465 |
+
The provided value is converted to integer value expressed in basis of
|
466 |
+
sample rate. Therefore, it is truncated to the nearest value of
|
467 |
+
``n / sample_rate``.
|
468 |
+
"""
|
469 |
+
self._s.write_audio_chunk(i, chunk, pts)
|
470 |
+
|
471 |
+
def write_video_chunk(self, i: int, chunk: torch.Tensor, pts: Optional[float] = None):
|
472 |
+
"""Write video/image data
|
473 |
+
|
474 |
+
Args:
|
475 |
+
i (int): Stream index.
|
476 |
+
chunk (Tensor): Video/image tensor.
|
477 |
+
Shape: `(time, channel, height, width)`.
|
478 |
+
The ``dtype`` must be ``torch.uint8``.
|
479 |
+
The shape (height, width and the number of channels) must match
|
480 |
+
what was configured when calling :py:meth:`add_video_stream`
|
481 |
+
pts (float, optional or None): If provided, overwrite the presentation timestamp.
|
482 |
+
|
483 |
+
.. note::
|
484 |
+
|
485 |
+
The provided value is converted to integer value expressed in basis of
|
486 |
+
frame rate. Therefore, it is truncated to the nearest value of
|
487 |
+
``n / frame_rate``.
|
488 |
+
"""
|
489 |
+
self._s.write_video_chunk(i, chunk, pts)
|
490 |
+
|
491 |
+
def flush(self):
|
492 |
+
"""Flush the frames from encoders and write the frames to the destination."""
|
493 |
+
self._s.flush()
|
494 |
+
|
495 |
+
def __enter__(self):
|
496 |
+
"""Context manager so that the destination is closed and data are flushed automatically."""
|
497 |
+
return self
|
498 |
+
|
499 |
+
def __exit__(self, exception_type, exception_value, traceback):
|
500 |
+
"""Context manager so that the destination is closed and data are flushed automatically."""
|
501 |
+
self.flush()
|
502 |
+
self.close()
|
MLPY/Lib/site-packages/torio/lib/__init__.py
ADDED
File without changes
|
MLPY/Lib/site-packages/torio/lib/__pycache__/__init__.cpython-39.pyc
ADDED
Binary file (146 Bytes). View file
|
|
MLPY/Lib/site-packages/torio/lib/_torio_ffmpeg4.pyd
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:144cdcf7ee3bf60d2589dccd6a17a127218ae38a4c99b2ab25519e9c46448090
|
3 |
+
size 1710080
|
MLPY/Lib/site-packages/torio/lib/_torio_ffmpeg5.pyd
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:c8a912dc0691b65075b20ebbdd517946b52da49600fb47ccd31fe7d6a2ea464c
|
3 |
+
size 1710080
|
MLPY/Lib/site-packages/torio/lib/_torio_ffmpeg6.pyd
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:a54d9a42baf649f5d759862f499e3c3863b634e2b44d957a31fae39092f03387
|
3 |
+
size 1710080
|
MLPY/Lib/site-packages/torio/lib/libtorio_ffmpeg4.pyd
ADDED
Binary file (964 kB). View file
|
|
MLPY/Lib/site-packages/torio/lib/libtorio_ffmpeg5.pyd
ADDED
Binary file (964 kB). View file
|
|
MLPY/Lib/site-packages/torio/lib/libtorio_ffmpeg6.pyd
ADDED
Binary file (964 kB). View file
|
|
MLPY/Lib/site-packages/torio/utils/__init__.py
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from . import ffmpeg_utils
|
2 |
+
|
3 |
+
|
4 |
+
__all__ = ["ffmpeg_utils"]
|
MLPY/Lib/site-packages/torio/utils/__pycache__/__init__.cpython-39.pyc
ADDED
Binary file (207 Bytes). View file
|
|
MLPY/Lib/site-packages/torio/utils/__pycache__/ffmpeg_utils.cpython-39.pyc
ADDED
Binary file (8.93 kB). View file
|
|
MLPY/Lib/site-packages/torio/utils/ffmpeg_utils.py
ADDED
@@ -0,0 +1,247 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Module to change the configuration of FFmpeg libraries (such as libavformat).
|
2 |
+
|
3 |
+
It affects functionalities in :py:mod:`torio.io`.
|
4 |
+
"""
|
5 |
+
from typing import Dict, List, Tuple
|
6 |
+
|
7 |
+
import torio
|
8 |
+
|
9 |
+
ffmpeg_ext = torio._extension.lazy_import_ffmpeg_ext()
|
10 |
+
|
11 |
+
|
12 |
+
def get_versions() -> Dict[str, Tuple[int]]:
|
13 |
+
"""Get the versions of FFmpeg libraries
|
14 |
+
|
15 |
+
Returns:
|
16 |
+
dict: mapping from library names to version string,
|
17 |
+
i.e. `"libavutil": (56, 22, 100)`.
|
18 |
+
"""
|
19 |
+
return ffmpeg_ext.get_versions()
|
20 |
+
|
21 |
+
|
22 |
+
def get_log_level() -> int:
|
23 |
+
"""Get the log level of FFmpeg.
|
24 |
+
|
25 |
+
See :py:func:`set_log_level` for the detail.
|
26 |
+
"""
|
27 |
+
return ffmpeg_ext.get_log_level()
|
28 |
+
|
29 |
+
|
30 |
+
def set_log_level(level: int):
|
31 |
+
"""Set the log level of FFmpeg (libavformat etc)
|
32 |
+
|
33 |
+
Arguments:
|
34 |
+
level (int): Log level. The larger, the more verbose.
|
35 |
+
|
36 |
+
The following values are common values, the corresponding ``ffmpeg``'s
|
37 |
+
``-loglevel`` option value and desription.
|
38 |
+
|
39 |
+
* ``-8`` (``quiet``):
|
40 |
+
Print no output.
|
41 |
+
* ``0`` (``panic``):
|
42 |
+
Something went really wrong and we will crash now.
|
43 |
+
* ``8`` (``fatal``):
|
44 |
+
Something went wrong and recovery is not possible.
|
45 |
+
For example, no header was found for a format which depends
|
46 |
+
on headers or an illegal combination of parameters is used.
|
47 |
+
* ``16`` (``error``):
|
48 |
+
Something went wrong and cannot losslessly be recovered.
|
49 |
+
However, not all future data is affected.
|
50 |
+
* ``24`` (``warning``):
|
51 |
+
Something somehow does not look correct.
|
52 |
+
This may or may not lead to problems.
|
53 |
+
* ``32`` (``info``):
|
54 |
+
Standard information.
|
55 |
+
* ``40`` (``verbose``):
|
56 |
+
Detailed information.
|
57 |
+
* ``48`` (``debug``):
|
58 |
+
Stuff which is only useful for libav* developers.
|
59 |
+
* ``56`` (``trace``):
|
60 |
+
Extremely verbose debugging, useful for libav* development.
|
61 |
+
|
62 |
+
"""
|
63 |
+
ffmpeg_ext.set_log_level(level)
|
64 |
+
|
65 |
+
|
66 |
+
def get_demuxers() -> Dict[str, str]:
|
67 |
+
"""Get the available demuxers.
|
68 |
+
|
69 |
+
Returns:
|
70 |
+
Dict[str, str]: Mapping from demuxer (format) short name to long name.
|
71 |
+
|
72 |
+
Example
|
73 |
+
>>> for k, v in get_demuxers().items():
|
74 |
+
>>> print(f"{k}: {v}")
|
75 |
+
... aa: Audible AA format files
|
76 |
+
... aac: raw ADTS AAC (Advanced Audio Coding)
|
77 |
+
... aax: CRI AAX
|
78 |
+
... ac3: raw AC-3
|
79 |
+
"""
|
80 |
+
return ffmpeg_ext.get_demuxers()
|
81 |
+
|
82 |
+
|
83 |
+
def get_muxers() -> Dict[str, str]:
|
84 |
+
"""Get the available muxers.
|
85 |
+
|
86 |
+
Returns:
|
87 |
+
Dict[str, str]: Mapping from muxer (format) short name to long name.
|
88 |
+
|
89 |
+
Example
|
90 |
+
>>> for k, v in get_muxers().items():
|
91 |
+
>>> print(f"{k}: {v}")
|
92 |
+
... a64: a64 - video for Commodore 64
|
93 |
+
... ac3: raw AC-3
|
94 |
+
... adts: ADTS AAC (Advanced Audio Coding)
|
95 |
+
... adx: CRI ADX
|
96 |
+
... aiff: Audio IFF
|
97 |
+
"""
|
98 |
+
return ffmpeg_ext.get_muxers()
|
99 |
+
|
100 |
+
|
101 |
+
def get_audio_decoders() -> Dict[str, str]:
|
102 |
+
"""Get the available audio decoders.
|
103 |
+
|
104 |
+
Returns:
|
105 |
+
Dict[str, str]: Mapping from decoder short name to long name.
|
106 |
+
|
107 |
+
Example
|
108 |
+
>>> for k, v in get_audio_decoders().items():
|
109 |
+
>>> print(f"{k}: {v}")
|
110 |
+
... a64: a64 - video for Commodore 64
|
111 |
+
... ac3: raw AC-3
|
112 |
+
... adts: ADTS AAC (Advanced Audio Coding)
|
113 |
+
... adx: CRI ADX
|
114 |
+
... aiff: Audio IFF
|
115 |
+
"""
|
116 |
+
return ffmpeg_ext.get_audio_decoders()
|
117 |
+
|
118 |
+
|
119 |
+
def get_audio_encoders() -> Dict[str, str]:
|
120 |
+
"""Get the available audio encoders.
|
121 |
+
|
122 |
+
Returns:
|
123 |
+
Dict[str, str]: Mapping from encoder short name to long name.
|
124 |
+
|
125 |
+
Example
|
126 |
+
>>> for k, v in get_audio_encoders().items():
|
127 |
+
>>> print(f"{k}: {v}")
|
128 |
+
... comfortnoise: RFC 3389 comfort noise generator
|
129 |
+
... s302m: SMPTE 302M
|
130 |
+
... aac: AAC (Advanced Audio Coding)
|
131 |
+
... ac3: ATSC A/52A (AC-3)
|
132 |
+
... ac3_fixed: ATSC A/52A (AC-3)
|
133 |
+
... alac: ALAC (Apple Lossless Audio Codec)
|
134 |
+
"""
|
135 |
+
return ffmpeg_ext.get_audio_encoders()
|
136 |
+
|
137 |
+
|
138 |
+
def get_video_decoders() -> Dict[str, str]:
|
139 |
+
"""Get the available video decoders.
|
140 |
+
|
141 |
+
Returns:
|
142 |
+
Dict[str, str]: Mapping from decoder short name to long name.
|
143 |
+
|
144 |
+
Example
|
145 |
+
>>> for k, v in get_video_decoders().items():
|
146 |
+
>>> print(f"{k}: {v}")
|
147 |
+
... aasc: Autodesk RLE
|
148 |
+
... aic: Apple Intermediate Codec
|
149 |
+
... alias_pix: Alias/Wavefront PIX image
|
150 |
+
... agm: Amuse Graphics Movie
|
151 |
+
... amv: AMV Video
|
152 |
+
... anm: Deluxe Paint Animation
|
153 |
+
"""
|
154 |
+
return ffmpeg_ext.get_video_decoders()
|
155 |
+
|
156 |
+
|
157 |
+
def get_video_encoders() -> Dict[str, str]:
|
158 |
+
"""Get the available video encoders.
|
159 |
+
|
160 |
+
Returns:
|
161 |
+
Dict[str, str]: Mapping from encoder short name to long name.
|
162 |
+
|
163 |
+
Example
|
164 |
+
>>> for k, v in get_audio_encoders().items():
|
165 |
+
>>> print(f"{k}: {v}")
|
166 |
+
... a64multi: Multicolor charset for Commodore 64
|
167 |
+
... a64multi5: Multicolor charset for Commodore 64, extended with 5th color (colram)
|
168 |
+
... alias_pix: Alias/Wavefront PIX image
|
169 |
+
... amv: AMV Video
|
170 |
+
... apng: APNG (Animated Portable Network Graphics) image
|
171 |
+
... asv1: ASUS V1
|
172 |
+
... asv2: ASUS V2
|
173 |
+
"""
|
174 |
+
return ffmpeg_ext.get_video_encoders()
|
175 |
+
|
176 |
+
|
177 |
+
def get_input_devices() -> Dict[str, str]:
|
178 |
+
"""Get the available input devices.
|
179 |
+
|
180 |
+
Returns:
|
181 |
+
Dict[str, str]: Mapping from device short name to long name.
|
182 |
+
|
183 |
+
Example
|
184 |
+
>>> for k, v in get_input_devices().items():
|
185 |
+
>>> print(f"{k}: {v}")
|
186 |
+
... avfoundation: AVFoundation input device
|
187 |
+
... lavfi: Libavfilter virtual input device
|
188 |
+
"""
|
189 |
+
return ffmpeg_ext.get_input_devices()
|
190 |
+
|
191 |
+
|
192 |
+
def get_output_devices() -> Dict[str, str]:
|
193 |
+
"""Get the available output devices.
|
194 |
+
|
195 |
+
Returns:
|
196 |
+
Dict[str, str]: Mapping from device short name to long name.
|
197 |
+
|
198 |
+
Example
|
199 |
+
>>> for k, v in get_output_devices().items():
|
200 |
+
>>> print(f"{k}: {v}")
|
201 |
+
... audiotoolbox: AudioToolbox output device
|
202 |
+
"""
|
203 |
+
return ffmpeg_ext.get_output_devices()
|
204 |
+
|
205 |
+
|
206 |
+
def get_input_protocols() -> List[str]:
|
207 |
+
"""Get the supported input protocols.
|
208 |
+
|
209 |
+
Returns:
|
210 |
+
List[str]: The names of supported input protocols
|
211 |
+
|
212 |
+
Example
|
213 |
+
>>> print(get_input_protocols())
|
214 |
+
... ['file', 'ftp', 'hls', 'http','https', 'pipe', 'rtmp', 'tcp', 'tls', 'udp', 'unix']
|
215 |
+
"""
|
216 |
+
return ffmpeg_ext.get_input_protocols()
|
217 |
+
|
218 |
+
|
219 |
+
def get_output_protocols() -> List[str]:
|
220 |
+
"""Get the supported output protocols.
|
221 |
+
|
222 |
+
Returns:
|
223 |
+
list of str: The names of supported output protocols
|
224 |
+
|
225 |
+
Example
|
226 |
+
>>> print(get_output_protocols())
|
227 |
+
... ['file', 'ftp', 'http', 'https', 'md5', 'pipe', 'prompeg', 'rtmp', 'tee', 'tcp', 'tls', 'udp', 'unix']
|
228 |
+
"""
|
229 |
+
return ffmpeg_ext.get_output_protocols()
|
230 |
+
|
231 |
+
|
232 |
+
def get_build_config() -> str:
|
233 |
+
"""Get the FFmpeg build configuration
|
234 |
+
|
235 |
+
Returns:
|
236 |
+
str: Build configuration string.
|
237 |
+
|
238 |
+
Example
|
239 |
+
>>> print(get_build_config())
|
240 |
+
--prefix=/Users/runner/miniforge3 --cc=arm64-apple-darwin20.0.0-clang --enable-gpl --enable-hardcoded-tables --enable-libfreetype --enable-libopenh264 --enable-neon --enable-libx264 --enable-libx265 --enable-libaom --enable-libsvtav1 --enable-libxml2 --enable-libvpx --enable-pic --enable-pthreads --enable-shared --disable-static --enable-version3 --enable-zlib --enable-libmp3lame --pkg-config=/Users/runner/miniforge3/conda-bld/ffmpeg_1646229390493/_build_env/bin/pkg-config --enable-cross-compile --arch=arm64 --target-os=darwin --cross-prefix=arm64-apple-darwin20.0.0- --host-cc=/Users/runner/miniforge3/conda-bld/ffmpeg_1646229390493/_build_env/bin/x86_64-apple-darwin13.4.0-clang # noqa
|
241 |
+
"""
|
242 |
+
return ffmpeg_ext.get_build_config()
|
243 |
+
|
244 |
+
|
245 |
+
def clear_cuda_context_cache():
|
246 |
+
"""Clear the CUDA context used by CUDA Hardware accelerated video decoding"""
|
247 |
+
ffmpeg_ext.clear_cuda_context_cache()
|
MLPY/Lib/site-packages/typing_extensions-4.12.2.dist-info/INSTALLER
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
pip
|
MLPY/Lib/site-packages/typing_extensions-4.12.2.dist-info/LICENSE
ADDED
@@ -0,0 +1,279 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
A. HISTORY OF THE SOFTWARE
|
2 |
+
==========================
|
3 |
+
|
4 |
+
Python was created in the early 1990s by Guido van Rossum at Stichting
|
5 |
+
Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands
|
6 |
+
as a successor of a language called ABC. Guido remains Python's
|
7 |
+
principal author, although it includes many contributions from others.
|
8 |
+
|
9 |
+
In 1995, Guido continued his work on Python at the Corporation for
|
10 |
+
National Research Initiatives (CNRI, see https://www.cnri.reston.va.us)
|
11 |
+
in Reston, Virginia where he released several versions of the
|
12 |
+
software.
|
13 |
+
|
14 |
+
In May 2000, Guido and the Python core development team moved to
|
15 |
+
BeOpen.com to form the BeOpen PythonLabs team. In October of the same
|
16 |
+
year, the PythonLabs team moved to Digital Creations, which became
|
17 |
+
Zope Corporation. In 2001, the Python Software Foundation (PSF, see
|
18 |
+
https://www.python.org/psf/) was formed, a non-profit organization
|
19 |
+
created specifically to own Python-related Intellectual Property.
|
20 |
+
Zope Corporation was a sponsoring member of the PSF.
|
21 |
+
|
22 |
+
All Python releases are Open Source (see https://opensource.org for
|
23 |
+
the Open Source Definition). Historically, most, but not all, Python
|
24 |
+
releases have also been GPL-compatible; the table below summarizes
|
25 |
+
the various releases.
|
26 |
+
|
27 |
+
Release Derived Year Owner GPL-
|
28 |
+
from compatible? (1)
|
29 |
+
|
30 |
+
0.9.0 thru 1.2 1991-1995 CWI yes
|
31 |
+
1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
|
32 |
+
1.6 1.5.2 2000 CNRI no
|
33 |
+
2.0 1.6 2000 BeOpen.com no
|
34 |
+
1.6.1 1.6 2001 CNRI yes (2)
|
35 |
+
2.1 2.0+1.6.1 2001 PSF no
|
36 |
+
2.0.1 2.0+1.6.1 2001 PSF yes
|
37 |
+
2.1.1 2.1+2.0.1 2001 PSF yes
|
38 |
+
2.1.2 2.1.1 2002 PSF yes
|
39 |
+
2.1.3 2.1.2 2002 PSF yes
|
40 |
+
2.2 and above 2.1.1 2001-now PSF yes
|
41 |
+
|
42 |
+
Footnotes:
|
43 |
+
|
44 |
+
(1) GPL-compatible doesn't mean that we're distributing Python under
|
45 |
+
the GPL. All Python licenses, unlike the GPL, let you distribute
|
46 |
+
a modified version without making your changes open source. The
|
47 |
+
GPL-compatible licenses make it possible to combine Python with
|
48 |
+
other software that is released under the GPL; the others don't.
|
49 |
+
|
50 |
+
(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
|
51 |
+
because its license has a choice of law clause. According to
|
52 |
+
CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
|
53 |
+
is "not incompatible" with the GPL.
|
54 |
+
|
55 |
+
Thanks to the many outside volunteers who have worked under Guido's
|
56 |
+
direction to make these releases possible.
|
57 |
+
|
58 |
+
|
59 |
+
B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
|
60 |
+
===============================================================
|
61 |
+
|
62 |
+
Python software and documentation are licensed under the
|
63 |
+
Python Software Foundation License Version 2.
|
64 |
+
|
65 |
+
Starting with Python 3.8.6, examples, recipes, and other code in
|
66 |
+
the documentation are dual licensed under the PSF License Version 2
|
67 |
+
and the Zero-Clause BSD license.
|
68 |
+
|
69 |
+
Some software incorporated into Python is under different licenses.
|
70 |
+
The licenses are listed with code falling under that license.
|
71 |
+
|
72 |
+
|
73 |
+
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
74 |
+
--------------------------------------------
|
75 |
+
|
76 |
+
1. This LICENSE AGREEMENT is between the Python Software Foundation
|
77 |
+
("PSF"), and the Individual or Organization ("Licensee") accessing and
|
78 |
+
otherwise using this software ("Python") in source or binary form and
|
79 |
+
its associated documentation.
|
80 |
+
|
81 |
+
2. Subject to the terms and conditions of this License Agreement, PSF hereby
|
82 |
+
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
|
83 |
+
analyze, test, perform and/or display publicly, prepare derivative works,
|
84 |
+
distribute, and otherwise use Python alone or in any derivative version,
|
85 |
+
provided, however, that PSF's License Agreement and PSF's notice of copyright,
|
86 |
+
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
|
87 |
+
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation;
|
88 |
+
All Rights Reserved" are retained in Python alone or in any derivative version
|
89 |
+
prepared by Licensee.
|
90 |
+
|
91 |
+
3. In the event Licensee prepares a derivative work that is based on
|
92 |
+
or incorporates Python or any part thereof, and wants to make
|
93 |
+
the derivative work available to others as provided herein, then
|
94 |
+
Licensee hereby agrees to include in any such work a brief summary of
|
95 |
+
the changes made to Python.
|
96 |
+
|
97 |
+
4. PSF is making Python available to Licensee on an "AS IS"
|
98 |
+
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
99 |
+
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
|
100 |
+
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
101 |
+
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
|
102 |
+
INFRINGE ANY THIRD PARTY RIGHTS.
|
103 |
+
|
104 |
+
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
105 |
+
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
106 |
+
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
|
107 |
+
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
108 |
+
|
109 |
+
6. This License Agreement will automatically terminate upon a material
|
110 |
+
breach of its terms and conditions.
|
111 |
+
|
112 |
+
7. Nothing in this License Agreement shall be deemed to create any
|
113 |
+
relationship of agency, partnership, or joint venture between PSF and
|
114 |
+
Licensee. This License Agreement does not grant permission to use PSF
|
115 |
+
trademarks or trade name in a trademark sense to endorse or promote
|
116 |
+
products or services of Licensee, or any third party.
|
117 |
+
|
118 |
+
8. By copying, installing or otherwise using Python, Licensee
|
119 |
+
agrees to be bound by the terms and conditions of this License
|
120 |
+
Agreement.
|
121 |
+
|
122 |
+
|
123 |
+
BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
|
124 |
+
-------------------------------------------
|
125 |
+
|
126 |
+
BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
|
127 |
+
|
128 |
+
1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
|
129 |
+
office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
|
130 |
+
Individual or Organization ("Licensee") accessing and otherwise using
|
131 |
+
this software in source or binary form and its associated
|
132 |
+
documentation ("the Software").
|
133 |
+
|
134 |
+
2. Subject to the terms and conditions of this BeOpen Python License
|
135 |
+
Agreement, BeOpen hereby grants Licensee a non-exclusive,
|
136 |
+
royalty-free, world-wide license to reproduce, analyze, test, perform
|
137 |
+
and/or display publicly, prepare derivative works, distribute, and
|
138 |
+
otherwise use the Software alone or in any derivative version,
|
139 |
+
provided, however, that the BeOpen Python License is retained in the
|
140 |
+
Software, alone or in any derivative version prepared by Licensee.
|
141 |
+
|
142 |
+
3. BeOpen is making the Software available to Licensee on an "AS IS"
|
143 |
+
basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
144 |
+
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
|
145 |
+
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
146 |
+
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
|
147 |
+
INFRINGE ANY THIRD PARTY RIGHTS.
|
148 |
+
|
149 |
+
4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
|
150 |
+
SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
|
151 |
+
AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
|
152 |
+
DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
153 |
+
|
154 |
+
5. This License Agreement will automatically terminate upon a material
|
155 |
+
breach of its terms and conditions.
|
156 |
+
|
157 |
+
6. This License Agreement shall be governed by and interpreted in all
|
158 |
+
respects by the law of the State of California, excluding conflict of
|
159 |
+
law provisions. Nothing in this License Agreement shall be deemed to
|
160 |
+
create any relationship of agency, partnership, or joint venture
|
161 |
+
between BeOpen and Licensee. This License Agreement does not grant
|
162 |
+
permission to use BeOpen trademarks or trade names in a trademark
|
163 |
+
sense to endorse or promote products or services of Licensee, or any
|
164 |
+
third party. As an exception, the "BeOpen Python" logos available at
|
165 |
+
http://www.pythonlabs.com/logos.html may be used according to the
|
166 |
+
permissions granted on that web page.
|
167 |
+
|
168 |
+
7. By copying, installing or otherwise using the software, Licensee
|
169 |
+
agrees to be bound by the terms and conditions of this License
|
170 |
+
Agreement.
|
171 |
+
|
172 |
+
|
173 |
+
CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
|
174 |
+
---------------------------------------
|
175 |
+
|
176 |
+
1. This LICENSE AGREEMENT is between the Corporation for National
|
177 |
+
Research Initiatives, having an office at 1895 Preston White Drive,
|
178 |
+
Reston, VA 20191 ("CNRI"), and the Individual or Organization
|
179 |
+
("Licensee") accessing and otherwise using Python 1.6.1 software in
|
180 |
+
source or binary form and its associated documentation.
|
181 |
+
|
182 |
+
2. Subject to the terms and conditions of this License Agreement, CNRI
|
183 |
+
hereby grants Licensee a nonexclusive, royalty-free, world-wide
|
184 |
+
license to reproduce, analyze, test, perform and/or display publicly,
|
185 |
+
prepare derivative works, distribute, and otherwise use Python 1.6.1
|
186 |
+
alone or in any derivative version, provided, however, that CNRI's
|
187 |
+
License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
|
188 |
+
1995-2001 Corporation for National Research Initiatives; All Rights
|
189 |
+
Reserved" are retained in Python 1.6.1 alone or in any derivative
|
190 |
+
version prepared by Licensee. Alternately, in lieu of CNRI's License
|
191 |
+
Agreement, Licensee may substitute the following text (omitting the
|
192 |
+
quotes): "Python 1.6.1 is made available subject to the terms and
|
193 |
+
conditions in CNRI's License Agreement. This Agreement together with
|
194 |
+
Python 1.6.1 may be located on the internet using the following
|
195 |
+
unique, persistent identifier (known as a handle): 1895.22/1013. This
|
196 |
+
Agreement may also be obtained from a proxy server on the internet
|
197 |
+
using the following URL: http://hdl.handle.net/1895.22/1013".
|
198 |
+
|
199 |
+
3. In the event Licensee prepares a derivative work that is based on
|
200 |
+
or incorporates Python 1.6.1 or any part thereof, and wants to make
|
201 |
+
the derivative work available to others as provided herein, then
|
202 |
+
Licensee hereby agrees to include in any such work a brief summary of
|
203 |
+
the changes made to Python 1.6.1.
|
204 |
+
|
205 |
+
4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
|
206 |
+
basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
207 |
+
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
|
208 |
+
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
209 |
+
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
|
210 |
+
INFRINGE ANY THIRD PARTY RIGHTS.
|
211 |
+
|
212 |
+
5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
213 |
+
1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
214 |
+
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
|
215 |
+
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
216 |
+
|
217 |
+
6. This License Agreement will automatically terminate upon a material
|
218 |
+
breach of its terms and conditions.
|
219 |
+
|
220 |
+
7. This License Agreement shall be governed by the federal
|
221 |
+
intellectual property law of the United States, including without
|
222 |
+
limitation the federal copyright law, and, to the extent such
|
223 |
+
U.S. federal law does not apply, by the law of the Commonwealth of
|
224 |
+
Virginia, excluding Virginia's conflict of law provisions.
|
225 |
+
Notwithstanding the foregoing, with regard to derivative works based
|
226 |
+
on Python 1.6.1 that incorporate non-separable material that was
|
227 |
+
previously distributed under the GNU General Public License (GPL), the
|
228 |
+
law of the Commonwealth of Virginia shall govern this License
|
229 |
+
Agreement only as to issues arising under or with respect to
|
230 |
+
Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
|
231 |
+
License Agreement shall be deemed to create any relationship of
|
232 |
+
agency, partnership, or joint venture between CNRI and Licensee. This
|
233 |
+
License Agreement does not grant permission to use CNRI trademarks or
|
234 |
+
trade name in a trademark sense to endorse or promote products or
|
235 |
+
services of Licensee, or any third party.
|
236 |
+
|
237 |
+
8. By clicking on the "ACCEPT" button where indicated, or by copying,
|
238 |
+
installing or otherwise using Python 1.6.1, Licensee agrees to be
|
239 |
+
bound by the terms and conditions of this License Agreement.
|
240 |
+
|
241 |
+
ACCEPT
|
242 |
+
|
243 |
+
|
244 |
+
CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
|
245 |
+
--------------------------------------------------
|
246 |
+
|
247 |
+
Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
|
248 |
+
The Netherlands. All rights reserved.
|
249 |
+
|
250 |
+
Permission to use, copy, modify, and distribute this software and its
|
251 |
+
documentation for any purpose and without fee is hereby granted,
|
252 |
+
provided that the above copyright notice appear in all copies and that
|
253 |
+
both that copyright notice and this permission notice appear in
|
254 |
+
supporting documentation, and that the name of Stichting Mathematisch
|
255 |
+
Centrum or CWI not be used in advertising or publicity pertaining to
|
256 |
+
distribution of the software without specific, written prior
|
257 |
+
permission.
|
258 |
+
|
259 |
+
STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
|
260 |
+
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
261 |
+
FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
|
262 |
+
FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
263 |
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
264 |
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
265 |
+
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
266 |
+
|
267 |
+
ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION
|
268 |
+
----------------------------------------------------------------------
|
269 |
+
|
270 |
+
Permission to use, copy, modify, and/or distribute this software for any
|
271 |
+
purpose with or without fee is hereby granted.
|
272 |
+
|
273 |
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
274 |
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
275 |
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
276 |
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
277 |
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
278 |
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
279 |
+
PERFORMANCE OF THIS SOFTWARE.
|
MLPY/Lib/site-packages/typing_extensions-4.12.2.dist-info/METADATA
ADDED
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Metadata-Version: 2.1
|
2 |
+
Name: typing_extensions
|
3 |
+
Version: 4.12.2
|
4 |
+
Summary: Backported and Experimental Type Hints for Python 3.8+
|
5 |
+
Keywords: annotations,backport,checker,checking,function,hinting,hints,type,typechecking,typehinting,typehints,typing
|
6 |
+
Author-email: "Guido van Rossum, Jukka Lehtosalo, Łukasz Langa, Michael Lee" <[email protected]>
|
7 |
+
Requires-Python: >=3.8
|
8 |
+
Description-Content-Type: text/markdown
|
9 |
+
Classifier: Development Status :: 5 - Production/Stable
|
10 |
+
Classifier: Environment :: Console
|
11 |
+
Classifier: Intended Audience :: Developers
|
12 |
+
Classifier: License :: OSI Approved :: Python Software Foundation License
|
13 |
+
Classifier: Operating System :: OS Independent
|
14 |
+
Classifier: Programming Language :: Python :: 3
|
15 |
+
Classifier: Programming Language :: Python :: 3 :: Only
|
16 |
+
Classifier: Programming Language :: Python :: 3.8
|
17 |
+
Classifier: Programming Language :: Python :: 3.9
|
18 |
+
Classifier: Programming Language :: Python :: 3.10
|
19 |
+
Classifier: Programming Language :: Python :: 3.11
|
20 |
+
Classifier: Programming Language :: Python :: 3.12
|
21 |
+
Classifier: Programming Language :: Python :: 3.13
|
22 |
+
Classifier: Topic :: Software Development
|
23 |
+
Project-URL: Bug Tracker, https://github.com/python/typing_extensions/issues
|
24 |
+
Project-URL: Changes, https://github.com/python/typing_extensions/blob/main/CHANGELOG.md
|
25 |
+
Project-URL: Documentation, https://typing-extensions.readthedocs.io/
|
26 |
+
Project-URL: Home, https://github.com/python/typing_extensions
|
27 |
+
Project-URL: Q & A, https://github.com/python/typing/discussions
|
28 |
+
Project-URL: Repository, https://github.com/python/typing_extensions
|
29 |
+
|
30 |
+
# Typing Extensions
|
31 |
+
|
32 |
+
[![Chat at https://gitter.im/python/typing](https://badges.gitter.im/python/typing.svg)](https://gitter.im/python/typing)
|
33 |
+
|
34 |
+
[Documentation](https://typing-extensions.readthedocs.io/en/latest/#) –
|
35 |
+
[PyPI](https://pypi.org/project/typing-extensions/)
|
36 |
+
|
37 |
+
## Overview
|
38 |
+
|
39 |
+
The `typing_extensions` module serves two related purposes:
|
40 |
+
|
41 |
+
- Enable use of new type system features on older Python versions. For example,
|
42 |
+
`typing.TypeGuard` is new in Python 3.10, but `typing_extensions` allows
|
43 |
+
users on previous Python versions to use it too.
|
44 |
+
- Enable experimentation with new type system PEPs before they are accepted and
|
45 |
+
added to the `typing` module.
|
46 |
+
|
47 |
+
`typing_extensions` is treated specially by static type checkers such as
|
48 |
+
mypy and pyright. Objects defined in `typing_extensions` are treated the same
|
49 |
+
way as equivalent forms in `typing`.
|
50 |
+
|
51 |
+
`typing_extensions` uses
|
52 |
+
[Semantic Versioning](https://semver.org/). The
|
53 |
+
major version will be incremented only for backwards-incompatible changes.
|
54 |
+
Therefore, it's safe to depend
|
55 |
+
on `typing_extensions` like this: `typing_extensions >=x.y, <(x+1)`,
|
56 |
+
where `x.y` is the first version that includes all features you need.
|
57 |
+
|
58 |
+
## Included items
|
59 |
+
|
60 |
+
See [the documentation](https://typing-extensions.readthedocs.io/en/latest/#) for a
|
61 |
+
complete listing of module contents.
|
62 |
+
|
63 |
+
## Contributing
|
64 |
+
|
65 |
+
See [CONTRIBUTING.md](https://github.com/python/typing_extensions/blob/main/CONTRIBUTING.md)
|
66 |
+
for how to contribute to `typing_extensions`.
|
67 |
+
|
MLPY/Lib/site-packages/typing_extensions-4.12.2.dist-info/RECORD
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
__pycache__/typing_extensions.cpython-39.pyc,,
|
2 |
+
typing_extensions-4.12.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
3 |
+
typing_extensions-4.12.2.dist-info/LICENSE,sha256=Oy-B_iHRgcSZxZolbI4ZaEVdZonSaaqFNzv7avQdo78,13936
|
4 |
+
typing_extensions-4.12.2.dist-info/METADATA,sha256=BeUQIa8cnYbrjWx-N8TOznM9UGW5Gm2DicVpDtRA8W0,3018
|
5 |
+
typing_extensions-4.12.2.dist-info/RECORD,,
|
6 |
+
typing_extensions-4.12.2.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
|
7 |
+
typing_extensions.py,sha256=gwekpyG9DVG3lxWKX4ni8u7nk3We5slG98mA9F3DJQw,134451
|
MLPY/Lib/site-packages/typing_extensions-4.12.2.dist-info/WHEEL
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Wheel-Version: 1.0
|
2 |
+
Generator: flit 3.9.0
|
3 |
+
Root-Is-Purelib: true
|
4 |
+
Tag: py3-none-any
|