Skip to content

Commit cfcdca0

Browse files
committed
Merge remote-tracking branch 'origin/master' into renderer-refactor
2 parents 97f0715 + dde2228 commit cfcdca0

File tree

5 files changed

+113
-51
lines changed

5 files changed

+113
-51
lines changed

manim/renderer/cairo_renderer.py

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,16 @@ def wrapper(self, *args, **kwargs):
2727
if file_writer_config["skip_animations"]:
2828
logger.debug(f"Skipping animation {self.num_plays}")
2929
func(self, *args, **kwargs)
30+
# If the animation is skipped, we mark its hash as None.
31+
# When sceneFileWriter will start combining partial movie files, it won't take into account None hashes.
32+
self.animations_hashes.append(None)
33+
self.file_writer.add_partial_movie_file(None)
3034
return
3135
if not file_writer_config["disable_caching"]:
3236
mobjects_on_scene = self.scene.get_mobjects()
3337
hash_play = get_hash_from_play_call(
3438
self, self.camera, animations, mobjects_on_scene
3539
)
36-
self.play_hashes_list.append(hash_play)
3740
if self.file_writer.is_already_cached(hash_play):
3841
logger.info(
3942
f"Animation {self.num_plays} : Using cached data (hash : %(hash_play)s)",
@@ -42,12 +45,62 @@ def wrapper(self, *args, **kwargs):
4245
file_writer_config["skip_animations"] = True
4346
else:
4447
hash_play = "uncached_{:05}".format(self.num_plays)
45-
self.play_hashes_list.append(hash_play)
48+
self.animations_hashes.append(hash_play)
49+
self.file_writer.add_partial_movie_file(hash_play)
50+
logger.debug(
51+
"List of the first few animation hashes of the scene: %(h)s",
52+
{"h": str(self.animations_hashes[:5])},
53+
)
4654
func(self, *args, **kwargs)
4755

4856
return wrapper
4957

5058

59+
def handle_caching_wait(func):
60+
"""
61+
Decorator that returns a wrapped version of func that will compute the hash of the wait invocation.
62+
63+
The returned function will act according to the computed hash: either skip the animation because it's already cached, or let the invoked function play normally.
64+
65+
Parameters
66+
----------
67+
func : Callable[[...], None]
68+
The wait like function that has to be written to the video file stream. Take the same parameters as `scene.wait`.
69+
"""
70+
71+
def wrapper(self, duration=DEFAULT_WAIT_TIME, stop_condition=None):
72+
self.revert_to_original_skipping_status()
73+
self.update_skipping_status()
74+
if file_writer_config["skip_animations"]:
75+
logger.debug(f"Skipping wait {self.num_plays}")
76+
func(self, duration, stop_condition)
77+
# If the animation is skipped, we mark its hash as None.
78+
# When sceneFileWriter will start combining partial movie files, it won't take into account None hashes.
79+
self.animations_hashes.append(None)
80+
self.file_writer.add_partial_movie_file(None)
81+
return
82+
if not file_writer_config["disable_caching"]:
83+
hash_wait = get_hash_from_wait_call(
84+
self, self.camera, duration, stop_condition, self.get_mobjects()
85+
)
86+
if self.file_writer.is_already_cached(hash_wait):
87+
logger.info(
88+
f"Wait {self.num_plays} : Using cached data (hash : {hash_wait})"
89+
)
90+
file_writer_config["skip_animations"] = True
91+
else:
92+
hash_wait = "uncached_{:05}".format(self.num_plays)
93+
self.animations_hashes.append(hash_wait)
94+
self.file_writer.add_partial_movie_file(hash_wait)
95+
logger.debug(
96+
"Animations hashes list of the scene : (concatened to 5) %(h)s",
97+
{"h": str(self.animations_hashes[:5])},
98+
)
99+
func(self, duration, stop_condition)
100+
101+
return wrapper
102+
103+
51104
def handle_play_like_call(func):
52105
"""
53106
This method is used internally to wrap the
@@ -110,7 +163,7 @@ def __init__(self, scene, camera_class, **kwargs):
110163
)
111164
self.scene = scene
112165
self.original_skipping_status = file_writer_config["skip_animations"]
113-
self.play_hashes_list = []
166+
self.animations_hashes = []
114167
self.num_plays = 0
115168
self.time = 0
116169

manim/scene/scene_file_writer.py

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ class SceneFileWriter(object):
3737
The PIL image mode to use when outputting PNGs
3838
"movie_file_extension" (str=".mp4")
3939
The file-type extension of the outputted video.
40+
"partial_movie_files"
41+
List of all the partial-movie files.
42+
4043
"""
4144

4245
def __init__(self, video_quality_config, scene, **kwargs):
@@ -47,7 +50,7 @@ def __init__(self, video_quality_config, scene, **kwargs):
4750
self.init_output_directories()
4851
self.init_audio()
4952
self.frame_count = 0
50-
self.index_partial_movie_file = 0
53+
self.partial_movie_files = []
5154

5255
# Output directories and files
5356
def init_output_directories(self):
@@ -114,6 +117,29 @@ def init_output_directories(self):
114117
)
115118
)
116119

