Merge pull request #374 from eulertour/config_refactor

Refactor into manim, config, rendering, and constants modules.
This commit is contained in:
Devin Neal 2018-12-23 12:54:07 -08:00 committed by GitHub
commit 458c6a5aca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 340 additions and 271 deletions

View file

@ -15,7 +15,7 @@ Manim runs on python 3.7. You can install the python requirements with
git clone https://github.com/3b1b/manim.git
cd manim
pip install -r requirements.txt
python3 extract_scene.py example_scenes.py SquareToCircle -pl
python3 -m manim example_scenes.py SquareToCircle -pl
```
### Using `virtualenv` and `virtualenvwrapper`
@ -23,7 +23,7 @@ After installing `virtualenv` and `virtualenvwrapper`
```sh
git clone https://github.com/3b1b/manim.git
mkvirtualenv -a manim -r requirements.txt manim
python3 extract_scene.py example_scenes.py SquareToCircle -pl
python3 -m manim example_scenes.py SquareToCircle -pl
```
### Using Docker
@ -41,14 +41,14 @@ The image does not contain a copy of the repo. This is intentional, as it allows
4. Render an animation
```sh
cd manim
python3 extract_scene.py example_scenes.py SquareToCircle -l
python3 -m manim example_scenes.py SquareToCircle -l
```
Note that the image doesn't have any development tools installed and can't preview animations. Its purpose is building and testing only.
## Using manim
Try running the following:
```sh
python3 extract_scene.py example_scenes.py SquareToCircle -pl
python3 -m manim example_scenes.py SquareToCircle -pl
```
The -p is for previewing, meaning the the video file will automatically open when it is done rendering.
Use -l for a faster rendering at a lower quality.
@ -69,18 +69,24 @@ Documentation is in progress at [manim.readthedocs.io](https://manim.readthedocs
Todd Zimmerman put together a [tutorial](https://talkingphysics.wordpress.com/2018/06/11/learning-how-to-animate-videos-using-manim-series-a-journey/) on getting started with manim, but it uses an outdated version that runs on python 2.7. It may not be fully compatible with the current version of manim, but it does a good job laying out the basics.
### Live Streaming
To live stream your animations, simply assign `IS_LIVE_STREAMING = True` in `constants.py` file and from your Python Interactive Shell (`python3`) import the stream starter with `from stream_starter import *` while under the project directory. This will provide a clean interactive shell to enter your commands. `manim` object is a `Manim()` instance so as soon as you play an animation with `manim.play()` your stream will start. A video player will pop-up and you can broadcast that video using [OBS Studio](https://obsproject.com/) which is the most practical way of streaming with this math animation library. An example:
To live stream your animations, simply run manim with the `--livestream` option.
```
>>> from stream_starter import *
YOUR STREAM IS READY!
>>> circle = Circle()
>>> manim.play(ShowCreation(circle))
Animation 0: ShowCreationCircle: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 60/60 [00:01<00:00, 37.30it/s]
<scene.scene.Scene object at 0x7f0756d5a8d0>
```sh
> python -m manim --livestream
Writing to media/videos/scene/scene/1080p30/LiveStreamTemp.mp4
Manim is now running in streaming mode. Stream animations by passing
them to manim.play(), e.g.
>>> c = Circle()
>>> manim.play(ShowCreation(c))
>>>
```
It is also possible to stream directly to Twitch. To do that simply assign `IS_STREAMING_TO_TWITCH = True` in `constants.py` file and put your Twitch Stream Key to `TWITCH_STREAM_KEY = "YOUR_STREAM_KEY"` and when you follow the above example the stream will directly start on your Twitch channel(with no audio support).
It is also possible to stream directly to Twitch. To do that simply pass
--livestream and --to-twitch to manim and specify the stream key with
--with-key. Then when you follow the above example the stream will directly
start on your Twitch channel (with no audio support).
## Contributing

161
config.py Normal file
View file

