Skip to content
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
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Features:
- Add ``options`` parameter to ``AudioResampler`` for passing ``libswresample`` options (e.g. ``resampler``, ``filter_size``, ``cutoff``) by :gh-user:`WyattBlue` (:issue:`2262`).
- Support ``yuv420p10le`` in ``VideoFrame.to_ndarray`` and ``VideoFrame.from_ndarray`` by :gh-user:`WyattBlue` (:issue:`1981`).
- Add ``at`` parameter to ``Graph.push`` and ``Graph.vpush`` to push a frame to a single buffer source by index, for multi-input filters like ``overlay`` by :gh-user:`WyattBlue`.
- ``find_best_pix_fmt_of_list`` now returns the loss as a ``PixFmtLoss`` ``enum.IntFlag`` instead of a plain ``int`` by :gh-user:`WyattBlue` (:issue:`2300`).

Fixes:

Expand Down
2 changes: 2 additions & 0 deletions av/codec/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .codec import (
Capabilities,
Codec,
PixFmtLoss,
Properties,
codecs_available,
find_best_pix_fmt_of_list,
Expand All @@ -10,6 +11,7 @@
__all__ = (
"Capabilities",
"Codec",
"PixFmtLoss",
"Properties",
"codecs_available",
"find_best_pix_fmt_of_list",
Expand Down
24 changes: 20 additions & 4 deletions av/codec/codec.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from enum import Flag, IntEnum
from enum import Flag, IntEnum, IntFlag

import cython
from cython.cimports import libav as lib
Expand Down Expand Up @@ -54,6 +54,22 @@ class Capabilities(IntEnum):
encoder_recon_frame = 1 << 22


class PixFmtLoss(IntFlag):
"""Flags describing what is lost when converting between pixel formats.

Returned by :func:`find_best_pix_fmt_of_list`. Mirrors FFmpeg's
``FF_LOSS_*`` flags.
"""

NONE = 0
RESOLUTION = 0x0001 # loss due to resolution change
DEPTH = 0x0002 # loss due to color depth change
COLORSPACE = 0x0004 # loss due to color space conversion
ALPHA = 0x0008 # loss of alpha bit
COLORQUANT = 0x0010 # loss due to color quantization
CHROMA = 0x0020 # loss of chroma (e.g. RGB to gray conversion)


class UnknownCodecError(ValueError):
pass

Expand Down Expand Up @@ -419,7 +435,7 @@ def find_best_pix_fmt_of_list(pix_fmts, src_pix_fmt, has_alpha=False):
:param src_pix_fmt: Source pixel format (str or VideoFormat).
:param bool has_alpha: Whether the source alpha channel is used.
:return: (best_format, loss)
:rtype: (VideoFormat | None, int)
:rtype: (VideoFormat | None, PixFmtLoss)
"""
src: lib.AVPixelFormat
best: lib.AVPixelFormat
Expand All @@ -434,7 +450,7 @@ def find_best_pix_fmt_of_list(pix_fmts, src_pix_fmt, has_alpha=False):

pix_fmts = tuple(pix_fmts)
if not pix_fmts:
return None, 0
return None, PixFmtLoss.NONE

if isinstance(src_pix_fmt, VideoFormat):
src = cython.cast(VideoFormat, src_pix_fmt).pix_fmt
Expand Down Expand Up @@ -462,7 +478,7 @@ def find_best_pix_fmt_of_list(pix_fmts, src_pix_fmt, has_alpha=False):
best = lib.avcodec_find_best_pix_fmt_of_list(
c_list, src, 1 if has_alpha else 0, cython.address(c_loss)
)
return get_video_format(best, 0, 0), c_loss
return get_video_format(best, 0, 0), PixFmtLoss(c_loss)
finally:
if c_list != cython.NULL:
free(c_list)
24 changes: 19 additions & 5 deletions av/codec/codec.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from collections.abc import Sequence
from enum import Flag, IntEnum
from enum import Flag, IntEnum, IntFlag
from fractions import Fraction
from typing import ClassVar, Literal, cast, overload

Expand Down Expand Up @@ -44,6 +44,15 @@ class Capabilities(IntEnum):
encoder_flush = cast(int, ...)
encoder_recon_frame = cast(int, ...)

class PixFmtLoss(IntFlag):
NONE = cast(ClassVar[PixFmtLoss], ...)
RESOLUTION = cast(ClassVar[PixFmtLoss], ...)
DEPTH = cast(ClassVar[PixFmtLoss], ...)
COLORSPACE = cast(ClassVar[PixFmtLoss], ...)
ALPHA = cast(ClassVar[PixFmtLoss], ...)
COLORQUANT = cast(ClassVar[PixFmtLoss], ...)
CHROMA = cast(ClassVar[PixFmtLoss], ...)

class UnknownCodecError(ValueError): ...

class Codec:
Expand Down Expand Up @@ -117,7 +126,7 @@ def find_best_pix_fmt_of_list(
pix_fmts: Sequence[PixFmtLike],
src_pix_fmt: PixFmtLike,
has_alpha: bool = False,
) -> tuple[VideoFormat | None, int]:
) -> tuple[VideoFormat | None, PixFmtLoss]:
"""
Find the best pixel format to convert to given a source format.

Expand All @@ -127,10 +136,15 @@ def find_best_pix_fmt_of_list(
:param src_pix_fmt: Source pixel format (str or VideoFormat).
:param bool has_alpha: Whether the source alpha channel is used.
:return: (best_format, loss): best_format is the best matching pixel format from
the list, or None if no suitable format was found; loss is Combination of flags informing you what kind of losses will occur.
:rtype: (VideoFormat | None, int)
the list, or None if no suitable format was found; loss is a combination of
:class:`PixFmtLoss` flags informing you what kind of losses will occur.
:rtype: (VideoFormat | None, PixFmtLoss)

Note on loss: it is a bitmask of FFmpeg loss flags describing what kinds of information would be lost converting from src_pix_fmt to best_format (e.g. loss of alpha, chroma, colorspace, resolution, bit depth, etc.). Multiple losses can be present at once, so the value is meant to be interpreted with bitwise & against FFmpeg's FF_LOSS_* constants.
Note on loss: it is an :class:`enum.IntFlag` describing what kinds of information
would be lost converting from src_pix_fmt to best_format (e.g. loss of alpha,
chroma, colorspace, resolution, bit depth, etc.). Multiple losses can be present
at once, so the value can be tested with bitwise & against the :class:`PixFmtLoss`
members.
For exact behavior see: libavutil/pixdesc.c/get_pix_fmt_score() in ffmpeg source code.
"""
...
13 changes: 13 additions & 0 deletions docs/api/codec.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,19 @@ Flags

Note that ``ffmpeg -codecs`` prefers the properties versions of ``INTRA_ONLY`` and ``LOSSLESS``.

Pixel Format Selection
----------------------

.. autofunction:: find_best_pix_fmt_of_list

.. autoclass:: PixFmtLoss

Wraps FFmpeg's ``FF_LOSS_*`` flags. Returned by
:func:`find_best_pix_fmt_of_list` to describe what is lost when converting
from the source pixel format to the chosen one. Being an
:class:`enum.IntFlag`, members can be combined and tested with bitwise
operators.

Contexts
--------

Expand Down
16 changes: 15 additions & 1 deletion tests/test_codec.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pytest

from av import AudioFormat, Codec, VideoFormat, codecs_available
from av.codec import find_best_pix_fmt_of_list
from av.codec import PixFmtLoss, find_best_pix_fmt_of_list
from av.codec.codec import UnknownCodecError


Expand Down Expand Up @@ -96,6 +96,7 @@ def test_find_best_pix_fmt_of_list_empty() -> None:
best, loss = find_best_pix_fmt_of_list([], "rgb24")
assert best is None
assert loss == 0
assert loss is PixFmtLoss.NONE


@pytest.mark.parametrize(
Expand Down Expand Up @@ -151,3 +152,16 @@ def test_find_best_pix_fmt_of_list_alpha_loss_flagged_when_used() -> None:
assert best is not None
assert best.name == "rgb24"
assert loss != 0
assert isinstance(loss, PixFmtLoss)
assert loss & PixFmtLoss.ALPHA


def test_find_best_pix_fmt_of_list_loss_flags() -> None:
# An identical format loses nothing.
_, loss = find_best_pix_fmt_of_list(["yuv420p"], "yuv420p")
assert loss is PixFmtLoss.NONE

# Converting color to grayscale drops the chroma planes.
_, loss = find_best_pix_fmt_of_list(["gray"], "rgb24")
assert isinstance(loss, PixFmtLoss)
assert loss & PixFmtLoss.CHROMA
Loading