numpyimage 3.0.2__tar.gz → 3.1.1__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.
- {numpyimage-3.0.2/numpyimage.egg-info → numpyimage-3.1.1}/PKG-INFO +1 -1
- {numpyimage-3.0.2 → numpyimage-3.1.1}/npimage/vidio.py +42 -16
- {numpyimage-3.0.2 → numpyimage-3.1.1/numpyimage.egg-info}/PKG-INFO +1 -1
- {numpyimage-3.0.2 → numpyimage-3.1.1}/pyproject.toml +1 -1
- {numpyimage-3.0.2 → numpyimage-3.1.1}/LICENSE +0 -0
- {numpyimage-3.0.2 → numpyimage-3.1.1}/README.md +0 -0
- {numpyimage-3.0.2 → numpyimage-3.1.1}/npimage/__init__.py +0 -0
- {numpyimage-3.0.2 → numpyimage-3.1.1}/npimage/align.py +0 -0
- {numpyimage-3.0.2 → numpyimage-3.1.1}/npimage/graphics.py +0 -0
- {numpyimage-3.0.2 → numpyimage-3.1.1}/npimage/imageio.py +0 -0
- {numpyimage-3.0.2 → numpyimage-3.1.1}/npimage/nrrd_utils.py +0 -0
- {numpyimage-3.0.2 → numpyimage-3.1.1}/npimage/operations.py +0 -0
- {numpyimage-3.0.2 → numpyimage-3.1.1}/npimage/utils.py +0 -0
- {numpyimage-3.0.2 → numpyimage-3.1.1}/numpyimage.egg-info/SOURCES.txt +0 -0
- {numpyimage-3.0.2 → numpyimage-3.1.1}/numpyimage.egg-info/dependency_links.txt +0 -0
- {numpyimage-3.0.2 → numpyimage-3.1.1}/numpyimage.egg-info/requires.txt +0 -0
- {numpyimage-3.0.2 → numpyimage-3.1.1}/numpyimage.egg-info/top_level.txt +0 -0
- {numpyimage-3.0.2 → numpyimage-3.1.1}/setup.cfg +0 -0
- {numpyimage-3.0.2 → numpyimage-3.1.1}/tests/test_heic.py +0 -0
- {numpyimage-3.0.2 → numpyimage-3.1.1}/tests/test_pbm.py +0 -0
- {numpyimage-3.0.2 → numpyimage-3.1.1}/tests/test_video_writers.py +0 -0
|
@@ -161,6 +161,7 @@ class VideoStreamer:
|
|
|
161
161
|
self.container = av.open(str(self.filename))
|
|
162
162
|
self.stream = self.container.streams.video[0]
|
|
163
163
|
self.time_base = self.stream.time_base
|
|
164
|
+
self._frame_iterator = self.container.decode(self.stream)
|
|
164
165
|
self._shape = None
|
|
165
166
|
self._first_frame = None
|
|
166
167
|
self._width = None
|
|
@@ -201,14 +202,14 @@ class VideoStreamer:
|
|
|
201
202
|
frames_pts = []
|
|
202
203
|
# Fallback to ffprobe if PyAV didn't work
|
|
203
204
|
cmd = ['ffprobe',
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
205
|
+
'-select_streams', 'v:0',
|
|
206
|
+
'-show_frames',
|
|
207
|
+
'-show_entries', 'frame=pts',
|
|
208
|
+
'-of', 'default=noprint_wrappers=1:nokey=1',
|
|
209
|
+
'-v', 'quiet',
|
|
210
|
+
self.filename]
|
|
210
211
|
ffprobe_result = subprocess.run(cmd, stdout=subprocess.PIPE,
|
|
211
|
-
|
|
212
|
+
stderr=subprocess.PIPE, text=True)
|
|
212
213
|
if ffprobe_result.returncode != 0:
|
|
213
214
|
raise RuntimeError(f'ffprobe failed: {ffprobe_result.stderr}')
|
|
214
215
|
|
|
@@ -322,6 +323,22 @@ class VideoStreamer:
|
|
|
322
323
|
def frame_number_to_time(self, frame_number: int) -> float:
|
|
323
324
|
return float(self.frame_number_to_pts(frame_number) * self.time_base)
|
|
324
325
|
|
|
326
|
+
def pts_to_frame_number(self, pts: int) -> int:
|
|
327
|
+
if self._framerate == 'variable':
|
|
328
|
+
if pts not in self.frames_pts:
|
|
329
|
+
raise ValueError(f'PTS {pts} not in video index.')
|
|
330
|
+
return self.frames_pts.index(pts)
|
|
331
|
+
else:
|
|
332
|
+
if pts < self.pts0:
|
|
333
|
+
raise ValueError(f'PTS {pts} is before the start of the'
|
|
334
|
+
f' video (PTS {self.pts0}).')
|
|
335
|
+
if pts > self.pts_delta * (self.n_frames - 1) + self.pts0:
|
|
336
|
+
raise ValueError(f'PTS {pts} is after the end of the video (PTS '
|
|
337
|
+
f'{self.pts_delta * (self.n_frames - 1) + self.pts0}).')
|
|
338
|
+
if (pts - self.pts0) % self.pts_delta != 0:
|
|
339
|
+
raise ValueError(f'PTS {pts} is between frames for this video.')
|
|
340
|
+
return (pts - self.pts0) // self.pts_delta
|
|
341
|
+
|
|
325
342
|
def __getitem__(self, key) -> np.ndarray:
|
|
326
343
|
if isinstance(key, tuple):
|
|
327
344
|
frame_idx = key[0]
|
|
@@ -350,9 +367,11 @@ class VideoStreamer:
|
|
|
350
367
|
to the requested frame number.
|
|
351
368
|
"""
|
|
352
369
|
target_pts = self.frame_number_to_pts(frame_number)
|
|
353
|
-
for frame in self.
|
|
370
|
+
for frame in self._frame_iterator:
|
|
354
371
|
if frame.pts is None:
|
|
355
|
-
|
|
372
|
+
if self.verbose:
|
|
373
|
+
print('WARNING: Skipping a frame with no PTS.')
|
|
374
|
+
continue
|
|
356
375
|
if frame.pts == target_pts:
|
|
357
376
|
frame = frame.to_ndarray(format='rgb24')
|
|
358
377
|
self._current_frame_number = frame_number
|
|
@@ -360,8 +379,8 @@ class VideoStreamer:
|
|
|
360
379
|
if frame.pts > target_pts:
|
|
361
380
|
raise RuntimeError(f'Frame with PTS {target_pts} not found after'
|
|
362
381
|
f' seeking – current frame PTS: {frame.pts}')
|
|
363
|
-
raise RuntimeError(
|
|
364
|
-
f'
|
|
382
|
+
raise RuntimeError('Ran out of frames before finding frame with requested'
|
|
383
|
+
f' PTS {target_pts} – current frame PTS: {frame.pts}')
|
|
365
384
|
|
|
366
385
|
if isinstance(frame_number, slice): # Support slicing
|
|
367
386
|
start, stop, step = frame_number.indices(self.n_frames)
|
|
@@ -385,10 +404,14 @@ class VideoStreamer:
|
|
|
385
404
|
|
|
386
405
|
def _normalize_frame_number(self, frame_number: int) -> int:
|
|
387
406
|
"""
|
|
388
|
-
|
|
407
|
+
Support negative indexing by converting negative frame numbers to
|
|
408
|
+
positive ones, e.g. -1 becomes n_frames - 1, -2 becomes n_frames - 2, etc.
|
|
389
409
|
"""
|
|
390
|
-
|
|
391
|
-
|
|
410
|
+
try:
|
|
411
|
+
frame_number = int(frame_number)
|
|
412
|
+
except ValueError:
|
|
413
|
+
raise TypeError(f'Frame number must be castable to int but got "{frame_number}"')
|
|
414
|
+
|
|
392
415
|
if frame_number < -self.n_frames:
|
|
393
416
|
raise IndexError(f'Negative frame {frame_number} not in'
|
|
394
417
|
f' valid range [-{self.n_frames}, -1]')
|
|
@@ -626,6 +649,8 @@ class FFmpegVideoWriter:
|
|
|
626
649
|
command = [
|
|
627
650
|
'ffmpeg',
|
|
628
651
|
'-hide_banner',
|
|
652
|
+
'-loglevel', 'error',
|
|
653
|
+
'-nostats',
|
|
629
654
|
'-y', # Overwrite output
|
|
630
655
|
'-f', 'rawvideo',
|
|
631
656
|
'-vcodec', 'rawvideo',
|
|
@@ -710,12 +735,13 @@ class FFmpegVideoWriter:
|
|
|
710
735
|
|
|
711
736
|
# Wait for FFmpeg to finish
|
|
712
737
|
if self._process:
|
|
738
|
+
stderr_data = self._process.stderr.read()
|
|
713
739
|
return_code = self._process.wait()
|
|
714
740
|
|
|
715
741
|
# Check for errors
|
|
716
742
|
if return_code != 0:
|
|
717
|
-
|
|
718
|
-
|
|
743
|
+
raise RuntimeError(f'FFmpeg failed with return code {return_code}:'
|
|
744
|
+
f' {stderr_data.decode()}')
|
|
719
745
|
finally:
|
|
720
746
|
# Clean up process references
|
|
721
747
|
self._process = None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|