mvdata 0.9.0__tar.gz

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 (59) hide show
  1. mvdata-0.9.0/PKG-INFO +52 -0
  2. mvdata-0.9.0/README.md +6 -0
  3. mvdata-0.9.0/mvdata/__init__.py +169 -0
  4. mvdata-0.9.0/mvdata/cloud_storage.py +204 -0
  5. mvdata-0.9.0/mvdata/codec/__init__.py +126 -0
  6. mvdata-0.9.0/mvdata/codec/_imports.py +42 -0
  7. mvdata-0.9.0/mvdata/codec/decode.py +123 -0
  8. mvdata-0.9.0/mvdata/codec/encode.py +313 -0
  9. mvdata-0.9.0/mvdata/codec/frames.py +199 -0
  10. mvdata-0.9.0/mvdata/codec/probe.py +239 -0
  11. mvdata-0.9.0/mvdata/codec/select.py +153 -0
  12. mvdata-0.9.0/mvdata/dataset_base.py +1049 -0
  13. mvdata-0.9.0/mvdata/downloader.py +806 -0
  14. mvdata-0.9.0/mvdata/gpu_policy.py +128 -0
  15. mvdata-0.9.0/mvdata/gpu_support.py +384 -0
  16. mvdata-0.9.0/mvdata/image_metrics.py +24 -0
  17. mvdata-0.9.0/mvdata/legacy_writer.py +371 -0
  18. mvdata-0.9.0/mvdata/multivideo.py +982 -0
  19. mvdata-0.9.0/mvdata/multivideo_decode_benchmark.py +216 -0
  20. mvdata-0.9.0/mvdata/multivideo_slicer.py +930 -0
  21. mvdata-0.9.0/mvdata/multivideo_writer.py +375 -0
  22. mvdata-0.9.0/mvdata/nvdec_parallel.py +145 -0
  23. mvdata-0.9.0/mvdata/nvenc_codec.py +17 -0
  24. mvdata-0.9.0/mvdata/per_frame.py +1799 -0
  25. mvdata-0.9.0/mvdata/ranged.py +1349 -0
  26. mvdata-0.9.0/mvdata/ranged_writer.py +964 -0
  27. mvdata-0.9.0/mvdata/stash_utils.py +358 -0
  28. mvdata-0.9.0/mvdata/utils.py +33 -0
  29. mvdata-0.9.0/mvdata/video_stream_reader.py +490 -0
  30. mvdata-0.9.0/mvdata/write_progress.py +213 -0
  31. mvdata-0.9.0/mvdata/writer_base.py +161 -0
  32. mvdata-0.9.0/mvdata.egg-info/PKG-INFO +52 -0
  33. mvdata-0.9.0/mvdata.egg-info/SOURCES.txt +57 -0
  34. mvdata-0.9.0/mvdata.egg-info/dependency_links.txt +1 -0
  35. mvdata-0.9.0/mvdata.egg-info/requires.txt +29 -0
  36. mvdata-0.9.0/mvdata.egg-info/top_level.txt +1 -0
  37. mvdata-0.9.0/pyproject.toml +160 -0
  38. mvdata-0.9.0/setup.cfg +4 -0
  39. mvdata-0.9.0/tests/test_dataset_base_defaults.py +203 -0
  40. mvdata-0.9.0/tests/test_gpu_policy.py +62 -0
  41. mvdata-0.9.0/tests/test_gpu_support.py +126 -0
  42. mvdata-0.9.0/tests/test_image_metrics.py +18 -0
  43. mvdata-0.9.0/tests/test_multivideo_bit_depth.py +102 -0
  44. mvdata-0.9.0/tests/test_multivideo_decode_benchmark.py +127 -0
  45. mvdata-0.9.0/tests/test_multivideo_slicer.py +223 -0
  46. mvdata-0.9.0/tests/test_nvdec_parallel.py +57 -0
  47. mvdata-0.9.0/tests/test_per_camera.py +44 -0
  48. mvdata-0.9.0/tests/test_ranged_mixed_streams.py +467 -0
  49. mvdata-0.9.0/tests/test_ranged_nvenc_roundtrip.py +468 -0
  50. mvdata-0.9.0/tests/test_ranged_resume.py +453 -0
  51. mvdata-0.9.0/tests/test_ranged_stream_discovery.py +27 -0
  52. mvdata-0.9.0/tests/test_roundtrip.py +385 -0
  53. mvdata-0.9.0/tests/test_s3_downloader.py +287 -0
  54. mvdata-0.9.0/tests/test_stash_bit_depth.py +114 -0
  55. mvdata-0.9.0/tests/test_stash_comprehensive.py +754 -0
  56. mvdata-0.9.0/tests/test_stash_policy.py +158 -0
  57. mvdata-0.9.0/tests/test_stash_regenerate.py +520 -0
  58. mvdata-0.9.0/tests/test_video_stream_reader.py +875 -0
  59. mvdata-0.9.0/tests/test_write_progress.py +33 -0
