chore(dev): move to Rye instead of PDM (#420)

* test: re-add opencv-python

* chore(dev): move to Rye instead of PDM

* try fix

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

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

* fix: build backend

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

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

* fix: string quotes?

* small fixes

* upgrade typing

* fix(ci): rye install on Windows

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

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

* fix(ci): typos

* fix

* fix(ci): actually use right python version

* fix(deps): manimgl

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

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

* fix docs

* another fix

* cleanup

* make sure to use trusted publisher

* chore(docs): remove PDM

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Jérome Eertmans
2024-04-18 22:12:45 +02:00
committed by GitHub
parent bd04dae2bf
commit d5d1513d94
31 changed files with 965 additions and 3677 deletions

View File

@ -18,15 +18,22 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install PDM - name: Setup Rye
uses: pdm-project/setup-pdm@v4 env:
with: RYE_INSTALL_OPTION: --yes
python-version: '3.10' run: |
cache: true curl -sSf https://rye-up.com/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
- name: Configure Rye
run: rye config --set-bool behavior.use-uv=true
- name: Build package
run: rye build
- name: Publish to PyPI - name: Publish to PyPI
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
run: pdm publish uses: pypa/gh-action-pypi-publish@release/v1
publish-docker: publish-docker:
name: Publish Docker image name: Publish Docker image

View File

@ -25,28 +25,30 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install PDM - name: Setup Rye
uses: pdm-project/setup-pdm@v4 if: matrix.os != 'windows-latest'
with: env:
python-version: ${{ matrix.pyversion }} RYE_TOOLCHAIN_VERSION: ${{ matrix.pyversion}}
cache: true RYE_INSTALL_OPTION: --yes
# Path related stuff
- name: Append to Path on MacOS
if: matrix.os == 'macos-latest'
run: | run: |
echo "${HOME}/.local/bin" >> $GITHUB_PATH curl -sSf https://rye-up.com/get | bash
echo "/Users/runner/Library/Python/${{ matrix.pyversion }}/bin" >> $GITHUB_PATH echo "$HOME/.rye/shims" >> $GITHUB_PATH
- name: Append to Path on Ubuntu # Stolen from https://github.com/bluss/pyproject-local-kernel/blob/2b641290694adc998fb6bceea58d3737523a68b7/.github/workflows/ci.yaml
if: matrix.os == 'ubuntu-latest' - name: Install Rye (Windows)
run: echo "${HOME}/.local/bin" >> $GITHUB_PATH
- name: Append to Path on Windows
if: matrix.os == 'windows-latest' if: matrix.os == 'windows-latest'
run: echo "${HOME}/.local/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append shell: bash
run: |
C:/msys64/usr/bin/wget.exe -q 'https://github.com/astral-sh/rye/releases/latest/download/rye-x86_64-windows.exe' -O rye-x86_64-windows.exe
./rye-x86_64-windows.exe self install --toolchain-version ${{ matrix.pyversion }} --modify-path -y
echo "$HOME\\.rye\\shims" >> $GITHUB_PATH
- name: Configure Rye
shell: bash
run: |
rye config --set-bool behavior.use-uv=true
rye pin ${{ matrix.pyversion }}
# OS dependencies
- name: Install manim dependencies on MacOS - name: Install manim dependencies on MacOS
if: matrix.os == 'macos-latest' if: matrix.os == 'macos-latest'
run: brew install ffmpeg py3cairo run: brew install ffmpeg py3cairo
@ -68,16 +70,17 @@ jobs:
uses: ssciwr/setup-mesa-dist-win@v2 uses: ssciwr/setup-mesa-dist-win@v2
- name: Install Manim Slides - name: Install Manim Slides
run: | shell: bash
pdm sync -Ggithub-action -Gtest run: rye sync
- name: Run pytest - name: Run pytest
shell: bash
if: matrix.os != 'ubuntu-latest' || matrix.pyversion != '3.11' if: matrix.os != 'ubuntu-latest' || matrix.pyversion != '3.11'
run: pdm run pytest run: rye run pytest
- name: Run pytest and coverage - name: Run pytest and coverage
if: matrix.os == 'ubuntu-latest' && matrix.pyversion == '3.11' if: matrix.os == 'ubuntu-latest' && matrix.pyversion == '3.11'
run: pdm run pytest --cov-report xml --cov=manim_slides tests/ run: rye run pytest --cov-report xml --cov=manim_slides tests/
- name: Upload to codecov.io - name: Upload to codecov.io
if: matrix.os == 'ubuntu-latest' && matrix.pyversion == '3.11' if: matrix.os == 'ubuntu-latest' && matrix.pyversion == '3.11'

1
.python-version Normal file
View File

@ -0,0 +1 @@
3.11.8

View File

@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#417](https://github.com/jeertmans/manim-slides/pull/417) [#417](https://github.com/jeertmans/manim-slides/pull/417)
- Create FAQ page and clear FAQ from README.md. - Create FAQ page and clear FAQ from README.md.
[#418](https://github.com/jeertmans/manim-slides/pull/418) [#418](https://github.com/jeertmans/manim-slides/pull/418)
- Used Rye instead of PDM for faster development.
[#420](https://github.com/jeertmans/manim-slides/pull/420)
(unreleased-fixed)= (unreleased-fixed)=
### Fixed ### Fixed

View File

@ -24,61 +24,25 @@ the repository, and clone it locally.
As for every Python project, using virtual environment is recommended to avoid As for every Python project, using virtual environment is recommended to avoid
conflicts between modules. conflicts between modules.
For this project, we use [PDM](https://pdm-project.org/) to easily manage project For this project, we use [Rye](https://rye-up.com/) to easily manage project
and development dependencies. If not already, please install this tool. and development dependencies. If not already, please install this tool.
## Installing Python modules ## Installing Python modules
With PDM, installation becomes straightforward: With Rye, installation becomes straightforward:
```bash ```bash
pdm install rye sync --all-features
``` ```
This, however, only installs the minimal set of dependencies to run the package.
If you would like to install Manim or ManimGL,
as documented in the [quickstart](/quickstart),
you can use the `-G|--group` option:
```bash
pdm install -Gmanim # For Manim
# or
pdm install -Gmanimgl # For ManimGL
```
Additionally, Manim Slides comes with groups of dependencies for development purposes:
```bash
pdm install -Gdev # For linters and formatters
# or
pdm install -Gdocs # To build the documentation locally
# or
pdm install -Gtest # To run tests
```
:::{note}
You can combine any number of groups or extras when installing the package locally.
You can also install everything with `pdm install -G:all`.
:::
## Running commands ## Running commands
Because modules are installed in a new Python environment, Because modules are installed in a new Python environment,
you cannot use them directly in the shell. you cannot use them directly in the shell.
Instead, you either need to prepend `pdm run` to any command, e.g.: Instead, you either need to prepend `rye run` to any command, e.g.:
```bash ```bash
pdm run manim-slides wizard rye run manim-slides wizard
```
or [enter a new shell](https://pdm-project.org/latest/usage/venv/#activate-a-virtualenv)
that uses this new Python environment:
```bash
eval $(pdm venv activate) # Click on the link above to see shell-specific command
manim-slides wizard
``` ```
## Testing your code ## Testing your code
@ -87,7 +51,7 @@ Most of the tests are done with GitHub actions, thus not on your computer.
The only command you should run locally is: The only command you should run locally is:
```bash ```bash
pdm run pre-commit run --all-files rye run pre-commit run --all-files
``` ```
This runs a few linter and formatter to make sure the code quality and style stay This runs a few linter and formatter to make sure the code quality and style stay
@ -97,7 +61,7 @@ If a warning or an error is displayed, please fix it before going to next step.
For testing your code, simply run: For testing your code, simply run:
```bash ```bash
pdm run pytest rye run pytest
``` ```
## Building the documentation ## Building the documentation
@ -109,7 +73,7 @@ To generate the documentation, run the following:
```bash ```bash
cd docs cd docs
pdm run make html rye run make html
``` ```
Then, the output index file is located at `docs/build/html/index.html` and Then, the output index file is located at `docs/build/html/index.html` and

View File

@ -106,7 +106,9 @@ Along with the optional dependencies for Manim and ManimGL,
Manim Slides offers additional *extras*, that can be activated Manim Slides offers additional *extras*, that can be activated
using optional dependencies: using optional dependencies:
- `full`, to include `magic`, `manim`, `manimgl`, and - `full`, to include `magic`, `manim`, and
`sphinx-directive` extras (see below);
- `full-gl`, to include `magic`, `manimgl`, and
`sphinx-directive` extras (see below); `sphinx-directive` extras (see below);
- `magic`, to include a Jupyter magic to render - `magic`, to include a Jupyter magic to render
animations inside notebooks. This automatically installs `manim`, animations inside notebooks. This automatically installs `manim`,

View File

@ -15,7 +15,6 @@ the slides.
If both modules are present in {py:data}`sys.modules`, then Manim Slides will If both modules are present in {py:data}`sys.modules`, then Manim Slides will
prefer using `manim`. prefer using `manim`.
### Usage ### Usage
The simplest way to use Manim Slides with the correct Manim API is to: The simplest way to use Manim Slides with the correct Manim API is to:

View File

@ -18,6 +18,7 @@
"outputs": [], "outputs": [],
"source": [ "source": [
"from manim import *\n", "from manim import *\n",
"\n",
"from manim_slides import *" "from manim_slides import *"
] ]
}, },

View File

@ -29,7 +29,7 @@ class Module(ModuleType):
return ModuleType.__getattribute__(self, name) return ModuleType.__getattribute__(self, name)
def __dir__(self) -> List[str]: def __dir__(self) -> list[str]:
result = list(new_module.__all__) result = list(new_module.__all__)
result.extend( result.extend(
( (

View File

@ -4,7 +4,7 @@ from functools import wraps
from inspect import Parameter, signature from inspect import Parameter, signature
from pathlib import Path from pathlib import Path
from textwrap import dedent from textwrap import dedent
from typing import Any, Callable, Dict, List, Optional, Set, Tuple from typing import Any, Callable, Optional
import rtoml import rtoml
from pydantic import ( from pydantic import (
@ -24,7 +24,7 @@ Receiver = Callable[..., Any]
class Signal(BaseModel): # type: ignore[misc] class Signal(BaseModel): # type: ignore[misc]
__receivers: List[Receiver] = PrivateAttr(default_factory=list) __receivers: list[Receiver] = PrivateAttr(default_factory=list)
def connect(self, receiver: Receiver) -> None: def connect(self, receiver: Receiver) -> None:
self.__receivers.append(receiver) self.__receivers.append(receiver)
@ -47,14 +47,14 @@ def key_id(name: str) -> PositiveInt:
class Key(BaseModel): # type: ignore[misc] class Key(BaseModel): # type: ignore[misc]
"""Represents a list of key codes, with optionally a name.""" """Represents a list of key codes, with optionally a name."""
ids: List[PositiveInt] = Field(unique=True) ids: list[PositiveInt] = Field(unique=True)
name: Optional[str] = None name: Optional[str] = None
__signal: Signal = PrivateAttr(default_factory=Signal) __signal: Signal = PrivateAttr(default_factory=Signal)
@field_validator("ids") @field_validator("ids")
@classmethod @classmethod
def ids_is_non_empty_set(cls, ids: Set[Any]) -> Set[Any]: def ids_is_non_empty_set(cls, ids: set[Any]) -> set[Any]:
if len(ids) <= 0: if len(ids) <= 0:
raise ValueError("Key's ids must be a non-empty set") raise ValueError("Key's ids must be a non-empty set")
return ids return ids
@ -98,8 +98,8 @@ class Keys(BaseModel): # type: ignore[misc]
@model_validator(mode="before") @model_validator(mode="before")
@classmethod @classmethod
def ids_are_unique_across_keys(cls, values: Dict[str, Key]) -> Dict[str, Key]: def ids_are_unique_across_keys(cls, values: dict[str, Key]) -> dict[str, Key]:
ids: Set[int] = set() ids: set[int] = set()
for key in values.values(): for key in values.values():
if len(ids.intersection(key["ids"])) != 0: if len(ids.intersection(key["ids"])) != 0:
@ -296,8 +296,8 @@ class SlideConfig(BaseSlideConfig):
class PresentationConfig(BaseModel): # type: ignore[misc] class PresentationConfig(BaseModel): # type: ignore[misc]
slides: List[SlideConfig] = Field(min_length=1) slides: list[SlideConfig] = Field(min_length=1)
resolution: Tuple[PositiveInt, PositiveInt] = (1920, 1080) resolution: tuple[PositiveInt, PositiveInt] = (1920, 1080)
background_color: Color = "black" background_color: Color = "black"
@classmethod @classmethod

View File

@ -2,7 +2,6 @@ import mimetypes
import os import os
import platform import platform
import subprocess import subprocess
import sys
import tempfile import tempfile
import webbrowser import webbrowser
from base64 import b64encode from base64 import b64encode
@ -10,7 +9,7 @@ from collections import deque
from enum import Enum from enum import Enum
from importlib import resources from importlib import resources
from pathlib import Path from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Type, Union from typing import Any, Callable, Optional, Union
import av import av
import click import click
@ -53,7 +52,7 @@ def open_with_default(file: Path) -> None:
def validate_config_option( def validate_config_option(
ctx: Context, param: Parameter, value: Any ctx: Context, param: Parameter, value: Any
) -> Dict[str, str]: ) -> dict[str, str]:
config = {} config = {}
for c_option in value: for c_option in value:
@ -121,7 +120,7 @@ class Converter(BaseModel): # type: ignore
raise NotImplementedError raise NotImplementedError
@classmethod @classmethod
def from_string(cls, s: str) -> Type["Converter"]: def from_string(cls, s: str) -> type["Converter"]:
"""Return the appropriate converter from a string name.""" """Return the appropriate converter from a string name."""
return { return {
"html": RevealJS, "html": RevealJS,
@ -329,7 +328,7 @@ class RevealJS(Converter):
auto_animate_easing: AutoAnimateEasing = AutoAnimateEasing.ease auto_animate_easing: AutoAnimateEasing = AutoAnimateEasing.ease
auto_animate_duration: float = 1.0 auto_animate_duration: float = 1.0
auto_animate_unmatched: JsBool = JsBool.true auto_animate_unmatched: JsBool = JsBool.true
auto_animate_styles: List[str] = Field( auto_animate_styles: list[str] = Field(
default_factory=lambda: [ default_factory=lambda: [
"opacity", "opacity",
"color", "color",
@ -379,9 +378,6 @@ class RevealJS(Converter):
if isinstance(self.template, Path): if isinstance(self.template, Path):
return self.template.read_text() return self.template.read_text()
if sys.version_info < (3, 9):
return resources.read_text(templates, "revealjs.html")
return resources.files(templates).joinpath("revealjs.html").read_text() return resources.files(templates).joinpath("revealjs.html").read_text()
def open(self, file: Path) -> bool: def open(self, file: Path) -> bool:
@ -658,13 +654,13 @@ def show_template_option(function: Callable[..., Any]) -> Callable[..., Any]:
@show_config_options @show_config_options
@verbosity_option @verbosity_option
def convert( def convert(
scenes: List[str], scenes: list[str],
folder: Path, folder: Path,
dest: Path, dest: Path,
to: str, to: str,
open_result: bool, open_result: bool,
force: bool, force: bool,
config_options: Dict[str, str], config_options: dict[str, str],
template: Optional[Path], template: Optional[Path],
) -> None: ) -> None:
"""Convert SCENE(s) into a given format and writes the result in DEST.""" """Convert SCENE(s) into a given format and writes the result in DEST."""

View File

@ -39,7 +39,7 @@ def list_scenes(folder: Path) -> None:
click.secho(f"{i}: {scene}", fg="green") click.secho(f"{i}: {scene}", fg="green")
def _list_scenes(folder: Path) -> List[str]: def _list_scenes(folder: Path) -> list[str]:
"""List available scenes in given directory.""" """List available scenes in given directory."""
scenes = [] scenes = []
@ -59,7 +59,7 @@ def _list_scenes(folder: Path) -> List[str]:
return scenes return scenes
def prompt_for_scenes(folder: Path) -> List[str]: def prompt_for_scenes(folder: Path) -> list[str]:
"""Prompt the user to select scenes within a given folder.""" """Prompt the user to select scenes within a given folder."""
scene_choices = dict(enumerate(_list_scenes(folder), start=1)) scene_choices = dict(enumerate(_list_scenes(folder), start=1))
@ -71,7 +71,7 @@ def prompt_for_scenes(folder: Path) -> List[str]:
click.echo("Choose number corresponding to desired scene/arguments.") click.echo("Choose number corresponding to desired scene/arguments.")
click.echo("(Use comma separated list for multiple entries)") click.echo("(Use comma separated list for multiple entries)")
def value_proc(value: Optional[str]) -> List[str]: def value_proc(value: Optional[str]) -> list[str]:
indices = list(map(int, (value or "").strip().replace(" ", "").split(","))) indices = list(map(int, (value or "").strip().replace(" ", "").split(",")))
if not all(0 < i <= len(scene_choices) for i in indices): if not all(0 < i <= len(scene_choices) for i in indices):
@ -93,8 +93,8 @@ def prompt_for_scenes(folder: Path) -> List[str]:
def get_scenes_presentation_config( def get_scenes_presentation_config(
scenes: List[str], folder: Path scenes: list[str], folder: Path
) -> List[PresentationConfig]: ) -> list[PresentationConfig]:
"""Return a list of presentation configurations based on the user input.""" """Return a list of presentation configurations based on the user input."""
if len(scenes) == 0: if len(scenes) == 0:
scenes = prompt_for_scenes(folder) scenes = prompt_for_scenes(folder)
@ -116,7 +116,7 @@ def get_scenes_presentation_config(
def start_at_callback( def start_at_callback(
ctx: Context, param: Parameter, values: str ctx: Context, param: Parameter, values: str
) -> Tuple[Optional[int], ...]: ) -> tuple[Optional[int], ...]:
if values == "(None, None)": if values == "(None, None)":
return (None, None) return (None, None)
@ -253,7 +253,7 @@ def start_at_callback(
@click.help_option("-h", "--help") @click.help_option("-h", "--help")
@verbosity_option @verbosity_option
def present( def present(
scenes: List[str], scenes: list[str],
config_path: Path, config_path: Path,
folder: Path, folder: Path,
start_paused: bool, start_paused: bool,
@ -262,7 +262,7 @@ def present(
exit_after_last_slide: bool, exit_after_last_slide: bool,
hide_mouse: bool, hide_mouse: bool,
aspect_ratio: str, aspect_ratio: str,
start_at: Tuple[Optional[int], Optional[int], Optional[int]], start_at: tuple[Optional[int], Optional[int], Optional[int]],
start_at_scene_number: int, start_at_scene_number: int,
start_at_slide_number: int, start_at_slide_number: int,
screen_number: Optional[int], screen_number: Optional[int],

View File

@ -1,6 +1,6 @@
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import List, Optional from typing import Optional
from qtpy.QtCore import Qt, QTimer, QUrl, Signal, Slot from qtpy.QtCore import Qt, QTimer, QUrl, Signal, Slot
from qtpy.QtGui import QCloseEvent, QIcon, QKeyEvent, QScreen from qtpy.QtGui import QCloseEvent, QIcon, QKeyEvent, QScreen
@ -169,7 +169,7 @@ class Player(QMainWindow): # type: ignore[misc]
def __init__( def __init__(
self, self,
config: Config, config: Config,
presentation_configs: List[PresentationConfig], presentation_configs: list[PresentationConfig],
*, *,
start_paused: bool = False, start_paused: bool = False,
full_screen: bool = False, full_screen: bool = False,

View File

@ -12,7 +12,6 @@ This is especially useful for two reasons:
import subprocess import subprocess
import sys import sys
from typing import Tuple
import click import click
@ -39,7 +38,7 @@ import click
help="If set, use ManimGL renderer.", help="If set, use ManimGL renderer.",
) )
@click.argument("args", metavar="[RENDERER_ARGS]...", nargs=-1, type=click.UNPROCESSED) @click.argument("args", metavar="[RENDERER_ARGS]...", nargs=-1, type=click.UNPROCESSED)
def render(ce: bool, gl: bool, args: Tuple[str, ...]) -> None: def render(ce: bool, gl: bool, args: tuple[str, ...]) -> None:
""" """
Render SCENE(s) from the input FILE, using the specified renderer. Render SCENE(s) from the input FILE, using the specified renderer.

View File

@ -11,7 +11,8 @@ that directly calls ``self.play(Animation(...))``, see
__all__ = ["Wipe", "Zoom"] __all__ = ["Wipe", "Zoom"]
from typing import Any, Mapping, Optional, Sequence from collections.abc import Mapping, Sequence
from typing import Any, Optional
import numpy as np import numpy as np

View File

@ -4,13 +4,11 @@ __all__ = ["BaseSlide"]
import platform import platform
from abc import abstractmethod from abc import abstractmethod
from collections.abc import MutableMapping, Sequence, ValuesView
from pathlib import Path from pathlib import Path
from typing import ( from typing import (
TYPE_CHECKING, TYPE_CHECKING,
Any, Any,
MutableMapping,
Sequence,
ValuesView,
) )
import numpy as np import numpy as np

View File

@ -1,5 +1,5 @@
from pathlib import Path from pathlib import Path
from typing import Any, List, Optional, Tuple from typing import Any, Optional
from manim import Scene, ThreeDScene, config from manim import Scene, ThreeDScene, config
@ -30,11 +30,11 @@ class Slide(BaseSlide, Scene): # type: ignore[misc]
return color.to_hex() # type: ignore return color.to_hex() # type: ignore
@property @property
def _resolution(self) -> Tuple[int, int]: def _resolution(self) -> tuple[int, int]:
return config["pixel_width"], config["pixel_height"] return config["pixel_width"], config["pixel_height"]
@property @property
def _partial_movie_files(self) -> List[Path]: def _partial_movie_files(self) -> list[Path]:
# When rendering with -na,b (manim only) # When rendering with -na,b (manim only)
# the animations not in [a,b] will be skipped, # the animations not in [a,b] will be skipped,
# but animation before a will have a None source file. # but animation before a will have a None source file.

View File

@ -1,5 +1,5 @@
from pathlib import Path from pathlib import Path
from typing import Any, ClassVar, Dict, List, Optional, Tuple from typing import Any, ClassVar, Optional
from manimlib import Scene, ThreeDCamera from manimlib import Scene, ThreeDCamera
from manimlib.utils.file_ops import get_sorted_integer_files from manimlib.utils.file_ops import get_sorted_integer_files
@ -31,11 +31,11 @@ class Slide(BaseSlide, Scene): # type: ignore[misc]
return self.camera_config["background_color"].hex # type: ignore return self.camera_config["background_color"].hex # type: ignore
@property @property
def _resolution(self) -> Tuple[int, int]: def _resolution(self) -> tuple[int, int]:
return self.camera_config["pixel_width"], self.camera_config["pixel_height"] return self.camera_config["pixel_width"], self.camera_config["pixel_height"]
@property @property
def _partial_movie_files(self) -> List[Path]: def _partial_movie_files(self) -> list[Path]:
kwargs = { kwargs = {
"remove_non_integer_files": True, "remove_non_integer_files": True,
"extension": self.file_writer.movie_file_extension, "extension": self.file_writer.movie_file_extension,
@ -66,7 +66,7 @@ class Slide(BaseSlide, Scene): # type: ignore[misc]
class ThreeDSlide(Slide): class ThreeDSlide(Slide):
CONFIG: ClassVar[Dict[str, Any]] = { CONFIG: ClassVar[dict[str, Any]] = {
"camera_class": ThreeDCamera, "camera_class": ThreeDCamera,
} }
pass pass

View File

@ -1,18 +1,18 @@
import hashlib import hashlib
import os import os
import tempfile import tempfile
from collections.abc import Iterator
from pathlib import Path from pathlib import Path
from typing import Iterator, List
import av import av
from .logger import logger from .logger import logger
def concatenate_video_files(files: List[Path], dest: Path) -> None: def concatenate_video_files(files: list[Path], dest: Path) -> None:
"""Concatenate multiple video files into one.""" """Concatenate multiple video files into one."""
def _filter(files: List[Path]) -> Iterator[Path]: def _filter(files: list[Path]) -> Iterator[Path]:
"""Patch possibly empty video files.""" """Patch possibly empty video files."""
for file in files: for file in files:
with av.open(str(file)) as container: with av.open(str(file)) as container:
@ -30,9 +30,10 @@ def concatenate_video_files(files: List[Path], dest: Path) -> None:
f.writelines(f"file '{file}'\n" for file in _filter(files)) f.writelines(f"file '{file}'\n" for file in _filter(files))
tmp_file = f.name tmp_file = f.name
with av.open( with (
tmp_file, format="concat", options={"safe": "0"} av.open(tmp_file, format="concat", options={"safe": "0"}) as input_container,
) as input_container, av.open(str(dest), mode="w") as output_container: av.open(str(dest), mode="w") as output_container,
):
input_video_stream = input_container.streams.video[0] input_video_stream = input_container.streams.video[0]
output_video_stream = output_container.add_stream( output_video_stream = output_container.add_stream(
template=input_video_stream, template=input_video_stream,
@ -61,7 +62,7 @@ def concatenate_video_files(files: List[Path], dest: Path) -> None:
os.unlink(tmp_file) # https://stackoverflow.com/a/54768241 os.unlink(tmp_file) # https://stackoverflow.com/a/54768241
def merge_basenames(files: List[Path]) -> Path: def merge_basenames(files: list[Path]) -> Path:
"""Merge multiple filenames by concatenating basenames.""" """Merge multiple filenames by concatenating basenames."""
if len(files) == 0: if len(files) == 0:
raise ValueError("Cannot merge an empty list of files!") raise ValueError("Cannot merge an empty list of files!")
@ -90,9 +91,10 @@ def link_nodes(*nodes: av.filter.context.FilterContext) -> None:
def reverse_video_file(src: Path, dest: Path) -> None: def reverse_video_file(src: Path, dest: Path) -> None:
"""Reverses a video file, writing the result to `dest`.""" """Reverses a video file, writing the result to `dest`."""
with av.open(str(src)) as input_container, av.open( with (
str(dest), mode="w" av.open(str(src)) as input_container,
) as output_container: av.open(str(dest), mode="w") as output_container,
):
input_stream = input_container.streams.video[0] input_stream = input_container.streams.video[0]
output_stream = output_container.add_stream( output_stream = output_container.add_stream(
codec_name="libx264", rate=input_stream.base_rate codec_name="libx264", rate=input_stream.base_rate

3493
pdm.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[build-system] [build-system]
build-backend = "pdm.backend" build-backend = "hatchling.build"
requires = ["pdm-backend", "setuptools"] requires = ["hatchling"]
[project] [project]
authors = [{name = "Jérome Eertmans", email = "jeertmans@icloud.com"}] authors = [{name = "Jérome Eertmans", email = "jeertmans@icloud.com"}]
@ -39,7 +39,7 @@ keywords = ["manim", "slides", "plugin", "manimgl"]
license = {text = "MIT"} license = {text = "MIT"}
name = "manim-slides" name = "manim-slides"
readme = "README.md" readme = "README.md"
requires-python = ">=3.9,<3.13" requires-python = ">=3.9"
[project.optional-dependencies] [project.optional-dependencies]
docs = [ docs = [
@ -55,11 +55,14 @@ docs = [
"sphinxext-opengraph>=0.7.5", "sphinxext-opengraph>=0.7.5",
] ]
full = [ full = [
"manim-slides[magic,manim,manimgl,sphinx-directive]", "manim-slides[magic,manim,sphinx-directive]",
]
full-gl = [
"manim-slides[magic,manimgl,sphinx-directive]",
] ]
magic = ["manim-slides[manim]", "ipython>=8.12.2"] magic = ["manim-slides[manim]", "ipython>=8.12.2"]
manim = ["manim>=0.17.3"] manim = ["manim>=0.17.3"]
manimgl = ["manimgl>=1.6.1"] manimgl = ["manimgl>=1.6.1;python_version<'3.12'"]
pyqt6 = ["pyqt6>=6.6.1"] pyqt6 = ["pyqt6>=6.6.1"]
pyqt6-full = ["manim-slides[full,pyqt6]"] pyqt6-full = ["manim-slides[full,pyqt6]"]
pyside6 = ["pyside6>=6.5.1,<6.5.3;python_version<'3.12'"] pyside6 = ["pyside6>=6.5.1,<6.5.3;python_version<'3.12'"]
@ -80,7 +83,7 @@ Repository = "https://github.com/jeertmans/manim-slides"
builtin = "clear,rare,informal,usage,names,en-GB_to_en-US" builtin = "clear,rare,informal,usage,names,en-GB_to_en-US"
check-hidden = true check-hidden = true
ignore-words-list = "master" ignore-words-list = "master"
skip = "pdm.lock" skip = "requirements.lock,requirements-dev.lock"
[tool.coverage.report] [tool.coverage.report]
exclude_lines = [ exclude_lines = [
@ -91,35 +94,15 @@ exclude_lines = [
] ]
precision = 2 precision = 2
[tool.hatch.version]
path = "manim_slides/__version__.py"
[tool.mypy] [tool.mypy]
disallow_untyped_decorators = false disallow_untyped_decorators = false
install_types = true install_types = true
python_version = "3.8" python_version = "3.9"
strict = true strict = true
[tool.pdm.dev-dependencies]
dev = [
"bump2version>=1.0.1",
"pre-commit>=3.5.0",
]
github-action = ["setuptools"]
test = [
"manim-slides[manim,manimgl,pyqt6]",
"pytest>=7.4.0",
"pytest-cov>=4.1.0",
"pytest-env>=0.8.2",
"pytest-qt>=4.2.0",
"pytest-xdist>=3.3.1",
]
[tool.pdm.resolution.overrides]
manimpango = "<1.0.0,>=0.5.0" # This conflicts with ManimGL, hopefully not an issue
skia-pathops = "0.8.0.post1" # From manim 0.18.0 (Python 3.12 support)
[tool.pdm.version]
path = "manim_slides/__version__.py"
source = "file"
[tool.pytest.ini_options] [tool.pytest.ini_options]
env = [ env = [
"QT_QPA_PLATFORM=offscreen", "QT_QPA_PLATFORM=offscreen",
@ -133,7 +116,7 @@ filterwarnings = [
extend-exclude = ["manim_slides/resources.py"] extend-exclude = ["manim_slides/resources.py"]
extend-include = ["*.ipynb"] extend-include = ["*.ipynb"]
line-length = 88 line-length = 88
target-version = "py38" target-version = "py39"
[tool.ruff.lint] [tool.ruff.lint]
extend-ignore = [ extend-ignore = [
@ -152,3 +135,16 @@ extend-ignore = [
] ]
extend-select = ["B", "C90", "D", "I", "N", "RUF", "UP", "T"] extend-select = ["B", "C90", "D", "I", "N", "RUF", "UP", "T"]
isort = {known-first-party = ["manim_slides", "tests"]} isort = {known-first-party = ["manim_slides", "tests"]}
[tool.rye]
dev-dependencies = [
"bump2version>=1.0.1",
"manim-slides[manim,manimgl,pyqt6]",
"pre-commit>=3.5.0",
"pytest>=7.4.0",
"pytest-cov>=4.1.0",
"pytest-env>=0.8.2",
"pytest-qt>=4.2.0",
"pytest-xdist>=3.3.1",
]
managed = true

419
requirements-dev.lock Normal file
View File

@ -0,0 +1,419 @@
# generated by rye
# use `rye lock` or `rye sync` to update this lockfile
#
# last locked with the following flags:
# pre: false
# features: []
# all-features: true
# with-sources: false
-e file:.
alabaster==0.7.16
# via sphinx
annotated-types==0.6.0
# via pydantic
asttokens==2.4.1
# via stack-data
attrs==23.2.0
# via jsonschema
# via referencing
av==12.0.0
# via manim-slides
babel==2.14.0
# via sphinx
beautifulsoup4==4.12.3
# via furo
# via nbconvert
bleach==6.1.0
# via nbconvert
bump2version==1.0.1
certifi==2024.2.2
# via requests
cfgv==3.4.0
# via pre-commit
charset-normalizer==3.3.2
# via requests
click==8.1.7
# via click-default-group
# via cloup
# via manim
# via manim-slides
# via sphinx-click
click-default-group==1.2.4
# via manim
# via manim-slides
cloup==0.13.1
# via manim
colour==0.1.5
# via manim
# via manimgl
comm==0.2.2
# via ipykernel
contourpy==1.2.1
# via matplotlib
coverage==7.4.4
# via pytest-cov
cycler==0.12.1
# via matplotlib
debugpy==1.8.1
# via ipykernel
decorator==5.1.1
# via ipython
# via manim
defusedxml==0.7.1
# via nbconvert
distlib==0.3.8
# via virtualenv
docutils==0.20.1
# via manim-slides
# via myst-parser
# via nbsphinx
# via sphinx
# via sphinx-click
execnet==2.1.1
# via pytest-xdist
executing==2.0.1
# via stack-data
fastjsonschema==2.19.1
# via nbformat
filelock==3.13.4
# via virtualenv
fonttools==4.51.0
# via matplotlib
furo==2024.1.29
# via manim-slides
glcontext==2.5.0
# via moderngl
identify==2.5.35
# via pre-commit
idna==3.7
# via requests
imagesize==1.4.1
# via sphinx
iniconfig==2.0.0
# via pytest
ipykernel==6.29.4
# via manim-slides
ipython==8.18.1
# via ipykernel
# via manim-slides
# via manimgl
isosurfaces==0.1.0
# via manim
# via manimgl
jedi==0.19.1
# via ipython
jinja2==3.1.3
# via manim-slides
# via myst-parser
# via nbconvert
# via nbsphinx
# via sphinx
jsonschema==4.21.1
# via nbformat
jsonschema-specifications==2023.12.1
# via jsonschema
jupyter-client==8.6.1
# via ipykernel
# via nbclient
jupyter-core==5.7.2
# via ipykernel
# via jupyter-client
# via nbclient
# via nbconvert
# via nbformat
jupyterlab-pygments==0.3.0
# via nbconvert
kiwisolver==1.4.5
# via matplotlib
lxml==5.2.1
# via manim-slides
# via python-pptx
manim==0.17.3
# via manim-slides
manimgl==1.6.1
# via manim-slides
manimpango==0.4.4
# via manim
# via manimgl
mapbox-earcut==1.0.1
# via manim
# via manimgl
markdown-it-py==3.0.0
# via mdit-py-plugins
# via myst-parser
# via rich
markupsafe==2.1.5
# via jinja2
# via nbconvert
matplotlib==3.8.4
# via manimgl
matplotlib-inline==0.1.7
# via ipykernel
# via ipython
mdit-py-plugins==0.4.0
# via myst-parser
mdurl==0.1.2
# via markdown-it-py
mistune==3.0.2
# via nbconvert
moderngl==5.10.0
# via manim
# via manimgl
# via moderngl-window
moderngl-window==2.4.4
# via manim
# via manimgl
mpmath==1.3.0
# via sympy
multipledispatch==1.0.0
# via pyrr
myst-parser==2.0.0
# via manim-slides
nbclient==0.10.0
# via nbconvert
nbconvert==7.16.3
# via nbsphinx
nbformat==5.10.4
# via nbclient
# via nbconvert
# via nbsphinx
nbsphinx==0.9.3
# via manim-slides
nest-asyncio==1.6.0
# via ipykernel
networkx==2.8.8
# via manim
nodeenv==1.8.0
# via pre-commit
numpy==1.26.4
# via contourpy
# via isosurfaces
# via manim
# via manim-slides
# via manimgl
# via mapbox-earcut
# via matplotlib
# via moderngl-window
# via pyrr
# via scipy
packaging==24.0
# via ipykernel
# via matplotlib
# via nbconvert
# via pytest
# via qtpy
# via sphinx
pandoc==2.3
# via manim-slides
pandocfilters==1.5.1
# via nbconvert
parso==0.8.4
# via jedi
pexpect==4.9.0
# via ipython
pillow==9.5.0
# via manim
# via manim-slides
# via manimgl
# via matplotlib
# via moderngl-window
# via python-pptx
platformdirs==4.2.0
# via jupyter-core
# via virtualenv
pluggy==1.4.0
# via pytest
# via pytest-qt
plumbum==1.8.2
# via pandoc
ply==3.11
# via pandoc
pre-commit==3.7.0
prompt-toolkit==3.0.43
# via ipython
psutil==5.9.8
# via ipykernel
ptyprocess==0.7.0
# via pexpect
pure-eval==0.2.2
# via stack-data
pycairo==1.26.0
# via manim
pydantic==2.7.0
# via manim-slides
# via pydantic-extra-types
pydantic-core==2.18.1
# via pydantic
pydantic-extra-types==2.6.0
# via manim-slides
pydub==0.25.1
# via manim
# via manimgl
pyglet==2.0.15
# via moderngl-window
pygments==2.17.2
# via furo
# via ipython
# via manim
# via manimgl
# via nbconvert
# via rich
# via sphinx
pyopengl==3.1.7
# via manimgl
pyparsing==3.1.2
# via matplotlib
pyqt6==6.6.1
# via manim-slides
pyqt6-qt6==6.6.3
# via pyqt6
pyqt6-sip==13.6.0
# via pyqt6
pyrr==0.10.3
# via moderngl-window
pyside6==6.5.2
# via manim-slides
pyside6-addons==6.5.2
# via pyside6
pyside6-essentials==6.5.2
# via pyside6
# via pyside6-addons
pytest==8.1.1
# via pytest-cov
# via pytest-env
# via pytest-qt
# via pytest-xdist
pytest-cov==5.0.0
pytest-env==1.1.3
pytest-qt==4.4.0
pytest-xdist==3.5.0
python-dateutil==2.9.0.post0
# via jupyter-client
# via matplotlib
python-pptx==0.6.23
# via manim-slides
pyyaml==6.0.1
# via manimgl
# via myst-parser
# via pre-commit
pyzmq==26.0.0
# via ipykernel
# via jupyter-client
qtpy==2.4.1
# via manim-slides
referencing==0.34.0
# via jsonschema
# via jsonschema-specifications
requests==2.31.0
# via manim
# via manim-slides
# via sphinx
rich==13.7.1
# via manim
# via manim-slides
# via manimgl
rpds-py==0.18.0
# via jsonschema
# via referencing
rtoml==0.10.0
# via manim-slides
scipy==1.13.0
# via manim
# via manimgl
screeninfo==0.8.1
# via manim
# via manimgl
setuptools==69.5.1
# via nodeenv
shiboken6==6.5.2
# via pyside6
# via pyside6-addons
# via pyside6-essentials
six==1.16.0
# via asttokens
# via bleach
# via python-dateutil
skia-pathops==0.7.4
# via manim
# via manimgl
snowballstemmer==2.2.0
# via sphinx
soupsieve==2.5
# via beautifulsoup4
sphinx==7.3.6
# via furo
# via manim-slides
# via myst-parser
# via nbsphinx
# via sphinx-basic-ng
# via sphinx-click
# via sphinx-copybutton
# via sphinxext-opengraph
sphinx-basic-ng==1.0.0b2
# via furo
sphinx-click==5.1.0
# via manim-slides
sphinx-copybutton==0.5.2
# via manim-slides
sphinxcontrib-applehelp==1.0.8
# via sphinx
sphinxcontrib-devhelp==1.0.6
# via sphinx
sphinxcontrib-htmlhelp==2.0.5
# via sphinx
sphinxcontrib-jsmath==1.0.1
# via sphinx
sphinxcontrib-qthelp==1.0.7
# via sphinx
sphinxcontrib-serializinghtml==1.1.10
# via sphinx
sphinxext-opengraph==0.9.1
# via manim-slides
srt==3.5.3
# via manim
stack-data==0.6.3
# via ipython
svgelements==1.9.6
# via manim
# via manimgl
sympy==1.12
# via manimgl
tinycss2==1.2.1
# via nbconvert
tornado==6.4
# via ipykernel
# via jupyter-client
tqdm==4.66.2
# via manim
# via manim-slides
# via manimgl
traitlets==5.14.2
# via comm
# via ipykernel
# via ipython
# via jupyter-client
# via jupyter-core
# via matplotlib-inline
# via nbclient
# via nbconvert
# via nbformat
# via nbsphinx
typing-extensions==4.11.0
# via pydantic
# via pydantic-core
urllib3==2.2.1
# via requests
validators==0.28.0
# via manimgl
virtualenv==20.25.3
# via pre-commit
watchdog==2.3.1
# via manim
wcwidth==0.2.13
# via prompt-toolkit
webencodings==0.5.1
# via bleach
# via tinycss2
xlsxwriter==3.2.0
# via python-pptx

382
requirements.lock Normal file
View File

@ -0,0 +1,382 @@
# generated by rye
# use `rye lock` or `rye sync` to update this lockfile
#
# last locked with the following flags:
# pre: false
# features: []
# all-features: true
# with-sources: false
-e file:.
alabaster==0.7.16
# via sphinx
annotated-types==0.6.0
# via pydantic
asttokens==2.4.1
# via stack-data
attrs==23.2.0
# via jsonschema
# via referencing
av==12.0.0
# via manim-slides
babel==2.14.0
# via sphinx
beautifulsoup4==4.12.3
# via furo
# via nbconvert
bleach==6.1.0
# via nbconvert
certifi==2024.2.2
# via requests
charset-normalizer==3.3.2
# via requests
click==8.1.7
# via click-default-group
# via cloup
# via manim
# via manim-slides
# via sphinx-click
click-default-group==1.2.4
# via manim
# via manim-slides
cloup==0.13.1
# via manim
colour==0.1.5
# via manim
# via manimgl
comm==0.2.2
# via ipykernel
contourpy==1.2.1
# via matplotlib
cycler==0.12.1
# via matplotlib
debugpy==1.8.1
# via ipykernel
decorator==5.1.1
# via ipython
# via manim
defusedxml==0.7.1
# via nbconvert
docutils==0.20.1
# via manim-slides
# via myst-parser
# via nbsphinx
# via sphinx
# via sphinx-click
executing==2.0.1
# via stack-data
fastjsonschema==2.19.1
# via nbformat
fonttools==4.51.0
# via matplotlib
furo==2024.1.29
# via manim-slides
glcontext==2.5.0
# via moderngl
idna==3.7
# via requests
imagesize==1.4.1
# via sphinx
ipykernel==6.29.4
# via manim-slides
ipython==8.18.1
# via ipykernel
# via manim-slides
# via manimgl
isosurfaces==0.1.0
# via manim
# via manimgl
jedi==0.19.1
# via ipython
jinja2==3.1.3
# via manim-slides
# via myst-parser
# via nbconvert
# via nbsphinx
# via sphinx
jsonschema==4.21.1
# via nbformat
jsonschema-specifications==2023.12.1
# via jsonschema
jupyter-client==8.6.1
# via ipykernel
# via nbclient
jupyter-core==5.7.2
# via ipykernel
# via jupyter-client
# via nbclient
# via nbconvert
# via nbformat
jupyterlab-pygments==0.3.0
# via nbconvert
kiwisolver==1.4.5
# via matplotlib
lxml==5.2.1
# via manim-slides
# via python-pptx
manim==0.17.3
# via manim-slides
manimgl==1.6.1
# via manim-slides
manimpango==0.4.4
# via manim
# via manimgl
mapbox-earcut==1.0.1
# via manim
# via manimgl
markdown-it-py==3.0.0
# via mdit-py-plugins
# via myst-parser
# via rich
markupsafe==2.1.5
# via jinja2
# via nbconvert
matplotlib==3.8.4
# via manimgl
matplotlib-inline==0.1.7
# via ipykernel
# via ipython
mdit-py-plugins==0.4.0
# via myst-parser
mdurl==0.1.2
# via markdown-it-py
mistune==3.0.2
# via nbconvert
moderngl==5.10.0
# via manim
# via manimgl
# via moderngl-window
moderngl-window==2.4.4
# via manim
# via manimgl
mpmath==1.3.0
# via sympy
multipledispatch==1.0.0
# via pyrr
myst-parser==2.0.0
# via manim-slides
nbclient==0.10.0
# via nbconvert
nbconvert==7.16.3
# via nbsphinx
nbformat==5.10.4
# via nbclient
# via nbconvert
# via nbsphinx
nbsphinx==0.9.3
# via manim-slides
nest-asyncio==1.6.0
# via ipykernel
networkx==2.8.8
# via manim
numpy==1.26.4
# via contourpy
# via isosurfaces
# via manim
# via manim-slides
# via manimgl
# via mapbox-earcut
# via matplotlib
# via moderngl-window
# via pyrr
# via scipy
packaging==24.0
# via ipykernel
# via matplotlib
# via nbconvert
# via qtpy
# via sphinx
pandoc==2.3
# via manim-slides
pandocfilters==1.5.1
# via nbconvert
parso==0.8.4
# via jedi
pexpect==4.9.0
# via ipython
pillow==9.5.0
# via manim
# via manim-slides
# via manimgl
# via matplotlib
# via moderngl-window
# via python-pptx
platformdirs==4.2.0
# via jupyter-core
plumbum==1.8.2
# via pandoc
ply==3.11
# via pandoc
prompt-toolkit==3.0.43
# via ipython
psutil==5.9.8
# via ipykernel
ptyprocess==0.7.0
# via pexpect
pure-eval==0.2.2
# via stack-data
pycairo==1.26.0
# via manim
pydantic==2.7.0
# via manim-slides
# via pydantic-extra-types
pydantic-core==2.18.1
# via pydantic
pydantic-extra-types==2.6.0
# via manim-slides
pydub==0.25.1
# via manim
# via manimgl
pyglet==2.0.15
# via moderngl-window
pygments==2.17.2
# via furo
# via ipython
# via manim
# via manimgl
# via nbconvert
# via rich
# via sphinx
pyopengl==3.1.7
# via manimgl
pyparsing==3.1.2
# via matplotlib
pyqt6==6.6.1
# via manim-slides
pyqt6-qt6==6.6.3
# via pyqt6
pyqt6-sip==13.6.0
# via pyqt6
pyrr==0.10.3
# via moderngl-window
pyside6==6.5.2
# via manim-slides
pyside6-addons==6.5.2
# via pyside6
pyside6-essentials==6.5.2
# via pyside6
# via pyside6-addons
python-dateutil==2.9.0.post0
# via jupyter-client
# via matplotlib
python-pptx==0.6.23
# via manim-slides
pyyaml==6.0.1
# via manimgl
# via myst-parser
pyzmq==26.0.0
# via ipykernel
# via jupyter-client
qtpy==2.4.1
# via manim-slides
referencing==0.34.0
# via jsonschema
# via jsonschema-specifications
requests==2.31.0
# via manim
# via manim-slides
# via sphinx
rich==13.7.1
# via manim
# via manim-slides
# via manimgl
rpds-py==0.18.0
# via jsonschema
# via referencing
rtoml==0.10.0
# via manim-slides
scipy==1.13.0
# via manim
# via manimgl
screeninfo==0.8.1
# via manim
# via manimgl
shiboken6==6.5.2
# via pyside6
# via pyside6-addons
# via pyside6-essentials
six==1.16.0
# via asttokens
# via bleach
# via python-dateutil
skia-pathops==0.7.4
# via manim
# via manimgl
snowballstemmer==2.2.0
# via sphinx
soupsieve==2.5
# via beautifulsoup4
sphinx==7.3.6
# via furo
# via manim-slides
# via myst-parser
# via nbsphinx
# via sphinx-basic-ng
# via sphinx-click
# via sphinx-copybutton
# via sphinxext-opengraph
sphinx-basic-ng==1.0.0b2
# via furo
sphinx-click==5.1.0
# via manim-slides
sphinx-copybutton==0.5.2
# via manim-slides
sphinxcontrib-applehelp==1.0.8
# via sphinx
sphinxcontrib-devhelp==1.0.6
# via sphinx
sphinxcontrib-htmlhelp==2.0.5
# via sphinx
sphinxcontrib-jsmath==1.0.1
# via sphinx
sphinxcontrib-qthelp==1.0.7
# via sphinx
sphinxcontrib-serializinghtml==1.1.10
# via sphinx
sphinxext-opengraph==0.9.1
# via manim-slides
srt==3.5.3
# via manim
stack-data==0.6.3
# via ipython
svgelements==1.9.6
# via manim
# via manimgl
sympy==1.12
# via manimgl
tinycss2==1.2.1
# via nbconvert
tornado==6.4
# via ipykernel
# via jupyter-client
tqdm==4.66.2
# via manim
# via manim-slides
# via manimgl
traitlets==5.14.2
# via comm
# via ipykernel
# via ipython
# via jupyter-client
# via jupyter-core
# via matplotlib-inline
# via nbclient
# via nbconvert
# via nbformat
# via nbsphinx
typing-extensions==4.11.0
# via pydantic
# via pydantic-core
urllib3==2.2.1
# via requests
validators==0.28.0
# via manimgl
watchdog==2.3.1
# via manim
wcwidth==0.2.13
# via prompt-toolkit
webencodings==0.5.1
# via bleach
# via tinycss2
xlsxwriter==3.2.0
# via python-pptx

View File

@ -1,5 +1,5 @@
#! /bin/bash #! /bin/bash
pdm run manim-slides render -t -qk -s --format png --resolution 64,64 static/logo.py ManimSlidesFavicon && mv media/images/logo/*.png static/favicon.png rye run manim-slides render -t -qk -s --format png --resolution 64,64 static/logo.py ManimSlidesFavicon && mv media/images/logo/*.png static/favicon.png
ln -f -r -s static/favicon.png docs/source/_static/favicon.png ln -f -r -s static/favicon.png docs/source/_static/favicon.png

View File

@ -1,21 +1,21 @@
#! /bin/bash #! /bin/bash
MANIM_SLIDES_THEME=light pdm run manim-slides render -qk -s --format png --resolution 2560,1280 static/logo.py ManimSlidesLogo && mv media/images/logo/*.png static/logo.png MANIM_SLIDES_THEME=light rye run manim-slides render -qk -s --format png --resolution 2560,1280 static/logo.py ManimSlidesLogo && mv media/images/logo/*.png static/logo.png
ln -f -r -s static/logo.png docs/source/_static/logo.png ln -f -r -s static/logo.png docs/source/_static/logo.png
MANIM_SLIDES_THEME=dark_docs pdm run manim-slides render -qk -s --format png --resolution 2560,1280 static/logo.py ManimSlidesLogo && mv media/images/logo/*.png static/logo_dark_docs.png MANIM_SLIDES_THEME=dark_docs rye run manim-slides render -qk -s --format png --resolution 2560,1280 static/logo.py ManimSlidesLogo && mv media/images/logo/*.png static/logo_dark_docs.png
ln -f -r -s static/logo_dark_docs.png docs/source/_static/logo_dark_docs.png ln -f -r -s static/logo_dark_docs.png docs/source/_static/logo_dark_docs.png
MANIM_SLIDES_THEME=dark_github pdm run manim-slides render -qk -s --format png --resolution 2560,1280 static/logo.py ManimSlidesLogo && mv media/images/logo/*.png static/logo_dark_github.png MANIM_SLIDES_THEME=dark_github rye run manim-slides render -qk -s --format png --resolution 2560,1280 static/logo.py ManimSlidesLogo && mv media/images/logo/*.png static/logo_dark_github.png
ln -f -r -s static/logo_dark_github.png docs/source/_static/logo_dark_github.png ln -f -r -s static/logo_dark_github.png docs/source/_static/logo_dark_github.png
MANIM_SLIDES_THEME=light pdm run manim-slides render -t -qk -s --format png --resolution 2560,1280 static/logo.py ManimSlidesLogo && mv media/images/logo/*.png static/logo_light_transparent.png MANIM_SLIDES_THEME=light rye run manim-slides render -t -qk -s --format png --resolution 2560,1280 static/logo.py ManimSlidesLogo && mv media/images/logo/*.png static/logo_light_transparent.png
ln -f -r -s static/logo_light_transparent.png docs/source/_static/logo_light_transparent.png ln -f -r -s static/logo_light_transparent.png docs/source/_static/logo_light_transparent.png
MANIM_SLIDES_THEME=dark_docs pdm run manim-slides render -t -qk -s --format png --resolution 2560,1280 static/logo.py ManimSlidesLogo && mv media/images/logo/*.png static/logo_dark_transparent.png MANIM_SLIDES_THEME=dark_docs rye run manim-slides render -t -qk -s --format png --resolution 2560,1280 static/logo.py ManimSlidesLogo && mv media/images/logo/*.png static/logo_dark_transparent.png
ln -f -r -s static/logo_dark_transparent.png docs/source/_static/logo_dark_transparent.png ln -f -r -s static/logo_dark_transparent.png docs/source/_static/logo_dark_transparent.png

View File

@ -1,7 +1,7 @@
import random import random
import string import string
from collections.abc import Generator, Iterator
from pathlib import Path from pathlib import Path
from typing import Generator, Iterator, List
import pytest import pytest
@ -65,7 +65,7 @@ def random_path(
@pytest.fixture @pytest.fixture
def paths() -> Generator[List[Path], None, None]: def paths() -> Generator[list[Path], None, None]:
random.seed(1234) random.seed(1234)
yield [random_path() for _ in range(20)] yield [random_path() for _ in range(20)]

View File

@ -1,4 +1,4 @@
from typing import MutableMapping from collections.abc import MutableMapping
import pytest import pytest

View File

@ -21,6 +21,7 @@ def assert_import(
def test_force_api() -> None: def test_force_api() -> None:
pytest.importorskip("manimlib")
import manim # noqa: F401 import manim # noqa: F401
if "manimlib" in sys.modules: if "manimlib" in sys.modules:
@ -54,6 +55,7 @@ def test_invalid_api() -> None:
@pytest.mark.filterwarnings("ignore:assert_import") @pytest.mark.filterwarnings("ignore:assert_import")
def test_manim_and_manimgl_imported() -> None: def test_manim_and_manimgl_imported() -> None:
pytest.importorskip("manimlib")
import manim # noqa: F401 import manim # noqa: F401
import manimlib # noqa: F401 import manimlib # noqa: F401
@ -78,6 +80,7 @@ def test_manim_imported() -> None:
def test_manimgl_imported() -> None: def test_manimgl_imported() -> None:
pytest.importorskip("manimlib")
import manimlib # noqa: F401 import manimlib # noqa: F401
if "manim" in sys.modules: if "manim" in sys.modules:

View File

@ -1,5 +1,5 @@
from collections.abc import Iterator
from pathlib import Path from pathlib import Path
from typing import Iterator, Tuple
import pytest import pytest
from click.testing import CliRunner from click.testing import CliRunner
@ -20,11 +20,11 @@ def auto_shutdown_qapp() -> Iterator[None]:
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def args(slides_folder: Path) -> Iterator[Tuple[str, ...]]: def args(slides_folder: Path) -> Iterator[tuple[str, ...]]:
yield ("--folder", str(slides_folder), "--skip-all", "--playback-rate", "25") yield ("--folder", str(slides_folder), "--skip-all", "--playback-rate", "25")
def test_present(args: Tuple[str, ...]) -> None: def test_present(args: tuple[str, ...]) -> None:
runner = CliRunner() runner = CliRunner()
with runner.isolated_filesystem(): with runner.isolated_filesystem():
@ -34,7 +34,7 @@ def test_present(args: Tuple[str, ...]) -> None:
assert results.stdout == "" assert results.stdout == ""
def test_present_unexisting_slide(args: Tuple[str, ...]) -> None: def test_present_unexisting_slide(args: tuple[str, ...]) -> None:
runner = CliRunner() runner = CliRunner()
with runner.isolated_filesystem(): with runner.isolated_filesystem():
@ -44,7 +44,7 @@ def test_present_unexisting_slide(args: Tuple[str, ...]) -> None:
assert "UnexistingSlide.json does not exist" in results.stdout assert "UnexistingSlide.json does not exist" in results.stdout
def test_present_full_screen(args: Tuple[str, ...]) -> None: def test_present_full_screen(args: tuple[str, ...]) -> None:
runner = CliRunner() runner = CliRunner()
with runner.isolated_filesystem(): with runner.isolated_filesystem():
@ -54,7 +54,7 @@ def test_present_full_screen(args: Tuple[str, ...]) -> None:
assert results.stdout == "" assert results.stdout == ""
def test_present_hide_mouse(args: Tuple[str, ...]) -> None: def test_present_hide_mouse(args: tuple[str, ...]) -> None:
runner = CliRunner() runner = CliRunner()
with runner.isolated_filesystem(): with runner.isolated_filesystem():
@ -64,7 +64,7 @@ def test_present_hide_mouse(args: Tuple[str, ...]) -> None:
assert results.stdout == "" assert results.stdout == ""
def test_present_ignore_aspect_ratio(args: Tuple[str, ...]) -> None: def test_present_ignore_aspect_ratio(args: tuple[str, ...]) -> None:
runner = CliRunner() runner = CliRunner()
with runner.isolated_filesystem(): with runner.isolated_filesystem():
@ -76,7 +76,7 @@ def test_present_ignore_aspect_ratio(args: Tuple[str, ...]) -> None:
assert results.stdout == "" assert results.stdout == ""
def test_present_start_at(args: Tuple[str, ...]) -> None: def test_present_start_at(args: tuple[str, ...]) -> None:
runner = CliRunner() runner = CliRunner()
with runner.isolated_filesystem(): with runner.isolated_filesystem():
@ -86,7 +86,7 @@ def test_present_start_at(args: Tuple[str, ...]) -> None:
assert results.stdout == "" assert results.stdout == ""
def test_present_start_at_invalid(args: Tuple[str, ...]) -> None: def test_present_start_at_invalid(args: tuple[str, ...]) -> None:
runner = CliRunner() runner = CliRunner()
with runner.isolated_filesystem(): with runner.isolated_filesystem():
@ -96,7 +96,7 @@ def test_present_start_at_invalid(args: Tuple[str, ...]) -> None:
assert "Could not set presentation index to 1234" assert "Could not set presentation index to 1234"
def test_present_start_at_scene_number(args: Tuple[str, ...]) -> None: def test_present_start_at_scene_number(args: tuple[str, ...]) -> None:
runner = CliRunner() runner = CliRunner()
with runner.isolated_filesystem(): with runner.isolated_filesystem():
@ -108,7 +108,7 @@ def test_present_start_at_scene_number(args: Tuple[str, ...]) -> None:
assert results.stdout == "" assert results.stdout == ""
def test_present_start_at_slide_number(args: Tuple[str, ...]) -> None: def test_present_start_at_slide_number(args: tuple[str, ...]) -> None:
runner = CliRunner() runner = CliRunner()
with runner.isolated_filesystem(): with runner.isolated_filesystem():
@ -120,7 +120,7 @@ def test_present_start_at_slide_number(args: Tuple[str, ...]) -> None:
assert results.stdout == "" assert results.stdout == ""
def test_present_set_screen(args: Tuple[str, ...]) -> None: def test_present_set_screen(args: tuple[str, ...]) -> None:
runner = CliRunner() runner = CliRunner()
with runner.isolated_filesystem(): with runner.isolated_filesystem():
@ -131,7 +131,7 @@ def test_present_set_screen(args: Tuple[str, ...]) -> None:
@pytest.mark.skip(reason="Fails when running the whole test suite.") @pytest.mark.skip(reason="Fails when running the whole test suite.")
def test_present_set_invalid_screen(args: Tuple[str, ...]) -> None: def test_present_set_invalid_screen(args: tuple[str, ...]) -> None:
runner = CliRunner() runner = CliRunner()
with runner.isolated_filesystem(): with runner.isolated_filesystem():

View File

@ -1,7 +1,9 @@
import random import random
import shutil import shutil
import sys
from pathlib import Path from pathlib import Path
import manim
import numpy as np import numpy as np
import pytest import pytest
from click.testing import CliRunner from click.testing import CliRunner
@ -35,8 +37,9 @@ from manim_slides.slide.manim import Slide
pytest.param( pytest.param(
"--GL", "--GL",
marks=pytest.mark.skipif( marks=pytest.mark.skipif(
version.parse(np.__version__) >= version.parse("1.25"), version.parse(np.__version__) >= version.parse("1.25")
reason="ManimGL requires numpy<1.25, which is outdate", or sys.version_info >= (3, 12),
reason="ManimGL requires numpy<1.25, which is outdated and Python < 3.12",
), ),
), ),
], ],
@ -109,6 +112,10 @@ class TestSlide:
assert len(self._canvas) == 0 assert len(self._canvas) == 0
assert self._wait_time_between_slides == 0.0 assert self._wait_time_between_slides == 0.0
@pytest.mark.skipif(
version.parse(manim.__version__) < version.parse("0.18"),
reason="Manim change how color are represented in 0.18",
)
@assert_constructs @assert_constructs
class TestBackgroundColor(Slide): class TestBackgroundColor(Slide):
def construct(self) -> None: def construct(self) -> None:

View File

@ -1,17 +1,16 @@
from pathlib import Path from pathlib import Path
from typing import List
from manim_slides.utils import merge_basenames from manim_slides.utils import merge_basenames
def test_merge_basenames(paths: List[Path]) -> None: def test_merge_basenames(paths: list[Path]) -> None:
path = merge_basenames(paths) path = merge_basenames(paths)
assert path.suffix == paths[0].suffix assert path.suffix == paths[0].suffix
assert path.parent == paths[0].parent assert path.parent == paths[0].parent
def test_merge_basenames_same_with_different_parent_directories( def test_merge_basenames_same_with_different_parent_directories(
paths: List[Path], paths: list[Path],
) -> None: ) -> None:
d1 = Path("a/b/c") d1 = Path("a/b/c")
d2 = Path("d/e/f") d2 = Path("d/e/f")