Make manim into a module and refactor configuration

This commit is contained in:
Devin Neal 2018-12-22 14:27:22 -08:00
parent 465951ab88
commit 27f677e45f
6 changed files with 290 additions and 257 deletions

142
config.py Normal file
View file

@ -0,0 +1,142 @@
#!/usr/bin/env python
import argparse
import colour
import constants
import os
import sys
def parse_cli():
try:
parser = argparse.ArgumentParser()
module_location = parser.add_mutually_exclusive_group()
module_location.add_argument(
"file",
nargs="?",
help="path to file holding the python code for the scene",
)
parser.add_argument(
"scene_name",
nargs="?",
help="Name of the Scene class you want to see",
)
module_location.add_argument("--livestream", action="store_true")
parser.add_argument("--to-twitch", action="store_true")
optional_args = [
("-p", "--preview"),
("-w", "--write_to_movie"),
("-s", "--show_last_frame"),
("-l", "--low_quality"),
("-m", "--medium_quality"),
("-g", "--save_pngs"),
("-f", "--show_file_in_finder"),
("-t", "--transparent"),
("-q", "--quiet"),
("-a", "--write_all")
]
for short_arg, long_arg in optional_args:
parser.add_argument(short_arg, long_arg, action="store_true")
parser.add_argument("-o", "--output_name")
parser.add_argument("-n", "--start_at_animation_number")
parser.add_argument("-r", "--resolution")
parser.add_argument("-c", "--color")
args = parser.parse_args()
if args.file is None and not args.livestream:
parser.print_help()
sys.exit(2)
else:
return args
except argparse.ArgumentError as err:
print(str(err))
sys.exit(2)
def get_configuration(args):
if args.output_name is not None:
output_name_root, output_name_ext = os.path.splitext(
args.output_name)
expected_ext = '.png' if args.show_last_frame else '.mp4'
if output_name_ext not in ['', expected_ext]:
print("WARNING: The output will be to (doubly-dotted) %s%s" %
output_name_root % expected_ext)
output_name = args.output_name
else:
# If anyone wants .mp4.mp4 and is surprised to only get .mp4, or such... Well, too bad.
output_name = output_name_root
else:
output_name = args.output_name
config = {
"file": args.file,
"scene_name": args.scene_name,
"open_video_upon_completion": args.preview,
"show_file_in_finder": args.show_file_in_finder,
# By default, write to file
"write_to_movie": args.write_to_movie or not args.show_last_frame,
"show_last_frame": args.show_last_frame,
"save_pngs": args.save_pngs,
# If -t is passed in (for transparent), this will be RGBA
"saved_image_mode": "RGBA" if args.transparent else "RGB",
"movie_file_extension": ".mov" if args.transparent else ".mp4",
"quiet": args.quiet or args.write_all,
"ignore_waits": args.preview,
"write_all": args.write_all,
"output_name": output_name,
"start_at_animation_number": args.start_at_animation_number,
"end_at_animation_number": None,
}
# Camera configuration
config["camera_config"] = {}
if args.low_quality:
config["camera_config"].update(constants.LOW_QUALITY_CAMERA_CONFIG)
config["frame_duration"] = constants.LOW_QUALITY_FRAME_DURATION
elif args.medium_quality:
config["camera_config"].update(constants.MEDIUM_QUALITY_CAMERA_CONFIG)
config["frame_duration"] = constants.MEDIUM_QUALITY_FRAME_DURATION
else:
config["camera_config"].update(constants.PRODUCTION_QUALITY_CAMERA_CONFIG)
config["frame_duration"] = constants.PRODUCTION_QUALITY_FRAME_DURATION
# If the resolution was passed in via -r
if args.resolution:
if "," in args.resolution:
height_str, width_str = args.resolution.split(",")
height = int(height_str)
width = int(width_str)
else:
height = int(args.resolution)
width = int(16 * height / 9)
config["camera_config"].update({
"pixel_height": height,
"pixel_width": width,
})
if args.color:
try:
config["camera_config"]["background_color"] = colour.Color(args.color)
except AttributeError as err:
print("Please use a valid color")
print(err)
sys.exit(2)
# If rendering a transparent image/move, make sure the
# scene has a background opacity of 0
if args.transparent:
config["camera_config"]["background_opacity"] = 0
# Arguments related to skipping
stan = config["start_at_animation_number"]
if stan is not None:
if "," in stan:
start, end = stan.split(",")
config["start_at_animation_number"] = int(start)
config["end_at_animation_number"] = int(end)
else:
config["start_at_animation_number"] = int(stan)
config["skip_animations"] = any([
config["show_last_frame"] and not config["write_to_movie"],
config["start_at_animation_number"],
])
return config

