177
177
178
178
from fractions import Fraction
179
179
from math import ceil
180
- from typing import Any , Dict , List , Optional , Tuple , Union , Generator
180
+ from typing import Any , Dict , Generator , List , Optional , Tuple , Union
181
181
182
182
import av
183
183
import av .filter
184
184
import numpy as np
185
+ from av .codec .context import Flags
185
186
from numpy .lib .stride_tricks import as_strided
186
187
187
188
from ..core import Request
@@ -258,6 +259,39 @@ def _get_frame_shape(frame: av.VideoFrame) -> Tuple[int, ...]:
258
259
return tuple (shape )
259
260
260
261
262
+ def _get_frame_type (picture_type : int ) -> str :
263
+ """Return a human-readable name for provided picture type
264
+
265
+ Parameters
266
+ ----------
267
+ picture_type : int
268
+ The picture type extracted from Frame.pict_type
269
+
270
+ Returns
271
+ -------
272
+ picture_name : str
273
+ A human readable name of the picture type
274
+
275
+ """
276
+
277
+ if not isinstance (picture_type , int ):
278
+ # old pyAV versions send an enum, not an int
279
+ return picture_type .name
280
+
281
+ picture_types = [
282
+ "NONE" ,
283
+ "I" ,
284
+ "P" ,
285
+ "B" ,
286
+ "S" ,
287
+ "SI" ,
288
+ "SP" ,
289
+ "BI" ,
290
+ ]
291
+
292
+ return picture_types [picture_type ]
293
+
294
+
261
295
class PyAVPlugin (PluginV3 ):
262
296
"""Support for pyAV as backend.
263
297
@@ -308,7 +342,7 @@ def __init__(self, request: Request, *, container: str = None, **kwargs) -> None
308
342
self ._container = av .open (request .get_file (), ** kwargs )
309
343
self ._video_stream = self ._container .streams .video [0 ]
310
344
self ._decoder = self ._container .decode (video = 0 )
311
- except av .AVError :
345
+ except av .FFmpegError :
312
346
if isinstance (request .raw_uri , bytes ):
313
347
msg = "PyAV does not support these `<bytes>`"
314
348
else :
@@ -458,13 +492,16 @@ def read(
458
492
459
493
# reset stream container, because threading model can't change after
460
494
# first access
461
- self ._video_stream .close ()
462
495
self ._video_stream = self ._container .streams .video [0 ]
463
496
464
497
return frames
465
498
466
- if thread_type is not None and thread_type != self ._video_stream .thread_type :
499
+ if thread_type is not None and not (
500
+ self ._video_stream .thread_type == thread_type
501
+ or self ._video_stream .thread_type .name == thread_type
502
+ ):
467
503
self ._video_stream .thread_type = thread_type
504
+
468
505
if (
469
506
thread_count != 0
470
507
and thread_count != self ._video_stream .codec_context .thread_count
@@ -474,7 +511,10 @@ def read(
474
511
self ._video_stream .codec_context .thread_count = thread_count
475
512
476
513
if constant_framerate is None :
477
- constant_framerate = not self ._container .format .variable_fps
514
+ # "variable_fps" is now a flag (handle got removed). Full list at
515
+ # https://pyav.org/docs/stable/api/container.html#module-av.format
516
+ variable_fps = bool (self ._container .format .flags & 0x400 )
517
+ constant_framerate = not variable_fps
478
518
479
519
# note: cheap for contigous incremental reads
480
520
self ._seek (index , constant_framerate = constant_framerate )
@@ -750,7 +790,10 @@ def metadata(
750
790
return metadata
751
791
752
792
if constant_framerate is None :
753
- constant_framerate = not self ._container .format .variable_fps
793
+ # "variable_fps" is now a flag (handle got removed). Full list at
794
+ # https://pyav.org/docs/stable/api/container.html#module-av.format
795
+ variable_fps = bool (self ._container .format .flags & 0x400 )
796
+ constant_framerate = not variable_fps
754
797
755
798
self ._seek (index , constant_framerate = constant_framerate )
756
799
desired_frame = next (self ._decoder )
@@ -762,7 +805,7 @@ def metadata(
762
805
"key_frame" : bool (desired_frame .key_frame ),
763
806
"time" : desired_frame .time ,
764
807
"interlaced_frame" : bool (desired_frame .interlaced_frame ),
765
- "frame_type" : desired_frame .pict_type . name ,
808
+ "frame_type" : _get_frame_type ( desired_frame .pict_type ) ,
766
809
}
767
810
)
768
811
@@ -781,10 +824,7 @@ def close(self) -> None:
781
824
self ._flush_writer ()
782
825
783
826
if self ._video_stream is not None :
784
- try :
785
- self ._video_stream .close ()
786
- except ValueError :
787
- pass # stream already closed
827
+ self ._video_stream = None
788
828
789
829
if self ._container is not None :
790
830
self ._container .close ()
@@ -815,7 +855,7 @@ def init_video_stream(
815
855
Parameters
816
856
----------
817
857
codec : str
818
- The codec to use, e.g. ``"libx264 "`` or ``"vp9"``.
858
+ The codec to use, e.g. ``"h264 "`` or ``"vp9"``.
819
859
fps : float
820
860
The desired framerate of the video stream (frames per second).
821
861
pixel_format : str
@@ -850,7 +890,10 @@ def init_video_stream(
850
890
if max_keyframe_interval is not None :
851
891
stream .gop_size = max_keyframe_interval
852
892
if force_keyframes is not None :
853
- stream .closed_gop = force_keyframes
893
+ if force_keyframes :
894
+ stream .codec_context .flags |= Flags .closed_gop
895
+ else :
896
+ stream .codec_context .flags &= ~ Flags .closed_gop
854
897
855
898
self ._video_stream = stream
856
899
0 commit comments