mirror of
https://github.com/3b1b/manim.git
synced 2025-04-13 09:47:07 +00:00
Scenes now pass frames directly to ffmpeg pipe when writing movies, rather than saving them all up in bulk then passing in the list
This commit is contained in:
parent
274b180eaa
commit
18b2267c7e
2 changed files with 68 additions and 81 deletions
|
@ -46,16 +46,17 @@ def get_configuration(sys_argv):
|
||||||
print str(err)
|
print str(err)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
config = {
|
config = {
|
||||||
"file" : None,
|
"file" : None,
|
||||||
"scene_name" : "",
|
"scene_name" : "",
|
||||||
"camera_config" : PRODUCTION_QUALITY_CAMERA_CONFIG,
|
"camera_config" : PRODUCTION_QUALITY_CAMERA_CONFIG,
|
||||||
"frame_duration" : PRODUCTION_QUALITY_FRAME_DURATION,
|
"frame_duration" : PRODUCTION_QUALITY_FRAME_DURATION,
|
||||||
"preview" : False,
|
"preview" : False,
|
||||||
"write" : False,
|
"write_to_movie" : False,
|
||||||
"save_image" : False,
|
"save_all_frames" : False,
|
||||||
"quiet" : False,
|
"save_image" : False,
|
||||||
"write_all" : False,
|
"quiet" : False,
|
||||||
"output_name" : None,
|
"write_all" : False,
|
||||||
|
"output_name" : None,
|
||||||
}
|
}
|
||||||
for opt, arg in opts:
|
for opt, arg in opts:
|
||||||
if opt == '-h':
|
if opt == '-h':
|
||||||
|
@ -66,11 +67,12 @@ def get_configuration(sys_argv):
|
||||||
config["frame_duration"] = LOW_QUALITY_FRAME_DURATION
|
config["frame_duration"] = LOW_QUALITY_FRAME_DURATION
|
||||||
if opt == '-p':
|
if opt == '-p':
|
||||||
config["preview"] = True
|
config["preview"] = True
|
||||||
|
config["save_all_frames"] = True
|
||||||
if opt == '-m':
|
if opt == '-m':
|
||||||
config["camera_config"] = MEDIUM_QUALITY_CAMERA_CONFIG
|
config["camera_config"] = MEDIUM_QUALITY_CAMERA_CONFIG
|
||||||
config["frame_duration"] = MEDIUM_QUALITY_FRAME_DURATION
|
config["frame_duration"] = MEDIUM_QUALITY_FRAME_DURATION
|
||||||
if opt == '-w':
|
if opt == '-w':
|
||||||
config["write"] = True
|
config["write_to_movie"] = True
|
||||||
if opt == '-s':
|
if opt == '-s':
|
||||||
config["save_image"] = True
|
config["save_image"] = True
|
||||||
if opt in ['-q', '-a']:
|
if opt in ['-q', '-a']:
|
||||||
|
@ -80,10 +82,10 @@ def get_configuration(sys_argv):
|
||||||
if opt == '-o':
|
if opt == '-o':
|
||||||
config["output_name"] = arg
|
config["output_name"] = arg
|
||||||
#By default, write to file
|
#By default, write to file
|
||||||
actions = ["write", "preview", "save_image"]
|
actions = ["write_to_movie", "preview", "save_image"]
|
||||||
if not any([config[key] for key in actions]):
|
if not any([config[key] for key in actions]):
|
||||||
config["write"] = True
|
config["write_to_movie"] = True
|
||||||
config["skip_animations"] = config["save_image"] and not config["write"]
|
config["skip_animations"] = config["save_image"] and not config["write_to_movie"]
|
||||||
|
|
||||||
if len(args) == 0:
|
if len(args) == 0:
|
||||||
print HELP_MESSAGE
|
print HELP_MESSAGE
|
||||||
|
@ -104,10 +106,7 @@ def handle_scene(scene, **config):
|
||||||
if config["save_image"]:
|
if config["save_image"]:
|
||||||
if not config["write_all"]:
|
if not config["write_all"]:
|
||||||
scene.show_frame()
|
scene.show_frame()
|
||||||
path = os.path.join(MOVIE_DIR, config["movie_prefix"])
|
scene.save_image(output_name)
|
||||||
scene.save_image(path, output_name)
|
|
||||||
if config["write"]:
|
|
||||||
scene.write_to_movie(os.path.join(config["movie_prefix"], output_name))
|
|
||||||
|
|
||||||
if config["quiet"]:
|
if config["quiet"]:
|
||||||
sys.stdout.close()
|
sys.stdout.close()
|
||||||
|
@ -167,12 +166,22 @@ def main():
|
||||||
scene_names_to_classes = dict(
|
scene_names_to_classes = dict(
|
||||||
inspect.getmembers(module, is_scene)
|
inspect.getmembers(module, is_scene)
|
||||||
)
|
)
|
||||||
config["movie_prefix"] = config["file"].replace(".py", "")
|
config["output_directory"] = os.path.join(
|
||||||
scene_kwargs = {
|
MOVIE_DIR,
|
||||||
"camera_config" : config["camera_config"],
|
config["file"].replace(".py", "")
|
||||||
"frame_duration" : config["frame_duration"],
|
)
|
||||||
"skip_animations" : config["skip_animations"],
|
|
||||||
}
|
scene_kwargs = dict([
|
||||||
|
(key, config[key])
|
||||||
|
for key in [
|
||||||
|
"camera_config",
|
||||||
|
"frame_duration",
|
||||||
|
"skip_animations",
|
||||||
|
"write_to_movie",
|
||||||
|
"save_all_frames",
|
||||||
|
"output_directory",
|
||||||
|
]
|
||||||
|
])
|
||||||
for SceneClass in get_scene_classes(scene_names_to_classes, config):
|
for SceneClass in get_scene_classes(scene_names_to_classes, config):
|
||||||
try:
|
try:
|
||||||
handle_scene(SceneClass(**scene_kwargs), **config)
|
handle_scene(SceneClass(**scene_kwargs), **config)
|
||||||
|
|
|
@ -21,22 +21,29 @@ from animation.transform import MoveToTarget
|
||||||
|
|
||||||
class Scene(object):
|
class Scene(object):
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"camera_class" : Camera,
|
"camera_class" : Camera,
|
||||||
"camera_config" : {},
|
"camera_config" : {},
|
||||||
"frame_duration" : LOW_QUALITY_FRAME_DURATION,
|
"frame_duration" : LOW_QUALITY_FRAME_DURATION,
|
||||||
"construct_args" : [],
|
"construct_args" : [],
|
||||||
"skip_animations" : False,
|
"skip_animations" : False,
|
||||||
|
"write_to_movie" : True,
|
||||||
|
"save_frames" : False,
|
||||||
|
"output_directory" : MOVIE_DIR,
|
||||||
}
|
}
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
digest_config(self, kwargs)
|
digest_config(self, kwargs)
|
||||||
self.camera = self.camera_class(**self.camera_config)
|
self.camera = self.camera_class(**self.camera_config)
|
||||||
self.frames = []
|
|
||||||
self.mobjects = []
|
self.mobjects = []
|
||||||
self.foreground_mobjects = []
|
self.foreground_mobjects = []
|
||||||
self.num_plays = 0
|
self.num_plays = 0
|
||||||
|
self.saved_frames = []
|
||||||
|
|
||||||
self.setup()
|
self.setup()
|
||||||
|
if self.write_to_movie:
|
||||||
|
self.open_movie_pipe()
|
||||||
self.construct(*self.construct_args)
|
self.construct(*self.construct_args)
|
||||||
|
if self.write_to_movie:
|
||||||
|
self.close_movie_pipe()
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
"""
|
"""
|
||||||
|
@ -313,31 +320,6 @@ class Scene(object):
|
||||||
return self.mobjects_from_last_animation
|
return self.mobjects_from_last_animation
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def play_over_time_range(self, t0, t1, *animations):
|
|
||||||
needed_scene_time = max(abs(t0), abs(t1))
|
|
||||||
existing_scene_time = len(self.frames)*self.frame_duration
|
|
||||||
if existing_scene_time < needed_scene_time:
|
|
||||||
self.dither(needed_scene_time - existing_scene_time)
|
|
||||||
existing_scene_time = needed_scene_time
|
|
||||||
#So negative values may be used
|
|
||||||
if t0 < 0:
|
|
||||||
t0 = float(t0)%existing_scene_time
|
|
||||||
if t1 < 0:
|
|
||||||
t1 = float(t1)%existing_scene_time
|
|
||||||
t0, t1 = min(t0, t1), max(t0, t1)
|
|
||||||
|
|
||||||
moving_mobjects, static_mobjects = \
|
|
||||||
self.separate_moving_and_static_mobjects(*animations)
|
|
||||||
for t in np.arange(t0, t1, self.frame_duration):
|
|
||||||
for animation in animations:
|
|
||||||
animation.update((t-t0)/(t1 - t0))
|
|
||||||
index = int(t/self.frame_duration)
|
|
||||||
self.update_frame(moving_mobjects, self.frames[index])
|
|
||||||
self.frames[index] = self.get_frame()
|
|
||||||
for animation in animations:
|
|
||||||
animation.clean_up()
|
|
||||||
return self
|
|
||||||
|
|
||||||
def dither(self, duration = DEFAULT_DITHER_TIME):
|
def dither(self, duration = DEFAULT_DITHER_TIME):
|
||||||
if self.skip_animations:
|
if self.skip_animations:
|
||||||
return self
|
return self
|
||||||
|
@ -356,21 +338,22 @@ class Scene(object):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def add_frames(self, *frames):
|
def add_frames(self, *frames):
|
||||||
self.frames += list(frames)
|
if self.write_to_movie:
|
||||||
|
for frame in frames:
|
||||||
|
self.writing_process.stdin.write(frame.tostring())
|
||||||
|
if self.save_frames:
|
||||||
|
self.saved_frames += list(frames)
|
||||||
|
|
||||||
def repeat_frames(self, num):
|
def repeat_frames(self, num = 1):
|
||||||
self.frames = self.frames*num
|
assert(self.save_frames)
|
||||||
return self
|
self.add_frames(*self.saved_frames*num)
|
||||||
|
|
||||||
def reverse_frames(self):
|
|
||||||
self.frames.reverse()
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def invert_colors(self):
|
def invert_colors(self):
|
||||||
white_frame = 255*np.ones(self.get_frame().shape, dtype = 'uint8')
|
white_frame = 255*np.ones(self.get_frame().shape, dtype = 'uint8')
|
||||||
self.frames = [
|
self.saved_frames = [
|
||||||
white_frame-frame
|
white_frame-frame
|
||||||
for frame in self.frames
|
for frame in self.saved_frames
|
||||||
]
|
]
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@ -381,8 +364,8 @@ class Scene(object):
|
||||||
def preview(self):
|
def preview(self):
|
||||||
TkSceneRoot(self)
|
TkSceneRoot(self)
|
||||||
|
|
||||||
def save_image(self, directory = MOVIE_DIR, name = None):
|
def save_image(self, name = None):
|
||||||
path = os.path.join(directory, "images")
|
path = os.path.join(self.output_directory, "images")
|
||||||
file_name = (name or str(self)) + ".png"
|
file_name = (name or str(self)) + ".png"
|
||||||
full_path = os.path.join(path, file_name)
|
full_path = os.path.join(path, file_name)
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
|
@ -391,20 +374,15 @@ class Scene(object):
|
||||||
Image.fromarray(self.get_frame()).save(full_path)
|
Image.fromarray(self.get_frame()).save(full_path)
|
||||||
|
|
||||||
def get_movie_file_path(self, name, extension):
|
def get_movie_file_path(self, name, extension):
|
||||||
file_path = os.path.join(MOVIE_DIR, name)
|
file_path = os.path.join(self.output_directory, name)
|
||||||
if not file_path.endswith(extension):
|
if not file_path.endswith(extension):
|
||||||
file_path += extension
|
file_path += extension
|
||||||
directory = os.path.split(file_path)[0]
|
if not os.path.exists(self.output_directory):
|
||||||
if not os.path.exists(directory):
|
os.makedirs(self.output_directory)
|
||||||
os.makedirs(directory)
|
|
||||||
return file_path
|
return file_path
|
||||||
|
|
||||||
def write_to_movie(self, name = None):
|
def open_movie_pipe(self):
|
||||||
if len(self.frames) == 0:
|
name = str(self)
|
||||||
print "No frames, so I'm not writing anything"
|
|
||||||
return
|
|
||||||
if name is None:
|
|
||||||
name = str(self)
|
|
||||||
file_path = self.get_movie_file_path(name, ".mp4")
|
file_path = self.get_movie_file_path(name, ".mp4")
|
||||||
print "Writing to %s"%file_path
|
print "Writing to %s"%file_path
|
||||||
|
|
||||||
|
@ -427,11 +405,11 @@ class Scene(object):
|
||||||
'-loglevel', 'error',
|
'-loglevel', 'error',
|
||||||
file_path,
|
file_path,
|
||||||
]
|
]
|
||||||
process = sp.Popen(command, stdin=sp.PIPE)
|
self.writing_process = sp.Popen(command, stdin=sp.PIPE)
|
||||||
for frame in self.frames:
|
|
||||||
process.stdin.write(frame.tostring())
|
def close_movie_pipe(self):
|
||||||
process.stdin.close()
|
self.writing_process.stdin.close()
|
||||||
process.wait()
|
self.writing_process.wait()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue