From da87b6c0d8d8345a3a521374f290795aa36d26af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jeremy=20Lain=C3=A9?= <jeremy.laine@m4x.org> Date: Sun, 14 Oct 2018 10:40:28 +0200 Subject: [PATCH] Add docstrings for Stream properties and methods. For consistency with other APIs we make `start_time` return None if unknown. --- av/container/streams.pyx | 2 +- av/stream.pyx | 77 +++++++++++++++++++++++++++++++++++--- docs/api/stream.rst | 6 +++ tests/test_file_probing.py | 34 ++++++++++------- 4 files changed, 98 insertions(+), 21 deletions(-) diff --git a/av/container/streams.pyx b/av/container/streams.pyx index dea0edc..8a7e225 100644 --- a/av/container/streams.pyx +++ b/av/container/streams.pyx @@ -12,7 +12,7 @@ Typed Collections ~~~~~~~~~~~~~~~~~ These attributes are preferred for readability if you don't need the -dynamic capabilities of :method:`.get`: +dynamic capabilities of :meth:`.get`: .. attribute:: StreamContainer.video diff --git a/av/stream.pyx b/av/stream.pyx index 1b792a9..8e6102e 100644 --- a/av/stream.pyx +++ b/av/stream.pyx @@ -47,6 +47,9 @@ cdef Stream wrap_stream(Container container, lib.AVStream *c_stream): cdef class Stream(object): + """ + A single stream of audio, video or subtitles within a :class:`Container`. + """ def __cinit__(self, name): if name is _cinit_bypass_sentinel: @@ -117,6 +120,10 @@ cdef class Stream(object): self._stream.time_base = self._codec_context.time_base def encode(self, frame=None): + """ + Encode an :class:`.AudioFrame` or :class:`.VideoFrame` and return a list + of :class:`.Packet`. + """ packets = self.codec_context.encode(frame) cdef Packet packet for packet in packets: @@ -125,11 +132,14 @@ cdef class Stream(object): return packets def decode(self, packet=None): + """ + Decode a :class:`.Packet` and return a list of :class:`.AudioFrame` + or :class:`.VideoFrame`. + """ return self.codec_context.decode(packet) def seek(self, offset, whence='time', backward=True, any_frame=False): - """seek(offset, whence='time', backward=True, any_frame=False) - + """ .. seealso:: :meth:`.InputContainer.seek` for documentation on parameters. The only difference is that ``offset`` will be interpreted in :attr:`.Stream.time_base` when ``whence == 'time'``. @@ -138,6 +148,11 @@ cdef class Stream(object): self._container.seek(self._stream.index, offset, whence, backward, any_frame) property id: + """ + The format-specific ID of this stream. + + :type: int + """ def __get__(self): return self._stream.id def __set__(self, v): @@ -147,6 +162,11 @@ cdef class Stream(object): self._stream.id = v property profile: + """ + The profile of this stream. + + :type: str + """ def __get__(self): if self._codec and lib.av_get_profile_name(self._codec, self._codec_context.profile): return lib.av_get_profile_name(self._codec, self._codec_context.profile) @@ -154,30 +174,75 @@ cdef class Stream(object): return None property index: + """ + The index of this stream in its :class:`.Container`. + + :type: int + """ def __get__(self): return self._stream.index property time_base: + """ + The unit of time (in fractional seconds) in which timestamps are expressed. + + :type: fractions.Fraction + """ def __get__(self): return avrational_to_fraction(&self._stream.time_base) def __set__(self, value): to_avrational(value, &self._stream.time_base) property average_rate: + """ + The average frame rate of this stream. + + :type: fractions.Fraction + """ def __get__(self): return avrational_to_fraction(&self._stream.avg_frame_rate) property start_time: - def __get__(self): return self._stream.start_time + """ + The presentation timestamp in :attr:`time_base` units of the first + frame in this stream. + + Returns `None` if it is not known. + + :type: int + """ + def __get__(self): + if self._stream.start_time != lib.AV_NOPTS_VALUE: + return self._stream.start_time property duration: + """ + The duration of this stream in :attr:`time_base` units. + + Returns `None` if it is not known. + + :type: int + """ def __get__(self): - if self._stream.duration == lib.AV_NOPTS_VALUE: - return None - return self._stream.duration + if self._stream.duration != lib.AV_NOPTS_VALUE: + return self._stream.duration property frames: + """ + The number of frames this stream contains. + + Returns `0` if it is not known. + + :type: int + """ def __get__(self): return self._stream.nb_frames property language: + """ + The language of the stream. + + Returns `None` if it is not known. + + :type: str + """ def __get__(self): return self.metadata.get('language') diff --git a/docs/api/stream.rst b/docs/api/stream.rst index 2155753..5833ee0 100644 --- a/docs/api/stream.rst +++ b/docs/api/stream.rst @@ -2,8 +2,14 @@ Streams ======= +Stream collections +------------------ + .. automodule:: av.container.streams +Streams +------- + .. automodule:: av.stream .. autoclass:: Stream diff --git a/tests/test_file_probing.py b/tests/test_file_probing.py index d441143..589cac6 100644 --- a/tests/test_file_probing.py +++ b/tests/test_file_probing.py @@ -66,28 +66,34 @@ class TestVideoProbe(TestCase): def test_stream_probing(self): stream = self.file.streams[0] + + # actual stream properties + self.assertEqual(stream.average_rate, Fraction(25, 1)) + self.assertEqual(stream.duration, 145800) + self.assertEqual(stream.frames, 0) + self.assertEqual(stream.id, 4131) self.assertEqual(stream.index, 0) - self.assertEqual(stream.type, 'video') - self.assertEqual(stream.name, 'mpeg2video') - self.assertEqual(stream.long_name, 'MPEG-2 video') + self.assertEqual(stream.language, None) + self.assertEqual(stream.metadata, {}) self.assertEqual(stream.profile, 'Simple') + self.assertEqual(stream.start_time, 2065806749) + self.assertEqual(stream.time_base, Fraction(1, 90000)) - # Libav is able to return a bit-rate for this file, but ffmpeg doesn't, - # so have to rely on rc_max_rate. - try: - self.assertEqual(stream.bit_rate, None) - self.assertEqual(stream.max_bit_rate, 3364800) - except AssertionError: - self.assertEqual(stream.bit_rate, 3364800) + # codec properties + self.assertEqual(stream.long_name, 'MPEG-2 video') + self.assertEqual(stream.name, 'mpeg2video') + self.assertEqual(stream.type, 'video') - self.assertEqual(stream.sample_aspect_ratio, Fraction(16, 15)) + # codec context properties + self.assertEqual(stream.bit_rate, 3364800) self.assertEqual(stream.display_aspect_ratio, Fraction(4, 3)) - self.assertEqual(stream.gop_size, 12) self.assertEqual(stream.format.name, 'yuv420p') self.assertFalse(stream.has_b_frames) - self.assertEqual(stream.average_rate, Fraction(25, 1)) - self.assertEqual(stream.width, 720) + self.assertEqual(stream.gop_size, 12) self.assertEqual(stream.height, 576) + self.assertEqual(stream.max_bit_rate, None) + self.assertEqual(stream.sample_aspect_ratio, Fraction(16, 15)) + self.assertEqual(stream.width, 720) # For some reason, these behave differently on OS X (@mikeboers) and # Ubuntu (Travis). We think it is FFmpeg, but haven't been able to -- GitLab