Bump minimum Python to 3.11 and av to 14.0.1 (#4385)

* chore: bump minimum supported python to 3.11

* fix: breaking changes from av upgrade

* chore: slightly bump minimum required version of av to 14.0.1

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* chore: update lockfile

* chore: update lockfile again with --upgrade

* Update pyproject.toml

* chore: CI pipeline os/version changes

* fix: indentation in ci.yml

* fix: use result.output instead of result.stdout for test_manim_cfg_subcommand

In Click 8.3.1 (pulled in by av>=14.0.1), help text output
behavior changed for no_args_is_help=True. Using result.output
instead of result.stdout makes the test robust across Click versions,
matching the pattern used in other tests like
test_manim_plugins_subcommand.

* fix: add UP to imports in get_winding_number doctest

* fix: add match_interpolate to imports in doctest

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Benjamin Hackl 2026-01-12 14:27:48 +01:00 committed by GitHub
commit c424f83cb4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 1827 additions and 1659 deletions

View file

@ -23,7 +23,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-22.04, macos-latest, windows-latest]
python: ["3.10", "3.11", "3.12", "3.13"]
python: ["3.11", "3.12", "3.13", "3.14"]
include:
- os: macos-15-intel
python: "3.13"

View file

@ -555,22 +555,22 @@ class SceneFileWriter:
partial_movie_file_codec = "qtrle"
partial_movie_file_pix_fmt = "argb"
with av.open(file_path, mode="w") as video_container:
stream = video_container.add_stream(
partial_movie_file_codec,
rate=fps,
options=av_options,
)
stream.pix_fmt = partial_movie_file_pix_fmt
stream.width = config.pixel_width
stream.height = config.pixel_height
video_container = av.open(file_path, mode="w")
stream = video_container.add_stream(
partial_movie_file_codec,
rate=fps,
options=av_options,
)
stream.pix_fmt = partial_movie_file_pix_fmt
stream.width = config.pixel_width
stream.height = config.pixel_height
self.video_container: OutputContainer = video_container
self.video_stream: Stream = stream
self.video_container: OutputContainer = video_container
self.video_stream: Stream = stream
self.queue: Queue[tuple[int, PixelArray | None]] = Queue()
self.writer_thread = Thread(target=self.listen_and_write, args=())
self.writer_thread.start()
self.queue: Queue[tuple[int, PixelArray | None]] = Queue()
self.writer_thread = Thread(target=self.listen_and_write, args=())
self.writer_thread.start()
def close_partial_movie_stream(self) -> None:
"""Close the currently opened video container.
@ -646,18 +646,15 @@ class SceneFileWriter:
output_container.metadata["comment"] = (
f"Rendered with Manim Community v{__version__}"
)
output_stream = output_container.add_stream(
codec_name="gif" if create_gif else None,
template=partial_movies_stream if not create_gif else None,
)
if config.transparent and config.movie_file_extension == ".webm":
output_stream.pix_fmt = "yuva420p"
if create_gif:
"""The following solution was largely inspired from this comment
https://github.com/imageio/imageio/issues/995#issuecomment-1580533018,
and the following code
https://github.com/imageio/imageio/blob/65d79140018bb7c64c0692ea72cb4093e8d632a0/imageio/plugins/pyav.py#L927-L996.
"""
output_stream = output_container.add_stream(
codec_name="gif",
)
output_stream.pix_fmt = "rgb8"
if config.transparent:
output_stream.pix_fmt = "pal8"
@ -702,6 +699,11 @@ class SceneFileWriter:
output_container.mux(packet)
else:
output_stream = output_container.add_stream_from_template(
template=partial_movies_stream,
)
if config.transparent and config.movie_file_extension == ".webm":
output_stream.pix_fmt = "yuva420p"
for packet in partial_movies_input.demux(partial_movies_stream):
# We need to skip the "flushing" packets that `demux` generates.
if packet.dts is None:
@ -789,8 +791,12 @@ class SceneFileWriter:
output_container = av.open(
str(temp_file_path), mode="w", options=av_options
)
output_video_stream = output_container.add_stream(template=video_stream)
output_audio_stream = output_container.add_stream(template=audio_stream)
output_video_stream = output_container.add_stream_from_template(
template=video_stream
)
output_audio_stream = output_container.add_stream_from_template(
template=audio_stream
)
for packet in video_input.demux(video_stream):
# We need to skip the "flushing" packets that `demux` generates.

View file

@ -1234,6 +1234,7 @@ def match_interpolate(
Examples
--------
>>> from manim import match_interpolate
>>> match_interpolate(0, 100, 10, 20, 15)
np.float64(50.0)
"""

View file

@ -630,7 +630,7 @@ def get_winding_number(points: Sequence[np.ndarray]) -> float:
Examples
--------
>>> from manim import Square, get_winding_number
>>> from manim import Square, UP, get_winding_number
>>> polygon = Square()
>>> get_winding_number(polygon.get_vertices())
np.float64(1.0)

View file

@ -14,16 +14,16 @@ classifiers = [
"Topic :: Scientific/Engineering",
"Topic :: Multimedia :: Video",
"Topic :: Multimedia :: Graphics",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Natural Language :: English",
]
requires-python = ">=3.10"
requires-python = ">=3.11"
dependencies = [
"audioop-lts>=0.2.1 ; python_full_version >= '3.13'",
"av>=9.0.0,<14.0.0",
"av>=14.0.1",
"beautifulsoup4>=4.12",
"click>=8.0",
"cloup>=2.0.0",

View file

@ -46,7 +46,7 @@ Commands:
Made with <3 by Manim Community developers.
"""
assert dedent(expected_output) == result.stdout
assert dedent(expected_output) == result.output
def test_manim_plugins_subcommand():

3345
uv.lock generated

File diff suppressed because it is too large Load diff