PyNvVideoCodec 2.2.1__cp311-cp311-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.
Files changed (63) hide show
  1. PyNvVideoCodec/PyNvVideoCodec_121.cp311-win_arm64.pyd +0 -0
  2. PyNvVideoCodec/PyNvVideoCodec_130.cp311-win_arm64.pyd +0 -0
  3. PyNvVideoCodec/VersionCheck.cp311-win_arm64.pyd +0 -0
  4. PyNvVideoCodec/__init__.py +251 -0
  5. PyNvVideoCodec/avformat-62.dll +0 -0
  6. PyNvVideoCodec/avutil-60.dll +0 -0
  7. PyNvVideoCodec/decoders/SimpleDecoder.py +273 -0
  8. PyNvVideoCodec/decoders/ThreadedDecoder.py +129 -0
  9. PyNvVideoCodec/decoders/__init__.py +22 -0
  10. PyNvVideoCodec/transcoder/Transcoder.py +55 -0
  11. PyNvVideoCodec/transcoder/__init__.py +22 -0
  12. benchmarks/cached_decoder_benchmark.py +813 -0
  13. benchmarks/frame_sampling_benchmark.py +765 -0
  14. benchmarks/requirements.txt +52 -0
  15. benchmarks/segmented_transcode_benchmark.py +1546 -0
  16. benchmarks/setup_pycuda_win_arm64.bat +531 -0
  17. pynvvideocodec-2.2.1.data/data/external/ffmpeg/readme.txt +39 -0
  18. pynvvideocodec-2.2.1.data/data/external/ffmpeg/src/ffmpeg-8.1.2.tar.xz +0 -0
  19. pynvvideocodec-2.2.1.dist-info/METADATA +97 -0
  20. pynvvideocodec-2.2.1.dist-info/RECORD +63 -0
  21. pynvvideocodec-2.2.1.dist-info/WHEEL +5 -0
  22. pynvvideocodec-2.2.1.dist-info/licenses/LICENSE.txt +22 -0
  23. pynvvideocodec-2.2.1.dist-info/licenses/NOTICES.txt +167 -0
  24. pynvvideocodec-2.2.1.dist-info/top_level.txt +3 -0
  25. samples/advanced/decode.py +173 -0
  26. samples/advanced/decode_from_memory_buffer.py +204 -0
  27. samples/advanced/decode_perf.py +299 -0
  28. samples/advanced/decode_reconfigure.py +184 -0
  29. samples/advanced/decode_sei_msg.py +193 -0
  30. samples/advanced/decode_with_cuda_control.py +214 -0
  31. samples/advanced/decode_with_low_latency.py +183 -0
  32. samples/advanced/encode_config.json +12 -0
  33. samples/advanced/encode_config_lowlatency.json +13 -0
  34. samples/advanced/encode_perf.py +338 -0
  35. samples/advanced/encode_reconfigure.py +211 -0
  36. samples/advanced/encode_sei_msg.py +188 -0
  37. samples/advanced/simple_decode_from_memory.py +151 -0
  38. samples/advanced/simple_decode_stats.py +179 -0
  39. samples/advanced/transcode.py +288 -0
  40. samples/basic/create_video_segments.py +187 -0
  41. samples/basic/encode.py +574 -0
  42. samples/basic/segments.txt +3 -0
  43. samples/basic/simple_decode_sampling.py +182 -0
  44. samples/basic/transcode_config.json +6 -0
  45. samples/jupyter/ReconfigureDecoder.png +0 -0
  46. samples/jupyter/SimpleDecoderAPIs.png +0 -0
  47. samples/jupyter/ThreadedDecoder.png +0 -0
  48. samples/jupyter/coco_classes.pickle +0 -0
  49. samples/jupyter/face_protect_tutorial.ipynb +881 -0
  50. samples/jupyter/object_detection_tutorial.ipynb +781 -0
  51. samples/jupyter/simple_decode_tutorial.ipynb +674 -0
  52. samples/requirements.txt +84 -0
  53. samples/setup_pycuda_win_arm64.bat +531 -0
  54. samples/utils/Utils.py +405 -0
  55. samples/utils/__init__.py +36 -0
  56. samples/utils/decode_parser.py +642 -0
  57. samples/utils/decode_stats_writer.py +177 -0
  58. samples/utils/encode_parallel_utils.py +174 -0
  59. samples/utils/encode_parser.py +379 -0
  60. samples/utils/frame_utils.py +89 -0
  61. samples/utils/sei_message_parser.py +223 -0
  62. samples/utils/sei_parser.py +223 -0
  63. samples/utils/transcode_parser.py +218 -0
@@ -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)