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:
Grant Sanderson 2017-05-09 13:14:08 -07:00
parent 274b180eaa
commit 18b2267c7e
2 changed files with 68 additions and 81 deletions

View file

@ -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)

View file

@ -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()