mvdata-0.9.0/PKG-INFO ADDED
@@ -0,0 +1,52 @@
1
+ Metadata-Version: 2.4
2
+ Name: mvdata
3
+ Version: 0.9.0
4
+ Summary: Gracia Dataset Convention - Python library for working with multi-view video datasets
5
+ Author: Gracia Team
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/gracia-labs/mvdata
8
+ Project-URL: Documentation, https://github.com/gracia-labs/mvdata/tree/main/docs
9
+ Project-URL: Repository, https://github.com/gracia-labs/mvdata
10
+ Project-URL: Issues, https://github.com/gracia-labs/mvdata/issues
11
+ Keywords: dataset,multi-view,video,avif,computer-vision
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Scientific/Engineering
19
+ Classifier: Topic :: Scientific/Engineering :: Image Processing
20
+ Classifier: Topic :: Multimedia :: Video
21
+ Requires-Python: <3.13,>=3.12
22
+ Description-Content-Type: text/markdown
23
+ Requires-Dist: numpy>=1.20.0
24
+ Requires-Dist: Pillow>=8.0.0
25
+ Requires-Dist: pyavif>=0.0.2
26
+ Requires-Dist: av>=10.0.0
27
+ Provides-Extra: pynvc
28
+ Requires-Dist: PyNvVideoCodec>=2.1.0; extra == "pynvc"
29
+ Provides-Extra: cuda
30
+ Requires-Dist: cupy-cuda12x>=13.0.0; extra == "cuda"
31
+ Provides-Extra: s3
32
+ Requires-Dist: boto3>=1.26.0; extra == "s3"
33
+ Provides-Extra: gcs
34
+ Requires-Dist: google-cloud-storage>=2.0.0; extra == "gcs"
35
+ Provides-Extra: cloud
36
+ Requires-Dist: boto3>=1.26.0; extra == "cloud"
37
+ Requires-Dist: google-cloud-storage>=2.0.0; extra == "cloud"
38
+ Provides-Extra: dev
39
+ Requires-Dist: ipykernel>=7.2.0; extra == "dev"
40
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
41
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
42
+ Requires-Dist: black>=22.0.0; extra == "dev"
43
+ Requires-Dist: isort>=5.10.0; extra == "dev"
44
+ Requires-Dist: mypy>=0.990; extra == "dev"
45
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
46
+
47
+ # mvdata
48
+
49
+ Python library for working with Gracia multi-view video datasets.
50
+
51
+ The package provides readers, writers, conversion tools, and GPU-aware video
52
+ decode helpers for the dataset layouts documented in the `docs` directory.
mvdata-0.9.0/README.md ADDED
@@ -0,0 +1,6 @@
1
+ # mvdata
2
+
3
+ Python library for working with Gracia multi-view video datasets.
4
+
5
+ The package provides readers, writers, conversion tools, and GPU-aware video
6
+ decode helpers for the dataset layouts documented in the `docs` directory.
@@ -0,0 +1,169 @@
1
+ """
2
+ Gracia Dataset Convention - Python library for working with multi-view video datasets.
3
+ """
4
+
5
+ from typing import Any
6
+
7
+ from .cloud_storage import CloudStorageProviderType, CloudStorageUrl
8
+ from .dataset_base import Dataset, FrameReader, auto_detect_dataset
9
+ from .downloader import (
10
+ DatasetDownloader,
11
+ LegacyDatasetDownloader,
12
+ MultiVideoDatasetDownloader,
13
+ RangedDatasetDownloader,
14
+ infer_and_download_dataset,
15
+ register_downloader,
16
+ )
17
+ from .legacy_writer import LegacyDatasetWriter
18
+ from .per_frame import LegacyDataset, LegacyFrameReader
19
+ from .writer_base import DatasetWriter
20
+ from .gpu_policy import (
21
+ clear_gpu_override,
22
+ gpu_disabled,
23
+ gpu_enabled,
24
+ gpu_enabled_override,
25
+ gpu_usage_allowed,
26
+ gpu_nvenc_disabled_globally,
27
+ nvdec_decode_allowed,
28
+ nvdec_decoding_usable,
29
+ nvenc_encode_allowed,
30
+ nvenc_encoding_usable,
31
+ pynvvideocodec_importable,
32
+ )
33
+ from .gpu_support import (
34
+ GpuDecodeMediaSupport,
35
+ GpuDecodeSupportReport,
36
+ gpu_decode_support,
37
+ supports_gpu_decode,
38
+ )
39
+ from .image_metrics import calculate_psnr
40
+ from .multivideo_decode_benchmark import (
41
+ MultiVideoDecodeBenchmarkResult,
42
+ NvdecRuntimeInfo,
43
+ benchmark_multivideo_decode,
44
+ inspect_nvdec_runtime,
45
+ summarize_multivideo_dataset,
46
+ )
47
+
48
+
49
+ def _build_missing_class(class_name: str, install_hint: str, import_error: Exception):
50
+ """Create a placeholder class that raises a clear dependency error when instantiated."""
51
+
52
+ def _raise_missing(self, *args: Any, **kwargs: Any) -> None:
53
+ raise ImportError(
54
+ f"{class_name} is unavailable because an optional dependency is missing. "
55
+ f"Install it with: {install_hint}"
56
+ ) from import_error
57
+
58
+ missing_class = type(class_name, (), {"__init__": _raise_missing})
59
+ missing_class.__module__ = __name__
60
+ return missing_class
61
+
62
+
63
+ try:
64
+ from .ranged import RangedDataset, RangedFrameReader, RangedMp4FrameReader
65
+ from .ranged_writer import RangedDatasetWriter, RangedNvencSettings
66
+ except Exception as exc: # pragma: no cover - exercised in environments without pyavif
67
+ RangedDataset = _build_missing_class("RangedDataset", "pip install pyavif", exc)
68
+ RangedFrameReader = _build_missing_class("RangedFrameReader", "pip install pyavif", exc)
69
+ RangedMp4FrameReader = _build_missing_class("RangedMp4FrameReader", "pip install pyavif", exc)
70
+ RangedDatasetWriter = _build_missing_class("RangedDatasetWriter", "pip install pyavif", exc)
71
+ RangedNvencSettings = _build_missing_class("RangedNvencSettings", "pip install pyavif", exc)
72
+
73
+ try:
74
+ from .multivideo import MultiVideoDataset, MultiVideoFrameReader
75
+ from .multivideo_writer import MultiVideoDatasetWriter
76
+ except Exception as exc: # pragma: no cover - exercised in environments without av/ffmpeg
77
+ MultiVideoDataset = _build_missing_class("MultiVideoDataset", "pip install av", exc)
78
+ MultiVideoFrameReader = _build_missing_class("MultiVideoFrameReader", "pip install av", exc)
79
+ MultiVideoDatasetWriter = _build_missing_class("MultiVideoDatasetWriter", "pip install av", exc)
80
+
81
+ try:
82
+ from .multivideo_slicer import (
83
+ MultiVideoSliceEligibility,
84
+ MultiVideoSliceEligibilityMetadata,
85
+ MultiVideoSliceError,
86
+ MultiVideoSlicePlan,
87
+ MultiVideoSliceRange,
88
+ MultiVideoStreamEligibility,
89
+ MultiVideoStreamSliceInfo,
90
+ MultiVideoToRangedSlicer,
91
+ )
92
+ except Exception as exc: # pragma: no cover - exercised in environments without av/pyavif
93
+ MultiVideoToRangedSlicer = _build_missing_class(
94
+ "MultiVideoToRangedSlicer", "pip install av pyavif", exc
95
+ )
96
+ MultiVideoSliceEligibility = _build_missing_class(
97
+ "MultiVideoSliceEligibility", "pip install av pyavif", exc
98
+ )
99
+ MultiVideoSliceEligibilityMetadata = _build_missing_class(
100
+ "MultiVideoSliceEligibilityMetadata", "pip install av pyavif", exc
101
+ )
102
+ MultiVideoSliceError = _build_missing_class(
103
+ "MultiVideoSliceError", "pip install av pyavif", exc
104
+ )
105
+ MultiVideoSlicePlan = _build_missing_class("MultiVideoSlicePlan", "pip install av pyavif", exc)
106
+ MultiVideoSliceRange = _build_missing_class(
107
+ "MultiVideoSliceRange", "pip install av pyavif", exc
108
+ )
109
+ MultiVideoStreamEligibility = _build_missing_class(
110
+ "MultiVideoStreamEligibility", "pip install av pyavif", exc
111
+ )
112
+ MultiVideoStreamSliceInfo = _build_missing_class(
113
+ "MultiVideoStreamSliceInfo", "pip install av pyavif", exc
114
+ )
115
+
116
+ __all__ = [
117
+ 'Dataset',
118
+ 'FrameReader',
119
+ 'auto_detect_dataset',
120
+ 'LegacyDataset',
121
+ 'LegacyFrameReader',
122
+ 'RangedDataset',
123
+ 'RangedFrameReader',
124
+ 'RangedMp4FrameReader',
125
+ 'MultiVideoDataset',
126
+ 'MultiVideoFrameReader',
127
+ 'DatasetWriter',
128
+ 'RangedDatasetWriter',
129
+ 'RangedNvencSettings',
130
+ 'LegacyDatasetWriter',
131
+ 'MultiVideoDatasetWriter',
132
+ 'MultiVideoToRangedSlicer',
133
+ 'MultiVideoSliceEligibility',
134
+ 'MultiVideoSliceEligibilityMetadata',
135
+ 'MultiVideoSliceError',
136
+ 'MultiVideoSlicePlan',
137
+ 'MultiVideoSliceRange',
138
+ 'MultiVideoStreamEligibility',
139
+ 'MultiVideoStreamSliceInfo',
140
+ 'calculate_psnr',
141
+ 'NvdecRuntimeInfo',
142
+ 'MultiVideoDecodeBenchmarkResult',
143
+ 'inspect_nvdec_runtime',
144
+ 'summarize_multivideo_dataset',
145
+ 'benchmark_multivideo_decode',
146
+ 'GpuDecodeMediaSupport',
147
+ 'GpuDecodeSupportReport',
148
+ 'gpu_decode_support',
149
+ 'supports_gpu_decode',
150
+ 'gpu_enabled',
151
+ 'clear_gpu_override',
152
+ 'gpu_disabled',
153
+ 'gpu_enabled_override',
154
+ 'gpu_usage_allowed',
155
+ 'gpu_nvenc_disabled_globally',
156
+ 'nvdec_decode_allowed',
157
+ 'nvenc_encode_allowed',
158
+ 'nvdec_decoding_usable',
159
+ 'nvenc_encoding_usable',
160
+ 'pynvvideocodec_importable',
161
+ 'DatasetDownloader',
162
+ 'RangedDatasetDownloader',
163
+ 'LegacyDatasetDownloader',
164
+ 'MultiVideoDatasetDownloader',
165
+ 'infer_and_download_dataset',
166
+ 'register_downloader',
167
+ 'CloudStorageUrl',
168
+ 'CloudStorageProviderType',
169
+ ]
@@ -0,0 +1,204 @@
1
+ from abc import ABC, abstractmethod
2
+ from enum import Enum
3
+ from pathlib import Path
4
+ from typing import List, Optional, Tuple
5
+ from urllib.parse import urlparse
6
+
7
+
8
+ class CloudStorageProviderType(Enum):
9
+ AWS_S3 = 's3'
10
+ GOOGLE_STORAGE = 'gs'
11
+
12
+
13
+ class CloudStorageUrl:
14
+ def __init__(self, url: str):
15
+ parsed = urlparse(url)
16
+ if parsed.scheme not in ("s3", "gs"):
17
+ raise ValueError(f"Invalid cloud URI scheme: {parsed.scheme}. Expected 's3' or 'gs'")
18
+
19
+ if not parsed.netloc:
20
+ raise ValueError(f"Invalid cloud URI: missing bucket name in {url}")
21
+
22
+ self.schema: str = parsed.scheme
23
+ self.bucket: str = parsed.netloc
24
+ path = parsed.path.lstrip("/")
25
+ if path and not path.endswith('/'):
26
+ path += "/"
27
+ self.path: str = path
28
+ self.cloud: CloudStorageProviderType = (
29
+ CloudStorageProviderType.AWS_S3
30
+ if parsed.scheme == "s3"
31
+ else CloudStorageProviderType.GOOGLE_STORAGE
32
+ )
33
+
34
+ @staticmethod
35
+ def from_parts(schema: str, bucket: str, path: str = "") -> "CloudStorageUrl":
36
+ path = path.lstrip("/")
37
+ return CloudStorageUrl(f"{schema}://{bucket}/{path}")
38
+
39
+
40
+ class BaseCloudStorageProvider(ABC):
41
+ @abstractmethod
42
+ def list_objects(self, bucket: str, prefix: str, delimiter: Optional[str] = None) -> Tuple[List[str], List[str]]:
43
+ pass
44
+
45
+ @abstractmethod
46
+ def download_file(self, bucket: str, key: str, local_path: Path) -> None:
47
+ pass
48
+
49
+
50
+ class AwsS3CloudStorageProvider(BaseCloudStorageProvider):
51
+ def __init__(self):
52
+ try:
53
+ import boto3
54
+ from botocore.exceptions import ClientError, NoCredentialsError
55
+ except ImportError as exc:
56
+ raise ImportError(
57
+ "boto3 is required for S3 support. "
58
+ "Install it with: pip install mvdata[s3]"
59
+ ) from exc
60
+
61
+ self._ClientError = ClientError
62
+
63
+ try:
64
+ self._client = boto3.client("s3")
65
+ except NoCredentialsError as exc:
66
+ raise RuntimeError(
67
+ "AWS credentials not found. Configure credentials using AWS CLI, "
68
+ "environment variables, or IAM role."
69
+ ) from exc
70
+
71
+ def list_objects(self, bucket: str, prefix: str, delimiter: Optional[str] = None) -> Tuple[List[str], List[str]]:
72
+ objects = []
73
+ common_prefixes = []
74
+
75
+ try:
76
+ paginator = self._client.get_paginator("list_objects_v2")
77
+
78
+ page_kwargs = {
79
+ "Bucket": bucket,
80
+ "Prefix": prefix,
81
+ }
82
+ if delimiter:
83
+ page_kwargs["Delimiter"] = delimiter
84
+
85
+ for page in paginator.paginate(**page_kwargs):
86
+ if "Contents" in page:
87
+ objects.extend([obj["Key"] for obj in page["Contents"]])
88
+
89
+ if delimiter and "CommonPrefixes" in page:
90
+ common_prefixes.extend([cp["Prefix"] for cp in page["CommonPrefixes"]])
91
+
92
+ return objects, common_prefixes
93
+
94
+ except self._ClientError as e:
95
+ raise RuntimeError(f"S3 access error: {e}") from e
96
+
97
+ def download_file(self, bucket: str, key: str, local_path: Path) -> None:
98
+ local_path.parent.mkdir(parents=True, exist_ok=True)
99
+
100
+ try:
101
+ self._client.download_file(bucket, key, str(local_path))
102
+ except self._ClientError as e:
103
+ raise RuntimeError(f"S3 download error for {key}: {e}") from e
104
+
105
+
106
+ class GoogleStorageCloudStorageProvider(BaseCloudStorageProvider):
107
+ def __init__(self):
108
+ try:
109
+ from google.cloud import storage
110
+ except ImportError as exc:
111
+ raise ImportError(
112
+ "google-cloud-storage is required for GCS support. "
113
+ "Install it with: pip install mvdata[gcs]"
114
+ ) from exc
115
+
116
+ from google.api_core.exceptions import Forbidden, NotFound, GoogleAPICallError # type: ignore[import-untyped]
117
+ from google.auth.exceptions import DefaultCredentialsError # type: ignore[import-untyped]
118
+
119
+ self._Forbidden = Forbidden
120
+ self._NotFound = NotFound
121
+ self._GoogleAPICallError = GoogleAPICallError
122
+
123
+ try:
124
+ self._client = storage.Client()
125
+ except DefaultCredentialsError as exc:
126
+ raise RuntimeError(
127
+ "GCS credentials not found. Configure credentials using "
128
+ "GOOGLE_APPLICATION_CREDENTIALS environment variable, "
129
+ "gcloud auth, or service account."
130
+ ) from exc
131
+
132
+ def list_objects(self, bucket: str, prefix: str, delimiter: Optional[str] = None) -> Tuple[List[str], List[str]]:
133
+ objects = []
134
+ common_prefixes = []
135
+
136
+ try:
137
+ bucket_obj = self._client.bucket(bucket)
138
+ blobs = self._client.list_blobs(bucket_obj, prefix=prefix, delimiter=delimiter)
139
+
140
+ for blob in blobs:
141
+ objects.append(blob.name)
142
+
143
+ if delimiter:
144
+ common_prefixes = list(blobs.prefixes)
145
+
146
+ return objects, common_prefixes
147
+
148
+ except (self._Forbidden, self._NotFound) as e:
149
+ raise RuntimeError(f"GCS access error: {e}") from e
150
+ except self._GoogleAPICallError as e:
151
+ raise RuntimeError(f"GCS access error: {e}") from e
152
+
153
+ def download_file(self, bucket: str, key: str, local_path: Path) -> None:
154
+ local_path.parent.mkdir(parents=True, exist_ok=True)
155
+
156
+ try:
157
+ bucket_obj = self._client.bucket(bucket)
158
+ blob = bucket_obj.blob(key)
159
+ blob.download_to_filename(str(local_path))
160
+ except (self._Forbidden, self._NotFound) as e:
161
+ raise RuntimeError(f"GCS download error for {key}: {e}") from e
162
+ except self._GoogleAPICallError as e:
163
+ raise RuntimeError(f"GCS download error for {key}: {e}") from e
164
+
165
+
166
+ class CloudStorageProvider:
167
+ _providers: dict[CloudStorageProviderType, BaseCloudStorageProvider] = {}
168
+
169
+ @classmethod
170
+ def _get_provider(cls, provider_type: CloudStorageProviderType) -> BaseCloudStorageProvider:
171
+ if provider_type not in cls._providers:
172
+ if provider_type == CloudStorageProviderType.AWS_S3:
173
+ cls._providers[provider_type] = AwsS3CloudStorageProvider()
174
+ else:
175
+ cls._providers[provider_type] = GoogleStorageCloudStorageProvider()
176
+ return cls._providers[provider_type]
177
+
178
+ @classmethod
179
+ def list_objects(cls, url: CloudStorageUrl, delimiter: Optional[str] = None) -> Tuple[List[str], List[str]]:
180
+ """
181
+ List objects in cloud storage bucket with given prefix.
182
+
183
+ Args:
184
+ url: CloudStorageUrl with schema, bucket, and path prefix
185
+ delimiter: Optional delimiter for hierarchical listing (e.g., '/')
186
+
187
+ Returns:
188
+ Tuple of (object_keys, common_prefixes)
189
+ """
190
+ provider = cls._get_provider(url.cloud)
191
+ return provider.list_objects(url.bucket, url.path, delimiter)
192
+
193
+ @classmethod
194
+ def download_file(cls, url: CloudStorageUrl, local_path: Path) -> None:
195
+ """
196
+ Download a single file from cloud storage.
197
+
198
+ Args:
199
+ url: CloudStorageUrl with schema, bucket, and path to the object
200
+ local_path: Local file path to save to
201
+ """
202
+ key = url.path.rstrip("/")
203
+ provider = cls._get_provider(url.cloud)
204
+ provider.download_file(url.bucket, key, local_path)
@@ -0,0 +1,126 @@
1
+ """NVENC / NVDEC / PyAV codec helpers for mvdata.
2
+
3
+ This subpackage is split into small modules:
4
+
5
+ - :mod:`mvdata.codec._imports` – lazy optional-dep imports and constants
6
+ - :mod:`mvdata.codec.probe` – bit-depth probing and GPU capability checks
7
+ - :mod:`mvdata.codec.frames` – decoded-frame → HWC RGB (numpy and cupy)
8
+ - :mod:`mvdata.codec.encode` – RGB → NVENC elementary stream → MP4
9
+ - :mod:`mvdata.codec.decode` – NVDEC / PyAV decode of MP4
10
+ - :mod:`mvdata.codec.select` – round-trip PSNR and auto codec selection
11
+
12
+ The flat namespace here is preserved for backward compatibility with callers
13
+ that use ``from mvdata.codec import X`` or the top-level ``mvdata.nvenc_codec``
14
+ shim.
15
+ """
16
+
17
+ from ._imports import (
18
+ AUTO_NVENC_CHROMA_SUBSAMPLING,
19
+ AUTO_NVENC_CODEC_CANDIDATES,
20
+ AUTO_NVENC_MIN_PSNR_DB,
21
+ AUTO_NVENC_PRESET,
22
+ AUTO_NVENC_RANGE_MODE,
23
+ _PYNVC_API_REF,
24
+ normalize_codec_name,
25
+ try_import_av,
26
+ try_import_cupy,
27
+ try_import_pynvvideocodec,
28
+ try_import_torch,
29
+ )
30
+ from .decode import (
31
+ _try_open_nvdec,
32
+ decode_mp4_to_rgb,
33
+ decode_mp4_to_rgb_nvdec,
34
+ decode_mp4_to_rgb_pyav,
35
+ preferred_decoder_for_mp4,
36
+ )
37
+ from .encode import (
38
+ assert_yuv444_encode_supported,
39
+ best_quality_extra_kwargs,
40
+ create_nvenc_encoder,
41
+ encode_rgb_frames_to_elementary_stream,
42
+ encode_rgb_iter_to_elementary_stream,
43
+ encode_rgb_iter_to_mp4,
44
+ encode_rgb_sequence_to_mp4,
45
+ ffmpeg_executable,
46
+ mux_elementary_to_mp4,
47
+ pad_rgb_for_nvenc,
48
+ resolve_nvenc_bitrate_bps,
49
+ rgb_hwc_to_nvenc_bgra_hwc4,
50
+ rgb_hwc_to_yuv444_planar,
51
+ suggest_nvenc_vbr_bitrate_bps,
52
+ )
53
+ from .frames import (
54
+ _decoded_frame_to_rgb_cupy,
55
+ _decoded_frame_to_rgb_numpy,
56
+ _normalize_rgb_numpy_output,
57
+ _pyav_frame_to_rgb,
58
+ numpy_to_cupy_rgb,
59
+ )
60
+ from .probe import (
61
+ infer_video_bit_depth_from_frame,
62
+ infer_video_bit_depth_from_pixel_format_name,
63
+ infer_video_bit_depth_from_stream,
64
+ infer_video_bit_depth_from_video_format,
65
+ nvdec_decode_compatibility_issue,
66
+ nvdec_decode_compatibility_issue_for_path,
67
+ nvenc_encode_compatibility_issue,
68
+ nvenc_encode_compatibility_issue_for_rgb,
69
+ probe_video_bit_depth,
70
+ probe_video_bit_depth_pyav,
71
+ probe_video_stream_metadata,
72
+ pynvc_chroma_format,
73
+ pynvc_codec_enum,
74
+ safe_get_decoder_caps,
75
+ safe_get_encoder_caps,
76
+ )
77
+ from .select import roundtrip_nvenc_stream_psnr, select_auto_nvenc_candidate_for_rgb
78
+
79
+ __all__ = [
80
+ "AUTO_NVENC_CHROMA_SUBSAMPLING",
81
+ "AUTO_NVENC_CODEC_CANDIDATES",
82
+ "AUTO_NVENC_MIN_PSNR_DB",
83
+ "AUTO_NVENC_PRESET",
84
+ "AUTO_NVENC_RANGE_MODE",
85
+ "assert_yuv444_encode_supported",
86
+ "best_quality_extra_kwargs",
87
+ "create_nvenc_encoder",
88
+ "decode_mp4_to_rgb",
89
+ "decode_mp4_to_rgb_nvdec",
90
+ "decode_mp4_to_rgb_pyav",
91
+ "encode_rgb_frames_to_elementary_stream",
92
+ "encode_rgb_iter_to_elementary_stream",
93
+ "encode_rgb_iter_to_mp4",
94
+ "encode_rgb_sequence_to_mp4",
95
+ "ffmpeg_executable",
96
+ "infer_video_bit_depth_from_frame",
97
+ "infer_video_bit_depth_from_pixel_format_name",
98
+ "infer_video_bit_depth_from_stream",
99
+ "infer_video_bit_depth_from_video_format",
100
+ "mux_elementary_to_mp4",
101
+ "normalize_codec_name",
102
+ "numpy_to_cupy_rgb",
103
+ "nvdec_decode_compatibility_issue",
104
+ "nvdec_decode_compatibility_issue_for_path",
105
+ "nvenc_encode_compatibility_issue",
106
+ "nvenc_encode_compatibility_issue_for_rgb",
107
+ "pad_rgb_for_nvenc",
108
+ "preferred_decoder_for_mp4",
109
+ "probe_video_bit_depth",
110
+ "probe_video_bit_depth_pyav",
111
+ "probe_video_stream_metadata",
112
+ "pynvc_chroma_format",
113
+ "pynvc_codec_enum",
114
+ "resolve_nvenc_bitrate_bps",
115
+ "rgb_hwc_to_nvenc_bgra_hwc4",
116
+ "rgb_hwc_to_yuv444_planar",
117
+ "roundtrip_nvenc_stream_psnr",
118
+ "safe_get_decoder_caps",
119
+ "safe_get_encoder_caps",
120
+ "select_auto_nvenc_candidate_for_rgb",
121
+ "suggest_nvenc_vbr_bitrate_bps",
122
+ "try_import_av",
123
+ "try_import_cupy",
124
+ "try_import_pynvvideocodec",
125
+ "try_import_torch",
126
+ ]
@@ -0,0 +1,42 @@
1
+ """Lazy optional-dependency imports and codec name normalisation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ from typing import Any
7
+
8
+ _PYNVC_API_REF = "PyNvVideoCodec_API_Reference.pdf"
9
+
10
+ AUTO_NVENC_CODEC_CANDIDATES = ("av1", "hevc", "h264")
11
+ AUTO_NVENC_CHROMA_SUBSAMPLING = "444"
12
+ AUTO_NVENC_PRESET = "P7"
13
+ AUTO_NVENC_RANGE_MODE = "full"
14
+ AUTO_NVENC_MIN_PSNR_DB = 38.0
15
+
16
+
17
+ def try_import_pynvvideocodec() -> Any:
18
+ return importlib.import_module("PyNvVideoCodec")
19
+
20
+
21
+ def _try_import(name: str) -> Any | None:
22
+ try:
23
+ return importlib.import_module(name)
24
+ except ImportError:
25
+ return None
26
+
27
+
28
+ def try_import_av() -> Any | None:
29
+ return _try_import("av")
30
+
31
+
32
+ def try_import_torch() -> Any | None:
33
+ return _try_import("torch")
34
+
35
+
36
+ def try_import_cupy() -> Any | None:
37
+ return _try_import("cupy")
38
+
39
+
40
+ def normalize_codec_name(codec: str) -> str:
41
+ c = codec.strip().lower()
42
+ return "hevc" if c == "h265" else c