mirror of
https://github.com/jeertmans/manim-slides.git
synced 2025-07-18 08:53:57 +08:00
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:
@ -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
|
- Added the `playback-rate` and `reversed-playback-rate` options
|
||||||
to slide config.
|
to slide config.
|
||||||
[#320](https://github.com/jeertmans/manim-slides/pull/320)
|
[#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)=
|
(v5.1-modified)=
|
||||||
### Modified
|
### Modified
|
||||||
|
@ -144,6 +144,7 @@ class BaseSlideConfig(BaseModel): # type: ignore
|
|||||||
auto_next: bool = False
|
auto_next: bool = False
|
||||||
playback_rate: float = 1.0
|
playback_rate: float = 1.0
|
||||||
reversed_playback_rate: float = 1.0
|
reversed_playback_rate: float = 1.0
|
||||||
|
notes: str = ""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def wrapper(cls, arg_name: str) -> Callable[..., Any]:
|
def wrapper(cls, arg_name: str) -> Callable[..., Any]:
|
||||||
|
@ -406,9 +406,16 @@ class RevealJS(Converter):
|
|||||||
options = self.dict()
|
options = self.dict()
|
||||||
options["assets_dir"] = assets_dir
|
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(
|
content = revealjs_template.render(
|
||||||
file_to_data_uri=file_to_data_uri,
|
file_to_data_uri=file_to_data_uri,
|
||||||
get_duration_ms=get_duration_ms,
|
get_duration_ms=get_duration_ms,
|
||||||
|
has_notes=has_notes,
|
||||||
**options,
|
**options,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ from PySide6.QtCore import Qt, QUrl, Signal, Slot
|
|||||||
from PySide6.QtGui import QCloseEvent, QIcon, QKeyEvent, QScreen
|
from PySide6.QtGui import QCloseEvent, QIcon, QKeyEvent, QScreen
|
||||||
from PySide6.QtMultimedia import QMediaPlayer
|
from PySide6.QtMultimedia import QMediaPlayer
|
||||||
from PySide6.QtMultimediaWidgets import QVideoWidget
|
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 ..config import Config, PresentationConfig, SlideConfig
|
||||||
from ..logger import logger
|
from ..logger import logger
|
||||||
@ -18,17 +18,25 @@ class Info(QDialog): # type: ignore[misc]
|
|||||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
layout = QGridLayout()
|
main_layout = QVBoxLayout()
|
||||||
|
labels_layout = QGridLayout()
|
||||||
|
notes_layout = QVBoxLayout()
|
||||||
self.scene_label = QLabel()
|
self.scene_label = QLabel()
|
||||||
self.slide_label = QLabel()
|
self.slide_label = QLabel()
|
||||||
|
self.slide_notes = QLabel("")
|
||||||
|
self.slide_notes.setWordWrap(True)
|
||||||
|
|
||||||
layout.addWidget(QLabel("Scene:"), 1, 1)
|
labels_layout.addWidget(QLabel("Scene:"), 1, 1)
|
||||||
layout.addWidget(QLabel("Slide:"), 2, 1)
|
labels_layout.addWidget(QLabel("Slide:"), 2, 1)
|
||||||
layout.addWidget(self.scene_label, 1, 2)
|
labels_layout.addWidget(self.scene_label, 1, 2)
|
||||||
layout.addWidget(self.slide_label, 2, 2)
|
labels_layout.addWidget(self.slide_label, 2, 2)
|
||||||
self.setLayout(layout)
|
|
||||||
self.setFixedWidth(150)
|
notes_layout.addWidget(self.slide_notes)
|
||||||
self.setFixedHeight(80)
|
|
||||||
|
main_layout.addLayout(labels_layout)
|
||||||
|
main_layout.addLayout(notes_layout)
|
||||||
|
|
||||||
|
self.setLayout(main_layout)
|
||||||
|
|
||||||
if parent := self.parent():
|
if parent := self.parent():
|
||||||
self.closeEvent = parent.closeEvent
|
self.closeEvent = parent.closeEvent
|
||||||
@ -312,6 +320,7 @@ class Player(QMainWindow): # type: ignore[misc]
|
|||||||
index = self.current_slide_index
|
index = self.current_slide_index
|
||||||
count = self.current_slides_count
|
count = self.current_slides_count
|
||||||
self.info.slide_label.setText(f"{index+1:4d}/{count:4<d}")
|
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:
|
def show(self) -> None:
|
||||||
super().show()
|
super().show()
|
||||||
|
@ -288,6 +288,11 @@ class BaseSlide:
|
|||||||
Playback rate at which the reversed video is played.
|
Playback rate at which the reversed video is played.
|
||||||
|
|
||||||
Note that this is only supported by ``manim-slides present``.
|
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:
|
:param kwargs:
|
||||||
Keyword arguments to be passed to
|
Keyword arguments to be passed to
|
||||||
:meth:`Scene.next_section<manim.scene.scene.Scene.next_section>`,
|
:meth:`Scene.next_section<manim.scene.scene.Scene.next_section>`,
|
||||||
@ -372,6 +377,29 @@ class BaseSlide:
|
|||||||
self.next_slide()
|
self.next_slide()
|
||||||
|
|
||||||
self.wipe(square)
|
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._current_animation > self._start_animation:
|
||||||
if self.wait_time_between_slides > 0.0:
|
if self.wait_time_between_slides > 0.0:
|
||||||
|
@ -40,6 +40,9 @@
|
|||||||
{% if slide_config.auto_next -%}
|
{% if slide_config.auto_next -%}
|
||||||
data-autoslide="{{ get_duration_ms(slide_config.file) }}"
|
data-autoslide="{{ get_duration_ms(slide_config.file) }}"
|
||||||
{%- endif -%}>
|
{%- endif -%}>
|
||||||
|
{% if slide_config.notes != "" -%}
|
||||||
|
<aside class="notes">{{ slide_config.notes }}</aside>
|
||||||
|
{%- endif %}
|
||||||
</section>
|
</section>
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
@ -50,9 +53,16 @@
|
|||||||
|
|
||||||
<!-- To include plugins, see: https://revealjs.com/plugins/ -->
|
<!-- 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 src="index.js"></script> -->
|
||||||
<script>
|
<script>
|
||||||
Reveal.initialize({
|
Reveal.initialize({
|
||||||
|
{% if has_notes -%}
|
||||||
|
plugins: [ RevealNotes ],
|
||||||
|
{%- endif %}
|
||||||
// The "normal" size of the presentation, aspect ratio will
|
// The "normal" size of the presentation, aspect ratio will
|
||||||
// be preserved when the presentation is scaled to fit different
|
// be preserved when the presentation is scaled to fit different
|
||||||
// resolutions. Can be specified using percentage units.
|
// resolutions. Can be specified using percentage units.
|
||||||
|
@ -196,6 +196,34 @@ class TestSlide:
|
|||||||
|
|
||||||
assert self._base_slide_config.playback_rate == 2.0
|
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
|
@assert_constructs
|
||||||
class TestWipe(Slide):
|
class TestWipe(Slide):
|
||||||
def construct(self) -> None:
|
def construct(self) -> None:
|
||||||
|
Reference in New Issue
Block a user