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
|
### 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.
|
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
|
## Contributing
|
||||||
Is always welcome. In particular, there is a dire need for tests and documentation.
|
Is always welcome. In particular, there is a dire need for tests and documentation.
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
All files in the directory `from_3b1b`, which by and large generate the visuals for 3b1b videos, are copyright 3Blue1Brown.
|
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__":
|
if __name__ == "__main__":
|
||||||
manimlib.main()
|
manimlib.main()
|
||||||
else:
|
|
||||||
manimlib.stream_starter.start_livestream()
|
|
||||||
|
|
|
@ -2,17 +2,10 @@
|
||||||
import manimlib.config
|
import manimlib.config
|
||||||
import manimlib.constants
|
import manimlib.constants
|
||||||
import manimlib.extract_scene
|
import manimlib.extract_scene
|
||||||
import manimlib.stream_starter
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
args = manimlib.config.parse_cli()
|
args = manimlib.config.parse_cli()
|
||||||
if not args.livestream:
|
config = manimlib.config.get_configuration(args)
|
||||||
config = manimlib.config.get_configuration(args)
|
manimlib.constants.initialize_directories(config)
|
||||||
manimlib.constants.initialize_directories(config)
|
manimlib.extract_scene.main(config)
|
||||||
manimlib.extract_scene.main(config)
|
|
||||||
else:
|
|
||||||
manimlib.stream_starter.start_livestream(
|
|
||||||
to_twitch=args.to_twitch,
|
|
||||||
twitch_key=args.twitch_key,
|
|
||||||
)
|
|
||||||
|
|
|
@ -129,35 +129,7 @@ def parse_cli():
|
||||||
"--tex_dir",
|
"--tex_dir",
|
||||||
help="directory to write tex",
|
help="directory to write tex",
|
||||||
)
|
)
|
||||||
|
return parser.parse_args()
|
||||||
# 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
|
|
||||||
except argparse.ArgumentError as err:
|
except argparse.ArgumentError as err:
|
||||||
print(str(err))
|
print(str(err))
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
|
@ -552,17 +552,6 @@ class Scene(Container):
|
||||||
self.update_frame(ignore_skipping=True)
|
self.update_frame(ignore_skipping=True)
|
||||||
self.get_image().show()
|
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):
|
class EndSceneEarlyException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -28,9 +28,6 @@ class SceneFileWriter(object):
|
||||||
"save_last_frame": False,
|
"save_last_frame": False,
|
||||||
"movie_file_extension": ".mp4",
|
"movie_file_extension": ".mp4",
|
||||||
"gif_file_extension": ".gif",
|
"gif_file_extension": ".gif",
|
||||||
"livestreaming": False,
|
|
||||||
"to_twitch": False,
|
|
||||||
"twitch_key": None,
|
|
||||||
# Previous output_file_name
|
# Previous output_file_name
|
||||||
# TODO, address this in extract_scene et. al.
|
# TODO, address this in extract_scene et. al.
|
||||||
"file_name": None,
|
"file_name": None,
|
||||||
|
@ -171,15 +168,10 @@ class SceneFileWriter(object):
|
||||||
def begin_animation(self, allow_write=False):
|
def begin_animation(self, allow_write=False):
|
||||||
if self.write_to_movie and allow_write:
|
if self.write_to_movie and allow_write:
|
||||||
self.open_movie_pipe()
|
self.open_movie_pipe()
|
||||||
if self.livestreaming:
|
|
||||||
self.stream_lock = False
|
|
||||||
|
|
||||||
def end_animation(self, allow_write=False):
|
def end_animation(self, allow_write=False):
|
||||||
if self.write_to_movie and allow_write:
|
if self.write_to_movie and allow_write:
|
||||||
self.close_movie_pipe()
|
self.close_movie_pipe()
|
||||||
if self.livestreaming:
|
|
||||||
self.stream_lock = True
|
|
||||||
thread.start_new_thread(self.idle_stream, ())
|
|
||||||
|
|
||||||
def write_frame(self, frame):
|
def write_frame(self, frame):
|
||||||
if self.write_to_movie:
|
if self.write_to_movie:
|
||||||
|
@ -247,22 +239,12 @@ class SceneFileWriter(object):
|
||||||
'-vcodec', 'libx264',
|
'-vcodec', 'libx264',
|
||||||
'-pix_fmt', 'yuv420p',
|
'-pix_fmt', 'yuv420p',
|
||||||
]
|
]
|
||||||
if self.livestreaming:
|
command += [temp_file_path]
|
||||||
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]
|
|
||||||
self.writing_process = subprocess.Popen(command, stdin=subprocess.PIPE)
|
self.writing_process = subprocess.Popen(command, stdin=subprocess.PIPE)
|
||||||
|
|
||||||
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 self.livestreaming:
|
|
||||||
return True
|
|
||||||
shutil.move(
|
shutil.move(
|
||||||
self.temp_partial_movie_file_path,
|
self.temp_partial_movie_file_path,
|
||||||
self.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