mirror of
https://github.com/jeertmans/manim-slides.git
synced 2025-08-06 14:19:52 +08:00
feat(cli): add basic logging facilities (#50)
* feat(cli): add basic logging facilities This PR adds basic logging support, to be extended in the future. * fix: typo in decorator name
This commit is contained in:
@ -1,11 +1,13 @@
|
|||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
from click import Context, Parameter
|
||||||
|
|
||||||
from .defaults import CONFIG_PATH
|
from .defaults import CONFIG_PATH
|
||||||
|
from .manim import logger
|
||||||
|
|
||||||
|
|
||||||
def config_path_option(function) -> Callable:
|
def config_path_option(function: Callable) -> Callable:
|
||||||
"""Wraps a function to add configuration path option."""
|
"""Wraps a function to add configuration path option."""
|
||||||
return click.option(
|
return click.option(
|
||||||
"-c",
|
"-c",
|
||||||
@ -18,7 +20,7 @@ def config_path_option(function) -> Callable:
|
|||||||
)(function)
|
)(function)
|
||||||
|
|
||||||
|
|
||||||
def config_options(function) -> Callable:
|
def config_options(function: Callable) -> Callable:
|
||||||
"""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(
|
||||||
@ -31,3 +33,27 @@ def config_options(function) -> Callable:
|
|||||||
help="Merge any existing configuration file with the new configuration.",
|
help="Merge any existing configuration file with the new configuration.",
|
||||||
)(function)
|
)(function)
|
||||||
return function
|
return function
|
||||||
|
|
||||||
|
|
||||||
|
def verbosity_option(function: Callable) -> Callable:
|
||||||
|
"""Wraps a function to add verbosity option."""
|
||||||
|
|
||||||
|
def callback(ctx: Context, param: Parameter, value: bool) -> None:
|
||||||
|
|
||||||
|
if not value or ctx.resilient_parsing:
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.setLevel(value)
|
||||||
|
|
||||||
|
return click.option(
|
||||||
|
"-v",
|
||||||
|
"--verbosity",
|
||||||
|
type=click.Choice(
|
||||||
|
["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
|
||||||
|
case_sensitive=False,
|
||||||
|
),
|
||||||
|
help="Verbosity of CLI output",
|
||||||
|
default=None,
|
||||||
|
expose_value=False,
|
||||||
|
callback=callback,
|
||||||
|
)(function)
|
||||||
|
@ -5,6 +5,7 @@ from typing import List, Optional, Set
|
|||||||
from pydantic import BaseModel, root_validator, validator
|
from pydantic import BaseModel, root_validator, validator
|
||||||
|
|
||||||
from .defaults import LEFT_ARROW_KEY_CODE, RIGHT_ARROW_KEY_CODE
|
from .defaults import LEFT_ARROW_KEY_CODE, RIGHT_ARROW_KEY_CODE
|
||||||
|
from .manim import logger
|
||||||
|
|
||||||
|
|
||||||
class Key(BaseModel):
|
class Key(BaseModel):
|
||||||
@ -20,7 +21,12 @@ class Key(BaseModel):
|
|||||||
return v
|
return v
|
||||||
|
|
||||||
def match(self, key_id: int):
|
def match(self, key_id: int):
|
||||||
return key_id in self.ids
|
m = key_id in self.ids
|
||||||
|
|
||||||
|
if m:
|
||||||
|
logger.debug(f"Pressed key: {self.name}")
|
||||||
|
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
class Config(BaseModel):
|
class Config(BaseModel):
|
||||||
|
@ -11,9 +11,10 @@ import numpy as np
|
|||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
from .commons import config_path_option
|
from .commons import config_path_option, verbosity_option
|
||||||
from .config import Config, PresentationConfig, SlideConfig, SlideType
|
from .config import Config, PresentationConfig, SlideConfig, SlideType
|
||||||
from .defaults import FOLDER_PATH, FONT_ARGS
|
from .defaults import FOLDER_PATH, FONT_ARGS
|
||||||
|
from .manim import logger
|
||||||
|
|
||||||
INTERPOLATION_FLAGS = {
|
INTERPOLATION_FLAGS = {
|
||||||
"nearest": cv2.INTER_NEAREST,
|
"nearest": cv2.INTER_NEAREST,
|
||||||
@ -450,6 +451,9 @@ class Display:
|
|||||||
cv2.destroyAllWindows()
|
cv2.destroyAllWindows()
|
||||||
|
|
||||||
if self.record_to is not None and len(self.recordings) > 0:
|
if self.record_to is not None and len(self.recordings) > 0:
|
||||||
|
logger.debug(
|
||||||
|
f"A total of {len(self.recordings)} frames will be saved to {self.record_to}"
|
||||||
|
)
|
||||||
file, frame_number, fps = self.recordings[0]
|
file, frame_number, fps = self.recordings[0]
|
||||||
|
|
||||||
cap = cv2.VideoCapture(file)
|
cap = cv2.VideoCapture(file)
|
||||||
@ -489,6 +493,7 @@ class Display:
|
|||||||
show_default=True,
|
show_default=True,
|
||||||
)
|
)
|
||||||
@click.help_option("-h", "--help")
|
@click.help_option("-h", "--help")
|
||||||
|
@verbosity_option
|
||||||
def list_scenes(folder) -> None:
|
def list_scenes(folder) -> None:
|
||||||
"""List available scenes."""
|
"""List available scenes."""
|
||||||
|
|
||||||
@ -502,13 +507,18 @@ def _list_scenes(folder) -> List[str]:
|
|||||||
|
|
||||||
for file in os.listdir(folder):
|
for file in os.listdir(folder):
|
||||||
if file.endswith(".json"):
|
if file.endswith(".json"):
|
||||||
try:
|
|
||||||
filepath = os.path.join(folder, file)
|
filepath = os.path.join(folder, file)
|
||||||
|
try:
|
||||||
_ = PresentationConfig.parse_file(filepath)
|
_ = PresentationConfig.parse_file(filepath)
|
||||||
scenes.append(os.path.basename(file)[:-5])
|
scenes.append(os.path.basename(file)[:-5])
|
||||||
except Exception: # Could not parse this file as a proper presentation config
|
except Exception as e: # Could not parse this file as a proper presentation config
|
||||||
|
logger.warn(
|
||||||
|
f"Something went wrong with parsing presentation config `{filepath}`: {e}"
|
||||||
|
)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
logger.info(f"Found {len(scenes)} valid scene configuration files in `{folder}`.")
|
||||||
|
|
||||||
return scenes
|
return scenes
|
||||||
|
|
||||||
|
|
||||||
@ -552,6 +562,7 @@ def _list_scenes(folder) -> List[str]:
|
|||||||
help="If set, the presentation will be recorded into a AVI video file with given name.",
|
help="If set, the presentation will be recorded into a AVI video file with given name.",
|
||||||
)
|
)
|
||||||
@click.help_option("-h", "--help")
|
@click.help_option("-h", "--help")
|
||||||
|
@verbosity_option
|
||||||
def present(
|
def present(
|
||||||
scenes,
|
scenes,
|
||||||
config_path,
|
config_path,
|
||||||
@ -616,8 +627,8 @@ def present(
|
|||||||
f"File {config_file} does not exist, check the scene name and make sure to use Slide as your scene base class"
|
f"File {config_file} does not exist, check the scene name and make sure to use Slide as your scene base class"
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
config = PresentationConfig.parse_file(config_file)
|
pres_config = PresentationConfig.parse_file(config_file)
|
||||||
presentations.append(Presentation(config))
|
presentations.append(Presentation(pres_config))
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
raise click.UsageError(str(e))
|
raise click.UsageError(str(e))
|
||||||
|
|
||||||
@ -627,6 +638,7 @@ def present(
|
|||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
raise click.UsageError(str(e))
|
raise click.UsageError(str(e))
|
||||||
else:
|
else:
|
||||||
|
logger.info("No configuration file found, using default configuration.")
|
||||||
config = Config()
|
config = Config()
|
||||||
|
|
||||||
if record_to is not None:
|
if record_to is not None:
|
||||||
|
@ -5,7 +5,7 @@ import click
|
|||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from .commons import config_options
|
from .commons import config_options, verbosity_option
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .defaults import CONFIG_PATH, FONT_ARGS
|
from .defaults import CONFIG_PATH, FONT_ARGS
|
||||||
|
|
||||||
@ -39,6 +39,7 @@ def prompt(question: str) -> int:
|
|||||||
@click.command()
|
@click.command()
|
||||||
@config_options
|
@config_options
|
||||||
@click.help_option("-h", "--help")
|
@click.help_option("-h", "--help")
|
||||||
|
@verbosity_option
|
||||||
def wizard(config_path, force, merge):
|
def wizard(config_path, force, merge):
|
||||||
"""Launch configuration wizard."""
|
"""Launch configuration wizard."""
|
||||||
return _init(config_path, force, merge, skip_interactive=False)
|
return _init(config_path, force, merge, skip_interactive=False)
|
||||||
@ -47,6 +48,7 @@ def wizard(config_path, force, merge):
|
|||||||
@click.command()
|
@click.command()
|
||||||
@config_options
|
@config_options
|
||||||
@click.help_option("-h", "--help")
|
@click.help_option("-h", "--help")
|
||||||
|
@verbosity_option
|
||||||
def init(config_path, force, merge, skip_interactive=False):
|
def init(config_path, force, merge, skip_interactive=False):
|
||||||
"""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)
|
||||||
|
Reference in New Issue
Block a user