Skip to content

Commit 865bd39

Browse files
scene_file_writer: convert frame_rate to fraction
1 parent d6f066c commit 865bd39

File tree

2 files changed

+32
-6
lines changed

2 files changed

+32
-6
lines changed

manim/scene/scene_file_writer.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,23 @@
4040
from manim.renderer.opengl_renderer import OpenGLRenderer
4141

4242

43+
def to_av_frame_rate(fps):
44+
epsilon1 = 1e-4
45+
epsilon2 = 0.02
46+
47+
if isinstance(fps, int):
48+
(num, denom) = (fps, 1)
49+
elif abs(fps - round(fps)) < epsilon1:
50+
(num, denom) = (round(fps), 1)
51+
else:
52+
denom = 1001
53+
num = round(fps * denom / 1000) * 1000
54+
if abs(fps - num / denom) >= epsilon2:
55+
raise ValueError("invalid frame rate")
56+
57+
return av.utils.Fraction(num, denom)
58+
59+
4360
class SceneFileWriter:
4461
"""
4562
SceneFileWriter is the object that actually writes the animations
@@ -506,9 +523,7 @@ def open_partial_movie_stream(self, file_path=None) -> None:
506523
file_path = self.partial_movie_files[self.renderer.num_plays]
507524
self.partial_movie_file_path = file_path
508525

509-
fps = config["frame_rate"]
510-
if fps == int(fps): # fps is integer
511-
fps = int(fps)
526+
fps = to_av_frame_rate(config.frame_rate)
512527

513528
partial_movie_file_codec = "libx264"
514529
partial_movie_file_pix_fmt = "yuv420p"
@@ -530,7 +545,7 @@ def open_partial_movie_stream(self, file_path=None) -> None:
530545
with av.open(file_path, mode="w") as video_container:
531546
stream = video_container.add_stream(
532547
partial_movie_file_codec,
533-
rate=config.frame_rate,
548+
rate=fps,
534549
options=av_options,
535550
)
536551
stream.pix_fmt = partial_movie_file_pix_fmt
@@ -636,7 +651,7 @@ def combine_files(
636651
output_stream.pix_fmt = "pal8"
637652
output_stream.width = config.pixel_width
638653
output_stream.height = config.pixel_height
639-
output_stream.rate = config.frame_rate
654+
output_stream.rate = to_av_frame_rate(config.frame_rate)
640655
graph = av.filter.Graph()
641656
input_buffer = graph.add_buffer(template=partial_movies_stream)
642657
split = graph.add("split")
@@ -663,7 +678,8 @@ def combine_files(
663678
while True:
664679
try:
665680
frame = graph.pull()
666-
frame.time_base = output_stream.codec_context.time_base
681+
if output_stream.codec_context.time_base is not None:
682+
frame.time_base = output_stream.codec_context.time_base
667683
frame.pts = frames_written
668684
frames_written += 1
669685
output_container.mux(output_stream.encode(frame))

tests/test_scene_rendering/test_file_writer.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
import av
55
import numpy as np
66
import pytest
7+
from av.utils import Fraction
78

89
from manim import DR, Circle, Create, Scene, Star, tempconfig
10+
from manim.scene.scene_file_writer import to_av_frame_rate
911
from manim.utils.commands import capture, get_video_metadata
1012

1113

@@ -175,3 +177,11 @@ def test_unicode_partial_movie(tmpdir, simple_scenes_path):
175177

176178
_, err, exit_code = capture(command)
177179
assert exit_code == 0, err
180+
181+
182+
def test_frame_rates():
183+
assert to_av_frame_rate(25) == Fraction(25, 1)
184+
assert to_av_frame_rate(24.0) == Fraction(24, 1)
185+
assert to_av_frame_rate(23.976) == Fraction(24 * 1000, 1001)
186+
assert to_av_frame_rate(23.98) == Fraction(24 * 1000, 1001)
187+
assert to_av_frame_rate(59.94) == Fraction(60 * 1000, 1001)

0 commit comments

Comments
 (0)