120+
def add_partial_movie_file(self, hash_animation):
121+
"""Adds a new partial movie file path to scene.partial_movie_files from an hash. This method will compute the path from the hash.
122+
123+
Parameters
124+
----------
125+
hash_animation : str
126+
Hash of the animation.
127+
"""
128+
129+
# None has to be added to partial_movie_files to keep the right index with scene.num_plays.
130+
# i.e if an animation is skipped, scene.num_plays is still incremented and we add an element to partial_movie_file be even with num_plays.
131+
if hash_animation is None:
132+
self.partial_movie_files.append(None)
133+
return
134+
new_partial_movie_file = os.path.join(
135+
self.partial_movie_directory,
136+
"{}{}".format(
137+
hash_animation,
138+
file_writer_config["movie_file_extension"],
139+
),
140+
)
141+
self.partial_movie_files.append(new_partial_movie_file)
142+
117143
def get_default_module_directory(self):
118144
"""
119145
This method gets the name of the directory containing
@@ -150,7 +176,7 @@ def get_resolution_directory(self):
150176
This method gets the name of the directory that immediately contains the
151177
video file. This name is ``<height_in_pixels_of_video>p<frame_rate>``.
152178
For example, if you are rendering an 854x480 px animation at 15fps,
153-
the name of the directory that immediately contains the video file
179+
the name of the directory that immediately contains the video, file
154180
will be ``480p15``.
155181
156182
The file structure should look something like::
@@ -187,29 +213,6 @@ def get_image_file_path(self):
187213
"""
188214
return self.image_file_path
189215