View file

@ -1,6 +1,7 @@
import os import os
import numpy as np import numpy as np
# Initialize directories
env_MEDIA_DIR = os.getenv("MEDIA_DIR") env_MEDIA_DIR = os.getenv("MEDIA_DIR")
if env_MEDIA_DIR: if env_MEDIA_DIR:
MEDIA_DIR = env_MEDIA_DIR MEDIA_DIR = env_MEDIA_DIR
@ -12,17 +13,78 @@ else:
os.path.expanduser('~'), os.path.expanduser('~'),
"Dropbox (3Blue1Brown)/3Blue1Brown Team Folder" "Dropbox (3Blue1Brown)/3Blue1Brown Team Folder"
) )
if not os.path.isdir(MEDIA_DIR): if not os.path.isdir(MEDIA_DIR):
MEDIA_DIR = "media" MEDIA_DIR = "media"
print( print(
f"Media will be stored in {MEDIA_DIR + os.sep}. You can change " + \ f"Media will be stored in {MEDIA_DIR + os.sep}. You can change "
"this behavior by writing a different directory to media_dir.txt." "this behavior by writing a different directory to media_dir.txt."
) )
with open("media_dir.txt", 'w') as media_file: with open("media_dir.txt", 'w') as media_file:
media_file.write(MEDIA_DIR) media_file.write(MEDIA_DIR)
VIDEO_DIR = os.path.join(MEDIA_DIR, "videos")
RASTER_IMAGE_DIR = os.path.join(MEDIA_DIR, "designs", "raster_images")
SVG_IMAGE_DIR = os.path.join(MEDIA_DIR, "designs", "svg_images")
# TODO, staged scenes should really go into a subdirectory of a given scenes directory
STAGED_SCENES_DIR = os.path.join(VIDEO_DIR, "staged_scenes")
###
THIS_DIR = os.path.dirname(os.path.realpath(__file__))
FILE_DIR = os.path.join(THIS_DIR, "files")
TEX_DIR = os.path.join(FILE_DIR, "Tex")
TEX_IMAGE_DIR = TEX_DIR # TODO, What is this doing?
# These two may be depricated now.
MOBJECT_DIR = os.path.join(FILE_DIR, "mobjects")
IMAGE_MOBJECT_DIR = os.path.join(MOBJECT_DIR, "image")
for folder in [FILE_DIR, RASTER_IMAGE_DIR, SVG_IMAGE_DIR, VIDEO_DIR, TEX_DIR,
TEX_IMAGE_DIR, MOBJECT_DIR, IMAGE_MOBJECT_DIR,
STAGED_SCENES_DIR]:
if not os.path.exists(folder):
os.makedirs(folder)
TEX_USE_CTEX = False
TEX_TEXT_TO_REPLACE = "YourTextHere"
TEMPLATE_TEX_FILE = os.path.join(
THIS_DIR, "tex_template.tex" if not TEX_USE_CTEX
else "ctex_template.tex"
)
with open(TEMPLATE_TEX_FILE, "r") as infile:
TEMPLATE_TEXT_FILE_BODY = infile.read()
TEMPLATE_TEX_FILE_BODY = TEMPLATE_TEXT_FILE_BODY.replace(
TEX_TEXT_TO_REPLACE,
"\\begin{align*}\n" + TEX_TEXT_TO_REPLACE + "\n\\end{align*}",
)
HELP_MESSAGE = """
Usage:
python extract_scene.py <module> [<scene name>]
-p preview in low quality
-s show and save picture of last frame
-w write result to file [this is default if nothing else is stated]
-o <file_name> write to a different file_name
-l use low quality
-m use medium quality
-a run and save every scene in the script, or all args for the given scene
-q don't print progress
-f when writing to a movie file, export the frames in png sequence
-t use transperency when exporting images
-n specify the number of the animation to start from
-r specify a resolution
-c specify a background color
"""
SCENE_NOT_FOUND_MESSAGE = """
That scene is not in the script
"""
CHOOSE_NUMBER_MESSAGE = """
Choose number corresponding to desired scene/arguments.
(Use comma separated list for multiple entries)
Choice(s): """
INVALID_NUMBER_MESSAGE = "Fine then, if you don't want to give a valid number I'll just quit"
NO_SCENE_MESSAGE = """
There are no scenes inside that module
"""
LOW_QUALITY_FRAME_DURATION = 1. / 15 LOW_QUALITY_FRAME_DURATION = 1. / 15
MEDIUM_QUALITY_FRAME_DURATION = 1. / 30 MEDIUM_QUALITY_FRAME_DURATION = 1. / 30
PRODUCTION_QUALITY_FRAME_DURATION = 1. / 60 PRODUCTION_QUALITY_FRAME_DURATION = 1. / 60
@ -102,44 +164,9 @@ PI = np.pi
TAU = 2 * PI TAU = 2 * PI
DEGREES = TAU / 360 DEGREES = TAU / 360
VIDEO_DIR = os.path.join(MEDIA_DIR, "videos")
RASTER_IMAGE_DIR = os.path.join(MEDIA_DIR, "designs", "raster_images")
SVG_IMAGE_DIR = os.path.join(MEDIA_DIR, "designs", "svg_images")
# TODO, staged scenes should really go into a subdirectory of a given scenes directory
STAGED_SCENES_DIR = os.path.join(VIDEO_DIR, "staged_scenes")
###
THIS_DIR = os.path.dirname(os.path.realpath(__file__))
FILE_DIR = os.path.join(THIS_DIR, "files")
TEX_DIR = os.path.join(FILE_DIR, "Tex")
TEX_IMAGE_DIR = TEX_DIR # TODO, What is this doing?
# These two may be depricated now.
MOBJECT_DIR = os.path.join(FILE_DIR, "mobjects")
IMAGE_MOBJECT_DIR = os.path.join(MOBJECT_DIR, "image")
for folder in [FILE_DIR, RASTER_IMAGE_DIR, SVG_IMAGE_DIR, VIDEO_DIR, TEX_DIR,
TEX_IMAGE_DIR, MOBJECT_DIR, IMAGE_MOBJECT_DIR,
STAGED_SCENES_DIR]:
if not os.path.exists(folder):
os.makedirs(folder)
TEX_USE_CTEX = False
TEX_TEXT_TO_REPLACE = "YourTextHere"
TEMPLATE_TEX_FILE = os.path.join(
THIS_DIR, "tex_template.tex" if not TEX_USE_CTEX
else "ctex_template.tex"
)
with open(TEMPLATE_TEX_FILE, "r") as infile:
TEMPLATE_TEXT_FILE_BODY = infile.read()
TEMPLATE_TEX_FILE_BODY = TEMPLATE_TEXT_FILE_BODY.replace(
TEX_TEXT_TO_REPLACE,
"\\begin{align*}\n" + TEX_TEXT_TO_REPLACE + "\n\\end{align*}",
)
FFMPEG_BIN = "ffmpeg" FFMPEG_BIN = "ffmpeg"
# Colors # Colors
COLOR_MAP = { COLOR_MAP = {
"DARK_BLUE": "#236B8E", "DARK_BLUE": "#236B8E",
"DARK_BROWN": "#8B4513", "DARK_BROWN": "#8B4513",
@ -202,12 +229,11 @@ locals().update(COLOR_MAP)
for name in [s for s in list(COLOR_MAP.keys()) if s.endswith("_C")]: for name in [s for s in list(COLOR_MAP.keys()) if s.endswith("_C")]:
locals()[name.replace("_C", "")] = locals()[name] locals()[name.replace("_C", "")] = locals()[name]
# Streaming related configurations # Streaming related configuration
IS_LIVE_STREAMING = False
LIVE_STREAM_NAME = "LiveStream" LIVE_STREAM_NAME = "LiveStream"
IS_STREAMING_TO_TWITCH = False
TWITCH_STREAM_KEY = "YOUR_STREAM_KEY" TWITCH_STREAM_KEY = "YOUR_STREAM_KEY"
STREAMING_PROTOCOL = "tcp" STREAMING_PROTOCOL = "tcp"
STREAMING_IP = "127.0.0.1" STREAMING_IP = "127.0.0.1"
STREAMING_PORT = "2000" STREAMING_PORT = "2000"
STREAMING_CLIENT = "ffplay" STREAMING_CLIENT = "ffplay"
STREAMING_URL = f"{STREAMING_PROTOCOL}://{STREAMING_IP}:{STREAMING_PORT}?listen"

View file

@ -1,173 +1,18 @@
# !/usr/bin/env python2 # !/usr/bin/env python
import sys import constants
import argparse
# import imp
import importlib import importlib
import inspect import inspect
import itertools as it import itertools as it
import os import os
import subprocess as sp import subprocess as sp
import sys
import traceback import traceback
from constants import *
from scene.scene import Scene from scene.scene import Scene
from utils.sounds import play_error_sound from utils.sounds import play_error_sound
from utils.sounds import play_finish_sound from utils.sounds import play_finish_sound
from colour import Color
HELP_MESSAGE = """
Usage:
python extract_scene.py <module> [<scene name>]
-p preview in low quality
-s show and save picture of last frame
-w write result to file [this is default if nothing else is stated]
-o <file_name> write to a different file_name
-l use low quality
-m use medium quality
-a run and save every scene in the script, or all args for the given scene
-q don't print progress
-f when writing to a movie file, export the frames in png sequence
-t use transperency when exporting images
-n specify the number of the animation to start from
-r specify a resolution
-c specify a background color
"""
SCENE_NOT_FOUND_MESSAGE = """
That scene is not in the script
"""
CHOOSE_NUMBER_MESSAGE = """
Choose number corresponding to desired scene/arguments.
(Use comma separated list for multiple entries)
Choice(s): """
INVALID_NUMBER_MESSAGE = "Fine then, if you don't want to give a valid number I'll just quit"
NO_SCENE_MESSAGE = """
There are no scenes inside that module
"""
def get_configuration():
try:
parser = argparse.ArgumentParser()
parser.add_argument(
"file", help="path to file holding the python code for the scene"
)
parser.add_argument(
"scene_name", help="Name of the Scene class you want to see"
)
optional_args = [
("-p", "--preview"),
("-w", "--write_to_movie"),
("-s", "--show_last_frame"),
("-l", "--low_quality"),
("-m", "--medium_quality"),
("-g", "--save_pngs"),
("-f", "--show_file_in_finder"),
("-t", "--transparent"),
("-q", "--quiet"),
("-a", "--write_all")
]
for short_arg, long_arg in optional_args:
parser.add_argument(short_arg, long_arg, action="store_true")
parser.add_argument("-o", "--output_name")
parser.add_argument("-n", "--start_at_animation_number")
parser.add_argument("-r", "--resolution")
parser.add_argument("-c", "--color")
args = parser.parse_args()
if args.output_name is not None:
output_name_root, output_name_ext = os.path.splitext(
args.output_name)
expected_ext = '.png' if args.show_last_frame else '.mp4'
if output_name_ext not in ['', expected_ext]:
print("WARNING: The output will be to (doubly-dotted) %s%s" %
output_name_root % expected_ext)
output_name = args.output_name
else:
# If anyone wants .mp4.mp4 and is surprised to only get .mp4, or such... Well, too bad.
output_name = output_name_root
else:
output_name = args.output_name
except argparse.ArgumentError as err:
print(str(err))
sys.exit(2)
config = {
"file": args.file,
"scene_name": args.scene_name,
"open_video_upon_completion": args.preview,
"show_file_in_finder": args.show_file_in_finder,
# By default, write to file
"write_to_movie": args.write_to_movie or not args.show_last_frame,
"show_last_frame": args.show_last_frame,
"save_pngs": args.save_pngs,
# If -t is passed in (for transparent), this will be RGBA
"saved_image_mode": "RGBA" if args.transparent else "RGB",
"movie_file_extension": ".mov" if args.transparent else ".mp4",
"quiet": args.quiet or args.write_all,
"ignore_waits": args.preview,
"write_all": args.write_all,
"output_name": output_name,
"start_at_animation_number": args.start_at_animation_number,
"end_at_animation_number": None,
}
# Camera configuration
config["camera_config"] = {}
if args.low_quality:
config["camera_config"].update(LOW_QUALITY_CAMERA_CONFIG)
config["frame_duration"] = LOW_QUALITY_FRAME_DURATION
elif args.medium_quality:
config["camera_config"].update(MEDIUM_QUALITY_CAMERA_CONFIG)
config["frame_duration"] = MEDIUM_QUALITY_FRAME_DURATION
else:
config["camera_config"].update(PRODUCTION_QUALITY_CAMERA_CONFIG)
config["frame_duration"] = PRODUCTION_QUALITY_FRAME_DURATION
# If the resolution was passed in via -r
if args.resolution:
if "," in args.resolution:
height_str, width_str = args.resolution.split(",")
height = int(height_str)
width = int(width_str)
else:
height = int(args.resolution)
width = int(16 * height / 9)
config["camera_config"].update({
"pixel_height": height,
"pixel_width": width,
})
if args.color:
try:
config["camera_config"]["background_color"] = Color(args.color)
except AttributeError as err:
print("Please use a valid color")
print(err)
sys.exit(2)
# If rendering a transparent image/move, make sure the
# scene has a background opacity of 0
if args.transparent:
config["camera_config"]["background_opacity"] = 0
# Arguments related to skipping
stan = config["start_at_animation_number"]
if stan is not None:
if "," in stan:
start, end = stan.split(",")
config["start_at_animation_number"] = int(start)
config["end_at_animation_number"] = int(end)
else:
config["start_at_animation_number"] = int(stan)
config["skip_animations"] = any([
config["show_last_frame"] and not config["write_to_movie"],
config["start_at_animation_number"],
])
return config
def handle_scene(scene, **config): def handle_scene(scene, **config):
import platform import platform
@ -223,26 +68,31 @@ def prompt_user_for_choice(name_to_obj):
print("%d: %s" % (count, name)) print("%d: %s" % (count, name))
num_to_name[count] = name num_to_name[count] = name
try: try:
user_input = input(CHOOSE_NUMBER_MESSAGE) user_input = input(constants.CHOOSE_NUMBER_MESSAGE)
return [ return [
name_to_obj[num_to_name[int(num_str)]] name_to_obj[num_to_name[int(num_str)]]
for num_str in user_input.split(",") for num_str in user_input.split(",")
] ]
except: except KeyError:
print(INVALID_NUMBER_MESSAGE) print(constants.INVALID_NUMBER_MESSAGE)
sys.exit() sys.exit()
user_input = input(constants.CHOOSE_NUMBER_MESSAGE)
return [
name_to_obj[num_to_name[int(num_str)]]
for num_str in user_input.split(",")
]
def get_scene_classes(scene_names_to_classes, config): def get_scene_classes(scene_names_to_classes, config):
if len(scene_names_to_classes) == 0: if len(scene_names_to_classes) == 0:
print(NO_SCENE_MESSAGE) print(constants.NO_SCENE_MESSAGE)
return [] return []
if len(scene_names_to_classes) == 1: if len(scene_names_to_classes) == 1:
return list(scene_names_to_classes.values()) return list(scene_names_to_classes.values())
if config["scene_name"] in scene_names_to_classes: if config["scene_name"] in scene_names_to_classes:
return [scene_names_to_classes[config["scene_name"]]] return [scene_names_to_classes[config["scene_name"]]]
if config["scene_name"] != "": if config["scene_name"] != "":
print(SCENE_NOT_FOUND_MESSAGE) print(constants.SCENE_NOT_FOUND_MESSAGE)
return [] return []
if config["write_all"]: if config["write_all"]:
return list(scene_names_to_classes.values()) return list(scene_names_to_classes.values())
@ -254,16 +104,10 @@ def get_module(file_name):
return importlib.import_module(module_name) return importlib.import_module(module_name)
def main(): def main(config):
config = get_configuration()
module = get_module(config["file"]) module = get_module(config["file"])
scene_names_to_classes = dict(inspect.getmembers(module, is_scene)) scene_names_to_classes = dict(inspect.getmembers(module, is_scene))
# config["output_directory"] = os.path.join(
# VIDEO_DIR,
# config["file"].replace(".py", "")
# )
scene_kwargs = dict([ scene_kwargs = dict([
(key, config[key]) (key, config[key])
for key in [ for key in [
@ -288,7 +132,7 @@ def main():
try: try:
handle_scene(SceneClass(**scene_kwargs), **config) handle_scene(SceneClass(**scene_kwargs), **config)
play_finish_sound() play_finish_sound()
except: except Exception:
print("\n\n") print("\n\n")
traceback.print_exc() traceback.print_exc()
print("\n\n") print("\n\n")

View file

@ -1,31 +1,11 @@
#!/usr/bin/python3 #!/usr/bin/env python
import config
import extract_scene
import stream_starter
from constants import * args = config.parse_cli()
from scene.scene import Scene if not args.livestream:
config = config.get_configuration(args)
extract_scene.main(config)
class Manim(): else:
stream_starter.start_livestream(args.to_twitch)
def __new__(cls):
kwargs = {
"scene_name": LIVE_STREAM_NAME,
"open_video_upon_completion": False,
"show_file_in_finder": False,
# By default, write to file
"write_to_movie": True,
"show_last_frame": False,
"save_pngs": False,
# If -t is passed in (for transparent), this will be RGBA
"saved_image_mode": "RGB",
"movie_file_extension": ".mp4",
"quiet": True,
"ignore_waits": False,
"write_all": False,
"name": LIVE_STREAM_NAME,
"start_at_animation_number": 0,
"end_at_animation_number": None,
"skip_animations": False,
"camera_config": HIGH_QUALITY_CAMERA_CONFIG,
"frame_duration": MEDIUM_QUALITY_FRAME_DURATION,
}
return Scene(**kwargs)

View file

@ -51,6 +51,8 @@ class Scene(Container):
"random_seed": 0, "random_seed": 0,
"start_at_animation_number": None, "start_at_animation_number": None,
"end_at_animation_number": None, "end_at_animation_number": None,
"livestreaming": False,
"to_twitch": False,
} }
def __init__(self, **kwargs): def __init__(self, **kwargs):
@ -76,7 +78,7 @@ class Scene(Container):
self.setup() self.setup()
if self.write_to_movie: if self.write_to_movie:
self.open_movie_pipe() self.open_movie_pipe()
if IS_LIVE_STREAMING: if self.livestreaming:
return None return None
try: try:
self.construct(*self.construct_args) self.construct(*self.construct_args)
@ -464,7 +466,7 @@ class Scene(Container):
raise EndSceneEarlyException() raise EndSceneEarlyException()
def play(self, *args, **kwargs): def play(self, *args, **kwargs):
if IS_LIVE_STREAMING: if self.livestreaming:
self.stream_lock = False self.stream_lock = False
if len(args) == 0: if len(args) == 0:
warnings.warn("Called Scene.play with no animations") warnings.warn("Called Scene.play with no animations")
@ -503,7 +505,7 @@ class Scene(Container):
self.continual_update(0) self.continual_update(0)
self.num_plays += 1 self.num_plays += 1
if IS_LIVE_STREAMING: if self.livestreaming:
self.stream_lock = True self.stream_lock = True
thread.start_new_thread(self.idle_stream, ()) thread.start_new_thread(self.idle_stream, ())
@ -653,8 +655,8 @@ class Scene(Container):
'-vcodec', 'libx264', '-vcodec', 'libx264',
'-pix_fmt', 'yuv420p', '-pix_fmt', 'yuv420p',
] ]
if IS_LIVE_STREAMING: if self.livestreaming:
if IS_STREAMING_TO_TWITCH: if self.to_twitch:
command += ['-f', 'flv'] command += ['-f', 'flv']
command += ['rtmp://live.twitch.tv/app/' + TWITCH_STREAM_KEY] command += ['rtmp://live.twitch.tv/app/' + TWITCH_STREAM_KEY]
else: else:
@ -668,7 +670,7 @@ class Scene(Container):
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()
if IS_LIVE_STREAMING: if self.livestreaming:
return True return True
if os.name == 'nt': if os.name == 'nt':
shutil.move(*self.args_to_rename_file) shutil.move(*self.args_to_rename_file)

View file

@ -1,13 +1,52 @@
from big_ol_pile_of_manim_imports import * from scene.scene import Scene
import subprocess
from time import sleep from time import sleep
from manim import Manim import code
import constants
import os
import subprocess
if not IS_STREAMING_TO_TWITCH:
def start_livestream(to_twitch=False):
class Manim():
def __new__(cls):
kwargs = {
"scene_name": constants.LIVE_STREAM_NAME,
"open_video_upon_completion": False,
"show_file_in_finder": False,
# By default, write to file
"write_to_movie": True,
"show_last_frame": False,
"save_pngs": False,
# If -t is passed in (for transparent), this will be RGBA
"saved_image_mode": "RGB",
"movie_file_extension": ".mp4",
"quiet": True,
"ignore_waits": False,
"write_all": False,
"name": constants.LIVE_STREAM_NAME,
"start_at_animation_number": 0,
"end_at_animation_number": None,
"skip_animations": False,
"camera_config": constants.HIGH_QUALITY_CAMERA_CONFIG,
"frame_duration": constants.MEDIUM_QUALITY_FRAME_DURATION,
"livestreaming": True,
"to_twitch": to_twitch,
}
return Scene(**kwargs)
if not to_twitch:
FNULL = open(os.devnull, 'w') FNULL = open(os.devnull, 'w')
subprocess.Popen([STREAMING_CLIENT, STREAMING_PROTOCOL + '://' + STREAMING_IP + ':' + STREAMING_PORT + '?listen'], stdout=FNULL, stderr=FNULL) subprocess.Popen(
[constants.STREAMING_CLIENT, constants.STREAMING_URL],
stdout=FNULL,
stderr=FNULL)
sleep(3) sleep(3)
manim = Manim() manim = Manim()
print("YOUR STREAM IS READY!")
print("YOUR STREAM IS READY!") variables = globals().copy()
variables.update(locals())
shell = code.InteractiveConsole(variables)
shell.push("from big_ol_pile_of_manim_imports import *")
shell.interact()