feat(lib): add support for presenter notes (#322)

* feat(lib): add support for presenter notes

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix(test): typo

* Update test_slide.py

* Update convert.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Jérome Eertmans
2023-11-23 18:20:10 +01:00
committed by GitHub
parent b09a000c17
commit f898dd3054
7 changed files with 94 additions and 9 deletions

View File

@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added the `playback-rate` and `reversed-playback-rate` options
to slide config.
[#320](https://github.com/jeertmans/manim-slides/pull/320)
- Added the speaker notes option.
[#322](https://github.com/jeertmans/manim-slides/pull/322)
(v5.1-modified)=
### Modified

View File

@ -144,6 +144,7 @@ class BaseSlideConfig(BaseModel): # type: ignore
auto_next: bool = False
playback_rate: float = 1.0
reversed_playback_rate: float = 1.0
notes: str = ""
@classmethod
def wrapper(cls, arg_name: str) -> Callable[..., Any]:

View File

@ -406,9 +406,16 @@ class RevealJS(Converter):
options = self.dict()
options["assets_dir"] = assets_dir
has_notes = any(
slide_config.notes != ""
for presentation_config in self.presentation_configs
for slide_config in presentation_config.slides
)
content = revealjs_template.render(
file_to_data_uri=file_to_data_uri,
get_duration_ms=get_duration_ms,
has_notes=has_notes,
**options,
)

View File

@ -5,7 +5,7 @@ from PySide6.QtCore import Qt, QUrl, Signal, Slot
from PySide6.QtGui import QCloseEvent, QIcon, QKeyEvent, QScreen
from PySide6.QtMultimedia import QMediaPlayer
from PySide6.QtMultimediaWidgets import QVideoWidget
from PySide6.QtWidgets import QDialog, QGridLayout, QLabel, QMainWindow
from PySide6.QtWidgets import QDialog, QGridLayout, QLabel, QMainWindow, QVBoxLayout
from ..config import Config, PresentationConfig, SlideConfig
from ..logger import logger
@ -18,17 +18,25 @@ class Info(QDialog): # type: ignore[misc]
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
layout = QGridLayout()
main_layout = QVBoxLayout()
labels_layout = QGridLayout()
notes_layout = QVBoxLayout()
self.scene_label = QLabel()
self.slide_label = QLabel()
self.slide_notes = QLabel("")
self.slide_notes.setWordWrap(True)
layout.addWidget(QLabel("Scene:"), 1, 1)
layout.addWidget(QLabel("Slide:"), 2, 1)
layout.addWidget(self.scene_label, 1, 2)
layout.addWidget(self.slide_label, 2, 2)
self.setLayout(layout)
self.setFixedWidth(150)
self.setFixedHeight(80)
labels_layout.addWidget(QLabel("Scene:"), 1, 1)
labels_layout.addWidget(QLabel("Slide:"), 2, 1)
labels_layout.addWidget(self.scene_label, 1, 2)
labels_layout.addWidget(self.slide_label, 2, 2)
notes_layout.addWidget(self.slide_notes)
main_layout.addLayout(labels_layout)
main_layout.addLayout(notes_layout)
self.setLayout(main_layout)
if parent := self.parent():
self.closeEvent = parent.closeEvent
@ -312,6 +320,7 @@ class Player(QMainWindow): # type: ignore[misc]
index = self.current_slide_index
count = self.current_slides_count
self.info.slide_label.setText(f"{index+1:4d}/{count:4<d}")
self.info.slide_notes.setText(self.current_slide_config.notes)
def show(self) -> None:
super().show()

View File

@ -288,6 +288,11 @@ class BaseSlide:
Playback rate at which the reversed video is played.
Note that this is only supported by ``manim-slides present``.
:param notes:
Presenter notes, in HTML format.
Note that this is only supported by ``manim-slides present``
and ``manim-slides convert --to=html``.
:param kwargs:
Keyword arguments to be passed to
:meth:`Scene.next_section<manim.scene.scene.Scene.next_section>`,
@ -372,6 +377,29 @@ class BaseSlide:
self.next_slide()
self.wipe(square)
The following contains speaker notes. On the webbrowser,
the speaker view can be triggered by pressing :kbd:`S`.
.. manim-slides:: SpeakerNotesExample
from manim import *
from manim_slides import Slide
class SpeakerNotesExample(Slide):
def construct(self):
self.next_slide(notes="Some introduction")
square = Square(color=GREEN, side_length=2)
self.play(GrowFromCenter(square))
self.next_slide(notes="We now rotate the slide")
self.play(Rotate(square, PI / 2))
self.next_slide(notes="Bye bye")
self.zoom(square)
"""
if self._current_animation > self._start_animation:
if self.wait_time_between_slides > 0.0:

View File

@ -40,6 +40,9 @@
{% if slide_config.auto_next -%}
data-autoslide="{{ get_duration_ms(slide_config.file) }}"
{%- endif -%}>
{% if slide_config.notes != "" -%}
<aside class="notes">{{ slide_config.notes }}</aside>
{%- endif %}
</section>
{%- endfor -%}
{%- endfor -%}
@ -50,9 +53,16 @@
<!-- To include plugins, see: https://revealjs.com/plugins/ -->
{% if has_notes -%}
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/{{ reveal_version }}/plugin/notes/notes.min.js"></script>
{%- endif -%}
<!-- <script src="index.js"></script> -->
<script>
Reveal.initialize({
{% if has_notes -%}
plugins: [ RevealNotes ],
{%- endif %}
// The "normal" size of the presentation, aspect ratio will
// be preserved when the presentation is scaled to fit different
// resolutions. Can be specified using percentage units.

View File

@ -196,6 +196,34 @@ class TestSlide:
assert self._base_slide_config.playback_rate == 2.0
@assert_constructs
class TestReversedPlaybackRate(Slide):
def construct(self) -> None:
text = Text("Some text")
self.add(text)
assert self._base_slide_config.reversed_playback_rate == 1.0
self.next_slide(reversed_playback_rate=2.0)
self.play(text.animate.scale(2))
assert self._base_slide_config.reversed_playback_rate == 2.0
@assert_constructs
class TestNotes(Slide):
def construct(self) -> None:
text = Text("Some text")
self.add(text)
assert self._base_slide_config.notes == ""
self.next_slide(notes="test")
self.play(text.animate.scale(2))
assert self._base_slide_config.notes == "test"
@assert_constructs
class TestWipe(Slide):
def construct(self) -> None: