Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
MANIM_SLIDES_VERBOSITY: debug
PYTHONFAULTHANDLER: 1
DISPLAY: :99
GITHUB_WORKFLOWS: 1
steps:
- name: Checkout repository
uses: actions/checkout@v4
Expand Down Expand Up @@ -68,7 +69,7 @@ jobs:

- name: Run pytest
if: matrix.os != 'ubuntu-latest' || matrix.pyversion != '3.11'
run: poetry run pytest -x -n auto
run: poetry run pytest

- name: Run pytest and coverage
if: matrix.os == 'ubuntu-latest' && matrix.pyversion == '3.11'
Expand Down
11 changes: 0 additions & 11 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@ repos:
- id: check-toml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort
name: isort (python)
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
rev: v2.11.0
hooks:
Expand All @@ -29,12 +24,6 @@ repos:
- id: blacken-docs
additional_dependencies:
- black==23.9.1
- repo: https://github.com/PyCQA/docformatter
rev: v1.7.5
hooks:
- id: docformatter
additional_dependencies: [tomli]
args: [--in-place, --config, ./pyproject.toml]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.292
hooks:
Expand Down
5 changes: 2 additions & 3 deletions manim_slides/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
# flake8: noqa: F401
import sys
from types import ModuleType
from typing import Any, List

from .__version__ import __version__


