chore(lib): simplify how to add config options (#321)

* chore(lib): simplify how to add config options

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

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

* chore(lint): some fixes

---------

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 15:38:49 +01:00
committed by GitHub
parent eb8efa8e3d
commit b09a000c17
7 changed files with 111 additions and 57 deletions

View File

@ -22,6 +22,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
to slide config.
[#320](https://github.com/jeertmans/manim-slides/pull/320)
(v5.1-modified)=
### Modified
- Modified the internal logic to simplify adding configuration options.
[#321](https://github.com/jeertmans/manim-slides/pull/321)
## [v5](https://github.com/jeertmans/manim-slides/compare/v4.16.0...v5.0.0)
Prior to v5, there was no real CHANGELOG other than the GitHub releases,

View File

@ -1,5 +1,7 @@
import json
import shutil
from functools import wraps
from inspect import Parameter, signature
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Set, Tuple
@ -135,14 +137,76 @@ class Config(BaseModel): # type: ignore[misc]
return self
class PreSlideConfig(BaseModel): # type: ignore
start_animation: int
end_animation: int
class BaseSlideConfig(BaseModel): # type: ignore
"""Base class for slide config."""
loop: bool = False
auto_next: bool = False
playback_rate: float = 1.0
reversed_playback_rate: float = 1.0
@classmethod
def wrapper(cls, arg_name: str) -> Callable[..., Any]:
"""
Wrap a function to transform keyword argument into an instance of this class.
The function signature is updated to reflect the new keyword-only arguments.
The wrapped function must follow two criteria:
- its last parameter must be ``**kwargs`` (or equivalent);
- and its second last parameter must be ``<arg_name>``.
"""
def _wrapper_(fun: Callable[..., Any]) -> Callable[..., Any]:
@wraps(fun)
def __wrapper__(*args: Any, **kwargs: Any) -> Any: # noqa: N807
fun_kwargs = {
key: value
for key, value in kwargs.items()
if key not in cls.__fields__
}
fun_kwargs[arg_name] = cls(**kwargs)
return fun(*args, **fun_kwargs)
sig = signature(fun)
parameters = list(sig.parameters.values())
parameters[-2:-1] = [
Parameter(
field_name,
Parameter.KEYWORD_ONLY,
default=field_info.default,
annotation=field_info.annotation,
)
for field_name, field_info in cls.__fields__.items()
]
sig = sig.replace(parameters=parameters)
__wrapper__.__signature__ = sig # type: ignore[attr-defined]
return __wrapper__
return _wrapper_
class PreSlideConfig(BaseSlideConfig):
"""Slide config to be used prior to rendering."""
start_animation: int
end_animation: int
@classmethod
def from_base_slide_config_and_animation_indices(
cls,
base_slide_config: BaseSlideConfig,
start_animation: int,
end_animation: int,
) -> "PreSlideConfig":
return cls(
start_animation=start_animation,
end_animation=end_animation,
**base_slide_config.dict(),
)
@field_validator("start_animation", "end_animation")
@classmethod
def index_is_posint(cls, v: int) -> int:
@ -187,26 +251,17 @@ class PreSlideConfig(BaseModel): # type: ignore
return slice(self.start_animation, self.end_animation)
class SlideConfig(BaseModel): # type: ignore[misc]
class SlideConfig(BaseSlideConfig):
"""Slide config to be used after rendering."""
file: FilePath
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(
cls, pre_slide_config: PreSlideConfig, file: Path, rev_file: Path
) -> "SlideConfig":
return cls(
file=file,
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,
)
return cls(file=file, rev_file=rev_file, **pre_slide_config.dict())
class PresentationConfig(BaseModel): # type: ignore[misc]

View File

@ -217,7 +217,9 @@ def start_at_callback(
metavar="RATE",
type=float,
default=1.0,
help="Playback rate of the video slides, see PySide6 docs for details.",
help="Playback rate of the video slides, see PySide6 docs for details. "
" The playback rate of each slide is defined as the product of its default "
" playback rate and the provided value.",
)
@click.option(
"--next-terminates-loop",

View File

@ -104,7 +104,7 @@ class Player(QMainWindow): # type: ignore[misc]
self.media_player = QMediaPlayer(self)
self.media_player.setVideoOutput(self.video_widget)
self.media_player.setPlaybackRate(playback_rate)
self.playback_rate = playback_rate
self.presentation_changed.connect(self.presentation_changed_callback)
self.slide_changed.connect(self.slide_changed_callback)
@ -238,10 +238,12 @@ class Player(QMainWindow): # type: ignore[misc]
if self.playing_reversed_slide:
self.media_player.setPlaybackRate(
self.current_slide_config.reversed_playback_rate
self.current_slide_config.reversed_playback_rate * self.playback_rate
)
else:
self.media_player.setPlaybackRate(self.current_slide_config.playback_rate)
self.media_player.setPlaybackRate(
self.current_slide_config.playback_rate * self.playback_rate
)
if start_paused:
self.media_player.pause()

View File

@ -8,7 +8,7 @@ from typing import Any, List, MutableMapping, Optional, Sequence, Tuple, ValuesV
import numpy as np
from tqdm import tqdm
from ..config import PresentationConfig, PreSlideConfig, SlideConfig
from ..config import BaseSlideConfig, PresentationConfig, PreSlideConfig, SlideConfig
from ..defaults import FFMPEG_BIN, FOLDER_PATH
from ..logger import logger
from ..utils import concatenate_video_files, merge_basenames, reverse_video_file
@ -29,7 +29,7 @@ class BaseSlide:
super().__init__(*args, **kwargs)
self._output_folder: Path = output_folder
self._slides: List[PreSlideConfig] = []
self._pre_slide_config_kwargs: MutableMapping[str, Any] = {}
self._base_slide_config: BaseSlideConfig = BaseSlideConfig()
self._current_slide = 1
self._current_animation = 0
self._start_animation = 0
@ -254,13 +254,11 @@ class BaseSlide:
super().play(*args, **kwargs) # type: ignore[misc]
self._current_animation += 1
@BaseSlideConfig.wrapper("base_slide_config")
def next_slide(
self,
*,
loop: bool = False,
auto_next: bool = False,
playback_rate: float = 1.0,
reversed_playback_rate: float = 1.0,
base_slide_config: BaseSlideConfig,
**kwargs: Any,
) -> None:
"""
@ -380,21 +378,16 @@ class BaseSlide:
self.wait(self.wait_time_between_slides) # type: ignore[attr-defined]
self._slides.append(
PreSlideConfig(
start_animation=self._start_animation,
end_animation=self._current_animation,
**self._pre_slide_config_kwargs,
PreSlideConfig.from_base_slide_config_and_animation_indices(
self._base_slide_config,
self._start_animation,
self._current_animation,
)
)
self._current_slide += 1
self._pre_slide_config_kwargs = dict(
loop=loop,
auto_next=auto_next,
playback_rate=playback_rate,
reversed_playback_rate=reversed_playback_rate,
)
self._base_slide_config = base_slide_config
self._start_animation = self._current_animation
def _add_last_slide(self) -> None:
@ -406,10 +399,10 @@ class BaseSlide:
return
self._slides.append(
PreSlideConfig(
start_animation=self._start_animation,
end_animation=self._current_animation,
**self._pre_slide_config_kwargs,
PreSlideConfig.from_base_slide_config_and_animation_indices(
self._base_slide_config,
self._start_animation,
self._current_animation,
)
)

View File

@ -3,6 +3,7 @@ from typing import Any, List, Optional, Tuple
from manim import Scene, ThreeDScene, config
from ..config import BaseSlideConfig
from .base import BaseSlide
@ -79,22 +80,17 @@ class Slide(BaseSlide, Scene): # type: ignore[misc]
"""
self.next_slide(*args, **kwargs)
@BaseSlideConfig.wrapper("base_slide_config")
def next_slide(
self,
*args: Any,
loop: bool = False,
auto_next: bool = False,
playback_rate: float = 1.0,
reversed_playback_rate: float = 1.0,
base_slide_config: BaseSlideConfig,
**kwargs: Any,
) -> None:
Scene.next_section(self, *args, **kwargs)
BaseSlide.next_slide(
BaseSlide.next_slide.__wrapped__(
self,
loop=loop,
auto_next=auto_next,
playback_rate=playback_rate,
reversed_playback_rate=reversed_playback_rate,
base_slide_config=base_slide_config,
)
def render(self, *args: Any, **kwargs: Any) -> None:

View File

@ -140,16 +140,16 @@ class TestSlide:
self.add(text)
assert "loop" not in self._pre_slide_config_kwargs
assert not self._base_slide_config.loop
self.next_slide(loop=True)
self.play(text.animate.scale(2))
assert self._pre_slide_config_kwargs["loop"]
assert self._base_slide_config.loop
self.next_slide(loop=False)
assert not self._pre_slide_config_kwargs["loop"]
assert not self._base_slide_config.loop
@assert_constructs
class TestAutoNext(Slide):
@ -158,16 +158,16 @@ class TestSlide:
self.add(text)
assert "auto_next" not in self._pre_slide_config_kwargs
assert not self._base_slide_config.auto_next
self.next_slide(auto_next=True)
self.play(text.animate.scale(2))
assert self._pre_slide_config_kwargs["auto_next"]
assert self._base_slide_config.auto_next
self.next_slide(auto_next=False)
assert not self._pre_slide_config_kwargs["auto_next"]
assert not self._base_slide_config.auto_next
@assert_constructs
class TestLoopAndAutoNextFails(Slide):
@ -189,12 +189,12 @@ class TestSlide:
self.add(text)
assert "playback_rate" not in self._pre_slide_config_kwargs
assert self._base_slide_config.playback_rate == 1.0
self.next_slide(playback_rate=2.0)
self.play(text.animate.scale(2))
assert self._pre_slide_config_kwargs["playback_rate"] == 2.0
assert self._base_slide_config.playback_rate == 2.0
@assert_constructs
class TestWipe(Slide):