diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ed30d8d..b691c651 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added the `manim-slides render` command to render slides using correct Manim installation. [#317](https://github.com/jeertmans/manim-slides/pull/317) +- Added the `playback-rate` and `reversed-playback-rate` options + to slide config. + [#320](https://github.com/jeertmans/manim-slides/pull/320) ## [v5](https://github.com/jeertmans/manim-slides/compare/v4.16.0...v5.0.0) diff --git a/manim_slides/__main__.py b/manim_slides/__main__.py index 7013b020..90932379 100644 --- a/manim_slides/__main__.py +++ b/manim_slides/__main__.py @@ -34,9 +34,7 @@ def cli(notify_outdated_version: bool) -> None: manim_info_url = "https://pypi.org/pypi/manim-slides/json" warn_prompt = "Cannot check if latest release of Manim Slides is installed" try: - req_info: requests.models.Response = requests.get( - manim_info_url, timeout=2 - ) + req_info: requests.models.Response = requests.get(manim_info_url, timeout=2) req_info.raise_for_status() stable = req_info.json()["info"]["version"] if stable != __version__: diff --git a/manim_slides/config.py b/manim_slides/config.py index cf4917bc..a086d8d8 100644 --- a/manim_slides/config.py +++ b/manim_slides/config.py @@ -140,6 +140,8 @@ class PreSlideConfig(BaseModel): # type: ignore end_animation: int loop: bool = False auto_next: bool = False + playback_rate: float = 1.0 + reversed_playback_rate: float = 1.0 @field_validator("start_animation", "end_animation") @classmethod @@ -190,6 +192,8 @@ class SlideConfig(BaseModel): # type: ignore[misc] rev_file: FilePath loop: bool = False auto_next: bool = False + playback_rate: float = 1.0 + reversed_playback_rate: float = 1.0 @classmethod def from_pre_slide_config_and_files( @@ -200,6 +204,8 @@ def from_pre_slide_config_and_files( rev_file=rev_file, loop=pre_slide_config.loop, auto_next=pre_slide_config.auto_next, + playback_rate=pre_slide_config.playback_rate, + reversed_playback_rate=pre_slide_config.reversed_playback_rate, ) diff --git a/manim_slides/present/player.py b/manim_slides/present/player.py index 42b3ba73..1ea66818 100644 --- a/manim_slides/present/player.py +++ b/manim_slides/present/player.py @@ -236,6 +236,13 @@ def load_current_media(self, start_paused: bool = False) -> None: url = QUrl.fromLocalFile(self.current_file) self.media_player.setSource(url) + if self.playing_reversed_slide: + self.media_player.setPlaybackRate( + self.current_slide_config.reversed_playback_rate + ) + else: + self.media_player.setPlaybackRate(self.current_slide_config.playback_rate) + if start_paused: self.media_player.pause() else: diff --git a/manim_slides/slide/base.py b/manim_slides/slide/base.py index 5ceed97c..ba350ab1 100644 --- a/manim_slides/slide/base.py +++ b/manim_slides/slide/base.py @@ -255,7 +255,13 @@ def play(self, *args: Any, **kwargs: Any) -> None: self._current_animation += 1 def next_slide( - self, *, loop: bool = False, auto_next: bool = False, **kwargs: Any + self, + *, + loop: bool = False, + auto_next: bool = False, + playback_rate: float = 1.0, + reversed_playback_rate: float = 1.0, + **kwargs: Any, ) -> None: """ Create a new slide with previous animations, and setup options @@ -276,6 +282,14 @@ def next_slide( Note that this is only supported by ``manim-slides present`` and ``manim-slides convert --to=html``. + :param playback_rate: + Playback rate at which the video is played. + + Note that this is only supported by ``manim-slides present``. + :param reversed_playback_rate: + Playback rate at which the reversed video is played. + + Note that this is only supported by ``manim-slides present``. :param kwargs: Keyword arguments to be passed to :meth:`Scene.next_section`, @@ -375,7 +389,12 @@ def construct(self): self._current_slide += 1 - self._pre_slide_config_kwargs = dict(loop=loop, auto_next=auto_next) + self._pre_slide_config_kwargs = dict( + loop=loop, + auto_next=auto_next, + playback_rate=playback_rate, + reversed_playback_rate=reversed_playback_rate, + ) self._start_animation = self._current_animation def _add_last_slide(self) -> None: diff --git a/manim_slides/slide/manim.py b/manim_slides/slide/manim.py index 89ac55b9..78635904 100644 --- a/manim_slides/slide/manim.py +++ b/manim_slides/slide/manim.py @@ -80,10 +80,22 @@ def next_section(self, *args: Any, **kwargs: Any) -> None: self.next_slide(*args, **kwargs) def next_slide( - self, *args: Any, loop: bool = False, auto_next: bool = False, **kwargs: Any + self, + *args: Any, + loop: bool = False, + auto_next: bool = False, + playback_rate: float = 1.0, + reversed_playback_rate: float = 1.0, + **kwargs: Any, ) -> None: Scene.next_section(self, *args, **kwargs) - BaseSlide.next_slide(self, loop=loop, auto_next=auto_next) + BaseSlide.next_slide( + self, + loop=loop, + auto_next=auto_next, + playback_rate=playback_rate, + reversed_playback_rate=reversed_playback_rate, + ) def render(self, *args: Any, **kwargs: Any) -> None: """MANIM render.""" diff --git a/tests/test_slide.py b/tests/test_slide.py index 93e4c1a8..c36c3cb9 100644 --- a/tests/test_slide.py +++ b/tests/test_slide.py @@ -182,6 +182,20 @@ def construct(self) -> None: with pytest.raises(ValidationError): self.next_slide() + @assert_constructs + class TestPlaybackRate(Slide): + def construct(self) -> None: + text = Text("Some text") + + self.add(text) + + assert "playback_rate" not in self._pre_slide_config_kwargs + + self.next_slide(playback_rate=2.0) + self.play(text.animate.scale(2)) + + assert self._pre_slide_config_kwargs["playback_rate"] == 2.0 + @assert_constructs class TestWipe(Slide): def construct(self) -> None: