Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 5 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions manim_slides/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down
7 changes: 7 additions & 0 deletions manim_slides/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,9 +406,16 @@ def convert_to(self, dest: Path) -> None:
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,
)

Expand Down
27 changes: 18 additions & 9 deletions manim_slides/present/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
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
Expand All @@ -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
Expand Down Expand Up @@ -312,6 +320,7 @@ def slide_changed_callback(self) -> None:
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()
Expand Down
28 changes: 28 additions & 0 deletions manim_slides/slide/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,11 @@ def next_slide(
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>`,
Expand Down Expand Up @@ -372,6 +377,29 @@ def construct(self):
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:
Expand Down
10 changes: 10 additions & 0 deletions manim_slides/templates/revealjs.html
Original file line number Diff line number Diff line change
Expand Up @@ -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 -%}
Expand All @@ -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.
Expand Down
28 changes: 28 additions & 0 deletions tests/test_slide.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,34 @@ def construct(self) -> None:

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:
Expand Down
Loading