From f898dd30543f6f4be4d6ea40afd06edcfebe0e3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9rome=20Eertmans?= Date: Thu, 23 Nov 2023 18:20:10 +0100 Subject: [PATCH] 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> --- CHANGELOG.md | 2 ++ manim_slides/config.py | 1 + manim_slides/convert.py | 7 +++++++ manim_slides/present/player.py | 27 ++++++++++++++++++--------- manim_slides/slide/base.py | 28 ++++++++++++++++++++++++++++ manim_slides/templates/revealjs.html | 10 ++++++++++ tests/test_slide.py | 28 ++++++++++++++++++++++++++++ 7 files changed, 94 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bd38ca0..e9e613b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/manim_slides/config.py b/manim_slides/config.py index fffe5379..dd3e3921 100644 --- a/manim_slides/config.py +++ b/manim_slides/config.py @@ -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]: diff --git a/manim_slides/convert.py b/manim_slides/convert.py index 47ff6066..d7d0384c 100644 --- a/manim_slides/convert.py +++ b/manim_slides/convert.py @@ -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, ) diff --git a/manim_slides/present/player.py b/manim_slides/present/player.py index f784007b..52d22bb0 100644 --- a/manim_slides/present/player.py +++ b/manim_slides/present/player.py @@ -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 @@ -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 @@ 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 None: super().show() diff --git a/manim_slides/slide/base.py b/manim_slides/slide/base.py index 08755985..5f5ca63a 100644 --- a/manim_slides/slide/base.py +++ b/manim_slides/slide/base.py @@ -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`, @@ -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: diff --git a/manim_slides/templates/revealjs.html b/manim_slides/templates/revealjs.html index f59f6906..7d357af0 100644 --- a/manim_slides/templates/revealjs.html +++ b/manim_slides/templates/revealjs.html @@ -40,6 +40,9 @@ {% if slide_config.auto_next -%} data-autoslide="{{ get_duration_ms(slide_config.file) }}" {%- endif -%}> + {% if slide_config.notes != "" -%} + + {%- endif %} {%- endfor -%} {%- endfor -%} @@ -50,9 +53,16 @@ + {% if has_notes -%} + + {%- endif -%} +