1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
|
cimport libav as lib
def _flatten(input_):
for x in input_:
if isinstance(x, (tuple, list)):
for y in _flatten(x):
yield y
else:
yield x
cdef lib.AVMediaType _get_media_type_enum(str type):
if type == "video":
return lib.AVMEDIA_TYPE_VIDEO
elif type == "audio":
return lib.AVMEDIA_TYPE_AUDIO
elif type == "subtitle":
return lib.AVMEDIA_TYPE_SUBTITLE
elif type == "attachment":
return lib.AVMEDIA_TYPE_ATTACHMENT
elif type == "data":
return lib.AVMEDIA_TYPE_DATA
else:
raise ValueError(f"Invalid stream type: {type}")
cdef class StreamContainer:
"""
A tuple-like container of :class:`Stream`.
::
# There are a few ways to pulling out streams.
first = container.streams[0]
video = container.streams.video[0]
audio = container.streams.get(audio=(0, 1))
"""
def __cinit__(self):
self._streams = []
self.video = ()
self.audio = ()
self.subtitles = ()
self.data = ()
self.attachments = ()
self.other = ()
cdef add_stream(self, Stream stream):
assert stream.ptr.index == len(self._streams)
self._streams.append(stream)
if stream.ptr.codecpar.codec_type == lib.AVMEDIA_TYPE_VIDEO:
self.video = self.video + (stream, )
elif stream.ptr.codecpar.codec_type == lib.AVMEDIA_TYPE_AUDIO:
self.audio = self.audio + (stream, )
elif stream.ptr.codecpar.codec_type == lib.AVMEDIA_TYPE_SUBTITLE:
self.subtitles = self.subtitles + (stream, )
elif stream.ptr.codecpar.codec_type == lib.AVMEDIA_TYPE_ATTACHMENT:
self.attachments = self.attachments + (stream, )
elif stream.ptr.codecpar.codec_type == lib.AVMEDIA_TYPE_DATA:
self.data = self.data + (stream, )
else:
self.other = self.other + (stream, )
# Basic tuple interface.
def __len__(self):
return len(self._streams)
def __iter__(self):
return iter(self._streams)
def __getitem__(self, index):
if isinstance(index, int):
return self.get(index)[0]
else:
return self.get(index)
def get(self, *args, **kwargs):
"""get(streams=None, video=None, audio=None, subtitles=None, data=None)
Get a selection of :class:`.Stream` as a ``list``.
Positional arguments may be ``int`` (which is an index into the streams),
or ``list`` or ``tuple`` of those::
# Get the first channel.
streams.get(0)
# Get the first two audio channels.
streams.get(audio=(0, 1))
Keyword arguments (or dicts as positional arguments) as interpreted
as ``(stream_type, index_value_or_set)`` pairs::
# Get the first video channel.
streams.get(video=0)
# or
streams.get({'video': 0})
:class:`.Stream` objects are passed through untouched.
If nothing is selected, then all streams are returned.
"""
selection = []
for x in _flatten((args, kwargs)):
if x is None:
pass
elif isinstance(x, Stream):
selection.append(x)
elif isinstance(x, int):
selection.append(self._streams[x])
elif isinstance(x, dict):
for type_, indices in x.items():
if type_ == "streams": # For compatibility with the pseudo signature
streams = self._streams
else:
streams = getattr(self, type_)
if not isinstance(indices, (tuple, list)):
indices = [indices]
for i in indices:
selection.append(streams[i])
else:
raise TypeError("Argument must be Stream or int.", type(x))
return selection or self._streams[:]
cdef int _get_best_stream_index(self, Container container, lib.AVMediaType type_enum, Stream related) noexcept:
cdef int stream_index
if related is None:
stream_index = lib.av_find_best_stream(container.ptr, type_enum, -1, -1, NULL, 0)
else:
stream_index = lib.av_find_best_stream(container.ptr, type_enum, -1, related.ptr.index, NULL, 0)
return stream_index
def best(self, str type, /, Stream related = None):
"""best(type: Literal["video", "audio", "subtitle", "attachment", "data"], /, related: Stream | None)
Finds the "best" stream in the file. Wraps :ffmpeg:`av_find_best_stream`. Example::
stream = container.streams.best("video")
:param type: The type of stream to find
:param related: A related stream to use as a reference (optional)
:return: The best stream of the specified type
:rtype: Stream | None
"""
cdef type_enum = _get_media_type_enum(type)
if len(self._streams) == 0:
return None
cdef container = self._streams[0].container
cdef int stream_index = self._get_best_stream_index(container, type_enum, related)
if stream_index < 0:
return None
return self._streams[stream_index]
|