class module(ModuleType):
class Module(ModuleType):
def __getattr__(self, name: str) -> Any:
if name == "Slide" or name == "ThreeDSlide":
module = __import__(
Expand Down Expand Up @@ -48,7 +47,7 @@ def __dir__(self) -> List[str]:


old_module = sys.modules["manim_slides"]
new_module = sys.modules["manim_slides"] = module("manim_slides")
new_module = sys.modules["manim_slides"] = Module("manim_slides")

new_module.__dict__.update(
{
Expand Down
8 changes: 4 additions & 4 deletions manim_slides/commons.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@


def config_path_option(function: F) -> F:
"""Wraps a function to add configuration path option."""
"""Wrap a function to add configuration path option."""
wrapper: Wrapper = click.option(
"-c",
"--config",
Expand All @@ -27,7 +27,7 @@ def config_path_option(function: F) -> F:


def config_options(function: F) -> F:
"""Wraps a function to add configuration options."""
"""Wrap a function to add configuration options."""
function = config_path_option(function)
function = click.option(
"-f", "--force", is_flag=True, help="Overwrite any existing configuration file."
Expand All @@ -42,7 +42,7 @@ def config_options(function: F) -> F:


def verbosity_option(function: F) -> F:
"""Wraps a function to add verbosity option."""
"""Wrap a function to add verbosity option."""

def callback(ctx: Context, param: Parameter, value: str) -> None:
if not value or ctx.resilient_parsing:
Expand All @@ -69,7 +69,7 @@ def callback(ctx: Context, param: Parameter, value: str) -> None:


def folder_path_option(function: F) -> F:
"""Wraps a function to add folder path option."""
"""Wrap a function to add folder path option."""
wrapper: Wrapper = click.option(
"--folder",
metavar="DIRECTORY",
Expand Down
13 changes: 8 additions & 5 deletions manim_slides/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class Keys(BaseModel): # type: ignore[misc]
HIDE_MOUSE: Key = Key(ids=[Qt.Key_H], name="HIDE / SHOW MOUSE")

@model_validator(mode="before")
@classmethod
def ids_are_unique_across_keys(cls, values: Dict[str, Key]) -> Dict[str, Key]:
ids: Set[int] = set()

Expand Down Expand Up @@ -121,14 +122,15 @@ class Config(BaseModel): # type: ignore[misc]

@classmethod
def from_file(cls, path: Path) -> "Config":
"""Reads a configuration from a file."""
"""Read a configuration from a file."""
return cls.model_validate(rtoml.load(path)) # type: ignore

def to_file(self, path: Path) -> None:
"""Dumps the configuration to a file."""
"""Dump the configuration to a file."""
rtoml.dump(self.model_dump(), path, pretty=True)

def merge_with(self, other: "Config") -> "Config":
"""Merge with another config."""
self.keys = self.keys.merge_with(other.keys)
return self

Expand All @@ -146,6 +148,7 @@ def index_is_posint(cls, v: int) -> int:
return v

@model_validator(mode="after")
@classmethod
def start_animation_is_before_end(
cls, pre_slide_config: "PreSlideConfig"
) -> "PreSlideConfig":
Expand Down Expand Up @@ -185,8 +188,8 @@ class PresentationConfig(BaseModel): # type: ignore[misc]

@classmethod
def from_file(cls, path: Path) -> "PresentationConfig":
"""Reads a presentation configuration from a file."""
with open(path, "r") as f:
"""Read a presentation configuration from a file."""
with open(path) as f:
obj = json.load(f)

slides = obj.setdefault("slides", [])
Expand All @@ -202,7 +205,7 @@ def from_file(cls, path: Path) -> "PresentationConfig":
return cls.model_validate(obj) # type: ignore

def to_file(self, path: Path) -> None:
"""Dumps the presentation configuration to a file."""
"""Dump the presentation configuration to a file."""
with open(path, "w") as f:
f.write(self.model_dump_json(indent=2))

Expand Down
42 changes: 23 additions & 19 deletions manim_slides/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from enum import Enum
from importlib import resources
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Type, Union
from typing import Any, Callable, ClassVar, Dict, List, Optional, Type, Union

import click
import cv2
Expand Down Expand Up @@ -60,13 +60,13 @@ def validate_config_option(
except ValueError:
raise click.BadParameter(
f"Configuration options `{c_option}` could not be parsed into a proper (key, value) pair. Please use an `=` sign to separate key from value."
)
) from None

return config


def file_to_data_uri(file: Path) -> str:
"""Reads a video and returns the corresponding data-uri."""
"""Read a video and return the corresponding data-uri."""
b64 = b64encode(file.read_bytes()).decode("ascii")
mime_type = mimetypes.guess_type(file)[0] or "video/mp4"

Expand All @@ -79,24 +79,24 @@ class Converter(BaseModel): # type: ignore
template: Optional[Path] = None

def convert_to(self, dest: Path) -> None:
"""Converts self, i.e., a list of presentations, into a given format."""
"""Convert self, i.e., a list of presentations, into a given format."""
raise NotImplementedError

def load_template(self) -> str:
"""
Returns the template as a string.
Return the template as a string.

An empty string is returned if no template is used.
"""
return ""

def open(self, file: Path) -> Any:
"""Opens a file, generated with converter, using appropriate application."""
"""Open a file, generated with converter, using appropriate application."""
raise NotImplementedError

@classmethod
def from_string(cls, s: str) -> Type["Converter"]:
"""Returns the appropriate converter from a string name."""
"""Return the appropriate converter from a string name."""
return {
"html": RevealJS,
"pdf": PDF,
Expand All @@ -117,7 +117,7 @@ def __get_pydantic_core_schema__(
return core_schema.str_schema()

def __str__(self) -> str:
"""Ensures that the string is correctly quoted."""
"""Ensure that the string is correctly quoted."""
if self in ["true", "false", "null"]:
return self
else:
Expand Down Expand Up @@ -303,7 +303,7 @@ class RevealJS(Converter):
auto_animate_easing: AutoAnimateEasing = AutoAnimateEasing.ease
auto_animate_duration: float = 1.0
auto_animate_unmatched: JsBool = JsBool.true
auto_animate_styles: List[str] = [
auto_animate_styles: ClassVar[List[str]] = [
"opacity",
"color",
"background-color",
Expand Down Expand Up @@ -346,7 +346,7 @@ class RevealJS(Converter):
model_config = ConfigDict(use_enum_values=True, extra="forbid")

def load_template(self) -> str:
"""Returns the RevealJS HTML template as a string."""
"""Return the RevealJS HTML template as a string."""
if isinstance(self.template, Path):
return self.template.read_text()

Expand All @@ -359,8 +359,10 @@ def open(self, file: Path) -> bool:
return webbrowser.open(file.absolute().as_uri())

def convert_to(self, dest: Path) -> None:
"""Converts this configuration into a RevealJS HTML presentation, saved to
DEST."""
"""
Convert this configuration into a RevealJS HTML presentation, saved to
DEST.
"""
if self.data_uri:
assets_dir = Path("") # Actually we won't care.
else:
Expand Down Expand Up @@ -409,7 +411,7 @@ def open(self, file: Path) -> None:
return open_with_default(file)

def convert_to(self, dest: Path) -> None:
"""Converts this configuration into a PDF presentation, saved to DEST."""
"""Convert this configuration into a PDF presentation, saved to DEST."""

def read_image_from_video_file(file: Path, frame_index: FrameIndex) -> Image:
cap = cv2.VideoCapture(str(file))
Expand All @@ -419,6 +421,7 @@ def read_image_from_video_file(file: Path, frame_index: FrameIndex) -> Image:
cap.set(cv2.CAP_PROP_POS_FRAMES, index - 1)

ret, frame = cap.read()
cap.release()

if ret:
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
Expand Down Expand Up @@ -462,7 +465,7 @@ def open(self, file: Path) -> None:
return open_with_default(file)

def convert_to(self, dest: Path) -> None:
"""Converts this configuration into a PowerPoint presentation, saved to DEST."""
"""Convert this configuration into a PowerPoint presentation, saved to DEST."""
prs = pptx.Presentation()
prs.slide_width = self.width * 9525
prs.slide_height = self.height * 9525
Expand Down Expand Up @@ -493,10 +496,12 @@ def xpath(el: etree.Element, query: str) -> etree.XPath:
def save_first_image_from_video_file(file: Path) -> Optional[str]:
cap = cv2.VideoCapture(file.as_posix())
ret, frame = cap.read()
cap.release()

if ret:
f = tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".png")
cv2.imwrite(f.name, frame)
f.close()
return f.name
else:
logger.warn("Failed to read first image from video file")
Expand Down Expand Up @@ -535,7 +540,7 @@ def save_first_image_from_video_file(file: Path) -> Optional[str]:


def show_config_options(function: Callable[..., Any]) -> Callable[..., Any]:
"""Wraps a function to add a `--show-config` option."""
"""Wrap a function to add a `--show-config` option."""

def callback(ctx: Context, param: Parameter, value: bool) -> None:
if not value or ctx.resilient_parsing:
Expand All @@ -547,7 +552,7 @@ def callback(ctx: Context, param: Parameter, value: bool) -> None:
presentation_configs=[PresentationConfig()]
)
for key, value in converter.dict().items():
click.echo(f"{key}: {repr(value)}")
click.echo(f"{key}: {value!r}")

ctx.exit()

Expand All @@ -563,7 +568,7 @@ def callback(ctx: Context, param: Parameter, value: bool) -> None:


def show_template_option(function: Callable[..., Any]) -> Callable[..., Any]:
"""Wraps a function to add a `--show-template` option."""
"""Wrap a function to add a `--show-template` option."""

def callback(ctx: Context, param: Parameter, value: bool) -> None:
if not value or ctx.resilient_parsing:
Expand Down Expand Up @@ -637,7 +642,6 @@ def convert(
template: Optional[Path],
) -> None:
"""Convert SCENE(s) into a given format and writes the result in DEST."""

presentation_configs = get_scenes_presentation_config(scenes, folder)

try:
Expand All @@ -664,4 +668,4 @@ def convert(
_msg = error["msg"]
msg.append(f"Option '{option}': {_msg}")

raise click.UsageError("\n".join(msg))
raise click.UsageError("\n".join(msg)) from None
Loading