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

Let SceneFileWriter access ffmpeg via av instead of via external process #3501

Merged
merged 134 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
134 commits
Select commit Hold shift + click to select a range
61222b5
added av as a dependency
behackl Dec 7, 2023
fa18ed5
make partial movie files use av instead of piping to external ffmpeg
behackl Dec 7, 2023
9ad5275
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 7, 2023
99e4e2f
opengl rendering: use av for movie files
behackl Dec 7, 2023
de1721b
Merge branch 'av-support' of github.com:behackl/manim into av-support
behackl Dec 7, 2023
205ab34
no need to check for ffmpeg executable
behackl Dec 7, 2023
86967dd
refactor: *_movie_pipe -> *_partial_movie_stream
behackl Dec 7, 2023
fabd4d1
improve (oneline) documentation
behackl Dec 7, 2023
068689b
pass more options to partial movie file rendering
behackl Dec 7, 2023
fb4233b
move ffmpeg verbosity settings to config; renamed option dict
behackl Dec 7, 2023
d8b99e8
replaced call to ffmpeg in combine_files by using av
behackl Dec 7, 2023
0f33f2b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 7, 2023
9adb09d
there was one examples saved as a gif?
behackl Dec 7, 2023
93cdf35
Merge branch 'av-support' of github.com:behackl/manim into av-support
behackl Dec 7, 2023
c1afbd6
chore(deps): re-order av
jeertmans Dec 8, 2023
50f5790
chore(lib): simplify `write_frame` method
jeertmans Dec 8, 2023
efcb12d
chore(lib): add audio
jeertmans Dec 8, 2023
6f7523d
fix(lib): same issue for conversion
jeertmans Dec 8, 2023
3b7772a
fix(lib): webm export
jeertmans Dec 8, 2023
6f62ed1
fix(lib): transparent export
jeertmans Dec 8, 2023
5f76ccf
try(lib): fix gif + TODOs
jeertmans Dec 8, 2023
11aebee
chore(deps): lower dep crit
jeertmans Dec 8, 2023
176e177
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 8, 2023
ca12971
feat(lib): add support for GIF
jeertmans Dec 11, 2023
26be507
fix(ci): rewrite tests
jeertmans Dec 11, 2023
0dd41d1
fix
jeertmans Dec 11, 2023
325ccba
chore(ci): prevent calling concat on empty list
jeertmans Dec 11, 2023
5f848bf
add missing dot
jeertmans Dec 11, 2023
788270c
Merge branch 'main' into av-support
jeertmans Dec 11, 2023
904cfb4
fix(ci): update frame comparison ?
jeertmans Dec 12, 2023
0ec9ab2
Merge branch 'main' into av-support
jeertmans Dec 12, 2023
8cc54c7
fix(log): add handler to libav logger
jeertmans Dec 12, 2023
2df10f6
Merge remote-tracking branch 'behackl/av-support' into av-support
jeertmans Dec 12, 2023
3983e13
chore: add TODO
jeertmans Dec 12, 2023
1286897
fix(lib): concat issue
jeertmans Dec 12, 2023
46b79d3
Revert "fix(ci): update frame comparison ?"
jeertmans Dec 12, 2023
156424d
fix(ci): make it pass tests
jeertmans Dec 12, 2023
7c96eaf
chore(lib/docs/ci): remove FFMPEG entirely
jeertmans Dec 12, 2023
eeaa3d9
added av as a dependency
behackl Dec 7, 2023
6ed0362
make partial movie files use av instead of piping to external ffmpeg
behackl Dec 7, 2023
717c50f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 7, 2023
3c2d56b
opengl rendering: use av for movie files
behackl Dec 7, 2023
3ace234
no need to check for ffmpeg executable
behackl Dec 7, 2023
731b287
refactor: *_movie_pipe -> *_partial_movie_stream
behackl Dec 7, 2023
2e41ce4
improve (oneline) documentation
behackl Dec 7, 2023
10e13e1
pass more options to partial movie file rendering
behackl Dec 7, 2023
d03aadb
move ffmpeg verbosity settings to config; renamed option dict
behackl Dec 7, 2023
16b0408
replaced call to ffmpeg in combine_files by using av
behackl Dec 7, 2023
74941f3
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 7, 2023
be993f1
there was one examples saved as a gif?
behackl Dec 7, 2023
7a4e6dc
chore(deps): re-order av
jeertmans Dec 8, 2023
463a630
chore(lib): simplify `write_frame` method
jeertmans Dec 8, 2023
e3f1c6f
chore(lib): add audio
jeertmans Dec 8, 2023
ead0b58
fix(lib): same issue for conversion
jeertmans Dec 8, 2023
e22e485
fix(lib): webm export
jeertmans Dec 8, 2023
cded948
fix(lib): transparent export
jeertmans Dec 8, 2023
c147bca
try(lib): fix gif + TODOs
jeertmans Dec 8, 2023
736b1ee
chore(deps): lower dep crit
jeertmans Dec 8, 2023
e10e726
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 8, 2023
4fbbd3c
feat(lib): add support for GIF
jeertmans Dec 11, 2023
0ddb1a0
fix(ci): rewrite tests
jeertmans Dec 11, 2023
7ed675f
fix
jeertmans Dec 11, 2023
f9aa05f
chore(ci): prevent calling concat on empty list
jeertmans Dec 11, 2023
931caab
add missing dot
jeertmans Dec 11, 2023
23ec3b2
fix(ci): update frame comparison ?
jeertmans Dec 12, 2023
86e78cc
fix(log): add handler to libav logger
jeertmans Dec 12, 2023
7c716e4
chore: add TODO
jeertmans Dec 12, 2023
482a108
fix(lib): concat issue
jeertmans Dec 12, 2023
cd9c4b4
Revert "fix(ci): update frame comparison ?"
jeertmans Dec 12, 2023
6c0bf90
fix(ci): make it pass tests
jeertmans Dec 12, 2023
18f0020
chore(lib/docs/ci): remove FFMPEG entirely
jeertmans Dec 12, 2023
a6f381f
chore(deps): update lockfile
jeertmans Dec 12, 2023
d839aa1
Merge remote-tracking branch 'behackl/av-support' into av-support
jeertmans Dec 12, 2023
ec799a9
chore(lib): rewrite ffprobe
jeertmans Dec 12, 2023
83758b8
Merge branch 'main' into av-support
jeertmans Dec 18, 2023
bafd285
Merge remote-tracking branch 'origin/main' into av-support
behackl Jan 31, 2024
9d33785
fix typo
behackl Jan 31, 2024
7d10ac7
slightly more aggressive removal of ffmpeg in docs; minor language ch…
behackl Jan 31, 2024
a8a3228
fix gif output stream dimensions
behackl Jan 31, 2024
c4c44f4
minor style change
behackl Feb 2, 2024
2882ed1
fix encoding of (transparent) mov files
behackl Feb 2, 2024
f4ae088
fixed metadata / comment
behackl Feb 3, 2024
7bca93b
set frame rate for --format=gif in output_stream
behackl Feb 3, 2024
f16d739
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 3, 2024
397c6e0
more video tests for different render settings, also test pix_fmt
behackl Feb 12, 2024
305fb31
improve default bitrate setting via crf
behackl Feb 12, 2024
9f01f43
Merge branch 'av-support' of github.com:behackl/manim into av-support
behackl Feb 12, 2024
4aec603
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 12, 2024
85d2326
Merge remote-tracking branch 'origin/main' into av-support
behackl Apr 19, 2024
fb5132d
parametrized format/transparency rendering test
behackl Apr 24, 2024
9ec6169
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 24, 2024
60abd6e
context managers for (some) av.open
behackl Apr 24, 2024
e7a0f3e
Merge branch 'av-support' of github.com:behackl/manim into av-support
behackl Apr 24, 2024
e0a2f29
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 24, 2024
418ef26
Update manim/utils/commands.py
behackl Apr 24, 2024
8cb266c
Merge branch 'main' into av-support
behackl Apr 25, 2024
28718aa
fixed segfault
behackl Apr 25, 2024
f1485e9
update test data involving implicit functions (output improved!)
behackl Apr 25, 2024
ca711b0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 25, 2024
4c45836
explicity set pix_fmt for transparent webms
behackl Apr 25, 2024
518bef9
Merge branch 'av-support' of github.com:behackl/manim into av-support
behackl Apr 25, 2024
03651f7
special-special case extracting frame from vp9-encoded file with tran…
behackl Apr 27, 2024
25a2419
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 27, 2024
1d8176d
fix transparent gifs, more special casing in parametrized video forma…
behackl Apr 27, 2024
bb9fb87
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 27, 2024
38b9011
Merge remote-tracking branch 'origin/main' into av-support
behackl Apr 28, 2024
f50efa4
run tests on macos-latest again
behackl Apr 28, 2024
4de6f85
removed old control data
behackl Apr 30, 2024
e60b542
Revert "run tests on macos-latest again"
behackl Apr 30, 2024
5fe5bb5
added sound to codec test; fixed issue with sound track in gif (disab…
behackl May 1, 2024
6f2473f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 1, 2024
174a631
manual wav -> ogg transcoding
behackl May 1, 2024
917294f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 1, 2024
d8ff9aa
fixed f-string
behackl May 1, 2024
aeaca15
Merge branch 'av-support' of github.com:behackl/manim into av-support
behackl May 1, 2024
4727a09
refactored codec test, split out gif
behackl May 1, 2024
761ab71
check for non-zero audio samples
behackl May 1, 2024
db8fac0
more cleanup
behackl May 1, 2024
e0a664d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 1, 2024
4633c41
Merge remote-tracking branch 'origin/main' into av-support
behackl May 1, 2024
2781a6d
Merge branch 'av-support' of github.com:behackl/manim into av-support
behackl May 1, 2024
39dcb6e
remove ffmpeg from readthedocs apt_packages
behackl May 1, 2024
dd3a545
round up run_time if positive and shorter than current frame rate
behackl May 5, 2024
15abc8c
added more run_time tests
behackl May 5, 2024
d91f412
black
behackl May 5, 2024
3ddbcc3
improve implementation of test
behackl May 5, 2024
f2a6e66
Merge remote-tracking branch 'origin/main' into av-support
behackl May 5, 2024
14ccf50
removed some unused imports
behackl May 9, 2024
1eb5a42
improve wording of logged warning
behackl May 9, 2024
878debf
move run_time checks from Animation.begin to Scene.get_run_time
behackl May 12, 2024
7d0c892
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 12, 2024
0048b72
remove unused import
behackl May 12, 2024
36207c6
flake: PT012
behackl May 12, 2024
e3f9ced
Merge branch 'main' into av-support
behackl May 15, 2024
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
2 changes: 2 additions & 0 deletions manim/_config/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from pathlib import Path
from typing import Any, ClassVar, Iterable, Iterator, NoReturn

import av
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Show resolved Hide resolved
import numpy as np
from typing_extensions import Self

Expand Down Expand Up @@ -1053,6 +1054,7 @@
val,
["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
)
logging.getLogger("libav").setLevel(self.ffmpeg_loglevel)

@property
def ffmpeg_executable(self) -> str:
Expand Down
1 change: 0 additions & 1 deletion manim/mobject/geometry/polygram.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,6 @@ class Star(Polygon):
Examples
--------
.. manim:: StarExample
:save_as_gif:
behackl marked this conversation as resolved.
Show resolved Hide resolved

class StarExample(Scene):
def construct(self):
Expand Down
165 changes: 80 additions & 85 deletions manim/scene/scene_file_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
__all__ = ["SceneFileWriter"]

import json
import logging
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Show resolved Hide resolved
import os
import shutil
import subprocess
from pathlib import Path
from typing import TYPE_CHECKING, Any

import av
import numpy as np
import srt
from PIL import Image
Expand All @@ -24,7 +26,6 @@
from ..utils.file_ops import (
add_extension_if_not_present,
add_version_before_extension,
ensure_executable,
guarantee_existence,
is_gif_format,
is_png_format,
Expand Down Expand Up @@ -83,14 +84,6 @@
self.next_section(
name="autocreated", type=DefaultSectionType.NORMAL, skip_animations=False
)
# fail fast if ffmpeg is not found
if not ensure_executable(Path(config.ffmpeg_executable)):
raise RuntimeError(
"Manim could not find ffmpeg, which is required for generating video output.\n"
"For installing ffmpeg please consult https://docs.manim.community/en/stable/installation.html\n"
"Make sure to either add ffmpeg to the PATH environment variable\n"
"or set path to the ffmpeg executable under the ffmpeg header in Manim's configuration."
)

def init_output_directories(self, scene_name):
"""Initialise output directories.
Expand Down Expand Up @@ -358,7 +351,7 @@
Whether or not to write to a video file.
"""
if write_to_movie() and allow_write:
self.open_movie_pipe(file_path=file_path)
self.open_partial_movie_stream(file_path=file_path)

def end_animation(self, allow_write: bool = False):
"""
Expand All @@ -371,7 +364,7 @@
Whether or not to write to a video file.
"""
if write_to_movie() and allow_write:
self.close_movie_pipe()
self.close_partial_movie_stream()

def write_frame(self, frame_or_renderer: np.ndarray | OpenGLRenderer):
"""
Expand All @@ -388,15 +381,17 @@
elif config.renderer == RendererType.CAIRO:
frame = frame_or_renderer
if write_to_movie():
self.writing_process.stdin.write(frame.tobytes())
av_frame = av.VideoFrame.from_ndarray(frame, format="rgba")
for packet in self.video_stream.encode(av_frame):
self.video_container.mux(packet)
if is_png_format() and not config["dry_run"]:
self.output_image_from_array(frame)

def write_opengl_frame(self, renderer: OpenGLRenderer):
if write_to_movie():
self.writing_process.stdin.write(
renderer.get_raw_frame_buffer_object_data(),
)
av_frame = av.VideoFrame.from_ndarray(renderer.get_frame(), format="rgba")
behackl marked this conversation as resolved.
Show resolved Hide resolved
behackl marked this conversation as resolved.
Show resolved Hide resolved
for packet in self.video_stream.encode(av_frame):
self.video_container.mux(packet)
elif is_png_format() and not config["dry_run"]:
target_dir = self.image_file_path.parent / self.image_file_path.stem
extension = self.image_file_path.suffix
Expand Down Expand Up @@ -467,11 +462,11 @@
if self.subcaptions:
self.write_subcaption_file()

def open_movie_pipe(self, file_path=None):
"""
Used internally by Manim to initialise
FFMPEG and begin writing to FFMPEG's input
buffer.
def open_partial_movie_stream(self, file_path=None):
"""Open a container holding a video stream.

This is used internally by Manim initialize the container holding
the video stream of a partial movie file.
"""
if file_path is None:
file_path = self.partial_movie_files[self.renderer.num_plays]
Expand All @@ -480,49 +475,45 @@
fps = config["frame_rate"]
if fps == int(fps): # fps is integer
fps = int(fps)
if config.renderer == RendererType.OPENGL:
width, height = self.renderer.get_pixel_shape()
else:
height = config["pixel_height"]
width = config["pixel_width"]

command = [
config.ffmpeg_executable,
"-y", # overwrite output file if it exists
"-f",
"rawvideo",
"-s",
"%dx%d" % (width, height), # size of one frame
"-pix_fmt",
"rgba",
"-r",
str(fps), # frames per second
"-i",
"-", # The input comes from a pipe
"-an", # Tells FFMPEG not to expect any audio
"-loglevel",
config["ffmpeg_loglevel"].lower(),
"-metadata",
f"comment=Rendered with Manim Community v{__version__}",
]
if config.renderer == RendererType.OPENGL:
command += ["-vf", "vflip"]

video_container = av.open(file_path, mode="w")

partial_movie_file_codec = "libx264"
partial_movie_file_pix_fmt = "yuv420p"
av_options = {
"an": "1", # ffmpeg: -an, no audio
}
if is_webm_format():
command += ["-vcodec", "libvpx-vp9", "-auto-alt-ref", "0"]
# .mov format
elif config["transparent"]:
command += ["-vcodec", "qtrle"]
else:
command += ["-vcodec", "libx264", "-pix_fmt", "yuv420p"]
command += [file_path]
self.writing_process = subprocess.Popen(command, stdin=subprocess.PIPE)
partial_movie_file_codec = "libvpx-vp9"
partial_movie_file_pix_fmt = "rgba"
av_options["-auto-alt-ref"] = "1"
elif config.transparent:
partial_movie_file_codec = "qtrle"
partial_movie_file_pix_fmt = "rgba"

stream = video_container.add_stream(
partial_movie_file_codec,
rate=config.frame_rate,
options=av_options,
)
stream.pix_fmt = partial_movie_file_pix_fmt
stream.width = config.pixel_width
stream.height = config.pixel_height

def close_movie_pipe(self):
"""
Used internally by Manim to gracefully stop writing to FFMPEG's input buffer
self.video_container = video_container
self.video_stream = stream

def close_partial_movie_stream(self):
"""Close the currently opened video container.

Used internally by Manim to first flush the remaining packages
in the video stream holding a partial file, and then close
the corresponding container.
"""
self.writing_process.stdin.close()
self.writing_process.wait()
for packet in self.video_stream.encode():
self.video_container.mux(packet)

self.video_container.close()

logger.info(
f"Animation {self.renderer.num_plays} : Partial movie file written in %(path)s",
Expand Down Expand Up @@ -567,37 +558,41 @@
for pf_path in input_files:
pf_path = Path(pf_path).as_posix()
fp.write(f"file 'file:{pf_path}'\n")
commands = [
config.ffmpeg_executable,
"-y", # overwrite output file if it exists
"-f",
"concat",
"-safe",
"0",
"-i",
str(file_list),
"-loglevel",
config.ffmpeg_loglevel.lower(),
"-metadata",
f"comment=Rendered with Manim Community v{__version__}",
"-nostdin",
]

av_options = {
"safe": "0",
"metadata": f"comment=Rendered with Manim Community v{__version__}",
}
if create_gif:
commands += [
"-vf",
f"fps={np.clip(config['frame_rate'], 1, 50)},split[s0][s1];[s0]palettegen=stats_mode=diff[p];[s1][p]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle",
]
else:
commands += ["-c", "copy"]
av_options["vf"] = ( # add video filter (is there a better way to do this?)
f"fps={np.clip(config['frame_rate'], 1, 50)},"
"split[s0][s1];[s0]palettegen=stats_mode=diff[p];"
"[s1][p]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle"
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like video filters must be applied separately: PyAV-Org/PyAV#239

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I've tried the Graph-construction that you have to push/pull frames through initially, but was happy when you came up with the alternative options-approach.

Well, seems like we'll have to look into it regardless.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But does the options approach work? I'm trying to move Manim Slides to use av too, and cannot make the reverse filter work.

else: # copy codec of combined files
av_options["c"] = "copy"

if not includes_sound:
commands += ["-an"]
av_options["an"] = "1"

partial_movies_input = av.open(
JasonGrace2282 marked this conversation as resolved.
Show resolved Hide resolved
str(file_list), options=av_options, format="concat"
)
partial_movies_stream = partial_movies_input.streams.video[0]
output_container = av.open(str(output_file), mode="w")
output_stream = output_container.add_stream(template=partial_movies_stream)

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:
continue

commands += [str(output_file)]
# We need to assign the packet to the new stream.
packet.stream = output_stream
output_container.mux(packet)

combine_process = subprocess.Popen(commands)
combine_process.wait()
partial_movies_input.close()
output_container.close()

def combine_to_movie(self):
"""Used internally by Manim to combine the separate
Expand Down
Loading
Loading