Skip to content

Commit df49961

Browse files
committed
Add render option to use x265 with lossless settings
By default we use x264 when rendering to the `mp4` format with `crf` set to 23. x265 (hevc) has a [lossless](https://x265.readthedocs.io/en/stable/lossless.html) mode, where the encoder is configured such that the output is an exact copy of the input. Since `manim` scenes consist of text and shapes, the lossless mode works well for us, and ensures that the output videos will be the highest quality when desired. This means that users can safely do an editing pass without risking losing further quality. Anecdotally, I've noticed slightly better performance than x264 with about 2.5x the file size. Before: Before (1,436,872 bytes): ```shell $ time venv/bin/manim -pqm quad.py Fermat ... venv/bin/manim -pqm quad.py Fermat 59.41s user 144.46s system 253% cpu 1:20.44 total ``` After (3,494,923 bytes): ```shell $ time venv/bin/manim -pqm quad.py Fermat --lossless ... venv/bin/manim -pqm quad.py Fermat --lossless 144.52s user 12.46s system 274% cpu 57.276 total ``` So, I added this as an option (for `mp4` containers).
1 parent 8f919b1 commit df49961

File tree

3 files changed

+26
-2
lines changed

3 files changed

+26
-2
lines changed

manim/_config/utils.py

+10
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,7 @@ def digest_args(self, args: argparse.Namespace) -> Self:
767767
"dry_run",
768768
"no_latex_cleanup",
769769
"preview_command",
770+
"lossless",
770771
]:
771772
if hasattr(args, key):
772773
attr = getattr(args, key)
@@ -1491,6 +1492,15 @@ def zero_pad(self) -> int:
14911492
def zero_pad(self, value: int) -> None:
14921493
self._set_int_between("zero_pad", value, 0, 9)
14931494

1495+
@property
1496+
def lossless(self) -> bool:
1497+
"""Whether to use lossless x265 encoding (mp4 format only)."""
1498+
return self._d["lossless"]
1499+
1500+
@lossless.setter
1501+
def lossless(self, value: bool) -> None:
1502+
self._set_boolean("lossless", value)
1503+
14941504
def get_dir(self, key: str, **kwargs: Any) -> Path:
14951505
"""Resolve a config option that stores a directory.
14961506

manim/cli/render/render_options.py

+6
Original file line numberDiff line numberDiff line change
@@ -212,4 +212,10 @@ def validate_resolution(
212212
help="Use shaders for OpenGLVMobject stroke which are compatible with transformation matrices.",
213213
default=None,
214214
),
215+
option(
216+
"--lossless",
217+
is_flag=True,
218+
help="Render with lossless x265 encoding (mp4 format only).",
219+
default=False,
220+
),
215221
)

manim/scene/scene_file_writer.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -536,12 +536,20 @@ def open_partial_movie_stream(self, file_path=None) -> None:
536536

537537
fps = to_av_frame_rate(config.frame_rate)
538538

539-
partial_movie_file_codec = "libx264"
540539
partial_movie_file_pix_fmt = "yuv420p"
541540
av_options = {
542541
"an": "1", # ffmpeg: -an, no audio
543-
"crf": "23", # ffmpeg: -crf, constant rate factor (improved bitrate)
544542
}
543+
if config.lossless:
544+
partial_movie_file_codec = "libx265"
545+
av_options["x265-params"] = (
546+
"lossless=1" # ffmpeg: set lossless mode for x265
547+
)
548+
else:
549+
partial_movie_file_codec = "libx264"
550+
av_options["crf"] = (
551+
"23" # ffmpeg: -crf, constant rate factor (improved bitrate)
552+
)
545553

546554
if config.movie_file_extension == ".webm":
547555
partial_movie_file_codec = "libvpx-vp9"

0 commit comments

Comments
 (0)