Spaces:
Paused
Paused
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 |
+
[](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
|