@ -0,0 +1,161 @@
#!/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",
)
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")
module_location.add_argument(
"--livestream",
action="store_true",
help="Run in streaming mode",
)
parser.add_argument(
"--to-twitch",
action="store_true",
help="Stream to twitch",
)
parser.add_argument(
"--with-key",
dest="twitch_key",
help="Stream key for twitch",
)
args = parser.parse_args()
if args.file is None and not args.livestream:
parser.print_help()
sys.exit(2)
if args.to_twitch and not args.livestream:
print("You must run in streaming mode in order to stream to twitch")
sys.exit(2)
if args.to_twitch and args.twitch_key is None:
print("Specify the twitch stream key with --with-key")
sys.exit(2)
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 numpy as np
# Initialize directories
env_MEDIA_DIR = os.getenv("MEDIA_DIR")
if env_MEDIA_DIR:
MEDIA_DIR = env_MEDIA_DIR
@ -12,17 +13,78 @@ else:
os.path.expanduser('~'),
"Dropbox (3Blue1Brown)/3Blue1Brown Team Folder"
)
if not os.path.isdir(MEDIA_DIR):
MEDIA_DIR = "media"
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."
)
with open("media_dir.txt", 'w') as media_file:
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
MEDIUM_QUALITY_FRAME_DURATION = 1. / 30
PRODUCTION_QUALITY_FRAME_DURATION = 1. / 60
@ -102,44 +164,9 @@ PI = np.pi
TAU = 2 * PI
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"
# Colors
COLOR_MAP = {
"DARK_BLUE": "#236B8E",
"DARK_BROWN": "#8B4513",
@ -202,12 +229,17 @@ locals().update(COLOR_MAP)
for name in [s for s in list(COLOR_MAP.keys()) if s.endswith("_C")]:
locals()[name.replace("_C", "")] = locals()[name]
# Streaming related configurations
IS_LIVE_STREAMING = False
# Streaming related configuration
LIVE_STREAM_NAME = "LiveStream"
IS_STREAMING_TO_TWITCH = False
TWITCH_STREAM_KEY = "YOUR_STREAM_KEY"
STREAMING_PROTOCOL = "tcp"
STREAMING_IP = "127.0.0.1"
STREAMING_PORT = "2000"
STREAMING_CLIENT = "ffplay"
STREAMING_URL = f"{STREAMING_PROTOCOL}://{STREAMING_IP}:{STREAMING_PORT}?listen"
STREAMING_CONSOLE_BANNER = """
Manim is now running in streaming mode. Stream animations by passing
them to manim.play(), e.g.
>>> c = Circle()
>>> manim.play(ShowCreation(c))
"""

View file

@ -1,173 +1,18 @@
# !/usr/bin/env python2
# !/usr/bin/env python
import sys
import argparse
# import imp
import constants
import importlib
import inspect
import itertools as it
import os
import subprocess as sp
import sys
import traceback
from constants import *
from scene.scene import Scene
from utils.sounds import play_error_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):
import platform
@ -223,26 +68,31 @@ def prompt_user_for_choice(name_to_obj):
print("%d: %s" % (count, name))
num_to_name[count] = name
try:
user_input = input(CHOOSE_NUMBER_MESSAGE)
user_input = input(constants.CHOOSE_NUMBER_MESSAGE)
return [
name_to_obj[num_to_name[int(num_str)]]
for num_str in user_input.split(",")
]
except:
print(INVALID_NUMBER_MESSAGE)
except KeyError:
print(constants.INVALID_NUMBER_MESSAGE)
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):
if len(scene_names_to_classes) == 0:
print(NO_SCENE_MESSAGE)
print(constants.NO_SCENE_MESSAGE)
return []
if len(scene_names_to_classes) == 1:
return list(scene_names_to_classes.values())
if config["scene_name"] in scene_names_to_classes:
return [scene_names_to_classes[config["scene_name"]]]
if config["scene_name"] != "":
print(SCENE_NOT_FOUND_MESSAGE)
print(constants.SCENE_NOT_FOUND_MESSAGE)
return []
if config["write_all"]:
return list(scene_names_to_classes.values())
@ -254,16 +104,10 @@ def get_module(file_name):
return importlib.import_module(module_name)
def main():
config = get_configuration()
def main(config):
module = get_module(config["file"])
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([
(key, config[key])
for key in [
@ -288,7 +132,7 @@ def main():
try:
handle_scene(SceneClass(**scene_kwargs), **config)
play_finish_sound()
except:
except Exception:
print("\n\n")
traceback.print_exc()
print("\n\n")

View file

@ -1,31 +1,14 @@
#!/usr/bin/python3
#!/usr/bin/env python
import config
import extract_scene
import stream_starter
from constants import *
from scene.scene import Scene
class Manim():
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)
args = config.parse_cli()
if not args.livestream:
config = config.get_configuration(args)
extract_scene.main(config)
else:
stream_starter.start_livestream(
to_twitch=args.to_twitch,
twitch_key=args.twitch_key,
)

View file

@ -51,6 +51,9 @@ class Scene(Container):
"random_seed": 0,
"start_at_animation_number": None,
"end_at_animation_number": None,
"livestreaming": False,
"to_twitch": False,
"twitch_key": None,
}
def __init__(self, **kwargs):
@ -76,7 +79,7 @@ class Scene(Container):
self.setup()
if self.write_to_movie:
self.open_movie_pipe()
if IS_LIVE_STREAMING:
if self.livestreaming:
return None
try:
self.construct(*self.construct_args)
@ -464,7 +467,7 @@ class Scene(Container):
raise EndSceneEarlyException()
def play(self, *args, **kwargs):
if IS_LIVE_STREAMING:
if self.livestreaming:
self.stream_lock = False
if len(args) == 0:
warnings.warn("Called Scene.play with no animations")
@ -503,7 +506,7 @@ class Scene(Container):
self.continual_update(0)
self.num_plays += 1
if IS_LIVE_STREAMING:
if self.livestreaming:
self.stream_lock = True
thread.start_new_thread(self.idle_stream, ())
@ -653,10 +656,10 @@ class Scene(Container):
'-vcodec', 'libx264',
'-pix_fmt', 'yuv420p',
]
if IS_LIVE_STREAMING:
if IS_STREAMING_TO_TWITCH:
if self.livestreaming:
if self.to_twitch:
command += ['-f', 'flv']
command += ['rtmp://live.twitch.tv/app/' + TWITCH_STREAM_KEY]
command += ['rtmp://live.twitch.tv/app/' + self.twitch_key]
else:
command += ['-f', 'mpegts']
command += [STREAMING_PROTOCOL + '://' + STREAMING_IP + ':' + STREAMING_PORT]
@ -668,7 +671,7 @@ class Scene(Container):
def close_movie_pipe(self):
self.writing_process.stdin.close()
self.writing_process.wait()
if IS_LIVE_STREAMING:
if self.livestreaming:
return True
if os.name == 'nt':
shutil.move(*self.args_to_rename_file)

View file

@ -1,13 +1,53 @@
from big_ol_pile_of_manim_imports import *
import subprocess
from scene.scene import Scene
from time import sleep
from manim import Manim
import code
import constants
import os
import readline
import subprocess
if not IS_STREAMING_TO_TWITCH:
FNULL = open(os.devnull, 'w')
subprocess.Popen([STREAMING_CLIENT, STREAMING_PROTOCOL + '://' + STREAMING_IP + ':' + STREAMING_PORT + '?listen'], stdout=FNULL, stderr=FNULL)
sleep(3)
manim = Manim()
def start_livestream(to_twitch=False, twitch_key=None):
class Manim():
print("YOUR STREAM IS READY!")
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,
"twitch_key": twitch_key,
}
return Scene(**kwargs)
if not to_twitch:
FNULL = open(os.devnull, 'w')
subprocess.Popen(
[constants.STREAMING_CLIENT, constants.STREAMING_URL],
stdout=FNULL,
stderr=FNULL)
sleep(3)
variables = globals().copy()
variables.update(locals())
shell = code.InteractiveConsole(variables)
shell.push("manim = Manim()")
shell.push("from big_ol_pile_of_manim_imports import *")
shell.interact(banner=constants.STREAMING_CONSOLE_BANNER)