mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
commit
1bb2e8c237
7 changed files with 5 additions and 146 deletions
22
README.md
22
README.md
|
@ -124,31 +124,9 @@ Documentation is in progress at [eulertour.com/docs](https://www.eulertour.com/d
|
|||
### Walkthrough
|
||||
Todd Zimmerman put together a [tutorial](https://talkingphysics.wordpress.com/2019/01/08/getting-started-animating-with-manim-and-python-3-7/) on getting started with manim, which has been updated to run on Python 3.7.
|
||||
|
||||
### Live Streaming
|
||||
To live stream your animations, simply run manim with the `--livestream` option.
|
||||
|
||||
```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 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
|
||||
Is always welcome. In particular, there is a dire need for tests and documentation.
|
||||
|
||||
|
||||
## License
|
||||
All files in the directory `from_3b1b`, which by and large generate the visuals for 3b1b videos, are copyright 3Blue1Brown.
|
||||
|
||||
|
|
2
manim.py
2
manim.py
|
@ -3,5 +3,3 @@ import manimlib
|
|||
|
||||
if __name__ == "__main__":
|
||||
manimlib.main()
|
||||
else:
|
||||
manimlib.stream_starter.start_livestream()
|
||||
|
|
|
@ -2,17 +2,10 @@
|
|||
import manimlib.config
|
||||
import manimlib.constants
|
||||
import manimlib.extract_scene
|
||||
import manimlib.stream_starter
|
||||
|
||||
|
||||
def main():
|
||||
args = manimlib.config.parse_cli()
|
||||
if not args.livestream:
|
||||
config = manimlib.config.get_configuration(args)
|
||||
manimlib.constants.initialize_directories(config)
|
||||
manimlib.extract_scene.main(config)
|
||||
else:
|
||||
manimlib.stream_starter.start_livestream(
|
||||
to_twitch=args.to_twitch,
|
||||
twitch_key=args.twitch_key,
|
||||
)
|
||||
config = manimlib.config.get_configuration(args)
|
||||
manimlib.constants.initialize_directories(config)
|
||||
manimlib.extract_scene.main(config)
|
||||
|
|
|
@ -129,35 +129,7 @@ def parse_cli():
|
|||
"--tex_dir",
|
||||
help="directory to write tex",
|
||||
)
|
||||
|
||||
# For live streaming
|
||||
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
|
||||
return parser.parse_args()
|
||||
except argparse.ArgumentError as err:
|
||||
print(str(err))
|
||||
sys.exit(2)
|
||||
|
|
|
@ -552,17 +552,6 @@ class Scene(Container):
|
|||
self.update_frame(ignore_skipping=True)
|
||||
self.get_image().show()
|
||||
|
||||
# TODO, this doesn't belong in Scene, but should be
|
||||
# part of some more specialized subclass optimized
|
||||
# for livestreaming
|
||||
def tex(self, latex):
|
||||
eq = TextMobject(latex)
|
||||
anims = []
|
||||
anims.append(Write(eq))
|
||||
for mobject in self.mobjects:
|
||||
anims.append(ApplyMethod(mobject.shift, 2 * UP))
|
||||
self.play(*anims)
|
||||
|
||||
|
||||
class EndSceneEarlyException(Exception):
|
||||
pass
|
||||
|
|
|
@ -28,9 +28,6 @@ class SceneFileWriter(object):
|
|||
"save_last_frame": False,
|
||||
"movie_file_extension": ".mp4",
|
||||
"gif_file_extension": ".gif",
|
||||
"livestreaming": False,
|
||||
"to_twitch": False,
|
||||
"twitch_key": None,
|
||||
# Previous output_file_name
|
||||
# TODO, address this in extract_scene et. al.
|
||||
"file_name": None,
|
||||
|
@ -171,15 +168,10 @@ class SceneFileWriter(object):
|
|||
def begin_animation(self, allow_write=False):
|
||||
if self.write_to_movie and allow_write:
|
||||
self.open_movie_pipe()
|
||||
if self.livestreaming:
|
||||
self.stream_lock = False
|
||||
|
||||
def end_animation(self, allow_write=False):
|
||||
if self.write_to_movie and allow_write:
|
||||
self.close_movie_pipe()
|
||||
if self.livestreaming:
|
||||
self.stream_lock = True
|
||||
thread.start_new_thread(self.idle_stream, ())
|
||||
|
||||
def write_frame(self, frame):
|
||||
if self.write_to_movie:
|
||||
|
@ -247,22 +239,12 @@ class SceneFileWriter(object):
|
|||
'-vcodec', 'libx264',
|
||||
'-pix_fmt', 'yuv420p',
|
||||
]
|
||||
if self.livestreaming:
|
||||
if self.to_twitch:
|
||||
command += ['-f', 'flv']
|
||||
command += ['rtmp://live.twitch.tv/app/' + self.twitch_key]
|
||||
else:
|
||||
command += ['-f', 'mpegts']
|
||||
command += [STREAMING_PROTOCOL + '://' + STREAMING_IP + ':' + STREAMING_PORT]
|
||||
else:
|
||||
command += [temp_file_path]
|
||||
command += [temp_file_path]
|
||||
self.writing_process = subprocess.Popen(command, stdin=subprocess.PIPE)
|
||||
|
||||
def close_movie_pipe(self):
|
||||
self.writing_process.stdin.close()
|
||||
self.writing_process.wait()
|
||||
if self.livestreaming:
|
||||
return True
|
||||
shutil.move(
|
||||
self.temp_partial_movie_file_path,
|
||||
self.partial_movie_file_path,
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
from time import sleep
|
||||
import code
|
||||
import os
|
||||
import readline
|
||||
import subprocess
|
||||
|
||||
from manimlib.scene.scene import Scene
|
||||
import manimlib.constants
|
||||
|
||||
|
||||
def start_livestream(to_twitch=False, twitch_key=None):
|
||||
class Manim():
|
||||
|
||||
def __new__(cls):
|
||||
kwargs = {
|
||||
"scene_name": manimlib.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": manimlib.constants.LIVE_STREAM_NAME,
|
||||
"start_at_animation_number": 0,
|
||||
"end_at_animation_number": None,
|
||||
"skip_animations": False,
|
||||
"camera_config": manimlib.constants.HIGH_QUALITY_CAMERA_CONFIG,
|
||||
"livestreaming": True,
|
||||
"to_twitch": to_twitch,
|
||||
"twitch_key": twitch_key,
|
||||
}
|
||||
return Scene(**kwargs)
|
||||
|
||||
if not to_twitch:
|
||||
FNULL = open(os.devnull, 'w')
|
||||
subprocess.Popen(
|
||||
[manimlib.constants.STREAMING_CLIENT, manimlib.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 manimlib.imports import *")
|
||||
shell.interact(banner=manimlib.constants.STREAMING_CONSOLE_BANNER)
|
Loading…
Add table
Reference in a new issue