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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: numpyimage
3
- Version: 3.0.2
3
+ Version: 3.1.1
4
4
  Summary: Load, save, & manipulate image files as numpy arrays
5
5
  Author-email: Jasper Phelps <jasper.s.phelps@gmail.com>
6
6
  License: MIT License
@@ -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
- '-select_streams', 'v:0',
205
- '-show_frames',
206
- '-show_entries', 'frame=pts',
207
- '-of', 'default=noprint_wrappers=1:nokey=1',
208
- '-v', 'quiet',
209
- self.filename]
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
- stderr=subprocess.PIPE, text=True)
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.container.decode(self.stream):
370
+ for frame in self._frame_iterator:
354
371
  if frame.pts is None:
355
- continue # Skip frames without PTS
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(f'Frame with PTS {target_pts} not found after'
364
- f' seeking – current frame PTS: {frame.pts}')
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
- Normalize frame number to be within the valid range.
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
- if not isinstance(frame_number, int):
391
- raise TypeError(f'Frame number must be an integer, not {type(frame_number)}')
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
- stderr_output = self._process.stderr.read().decode()
718
- raise RuntimeError(f'FFmpeg failed with return code {return_code}: {stderr_output}')
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: numpyimage
3
- Version: 3.0.2
3
+ Version: 3.1.1
4
4
  Summary: Load, save, & manipulate image files as numpy arrays
5
5
  Author-email: Jasper Phelps <jasper.s.phelps@gmail.com>
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = 'setuptools.build_meta'
4
4
 
5
5
  [project]
6
6
  name = 'numpyimage'
7
- version = '3.0.2'
7
+ version = '3.1.1'
8
8
  description = 'Load, save, & manipulate image files as numpy arrays'
9
9
  readme.file = 'README.md'
10
10
  readme.content-type = 'text/markdown'
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes