diff --git a/manim_slides/config.py b/manim_slides/config.py index e00b67b..88c2359 100644 --- a/manim_slides/config.py +++ b/manim_slides/config.py @@ -1,6 +1,5 @@ import hashlib import json -import os import shutil import subprocess import tempfile @@ -261,11 +260,11 @@ class PresentationConfig(BaseModel): # type: ignore continue f = tempfile.NamedTemporaryFile(mode="w", delete=False) - f.writelines(f"file '{os.path.abspath(path)}'\n" for path in files) + f.writelines(f"file '{path.absolute()}'\n" for path in files) f.close() command: List[str] = [ - FFMPEG_BIN, + str(FFMPEG_BIN), "-f", "concat", "-safe", diff --git a/manim_slides/defaults.py b/manim_slides/defaults.py index 9a682ef..65ccdd6 100644 --- a/manim_slides/defaults.py +++ b/manim_slides/defaults.py @@ -1,3 +1,5 @@ -FOLDER_PATH: str = "./slides" -CONFIG_PATH: str = ".manim-slides.toml" -FFMPEG_BIN: str = "ffmpeg" +from pathlib import Path + +FOLDER_PATH: Path = Path("./slides") +CONFIG_PATH: Path = Path(".manim-slides.toml") +FFMPEG_BIN: Path = Path("ffmpeg") diff --git a/manim_slides/present.py b/manim_slides/present.py index db82424..9e88654 100644 --- a/manim_slides/present.py +++ b/manim_slides/present.py @@ -76,7 +76,7 @@ class Presentation: self.__current_slide_index: int = 0 self.current_animation: int = self.current_slide.start_animation - self.current_file: str = "" + self.current_file: Path = Path("") self.loaded_animation_cap: int = -1 self.cap = None # cap = cv2.VideoCapture @@ -185,10 +185,10 @@ class Presentation: self.release_cap() - file: str = str(self.files[animation]) + file: Path = self.files[animation] if self.reverse: - file = "{}_reversed{}".format(*os.path.splitext(file)) + file = file.parent / f"{file.stem}_reversed{file.suffix}" self.reversed_animation = animation self.current_file = file @@ -371,7 +371,7 @@ class Display(QThread): # type: ignore self.config = config self.skip_all = skip_all self.record_to = record_to - self.recordings: List[Tuple[str, int, int]] = [] + self.recordings: List[Tuple[Path, int, int]] = [] self.state = State.PLAYING self.lastframe: Optional[np.ndarray] = None @@ -480,7 +480,7 @@ class Display(QThread): # type: ignore ) file, frame_number, fps = self.recordings[0] - cap = cv2.VideoCapture(file) + cap = cv2.VideoCapture(str(file)) cap.set(cv2.CAP_PROP_POS_FRAMES, frame_number - 1) _, frame = cap.read() @@ -496,7 +496,7 @@ class Display(QThread): # type: ignore if file != _file: cap.release() file = _file - cap = cv2.VideoCapture(_file) + cap = cv2.VideoCapture(str(_file)) cap.set(cv2.CAP_PROP_POS_FRAMES, frame_number - 1) _, frame = cap.read() diff --git a/manim_slides/slide.py b/manim_slides/slide.py index ae068f8..c5572a1 100644 --- a/manim_slides/slide.py +++ b/manim_slides/slide.py @@ -1,7 +1,7 @@ -import os import platform import shutil import subprocess +from pathlib import Path from typing import ( Any, List, @@ -9,6 +9,7 @@ from typing import ( MutableMapping, Optional, Sequence, + Set, Tuple, ValuesView, ) @@ -34,9 +35,9 @@ from .manim import ( ) -def reverse_video_file(src: str, dst: str) -> None: +def reverse_video_file(src: Path, dst: Path) -> None: """Reverses a video file, writting the result to `dst`.""" - command = [FFMPEG_BIN, "-i", src, "-vf", "reverse", dst] + command = [str(FFMPEG_BIN), "-i", str(src), "-vf", "reverse", str(dst)] logger.debug(" ".join(command)) process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) output, error = process.communicate() @@ -54,11 +55,10 @@ class Slide(Scene): # type:ignore """ def __init__( - self, *args: Any, output_folder: str = FOLDER_PATH, **kwargs: Any + self, *args: Any, output_folder: Path = FOLDER_PATH, **kwargs: Any ) -> None: if MANIMGL: - if not os.path.isdir("videos"): - os.mkdir("videos") + Path("videos").mkdir(exist_ok=True) kwargs["file_writer_config"] = { "break_into_partial_movies": True, "output_directory": "", @@ -69,7 +69,7 @@ class Slide(Scene): # type:ignore super().__init__(*args, **kwargs) - self.__output_folder = output_folder + self.__output_folder: Path = output_folder self.__slides: List[SlideConfig] = [] self.__current_slide = 1 self.__current_animation = 0 @@ -111,7 +111,7 @@ class Slide(Scene): # type:ignore return config["pixel_width"], config["pixel_height"] @property - def __partial_movie_files(self) -> List[str]: + def __partial_movie_files(self) -> List[Path]: """Returns a list of partial movie files, a.k.a animations.""" if MANIMGL: from manimlib.utils.file_ops import get_sorted_integer_files @@ -120,11 +120,13 @@ class Slide(Scene): # type:ignore "remove_non_integer_files": True, "extension": self.file_writer.movie_file_extension, } - return get_sorted_integer_files( # type: ignore + files = get_sorted_integer_files( self.file_writer.partial_movie_directory, **kwargs ) else: - return self.renderer.file_writer.partial_movie_files # type: ignore + files = self.renderer.file_writer.partial_movie_files + + return [Path(file) for file in files] @property def __show_progress_bar(self) -> bool: @@ -470,25 +472,23 @@ class Slide(Scene): # type:ignore """ self.__add_last_slide() - if not os.path.exists(self.__output_folder): - os.mkdir(self.__output_folder) + self.__output_folder.mkdir(parents=True, exist_ok=True) - files_folder = os.path.join(self.__output_folder, "files") - if not os.path.exists(files_folder): - os.mkdir(files_folder) + files_folder = self.__output_folder / "files" + files_folder.mkdir(exist_ok=True) scene_name = str(self) - scene_files_folder = os.path.join(files_folder, scene_name) + scene_files_folder = files_folder / scene_name - old_animation_files = set() + old_animation_files: Set[Path] = set() - if not os.path.exists(scene_files_folder): - os.mkdir(scene_files_folder) + if not scene_files_folder.exists(): + scene_files_folder.mkdir() elif not use_cache: shutil.rmtree(scene_files_folder) - os.mkdir(scene_files_folder) + scene_files_folder.mkdir() else: - old_animation_files.update(os.listdir(scene_files_folder)) + old_animation_files.update(scene_files_folder.iterdir()) files = [] for src_file in tqdm( @@ -504,10 +504,11 @@ class Slide(Scene): # type:ignore # but animations before a will have a None src_file continue - filename = os.path.basename(src_file) - rev_filename = "{}_reversed{}".format(*os.path.splitext(filename)) - - dst_file = os.path.join(scene_files_folder, filename) + filename = src_file.name + rev_filename = ( + src_file.parent / f"{src_file.stem}_reversed{src_file.suffix}" + ) + dst_file = scene_files_folder / filename # We only copy animation if it was not present if filename in old_animation_files: old_animation_files.remove(filename) @@ -518,7 +519,7 @@ class Slide(Scene): # type:ignore if rev_filename in old_animation_files: old_animation_files.remove(rev_filename) else: - rev_file = os.path.join(scene_files_folder, rev_filename) + rev_file = scene_files_folder / rev_filename reverse_video_file(src_file, rev_file) files.append(dst_file) @@ -533,23 +534,20 @@ class Slide(Scene): # type:ignore slide.end_animation -= offset logger.info( - f"Copied {len(files)} animations to '{os.path.abspath(scene_files_folder)}' and generated reversed animations" + f"Copied {len(files)} animations to '{scene_files_folder.absolute()}' and generated reversed animations" ) - slide_path = os.path.join(self.__output_folder, "%s.json" % (scene_name,)) + slide_path = self.__output_folder / f"{scene_name}.json" - with open(slide_path, "w") as f: - f.write( - PresentationConfig( - slides=self.__slides, - files=files, - resolution=self.__resolution, - background_color=self.__background_color, - ).model_dump_json(indent=2) - ) + PresentationConfig( + slides=self.__slides, + files=files, + resolution=self.__resolution, + background_color=self.__background_color, + ).to_file(slide_path) logger.info( - f"Slide '{scene_name}' configuration written in '{os.path.abspath(slide_path)}'" + f"Slide '{scene_name}' configuration written in '{slide_path.absolute()}'" ) def run(self, *args: Any, **kwargs: Any) -> None: diff --git a/tests/test_defaults.py b/tests/test_defaults.py index 6d79ed9..2e80640 100644 --- a/tests/test_defaults.py +++ b/tests/test_defaults.py @@ -1,13 +1,15 @@ +from pathlib import Path + from manim_slides.defaults import CONFIG_PATH, FFMPEG_BIN, FOLDER_PATH def test_folder_path() -> None: - assert FOLDER_PATH == "./slides" + assert FOLDER_PATH == Path("./slides") def test_config_path() -> None: - assert CONFIG_PATH == ".manim-slides.toml" + assert CONFIG_PATH == Path(".manim-slides.toml") def test_ffmpeg_bin() -> None: - assert FFMPEG_BIN == "ffmpeg" + assert FFMPEG_BIN == Path("ffmpeg")