Making writing to partial movies optional, and set the default to be False.

This commit is contained in:
Grant Sanderson 2021-01-23 11:02:22 -08:00
parent 090743aacb
commit 0ac155d150
4 changed files with 67 additions and 56 deletions

View file

@ -33,6 +33,13 @@ style:
# also, you can also specify the position(pixel) of the upper left corner of # also, you can also specify the position(pixel) of the upper left corner of
# the window on the monitor, e.g. "960,540" # the window on the monitor, e.g. "960,540"
window_position: UR window_position: UR
# If break_into_partial_movies is set to True, then many small
# files will be written corresponding to each Scene.play and
# Scene.wait call, and these files will then be combined
# to form the full scene. Sometimes video-editing is made
# easier when working with the broken up scene, which
# effectively has cuts at all the places you might want.
break_into_partial_movies: False
camera_qualities: camera_qualities:
low: low:
resolution: "854x480" resolution: "854x480"

View file

@ -174,6 +174,7 @@ def get_configuration(args):
write_file = any([args.write_file, args.open, args.finder]) write_file = any([args.write_file, args.open, args.finder])
file_writer_config = { file_writer_config = {
"write_to_movie": not args.skip_animations and write_file, "write_to_movie": not args.skip_animations and write_file,
"break_into_partial_movies": custom_defaults["break_into_partial_movies"],
"save_last_frame": args.skip_animations and write_file, "save_last_frame": args.skip_animations and write_file,
"save_pngs": args.save_pngs, "save_pngs": args.save_pngs,
"save_as_gif": args.save_as_gif, "save_as_gif": args.save_as_gif,

View file

@ -66,6 +66,7 @@ class Scene(object):
def run(self): def run(self):
self.virtual_animation_start_time = 0 self.virtual_animation_start_time = 0
self.real_animation_start_time = time.time() self.real_animation_start_time = time.time()
self.file_writer.begin()
self.setup() self.setup()
try: try:

View file

@ -17,6 +17,7 @@ from manimlib.utils.sounds import get_full_sound_file_path
class SceneFileWriter(object): class SceneFileWriter(object):
CONFIG = { CONFIG = {
"write_to_movie": False, "write_to_movie": False,
"break_into_partial_movies": False,
# TODO, save_pngs is doing nothing # TODO, save_pngs is doing nothing
"save_pngs": False, "save_pngs": False,
"png_mode": "RGBA", "png_mode": "RGBA",
@ -39,6 +40,7 @@ class SceneFileWriter(object):
def __init__(self, scene, **kwargs): def __init__(self, scene, **kwargs):
digest_config(self, kwargs) digest_config(self, kwargs)
self.scene = scene self.scene = scene
self.writing_process = None
self.init_output_directories() self.init_output_directories()
self.init_audio() self.init_audio()
@ -62,9 +64,10 @@ class SceneFileWriter(object):
self.movie_file_extension, self.movie_file_extension,
self.gif_file_extension, self.gif_file_extension,
) )
self.partial_movie_directory = guarantee_existence(os.path.join( if self.break_into_partial_movies:
movie_dir, "partial_movie_files", scene_name, self.partial_movie_directory = guarantee_existence(os.path.join(
)) movie_dir, "partial_movie_files", scene_name,
))
def get_default_module_directory(self): def get_default_module_directory(self):
path, _ = os.path.splitext(self.input_file_path) path, _ = os.path.splitext(self.input_file_path)
@ -143,12 +146,16 @@ class SceneFileWriter(object):
self.add_audio_segment(new_segment, time, **kwargs) self.add_audio_segment(new_segment, time, **kwargs)
# Writers # Writers
def begin(self):
if not self.break_into_partial_movies and self.write_to_movie:
self.open_movie_pipe(self.get_movie_file_path())
def begin_animation(self): def begin_animation(self):
if self.write_to_movie: if self.break_into_partial_movies and self.write_to_movie:
self.open_movie_pipe() self.open_movie_pipe(self.get_next_partial_movie_path())
def end_animation(self): def end_animation(self):
if self.write_to_movie: if self.break_into_partial_movies and self.write_to_movie:
self.close_movie_pipe() self.close_movie_pipe()
def write_frame(self, camera): def write_frame(self, camera):
@ -163,21 +170,24 @@ class SceneFileWriter(object):
def finish(self): def finish(self):
if self.write_to_movie: if self.write_to_movie:
if hasattr(self, "writing_process"): if not self.break_into_partial_movies:
self.close_movie_pipe()
if self.writing_process is not None:
self.writing_process.terminate() self.writing_process.terminate()
self.combine_movie_files() if self.break_into_partial_movies:
self.combine_movie_files()
if self.includes_sound:
self.add_sound_to_video()
self.print_file_ready_message(self.get_movie_file_path())
if self.save_last_frame: if self.save_last_frame:
self.scene.update_frame(ignore_skipping=True) self.scene.update_frame(ignore_skipping=True)
self.save_final_image(self.scene.get_image()) self.save_final_image(self.scene.get_image())
if self.should_open_file(): if self.should_open_file():
self.open_file() self.open_file()
def open_movie_pipe(self): def open_movie_pipe(self, file_path):
file_path = self.get_next_partial_movie_path() stem, ext = os.path.splitext(file_path)
temp_file_path = os.path.splitext(file_path)[0] + '_temp' + self.movie_file_extension temp_file_path = stem + "_temp" + ext
self.partial_movie_file_path = file_path
self.temp_partial_movie_file_path = temp_file_path
fps = self.scene.camera.frame_rate fps = self.scene.camera.frame_rate
width, height = self.scene.camera.get_pixel_shape() width, height = self.scene.camera.get_pixel_shape()
@ -209,23 +219,17 @@ class SceneFileWriter(object):
] ]
command += [temp_file_path] command += [temp_file_path]
self.writing_process = sp.Popen(command, stdin=sp.PIPE) self.writing_process = sp.Popen(command, stdin=sp.PIPE)
self.temp_file_path = temp_file_path
def close_movie_pipe(self): def close_movie_pipe(self):
self.writing_process.stdin.close() self.writing_process.stdin.close()
self.writing_process.wait() self.writing_process.wait()
shutil.move( shutil.move(
self.temp_partial_movie_file_path, self.temp_file_path,
self.partial_movie_file_path, self.temp_file_path.replace("_temp", ""),
) )
def combine_movie_files(self): def combine_movie_files(self):
# Manim renders the scene as many smaller movie files
# which are then concatenated to a larger one. The reason
# for this is that sometimes video-editing is made easier when
# one works with the broken up scene, which effectively has
# cuts at all the places you might want. But for viewing
# the scene as a whole, one of course wants to see it as a
# single piece.
kwargs = { kwargs = {
"remove_non_integer_files": True, "remove_non_integer_files": True,
"extension": self.movie_file_extension, "extension": self.movie_file_extension,
@ -273,41 +277,39 @@ class SceneFileWriter(object):
combine_process = sp.Popen(commands) combine_process = sp.Popen(commands)
combine_process.wait() combine_process.wait()
if self.includes_sound: def add_sound_to_video(self):
sound_file_path = movie_file_path.replace( movie_file_path = self.get_movie_file_path()
self.movie_file_extension, ".wav" stem, ext = os.path.splitext(movie_file_path)
) sound_file_path = stem + ".wav"
# Makes sure sound file length will match video file # Makes sure sound file length will match video file
self.add_audio_segment(AudioSegment.silent(0)) self.add_audio_segment(AudioSegment.silent(0))
self.audio_segment.export( self.audio_segment.export(
sound_file_path, sound_file_path,
bitrate='312k', bitrate='312k',
) )
temp_file_path = movie_file_path.replace(".", "_temp.") temp_file_path = stem + "_temp" + ext
commands = [ commands = [
"ffmpeg", "ffmpeg",
"-i", movie_file_path, "-i", movie_file_path,
"-i", sound_file_path, "-i", sound_file_path,
'-y', # overwrite output file if it exists '-y', # overwrite output file if it exists
"-c:v", "copy", "-c:v", "copy",
"-c:a", "aac", "-c:a", "aac",
"-b:a", "320k", "-b:a", "320k",
# select video stream from first file # select video stream from first file
"-map", "0:v:0", "-map", "0:v:0",
# select audio stream from second file # select audio stream from second file
"-map", "1:a:0", "-map", "1:a:0",
'-loglevel', 'error', '-loglevel', 'error',
# "-shortest", # "-shortest",
temp_file_path, temp_file_path,
] ]
sp.call(commands) sp.call(commands)
shutil.move(temp_file_path, movie_file_path) shutil.move(temp_file_path, movie_file_path)
os.remove(sound_file_path) os.remove(sound_file_path)
self.print_file_ready_message(movie_file_path)
def print_file_ready_message(self, file_path): def print_file_ready_message(self, file_path):
print("\nFile ready at {}\n".format(file_path)) print(f"\nFile ready at {file_path}\n")
def should_open_file(self): def should_open_file(self):
return any([ return any([