Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move encode and decode to concrete classes #1329

Merged
merged 4 commits into from
Mar 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion av/audio/stream.pxd
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from av.packet cimport Packet
from av.stream cimport Stream

from .frame cimport AudioFrame


cdef class AudioStream(Stream):
pass
cpdef encode(self, AudioFrame frame=?)
cpdef decode(self, Packet packet=?)
14 changes: 11 additions & 3 deletions av/audio/stream.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,19 @@ from av.stream import Stream
from .codeccontext import AudioCodecContext
from .format import AudioFormat
from .frame import AudioFrame
from .layout import AudioLayout

class AudioStream(Stream):
type: Literal["audio"]
format: AudioFormat
codec_context: AudioCodecContext
# From codec context
frame_size: int
sample_rate: int
rate: int
channels: int
channel_layout: int
layout: AudioLayout
format: AudioFormat
type: Literal["audio"]

def encode(self, frame: AudioFrame | None = None) -> list[Packet]: ... # type: ignore[override]
def encode(self, frame: AudioFrame | None = None) -> list[Packet]: ...
def decode(self, packet: Packet | None = None) -> list[AudioFrame]: ...
35 changes: 34 additions & 1 deletion av/audio/stream.pyx
Original file line number Diff line number Diff line change
@@ -1,7 +1,40 @@
from av.packet cimport Packet

from .frame cimport AudioFrame


cdef class AudioStream(Stream):
def __repr__(self):
form = self.format.name if self.format else None
return (
f"<av.{self.__class__.__name__} #{self.index} {self.name} at {self.rate}Hz,"
f"<av.AudioStream #{self.index} {self.name} at {self.rate}Hz,"
f" {self.layout.name}, {form} at 0x{id(self):x}>"
)

cpdef encode(self, AudioFrame frame=None):
"""
Encode an :class:`.AudioFrame` and return a list of :class:`.Packet`.

:rtype: list[Packet]

.. seealso:: This is mostly a passthrough to :meth:`.CodecContext.encode`.
"""

packets = self.codec_context.encode(frame)
cdef Packet packet
for packet in packets:
packet._stream = self
packet.ptr.stream_index = self.ptr.index

return packets

cpdef decode(self, Packet packet=None):
"""
Decode a :class:`.Packet` and return a list of :class:`.AudioFrame`.

:rtype: list[AudioFrame]

.. seealso:: This is a passthrough to :meth:`.CodecContext.decode`.
"""

