import inspect import itertools as it import os import platform import subprocess as sp import sys import traceback from manimlib.scene.scene import Scene from manimlib.utils.sounds import play_error_sound from manimlib.utils.sounds import play_finish_sound import manimlib.constants def open_file_if_needed(file_writer, **config): if config["quiet"]: curr_stdout = sys.stdout sys.stdout = open(os.devnull, "w") open_file = any([ config["open_video_upon_completion"], config["show_file_in_finder"] ]) if open_file: current_os = platform.system() file_paths = [] if config["file_writer_config"]["save_last_frame"]: file_paths.append(file_writer.get_image_file_path()) if config["file_writer_config"]["write_to_movie"]: file_paths.append(file_writer.get_movie_file_path()) for file_path in file_paths: if current_os == "Windows": os.startfile(file_path) else: commands = [] if (current_os == "Linux"): commands.append("xdg-open") else: # Assume macOS commands.append("open") if config["show_file_in_finder"]: commands.append("-R") commands.append(file_path) # commands.append("-g") FNULL = open(os.devnull, 'w') sp.call(commands, stdout=FNULL, stderr=sp.STDOUT) FNULL.close() if config["quiet"]: sys.stdout.close() sys.stdout = curr_stdout def is_child_scene(obj, module): if not inspect.isclass(obj): return False if not issubclass(obj, Scene): return False if obj == Scene: return False if not obj.__module__.startswith(module.__name__): return False return True def prompt_user_for_choice(name_to_obj): num_to_name = {} names = sorted(name_to_obj.keys()) for count, name in zip(it.count(1), names): print("%d: %s" % (count, name)) num_to_name[count] = name try: user_input = input(manimlib.constants.CHOOSE_NUMBER_MESSAGE) return [ name_to_obj[num_to_name[int(num_str)]] for num_str in user_input.split(",") ] except KeyError: print(manimlib.constants.INVALID_NUMBER_MESSAGE) sys.exit(2) user_input = input(manimlib.constants.CHOOSE_NUMBER_MESSAGE) return [ name_to_obj[num_to_name[int(num_str)]] for num_str in user_input.split(",") ] except EOFError: sys.exit(1) def get_scene_classes(scene_names_to_classes, config): if len(scene_names_to_classes) == 0: print(manimlib.constants.NO_SCENE_MESSAGE) return [] if config["write_all"]: return list(scene_names_to_classes.values()) scene_classes = [] for scene_name in config["scene_names"]: if scene_name in scene_names_to_classes: scene_classes.append(scene_names_to_classes[scene_name]) elif scene_name != "": print( manimlib.constants.SCENE_NOT_FOUND_MESSAGE.format( scene_name ), file=sys.stderr ) if scene_classes: return scene_classes return prompt_user_for_choice(scene_names_to_classes) def main(config): module = config["module"] scene_names_to_classes = dict( inspect.getmembers(module, lambda x: is_child_scene(x, module)) ) scene_kwargs = dict([ (key, config[key]) for key in [ "camera_config", "frame_duration", "skip_animations", "file_writer_config", "start_at_animation_number", "end_at_animation_number", "leave_progress_bars", ] ]) for SceneClass in get_scene_classes(scene_names_to_classes, config): try: # By invoking, this renders the full scene scene = SceneClass(**scene_kwargs) open_file_if_needed(scene.file_writer, **config) if config["sound"]: play_finish_sound() except Exception: print("\n\n") traceback.print_exc() print("\n\n") if config["sound"]: play_error_sound() if __name__ == "__main__": main()