190-
def get_next_partial_movie_path(self):
191-
"""
192-
Manim renders each play-like call in a short partial
193-
video file. All such files are then concatenated with
194-
the help of FFMPEG.
195-
196-
This method returns the path of the next partial movie.
197-
198-
Returns
199-
-------
200-
str
201-
The path of the next partial movie.
202-
"""
203-
result = os.path.join(
204-
self.partial_movie_directory,
205-
"{}{}".format(
206-
self.scene.renderer.play_hashes_list[self.index_partial_movie_file],
207-
file_writer_config["movie_file_extension"],
208-
),
209-
)
210-
self.index_partial_movie_file += 1
211-
return result
212-
213216
def get_movie_file_path(self):
214217
"""
215218
Returns the final path of the written video file.
@@ -397,7 +400,9 @@ def open_movie_pipe(self):
397400
FFMPEG and begin writing to FFMPEG's input
398401
buffer.
399402
"""
400-
file_path = self.get_next_partial_movie_path()
403+
file_path = self.partial_movie_files[self.scene.renderer.num_plays]
404+
405+
# TODO #486 Why does ffmpeg need temp files ?
401406
temp_file_path = (
402407
os.path.splitext(file_path)[0]
403408
+ "_temp"
@@ -495,21 +500,20 @@ def combine_movie_files(self):
495500
# cuts at all the places you might want. But for viewing
496501
# the scene as a whole, one of course wants to see it as a
497502
# single piece.
498-
partial_movie_files = [
499-
os.path.join(
500-
self.partial_movie_directory,
501-
"{}{}".format(hash_play, file_writer_config["movie_file_extension"]),
502-
)
503-
for hash_play in self.scene.renderer.play_hashes_list
504-
]
505-
if len(partial_movie_files) == 0:
506-
logger.error("No animations in this scene")
507-
return
503+
partial_movie_files = [el for el in self.partial_movie_files if el is not None]
504+
# NOTE : Here we should do a check and raise an exeption if partial movie file is empty.
505+
# We can't, as a lot of stuff (in particular, in tests) use scene initialization, and this error would be raised as it's just
506+
# an empty scene initialized.
507+
508508
# Write a file partial_file_list.txt containing all
509509
# partial movie files. This is used by FFMPEG.
510510
file_list = os.path.join(
511511
self.partial_movie_directory, "partial_movie_file_list.txt"
512512
)
513+
logger.debug(
514+
f"Partial movie files to combine ({len(partial_movie_files)} files): %(p)s",
515+
{"p": partial_movie_files[:5]},
516+
)
513517
with open(file_list, "w") as fp:
514518
fp.write("# This file is used internally by FFMPEG.\n")
515519
for pf_path in partial_movie_files:

manim/utils/hashing.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -252,9 +252,11 @@ def get_hash_from_play_call(
252252
]
253253
t_end = perf_counter()
254254
logger.debug("Hashing done in %(time)s s.", {"time": str(t_end - t_start)[:8]})
255+
hash_complete = f"{hash_camera}_{hash_animations}_{hash_current_mobjects}"
255256
# This will reset ALREADY_PROCESSED_ID as all the hashing processus is finished.
256257
ALREADY_PROCESSED_ID = {}
257-
return "{}_{}_{}".format(hash_camera, hash_animations, hash_current_mobjects)
258+
logger.debug("Hash generated : %(h)s", {"h": hash_complete})
259+
return hash_complete
258260

259261

260262
def get_hash_from_wait_call(
@@ -299,15 +301,15 @@ def get_hash_from_wait_call(
299301
ALREADY_PROCESSED_ID = {}
300302
t_end = perf_counter()
301303
logger.debug("Hashing done in %(time)s s.", {"time": str(t_end - t_start)[:8]})
302-
return "{}_{}{}_{}".format(
303-
hash_camera,
304-
str(wait_time).replace(".", "-"),
305-
hash_function,
306-
hash_current_mobjects,
307-
)
304+
hash_complete = f"{hash_camera}_{str(wait_time).replace('.', '-')}{hash_function}_{hash_current_mobjects}"
305+
logger.debug("Hash generated : %(h)s", {"h": hash_complete})
306+
return hash_complete
308307
ALREADY_PROCESSED_ID = {}
309308
t_end = perf_counter()
310309
logger.debug("Hashing done in %(time)s s.", {"time": str(t_end - t_start)[:8]})
311-
return "{}_{}_{}".format(
312-
hash_camera, str(wait_time).replace(".", "-"), hash_current_mobjects
310+
hash_complete = (
311+
f"{hash_camera}_{str(wait_time).replace('.', '-')}_{hash_current_mobjects}"
313312
)
313+
314+
logger.debug("Hash generated : %(h)s", {"h": hash_complete})
315+
return hash_complete
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
{"levelname": "INFO", "module": "config", "message": "Log file will be saved in <>"}
22
{"levelname": "DEBUG", "module": "hashing", "message": "Hashing ..."}
33
{"levelname": "DEBUG", "module": "hashing", "message": "Hashing done in <> s."}
4+
{"levelname": "DEBUG", "module": "hashing", "message": "Hash generated : <>"}
5+
{"levelname": "DEBUG", "module": "cairo_renderer", "message": "List of the first few animation hashes of the scene: <>"}
46
{"levelname": "INFO", "module": "scene_file_writer", "message": "Animation 0 : Partial movie file written in <>"}
7+
{"levelname": "DEBUG", "module": "scene_file_writer", "message": "Partial movie files to combine (1 files): <>"}
58
{"levelname": "INFO", "module": "scene_file_writer", "message": "\nFile ready at <>\n"}
69
{"levelname": "INFO", "module": "cairo_renderer", "message": "Rendered SquareToCircle\nPlayed 1 animations"}

tests/test_scene_rendering/test_caching_relalted.py renamed to tests/test_scene_rendering/test_caching_related.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from manim import file_writer_config
55

66
from ..utils.commands import capture
7-
from ..utils.video_tester import video_comparison
7+
from ..utils.video_tester import *
88

99

1010
@video_comparison(
@@ -20,7 +20,7 @@ def test_wait_skip(tmp_path, manim_cfg_file, simple_scenes_path):
2020
"manim",
2121
simple_scenes_path,
2222
scene_name,
23-
"-ql",
23+
"-l",
2424
"--media_dir",
2525
str(tmp_path),
2626
"-n",
@@ -43,7 +43,7 @@ def test_play_skip(tmp_path, manim_cfg_file, simple_scenes_path):
4343
"manim",
4444
simple_scenes_path,
4545
scene_name,
46-
"-ql",
46+
"-l",
4747
"--media_dir",
4848
str(tmp_path),
4949
"-n",

0 commit comments

Comments
 (0)