|
2 | 2 |
|
3 | 3 | import numpy as np
|
4 | 4 | from vsexprtools import norm_expr
|
5 |
| -from vstools import (CustomValueError, FuncExceptT, FunctionUtil, SPath, |
6 |
| - SPathLike, clip_async_render, core, fallback, get_depth, |
7 |
| - initialize_clip, vs) |
| 5 | +from vstools import (CustomValueError, FileWasNotFoundError, FuncExceptT, |
| 6 | + FunctionUtil, SPath, SPathLike, clip_async_render, core, |
| 7 | + fallback, get_depth, initialize_clip, vs) |
8 | 8 |
|
| 9 | +from ..exceptions import NumpyArrayLoadError |
9 | 10 | from .util import get_format_from_npy
|
10 | 11 |
|
11 | 12 | __all__: list[str] = [
|
@@ -183,100 +184,116 @@ def npy_to_clip(
|
183 | 184 | :param func_except: Function returned for custom error handling.
|
184 | 185 | This should only be set by VS package developers.
|
185 | 186 |
|
186 |
| - :return: The numpy array as a clip. |
| 187 | + :return: VapourSynth clip created from the numpy files. |
187 | 188 | """
|
188 | 189 |
|
189 | 190 | func = fallback(func_except, npy_to_clip)
|
| 191 | + paths = [SPath(file_paths)] if not isinstance(file_paths, list) else [SPath(x) for x in file_paths] |
190 | 192 | is_npz = False
|
191 | 193 |
|
192 |
| - if not isinstance(file_paths, list): |
193 |
| - file_paths_list = [SPath(file_paths)] |
194 |
| - else: |
195 |
| - file_paths_list = [SPath(fp) for fp in file_paths] |
196 |
| - |
197 |
| - if file_paths_list and file_paths_list[0].is_dir(): |
198 |
| - file_paths_list = list(file_paths_list[0].glob("*.np[yz]")) |
| 194 | + if not paths: |
| 195 | + raise FileWasNotFoundError("No files provided!", func) |
199 | 196 |
|
200 |
| - if not file_paths_list: |
201 |
| - raise CustomValueError("No .npy or .npz files found in the directory", func) |
202 |
| - elif not file_paths_list: |
203 |
| - raise CustomValueError("No files provided", func) |
204 |
| - |
205 |
| - is_npz = file_paths_list[0].suffix == '.npz' |
| 197 | + if paths[0].is_dir(): |
| 198 | + if npy_files := list(paths[0].glob("*.npy")): |
| 199 | + paths = npy_files |
| 200 | + elif npz_files := list(paths[0].glob("*.npz")): |
| 201 | + paths = npz_files |
| 202 | + is_npz = True |
| 203 | + else: |
| 204 | + raise FileWasNotFoundError("No .npy or .npz files found in the given directory!", func) |
| 205 | + elif paths[0].suffix == '.npz': |
| 206 | + is_npz = True |
206 | 207 |
|
207 | 208 | if not is_npz:
|
208 |
| - file_paths_list = sorted( |
209 |
| - file_paths_list, key=lambda x: int(x.stem) if x.stem.isdigit() else float('inf') |
210 |
| - ) |
| 209 | + try: |
| 210 | + paths = sorted(paths, key=lambda x: int(x.stem) if x.stem.isdigit() else float('inf')) |
| 211 | + except ValueError as e: |
| 212 | + if "invalid literal for int() with base 10" in str(e): |
| 213 | + raise CustomValueError("Error sorting paths: File names must be valid integers!", func) |
| 214 | + else: |
| 215 | + raise |
| 216 | + except Exception as e: |
| 217 | + raise CustomValueError(f"Error sorting paths! {str(e)}", func) |
211 | 218 |
|
212 | 219 | if is_npz:
|
213 |
| - with np.load(file_paths_list[0], allow_pickle=True) as npz_data: |
214 |
| - first_frame = next(iter(npz_data.values())).item() |
| 220 | + npz_data = np.load(paths[0], allow_pickle=True) |
| 221 | + first_key = list(npz_data.keys())[0] |
| 222 | + first_frame = npz_data[first_key] |
| 223 | + |
| 224 | + if isinstance(first_frame, np.ndarray) and first_frame.shape == (): |
| 225 | + first_frame = first_frame.item() |
215 | 226 | else:
|
216 |
| - first_frame = np.load(file_paths_list[0], allow_pickle=True).item() |
| 227 | + first_frame = np.load(paths[0], allow_pickle=True).item() |
217 | 228 |
|
218 | 229 | if isinstance(first_frame, dict):
|
219 |
| - plane_0 = first_frame.get('plane_0') |
| 230 | + plane_0_missing = 'plane_0' not in first_frame |
| 231 | + plane_0_not_array = not isinstance(first_frame['plane_0'], np.ndarray) |
| 232 | + plane_0_low_dim = first_frame['plane_0'].ndim < 2 |
220 | 233 |
|
221 |
| - if not isinstance(plane_0, np.ndarray) or plane_0.ndim < 2: |
222 |
| - raise CustomValueError( |
223 |
| - "Invalid frame data structure. 'plane_0' is missing, not a numpy array, " |
224 |
| - "or has less than 2 dimensions!", func |
225 |
| - ) |
| 234 | + if plane_0_missing or plane_0_not_array or plane_0_low_dim: |
| 235 | + error_message = "Invalid frame data structure." |
| 236 | + |
| 237 | + if plane_0_missing: |
| 238 | + error_message += " 'plane_0' is missing from the frame dictionary." |
| 239 | + elif plane_0_not_array: |
| 240 | + error_message += " 'plane_0' is not a numpy array." |
| 241 | + elif plane_0_low_dim: |
| 242 | + error_message += " 'plane_0' has less than 2 dimensions." |
226 | 243 |
|
227 |
| - height, width = plane_0.shape |
| 244 | + raise NumpyArrayLoadError(error_message, func) |
| 245 | + |
| 246 | + height, width = first_frame['plane_0'].shape |
228 | 247 | elif isinstance(first_frame, np.ndarray):
|
229 | 248 | if first_frame.ndim < 2:
|
230 |
| - raise CustomValueError( |
231 |
| - f"Invalid frame shape: {first_frame.shape}. Expected at least 2 dimensions!", func |
| 249 | + raise NumpyArrayLoadError( |
| 250 | + f"Invalid frame shape: {first_frame.shape}. Expected at least 2 dimensions.", func |
232 | 251 | )
|
233 | 252 |
|
234 | 253 | height, width = first_frame.shape[:2]
|
235 | 254 | else:
|
236 |
| - raise CustomValueError(f"Unsupported frame data type: {type(first_frame)}", func) |
| 255 | + raise NumpyArrayLoadError(f"Unsupported frame data type: {type(first_frame)}", func) |
237 | 256 |
|
238 | 257 | fmt = get_format_from_npy(first_frame)
|
239 | 258 |
|
| 259 | + clip_length = len(npz_data.keys()) if is_npz else len(paths) |
240 | 260 | blank_clip = core.std.BlankClip(
|
241 |
| - None, width, height, fmt, |
242 |
| - length=len(npz_data.keys()) if is_npz else len(file_paths_list), |
243 |
| - keep=True |
| 261 | + None, width, height, fmt, length=clip_length, keep=True |
244 | 262 | )
|
245 | 263 |
|
246 | 264 | def _read_frame(n: int, f: vs.VideoFrame) -> vs.VideoFrame:
|
247 | 265 | if is_npz:
|
248 |
| - loaded_frame = next(iter(npz_data.values())) |
| 266 | + loaded_frame = npz_data[list(npz_data.keys())[n]] |
| 267 | + if isinstance(loaded_frame, np.ndarray) and loaded_frame.shape == (): |
| 268 | + loaded_frame = loaded_frame.item() |
249 | 269 | else:
|
250 |
| - loaded_frame = np.load(file_paths_list[n], allow_pickle=True) |
251 |
| - |
252 |
| - loaded_frame = loaded_frame.item() |
| 270 | + loaded_frame = np.load(paths[n], allow_pickle=True).item() |
253 | 271 |
|
254 | 272 | fout = f.copy()
|
255 | 273 |
|
256 | 274 | if isinstance(loaded_frame, np.ndarray):
|
257 | 275 | if loaded_frame.ndim < 2:
|
258 |
| - raise CustomValueError( |
259 |
| - f"Invalid frame shape at index {n}: {loaded_frame.shape}. " |
260 |
| - "Expected at least 2 dimensions!", func |
| 276 | + raise NumpyArrayLoadError( |
| 277 | + f"Invalid frame shape at index {n}: {loaded_frame.shape}. Expected at least 2 dimensions.", func |
261 | 278 | )
|
| 279 | + |
262 | 280 | np.copyto(np.asarray(fout[0]), loaded_frame)
|
263 | 281 | elif isinstance(loaded_frame, dict):
|
264 | 282 | for plane in range(f.format.num_planes):
|
265 | 283 | plane_key = f'plane_{plane}'
|
266 |
| - plane_data = loaded_frame.get(plane_key) |
267 | 284 |
|
268 |
| - if plane_data is None or plane_data.ndim < 2: |
269 |
| - raise CustomValueError( |
| 285 | + if plane_key not in loaded_frame or loaded_frame[plane_key].ndim < 2: |
| 286 | + raise NumpyArrayLoadError( |
270 | 287 | f"Invalid frame data structure at index {n}. '{plane_key}' "
|
271 |
| - "is missing or has less than 2 dimensions!", func=func |
| 288 | + "is missing or has less than 2 dimensions.", func |
272 | 289 | )
|
273 | 290 |
|
274 |
| - np.copyto(np.asarray(fout[plane]), plane_data) |
| 291 | + np.copyto(np.asarray(fout[plane]), loaded_frame[plane_key]) |
275 | 292 | else:
|
276 |
| - raise CustomValueError(f"Unsupported frame data type at index {n}: {type(loaded_frame)}", func=func) |
| 293 | + raise CustomValueError(f"Unsupported frame data type at index {n}: {type(loaded_frame)}", func) |
277 | 294 |
|
278 | 295 | return fout
|
279 | 296 |
|
280 | 297 | proc_clip = blank_clip.std.ModifyFrame(blank_clip, _read_frame)
|
281 | 298 |
|
282 |
| - return initialize_clip(proc_clip, ref or get_depth(proc_clip), func=func) |
| 299 | + return initialize_clip(proc_clip, ref or get_depth(proc_clip), func=func) # type:ignore[arg-type] |
0 commit comments