Skip to content

Commit

Permalink
Merge pull request #6 from jeertmans/manimgl
Browse files Browse the repository at this point in the history
feat: add support for manimgl
  • Loading branch information
jeertmans authored Sep 10, 2022
2 parents c53e410 + cda304f commit ed1b2eb
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 27 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ __pycache__/
slides/

.manim-slides.json

videos/

images/
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
![PyPI - Downloads](https://img.shields.io/pypi/dm/manim-slides)
# Manim Slides

Tool for live presentations extending [manim-community](https://www.manim.community/)'s capabilities. Currently, support for 3b1b's manim is not planned.
Tool for live presentations using either [manim-community](https://www.manim.community/) or [manimgl](https://3b1b.github.io/manim/). `manim-slides` will automatically detect the one you are using!

> **_NOTE:_** This project is a fork of [`manim-presentation`](https://github.com/galatolofederico/manim-presentation). Since the project seemed to be inactive, I decided to create my own fork to deploy new features more rapidly.
> **_NOTE:_** This project extends to work of [`manim-presentation`](https://github.com/galatolofederico/manim-presentation), with a lot more features!
## Install

Expand All @@ -29,6 +29,7 @@ call `self.pause()` when you want to pause the playback and wait for an input to
Wrap a series of animations between `self.start_loop()` and `self.stop_loop()` when you want to loop them (until input to continue):
```python
from manim import *
# or: from manimlib import *
from manim_slides import Slide

class Example(Slide):
Expand Down Expand Up @@ -76,7 +77,7 @@ You can run the **configuration wizard** with:
manim-slides wizard
```

Alternatively you can specify different keybindings creating a file named `.manim-slides.json` with the keys: `QUIT` `CONTINUE` `BACK` `REWIND` and `PLAY_PAUSE`.
Alternatively you can specify different keybindings creating a file named `.manim-slides.json` with the keys: `QUIT` `CONTINUE` `BACK` `REVERSE` `REWIND` and `PLAY_PAUSE`.

A default file can be created with:
```
Expand All @@ -96,11 +97,15 @@ cd manim-slides
Install `manim` and `manim-slides`:
```
pip install manim manim-slides
# or
pip install manimgl manim-slides
```

Render the example scene:
```
manim -qh example.py
manim -qh example.py Example
# or
manimgl --hd example.py Example
```

Run the presentation
Expand All @@ -125,6 +130,7 @@ Here are a few things that I implemented (or that I'm planning to implement) on
- [x] Config file path can be manually set
- [x] Play animation in reverse [#9](https://github.com/galatolofederico/manim-presentation/issues/9)
- [x] Handle 3D scenes out of the box
- [x] Support for both `manim` and `manimgl` modules
- [ ] Generate docs online
- [x] Fix the quality problem on Windows platforms with `fullscreen` flag

Expand Down
4 changes: 3 additions & 1 deletion example.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from manim import *
# If you want to use manimgl, uncomment change
# manim to manimlib
from manimlib import *

from manim_slides import Slide, ThreeDSlide

Expand Down
45 changes: 45 additions & 0 deletions manim_slides/manim.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import sys
from importlib.util import find_spec

MANIM_PACKAGE_NAME = "manim"
MANIM_AVAILABLE = find_spec(MANIM_PACKAGE_NAME) is not None
MANIM_IMPORTED = MANIM_PACKAGE_NAME in sys.modules

MANIMGL_PACKAGE_NAME = "manimlib"
MANIMGL_AVAILABLE = find_spec(MANIMGL_PACKAGE_NAME) is not None
MANIMGL_IMPORTED = MANIMGL_PACKAGE_NAME in sys.modules

if MANIM_IMPORTED and MANIMGL_IMPORTED:
from manim import logger

logger.warn(
"Both manim and manimgl are installed, therefore `manim-slide` needs to need which one to use. Please only import one of the two modules so that `manim-slide` knows which one to use. Here, manim is used by default"
)
MANIM = True
MANIMGL = False
elif MANIM_AVAILABLE and not MANIMGL_IMPORTED:
MANIM = True
MANIMGL = False
elif MANIMGL_AVAILABLE:
MANIM = False
MANIMGL = True
else:
raise ImportError(
"Either manim (community) or manimgl (3b1b) package must be installed"
)


FFMPEG_BIN = None

if MANIMGL:
from manimlib import Scene, ThreeDScene, config
from manimlib.constants import FFMPEG_BIN
from manimlib.logger import log as logger

else:
from manim import Scene, ThreeDScene, config, logger

try: # For manim<v0.16.0.post0
from manim.constants import FFMPEG_BIN as FFMPEG_BIN
except ImportError:
FFMPEG_BIN = config.ffmpeg_executable
12 changes: 8 additions & 4 deletions manim_slides/present.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,25 +377,29 @@ def value_proc(value: str):
indices = list(map(int, value.strip().replace(" ", "").split(",")))

if not all(map(lambda i: 0 < i <= len(scene_choices), indices)):
raise ValueError("Please only enter numbers displayed on the screen.")
raise click.UsageError(
"Please only enter numbers displayed on the screen."
)

return [scene_choices[i] for i in indices]

if len(scene_choices) == 0:
raise ValueError("No scenes were found, are you in the correct directory?")
raise click.UsageError(
"No scenes were found, are you in the correct directory?"
)

while True:
try:
scenes = click.prompt("Choice(s)", value_proc=value_proc)
break
except ValueError as e:
click.secho(e, fg="red")
raise click.UsageError(e)

presentations = list()
for scene in scenes:
config_file = os.path.join(folder, f"{scene}.json")
if not os.path.exists(config_file):
raise Exception(
raise click.UsageError(
f"File {config_file} does not exist, check the scene name and make sure to use Slide as your scene base class"
)
config = json.load(open(config_file))
Expand Down
85 changes: 67 additions & 18 deletions manim_slides/slide.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,10 @@
import shutil
import subprocess

from manim import Scene, ThreeDScene, config, logger
from tqdm import tqdm

try: # For manim<v0.16.0.post0
from manim.constants import FFMPEG_BIN as ffmpeg_executable
except ImportError:
ffmpeg_executable = config.ffmpeg_executable

from .defaults import FOLDER_PATH
from .manim import FFMPEG_BIN, MANIMGL, Scene, ThreeDScene, config, logger


def reverse_video_path(src: str) -> str:
Expand All @@ -21,21 +16,62 @@ def reverse_video_path(src: str) -> str:


def reverse_video_file(src: str, dst: str):
command = [config.ffmpeg_executable, "-i", src, "-vf", "reverse", dst]
command = [FFMPEG_BIN, "-i", src, "-vf", "reverse", dst]
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
process.communicate()


class Slide(Scene):
def __init__(self, *args, output_folder=FOLDER_PATH, **kwargs):
if MANIMGL:
if not os.path.isdir("videos"):
os.mkdir("videos")
kwargs["file_writer_config"] = {
"break_into_partial_movies": True,
"output_directory": "",
"write_to_movie": True,
}

kwargs["preview"] = False

super().__init__(*args, **kwargs)

self.output_folder = output_folder
self.slides = list()
self.current_slide = 1
self.current_animation = 0
self.loop_start_animation = None
self.pause_start_animation = 0

@property
def partial_movie_files(self):
if MANIMGL:
from manimlib.utils.file_ops import get_sorted_integer_files

kwargs = {
"remove_non_integer_files": True,
"extension": self.file_writer.movie_file_extension,
}
return get_sorted_integer_files(
self.file_writer.partial_movie_directory, **kwargs
)
else:
return self.renderer.file_writer.partial_movie_files

@property
def show_progress_bar(self):
if MANIMGL:
return getattr(super(Scene, self), "show_progress_bar", True)
else:
return config["progress_bar"] != "none"

@property
def leave_progress_bar(self):
if MANIMGL:
return getattr(super(Scene, self), "leave_progress_bars", False)
else:
return config["progress_bar"] == "leave"

def play(self, *args, **kwargs):
super().play(*args, **kwargs)
self.current_animation += 1
Expand Down Expand Up @@ -72,14 +108,7 @@ def end_loop(self):
self.loop_start_animation = None
self.pause_start_animation = self.current_animation

def render(self, *args, **kwargs):
# We need to disable the caching limit since we rely on intermidiate files
max_files_cached = config["max_files_cached"]
config["max_files_cached"] = float("inf")

super().render(*args, **kwargs)

config["max_files_cached"] = max_files_cached
def save_slides(self, use_cache=True):

if not os.path.exists(self.output_folder):
os.mkdir(self.output_folder)
Expand All @@ -95,16 +124,19 @@ def render(self, *args, **kwargs):

if not os.path.exists(scene_files_folder):
os.mkdir(scene_files_folder)
elif not use_cache:
shutil.rmtree(scene_files_folder)
os.mkdir(scene_files_folder)
else:
old_animation_files.update(os.listdir(scene_files_folder))

files = list()
for src_file in tqdm(
self.renderer.file_writer.partial_movie_files,
self.partial_movie_files,
desc=f"Copying animation files to '{scene_files_folder}' and generating reversed animations",
leave=config["progress_bar"] == "leave",
leave=self.leave_progress_bar,
ascii=True if platform.system() == "Windows" else None,
disable=config["progress_bar"] == "none",
disable=not self.show_progress_bar,
):
filename = os.path.basename(src_file)
_hash, ext = os.path.splitext(filename)
Expand Down Expand Up @@ -140,6 +172,23 @@ def render(self, *args, **kwargs):
f"Slide '{scene_name}' configuration written in '{os.path.abspath(slide_path)}'"
)

def run(self, *args, **kwargs):
"""MANIMGL renderer"""
super().run(*args, **kwargs)
self.save_slides(use_cache=False)

def render(self, *args, **kwargs):
"""MANIM render"""
# We need to disable the caching limit since we rely on intermidiate files
max_files_cached = config["max_files_cached"]
config["max_files_cached"] = float("inf")

super().render(*args, **kwargs)

config["max_files_cached"] = max_files_cached

self.save_slides()


class ThreeDSlide(Slide, ThreeDScene):
pass

0 comments on commit ed1b2eb

Please sign in to comment.