mirror of
https://github.com/jeertmans/manim-slides.git
synced 2025-05-22 21:17:19 +08:00
chore(lib): add missing type hints and mypy is happy! (#75)
* chore(lib): add missing type hints and mypy is happy! Closes #34 * fix(ci): add missing dep for mypy
This commit is contained in:
@ -29,12 +29,12 @@ repos:
|
|||||||
rev: 'v0.991'
|
rev: 'v0.991'
|
||||||
hooks:
|
hooks:
|
||||||
- id: mypy
|
- id: mypy
|
||||||
|
additional_dependencies: [types-setuptools]
|
||||||
args:
|
args:
|
||||||
- --install-types
|
- --install-types
|
||||||
- --non-interactive
|
- --non-interactive
|
||||||
- --ignore-missing-imports
|
- --ignore-missing-imports
|
||||||
# Disallow dynamic typing
|
# Disallow dynamic typing
|
||||||
- --disallow-any-unimported
|
|
||||||
- --disallow-any-generics
|
- --disallow-any-generics
|
||||||
- --disallow-subclassing-any
|
- --disallow-subclassing-any
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ repos:
|
|||||||
# Configuring warnings
|
# Configuring warnings
|
||||||
- --warn-unused-ignores
|
- --warn-unused-ignores
|
||||||
- --warn-no-return
|
- --warn-no-return
|
||||||
- --warn-return-any
|
- --no-warn-return-any
|
||||||
- --warn-redundant-casts
|
- --warn-redundant-casts
|
||||||
|
|
||||||
# Strict equality
|
# Strict equality
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from typing import Callable
|
from typing import Any, Callable
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from click import Context, Parameter
|
from click import Context, Parameter
|
||||||
@ -6,10 +6,13 @@ from click import Context, Parameter
|
|||||||
from .defaults import CONFIG_PATH, FOLDER_PATH
|
from .defaults import CONFIG_PATH, FOLDER_PATH
|
||||||
from .manim import logger
|
from .manim import logger
|
||||||
|
|
||||||
|
F = Callable[..., Any]
|
||||||
|
Wrapper = Callable[[F], F]
|
||||||
|
|
||||||
def config_path_option(function: Callable) -> Callable:
|
|
||||||
|
def config_path_option(function: F) -> F:
|
||||||
"""Wraps a function to add configuration path option."""
|
"""Wraps a function to add configuration path option."""
|
||||||
return click.option(
|
wrapper: Wrapper = click.option(
|
||||||
"-c",
|
"-c",
|
||||||
"--config",
|
"--config",
|
||||||
"config_path",
|
"config_path",
|
||||||
@ -18,10 +21,11 @@ def config_path_option(function: Callable) -> Callable:
|
|||||||
type=click.Path(dir_okay=False),
|
type=click.Path(dir_okay=False),
|
||||||
help="Set path to configuration file.",
|
help="Set path to configuration file.",
|
||||||
show_default=True,
|
show_default=True,
|
||||||
)(function)
|
)
|
||||||
|
return wrapper(function)
|
||||||
|
|
||||||
|
|
||||||
def config_options(function: Callable) -> Callable:
|
def config_options(function: F) -> F:
|
||||||
"""Wraps a function to add configuration options."""
|
"""Wraps a function to add configuration options."""
|
||||||
function = config_path_option(function)
|
function = config_path_option(function)
|
||||||
function = click.option(
|
function = click.option(
|
||||||
@ -36,7 +40,7 @@ def config_options(function: Callable) -> Callable:
|
|||||||
return function
|
return function
|
||||||
|
|
||||||
|
|
||||||
def verbosity_option(function: Callable) -> Callable:
|
def verbosity_option(function: F) -> F:
|
||||||
"""Wraps a function to add verbosity option."""
|
"""Wraps a function to add verbosity option."""
|
||||||
|
|
||||||
def callback(ctx: Context, param: Parameter, value: bool) -> None:
|
def callback(ctx: Context, param: Parameter, value: bool) -> None:
|
||||||
@ -46,7 +50,7 @@ def verbosity_option(function: Callable) -> Callable:
|
|||||||
|
|
||||||
logger.setLevel(value)
|
logger.setLevel(value)
|
||||||
|
|
||||||
return click.option(
|
wrapper: Wrapper = click.option(
|
||||||
"-v",
|
"-v",
|
||||||
"--verbosity",
|
"--verbosity",
|
||||||
type=click.Choice(
|
type=click.Choice(
|
||||||
@ -59,16 +63,20 @@ def verbosity_option(function: Callable) -> Callable:
|
|||||||
envvar="MANIM_SLIDES_VERBOSITY",
|
envvar="MANIM_SLIDES_VERBOSITY",
|
||||||
show_envvar=True,
|
show_envvar=True,
|
||||||
callback=callback,
|
callback=callback,
|
||||||
)(function)
|
)
|
||||||
|
|
||||||
|
return wrapper(function)
|
||||||
|
|
||||||
|
|
||||||
def folder_path_option(function: Callable) -> Callable:
|
def folder_path_option(function: F) -> F:
|
||||||
"""Wraps a function to add folder path option."""
|
"""Wraps a function to add folder path option."""
|
||||||
return click.option(
|
wrapper: Wrapper = click.option(
|
||||||
"--folder",
|
"--folder",
|
||||||
metavar="DIRECTORY",
|
metavar="DIRECTORY",
|
||||||
default=FOLDER_PATH,
|
default=FOLDER_PATH,
|
||||||
type=click.Path(exists=True, file_okay=False),
|
type=click.Path(exists=True, file_okay=False),
|
||||||
help="Set slides folder.",
|
help="Set slides folder.",
|
||||||
show_default=True,
|
show_default=True,
|
||||||
)(function)
|
)
|
||||||
|
|
||||||
|
return wrapper(function)
|
||||||
|
@ -3,7 +3,7 @@ import shutil
|
|||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import List, Optional, Set
|
from typing import Callable, Dict, List, Optional, Set, Union
|
||||||
|
|
||||||
from pydantic import BaseModel, root_validator, validator
|
from pydantic import BaseModel, root_validator, validator
|
||||||
from PySide6.QtCore import Qt
|
from PySide6.QtCore import Qt
|
||||||
@ -24,7 +24,7 @@ def merge_basenames(files: List[str]) -> str:
|
|||||||
return os.path.join(dirname, basename + ext)
|
return os.path.join(dirname, basename + ext)
|
||||||
|
|
||||||
|
|
||||||
class Key(BaseModel):
|
class Key(BaseModel): # type: ignore
|
||||||
"""Represents a list of key codes, with optionally a name."""
|
"""Represents a list of key codes, with optionally a name."""
|
||||||
|
|
||||||
ids: Set[int]
|
ids: Set[int]
|
||||||
@ -48,7 +48,7 @@ class Key(BaseModel):
|
|||||||
return m
|
return m
|
||||||
|
|
||||||
|
|
||||||
class Config(BaseModel):
|
class Config(BaseModel): # type: ignore
|
||||||
"""General Manim Slides config"""
|
"""General Manim Slides config"""
|
||||||
|
|
||||||
QUIT: Key = Key(ids=[Qt.Key_Q], name="QUIT")
|
QUIT: Key = Key(ids=[Qt.Key_Q], name="QUIT")
|
||||||
@ -60,8 +60,8 @@ class Config(BaseModel):
|
|||||||
HIDE_MOUSE: Key = Key(ids=[Qt.Key_H], name="HIDE / SHOW MOUSE")
|
HIDE_MOUSE: Key = Key(ids=[Qt.Key_H], name="HIDE / SHOW MOUSE")
|
||||||
|
|
||||||
@root_validator
|
@root_validator
|
||||||
def ids_are_unique_across_keys(cls, values):
|
def ids_are_unique_across_keys(cls, values: Dict[str, Key]) -> Dict[str, Key]:
|
||||||
ids = 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:
|
||||||
@ -87,7 +87,7 @@ class SlideType(str, Enum):
|
|||||||
last = "last"
|
last = "last"
|
||||||
|
|
||||||
|
|
||||||
class SlideConfig(BaseModel):
|
class SlideConfig(BaseModel): # type: ignore
|
||||||
type: SlideType
|
type: SlideType
|
||||||
start_animation: int
|
start_animation: int
|
||||||
end_animation: int
|
end_animation: int
|
||||||
@ -95,20 +95,22 @@ class SlideConfig(BaseModel):
|
|||||||
terminated: bool = False
|
terminated: bool = False
|
||||||
|
|
||||||
@validator("start_animation", "end_animation")
|
@validator("start_animation", "end_animation")
|
||||||
def index_is_posint(cls, v: int):
|
def index_is_posint(cls, v: int) -> int:
|
||||||
if v < 0:
|
if v < 0:
|
||||||
raise ValueError("Animation index (start or end) cannot be negative")
|
raise ValueError("Animation index (start or end) cannot be negative")
|
||||||
return v
|
return v
|
||||||
|
|
||||||
@validator("number")
|
@validator("number")
|
||||||
def number_is_strictly_posint(cls, v: int):
|
def number_is_strictly_posint(cls, v: int) -> int:
|
||||||
if v <= 0:
|
if v <= 0:
|
||||||
raise ValueError("Slide number cannot be negative or zero")
|
raise ValueError("Slide number cannot be negative or zero")
|
||||||
return v
|
return v
|
||||||
|
|
||||||
@root_validator
|
@root_validator
|
||||||
def start_animation_is_before_end(cls, values):
|
def start_animation_is_before_end(
|
||||||
if values["start_animation"] >= values["end_animation"]:
|
cls, values: Dict[str, Union[SlideType, int, bool]]
|
||||||
|
) -> Dict[str, Union[SlideType, int, bool]]:
|
||||||
|
if values["start_animation"] >= values["end_animation"]: # type: ignore
|
||||||
|
|
||||||
if values["start_animation"] == values["end_animation"] == 0:
|
if values["start_animation"] == values["end_animation"] == 0:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@ -135,12 +137,12 @@ class SlideConfig(BaseModel):
|
|||||||
return slice(self.start_animation, self.end_animation)
|
return slice(self.start_animation, self.end_animation)
|
||||||
|
|
||||||
|
|
||||||
class PresentationConfig(BaseModel):
|
class PresentationConfig(BaseModel): # type: ignore
|
||||||
slides: List[SlideConfig]
|
slides: List[SlideConfig]
|
||||||
files: List[str]
|
files: List[str]
|
||||||
|
|
||||||
@validator("files", pre=True, each_item=True)
|
@validator("files", pre=True, each_item=True)
|
||||||
def is_file_and_exists(cls, v):
|
def is_file_and_exists(cls, v: str) -> str:
|
||||||
if not os.path.exists(v):
|
if not os.path.exists(v):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Animation file {v} does not exist. Are you in the right directory?"
|
f"Animation file {v} does not exist. Are you in the right directory?"
|
||||||
@ -152,7 +154,9 @@ class PresentationConfig(BaseModel):
|
|||||||
return v
|
return v
|
||||||
|
|
||||||
@root_validator
|
@root_validator
|
||||||
def animation_indices_match_files(cls, values):
|
def animation_indices_match_files(
|
||||||
|
cls, values: Dict[str, Union[List[SlideConfig], List[str]]]
|
||||||
|
) -> Dict[str, Union[List[SlideConfig], List[str]]]:
|
||||||
files = values.get("files")
|
files = values.get("files")
|
||||||
slides = values.get("slides")
|
slides = values.get("slides")
|
||||||
|
|
||||||
@ -162,18 +166,20 @@ class PresentationConfig(BaseModel):
|
|||||||
n_files = len(files)
|
n_files = len(files)
|
||||||
|
|
||||||
for slide in slides:
|
for slide in slides:
|
||||||
if slide.end_animation > n_files:
|
if slide.end_animation > n_files: # type: ignore
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"The following slide's contains animations not listed in files {files}: {slide}"
|
f"The following slide's contains animations not listed in files {files}: {slide}"
|
||||||
)
|
)
|
||||||
|
|
||||||
return values
|
return values
|
||||||
|
|
||||||
def move_to(self, dest: str, copy=True) -> "PresentationConfig":
|
def move_to(self, dest: str, copy: bool = True) -> "PresentationConfig":
|
||||||
"""
|
"""
|
||||||
Moves (or copy) the files to a given directory.
|
Moves (or copy) the files to a given directory.
|
||||||
"""
|
"""
|
||||||
move = shutil.copy if copy else shutil.move
|
copy_func: Callable[[str, str], None] = shutil.copy
|
||||||
|
move_func: Callable[[str, str], None] = shutil.move
|
||||||
|
move = copy_func if copy else move_func
|
||||||
|
|
||||||
n = len(self.files)
|
n = len(self.files)
|
||||||
for i in range(n):
|
for i in range(n):
|
||||||
|
@ -31,11 +31,11 @@ def validate_config_option(
|
|||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
class Converter(BaseModel):
|
class Converter(BaseModel): # type: ignore
|
||||||
presentation_configs: List[PresentationConfig] = []
|
presentation_configs: List[PresentationConfig] = []
|
||||||
assets_dir: str = "{basename}_assets"
|
assets_dir: str = "{basename}_assets"
|
||||||
|
|
||||||
def convert_to(self, dest: str):
|
def convert_to(self, dest: str) -> None:
|
||||||
"""Converts self, i.e., a list of presentations, into a given format."""
|
"""Converts self, i.e., a list of presentations, into a given format."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@ -105,7 +105,7 @@ class RevealJS(Converter):
|
|||||||
__name__, "data/revealjs_template.html"
|
__name__, "data/revealjs_template.html"
|
||||||
).decode()
|
).decode()
|
||||||
|
|
||||||
def convert_to(self, dest: str):
|
def convert_to(self, dest: str) -> None:
|
||||||
"""Converts this configuration into a RevealJS HTML presentation, saved to DEST."""
|
"""Converts this configuration into a RevealJS HTML presentation, saved to DEST."""
|
||||||
dirname = os.path.dirname(dest)
|
dirname = os.path.dirname(dest)
|
||||||
basename, ext = os.path.splitext(os.path.basename(dest))
|
basename, ext = os.path.splitext(os.path.basename(dest))
|
||||||
@ -130,7 +130,7 @@ class RevealJS(Converter):
|
|||||||
f.write(content)
|
f.write(content)
|
||||||
|
|
||||||
|
|
||||||
def show_config_options(function: Callable) -> Callable:
|
def show_config_options(function: Callable[..., Any]) -> Callable[..., Any]:
|
||||||
"""Wraps a function to add a `--show-config` option."""
|
"""Wraps a function to add a `--show-config` option."""
|
||||||
|
|
||||||
def callback(ctx: Context, param: Parameter, value: bool) -> None:
|
def callback(ctx: Context, param: Parameter, value: bool) -> None:
|
||||||
@ -191,7 +191,15 @@ def show_config_options(function: Callable) -> Callable:
|
|||||||
)
|
)
|
||||||
@show_config_options
|
@show_config_options
|
||||||
@verbosity_option
|
@verbosity_option
|
||||||
def convert(scenes, folder, dest, to, open_result, force, config_options):
|
def convert(
|
||||||
|
scenes: List[str],
|
||||||
|
folder: str,
|
||||||
|
dest: str,
|
||||||
|
to: str,
|
||||||
|
open_result: bool,
|
||||||
|
force: bool,
|
||||||
|
config_options: Dict[str, str],
|
||||||
|
) -> 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.
|
||||||
"""
|
"""
|
||||||
|
@ -3,7 +3,7 @@ import platform
|
|||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from enum import IntEnum, auto, unique
|
from enum import IntEnum, auto, unique
|
||||||
from typing import List, Optional, Tuple
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||||
|
|
||||||
import click
|
import click
|
||||||
import cv2
|
import cv2
|
||||||
@ -11,7 +11,7 @@ import numpy as np
|
|||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
from PySide6 import QtGui
|
from PySide6 import QtGui
|
||||||
from PySide6.QtCore import Qt, QThread, Signal, Slot
|
from PySide6.QtCore import Qt, QThread, Signal, Slot
|
||||||
from PySide6.QtGui import QIcon, QPixmap
|
from PySide6.QtGui import QCloseEvent, QIcon, QKeyEvent, QPixmap, QResizeEvent
|
||||||
from PySide6.QtWidgets import QApplication, QGridLayout, QLabel, QWidget
|
from PySide6.QtWidgets import QApplication, QGridLayout, QLabel, QWidget
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ class Presentation:
|
|||||||
|
|
||||||
self.current_slide_index: int = 0
|
self.current_slide_index: int = 0
|
||||||
self.current_animation: int = self.current_slide.start_animation
|
self.current_animation: int = self.current_slide.start_animation
|
||||||
self.current_file: Optional[str] = None
|
self.current_file: str = ""
|
||||||
|
|
||||||
self.loaded_animation_cap: int = -1
|
self.loaded_animation_cap: int = -1
|
||||||
self.cap = None # cap = cv2.VideoCapture
|
self.cap = None # cap = cv2.VideoCapture
|
||||||
@ -222,7 +222,7 @@ class Presentation:
|
|||||||
"""Returns current frame number."""
|
"""Returns current frame number."""
|
||||||
return int(self.current_cap.get(cv2.CAP_PROP_POS_FRAMES))
|
return int(self.current_cap.get(cv2.CAP_PROP_POS_FRAMES))
|
||||||
|
|
||||||
def update_state(self, state) -> Tuple[np.ndarray, State]:
|
def update_state(self, state: State) -> Tuple[np.ndarray, State]:
|
||||||
"""
|
"""
|
||||||
Updates the current state given the previous one.
|
Updates the current state given the previous one.
|
||||||
|
|
||||||
@ -236,7 +236,7 @@ class Presentation:
|
|||||||
still_playing, frame = self.current_cap.read()
|
still_playing, frame = self.current_cap.read()
|
||||||
if still_playing:
|
if still_playing:
|
||||||
self.lastframe = frame
|
self.lastframe = frame
|
||||||
elif state in [state.WAIT, state.PAUSED]:
|
elif state == state.WAIT or state == state.PAUSED: # type: ignore
|
||||||
return self.lastframe, state
|
return self.lastframe, state
|
||||||
elif self.current_slide.is_last() and self.current_slide.terminated:
|
elif self.current_slide.is_last() and self.current_slide.terminated:
|
||||||
return self.lastframe, State.END
|
return self.lastframe, State.END
|
||||||
@ -268,7 +268,7 @@ class Presentation:
|
|||||||
return self.lastframe, state
|
return self.lastframe, state
|
||||||
|
|
||||||
|
|
||||||
class Display(QThread):
|
class Display(QThread): # type: ignore
|
||||||
"""Displays one or more presentations one after each other."""
|
"""Displays one or more presentations one after each other."""
|
||||||
|
|
||||||
change_video_signal = Signal(np.ndarray)
|
change_video_signal = Signal(np.ndarray)
|
||||||
@ -277,12 +277,12 @@ class Display(QThread):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
presentations,
|
presentations: List[PresentationConfig],
|
||||||
config: Config = DEFAULT_CONFIG,
|
config: Config = DEFAULT_CONFIG,
|
||||||
start_paused=False,
|
start_paused: bool = False,
|
||||||
skip_all=False,
|
skip_all: bool = False,
|
||||||
record_to=None,
|
record_to: Optional[str] = None,
|
||||||
exit_after_last_slide=False,
|
exit_after_last_slide: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.presentations = presentations
|
self.presentations = presentations
|
||||||
@ -444,14 +444,14 @@ class Display(QThread):
|
|||||||
|
|
||||||
self.key = -1 # No more key to be handled
|
self.key = -1 # No more key to be handled
|
||||||
|
|
||||||
def stop(self):
|
def stop(self) -> None:
|
||||||
"""Stops current thread, without doing anything after."""
|
"""Stops current thread, without doing anything after."""
|
||||||
self.run_flag = False
|
self.run_flag = False
|
||||||
self.wait()
|
self.wait()
|
||||||
|
|
||||||
|
|
||||||
class Info(QWidget):
|
class Info(QWidget): # type: ignore
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.setWindowTitle(WINDOW_INFO_NAME)
|
self.setWindowTitle(WINDOW_INFO_NAME)
|
||||||
|
|
||||||
@ -474,7 +474,7 @@ class Info(QWidget):
|
|||||||
self.update_info({})
|
self.update_info({})
|
||||||
|
|
||||||
@Slot(dict)
|
@Slot(dict)
|
||||||
def update_info(self, info: dict):
|
def update_info(self, info: Dict[str, Union[str, int]]) -> None:
|
||||||
self.animationLabel.setText("Animation: {}".format(info.get("animation", "na")))
|
self.animationLabel.setText("Animation: {}".format(info.get("animation", "na")))
|
||||||
self.stateLabel.setText("State: {}".format(info.get("state", "unknown")))
|
self.stateLabel.setText("State: {}".format(info.get("state", "unknown")))
|
||||||
self.slideLabel.setText(
|
self.slideLabel.setText(
|
||||||
@ -490,27 +490,27 @@ class Info(QWidget):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class InfoThread(QThread):
|
class InfoThread(QThread): # type: ignore
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.dialog = Info()
|
self.dialog = Info()
|
||||||
self.run_flag = True
|
self.run_flag = True
|
||||||
|
|
||||||
def start(self):
|
def start(self) -> None:
|
||||||
super().start()
|
super().start()
|
||||||
|
|
||||||
self.dialog.show()
|
self.dialog.show()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self) -> None:
|
||||||
self.dialog.deleteLater()
|
self.dialog.deleteLater()
|
||||||
|
|
||||||
|
|
||||||
class App(QWidget):
|
class App(QWidget): # type: ignore
|
||||||
send_key_signal = Signal(int)
|
send_key_signal = Signal(int)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*args,
|
*args: Any,
|
||||||
config: Config = DEFAULT_CONFIG,
|
config: Config = DEFAULT_CONFIG,
|
||||||
fullscreen: bool = False,
|
fullscreen: bool = False,
|
||||||
resolution: Tuple[int, int] = (1980, 1080),
|
resolution: Tuple[int, int] = (1980, 1080),
|
||||||
@ -518,7 +518,7 @@ class App(QWidget):
|
|||||||
aspect_ratio: Qt.AspectRatioMode = Qt.IgnoreAspectRatio,
|
aspect_ratio: Qt.AspectRatioMode = Qt.IgnoreAspectRatio,
|
||||||
resize_mode: Qt.TransformationMode = Qt.SmoothTransformation,
|
resize_mode: Qt.TransformationMode = Qt.SmoothTransformation,
|
||||||
background_color: str = "black",
|
background_color: str = "black",
|
||||||
**kwargs,
|
**kwargs: Any,
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
@ -543,7 +543,8 @@ class App(QWidget):
|
|||||||
self.label.setMinimumSize(1, 1)
|
self.label.setMinimumSize(1, 1)
|
||||||
|
|
||||||
# create the video capture thread
|
# create the video capture thread
|
||||||
self.thread = Display(*args, config=config, **kwargs)
|
kwargs["config"] = config
|
||||||
|
self.thread = Display(*args, **kwargs)
|
||||||
# create the info dialog
|
# create the info dialog
|
||||||
self.info = Info()
|
self.info = Info()
|
||||||
self.info.show()
|
self.info.show()
|
||||||
@ -563,7 +564,7 @@ class App(QWidget):
|
|||||||
# start the thread
|
# start the thread
|
||||||
self.thread.start()
|
self.thread.start()
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
def keyPressEvent(self, event: QKeyEvent) -> None:
|
||||||
|
|
||||||
key = event.key()
|
key = event.key()
|
||||||
if self.config.HIDE_MOUSE.match(key):
|
if self.config.HIDE_MOUSE.match(key):
|
||||||
@ -577,35 +578,30 @@ class App(QWidget):
|
|||||||
self.send_key_signal.emit(key)
|
self.send_key_signal.emit(key)
|
||||||
event.accept()
|
event.accept()
|
||||||
|
|
||||||
def closeAll(self):
|
def closeAll(self) -> None:
|
||||||
logger.debug("Closing all QT windows")
|
logger.debug("Closing all QT windows")
|
||||||
self.thread.stop()
|
self.thread.stop()
|
||||||
self.info.deleteLater()
|
self.info.deleteLater()
|
||||||
self.deleteLater()
|
self.deleteLater()
|
||||||
|
|
||||||
def resizeEvent(self, event):
|
def resizeEvent(self, event: QResizeEvent) -> None:
|
||||||
self.pixmap = self.pixmap.scaled(
|
self.pixmap = self.pixmap.scaled(
|
||||||
self.width(), self.height(), self.aspect_ratio, self.resize_mode
|
self.width(), self.height(), self.aspect_ratio, self.resize_mode
|
||||||
)
|
)
|
||||||
self.label.setPixmap(self.pixmap)
|
self.label.setPixmap(self.pixmap)
|
||||||
self.label.resize(self.width(), self.height())
|
self.label.resize(self.width(), self.height())
|
||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event: QCloseEvent) -> None:
|
||||||
self.closeAll()
|
self.closeAll()
|
||||||
event.accept()
|
event.accept()
|
||||||
|
|
||||||
@Slot(np.ndarray)
|
@Slot(np.ndarray)
|
||||||
def update_image(self, cv_img: dict):
|
def update_image(self, cv_img: np.ndarray) -> None:
|
||||||
"""Updates the image_label with a new opencv image"""
|
"""Updates the (image) label with a new opencv image"""
|
||||||
self.pixmap = self.convert_cv_qt(cv_img)
|
self.pixmap = self.convert_cv_qt(cv_img)
|
||||||
self.label.setPixmap(self.pixmap)
|
self.label.setPixmap(self.pixmap)
|
||||||
|
|
||||||
@Slot(dict)
|
def convert_cv_qt(self, cv_img: np.ndarray) -> np.ndarray:
|
||||||
def update_info(self, info: dict):
|
|
||||||
"""Updates the image_label with a new opencv image"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def convert_cv_qt(self, cv_img):
|
|
||||||
"""Convert from an opencv image to QPixmap"""
|
"""Convert from an opencv image to QPixmap"""
|
||||||
rgb_image = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)
|
rgb_image = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)
|
||||||
h, w, ch = rgb_image.shape
|
h, w, ch = rgb_image.shape
|
||||||
@ -633,14 +629,14 @@ class App(QWidget):
|
|||||||
)
|
)
|
||||||
@click.help_option("-h", "--help")
|
@click.help_option("-h", "--help")
|
||||||
@verbosity_option
|
@verbosity_option
|
||||||
def list_scenes(folder) -> None:
|
def list_scenes(folder: str) -> None:
|
||||||
"""List available scenes."""
|
"""List available scenes."""
|
||||||
|
|
||||||
for i, scene in enumerate(_list_scenes(folder), start=1):
|
for i, scene in enumerate(_list_scenes(folder), start=1):
|
||||||
click.secho(f"{i}: {scene}", fg="green")
|
click.secho(f"{i}: {scene}", fg="green")
|
||||||
|
|
||||||
|
|
||||||
def _list_scenes(folder) -> List[str]:
|
def _list_scenes(folder: str) -> List[str]:
|
||||||
"""Lists available scenes in given directory."""
|
"""Lists available scenes in given directory."""
|
||||||
scenes = []
|
scenes = []
|
||||||
|
|
||||||
@ -792,19 +788,19 @@ def get_scenes_presentation_config(
|
|||||||
@click.help_option("-h", "--help")
|
@click.help_option("-h", "--help")
|
||||||
@verbosity_option
|
@verbosity_option
|
||||||
def present(
|
def present(
|
||||||
scenes,
|
scenes: List[str],
|
||||||
config_path,
|
config_path: str,
|
||||||
folder,
|
folder: str,
|
||||||
start_paused,
|
start_paused: bool,
|
||||||
fullscreen,
|
fullscreen: bool,
|
||||||
skip_all,
|
skip_all: bool,
|
||||||
resolution,
|
resolution: Tuple[int, int],
|
||||||
record_to,
|
record_to: Optional[str],
|
||||||
exit_after_last_slide,
|
exit_after_last_slide: bool,
|
||||||
hide_mouse,
|
hide_mouse: bool,
|
||||||
aspect_ratio,
|
aspect_ratio: str,
|
||||||
resize_mode,
|
resize_mode: str,
|
||||||
background_color,
|
background_color: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Present SCENE(s), one at a time, in order.
|
Present SCENE(s), one at a time, in order.
|
||||||
|
@ -25,7 +25,7 @@ def reverse_video_file(src: str, dst: str) -> None:
|
|||||||
logger.debug(error.decode())
|
logger.debug(error.decode())
|
||||||
|
|
||||||
|
|
||||||
class Slide(Scene):
|
class Slide(Scene): # type:ignore
|
||||||
"""
|
"""
|
||||||
Inherits from `manim.Scene` or `manimlib.Scene` and provide necessary tools for slides rendering.
|
Inherits from `manim.Scene` or `manimlib.Scene` and provide necessary tools for slides rendering.
|
||||||
"""
|
"""
|
||||||
@ -222,7 +222,7 @@ class Slide(Scene):
|
|||||||
self.save_slides()
|
self.save_slides()
|
||||||
|
|
||||||
|
|
||||||
class ThreeDSlide(Slide, ThreeDScene):
|
class ThreeDSlide(Slide, ThreeDScene): # type: ignore
|
||||||
"""
|
"""
|
||||||
Inherits from `manim.ThreeDScene` or `manimlib.ThreeDScene` and provide necessary tools for slides rendering.
|
Inherits from `manim.ThreeDScene` or `manimlib.ThreeDScene` and provide necessary tools for slides rendering.
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ from typing import Any
|
|||||||
|
|
||||||
import click
|
import click
|
||||||
from PySide6.QtCore import Qt
|
from PySide6.QtCore import Qt
|
||||||
from PySide6.QtGui import QIcon
|
from PySide6.QtGui import QIcon, QKeyEvent
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QApplication,
|
QApplication,
|
||||||
QDialog,
|
QDialog,
|
||||||
@ -31,7 +31,7 @@ for key in Qt.Key:
|
|||||||
keymap[key.value] = key.name.partition("_")[2]
|
keymap[key.value] = key.name.partition("_")[2]
|
||||||
|
|
||||||
|
|
||||||
class KeyInput(QDialog):
|
class KeyInput(QDialog): # type: ignore
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.key = None
|
self.key = None
|
||||||
@ -43,13 +43,13 @@ class KeyInput(QDialog):
|
|||||||
self.layout.addWidget(self.label)
|
self.layout.addWidget(self.label)
|
||||||
self.setLayout(self.layout)
|
self.setLayout(self.layout)
|
||||||
|
|
||||||
def keyPressEvent(self, event: Any) -> None:
|
def keyPressEvent(self, event: QKeyEvent) -> None:
|
||||||
self.key = event.key()
|
self.key = event.key()
|
||||||
self.deleteLater()
|
self.deleteLater()
|
||||||
event.accept()
|
event.accept()
|
||||||
|
|
||||||
|
|
||||||
class Wizard(QWidget):
|
class Wizard(QWidget): # type: ignore
|
||||||
def __init__(self, config: Config):
|
def __init__(self, config: Config):
|
||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -131,7 +131,7 @@ class Wizard(QWidget):
|
|||||||
@config_options
|
@config_options
|
||||||
@click.help_option("-h", "--help")
|
@click.help_option("-h", "--help")
|
||||||
@verbosity_option
|
@verbosity_option
|
||||||
def wizard(config_path, force, merge):
|
def wizard(config_path: str, force: bool, merge: bool) -> None:
|
||||||
"""Launch configuration wizard."""
|
"""Launch configuration wizard."""
|
||||||
return _init(config_path, force, merge, skip_interactive=False)
|
return _init(config_path, force, merge, skip_interactive=False)
|
||||||
|
|
||||||
@ -140,12 +140,16 @@ def wizard(config_path, force, merge):
|
|||||||
@config_options
|
@config_options
|
||||||
@click.help_option("-h", "--help")
|
@click.help_option("-h", "--help")
|
||||||
@verbosity_option
|
@verbosity_option
|
||||||
def init(config_path, force, merge, skip_interactive=False):
|
def init(
|
||||||
|
config_path: str, force: bool, merge: bool, skip_interactive: bool = False
|
||||||
|
) -> None:
|
||||||
"""Initialize a new default configuration file."""
|
"""Initialize a new default configuration file."""
|
||||||
return _init(config_path, force, merge, skip_interactive=True)
|
return _init(config_path, force, merge, skip_interactive=True)
|
||||||
|
|
||||||
|
|
||||||
def _init(config_path, force, merge, skip_interactive=False):
|
def _init(
|
||||||
|
config_path: str, force: bool, merge: bool, skip_interactive: bool = False
|
||||||
|
) -> None:
|
||||||
"""Actual initialization code for configuration file, with optional interactive mode."""
|
"""Actual initialization code for configuration file, with optional interactive mode."""
|
||||||
|
|
||||||
if os.path.exists(config_path):
|
if os.path.exists(config_path):
|
||||||
|
Reference in New Issue
Block a user