manim-slides
manim-slides copied to clipboard
[BUG] `[Errno 22] Invalid argument` when rendering a large number of animations
Terms
- [x] Checked the existing issues and discussions to see if my issue had not already been reported;
- [x] Checked the frequently asked questions;
- [x] Read the installation instructions;
- [x] Created a virtual environment in which I can reproduce my bug;
Describe the issue
I find that manim-slides is very useful when rendering a small number of animations. However, it will report an error once the number of animations is relatively large. For example, there are many animations in this piece of my code. Manim can render it into a video normally, but an error occurs when using manim-slides render. Who can help me? I really want to solve this problem!
Command
manim-slides render Golden_section_point.py
Issue Type
Visual bug when presenting (manim-slides present)
Python version
Python 3.11
Python environment
Manim bindings:
manim (version: 0.19.0)
manimgl not found
Qt API: pyside6 (version: 6.8.2)
What is your platform?
Windows
Other platform
No response
Manim Slides Python code
import numpy as np
from manim import *
from compass_and_ruler_scene_CE import *
from manim_slides import Slide
from ercihanshu_utils import *
import manimforge as mf
mf.setup()
config["max_files_cached"] = 5000
class Golden_section_point(CompassAndRulerScene, Slide):
Dot_config = {
"radius": 0.04,
"color": WHITE,
}
CONFIG = {
"compass_config": {
"leg_length": 3,
"stroke_color": RED, # 圆规腿颜色
"fill_color": WHITE, # 圆盘颜色
},
"arc_config": {
"stroke_color": YELLOW_B,
"stroke_width": 2.5,
},
"add_ruler": True, # add ruler when the scene starts
}
def setup(self):
# 先调用父类的setup方法来初始化ruler等属性
super().setup()
# 然后设置TeX模板
# MathTex.set_default(tex_template=TexTemplateLibrary.ctex)
def construct(self):
rate_ease_out_back = {"rate_func": rate_functions.ease_out_back}
d1, d2 = LEFT * 2, RIGHT * 2
dA = Dot(LEFT * 2, **self.Dot_config)
dB = Dot(RIGHT * 2, **self.Dot_config)
lAB = Line(dA.get_center(), dB.get_center())
lb_dA, lb_dB = Tex("A").next_to(dA, DOWN, buff=0.1), Tex("B").next_to(
dB, DOWN, buff=0.1
)
self.add(dA, dB, lAB, lb_dA, lb_dB)
self.bring_to_back(lAB)
# 延长AB
l1 = DashedLine(
dB.get_center(), RIGHT * 5, dash_length=0.1, **self.CONFIG["arc_config"]
)
self.set_ruler(dB.get_center(), RIGHT * 5)
self.wait(0.1)
self.draw_line_(l1, run_time=0.8)
self.wait(0.1)
self.put_aside_ruler(DOWN * 0.7)
p1, p2 = RIGHT * 0.5, RIGHT * 3.5
self.set_compass(dB.get_center(), RIGHT * 2.5)
self.wait(0.1)
self.set_compass(dB.get_center(), p2)
self.wait(0.1)
arc_r = Arc(
arc_center=dB.get_center(),
start_angle=-15 * DEGREES,
angle=30 * DEGREES,
radius=1.5,
**self.CONFIG["arc_config"]
)
arc_l = Arc(
arc_center=dB.get_center(),
start_angle=165 * DEGREES,
angle=30 * DEGREES,
radius=1.5,
**self.CONFIG["arc_config"]
)
self.set_compass_to_draw_arc_(arc_r, adjust_angle=-PI, run_time=0.6)
self.wait(0.1)
self.draw_arc_by_compass(arc_r, run_time=0.5)
self.wait(0.1)
self.set_compass_to_draw_arc_(arc_l, adjust_angle=PI, run_time=0.6)
self.wait(0.1)
self.draw_arc_by_compass(arc_l, run_time=0.5)
self.next_slide()
p3 = dB.get_center() + UP * 1.5 * np.sqrt(3)
p4 = dB.get_center() + DOWN * 1.5 * np.sqrt(3)
self.set_compass(p1, RIGHT * 1.5)
self.wait(0.2)
self.set_compass(p1, p2)
self.wait(0.2)
arc_ru = Arc(
arc_center=p1,
start_angle=45 * DEGREES,
angle=30 * DEGREES,
radius=3,
**self.CONFIG["arc_config"]
)
arc_lu = Arc(
arc_center=p2,
start_angle=105 * DEGREES,
angle=30 * DEGREES,
radius=3,
**self.CONFIG["arc_config"]
)
self.set_compass_to_draw_arc_(arc_ru, adjust_angle=PI, run_time=0.4)
self.wait(0.1)
self.draw_arc_by_compass(arc_ru, run_time=0.5)
self.wait(0.1)
self.set_compass_to_draw_arc_(arc_lu, adjust_angle=PI, run_time=0.4)
self.wait(0.2)
self.draw_arc_by_compass(arc_lu, run_time=0.5)
self.wait(0.1)
self.put_aside_compass(DR * 0.6)
self.wait(0.2)
# 画垂线
l2 = Line(p3 + UP, d2)
self.set_ruler(p3, p4)
self.wait(0.1)
self.emphasize_dot([p3, dB.get_center()], run_time=0.25)
self.wait(0.1)
self.draw_line_(l2)
self.wait(0.1)
self.put_aside_ruler(DOWN * 0.7)
self.wait(0.1)
self.highlight_on(l2)
angle_90 = Square(0.22, stroke_width=2, stroke_color=GREY_C).next_to(
dB.get_center(), UL, buff=0
)
self.bring_to_back(angle_90)
self.play(FadeIn(angle_90), run_time=0.64)
self.next_slide()
# 找AB中点
p5 = lAB.get_center() + UP * np.sqrt(5)
p6 = lAB.get_center() + DOWN * np.sqrt(5)
self.set_compass(dA.get_center(), dA.get_center() + RIGHT, run_time=0.75)
self.wait(0.1)
arc_r2 = Arc(
arc_center=dA.get_center(),
start_angle=-70 * DEGREES,
angle=140 * DEGREES,
radius=3,
**self.CONFIG["arc_config"]
)
arc_l2 = Arc(
arc_center=dB.get_center(),
start_angle=110 * DEGREES,
angle=140 * DEGREES,
radius=3,
**self.CONFIG["arc_config"]
)
self.set_compass_to_draw_arc_(arc_r2, adjust_angle=-PI, run_time=0.4)
self.wait(0.1)
self.draw_arc_by_compass(arc_r2)
self.wait(0.1)
self.set_compass_to_draw_arc_(arc_l2, adjust_angle=PI, run_time=0.4)
self.wait(0.1)
self.draw_arc_by_compass(arc_l2)
self.wait(0.1)
self.put_aside_compass(DOWN * 0.8)
self.wait(0.1)
self.emphasize_dot([p5, p6], run_time=0.25)
self.set_ruler(p5, p6)
self.wait(0.1)
l3 = DashedLine(p5, p6, dash_length=0.1, **self.CONFIG["arc_config"]).scale(
1.25
)
self.draw_line_(l3)
self.wait(0.2)
self.put_aside_ruler(DOWN * 0.8)
center = Dot(l3.get_center(), **self.Dot_config)
self.play(FadeIn(center))
self.wait(1)
arc_l3 = Arc(
arc_center=dB.get_center(),
start_angle=-180 * DEGREES,
angle=-90 * DEGREES,
radius=2,
**self.CONFIG["arc_config"]
)
p7 = dB.get_center() + UP * 2
self.set_compass(dB.get_center(), dB.get_center() + LEFT * 2, run_time=0.5)
self.wait(0.1)
self.set_compass_to_draw_arc_(arc_l3, adjust_angle=PI, run_time=0.4)
self.wait(0.1)
self.draw_arc_by_compass(arc_l3)
self.wait(0.1)
self.put_aside_compass(DOWN * 0.8)
dC = Dot(p7, **self.Dot_config)
lb_dC = Tex("C").next_to(dC, RIGHT, buff=0.1)
self.play(
LaggedStart(
FadeIn(dC),
FadeIn(lb_dC, shift=RIGHT * 0.5, scale=0.1, **rate_ease_out_back),
lag_ratio=0.6,
)
)
self.next_slide()
line_AC = Line(d1, p7, **self.CONFIG["arc_config"])
self.emphasize_dot([d1, p7], run_time=0.25)
self.set_ruler(d1, p7)
self.wait(0.1)
self.draw_line_(line_AC)
self.wait(0.2)
self.put_aside_ruler(DOWN * 0.8)
self.next_slide()
l4 = Line(d2, p7, **self.CONFIG["arc_config"])
self.add(l4)
out = Group(*self.mobjects).remove(
dA, dB, dC, lAB, line_AC, lb_dA, lb_dB, lb_dC, l4, dC, angle_90
)
self.play(FadeOut(out))
# out=VGroup(l1,arc_l,arc_l2,arc_l3,arc_lu,arc_r,arc_r2,arc_ru)
self.next_slide()
arc_4 = Arc(
arc_center=p7,
start_angle=-90 * DEGREES,
angle=-np.arctan(2) - 10 * DEGREES,
radius=2,
stroke_color=YELLOW_B,
stroke_width=2.5,
)
arc_5 = Arc(
arc_center=d1,
start_angle=np.arctan(1 / 2),
angle=-np.arctan(1 / 2) - 10 * DEGREES,
radius=2 * np.sqrt(5) - 2,
stroke_color=YELLOW_B,
stroke_width=2.5,
)
dD = Dot(line_AC.point_from_proportion(1 - np.sqrt(5) / 5), **self.Dot_config)
lb_dD = Tex("D").next_to(dD, UL, buff=0.1)
dE = Dot(lAB.point_from_proportion((np.sqrt(5) - 1) / 2), **self.Dot_config)
lb_dE = Tex("E").next_to(dE, DR, buff=0.1)
super().setup() # 圆规初始化
self.set_compass(p7, d2 + UP)
self.set_compass_to_draw_arc_(arc_4, adjust_angle=-PI, run_time=0.4)
self.wait(0.1)
self.draw_arc_by_compass(arc_4)
self.wait(0.1)
self.put_aside_compass(DOWN * 0.8)
self.wait(0.1)
self.emphasize_dot(dD.get_center())
self.wait(0.1)
self.play(
LaggedStart(
FadeIn(dD),
FadeIn(lb_dD, shift=UL * 0.5, scale=0.1, **rate_ease_out_back),
lag_ratio=0.6,
run_time=1,
)
)
self.next_slide()
self.set_compass(d1, line_AC.point_from_proportion(0.3))
self.wait(0.1)
self.set_compass_to_draw_arc_(arc_5, adjust_angle=PI, run_time=0.5)
self.wait(0.1)
self.draw_arc_by_compass(arc_5, run_time=0.5)
self.next_slide()
self.put_aside_compass(DOWN)
self.next_slide()
self.emphasize_dot(dE.get_center())
self.play(
LaggedStart(
FadeIn(dE),
FadeIn(lb_dE, shift=DOWN * 0.5, scale=0.1, **rate_ease_out_back),
lag_ratio=0.6,
run_time=1,
)
)
self.highlight_on([dE, lb_dE])
self.wait()
if __name__ == "__main__":
from os import system
system("manim-slides render {} ".format(__file__))
Relevant log output
INFO Animation 763 : Using cached data (hash : 2852726489_2655088185_1908071073) cairo_renderer.py:89
INFO Animation 764 : Using cached data (hash : 2852726489_3052040169_409836217) cairo_renderer.py:89
INFO Animation 765 : Using cached data (hash : 2852726489_1703042791_1696893326) cairo_renderer.py:89
INFO Animation 766 : Using cached data (hash : 2852726489_2143140768_2214148004) cairo_renderer.py:89
INFO Animation 767 : Using cached data (hash : 2852726489_146554378_2349365127) cairo_renderer.py:89
INFO Animation 768 : Using cached data (hash : 2852726489_2789980886_757578521) cairo_renderer.py:89
INFO Animation 769 : Using cached data (hash : 2852726489_2198369401_3849849265) cairo_renderer.py:89
INFO Animation 770 : Using cached data (hash : 2852726489_2834709167_2430403131) cairo_renderer.py:89
INFO Combining to Movie file. scene_file_writer.py:739
[04/03/25 10:11:43] INFO scene_file_writer.py:886
File ready at
'C:\Users\Administrator\Desktop\manimdemo\media\videos\3gold_point\1080p60\G
olden_section_point.mp4'
INFO Rendered Golden_section_point scene.py:262
Played 771 animations
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ C:\ProgramData\miniconda3\envs\manim-slides-CE\Lib\site-packages\manim\cli\render\commands.py:12 │
│ 5 in render │
│ │
│ 122 │ │ │ try: │
│ 123 │ │ │ │ with tempconfig({}): │
│ 124 │ │ │ │ │ scene = SceneClass() │
│ ❱ 125 │ │ │ │ │ scene.render() │
│ 126 │ │ │ except Exception: │
│ 127 │ │ │ │ error_console.print_exception() │
│ 128 │ │ │ │ sys.exit(1) │
│ │
│ C:\ProgramData\miniconda3\envs\manim-slides-CE\Lib\site-packages\manim_slides\slide\manim.py:167 │
│ in render │
│ │
│ 164 │ │ │
│ 165 │ │ config["max_files_cached"] = max_files_cached │
│ 166 │ │ │
│ ❱ 167 │ │ self._save_slides( │
│ 168 │ │ │ use_cache=not (config["disable_caching"] or self.disable_caching), │
│ 169 │ │ │ flush_cache=(config["flush_cache"] or self.flush_cache), │
│ 170 │ │ │ skip_reversing=self.skip_reversing, │
│ │
│ C:\ProgramData\miniconda3\envs\manim-slides-CE\Lib\site-packages\manim_slides\slide\base.py:576 │
│ in _save_slides │
│ │
│ 573 │ │ │ │
│ 574 │ │ │ # We only concat animations if it was not present │
│ 575 │ │ │ if not use_cache or not dst_file.exists(): │
│ ❱ 576 │ │ │ │ concatenate_video_files(slide_files, dst_file) │
│ 577 │ │ │ │
│ 578 │ │ │ # We only reverse video if it was not present │
│ 579 │ │ │ if not use_cache or not rev_file.exists(): │
│ │
│ C:\ProgramData\miniconda3\envs\manim-slides-CE\Lib\site-packages\manim_slides\utils.py:67 in │
│ concatenate_video_files │
│ │
│ 64 │ │ │ │ packet.stream = output_audio_stream │
│ 65 │ │ │ else: │
│ 66 │ │ │ │ continue # We don't support subtitles │
│ ❱ 67 │ │ │ output_container.mux(packet) │
│ 68 │ │
│ 69 │ os.unlink(tmp_file) # https://stackoverflow.com/a/54768241 │
│ 70 │
│ │
│ in av.container.output.OutputContainer.mux:257 │
│ │
│ in av.container.output.OutputContainer.mux_one:278 │
│ │
│ in av.container.core.Container.err_check:286 │
│ │
│ in av.error.err_check:326 │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
ValueError: [Errno 22] Invalid argument:
'slides\\files\\Golden_section_point\\e7fbccb2fb654008e5d649d0a3f4eea9874dfc3d2901efdd2691bb8d0f387b98.mp4'
Screenshots
Additional information
No response
Recommended fix or suggestions
No response
Hi @hcq11419, thanks for reporting your bug! Could you please provide a fully working example? Here, I am missing other files if I want to test locally :)
@jeertmans It has nothing to do with the code. I've tested many different versions. As long as there are a large number of animations, for example, more than a few hundred, this error will occur.
from manim import *
# utils
get_angle_ = lambda c: (
np.angle(-c) + PI if not c / abs(c) == 1 else 0
) # 利用np.angle求出复数a+bj与实轴直角的夹角,复数中c / abs(c) == 1的话意味着他的虚部为0
# c为复平面中,这里用-c+PI
# 可以调用my_fun中的的get_angle(a,b)代替。
convert_angle = lambda a: a if a >= 0 else a + TAU # 将负角度转化为正角度
class Compass(VGroup):
def __init__(
self,
stroke_color=RED, # 圆规颜色
fill_color=WHITE,
stroke_width=2,
leg_length=3,
leg_width=0.12,
r=0.2,
depth_test=True,
span=2.5, # 针脚距离,半径
**kwargs
):
super().__init__()
self.span = span
self.leg_length = leg_length
self.stroke_color = stroke_color
self.fill_color = fill_color
self.stroke_width = stroke_width
self.leg_width = leg_width
self.r = r
self.depth_test = depth_test
self.create_compass()
def create_compass(self):
s, l, r, w = self.span, self.leg_length, self.r, self.leg_width
self.theta = np.arcsin(s / 2 / l) # theta 为圆规腿与针脚连线的夹角
self.c = Circle(
radius=r,
fill_color=self.fill_color,
fill_opacity=1,
stroke_color=self.stroke_color,
stroke_width=self.stroke_width * 5,
)
c2 = Circle(
radius=r + self.stroke_width * 5 / 100 / 2,
fill_opacity=0,
stroke_color=self.fill_color,
stroke_width=self.stroke_width,
)
# leg_1是针尖腿,leg_2是笔尖腿
self.leg_1 = Polygon(
ORIGIN,
l * RIGHT,
(l - w * np.sqrt(3)) * RIGHT + w * DOWN,
w * DOWN,
stroke_width=0,
stroke_color=self.fill_color,
fill_color=self.stroke_color,
fill_opacity=1,
).rotate(-PI / 2 - self.theta, about_point=self.c.get_center())
self.leg_2 = Polygon(
ORIGIN,
l * RIGHT,
(l - w * np.sqrt(3)) * RIGHT + w * UP,
w * UP,
stroke_width=0,
stroke_color=self.fill_color,
fill_color=self.stroke_color,
fill_opacity=1,
).rotate(-PI / 2 + self.theta, about_point=self.c.get_center())
# self.leg_1, self.leg_2 = VGroup(leg_01, leg_11), VGroup(leg_02, leg_12, pen_point)
h = Line(
UP * r,
UP * (r + r * 1.8),
stroke_color=self.stroke_color,
stroke_width=self.stroke_width * 6,
)
self.head = VGroup(h, self.c, c2)
self.add(self.leg_1, self.leg_2, self.head)
self.move_to(ORIGIN)
return self
def get_niddle_tip(self): # 获取针脚
return self.leg_1.get_vertices()[1]
def get_pen_tip(self): # 获取笔尖
return self.leg_2.get_vertices()[1]
def move_niddle_tip_to(self, pos):
self.shift(pos - self.get_niddle_tip())
return self
def rotate_about_niddle_tip(self, angle=PI / 2):
p = Dot(self.get_niddle_tip()) # manim bug?
self.rotate(angle=angle, about_point=p.get_center())
def get_span(self):
# return self.span 如果进行了缩放而self.span没变会有问题
return get_norm(self.get_pen_tip() - self.get_niddle_tip())
def set_span(self, s):
self.span = s
l, r, w = self.leg_length, self.r, self.leg_width
theta_new, theta_old = np.arcsin(s / 2 / l), self.theta
sign = np.sign(
get_angle_(
R3_to_complex(
self.leg_2.get_vertices()[1] - self.leg_2.get_vertices()[0]
)
)
- get_angle_(
R3_to_complex(
self.leg_1.get_vertices()[1] - self.leg_1.get_vertices()[0]
)
)
)
# sign=np.sign(get_angle(self.leg_2.get_vertices()[0],self.leg_2.get_vertices()[1])-get_angle(
# self.leg_1.get_vertices()[0],self.leg_1.get_vertices()[1])) #调用my_fun
rotate_angle = 2 * (theta_new - theta_old) * sign
self.leg_2.rotate(rotate_angle, about_point=self.c.get_center())
self.theta = theta_new
self.head.rotate(rotate_angle / 2, about_point=self.c.get_center())
self.rotate_about_niddle_tip(-rotate_angle / 2)
return self
def set_compass(self, center, pen_tip):
self.move_niddle_tip_to(center)
self.set_span(get_norm(pen_tip - center))
self.rotate_about_niddle_tip(
np.angle(R3_to_complex(pen_tip - center))
- np.angle(R3_to_complex(self.get_pen_tip() - center))
)
return self
def set_compass_to_draw_arc(self, arc):
return self.set_compass(arc.arc_center, arc.get_start())
def reverse_tip(self):
return self.flip(
axis=self.head[0].get_end() - self.head[0].get_start(),
about_point=self.c.get_center(),
)
class DrawingScene(Scene):
def __init__(
self,
compass_config={
"stroke_color": RED, # 圆规颜色
"fill_color": WHITE,
"stroke_width": 2,
"leg_length": 3,
"leg_width": 0.12,
"r": 0.2,
"depth_test": True,
}, # to define size and style of the compass
ruler_config={
"width": 10,
"height": 0.8,
"stroke_width": 8,
"stroke_color": GREY_E,
"stroke_opacity": 0.4,
"fill_color": WHITE,
"fill_opacity": 0.5,
}, # to define size and style of the ruler
dot_config={
"radius": 0.06,
"color": GREY_E,
}, # to define size and color of the dot (e.g., dot in arc/circle's center)
line_config={
"stroke_color": YELLOW_E,
"stroke_width": 2.5,
}, # the default line style drawing by ruler (not the defualt arc style drawing by compass)
brace_config={
"fill_color": YELLOW_E,
"buff": 0.025,
},
text_config={
"font_size": 20, # 5 times than the actual size here and
# will be sacle down to the actual size later
# in 'get_length_label' methods.
# 'font': 'Cambria Math',
"color": YELLOW_E,
},
add_ruler=True,
):
super().__init__()
self.compass_config = compass_config
self.ruler_config = ruler_config
self.dot_config = dot_config
self.line_config = line_config
self.brace_config = brace_config
self.text_config = text_config
self.add_ruler = add_ruler
def setup(self):
self.cp = Compass(**self.compass_config)
self.ruler = VGroup(
Rectangle(**self.ruler_config)
.stretch_to_fit_height(
self.ruler_config["height"]
- self.ruler_config["stroke_width"] / 2 / 100
)
.round_corners(self.ruler_config["height"] / 8),
Rectangle(**self.ruler_config).set_opacity(0),
)
self.dot = Dot(**self.dot_config)
self.cp.move_to(UP * 10)
if self.add_ruler:
self.ruler.move_to(DOWN * 10)
self.add(self.ruler)
self.add(self.cp)
self.temp_points = []
def construct(self):
self.add(self.cp)
self.play(self.cp.animate.move_niddle_tip_to(ORIGIN), run_time=1)
# self.wait(0.3)
self.play(Wait(0.3))
self.set_span(3.6, run_time=1, rate_func=smooth)
# self.wait(0.5)
self.play(Wait(0.5))
self.set_compass(DL * 0.5, UR * 0.5, run_time=1, rate_func=there_and_back)
arc = Arc(color=GREY_E)
self.set_compass_to_draw_arc(arc)
self.draw_arc_by_compass(arc)
# self.wait()
self.play(Wait(1))
def set_span(self, s, run_time=1, rate_func=smooth):
"""设置针尖"""
s_old = self.cp.get_span()
n = int(run_time * self.camera.frame_rate)
dt = 1 / self.camera.frame_rate
t_series = np.linspace(1, n, n) / n
# s_series = s_old + rate_func(t_series) * (s - s_old)
s_series = [s_old + rate_func(t_series[i]) * (s - s_old) for i in range(n)]
for i in range(n):
self.cp.set_span(s_series[i])
self.play(Wait(dt))
# self.wait(dt)
def set_compass_direction(self, start, end, run_time=1, rate_func=smooth):
vect = end - start
a = np.angle(R3_to_complex(vect))
c_old, p_old = self.cp.get_niddle_tip(), self.cp.get_pen_tip()
a_old = np.angle(R3_to_complex(p_old - c_old))
n = int(run_time * self.camera.frame_rate)
dt = 1 / self.camera.frame_rate
t_series = np.linspace(1, n, n) / n
c_series = [c_old + rate_func(t_series[i]) * (start - c_old) for i in range(n)]
delta_a = (a - a_old) / n
for i in range(n):
self.bring_to_front(self.cp)
self.cp.move_niddle_tip_to(c_series[i])
self.cp.rotate_about_niddle_tip(delta_a)
# self.wait(dt)
self.play(Wait(dt))
def set_compass(
self, center, pen_tip, run_time=1, rate_func=smooth, emphasize_dot=False
):
if emphasize_dot:
run_time -= 0.15
c_old, p_old = self.cp.get_niddle_tip(), self.cp.get_pen_tip()
n = int(run_time * self.camera.frame_rate)
dt = 1 / self.camera.frame_rate
t_series = np.linspace(1, n, n) / n
# s_series = s_old + rate_func(t_series) * (s - s_old)
c_series = [c_old + rate_func(t_series[i]) * (center - c_old) for i in range(n)]
p_series = [
p_old + rate_func(t_series[i]) * (pen_tip - p_old) for i in range(n)
]
for i in range(n):
self.bring_to_front(self.cp)
self.cp.set_compass(c_series[i], p_series[i])
# self.wait(dt)
self.play(Wait(dt))
if emphasize_dot:
self.emphasize_dot([center, pen_tip], run_time=0.15)
def set_compass_(
self,
center,
pen_tip,
adjust_angle=0,
run_time=1,
rate_func=smooth,
emphasize_dot=False,
):
vect = center - pen_tip
a = np.angle(R3_to_complex(vect)) + adjust_angle
s = get_norm(vect)
c_old, p_old, s_old = (
self.cp.get_niddle_tip(),
self.cp.get_pen_tip(),
self.cp.get_span(),
)
a_old = np.angle(R3_to_complex(p_old - c_old))
if emphasize_dot:
run_time -= 0.15
n = int(run_time * self.camera.frame_rate)
dt = 1 / self.camera.frame_rate
t_series = np.linspace(1, n, n) / n
c_series = [c_old + rate_func(t_series[i]) * (center - c_old) for i in range(n)]
delta_a = (a - a_old) / n
s_series = [s_old + rate_func(t_series[i]) * (s - s_old) for i in range(n)]
for i in range(n):
self.bring_to_front(self.cp)
self.cp.move_niddle_tip_to(c_series[i])
self.cp.rotate_about_niddle_tip(delta_a)
self.cp.set_span(s_series[i])
# self.wait(dt)
self.play(Wait(dt))
if emphasize_dot:
self.emphasize_dot([center, pen_tip], run_time=0.15)
def set_compass_to_draw_arc(self, arc, **kwargs):
self.set_compass(arc.arc_center, arc.get_start(), **kwargs)
def set_compass_to_draw_arc_(self, arc, **kwargs):
self.set_compass_(arc.arc_center, arc.get_start(), **kwargs)
def draw_arc_by_compass(
self,
arc,
is_prepared=True,
run_time=1,
rate_func=smooth,
reverse=False,
add_center=False,
**kwargs
):
self.bring_to_front(self.cp)
if not is_prepared:
self.set_compass_to_draw_arc(arc, run_time=0.5)
theta = arc.angle if not reverse else -1 * arc.angle
self.play(
Rotating(self.cp, radians=theta, about_point=self.cp.get_niddle_tip()),
Create(arc),
rate_func=rate_func,
run_time=run_time,
)
if add_center:
d = Dot(self.cp.get_niddle_tip(), **self.dot_config).scale(0.5)
self.temp_points.append(d)
self.add(d)
def emphasize_dot(self, pos, add_dot=False, size=1.2, run_time=0.2, **kwargs):
if type(pos) == list:
d = VGroup(
*[
Dot(
pos[i], radius=size / 2, color=YELLOW_C, fill_opacity=0.25
).scale(0.25)
for i in range(len(pos))
]
)
else:
d = Dot(pos, radius=size / 2, color=YELLOW_C, fill_opacity=0.25).scale(0.25)
self.add(d)
if type(pos) == list:
self.play(
d[0].animate.scale(4),
d[1].animate.scale(4),
rate_func=linear,
run_time=run_time,
)
else:
self.play(d.animate.scale(4), rate_func=linear, run_time=run_time)
self.remove(d)
if add_dot:
if type(pos) == list:
dot = VGroup(*[Dot(pos[i], **kwargs) for i in range(len(pos))])
else:
dot = Dot(pos, **kwargs)
self.add(dot)
return dot
def set_ruler(self, pos1, pos2, run_time=1, rate_func=smooth):
p1, p2 = self.ruler[-1].get_vertices()[1], self.ruler[-1].get_vertices()[0]
c12 = (p1 + p2) / 2
center = (pos1 + pos2) / 2
self.bring_to_front(self.ruler)
self.play(
self.ruler.animate.shift(center - c12),
run_time=run_time / 2,
rate_func=rate_func,
)
self.play(
Rotating(
self.ruler,
radians=np.angle(R3_to_complex(pos2 - pos1))
- np.angle(R3_to_complex(p2 - p1)),
about_point=center,
),
run_time=run_time / 2,
rate_func=rate_func,
)
def draw_line(
self, pos1, pos2, is_prepared=True, run_time=1.2, rate_func=smooth, pre_time=0.8
):
if not is_prepared:
self.set_ruler(pos1, pos2, run_time=pre_time)
self.dot.move_to(pos1)
self.emphasize_dot(pos1, run_time=0.15)
self.add(self.dot)
l = Line(pos1, pos2, **self.line_config)
self.play(
Create(l),
self.dot.animate.move_to(pos2),
run_time=run_time - 0.3,
rate_func=rate_func,
)
self.emphasize_dot(pos2, run_time=0.15)
self.remove(self.dot)
return l
def draw_line_(self, l, is_prepared=True, run_time=1.2, rate_func=smooth):
pos1, pos2 = l.get_start(), l.get_end()
if not is_prepared:
self.set_ruler(pos1, pos2, run_time=0.5)
self.dot.move_to(pos1)
self.emphasize_dot(pos1, run_time=0.15)
self.add(self.dot)
# l = Line(pos1, pos2, **self.line_config)
self.play(
Create(l),
self.dot.animate.move_to(pos2),
run_time=run_time - 0.3,
rate_func=rate_func,
)
self.emphasize_dot(pos2, run_time=0.15)
self.remove(self.dot)
return l
def put_aside_ruler(self, direction=DOWN, run_time=0.5):
self.bring_to_front(self.ruler)
self.play(self.ruler.animate.move_to(direction * 15), run_time=run_time)
def put_aside_compass(self, direction=DOWN, run_time=0.5):
self.bring_to_front(self.cp)
self.play(self.cp.animate.move_to(direction * 15), run_time=run_time)
def get_length_label(
self, p1, p2, text="", reverse_label=False, add_bg=False, bg_color=WHITE
):
l = Line(p1, p2)
b = Brace(
l,
direction=complex_to_R3(
np.exp(1j * (l.get_angle() + PI / 2 * (1 - 2 * float(reverse_label))))
),
**self.brace_config
)
t = b.get_text(text)
# t = Text(text, **self.text_config).scale(0.2)
if add_bg:
bg = SurroundingRectangle(
t, fill_color=bg_color, fill_opacity=0.6, stroke_opacity=0
).stretch_to_fit_height(t.get_height() + 0.05)
b.put_at_tip(bg, buff=0.0)
b.put_at_tip(t, buff=0.05)
return b, bg, t
else:
b.put_at_tip(t, buff=0.05)
return b, t
def set_compass_and_show_span(
self,
p1,
p2,
run_time=1,
show_span_time=[0.4, 0.3, 0.9, 0.4],
text="",
reverse_label=False,
add_bg=False,
**kwargs
):
self.set_compass(p1, p2, run_time=run_time, **kwargs)
bt = self.get_length_label(
p1, p2, text=text, reverse_label=reverse_label, add_bg=add_bg
)
b, t = bt[0], bt[-1]
st = show_span_time
self.play(Create(b), run_time=st[0])
if add_bg:
self.add(bt[1])
self.play(FadeIn(t), run_time=st[1])
else:
self.play(FadeIn(t), run_time=st[1])
# self.wait(st[2])
self.play(Wait(st[2]))
self.play(FadeOut(VGroup(*bt)), run_time=st[3])
return bt
def set_compass_and_show_span_(
self,
p1,
p2,
run_time=1,
show_span_time=[0.4, 0.3, 0.9, 0.4],
text="",
reverse_label=False,
add_bg=True,
**kwargs
):
self.set_compass_(p1, p2, run_time=run_time, **kwargs)
bt = self.get_length_label(p1, p2, text=text, reverse_label=reverse_label)
b, t = bt[0], bt[-1]
st = show_span_time
self.play(Create(b), run_time=st[0])
if add_bg:
self.add(bt[1])
self.play(FadeIn(t), run_time=st[1])
else:
self.play(FadeIn(t), run_time=st[1])
# self.wait(st[2])
self.play(Wait(st[2]))
self.play(FadeOut(VGroup(*bt)), run_time=st[3])
return bt
def highlight_on(self, *mobjects, to_front=True, run_time=1, **kwargs):
self.highlight = VGroup(*mobjects)
self.play(
self.highlight.animate.set_stroke(color="#66CCFF", width=4),
run_time=run_time,
**kwargs
)
if to_front:
self.bring_to_front(self.highlight)
self.bring_to_front(self.cp, self.ruler)
def highlight_off(self, *mobjects):
pass
def show_arc_info(self, arc, time_list=[0.5, 0.2, 0.3]):
c, r, s, a, ps, pe = (
arc.arc_center,
arc.radius,
arc.start_angle,
arc.angle,
arc.get_start(),
arc.get_end(),
)
d_center = Dot(c, radius=0.08, color=PINK)
r1, r2 = DashedLine(c, ps, stroke_width=3.5, stroke_color=PINK), DashedLine(
c, pe, stroke_width=3.5, stroke_color=PINK
)
arc_new = Arc(
arc_center=c,
radius=r,
start_angle=s,
angle=a,
stroke_width=8,
stroke_color=RED,
)
self.play(Create(arc_new), run_time=time_list[0])
self.play(FadeIn(arc_new), run_time=time_list[1])
self.play(Create(r1), Create(r2), run_time=time_list[2])
class CompassAndRulerScene(DrawingScene):
# just rename `DrawingScene`
pass
Hi @hcq11419, my apologies for the delayed reply, I was very busy last week.
Does your issue occur when you clear the cache? So cleanup file insides the media/ folder created by Manim (and also slides/ folder, to be sure).
Unfortunately, I am still missing some files to run your code (please provide a Git repository if it contains multiple files, and reduce their size to the minimum if possible), and I could not reproduce your bug locally by generating many slides.
Here I created 1000 slides, with no issue:
import random
from math import pi
from manim import *
from manim_slides import Slide
class Main(Slide):
def construct(self):
dot = Dot().move_to([0.0, 1.0, 0.0])
start_angle = pi
for _ in range(1000):
self.next_slide()
angle = random.uniform(-2*pi, +2*pi)
arc = Arc(start_angle=start_angle, angle=angle)
start_angle = (start_angle + angle) % (2 * pi)
self.play(MoveAlongPath(dot, arc))
FYI, I am using manim-slides[manim]>=5.5.1.
Hi @jeertmans I've reorganized the code. Please test it again. I've tested it on both Mac and Windows, and the errors reported are the same.
from manim import *
from manim_slides import Slide
# utils
get_angle_ = lambda c: (
np.angle(-c) + PI if not c / abs(c) == 1 else 0
)
convert_angle = lambda a: a if a >= 0 else a + TAU
class Compass(VGroup):
def __init__(
self,
stroke_color=RED, # 圆规颜色
fill_color=WHITE,
stroke_width=2,
leg_length=3,
leg_width=0.12,
r=0.2,
depth_test=True,
span=2.5, # 针脚距离,半径
**kwargs
):
super().__init__()
self.span = span
self.leg_length = leg_length
self.stroke_color = stroke_color
self.fill_color = fill_color
self.stroke_width = stroke_width
self.leg_width = leg_width
self.r = r
self.depth_test = depth_test
self.create_compass()
def create_compass(self):
s, l, r, w = self.span, self.leg_length, self.r, self.leg_width
self.theta = np.arcsin(s / 2 / l) # theta 为圆规腿与针脚连线的夹角
self.c = Circle(
radius=r,
fill_color=self.fill_color,
fill_opacity=1,
stroke_color=self.stroke_color,
stroke_width=self.stroke_width * 5,
)
c2 = Circle(
radius=r + self.stroke_width * 5 / 100 / 2,
fill_opacity=0,
stroke_color=self.fill_color,
stroke_width=self.stroke_width,
)
# leg_1是针尖腿,leg_2是笔尖腿
self.leg_1 = Polygon(
ORIGIN,
l * RIGHT,
(l - w * np.sqrt(3)) * RIGHT + w * DOWN,
w * DOWN,
stroke_width=0,
stroke_color=self.fill_color,
fill_color=self.stroke_color,
fill_opacity=1,
).rotate(-PI / 2 - self.theta, about_point=self.c.get_center())
self.leg_2 = Polygon(
ORIGIN,
l * RIGHT,
(l - w * np.sqrt(3)) * RIGHT + w * UP,
w * UP,
stroke_width=0,
stroke_color=self.fill_color,
fill_color=self.stroke_color,
fill_opacity=1,
).rotate(-PI / 2 + self.theta, about_point=self.c.get_center())
# self.leg_1, self.leg_2 = VGroup(leg_01, leg_11), VGroup(leg_02, leg_12, pen_point)
h = Line(
UP * r,
UP * (r + r * 1.8),
stroke_color=self.stroke_color,
stroke_width=self.stroke_width * 6,
)
self.head = VGroup(h, self.c, c2)
self.add(self.leg_1, self.leg_2, self.head)
self.move_to(ORIGIN)
return self
def get_niddle_tip(self): # 获取针脚
return self.leg_1.get_vertices()[1]
def get_pen_tip(self): # 获取笔尖
return self.leg_2.get_vertices()[1]
def move_niddle_tip_to(self, pos):
self.shift(pos - self.get_niddle_tip())
return self
def rotate_about_niddle_tip(self, angle=PI / 2):
p = Dot(self.get_niddle_tip()) # manim bug?
self.rotate(angle=angle, about_point=p.get_center())
def get_span(self):
# return self.span 如果进行了缩放而self.span没变会有问题
return get_norm(self.get_pen_tip() - self.get_niddle_tip())
def set_span(self, s):
self.span = s
l, r, w = self.leg_length, self.r, self.leg_width
theta_new, theta_old = np.arcsin(s / 2 / l), self.theta
sign = np.sign(
get_angle_(
R3_to_complex(
self.leg_2.get_vertices()[1] - self.leg_2.get_vertices()[0]
)
)
- get_angle_(
R3_to_complex(
self.leg_1.get_vertices()[1] - self.leg_1.get_vertices()[0]
)
)
)
# sign=np.sign(get_angle(self.leg_2.get_vertices()[0],self.leg_2.get_vertices()[1])-get_angle(
# self.leg_1.get_vertices()[0],self.leg_1.get_vertices()[1])) #调用my_fun
rotate_angle = 2 * (theta_new - theta_old) * sign
self.leg_2.rotate(rotate_angle, about_point=self.c.get_center())
self.theta = theta_new
self.head.rotate(rotate_angle / 2, about_point=self.c.get_center())
self.rotate_about_niddle_tip(-rotate_angle / 2)
return self
def set_compass(self, center, pen_tip):
self.move_niddle_tip_to(center)
self.set_span(get_norm(pen_tip - center))
self.rotate_about_niddle_tip(
np.angle(R3_to_complex(pen_tip - center))
- np.angle(R3_to_complex(self.get_pen_tip() - center))
)
return self
def set_compass_to_draw_arc(self, arc):
return self.set_compass(arc.arc_center, arc.get_start())
def reverse_tip(self):
return self.flip(
axis=self.head[0].get_end() - self.head[0].get_start(),
about_point=self.c.get_center(),
)
class DrawingScene(Scene):
def __init__(
self,
compass_config={
"stroke_color": RED, # 圆规颜色
"fill_color": WHITE,
"stroke_width": 2,
"leg_length": 3,
"leg_width": 0.12,
"r": 0.2,
"depth_test": True,
}, # to define size and style of the compass
ruler_config={
"width": 10,
"height": 0.8,
"stroke_width": 8,
"stroke_color": GREY_E,
"stroke_opacity": 0.4,
"fill_color": WHITE,
"fill_opacity": 0.5,
}, # to define size and style of the ruler
dot_config={
"radius": 0.06,
"color": GREY_E,
}, # to define size and color of the dot (e.g., dot in arc/circle's center)
line_config={
"stroke_color": YELLOW_E,
"stroke_width": 2.5,
}, # the default line style drawing by ruler (not the defualt arc style drawing by compass)
brace_config={
"fill_color": YELLOW_E,
"buff": 0.025,
},
text_config={
"font_size": 20, # 5 times than the actual size here and
# will be sacle down to the actual size later
# in 'get_length_label' methods.
# 'font': 'Cambria Math',
"color": YELLOW_E,
},
add_ruler=True,
):
super().__init__()
self.compass_config = compass_config
self.ruler_config = ruler_config
self.dot_config = dot_config
self.line_config = line_config
self.brace_config = brace_config
self.text_config = text_config
self.add_ruler = add_ruler
def setup(self):
self.cp = Compass(**self.compass_config)
self.ruler = VGroup(
Rectangle(**self.ruler_config)
.stretch_to_fit_height(
self.ruler_config["height"]
- self.ruler_config["stroke_width"] / 2 / 100
)
.round_corners(self.ruler_config["height"] / 8),
Rectangle(**self.ruler_config).set_opacity(0),
)
self.dot = Dot(**self.dot_config)
self.cp.move_to(UP * 10)
if self.add_ruler:
self.ruler.move_to(DOWN * 10)
self.add(self.ruler)
self.add(self.cp)
self.temp_points = []
def construct(self):
self.add(self.cp)
self.play(self.cp.animate.move_niddle_tip_to(ORIGIN), run_time=1)
# self.wait(0.3)
self.play(Wait(0.3))
self.set_span(3.6, run_time=1, rate_func=smooth)
# self.wait(0.5)
self.play(Wait(0.5))
self.set_compass(DL * 0.5, UR * 0.5, run_time=1, rate_func=there_and_back)
arc = Arc(color=GREY_E)
self.set_compass_to_draw_arc(arc)
self.draw_arc_by_compass(arc)
# self.wait()
self.play(Wait(1))
def set_span(self, s, run_time=1, rate_func=smooth):
"""设置针尖"""
s_old = self.cp.get_span()
n = int(run_time * self.camera.frame_rate)
dt = 1 / self.camera.frame_rate
t_series = np.linspace(1, n, n) / n
# s_series = s_old + rate_func(t_series) * (s - s_old)
s_series = [s_old + rate_func(t_series[i]) * (s - s_old) for i in range(n)]
for i in range(n):
self.cp.set_span(s_series[i])
self.play(Wait(dt))
# self.wait(dt)
def set_compass_direction(self, start, end, run_time=1, rate_func=smooth):
vect = end - start
a = np.angle(R3_to_complex(vect))
c_old, p_old = self.cp.get_niddle_tip(), self.cp.get_pen_tip()
a_old = np.angle(R3_to_complex(p_old - c_old))
n = int(run_time * self.camera.frame_rate)
dt = 1 / self.camera.frame_rate
t_series = np.linspace(1, n, n) / n
c_series = [c_old + rate_func(t_series[i]) * (start - c_old) for i in range(n)]
delta_a = (a - a_old) / n
for i in range(n):
self.bring_to_front(self.cp)
self.cp.move_niddle_tip_to(c_series[i])
self.cp.rotate_about_niddle_tip(delta_a)
# self.wait(dt)
self.play(Wait(dt))
def set_compass(
self, center, pen_tip, run_time=1, rate_func=smooth, emphasize_dot=False
):
if emphasize_dot:
run_time -= 0.15
c_old, p_old = self.cp.get_niddle_tip(), self.cp.get_pen_tip()
n = int(run_time * self.camera.frame_rate)
dt = 1 / self.camera.frame_rate
t_series = np.linspace(1, n, n) / n
# s_series = s_old + rate_func(t_series) * (s - s_old)
c_series = [c_old + rate_func(t_series[i]) * (center - c_old) for i in range(n)]
p_series = [
p_old + rate_func(t_series[i]) * (pen_tip - p_old) for i in range(n)
]
for i in range(n):
self.bring_to_front(self.cp)
self.cp.set_compass(c_series[i], p_series[i])
# self.wait(dt)
self.play(Wait(dt))
if emphasize_dot:
self.emphasize_dot([center, pen_tip], run_time=0.15)
def set_compass_(
self,
center,
pen_tip,
adjust_angle=0,
run_time=1,
rate_func=smooth,
emphasize_dot=False,
):
vect = center - pen_tip
a = np.angle(R3_to_complex(vect)) + adjust_angle
s = get_norm(vect)
c_old, p_old, s_old = (
self.cp.get_niddle_tip(),
self.cp.get_pen_tip(),
self.cp.get_span(),
)
a_old = np.angle(R3_to_complex(p_old - c_old))
if emphasize_dot:
run_time -= 0.15
n = int(run_time * self.camera.frame_rate)
dt = 1 / self.camera.frame_rate
t_series = np.linspace(1, n, n) / n
c_series = [c_old + rate_func(t_series[i]) * (center - c_old) for i in range(n)]
delta_a = (a - a_old) / n
s_series = [s_old + rate_func(t_series[i]) * (s - s_old) for i in range(n)]
for i in range(n):
self.bring_to_front(self.cp)
self.cp.move_niddle_tip_to(c_series[i])
self.cp.rotate_about_niddle_tip(delta_a)
self.cp.set_span(s_series[i])
# self.wait(dt)
self.play(Wait(dt))
if emphasize_dot:
self.emphasize_dot([center, pen_tip], run_time=0.15)
def set_compass_to_draw_arc(self, arc, **kwargs):
self.set_compass(arc.arc_center, arc.get_start(), **kwargs)
def set_compass_to_draw_arc_(self, arc, **kwargs):
self.set_compass_(arc.arc_center, arc.get_start(), **kwargs)
def draw_arc_by_compass(
self,
arc,
is_prepared=True,
run_time=1,
rate_func=smooth,
reverse=False,
add_center=False,
**kwargs
):
self.bring_to_front(self.cp)
if not is_prepared:
self.set_compass_to_draw_arc(arc, run_time=0.5)
theta = arc.angle if not reverse else -1 * arc.angle
self.play(
Rotating(self.cp, radians=theta, about_point=self.cp.get_niddle_tip()),
Create(arc),
rate_func=rate_func,
run_time=run_time,
)
if add_center:
d = Dot(self.cp.get_niddle_tip(), **self.dot_config).scale(0.5)
self.temp_points.append(d)
self.add(d)
def emphasize_dot(self, pos, add_dot=False, size=1.2, run_time=0.2, **kwargs):
if type(pos) == list:
d = VGroup(
*[
Dot(
pos[i], radius=size / 2, color=YELLOW_C, fill_opacity=0.25
).scale(0.25)
for i in range(len(pos))
]
)
else:
d = Dot(pos, radius=size / 2, color=YELLOW_C, fill_opacity=0.25).scale(0.25)
self.add(d)
if type(pos) == list:
self.play(
d[0].animate.scale(4),
d[1].animate.scale(4),
rate_func=linear,
run_time=run_time,
)
else:
self.play(d.animate.scale(4), rate_func=linear, run_time=run_time)
self.remove(d)
if add_dot:
if type(pos) == list:
dot = VGroup(*[Dot(pos[i], **kwargs) for i in range(len(pos))])
else:
dot = Dot(pos, **kwargs)
self.add(dot)
return dot
def set_ruler(self, pos1, pos2, run_time=1, rate_func=smooth):
p1, p2 = self.ruler[-1].get_vertices()[1], self.ruler[-1].get_vertices()[0]
c12 = (p1 + p2) / 2
center = (pos1 + pos2) / 2
self.bring_to_front(self.ruler)
self.play(
self.ruler.animate.shift(center - c12),
run_time=run_time / 2,
rate_func=rate_func,
)
self.play(
Rotating(
self.ruler,
radians=np.angle(R3_to_complex(pos2 - pos1))
- np.angle(R3_to_complex(p2 - p1)),
about_point=center,
),
run_time=run_time / 2,
rate_func=rate_func,
)
def draw_line(
self, pos1, pos2, is_prepared=True, run_time=1.2, rate_func=smooth, pre_time=0.8
):
if not is_prepared:
self.set_ruler(pos1, pos2, run_time=pre_time)
self.dot.move_to(pos1)
self.emphasize_dot(pos1, run_time=0.15)
self.add(self.dot)
l = Line(pos1, pos2, **self.line_config)
self.play(
Create(l),
self.dot.animate.move_to(pos2),
run_time=run_time - 0.3,
rate_func=rate_func,
)
self.emphasize_dot(pos2, run_time=0.15)
self.remove(self.dot)
return l
def draw_line_(self, l, is_prepared=True, run_time=1.2, rate_func=smooth):
pos1, pos2 = l.get_start(), l.get_end()
if not is_prepared:
self.set_ruler(pos1, pos2, run_time=0.5)
self.dot.move_to(pos1)
self.emphasize_dot(pos1, run_time=0.15)
self.add(self.dot)
# l = Line(pos1, pos2, **self.line_config)
self.play(
Create(l),
self.dot.animate.move_to(pos2),
run_time=run_time - 0.3,
rate_func=rate_func,
)
self.emphasize_dot(pos2, run_time=0.15)
self.remove(self.dot)
return l
def put_aside_ruler(self, direction=DOWN, run_time=0.5):
self.bring_to_front(self.ruler)
self.play(self.ruler.animate.move_to(direction * 15), run_time=run_time)
def put_aside_compass(self, direction=DOWN, run_time=0.5):
self.bring_to_front(self.cp)
self.play(self.cp.animate.move_to(direction * 15), run_time=run_time)
def get_length_label(
self, p1, p2, text="", reverse_label=False, add_bg=False, bg_color=WHITE
):
l = Line(p1, p2)
b = Brace(
l,
direction=complex_to_R3(
np.exp(1j * (l.get_angle() + PI / 2 * (1 - 2 * float(reverse_label))))
),
**self.brace_config
)
t = b.get_text(text)
# t = Text(text, **self.text_config).scale(0.2)
if add_bg:
bg = SurroundingRectangle(
t, fill_color=bg_color, fill_opacity=0.6, stroke_opacity=0
).stretch_to_fit_height(t.get_height() + 0.05)
b.put_at_tip(bg, buff=0.0)
b.put_at_tip(t, buff=0.05)
return b, bg, t
else:
b.put_at_tip(t, buff=0.05)
return b, t
def set_compass_and_show_span(
self,
p1,
p2,
run_time=1,
show_span_time=[0.4, 0.3, 0.9, 0.4],
text="",
reverse_label=False,
add_bg=False,
**kwargs
):
self.set_compass(p1, p2, run_time=run_time, **kwargs)
bt = self.get_length_label(
p1, p2, text=text, reverse_label=reverse_label, add_bg=add_bg
)
b, t = bt[0], bt[-1]
st = show_span_time
self.play(Create(b), run_time=st[0])
if add_bg:
self.add(bt[1])
self.play(FadeIn(t), run_time=st[1])
else:
self.play(FadeIn(t), run_time=st[1])
# self.wait(st[2])
self.play(Wait(st[2]))
self.play(FadeOut(VGroup(*bt)), run_time=st[3])
return bt
def set_compass_and_show_span_(
self,
p1,
p2,
run_time=1,
show_span_time=[0.4, 0.3, 0.9, 0.4],
text="",
reverse_label=False,
add_bg=True,
**kwargs
):
self.set_compass_(p1, p2, run_time=run_time, **kwargs)
bt = self.get_length_label(p1, p2, text=text, reverse_label=reverse_label)
b, t = bt[0], bt[-1]
st = show_span_time
self.play(Create(b), run_time=st[0])
if add_bg:
self.add(bt[1])
self.play(FadeIn(t), run_time=st[1])
else:
self.play(FadeIn(t), run_time=st[1])
# self.wait(st[2])
self.play(Wait(st[2]))
self.play(FadeOut(VGroup(*bt)), run_time=st[3])
return bt
def highlight_on(self, *mobjects, to_front=True, run_time=1, **kwargs):
self.highlight = VGroup(*mobjects)
self.play(
self.highlight.animate.set_stroke(color="#66CCFF", width=4),
run_time=run_time,
**kwargs
)
if to_front:
self.bring_to_front(self.highlight)
self.bring_to_front(self.cp, self.ruler)
def highlight_off(self, *mobjects):
pass
def show_arc_info(self, arc, time_list=[0.5, 0.2, 0.3]):
c, r, s, a, ps, pe = (
arc.arc_center,
arc.radius,
arc.start_angle,
arc.angle,
arc.get_start(),
arc.get_end(),
)
d_center = Dot(c, radius=0.08, color=PINK)
r1, r2 = DashedLine(c, ps, stroke_width=3.5, stroke_color=PINK), DashedLine(
c, pe, stroke_width=3.5, stroke_color=PINK
)
arc_new = Arc(
arc_center=c,
radius=r,
start_angle=s,
angle=a,
stroke_width=8,
stroke_color=RED,
)
self.play(Create(arc_new), run_time=time_list[0])
self.play(FadeIn(arc_new), run_time=time_list[1])
self.play(Create(r1), Create(r2), run_time=time_list[2])
class CompassAndRulerScene(DrawingScene):
# just rename `DrawingScene`
pass
# Scene
class Golden_section_point(CompassAndRulerScene, Slide):
Dot_config = {
"radius": 0.04,
"color": WHITE,
}
CONFIG = {
"compass_config": {
"leg_length": 3,
"stroke_color": RED, # 圆规腿颜色
"fill_color": WHITE, # 圆盘颜色
},
"arc_config": {
"stroke_color": YELLOW_B,
"stroke_width": 2.5,
},
"add_ruler": True, # add ruler when the scene starts
}
def setup(self):
# 先调用父类的setup方法来初始化ruler等属性
super().setup()
# 然后设置TeX模板
# MathTex.set_default(tex_template=TexTemplateLibrary.ctex)
def construct(self):
rate_ease_out_back = {"rate_func": rate_functions.ease_out_back}
d1, d2 = LEFT * 2, RIGHT * 2
dA = Dot(LEFT * 2, **self.Dot_config)
dB = Dot(RIGHT * 2, **self.Dot_config)
lAB = Line(dA.get_center(), dB.get_center())
lb_dA, lb_dB = Tex("A").next_to(dA, DOWN, buff=0.1), Tex("B").next_to(
dB, DOWN, buff=0.1
)
self.add(dA, dB, lAB, lb_dA, lb_dB)
self.bring_to_back(lAB)
# 延长AB
l1 = DashedLine(
dB.get_center(), RIGHT * 5, dash_length=0.1, **self.CONFIG["arc_config"]
)
self.set_ruler(dB.get_center(), RIGHT * 5)
self.wait(0.1)
self.draw_line_(l1, run_time=0.8)
self.wait(0.1)
self.put_aside_ruler(DOWN * 0.7)
p1, p2 = RIGHT * 0.5, RIGHT * 3.5
self.set_compass(dB.get_center(), RIGHT * 2.5)
self.wait(0.1)
self.set_compass(dB.get_center(), p2)
self.wait(0.1)
arc_r = Arc(
arc_center=dB.get_center(),
start_angle=-15 * DEGREES,
angle=30 * DEGREES,
radius=1.5,
**self.CONFIG["arc_config"]
)
arc_l = Arc(
arc_center=dB.get_center(),
start_angle=165 * DEGREES,
angle=30 * DEGREES,
radius=1.5,
**self.CONFIG["arc_config"]
)
self.set_compass_to_draw_arc_(arc_r, adjust_angle=-PI, run_time=0.6)
self.wait(0.1)
self.draw_arc_by_compass(arc_r, run_time=0.5)
self.wait(0.1)
self.set_compass_to_draw_arc_(arc_l, adjust_angle=PI, run_time=0.6)
self.wait(0.1)
self.draw_arc_by_compass(arc_l, run_time=0.5)
self.next_slide()
p3 = dB.get_center() + UP * 1.5 * np.sqrt(3)
p4 = dB.get_center() + DOWN * 1.5 * np.sqrt(3)
self.set_compass(p1, RIGHT * 1.5)
self.wait(0.2)
self.set_compass(p1, p2)
self.wait(0.2)
arc_ru = Arc(
arc_center=p1,
start_angle=45 * DEGREES,
angle=30 * DEGREES,
radius=3,
**self.CONFIG["arc_config"]
)
arc_lu = Arc(
arc_center=p2,
start_angle=105 * DEGREES,
angle=30 * DEGREES,
radius=3,
**self.CONFIG["arc_config"]
)
self.set_compass_to_draw_arc_(arc_ru, adjust_angle=PI, run_time=0.4)
self.wait(0.1)
self.draw_arc_by_compass(arc_ru, run_time=0.5)
self.wait(0.1)
self.set_compass_to_draw_arc_(arc_lu, adjust_angle=PI, run_time=0.4)
self.wait(0.2)
self.draw_arc_by_compass(arc_lu, run_time=0.5)
self.wait(0.1)
self.put_aside_compass(DR * 0.6)
self.wait(0.2)
# 画垂线
l2 = Line(p3 + UP, d2)
self.set_ruler(p3, p4)
self.wait(0.1)
self.emphasize_dot([p3, dB.get_center()], run_time=0.25)
self.wait(0.1)
self.draw_line_(l2)
self.wait(0.1)
self.put_aside_ruler(DOWN * 0.7)
self.wait(0.1)
self.highlight_on(l2)
angle_90 = Square(0.22, stroke_width=2, stroke_color=GREY_C).next_to(
dB.get_center(), UL, buff=0
)
self.bring_to_back(angle_90)
self.play(FadeIn(angle_90), run_time=0.64)
self.next_slide()
# 找AB中点
p5 = lAB.get_center() + UP * np.sqrt(5)
p6 = lAB.get_center() + DOWN * np.sqrt(5)
self.set_compass(dA.get_center(), dA.get_center() + RIGHT, run_time=0.75)
self.wait(0.1)
arc_r2 = Arc(
arc_center=dA.get_center(),
start_angle=-70 * DEGREES,
angle=140 * DEGREES,
radius=3,
**self.CONFIG["arc_config"]
)
arc_l2 = Arc(
arc_center=dB.get_center(),
start_angle=110 * DEGREES,
angle=140 * DEGREES,
radius=3,
**self.CONFIG["arc_config"]
)
self.set_compass_to_draw_arc_(arc_r2, adjust_angle=-PI, run_time=0.4)
self.wait(0.1)
self.draw_arc_by_compass(arc_r2)
self.wait(0.1)
self.set_compass_to_draw_arc_(arc_l2, adjust_angle=PI, run_time=0.4)
self.wait(0.1)
self.draw_arc_by_compass(arc_l2)
self.wait(0.1)
self.put_aside_compass(DOWN * 0.8)
self.wait(0.1)
self.emphasize_dot([p5, p6], run_time=0.25)
self.set_ruler(p5, p6)
self.wait(0.1)
l3 = DashedLine(p5, p6, dash_length=0.1, **self.CONFIG["arc_config"]).scale(
1.25
)
self.draw_line_(l3)
self.wait(0.2)
self.put_aside_ruler(DOWN * 0.8)
center = Dot(l3.get_center(), **self.Dot_config)
self.play(FadeIn(center))
self.wait(1)
arc_l3 = Arc(
arc_center=dB.get_center(),
start_angle=-180 * DEGREES,
angle=-90 * DEGREES,
radius=2,
**self.CONFIG["arc_config"]
)
p7 = dB.get_center() + UP * 2
self.set_compass(dB.get_center(), dB.get_center() + LEFT * 2, run_time=0.5)
self.wait(0.1)
self.set_compass_to_draw_arc_(arc_l3, adjust_angle=PI, run_time=0.4)
self.wait(0.1)
self.draw_arc_by_compass(arc_l3)
self.wait(0.1)
self.put_aside_compass(DOWN * 0.8)
dC = Dot(p7, **self.Dot_config)
lb_dC = Tex("C").next_to(dC, RIGHT, buff=0.1)
self.play(
LaggedStart(
FadeIn(dC),
FadeIn(lb_dC, shift=RIGHT * 0.5, scale=0.1, **rate_ease_out_back),
lag_ratio=0.6,
)
)
self.next_slide()
line_AC = Line(d1, p7, **self.CONFIG["arc_config"])
self.emphasize_dot([d1, p7], run_time=0.25)
self.set_ruler(d1, p7)
self.wait(0.1)
self.draw_line_(line_AC)
self.wait(0.2)
self.put_aside_ruler(DOWN * 0.8)
self.next_slide()
l4 = Line(d2, p7, **self.CONFIG["arc_config"])
self.add(l4)
out = Group(*self.mobjects).remove(
dA, dB, dC, lAB, line_AC, lb_dA, lb_dB, lb_dC, l4, dC, angle_90
)
self.play(FadeOut(out))
# out=VGroup(l1,arc_l,arc_l2,arc_l3,arc_lu,arc_r,arc_r2,arc_ru)
self.next_slide()
arc_4 = Arc(
arc_center=p7,
start_angle=-90 * DEGREES,
angle=-np.arctan(2) - 10 * DEGREES,
radius=2,
stroke_color=YELLOW_B,
stroke_width=2.5,
)
arc_5 = Arc(
arc_center=d1,
start_angle=np.arctan(1 / 2),
angle=-np.arctan(1 / 2) - 10 * DEGREES,
radius=2 * np.sqrt(5) - 2,
stroke_color=YELLOW_B,
stroke_width=2.5,
)
dD = Dot(line_AC.point_from_proportion(1 - np.sqrt(5) / 5), **self.Dot_config)
lb_dD = Tex("D").next_to(dD, UL, buff=0.1)
dE = Dot(lAB.point_from_proportion((np.sqrt(5) - 1) / 2), **self.Dot_config)
lb_dE = Tex("E").next_to(dE, DR, buff=0.1)
super().setup() # 圆规初始化
self.set_compass(p7, d2 + UP)
self.set_compass_to_draw_arc_(arc_4, adjust_angle=-PI, run_time=0.4)
self.wait(0.1)
self.draw_arc_by_compass(arc_4)
self.wait(0.1)
self.put_aside_compass(DOWN * 0.8)
self.wait(0.1)
self.emphasize_dot(dD.get_center())
self.wait(0.1)
self.play(
LaggedStart(
FadeIn(dD),
FadeIn(lb_dD, shift=UL * 0.5, scale=0.1, **rate_ease_out_back),
lag_ratio=0.6,
run_time=1,
)
)
self.next_slide()
self.set_compass(d1, line_AC.point_from_proportion(0.3))
self.wait(0.1)
self.set_compass_to_draw_arc_(arc_5, adjust_angle=PI, run_time=0.5)
self.wait(0.1)
self.draw_arc_by_compass(arc_5, run_time=0.5)
self.next_slide()
self.put_aside_compass(DOWN)
self.next_slide()
self.emphasize_dot(dE.get_center())
self.play(
LaggedStart(
FadeIn(dE),
FadeIn(lb_dE, shift=DOWN * 0.5, scale=0.1, **rate_ease_out_back),
lag_ratio=0.6,
run_time=1,
)
)
self.highlight_on([dE, lb_dE])
self.wait()
if __name__ == "__main__":
from os import system
system("manim-slides render Golden_section_point {} ".format(__file__))
@jeertmans I also tested the code you provided, and I was able to run it normally. However, when I switched to my specific example, this error occurred. I've tried many different scenarios, including changing platforms, switching between ManimCE and ManimGL, using different computers, and trying different code, etc. I'm really frustrated! The error occurs in the last step when using manim - slides to process the previously generated video (there is no problem with Manim rendering). The following is the error I encountered when running the above code with manimgl 1.7.2 on my Mac.
ManimGL v1.7.2
Traceback (most recent call last):
File "<frozen runpy>", line 198, in _run_module_as_main
File "<frozen runpy>", line 88, in _run_code
File "/opt/miniconda3/envs/manim-gl-172/lib/python3.11/site-packages/manimlib/__main__.py", line 65, in <module>
main()
File "/opt/miniconda3/envs/manim-gl-172/lib/python3.11/site-packages/manimlib/__main__.py", line 61, in main
run_scenes()
File "/opt/miniconda3/envs/manim-gl-172/lib/python3.11/site-packages/manimlib/__main__.py", line 39, in run_scenes
scene.run()
File "/opt/miniconda3/envs/manim-gl-172/lib/python3.11/site-packages/manim_slides/slide/manimlib.py", line 62, in run
self._save_slides(
File "/opt/miniconda3/envs/manim-gl-172/lib/python3.11/site-packages/manim_slides/slide/base.py", line 576, in _save_slides
concatenate_video_files(slide_files, dst_file)
File "/opt/miniconda3/envs/manim-gl-172/lib/python3.11/site-packages/manim_slides/utils.py", line 67, in concatenate_video_files
output_container.mux(packet)
File "av/container/output.pyx", line 257, in av.container.output.OutputContainer.mux
File "av/container/output.pyx", line 278, in av.container.output.OutputContainer.mux_one
File "av/container/core.pyx", line 286, in av.container.core.Container.err_check
File "av/error.pyx", line 326, in av.error.err_check
av.error.ValueError: [Errno 22] Invalid argument: 'slides/files/Golden_section_point/3a409fe872180224acd65b5617c99400fc0dbc63901ec6789908eb6fd0733520.mp4'
Hi @hcq11419, so I could reproduce your bug, by adding get_norm = np.linalg.norm at the top of the file.
I spent a few hours on the problem, and I'm sorry but can't figure out what is the root cause of it. When I try to reproduce the bug with a smaller code, similar to https://github.com/jeertmans/manim-slides/issues/540#issuecomment-2792016477, but with more animations per slide, I can't reproduce the bug. I hacked into the Manim Slides package to replace concatenate_files (see code below) with Manim's combine_files, and it works... but not always!
I try to clear the cache each time, to avoid caching effects, and most of the time the bug occurs as expected, but sometimes it doesn't and everything just works fine.
https://github.com/jeertmans/manim-slides/blob/a5412a8df2f4e86e949066db3a707cdafeb97007/manim_slides/utils.py#L16-L69
I know my implementation differs from the one written in Manim, but when I try to match both implementation, mine fails and the one from Manim works.
If you could provide a more concise non-working example, it would be great. Especially an example when we can observe the transition from it works to it doesn't work, e.g., with a variable number of animations.
Thank you very, very much for your attention. I'm very sorry for taking up so much of your time. Actually, I've also encountered this problem when rendering other relatively complex code. Currently, the method I'm using is to skip 'manim - slides render', customize the generation of the slides file, and then use 'manim - slides convert'. I'm providing this to you for reference. First, generate the time_data.json file.
from manim import *
import time
from typing import TYPE_CHECKING,Callable
from manim.mobject.mobject import _AnimationBuilder
import threading
Scene.pause_time=0 #1
Scene.time_list=[Scene.pause_time]
Scene.mark_time_list=[]#1
Scene.time_mark_notes={}#1
def play(
self,
*args: Animation | Mobject | _AnimationBuilder,
subcaption=None,
subcaption_duration=None,
subcaption_offset=0,
**kwargs,
):
if (
self.interactive_mode
and config.renderer == RendererType.OPENGL
and threading.current_thread().name != "MainThread"
):
kwargs.update(
{
"subcaption": subcaption,
"subcaption_duration": subcaption_duration,
"subcaption_offset": subcaption_offset,
}
)
self.queue.put(
(
"play",
args,
kwargs,
)
)
return
start_time = self.time
self.renderer.play(self, *args, **kwargs)
run_time = self.time - start_time
self.pause_time+=run_time#1
if subcaption:
if subcaption_duration is None:
subcaption_duration = run_time
self.add_subcaption(
content=subcaption,
duration=subcaption_duration,
offset=-run_time + subcaption_offset,
)
def wait(
self,
duration: float = DEFAULT_WAIT_TIME,
stop_condition: Callable[[], bool] | None = None,
frozen_frame: bool | None = None,
time_mark_notes:str|None=None,#1
only_mark_time_without_loop:bool=False,#1
):
self.play(
Wait(
run_time=duration,
stop_condition=stop_condition,
frozen_frame=frozen_frame,
)
)
if time_mark_notes:
if not only_mark_time_without_loop:#1 如果不是只记录时间,则把当前时间加入到循环数组中
self.mark_time_list.append(self.pause_time)
self.time_mark_notes[time_mark_notes]=self.pause_time#1
self.time_list.append(round(self.pause_time,4))#1
Scene.play=play#1
Scene.wait=wait#1
def save_combined_time_data_json(time_list, mark_time_list, time_mark_notes, render_time=None, path="./time_data.json"):
"""
参数:
time_list - 全部时间点数组
mark_time_list - 标记的时间点数组
time_mark_notes - 时间标记笔记字典
render_time - 渲染总耗时(秒)
path - 输出JSON文件路径
"""
import json
import time
# 将数组中的值四舍五入到3位小数
time_list_rounded = np.round(time_list, 3).tolist()
mark_time_list_rounded = np.round(mark_time_list, 3).tolist()
# 处理time_mark_notes字典,将值四舍五入
notes_dict = {}
if time_mark_notes and isinstance(next(iter(time_mark_notes.values())), (int, float)):
for k, v in time_mark_notes.items():
notes_dict[k] = round(v, 3) if isinstance(v, (int, float)) else v
else:
for k, v in time_mark_notes.items():
if isinstance(k, (int, float)):
key = f"{round(k, 3)}"
else:
key = str(k)
notes_dict[key] = v
# 计算分钟和秒
if render_time is not None:
minutes = int(render_time // 60)
seconds = render_time % 60
render_time_formatted = f"{minutes}分 {seconds:.2f}秒"
else:
render_time_formatted = "未记录"
# 手动格式化输出,保持原有风格
with open(path, "w", encoding='utf-8') as file:
file.write('{\n')
# 写入渲染总时间
file.write(f' "total_render_time": "{render_time_formatted}",\n')
# 写入time_list数组,每10个元素换行
file.write(' "time_list": [\n ')
for i, value in enumerate(time_list_rounded):
if i > 0 and i % 10 == 0:
file.write(',\n ')
elif i > 0:
file.write(', ')
file.write(f"{value}")
file.write('\n ],\n')
# 写入time_list_mark数组,每10个元素换行
file.write(' "time_list_mark": [\n ')
for i, value in enumerate(mark_time_list_rounded):
if i > 0 and i % 10 == 0:
file.write(',\n ')
elif i > 0:
file.write(', ')
file.write(f"{value}")
file.write('\n ],\n')
# 写入time_mark_notes字典
file.write(' "time_list_mark_notes": ')
json_str = json.dumps(notes_dict, ensure_ascii=False, indent=4)
# 缩进处理,确保格式一致
json_str = json_str.replace('\n', '\n ')
file.write(json_str)
# 关闭最外层花括号
file.write('\n}')
class Demo2(Scene):
def setup(self):
self.default_wait_time=.5
self.render_start_time = time.time() # 记录开始时间
def tear_down(self):
render_time = time.time() - self.render_start_time # 计算渲染时间
save_combined_time_data_json(
self.time_list,
self.mark_time_list,
self.time_mark_notes,
render_time=render_time, # 传递渲染时间
path="./time_data.json"
)
def construct(self):
r=Circle(fill_opacity=1,fill_color=BLUE_A)
self.play(Create(r,run_time=.25))
self.wait(time_mark_notes="section 1",only_mark_time_without_loop=True)
self.play(FadeOut(r,run_time=.5))
self.wait(time_mark_notes="section 2")
self.play(Create(r))
self.wait(time_mark_notes="section 3")
self.play(FadeOut(r))
self.wait(time_mark_notes="section 4")
self.play(FadeIn(r,scale=.1))
self.wait(time_mark_notes="section 5")
self.play(FadeOut(r))
self.wait(time_mark_notes="section 6")
self.play(FadeIn(r,shift=DOWN))
self.wait(time_mark_notes="section 7")
self.play(FadeOut(r))
self.wait(time_mark_notes="section 8")
self.play(FadeIn(r))
self.wait(time_mark_notes="section 9")
self.play(FadeOut(r))
self.wait(time_mark_notes="section 10")
self.play(FadeIn(r))
self.wait(time_mark_notes="section 11")
self.play(FadeOut(r))
self.wait(time_mark_notes="section 12")
self.play(FadeIn(r,scale=.1,rate_func=rate_functions.ease_out_back))
self.wait(time_mark_notes="section 13")
self.play(FadeOut(r))
self.wait(time_mark_notes="section 14")
self.play(FadeIn(r,scale=3,rate_func=rate_functions.ease_out_back))
self.wait(time_mark_notes="section1 5")
self.play(FadeOut(r))
self.wait(time_mark_notes="section 16")
self.play(FadeIn(r,shift=RIGHT*2,rate_func=rate_functions.ease_out_back))
self.wait(time_mark_notes="section 17")
self.play(FadeOut(r))
self.wait(time_mark_notes="section 18")
square = Square().next_to(r,DOWN)
self.play(Create(square))
self.wait()
if __name__ == "__main__":
from os import system
system("manim -pqh {} ".format(__file__))
Next, convert the previously generated data_time.json into individual files in the slides.
import os
import json
import subprocess
import shutil
# Video file path
video_file = "media\\Demo.mp4" # Modify this to the video directory
# Read time data from time_data.json
try:
with open("time_data.json", "r", encoding="utf-8") as f:
time_data = json.load(f)
time_list = time_data["time_list"]
loop_marks = time_data["time_list_mark"]
except FileNotFoundError:
print("time_data.json file not found, using default time points")
time_list = []
loop_marks = []
# Set output directory
output_dir = "slides\\files\\Demo"
os.makedirs(output_dir, exist_ok=True)
# Whether to create reversed videos
CREATE_REVERSED = True # Set to False to skip creating reversed videos
# Store paths of split video files
video_files = []
# Split video
for i in range(len(time_list) - 1):
start_time = time_list[i]
end_time = time_list[i + 1]
duration = end_time - start_time - 0.3
# Check if the current segment should loop
should_loop = end_time in loop_marks
if should_loop:
print(f"Segment {i+1} (from {start_time} to {end_time}) will be set to loop")
# Use simple sequential naming
output_file = os.path.join(output_dir, f"part_{i+1}.mp4")
output_reversed_file = os.path.join(output_dir, f"part_{i+1}_reversed.mp4")
# Use FFmpeg to split the video
print(f"Splitting video: {start_time} to {end_time}")
# All video segments use re-encoding to ensure correct cutting
if i == 0 and abs(start_time) < 0.001: # If it's the first segment and start time is close to 0
ffmpeg_cmd = [
"ffmpeg",
"-y",
"-i",
video_file,
"-t", # Specify only the duration, not the start time
str(duration),
"-c:v", # Use re-encoding to ensure accuracy
"libx264",
"-preset",
"medium",
"-crf",
"23",
output_file,
]
else:
ffmpeg_cmd = [
"ffmpeg",
"-y",
"-i",
video_file,
"-ss",
str(start_time),
"-t",
str(duration),
"-c:v", # Use re-encoding instead of stream copy
"libx264",
"-preset",
"medium",
"-crf",
"23",
output_file,
]
result = subprocess.run(ffmpeg_cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"FFmpeg command failed! Error message: {result.stderr}")
continue # Skip the current video segment
else:
print(f"Successfully created file: {output_file}")
reversed_success = False
if CREATE_REVERSED:
# Create reversed video
print(f"Creating reversed video: {output_reversed_file}")
reversed_cmd = [
"ffmpeg",
"-y",
"-i",
output_file,
"-vf",
"reverse",
"-af",
"areverse",
output_reversed_file,
]
rev_result = subprocess.run(reversed_cmd, capture_output=True, text=True)
if rev_result.returncode != 0:
print(f"Failed to create reversed video! Error message: {rev_result.stderr}")
# If reversed video creation fails, use the original video as the reversed video
shutil.copy(output_file, output_reversed_file)
print(f"Copied original video as reversed video: {output_reversed_file}")
else:
print(f"Successfully created reversed video: {output_reversed_file}")
reversed_success = True
# Add to file list
video_files.append(
{
"file": output_file,
"rev_file": output_reversed_file,
"reversed_success": reversed_success if CREATE_REVERSED else False,
"should_loop": should_loop, # Record whether it should loop
}
)
# Create JSON file
slides_data = []
for i, video in enumerate(video_files):
# Get the start and end time of the current segment
start_time = time_list[i]
end_time = time_list[i + 1]
# Correct file path to ensure consistency with output_dir
# Use os.path.join to ensure cross-platform compatibility
file_path = os.path.join("slides", "files", "Demo", os.path.basename(video["file"]))
rev_file_path = (
file_path
if not CREATE_REVERSED
else os.path.join(
"slides", "files", "Demo", os.path.basename(video["rev_file"])
)
)
slides_data.append(
{
"loop": video["should_loop"], # Set loop attribute based on mark
"auto_next": False,
"playback_rate": 1.0,
"reversed_playback_rate": 1.0,
"notes": "",
"dedent_notes": True,
"skip_animations": False,
"src": None,
"file": file_path,
"rev_file": rev_file_path,
"time_range": f"{start_time:.3f} - {end_time:.3f}", # Add time range identifier
}
)
# Create a structure similar to Demo.json
json_data = {
"slides": slides_data,
"resolution": [1920, 1080],
"background_color": "black",
}
# Save JSON file
with open("slides/Demo.json", "w") as f:
json.dump(json_data, f, indent=2)
print(f"Video splitting completed, a total of {len(video_files)} segments generated")
print(f"JSON file saved to slides/Demo.json")
print(f"Set {sum(1 for v in video_files if v['should_loop'])} segments to loop")
I'm a newbie. I really admire you. Thank you again!
Thanks for sharing @hcq11419! I'll leave this issue open, as we didn't find a good fix yet. If you ever find a simpler example showcasing your bug, please share it here :-)
@jeertmans Okay, thank you for your attention!
I thought I was hitting the same problem. After some minimizing I can trigger it with very few animations.
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "manim-slides[manim]==5.5.1",
# ]
# ///
from manim import *
from manim_slides import ThreeDSlide
class T2vert(ThreeDSlide):
def construct(self):
k = ValueTracker(0)
def slice_function(x, y):
return x + y
def z_slice(num):
k.set_value(num)
return ImplicitFunction(
slice_function, color=YELLOW, x_range=((-5, 5)), y_range=((-5, 5))
)
ref_dot2 = always_redraw(
lambda: Dot3D(
point=interpolate(
DOWN,
UP,
k.get_value(),
),
)
)
slice = z_slice(1)
self.play(
FadeIn(slice),
run_time=0.025,
)
self.play(FadeOut(ref_dot2))
if __name__ == "__main__":
import subprocess
import sys
subprocess.run([sys.executable, "-m", "manim_slides", "render", __file__])
If either of the last two animations are omitted the bug is not triggered
[06/03/25 21:05:52] INFO Animation 1 : Partial movie file written in '/home/ethan/manim-repo/media/videos/T2vert/1440p60/partial_movie_files/T2vert/1616758632_239749663_3857932860.mp4' scene_file_writer.py:588
INFO Combining to Movie file. scene_file_writer.py:739
INFO scene_file_writer.py:886
File ready at '/home/ethan/manim-repo/media/videos/T2vert/1440p60/T2vert.mp4'
INFO Rendered T2vert scene.py:255
Played 2 animations
Concatenating animation files to 'slides/files/T2vert' and generating reversed animations: 0%| | 0/1 [00:00<?, ?it/s]
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ /nix/store/pq0jl3sl9m5vabhnrqpir3w3dlxmfcpb-python3-3.12.9-env/lib/python3.12/site-packages/mani │
│ m/cli/render/commands.py:125 in render │
│ │
│ 122 │ │ │ try: │
│ 123 │ │ │ │ with tempconfig({}): │
│ 124 │ │ │ │ │ scene = SceneClass() │
│ ❱ 125 │ │ │ │ │ scene.render() │
│ 126 │ │ │ except Exception: │
│ 127 │ │ │ │ error_console.print_exception() │
│ 128 │ │ │ │ sys.exit(1) │
│ │
│ /nix/store/pq0jl3sl9m5vabhnrqpir3w3dlxmfcpb-python3-3.12.9-env/lib/python3.12/site-packages/mani │
│ m_slides/slide/manim.py:156 in render │
│ │
│ 153 │ │ │
│ 154 │ │ config["max_files_cached"] = max_files_cached │
│ 155 │ │ │
│ ❱ 156 │ │ self._save_slides( │
│ 157 │ │ │ use_cache=not (config["disable_caching"] or self.disable_caching), │
│ 158 │ │ │ flush_cache=(config["flush_cache"] or self.flush_cache), │
│ 159 │ │ │ skip_reversing=self.skip_reversing, │
│ │
│ /nix/store/pq0jl3sl9m5vabhnrqpir3w3dlxmfcpb-python3-3.12.9-env/lib/python3.12/site-packages/mani │
│ m_slides/slide/base.py:553 in _save_slides │
│ │
│ 550 │ │ │ │
│ 551 │ │ │ # We only concat animations if it was not present │
│ 552 │ │ │ if not use_cache or not dst_file.exists(): │
│ ❱ 553 │ │ │ │ concatenate_video_files(slide_files, dst_file) │
│ 554 │ │ │ │
│ 555 │ │ │ # We only reverse video if it was not present │
│ 556 │ │ │ if not use_cache or not rev_file.exists(): │
│ │
│ /nix/store/pq0jl3sl9m5vabhnrqpir3w3dlxmfcpb-python3-3.12.9-env/lib/python3.12/site-packages/mani │
│ m_slides/utils.py:60 in concatenate_video_files │
│ │
│ 57 │ │ │ │ packet.stream = output_audio_stream │
│ 58 │ │ │ else: │
│ 59 │ │ │ │ continue # We don't support subtitles │
│ ❱ 60 │ │ │ output_container.mux(packet) │
│ 61 │ │
│ 62 │ os.unlink(tmp_file) # https://stackoverflow.com/a/54768241 │
│ 63 │
│ │
│ in av.container.output.OutputContainer.mux:257 │
│ │
│ in av.container.output.OutputContainer.mux_one:278 │
│ │
│ in av.container.core.Container.err_check:286 │
│ │
│ in av.error.err_check:326 │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
ValueError: [Errno 22] Invalid argument: 'slides/files/T2vert/f138f54039f0736b833e15b6fe59b38d26e90ec90e497f9544604db7955b195c.mp4'
System info: manimce: 0.19.0 manim-slides: 5.5.1
Thanks @erooke, I have reformatted it as a runnable script (I edited your comment) with uv: uv run file.py.
any updates on this? I am running into the same problem. @jeertmans
wild guess: I think the problem happens if the run_time and the fps values are too short so a video gets rendered which is bascially an empty frame. A run_time of 0.025 is less than two frames at 60fps and only one frame at 30fps.
I managed to render my slides by changing the fps value, I guess equivalently one could increase the run_time of the animation
Hi @ufuk-cakir, thanks for investigating this! I already had some issues when the video was too short to first one at least one frame, but I don't know if we can provide a good fix to this. This might be worth asking the team behind PyAV...