return self.codec_context.decode(packet)
1 change: 0 additions & 1 deletion av/container/core.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,6 @@ def open(
Honored only when ``file`` is a file-like object. Defaults to 32768 (32k).
:param timeout: How many seconds to wait for data before giving up, as a float, or a
:ref:`(open timeout, read timeout) <timeouts>` tuple.
:type timeout: float or tuple
:param callable io_open: Custom I/O callable for opening files/streams.
This option is intended for formats that need to open additional
file-like objects to ``file`` using custom I/O.
Expand Down
2 changes: 0 additions & 2 deletions av/stream.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,3 @@ class Stream:
frames: int
language: str | None
type: Literal["video", "audio", "data", "subtitle", "attachment"]

def encode(self, frame: Frame | None = None) -> list[Packet]: ...
61 changes: 0 additions & 61 deletions av/stream.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -146,25 +146,6 @@ cdef class Stream:
# Lets just copy what we want.
err_check(lib.avcodec_parameters_from_context(self.ptr.codecpar, self.codec_context.ptr))

def encode(self, frame=None):
"""
Encode an :class:`.AudioFrame` or :class:`.VideoFrame` and return a list
of :class:`.Packet`.

:return: :class:`list` of :class:`.Packet`.

.. seealso:: This is mostly a passthrough to :meth:`.CodecContext.encode`.
"""
if self.codec_context is None:
raise RuntimeError("Stream.encode requires a valid CodecContext")

packets = self.codec_context.encode(frame)
cdef Packet packet
for packet in packets:
packet._stream = self
packet.ptr.stream_index = self.ptr.index
return packets

cdef _get_side_data(self, lib.AVStream *stream):
# Get DISPLAYMATRIX SideData from a lib.AVStream object.
# Returns: tuple[int, dict[str, Any]]
Expand Down Expand Up @@ -236,48 +217,6 @@ cdef class Stream:
"""
to_avrational(value, &self.ptr.time_base)

@property
def average_rate(self):
"""
The average frame rate of this video stream.

This is calculated when the file is opened by looking at the first
few frames and averaging their rate.

:type: :class:`~fractions.Fraction` or ``None``


"""
return avrational_to_fraction(&self.ptr.avg_frame_rate)

@property
def base_rate(self):
"""
The base frame rate of this stream.

This is calculated as the lowest framerate at which the timestamps of
frames can be represented accurately. See :ffmpeg:`AVStream.r_frame_rate`
for more.

:type: :class:`~fractions.Fraction` or ``None``

"""
return avrational_to_fraction(&self.ptr.r_frame_rate)

@property
def guessed_rate(self):
"""The guessed frame rate of this stream.

This is a wrapper around :ffmpeg:`av_guess_frame_rate`, and uses multiple
heuristics to decide what is "the" frame rate.

:type: :class:`~fractions.Fraction` or ``None``

"""
# The two NULL arguments aren't used in FFmpeg >= 4.0
cdef lib.AVRational val = lib.av_guess_frame_rate(NULL, self.ptr, NULL)
return avrational_to_fraction(&val)

@property
def start_time(self):
"""
Expand Down
10 changes: 6 additions & 4 deletions av/video/codeccontext.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,29 @@ from typing import Iterator, Literal
from av.codec.context import CodecContext
from av.packet import Packet

from .format import VideoFormat
from .frame import VideoFrame

class VideoCodecContext(CodecContext):
format: VideoFormat
width: int
height: int
bits_per_codec_sample: int
pix_fmt: str
pix_fmt: str | None
framerate: Fraction
rate: Fraction
gop_size: int
sample_aspect_ratio: Fraction
display_aspect_ratio: Fraction
sample_aspect_ratio: Fraction | None
display_aspect_ratio: Fraction | None
has_b_frames: bool
coded_width: int
coded_height: int
color_range: int
color_primaries: int
color_trc: int
colorspace: int

type: Literal["video"]

def encode(self, frame: VideoFrame | None = None) -> list[Packet]: ...
def encode_lazy(self, frame: VideoFrame | None = None) -> Iterator[Packet]: ...
def decode(self, packet: Packet | None = None) -> list[VideoFrame]: ...
4 changes: 1 addition & 3 deletions av/video/frame.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -464,9 +464,7 @@ cdef class VideoFrame(Frame):
.. note:: For formats which expect an array of ``uint16``, the samples
must be in the system's native byte order.

.. note:: for ``pal8``, an ``(image, palette)`` pair must be passed.
`palette` must have shape (256, 4) and is given in ARGB format
(PyAV will swap bytes if needed).
.. note:: for ``pal8``, an ``(image, palette)`` pair must be passed. `palette` must have shape (256, 4) and is given in ARGB format (PyAV will swap bytes if needed).
"""
if format == "pal8":
array, palette = array
Expand Down
6 changes: 5 additions & 1 deletion av/video/stream.pxd
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from av.packet cimport Packet
from av.stream cimport Stream

from .frame cimport VideoFrame


cdef class VideoStream(Stream):
pass
cpdef encode(self, VideoFrame frame=?)
cpdef decode(self, Packet packet=?)
31 changes: 21 additions & 10 deletions av/video/stream.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,31 @@ from .format import VideoFormat
from .frame import VideoFrame

class VideoStream(Stream):
width: int
height: int
format: VideoFormat
pix_fmt: str | None
sample_aspect_ratio: Fraction | None
codec_context: VideoCodecContext
type: Literal["video"]

# from codec context
bit_rate: int | None
max_bit_rate: int | None
bit_rate_tolerance: int
thread_count: int
thread_type: Any
codec_context: VideoCodecContext
# from codec context
format: VideoFormat
width: int
height: int
bits_per_codec_sample: int
pix_fmt: str | None
framerate: Fraction
rate: Fraction
gop_size: int
sample_aspect_ratio: Fraction | None
display_aspect_ratio: Fraction | None
has_b_frames: bool
coded_width: int
coded_height: int
color_range: int
color_primaries: int
color_trc: int
colorspace: int
type: Literal["video"]

def encode(self, frame: VideoFrame | None = None) -> list[Packet]: ... # type: ignore[override]
def encode(self, frame: VideoFrame | None = None) -> list[Packet]: ...
def decode(self, packet: Packet | None = None) -> list[VideoFrame]: ...
77 changes: 76 additions & 1 deletion av/video/stream.pyx
Original file line number Diff line number Diff line change
@@ -1,7 +1,82 @@
cimport libav as lib

from av.packet cimport Packet
from av.utils cimport avrational_to_fraction, to_avrational

from .frame cimport VideoFrame


cdef class VideoStream(Stream):
def __repr__(self):
return (
f"<av.{self.__class__.__name__} #{self.index} {self.name}, "
f"<av.VideoStream #{self.index} {self.name}, "
f"{self.format.name if self.format else None} {self.codec_context.width}x"
f"{self.codec_context.height} at 0x{id(self):x}>"
)

cpdef encode(self, VideoFrame frame=None):
"""
Encode an :class:`.VideoFrame` and return a list of :class:`.Packet`.

:rtype: list[Packet]

.. seealso:: This is mostly a passthrough to :meth:`.CodecContext.encode`.
"""

packets = self.codec_context.encode(frame)
cdef Packet packet
for packet in packets:
packet._stream = self
packet.ptr.stream_index = self.ptr.index

return packets


cpdef decode(self, Packet packet=None):
"""
Decode a :class:`.Packet` and return a list of :class:`.VideoFrame`.

:rtype: list[VideoFrame]

.. seealso:: This is a passthrough to :meth:`.CodecContext.decode`.
"""

return self.codec_context.decode(packet)

@property
def average_rate(self):
"""
The average frame rate of this video stream.

This is calculated when the file is opened by looking at the first
few frames and averaging their rate.

:type: :class:`~fractions.Fraction` or ``None``
"""
return avrational_to_fraction(&self.ptr.avg_frame_rate)

@property
def base_rate(self):
"""
The base frame rate of this stream.

This is calculated as the lowest framerate at which the timestamps of
frames can be represented accurately. See :ffmpeg:`AVStream.r_frame_rate`
for more.

:type: :class:`~fractions.Fraction` or ``None``
"""
return avrational_to_fraction(&self.ptr.r_frame_rate)

@property
def guessed_rate(self):
"""The guessed frame rate of this stream.

This is a wrapper around :ffmpeg:`av_guess_frame_rate`, and uses multiple
heuristics to decide what is "the" frame rate.

:type: :class:`~fractions.Fraction` or ``None``
"""
# The two NULL arguments aren't used in FFmpeg >= 4.0
cdef lib.AVRational val = lib.av_guess_frame_rate(NULL, self.ptr, NULL)
return avrational_to_fraction(&val)
29 changes: 0 additions & 29 deletions docs/api/stream.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,6 @@ Basics
.. autoattribute:: Stream.index


Transcoding
~~~~~~~~~~~

.. automethod:: Stream.encode

.. automethod:: Stream.decode


Timing
~~~~~~

Expand All @@ -87,27 +79,6 @@ Timing
.. autoattribute:: Stream.frames


.. _frame_rates:

Frame Rates
...........


These attributes are different ways of calculating frame rates.

Since containers don't need to explicitly identify a frame rate, nor
even have a static frame rate, these attributes are not guaranteed to be accurate.
You must experiment with them with your media to see which ones work for you for your purposes.

Whenever possible, we advise that you use raw timing instead of frame rates.

.. autoattribute:: Stream.average_rate

.. autoattribute:: Stream.base_rate

.. autoattribute:: Stream.guessed_rate


Others
~~~~~~

Expand Down
Loading
Loading