PyNvVideoCodec 2.2.1__cp312-cp312-win_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- PyNvVideoCodec/PyNvVideoCodec_121.cp312-win_arm64.pyd +0 -0
- PyNvVideoCodec/PyNvVideoCodec_130.cp312-win_arm64.pyd +0 -0
- PyNvVideoCodec/VersionCheck.cp312-win_arm64.pyd +0 -0
- PyNvVideoCodec/__init__.py +251 -0
- PyNvVideoCodec/avformat-62.dll +0 -0
- PyNvVideoCodec/avutil-60.dll +0 -0
- PyNvVideoCodec/decoders/SimpleDecoder.py +273 -0
- PyNvVideoCodec/decoders/ThreadedDecoder.py +129 -0
- PyNvVideoCodec/decoders/__init__.py +22 -0
- PyNvVideoCodec/transcoder/Transcoder.py +55 -0
- PyNvVideoCodec/transcoder/__init__.py +22 -0
- benchmarks/cached_decoder_benchmark.py +813 -0
- benchmarks/frame_sampling_benchmark.py +765 -0
- benchmarks/requirements.txt +52 -0
- benchmarks/segmented_transcode_benchmark.py +1546 -0
- benchmarks/setup_pycuda_win_arm64.bat +531 -0
- pynvvideocodec-2.2.1.data/data/external/ffmpeg/readme.txt +39 -0
- pynvvideocodec-2.2.1.data/data/external/ffmpeg/src/ffmpeg-8.1.2.tar.xz +0 -0
- pynvvideocodec-2.2.1.dist-info/METADATA +97 -0
- pynvvideocodec-2.2.1.dist-info/RECORD +63 -0
- pynvvideocodec-2.2.1.dist-info/WHEEL +5 -0
- pynvvideocodec-2.2.1.dist-info/licenses/LICENSE.txt +22 -0
- pynvvideocodec-2.2.1.dist-info/licenses/NOTICES.txt +167 -0
- pynvvideocodec-2.2.1.dist-info/top_level.txt +3 -0
- samples/advanced/decode.py +173 -0
- samples/advanced/decode_from_memory_buffer.py +204 -0
- samples/advanced/decode_perf.py +299 -0
- samples/advanced/decode_reconfigure.py +184 -0
- samples/advanced/decode_sei_msg.py +193 -0
- samples/advanced/decode_with_cuda_control.py +214 -0
- samples/advanced/decode_with_low_latency.py +183 -0
- samples/advanced/encode_config.json +12 -0
- samples/advanced/encode_config_lowlatency.json +13 -0
- samples/advanced/encode_perf.py +338 -0
- samples/advanced/encode_reconfigure.py +211 -0
- samples/advanced/encode_sei_msg.py +188 -0
- samples/advanced/simple_decode_from_memory.py +151 -0
- samples/advanced/simple_decode_stats.py +179 -0
- samples/advanced/transcode.py +288 -0
- samples/basic/create_video_segments.py +187 -0
- samples/basic/encode.py +574 -0
- samples/basic/segments.txt +3 -0
- samples/basic/simple_decode_sampling.py +182 -0
- samples/basic/transcode_config.json +6 -0
- samples/jupyter/ReconfigureDecoder.png +0 -0
- samples/jupyter/SimpleDecoderAPIs.png +0 -0
- samples/jupyter/ThreadedDecoder.png +0 -0
- samples/jupyter/coco_classes.pickle +0 -0
- samples/jupyter/face_protect_tutorial.ipynb +881 -0
- samples/jupyter/object_detection_tutorial.ipynb +781 -0
- samples/jupyter/simple_decode_tutorial.ipynb +674 -0
- samples/requirements.txt +84 -0
- samples/setup_pycuda_win_arm64.bat +531 -0
- samples/utils/Utils.py +405 -0
- samples/utils/__init__.py +36 -0
- samples/utils/decode_parser.py +642 -0
- samples/utils/decode_stats_writer.py +177 -0
- samples/utils/encode_parallel_utils.py +174 -0
- samples/utils/encode_parser.py +379 -0
- samples/utils/frame_utils.py +89 -0
- samples/utils/sei_message_parser.py +223 -0
- samples/utils/sei_parser.py +223 -0
- samples/utils/transcode_parser.py +218 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# This copyright notice applies to this file only
|
|
2
|
+
#
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
7
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
8
|
+
# to deal in the Software without restriction, including without limitation
|
|
9
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
10
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
11
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
12
|
+
#
|
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
|
14
|
+
# all copies or substantial portions of the Software.
|
|
15
|
+
#
|
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
19
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
21
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
22
|
+
# DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
__author__ = "NVIDIA"
|
|
29
|
+
__copyright__ = "Copyright 2026, NVIDIA"
|
|
30
|
+
__credits__ = []
|
|
31
|
+
__license__ = "MIT"
|
|
32
|
+
__version__ = "2.2.1"
|
|
33
|
+
__maintainer__ = "NVIDIA"
|
|
34
|
+
__email__ = "TODO"
|
|
35
|
+
__status__ = "Production"
|
|
36
|
+
|
|
37
|
+
from ast import Str
|
|
38
|
+
import string
|
|
39
|
+
import os
|
|
40
|
+
import platform
|
|
41
|
+
import sys
|
|
42
|
+
import sysconfig
|
|
43
|
+
import site
|
|
44
|
+
from pathlib import Path
|
|
45
|
+
import importlib.util
|
|
46
|
+
import logging
|
|
47
|
+
|
|
48
|
+
logger = logging.getLogger(__name__)
|
|
49
|
+
logger.setLevel(logging.INFO)
|
|
50
|
+
|
|
51
|
+
def _setup_environment():
|
|
52
|
+
"""Setup environment variables and paths"""
|
|
53
|
+
package_dir = os.path.dirname(os.path.abspath(__file__))
|
|
54
|
+
machine = platform.machine()
|
|
55
|
+
cuda_lib_dir = "arm64" if machine.lower() in ("arm64", "aarch64") else "x64"
|
|
56
|
+
|
|
57
|
+
if platform.system() == 'Windows':
|
|
58
|
+
try:
|
|
59
|
+
# Add CUDA path if available
|
|
60
|
+
cuda_path = os.environ.get('CUDA_PATH')
|
|
61
|
+
if cuda_path:
|
|
62
|
+
dll_paths = [
|
|
63
|
+
os.path.join(cuda_path, "bin"),
|
|
64
|
+
os.path.join(cuda_path, "lib", cuda_lib_dir),
|
|
65
|
+
package_dir, # Package directory for ffmpeg DLLs
|
|
66
|
+
os.path.join(package_dir, "lib") # Additional lib directory
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
# Add paths to DLL search path
|
|
70
|
+
if hasattr(os, 'add_dll_directory'):
|
|
71
|
+
for path in dll_paths:
|
|
72
|
+
if os.path.exists(path):
|
|
73
|
+
os.add_dll_directory(path)
|
|
74
|
+
logger.debug(f"Added DLL directory: {path}")
|
|
75
|
+
except Exception as e:
|
|
76
|
+
logger.error(f"Error setting up DLL paths: {e}")
|
|
77
|
+
|
|
78
|
+
def _get_module_path(module_suffix):
|
|
79
|
+
"""Get the appropriate module file path"""
|
|
80
|
+
package_dir = os.path.dirname(os.path.abspath(__file__))
|
|
81
|
+
py_version = f"{sys.version_info.major}{sys.version_info.minor}"
|
|
82
|
+
machine = platform.machine()
|
|
83
|
+
|
|
84
|
+
if platform.system() == "Windows":
|
|
85
|
+
ext = ".pyd"
|
|
86
|
+
platform_tag = sysconfig.get_platform().replace("-", "_")
|
|
87
|
+
if platform_tag not in ("win_amd64", "win_arm64"):
|
|
88
|
+
platform_tag = "win_arm64" if machine.lower() in ("arm64", "aarch64") else "win_amd64"
|
|
89
|
+
possible_names = [
|
|
90
|
+
f"PyNvVideoCodec{module_suffix}.cp{py_version}-{platform_tag}{ext}"
|
|
91
|
+
]
|
|
92
|
+
else:
|
|
93
|
+
ext = ".so"
|
|
94
|
+
if machine == 'aarch64':
|
|
95
|
+
possible_names = [
|
|
96
|
+
f"PyNvVideoCodec{module_suffix}.cpython-{py_version}-aarch64-linux-gnu{ext}",
|
|
97
|
+
f"PyNvVideoCodec{module_suffix}.cpython-{py_version}-aarch64-linux{ext}",
|
|
98
|
+
f"PyNvVideoCodec{module_suffix}.cp{py_version}-manylinux_2_28_aarch64{ext}"
|
|
99
|
+
]
|
|
100
|
+
else: # x86_64
|
|
101
|
+
possible_names = [
|
|
102
|
+
f"PyNvVideoCodec{module_suffix}.cpython-{py_version}-x86_64-linux-gnu{ext}",
|
|
103
|
+
f"PyNvVideoCodec{module_suffix}.cpython-{py_version}-x86_64-linux{ext}",
|
|
104
|
+
f"PyNvVideoCodec{module_suffix}.cp{py_version}-manylinux_2_28_x86_64{ext}"
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
# Try each possible name
|
|
108
|
+
for name in possible_names:
|
|
109
|
+
path = os.path.join(package_dir, name)
|
|
110
|
+
if os.path.exists(path):
|
|
111
|
+
logger.debug(f"Found module: {path}")
|
|
112
|
+
return path
|
|
113
|
+
|
|
114
|
+
# If no module found, list directory contents for debugging
|
|
115
|
+
logger.error(f"Available files in {package_dir}:")
|
|
116
|
+
try:
|
|
117
|
+
for file in os.listdir(package_dir):
|
|
118
|
+
logger.error(f" {file}")
|
|
119
|
+
except Exception as e:
|
|
120
|
+
logger.error(f"Error listing directory: {e}")
|
|
121
|
+
|
|
122
|
+
# Return the first name for error message
|
|
123
|
+
return os.path.join(package_dir, possible_names[0])
|
|
124
|
+
|
|
125
|
+
def _get_driver_version():
|
|
126
|
+
"""Get the driver version using VersionCheck"""
|
|
127
|
+
try:
|
|
128
|
+
from .VersionCheck import DriverWrapper
|
|
129
|
+
driver = DriverWrapper()
|
|
130
|
+
version = driver.GetDriverVersion()
|
|
131
|
+
logger.info(f"Supported NVENC Driver Version: {version}")
|
|
132
|
+
return version
|
|
133
|
+
except Exception as e:
|
|
134
|
+
error_msg = str(e)
|
|
135
|
+
|
|
136
|
+
# Provide troubleshooting guidance
|
|
137
|
+
troubleshooting = "\n\n=== TROUBLESHOOTING STEPS ===\n"
|
|
138
|
+
|
|
139
|
+
if "libnvidia-encode.so" in error_msg or "cannot open shared object file" in error_msg:
|
|
140
|
+
troubleshooting += "\nThe NVENC library could not be found. This usually means:\n"
|
|
141
|
+
troubleshooting += "\n1. **Running in Docker?**"
|
|
142
|
+
troubleshooting += "\n - Ensure your container was launched with: NVIDIA_DRIVER_CAPABILITIES=compute,utility,video"
|
|
143
|
+
troubleshooting += "\n - Example: docker run --gpus all -e NVIDIA_DRIVER_CAPABILITIES=compute,utility,video ..."
|
|
144
|
+
troubleshooting += "\n - Or add to docker-compose.yml:"
|
|
145
|
+
troubleshooting += "\n environment:"
|
|
146
|
+
troubleshooting += "\n - NVIDIA_DRIVER_CAPABILITIES=compute,utility,video"
|
|
147
|
+
troubleshooting += "\n\n2. **NVIDIA drivers not installed or outdated**"
|
|
148
|
+
troubleshooting += "\n - Check driver installation: nvidia-smi"
|
|
149
|
+
troubleshooting += "\n - Ensure NVENC-capable GPU is present"
|
|
150
|
+
troubleshooting += "\n - Update to latest NVIDIA drivers if needed"
|
|
151
|
+
troubleshooting += "\n\n3. **Library path issues**"
|
|
152
|
+
troubleshooting += "\n - Check if library exists: find /usr -name 'libnvidia-encode.so*' 2>/dev/null"
|
|
153
|
+
troubleshooting += "\n - Verify LD_LIBRARY_PATH includes NVIDIA library directories"
|
|
154
|
+
else:
|
|
155
|
+
troubleshooting += f"\nOriginal error: {error_msg}\n"
|
|
156
|
+
troubleshooting += "\nPlease ensure:"
|
|
157
|
+
troubleshooting += "\n1. NVIDIA drivers are properly installed"
|
|
158
|
+
troubleshooting += "\n2. NVENC-capable GPU is available"
|
|
159
|
+
troubleshooting += "\n3. If using Docker, NVIDIA_DRIVER_CAPABILITIES includes 'video'"
|
|
160
|
+
|
|
161
|
+
raise RuntimeError(f"{error_msg}{troubleshooting}") from e
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
# Setup environment
|
|
165
|
+
_setup_environment()
|
|
166
|
+
|
|
167
|
+
# Get driver version
|
|
168
|
+
supportedNvEncVersion = _get_driver_version()
|
|
169
|
+
|
|
170
|
+
# Define driver version thresholds
|
|
171
|
+
NVENC_VER_13 = 13 * 16 # Driver version for NVENC 13
|
|
172
|
+
NVENC_VER_12 = 12 * 16 + 1 # Driver version for NVENC 12.1
|
|
173
|
+
|
|
174
|
+
# Determine which version to use
|
|
175
|
+
if supportedNvEncVersion >= NVENC_VER_13:
|
|
176
|
+
module_suffix = "_130"
|
|
177
|
+
logger.info("Using NVENC 13.0 version")
|
|
178
|
+
elif supportedNvEncVersion >= NVENC_VER_12:
|
|
179
|
+
module_suffix = "_121"
|
|
180
|
+
logger.info("Using NVENC 12.1 version")
|
|
181
|
+
else:
|
|
182
|
+
raise RuntimeError("Driver version is too old")
|
|
183
|
+
|
|
184
|
+
# Get module path
|
|
185
|
+
module_path = _get_module_path(module_suffix)
|
|
186
|
+
if not os.path.exists(module_path):
|
|
187
|
+
raise ImportError(f"Module not found: {module_path}")
|
|
188
|
+
|
|
189
|
+
# Import the module
|
|
190
|
+
spec = importlib.util.spec_from_file_location("_PyNvVideoCodec", module_path)
|
|
191
|
+
if spec is None:
|
|
192
|
+
raise ImportError(f"Could not create module spec for {module_path}")
|
|
193
|
+
|
|
194
|
+
_PyNvVideoCodec = importlib.util.module_from_spec(spec)
|
|
195
|
+
spec.loader.exec_module(_PyNvVideoCodec)
|
|
196
|
+
|
|
197
|
+
# Import all symbols into current namespace
|
|
198
|
+
globals().update({name: getattr(_PyNvVideoCodec, name) for name in dir(_PyNvVideoCodec)
|
|
199
|
+
if not name.startswith('_')})
|
|
200
|
+
|
|
201
|
+
# Import submodules
|
|
202
|
+
from .decoders.SimpleDecoder import SimpleDecoder
|
|
203
|
+
from .decoders.ThreadedDecoder import ThreadedDecoder
|
|
204
|
+
from .transcoder.Transcoder import Transcoder
|
|
205
|
+
|
|
206
|
+
except Exception as e:
|
|
207
|
+
# Only log additional diagnostics if troubleshooting steps not already included
|
|
208
|
+
if "TROUBLESHOOTING STEPS" not in str(e):
|
|
209
|
+
logger.error(f"Error importing PyNvVideoCodec: {e}")
|
|
210
|
+
logger.error(f"System: {platform.system()}")
|
|
211
|
+
logger.error(f"Python version: {sys.version}")
|
|
212
|
+
if platform.system() == "Linux":
|
|
213
|
+
logger.error(f"LD_LIBRARY_PATH: {os.environ.get('LD_LIBRARY_PATH', 'Not set')}")
|
|
214
|
+
raise
|
|
215
|
+
|
|
216
|
+
def tostring(val):
|
|
217
|
+
if type(val) == dict:
|
|
218
|
+
x = dict(map(lambda x: (x[0], tostring(x[1])), val.items()))
|
|
219
|
+
else:
|
|
220
|
+
x = str(val)
|
|
221
|
+
x = x.replace("]", "")
|
|
222
|
+
x = x.replace("[", "")
|
|
223
|
+
return x
|
|
224
|
+
|
|
225
|
+
def format_optional_params(optional_param):
|
|
226
|
+
param = tostring(optional_param)
|
|
227
|
+
|
|
228
|
+
if "slice" in param:
|
|
229
|
+
param["slice_mode"] = param["slice"]["mode"]
|
|
230
|
+
param["slice_data"] = param["slice"]["data"]
|
|
231
|
+
del param["slice"]
|
|
232
|
+
|
|
233
|
+
if "timinginfo" in param:
|
|
234
|
+
param["num_unit_in_ticks"] = param["timinginfo"]["num_unit_in_ticks"]
|
|
235
|
+
param["timescale"] = param["timinginfo"]["timescale"]
|
|
236
|
+
del param["timinginfo"]
|
|
237
|
+
|
|
238
|
+
return param
|
|
239
|
+
|
|
240
|
+
def CreateEncoder(width, height, fmt, usecpuinputbuffer, **kwargs):
|
|
241
|
+
cudacontext = 0
|
|
242
|
+
cudastream = 0
|
|
243
|
+
if "cudacontext" in kwargs:
|
|
244
|
+
cudacontext = int(kwargs["cudacontext"])
|
|
245
|
+
del kwargs["cudacontext"]
|
|
246
|
+
if "cudastream" in kwargs:
|
|
247
|
+
cudastream = int(kwargs["cudastream"])
|
|
248
|
+
del kwargs["cudastream"]
|
|
249
|
+
|
|
250
|
+
optional_args = format_optional_params(kwargs)
|
|
251
|
+
return PyNvEncoder(width, height, fmt, cudacontext, cudastream, usecpuinputbuffer, optional_args)
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# This copyright notice applies to this file only
|
|
2
|
+
#
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
7
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
8
|
+
# to deal in the Software without restriction, including without limitation
|
|
9
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
10
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
11
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
12
|
+
#
|
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
|
14
|
+
# all copies or substantial portions of the Software.
|
|
15
|
+
#
|
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
19
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
21
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
22
|
+
# DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
from typing import Union
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
# from ._SimpleDecoderImpl import SimpleDecoderImpl
|
|
27
|
+
import PyNvVideoCodec as nvc
|
|
28
|
+
import warnings
|
|
29
|
+
import logging
|
|
30
|
+
|
|
31
|
+
class SimpleDecoder:
|
|
32
|
+
"""
|
|
33
|
+
A simple class for decoding a video stream and accessing frames in varied ways.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
enc_file_path (str, bytes, bytearray, memoryview, or Path): Encoded file path
|
|
37
|
+
or in-memory video data. When bytes/bytearray/memoryview is provided, the
|
|
38
|
+
entire buffer is copied once into native memory for seekable decoding.
|
|
39
|
+
gpu_id (int) : gpu id on which to decode on.
|
|
40
|
+
cuda_context(pycuda._driver.Context): Cuda context under which the source is decoded
|
|
41
|
+
cuda_stream(pycuda._driver.Stream): Cuda stream used by the decoder
|
|
42
|
+
use_device_memory (bool): If set to 'True' output decoded frame is CUDeviceptr wrapped in CUDA Array Interface else its Host memory.
|
|
43
|
+
max_width(int): maximum width that the decoder must support
|
|
44
|
+
max_height(int): maximum height that the decoder must support
|
|
45
|
+
need_scanned_stream_metadata(bool): If set to 'True', the stream metadata will be collected by going through each of the packets. This
|
|
46
|
+
runs on a different thread and its running time depends on the total size of input stream.
|
|
47
|
+
decoder_cache_size(int): LRU cache size for the number of decoders to cache.
|
|
48
|
+
output_color_type (OutputColorType): Output format type of the decoded frames. By default it is OutputColorType::NATIVE which
|
|
49
|
+
implies decoded output is returned in native format(NV12, YUV444 etc.). Other supported values are OutputColorType::RGB(which implies
|
|
50
|
+
the decoded output is converted to interleaved RGB(HWC)) and OutputColorType::RGBP(which implies decoded output is converted to planar
|
|
51
|
+
RGB(CHW))
|
|
52
|
+
enableDecodeStats (bool): If set to 'True', decode statistics will be available for each decoded frame. Default is False.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, enc_file_path,
|
|
56
|
+
gpu_id = 0, cuda_context = 0,
|
|
57
|
+
cuda_stream = 0, use_device_memory = True,
|
|
58
|
+
max_width = 0,
|
|
59
|
+
max_height = 0,
|
|
60
|
+
need_scanned_stream_metadata = 0,
|
|
61
|
+
decoder_cache_size = 4,
|
|
62
|
+
output_color_type = nvc.OutputColorType.NATIVE,
|
|
63
|
+
bWaitForSessionWarmUp = False,
|
|
64
|
+
enableDecodeStats = False):
|
|
65
|
+
|
|
66
|
+
self.need_scanned_stream_metadata = need_scanned_stream_metadata
|
|
67
|
+
|
|
68
|
+
# Normalize input: Path -> str, bytearray/memoryview -> bytes
|
|
69
|
+
if isinstance(enc_file_path, Path):
|
|
70
|
+
enc_file_path = str(enc_file_path)
|
|
71
|
+
|
|
72
|
+
if hasattr(enc_file_path, 'read') and hasattr(enc_file_path, 'seek'):
|
|
73
|
+
# BinaryIO stream: reads on-demand, no up-front copy
|
|
74
|
+
if hasattr(enc_file_path, 'seekable') and not enc_file_path.seekable():
|
|
75
|
+
raise ValueError(
|
|
76
|
+
"SimpleDecoder requires a seekable stream. "
|
|
77
|
+
"The provided stream reports seekable() == False."
|
|
78
|
+
)
|
|
79
|
+
if need_scanned_stream_metadata:
|
|
80
|
+
raise ValueError(
|
|
81
|
+
"need_scanned_stream_metadata is not supported for BinaryIO stream "
|
|
82
|
+
"input, because the metadata scan cannot share a Python stream across "
|
|
83
|
+
"threads. Use a file path or bytes/bytearray/memoryview input if you "
|
|
84
|
+
"need scanned stream metadata."
|
|
85
|
+
)
|
|
86
|
+
self.simple_decoder = nvc.CreateSimpleDecoder(enc_file_path, gpu_id,
|
|
87
|
+
cuda_context, cuda_stream,
|
|
88
|
+
use_device_memory, max_width,
|
|
89
|
+
max_height,
|
|
90
|
+
decoder_cache_size, output_color_type,
|
|
91
|
+
bWaitForSessionWarmUp, enableDecodeStats)
|
|
92
|
+
elif isinstance(enc_file_path, (bytes, bytearray, memoryview)):
|
|
93
|
+
source_bytes = bytes(enc_file_path)
|
|
94
|
+
if len(source_bytes) > 8 * 1024 * 1024:
|
|
95
|
+
warnings.warn(
|
|
96
|
+
f"SimpleDecoder: in-memory buffer is {len(source_bytes) / (1024*1024):.1f} MiB. "
|
|
97
|
+
"Large buffers consume additional memory since the data is copied into native storage."
|
|
98
|
+
)
|
|
99
|
+
self.simple_decoder = nvc.CreateSimpleDecoder(source_bytes, gpu_id,
|
|
100
|
+
cuda_context, cuda_stream,
|
|
101
|
+
use_device_memory, max_width,
|
|
102
|
+
max_height, need_scanned_stream_metadata,
|
|
103
|
+
decoder_cache_size, output_color_type,
|
|
104
|
+
bWaitForSessionWarmUp, enableDecodeStats)
|
|
105
|
+
elif isinstance(enc_file_path, str):
|
|
106
|
+
self.simple_decoder = nvc.CreateSimpleDecoder(enc_file_path, gpu_id,
|
|
107
|
+
cuda_context, cuda_stream,
|
|
108
|
+
use_device_memory, max_width,
|
|
109
|
+
max_height, need_scanned_stream_metadata,
|
|
110
|
+
decoder_cache_size, output_color_type,
|
|
111
|
+
bWaitForSessionWarmUp, enableDecodeStats)
|
|
112
|
+
else:
|
|
113
|
+
raise TypeError(
|
|
114
|
+
f"enc_file_path must be str, bytes, bytearray, memoryview, Path, "
|
|
115
|
+
f"or a seekable file-like object (BinaryIO), "
|
|
116
|
+
f"got {type(enc_file_path).__name__}"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
total_frames = self.__len__()
|
|
120
|
+
if total_frames == 0:
|
|
121
|
+
raise Exception("Elementary streams not supported by Simple Decoder. Invalid input stream.")
|
|
122
|
+
|
|
123
|
+
def __getitem__(self, key: Union[int, slice]):
|
|
124
|
+
"""
|
|
125
|
+
Return frame or batch of frames depending on the key.
|
|
126
|
+
Supports Python slice semantics: stop is exclusive, so dec[0:len(dec)]
|
|
127
|
+
returns all frames.
|
|
128
|
+
Args:
|
|
129
|
+
key (int or slice): The index or range of indices to get the frames
|
|
130
|
+
Returns:
|
|
131
|
+
DecodedFrame: Decoded frame(s) based on the key
|
|
132
|
+
"""
|
|
133
|
+
if isinstance(key, int):
|
|
134
|
+
self.validate_index(key)
|
|
135
|
+
return self.simple_decoder[key]
|
|
136
|
+
elif isinstance(key, slice):
|
|
137
|
+
start, stop, step = key.indices(len(self))
|
|
138
|
+
indices = list(range(start, stop, step))
|
|
139
|
+
if not indices:
|
|
140
|
+
return self.get_batch_frames_by_index([])
|
|
141
|
+
return self.get_batch_frames_by_index(indices)
|
|
142
|
+
else:
|
|
143
|
+
raise TypeError(
|
|
144
|
+
f"Unsupported key type: {type(key)}. Supported types are int and slice."
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def __len__(self):
|
|
149
|
+
"""
|
|
150
|
+
Returns the total number of frames in the video
|
|
151
|
+
"""
|
|
152
|
+
stream_meta = self.simple_decoder.get_stream_metadata()
|
|
153
|
+
if stream_meta.num_frames == 0 and self.need_scanned_stream_metadata == True:
|
|
154
|
+
scanned_stream_data = self.simple_decoder.get_scanned_stream_metadata()
|
|
155
|
+
return scanned_stream_data.num_frames
|
|
156
|
+
return stream_meta.num_frames
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def get_batch_frames(self, batch_size):
|
|
160
|
+
"""
|
|
161
|
+
Returns a batch of frames based on the batch size
|
|
162
|
+
Args:
|
|
163
|
+
batch_size(int): Number of frames in the batch
|
|
164
|
+
Returns:
|
|
165
|
+
DecodedFrame[] : A list of decoded frames
|
|
166
|
+
"""
|
|
167
|
+
return self.simple_decoder.get_batch_frames(batch_size)
|
|
168
|
+
|
|
169
|
+
def stop(self):
|
|
170
|
+
return self.simple_decoder.stop()
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def get_batch_frames_by_index(self, indices):
|
|
174
|
+
"""
|
|
175
|
+
Returns a batch of frames based on the indices
|
|
176
|
+
Args:
|
|
177
|
+
indices(list): A list containing the frame indices to be retrieved
|
|
178
|
+
Returns:
|
|
179
|
+
DecodedFrame[] : A list of decoded frames
|
|
180
|
+
"""
|
|
181
|
+
total_frames = self.__len__()
|
|
182
|
+
validated_indices = []
|
|
183
|
+
seen = set()
|
|
184
|
+
for index in indices:
|
|
185
|
+
if 0 <= index < total_frames:
|
|
186
|
+
if index not in seen:
|
|
187
|
+
seen.add(index)
|
|
188
|
+
validated_indices.append(index)
|
|
189
|
+
else:
|
|
190
|
+
warnings.warn(
|
|
191
|
+
f"Skipping index {index}: Out of valid range [0, {total_frames-1}]")
|
|
192
|
+
|
|
193
|
+
sorted_validated_indices = sorted(validated_indices)
|
|
194
|
+
sorted_frames = self.simple_decoder.get_batch_frames_by_index(sorted_validated_indices)
|
|
195
|
+
mapping = {val: sorted_frames[idx] for idx, val in enumerate(sorted_validated_indices)}
|
|
196
|
+
reordered_frames = [mapping[val] for val in validated_indices]
|
|
197
|
+
return reordered_frames
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def get_stream_metadata(self):
|
|
201
|
+
"""
|
|
202
|
+
Returns stream metadata
|
|
203
|
+
Returns:
|
|
204
|
+
StreamMetadata : StreamMetadata structure with metadata info
|
|
205
|
+
"""
|
|
206
|
+
stream_meta = self.simple_decoder.get_stream_metadata()
|
|
207
|
+
return stream_meta
|
|
208
|
+
|
|
209
|
+
def get_scanned_stream_metadata(self):
|
|
210
|
+
"""
|
|
211
|
+
Returns stream metadata by performing entitre stream scan.
|
|
212
|
+
Returns:
|
|
213
|
+
ScannedStreamMetadata : ScannedStreamMetadata structure with
|
|
214
|
+
metadata info
|
|
215
|
+
"""
|
|
216
|
+
if self.need_scanned_stream_metadata == 0:
|
|
217
|
+
raise Exception("Invalid call to funtion. Decoder was created with \
|
|
218
|
+
need_scanned_stream_metadata = 0.")
|
|
219
|
+
stream_meta = self.simple_decoder.get_scanned_stream_metadata()
|
|
220
|
+
return stream_meta
|
|
221
|
+
|
|
222
|
+
def seek_to_index(self, index):
|
|
223
|
+
"""
|
|
224
|
+
Moves the demuxer marker to the specified index
|
|
225
|
+
Args:
|
|
226
|
+
index(int): The index to which the marker needs to be set
|
|
227
|
+
"""
|
|
228
|
+
self.validate_index(index)
|
|
229
|
+
return self.simple_decoder.seek_to_index(index)
|
|
230
|
+
|
|
231
|
+
def get_index_from_time_in_seconds(self, time_in_seconds):
|
|
232
|
+
"""
|
|
233
|
+
Gives the frame index corresponding to the time in seconds
|
|
234
|
+
Args:
|
|
235
|
+
timestamp(float): Frame timestamp
|
|
236
|
+
"""
|
|
237
|
+
return self.simple_decoder.get_index_from_time_in_seconds(time_in_seconds)
|
|
238
|
+
|
|
239
|
+
def validate_index(self, index):
|
|
240
|
+
total_frames = self.__len__()
|
|
241
|
+
if not (0 <= index < total_frames):
|
|
242
|
+
raise IndexError(f"Index {index} is out of range [0, {total_frames-1}]")
|
|
243
|
+
|
|
244
|
+
def reconfigure_decoder(self, new_source):
|
|
245
|
+
"""
|
|
246
|
+
Reconfigures the current decoder to use the new source. Internally it
|
|
247
|
+
holds a cache of decoders. The best available is choosen else a new decoder is
|
|
248
|
+
created.
|
|
249
|
+
Args:
|
|
250
|
+
new_source(str): Encoded source
|
|
251
|
+
"""
|
|
252
|
+
return self.simple_decoder.reconfigure_decoder(new_source)
|
|
253
|
+
|
|
254
|
+
def get_session_init_time(self):
|
|
255
|
+
"""
|
|
256
|
+
Returns the session initialization time in milliseconds.
|
|
257
|
+
This is useful for performance measurement.
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
int64_t: Session initialization time in milliseconds
|
|
261
|
+
"""
|
|
262
|
+
return self.simple_decoder.get_session_init_time()
|
|
263
|
+
|
|
264
|
+
@staticmethod
|
|
265
|
+
def set_session_count(count):
|
|
266
|
+
"""
|
|
267
|
+
Sets the number of decoder sessions to be created.
|
|
268
|
+
This is useful for multi-threaded scenarios.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
count (int): Number of decoder sessions
|
|
272
|
+
"""
|
|
273
|
+
nvc.SimpleDecoder.set_session_count(count)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# This copyright notice applies to this file only
|
|
2
|
+
#
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
7
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
8
|
+
# to deal in the Software without restriction, including without limitation
|
|
9
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
10
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
11
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
12
|
+
#
|
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
|
14
|
+
# all copies or substantial portions of the Software.
|
|
15
|
+
#
|
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
19
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
21
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
22
|
+
# DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
from typing import Union
|
|
25
|
+
#from ._SimpleDecoderImpl import SimpleDecoderImpl
|
|
26
|
+
import PyNvVideoCodec as nvc
|
|
27
|
+
|
|
28
|
+
class ThreadedDecoder():
|
|
29
|
+
"""
|
|
30
|
+
Class for decoding video in background thread. This can be used for linear access in pipeines which are
|
|
31
|
+
CPU bound. In such a pipeline this decoder offes near zero frame fetch latency.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
enc_file_path (str): Encoded file path.
|
|
35
|
+
gpu_id (int) : gpu id on which to decode on.
|
|
36
|
+
cuda_context(pycuda._driver.Context): Cuda context under which the source is decoded
|
|
37
|
+
cuda_stream(pycuda._driver.Stream): Cuda stream used by the decoder
|
|
38
|
+
use_device_memory (bool): If set to 'True' output decoded frame is CUDeviceptr wrapped in CUDA Array Interface else its Host memory.
|
|
39
|
+
max_width(int): maximum width that the decoder must support
|
|
40
|
+
max_height(int): maximum height that the decoder must support
|
|
41
|
+
need_scanned_stream_metadata(bool): If set to 'True', the stream metadata will be collected by going through each of the packets. This
|
|
42
|
+
runs on a different thread and its running time depends on the total size of input stream.
|
|
43
|
+
decoder_cache_size(int): LRU cache size for the number of decoders to cache.
|
|
44
|
+
output_color_type (OutputColorType): Output format type of the decoded frames. By default it is OutputColorType::NATIVE which
|
|
45
|
+
implies decoded output is returned in native format(NV12, YUV444 etc.). Other supported values are OutputColorType::RGB(which implies
|
|
46
|
+
the decoded output is converted to interleaved RGB(HWC)) and OutputColorType::RGBP(which implies decoded output is converted to planar
|
|
47
|
+
RGB(CHW))
|
|
48
|
+
start_frame (int): Frame number to start decoding from (uses seeking)
|
|
49
|
+
enableDecodeStats (bool): If set to 'True', decode statistics will be available for each decoded frame. Default is False.
|
|
50
|
+
"""
|
|
51
|
+
def __init__(self, enc_file_path, buffer_size,
|
|
52
|
+
gpu_id = 0, cuda_context = 0,
|
|
53
|
+
cuda_stream = 0, use_device_memory = True,
|
|
54
|
+
max_width = 0,
|
|
55
|
+
max_height = 0,
|
|
56
|
+
need_scanned_stream_metadata = 0,
|
|
57
|
+
decoder_cache_size = 4,
|
|
58
|
+
output_color_type = nvc.OutputColorType.NATIVE,
|
|
59
|
+
start_frame = 0,
|
|
60
|
+
enableDecodeStats = False):
|
|
61
|
+
self.buffer_size = buffer_size
|
|
62
|
+
self.need_scanned_stream_metadata = need_scanned_stream_metadata
|
|
63
|
+
self.start_frame = start_frame
|
|
64
|
+
self.threaded_decoder = nvc.CreateThreadedDecoder(enc_file_path, buffer_size, gpu_id,
|
|
65
|
+
cuda_context, cuda_stream,
|
|
66
|
+
use_device_memory, max_width,
|
|
67
|
+
max_height, need_scanned_stream_metadata,
|
|
68
|
+
decoder_cache_size, output_color_type,
|
|
69
|
+
start_frame, enableDecodeStats)
|
|
70
|
+
|
|
71
|
+
def __len__(self):
|
|
72
|
+
stream_meta = self.threaded_decoder.get_stream_metadata()
|
|
73
|
+
if stream_meta.num_frames == 0 and self.need_scanned_stream_metadata == True:
|
|
74
|
+
scanned_stream_data = self.threaded_decoder.get_scanned_stream_metadata()
|
|
75
|
+
return scanned_stream_data.num_frames
|
|
76
|
+
return stream_meta.num_frames
|
|
77
|
+
|
|
78
|
+
def get_batch_frames(self, batch_size):
|
|
79
|
+
"""
|
|
80
|
+
Returns a batch of frames based on the batch size
|
|
81
|
+
Args:
|
|
82
|
+
batch_size(int): Number of frames in the batch
|
|
83
|
+
Returns:
|
|
84
|
+
DecodedFrame[] : A list of decoded frames
|
|
85
|
+
"""
|
|
86
|
+
if batch_size > self.buffer_size:
|
|
87
|
+
raise Exception(f"batch_size cannot be greater than buffer_size. \
|
|
88
|
+
Got batch_size = {batch_size} whereas buffer_size = {self.buffer_size}")
|
|
89
|
+
return self.threaded_decoder.get_batch_frames(batch_size)
|
|
90
|
+
|
|
91
|
+
def end(self):
|
|
92
|
+
return self.threaded_decoder.end()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def get_stream_metadata(self):
|
|
96
|
+
"""
|
|
97
|
+
Returns stream metadata
|
|
98
|
+
Returns:
|
|
99
|
+
StreamMetadata : StreamMetadata structure with metadata info
|
|
100
|
+
"""
|
|
101
|
+
stream_meta = self.threaded_decoder.get_stream_metadata()
|
|
102
|
+
return stream_meta
|
|
103
|
+
|
|
104
|
+
def get_scanned_stream_metadata(self):
|
|
105
|
+
"""
|
|
106
|
+
Returns stream metadata by performing entitre stream scan.
|
|
107
|
+
Returns:
|
|
108
|
+
ScannedStreamMetadata : ScannedStreamMetadata structure with
|
|
109
|
+
metadata info
|
|
110
|
+
"""
|
|
111
|
+
if self.need_scanned_stream_metadata == 0:
|
|
112
|
+
raise Exception("Invalid call to funtion. Decoder was created with \
|
|
113
|
+
need_scanned_stream_metadata = 0.")
|
|
114
|
+
stream_meta = self.threaded_decoder.get_scanned_stream_metadata()
|
|
115
|
+
return stream_meta
|
|
116
|
+
|
|
117
|
+
def reconfigure_decoder(self, new_source, start_frame=0):
|
|
118
|
+
"""
|
|
119
|
+
Reconfigures the current decoder to use the new source and optionally
|
|
120
|
+
start from a specific frame. Internally it holds a cache of decoders.
|
|
121
|
+
The best available is chosen else a new decoder is created.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
new_source (str): Path to new encoded video file
|
|
125
|
+
start_frame (int): Frame number to start decoding from (default 0).
|
|
126
|
+
Uses seeking to efficiently skip to the target frame.
|
|
127
|
+
"""
|
|
128
|
+
self.start_frame = start_frame
|
|
129
|
+
return self.threaded_decoder.reconfigure_decoder(new_source, start_frame)
|