mirror of
https://github.com/jeertmans/manim-slides.git
synced 2025-05-28 19:26:55 +08:00
WIP: reversing videos, etc.
This commit is contained in:
@ -3,7 +3,7 @@
|
|||||||

|

|
||||||
# Manim Slides
|
# Manim Slides
|
||||||
|
|
||||||
Tool for live presentations using either [manim](http://3b1b.github.io/manim/) or [manim-community](https://www.manim.community/).
|
Tool for live presentations using either [manim-community](https://www.manim.community/). Currently, support for 3b1b's manim is not planned.
|
||||||
|
|
||||||
> **_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 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.
|
||||||
|
|
||||||
@ -124,7 +124,6 @@ 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] Config file path can be manually set
|
||||||
- [ ] Play animation in reverse [#9](https://github.com/galatolofederico/manim-presentation/issues/9)
|
- [ ] Play animation in reverse [#9](https://github.com/galatolofederico/manim-presentation/issues/9)
|
||||||
- [x] Handle 3D scenes out of the box
|
- [x] Handle 3D scenes out of the box
|
||||||
- [ ] Can work with both community and 3b1b versions (not tested)
|
|
||||||
- [ ] Generate docs online
|
- [ ] Generate docs online
|
||||||
- [ ] Fix the quality problem on Windows platforms with `fullscreen` flag
|
- [ ] Fix the quality problem on Windows platforms with `fullscreen` flag
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import numpy as np
|
|||||||
from .commons import config_path_option
|
from .commons import config_path_option
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .defaults import CONFIG_PATH, FOLDER_PATH
|
from .defaults import CONFIG_PATH, FOLDER_PATH
|
||||||
|
from .slide import reverse_video_path
|
||||||
|
|
||||||
|
|
||||||
@unique
|
@unique
|
||||||
@ -41,7 +42,7 @@ class Presentation:
|
|||||||
def __init__(self, config, last_frame_next: bool = False):
|
def __init__(self, config, last_frame_next: bool = False):
|
||||||
self.last_frame_next = last_frame_next
|
self.last_frame_next = last_frame_next
|
||||||
self.slides = config["slides"]
|
self.slides = config["slides"]
|
||||||
self.files = config["files"]
|
self.files = [reverse_video_path(path) for path in config["files"]]
|
||||||
|
|
||||||
self.lastframe = []
|
self.lastframe = []
|
||||||
|
|
||||||
@ -79,6 +80,9 @@ class Presentation:
|
|||||||
self.current_slide_i = max(0, self.current_slide_i - 1)
|
self.current_slide_i = max(0, self.current_slide_i - 1)
|
||||||
self.rewind_slide()
|
self.rewind_slide()
|
||||||
|
|
||||||
|
def reserve_slide(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def rewind_slide(self):
|
def rewind_slide(self):
|
||||||
self.current_animation = self.current_slide["start_animation"]
|
self.current_animation = self.current_slide["start_animation"]
|
||||||
self.current_cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
|
self.current_cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
|
||||||
@ -292,8 +296,28 @@ class Display:
|
|||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.argument("scenes", nargs=-1)
|
@click.option(
|
||||||
|
"--folder",
|
||||||
|
default=FOLDER_PATH,
|
||||||
|
type=click.Path(exists=True, file_okay=False),
|
||||||
|
help="Set slides folder.",
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
@click.help_option("-h", "--help")
|
||||||
|
def list_scenes(folder):
|
||||||
|
scenes = []
|
||||||
|
|
||||||
|
for file in os.listdir(folder):
|
||||||
|
if file.endswith(".json"):
|
||||||
|
scenes.append(os.path.basename(file)[:-4])
|
||||||
|
|
||||||
|
return scenes
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.option("--scenes", nargs=-1, prompt=True)
|
||||||
@config_path_option
|
@config_path_option
|
||||||
@click.option(
|
@click.option(
|
||||||
"--folder",
|
"--folder",
|
||||||
@ -312,6 +336,20 @@ class Display:
|
|||||||
def present(scenes, config_path, folder, start_paused, fullscreen, last_frame_next):
|
def present(scenes, config_path, folder, start_paused, fullscreen, last_frame_next):
|
||||||
"""Present the different scenes."""
|
"""Present the different scenes."""
|
||||||
|
|
||||||
|
if len(scenes) == 0:
|
||||||
|
print("ICI")
|
||||||
|
scene_choices = list_scenes(folder)
|
||||||
|
|
||||||
|
scene_choices = dict(enumerate(scene_choices, start=1))
|
||||||
|
choices = [str(i) for i in scene_choices.keys()]
|
||||||
|
|
||||||
|
def value_proc(value: str):
|
||||||
|
raise ValueError("Value:")
|
||||||
|
|
||||||
|
print(scene_choices)
|
||||||
|
|
||||||
|
scenes = click.prompt("Choose a scene", value_proc=value_proc)
|
||||||
|
|
||||||
presentations = list()
|
presentations = list()
|
||||||
for scene in scenes:
|
for scene in scenes:
|
||||||
config_file = os.path.join(folder, f"{scene}.json")
|
config_file = os.path.join(folder, f"{scene}.json")
|
||||||
|
@ -1,12 +1,31 @@
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import platform
|
||||||
import shutil
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
|
||||||
from manim import Scene, ThreeDScene, config
|
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 .defaults import FOLDER_PATH
|
||||||
|
|
||||||
|
|
||||||
|
def reverse_video_path(src: str) -> str:
|
||||||
|
file, ext = os.path.splitext(src)
|
||||||
|
return f"{file}_reversed{ext}"
|
||||||
|
|
||||||
|
|
||||||
|
def reverse_video_file(src: str, dst: str):
|
||||||
|
command = [config.ffmpeg_executable, "-i", src, "-vf", "reverse", dst]
|
||||||
|
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
|
process.communicate()
|
||||||
|
|
||||||
|
|
||||||
class Slide(Scene):
|
class Slide(Scene):
|
||||||
def __init__(self, *args, output_folder=FOLDER_PATH, **kwargs):
|
def __init__(self, *args, output_folder=FOLDER_PATH, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@ -79,14 +98,31 @@ class Slide(Scene):
|
|||||||
os.mkdir(scene_files_folder)
|
os.mkdir(scene_files_folder)
|
||||||
|
|
||||||
files = list()
|
files = list()
|
||||||
for src_file in self.renderer.file_writer.partial_movie_files:
|
for src_file in tqdm(
|
||||||
|
self.renderer.file_writer.partial_movie_files,
|
||||||
|
desc=f"Copying animation files to '{scene_files_folder}' and generating reversed animations",
|
||||||
|
leave=config["progress_bar"] == "leave",
|
||||||
|
ascii=True if platform.system() == "Windows" else None,
|
||||||
|
disable=config["progress_bar"] == "none",
|
||||||
|
):
|
||||||
dst_file = os.path.join(scene_files_folder, os.path.basename(src_file))
|
dst_file = os.path.join(scene_files_folder, os.path.basename(src_file))
|
||||||
shutil.copyfile(src_file, dst_file)
|
shutil.copyfile(src_file, dst_file)
|
||||||
|
rev_file = reverse_video_path(dst_file)
|
||||||
|
reverse_video_file(src_file, rev_file)
|
||||||
files.append(dst_file)
|
files.append(dst_file)
|
||||||
|
|
||||||
f = open(os.path.join(self.output_folder, "%s.json" % (scene_name,)), "w")
|
logger.info(
|
||||||
|
f"Copied {len(files)} animations to '{os.path.abspath(scene_files_folder)}' and generated reversed animations"
|
||||||
|
)
|
||||||
|
|
||||||
|
slide_path = os.path.join(self.output_folder, "%s.json" % (scene_name,))
|
||||||
|
|
||||||
|
f = open(slide_path, "w")
|
||||||
json.dump(dict(slides=self.slides, files=files), f)
|
json.dump(dict(slides=self.slides, files=files), f)
|
||||||
f.close()
|
f.close()
|
||||||
|
logger.info(
|
||||||
|
f"Slide '{scene_name}' configuration written in '{os.path.abspath(slide_path)}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ThreeDSlide(Slide, ThreeDScene):
|
class ThreeDSlide(Slide, ThreeDScene):
|
||||||
|
5
setup.py
5
setup.py
@ -2,9 +2,7 @@ import sys
|
|||||||
|
|
||||||
import setuptools
|
import setuptools
|
||||||
|
|
||||||
sys.path.append("manim_slides") # To avoid importing manim, which may not be installed
|
from .__version__ import __version__ as version
|
||||||
|
|
||||||
from __version__ import __version__ as version
|
|
||||||
|
|
||||||
if sys.version_info < (3, 7):
|
if sys.version_info < (3, 7):
|
||||||
raise RuntimeError("This package requires Python 3.7+")
|
raise RuntimeError("This package requires Python 3.7+")
|
||||||
@ -31,6 +29,7 @@ setuptools.setup(
|
|||||||
install_requires=[
|
install_requires=[
|
||||||
"click>=8.0",
|
"click>=8.0",
|
||||||
"click-default-group>=1.2",
|
"click-default-group>=1.2",
|
||||||
|
"manim",
|
||||||
"numpy>=1.19.3",
|
"numpy>=1.19.3",
|
||||||
"pydantic>=1.9.1",
|
"pydantic>=1.9.1",
|
||||||
"opencv-python>=4.6",
|
"opencv-python>=4.6",
|
||||||
|
Reference in New Issue
Block a user