Compare commits

...

19 Commits
v0.1.3 ... v2.0

Author SHA1 Message Date
d1c3e9c075 major improvements, bumping to 2.0 2021-06-18 21:43:08 +02:00
38e03db9e9 updated readme 2021-06-18 21:33:11 +02:00
2eafa0b82e Solves #4 2021-06-18 21:31:10 +02:00
8a4fea687d added show next frame as last frame hack (#7) 2021-06-18 21:26:46 +02:00
128d6718ae automatically got to next Scene 2021-06-18 21:19:16 +02:00
22cbb7ec94 Merge branch 'glatteis-main' 2021-06-18 20:10:12 +02:00
5feb13da10 updated readme 2021-06-18 20:08:50 +02:00
90b2e4d46b modified example 2021-06-18 20:06:31 +02:00
43b9fa7cf7 removed leftover self.play 2021-06-18 20:05:27 +02:00
1cc070db86 Re added add_last_slide becouse it was broken if the last slide was a loop 2021-06-18 20:02:24 +02:00
c5274fb57f pause() displays final frame not pre-final 2021-06-18 14:43:06 +02:00
2a136ed585 Fix pause, loops 2021-06-11 09:10:02 +02:00
c9390f0e59 Fix loops 2021-06-11 08:42:56 +02:00
41de205675 Add fullscreen option 2021-06-05 14:25:29 +02:00
133ec17ebb Fix a lot of stuff in present.py 2021-06-05 14:16:17 +02:00
09199777e0 Support for windows keys and config file as in #4 2021-06-05 13:36:54 +02:00
bfcf7db26e bump to 0.1.5 2021-05-22 12:14:54 +02:00
b6522f4756 bump version to 0.1.4 2021-05-22 12:10:05 +02:00
4216299b39 Fix #3 2021-05-22 12:09:07 +02:00
5 changed files with 111 additions and 42 deletions

View File

@ -46,6 +46,8 @@ class Example(Slide):
self.wait()
```
You **must** end your `Slide` with a `self.play(...)` or a `self.wait(..)`
To start the presentation using `Scene1`, `Scene2` and so on simply run:
```
@ -64,6 +66,10 @@ Default keybindings to control the presentation
| Spacebar | Play/Pause |
| Q | Quit |
You can specify different keybindings creating a file named `manim-presentation.json` with the keys: `QUIT_KEY` `CONTINUE_KEY` `BACK_KEY` `REWIND_KEY` and `PLAYPAUSE_KEY`
`manim-presentation` uses `cv2.waitKeyEx()` to wait for keypresses
## Run Example
Clone this repository

View File

@ -16,4 +16,13 @@ class Example(Slide):
self.play(dot.animate.move_to(ORIGIN))
self.pause()
self.wait()
self.play(dot.animate.move_to(RIGHT*3))
self.pause()
self.start_loop()
self.play(MoveAlongPath(dot, circle), run_time=2, rate_func=linear)
self.end_loop()
# Each slide MUST end with an animation (a self.wait is considered an animation)
self.play(dot.animate.move_to(ORIGIN))

View File

@ -7,13 +7,28 @@ import math
import time
import argparse
from enum import Enum
import platform
class Config:
QUIT_KEY = ord("q")
CONTINUE_KEY = 83 #right arrow
BACK_KEY = 81 #left arrow
REWIND_KEY = ord("r")
PLAYPAUSE_KEY = 32 #spacebar
@classmethod
def init(cls):
if platform.system() == "Windows":
cls.QUIT_KEY = ord("q")
cls.CONTINUE_KEY = 2555904 #right arrow
cls.BACK_KEY = 2424832 #left arrow
cls.REWIND_KEY = ord("r")
cls.PLAYPAUSE_KEY = 32 #spacebar
else:
cls.QUIT_KEY = ord("q")
cls.CONTINUE_KEY = 65363 #right arrow
cls.BACK_KEY = 65361 #left arrow
cls.REWIND_KEY = ord("r")
cls.PLAYPAUSE_KEY = 32 #spacebar
if os.path.exists(os.path.join(os.getcwd(), "./manim-presentation.json")):
json_config = json.load(open(os.path.join(os.getcwd(), "./manim-presentation.json"), "r"))
for key, value in json_config.items():
setattr(cls, key, value)
class State(Enum):
PLAYING = 0
@ -35,44 +50,54 @@ def fix_time(x):
return x if x > 0 else 1
class Presentation:
def __init__(self, config):
def __init__(self, config, last_frame_next=False):
self.last_frame_next = last_frame_next
self.slides = config["slides"]
self.files = config["files"]
self.lastframe = []
self.reset()
self.load_files()
self.add_last_slide()
def reset(self):
self.current_animation = 0
self.current_slide_i = 0
def add_last_slide(self):
last_slide_end = self.slides[-1]["end_animation"]
last_animation = len(self.files) - 1
last_animation = len(self.files)
self.slides.append(dict(
start_animation = last_slide_end,
end_animation = last_animation,
type = "last",
number = len(self.slides) + 1
number = len(self.slides) + 1,
terminated = False
))
def reset(self):
self.current_animation = 0
self.current_slide_i = 0
self.slides[-1]["terminated"] = False
def load_files(self):
self.caps = list()
for f in self.files:
self.caps.append(cv2.VideoCapture(f))
def next(self):
self.current_slide_i = min(len(self.slides) - 1, self.current_slide_i + 1)
self.current_animation = self.current_slide["start_animation"]
if self.current_slide["type"] == "last":
self.current_slide["terminated"] = True
else:
self.current_slide_i = min(len(self.slides) - 1, self.current_slide_i + 1)
self.rewind_slide()
def prev(self):
self.current_slide_i = max(0, self.current_slide_i - 1)
self.current_animation = self.current_slide["start_animation"]
self.rewind_slide()
def rewind(self):
def rewind_slide(self):
self.current_animation = self.current_slide["start_animation"]
self.current_cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
@property
def current_slide(self):
@ -86,30 +111,51 @@ class Presentation:
def fps(self):
return self.current_cap.get(cv2.CAP_PROP_FPS)
def get_frame_and_state(self):
ret, frame = self.current_cap.read()
state = State.PLAYING
if ret:
# This function updates the state given the previous state.
# It does this by reading the video information and checking if the state is still correct.
# It returns the frame to show (lastframe) and the new state.
def update_state(self, state):
if state == State.PAUSED:
if len(self.lastframe) == 0:
_, self.lastframe = self.current_cap.read()
return self.lastframe, state
still_playing, frame = self.current_cap.read()
if still_playing:
self.lastframe = frame
else:
self.current_cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
elif state in [state.WAIT, state.PAUSED]:
return self.lastframe, state
elif self.current_slide["type"] == "last" and self.current_slide["terminated"]:
return self.lastframe, State.END
if not still_playing:
if self.current_slide["end_animation"] == self.current_animation + 1:
if self.current_slide["type"] == "slide":
# To fix "it always ends one frame before the animation", uncomment this.
# But then clears on the next slide will clear the stationary after this slide.
if self.last_frame_next:
self.next_cap = self.caps[self.current_animation + 1]
self.next_cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
_, self.lastframe = self.next_cap.read()
state = State.WAIT
elif self.current_slide["type"] == "loop":
self.current_animation = self.current_slide["start_animation"]
state = State.PLAYING
self.rewind_slide()
elif self.current_slide["type"] == "last":
return self.lastframe, State.END
self.current_slide["terminated"] = True
elif self.current_slide["type"] == "last" and self.current_slide["end_animation"] == self.current_animation:
return self.lastframe, State.END
state = State.WAIT
else:
# Play next video!
self.current_animation += 1
# Reset video to position zero if it has been played before
self.current_cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
return self.lastframe, state
class Display:
def __init__(self, presentations, start_paused=False):
def __init__(self, presentations, start_paused=False, fullscreen=False):
self.presentations = presentations
self.start_paused = start_paused
@ -120,14 +166,18 @@ class Display:
self.lag = 0
self.last_time = now()
if fullscreen:
cv2.namedWindow("Video", cv2.WND_PROP_FULLSCREEN)
cv2.setWindowProperty("Video", cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
@property
def current_presentation(self):
return self.presentations[self.current_presentation_i]
def run(self):
while True:
if self.state == State.PLAYING:
self.lastframe, self.state = self.current_presentation.get_frame_and_state()
self.lastframe, self.state = self.current_presentation.update_state(self.state)
if self.state == State.PLAYING or self.state == State.PAUSED:
if self.start_paused:
self.state = State.PAUSED
self.start_paused = False
@ -137,10 +187,9 @@ class Display:
else:
self.current_presentation_i += 1
self.state = State.PLAYING
self.handle_key()
self.show_video()
self.show_info()
self.handle_key()
def show_video(self):
self.lag = now() - self.last_time
@ -190,7 +239,7 @@ class Display:
def handle_key(self):
sleep_time = math.ceil(1000/self.current_presentation.fps)
key = cv2.waitKey(fix_time(sleep_time - self.lag)) & 0xFF
key = cv2.waitKeyEx(fix_time(sleep_time - self.lag))
if key == Config.QUIT_KEY:
self.quit()
@ -212,7 +261,7 @@ class Display:
self.current_presentation.prev()
self.state = State.PLAYING
elif key == Config.REWIND_KEY:
self.current_presentation.rewind()
self.current_presentation.rewind_slide()
self.state = State.PLAYING
@ -227,9 +276,13 @@ def main():
parser.add_argument("scenes", metavar="scenes", type=str, nargs="+", help="Scenes to present")
parser.add_argument("--folder", type=str, default="./presentation", help="Presentation files folder")
parser.add_argument("--start-paused", action="store_true", help="Start paused")
parser.add_argument("--fullscreen", action="store_true", help="Fullscreen")
parser.add_argument("--last-frame-next", action="store_true", help="Show the next animation first frame as last frame (hack)")
args = parser.parse_args()
args.folder = os.path.normcase(args.folder)
Config.init()
presentations = list()
for scene in args.scenes:
@ -237,9 +290,9 @@ def main():
if not os.path.exists(config_file):
raise Exception(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))
presentations.append(Presentation(config))
presentations.append(Presentation(config, last_frame_next=args.last_frame_next))
display = Display(presentations, start_paused=args.start_paused)
display = Display(presentations, start_paused=args.start_paused, fullscreen=args.fullscreen)
display.run()
if __name__ == "__main__":

View File

@ -2,6 +2,7 @@ import os
import json
import shutil
from manim import Scene, config
from manim.animation.animation import Wait
class Slide(Scene):
def __init__(self, *args, **kwargs):

View File

@ -6,7 +6,7 @@ with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "README.md")
setuptools.setup(
name="manim_presentation",
version="0.1.3",
version="0.2.0",
author="Federico A. Galatolo",
author_email="federico.galatolo@ing.unipi.it",
description="Tool for live presentations using manim",