mirror of
https://github.com/jeertmans/manim-slides.git
synced 2025-05-19 19:46:49 +08:00
feat(lib): add Jupyter magic (#237)
* feat(lib): add Jupyter magic And also use the same logger level as manim (by default) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix(lib): remove deleted module * chore(lib): fix typing issues * chore(docs): document magic * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix(ci): install kernel * fix(ci): spawning is not necessary (and fails) * chore(ci): add ipykernel --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
4
.github/workflows/pages.yml
vendored
4
.github/workflows/pages.yml
vendored
@ -46,8 +46,12 @@ jobs:
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install libcairo2-dev libpango1.0-dev ffmpeg freeglut3-dev
|
||||
- name: Setup Pandoc
|
||||
uses: nikeee/setup-pandoc@v1
|
||||
- name: Install local Python package
|
||||
run: poetry install --with docs
|
||||
- name: Install IPython kernel
|
||||
run: poetry run ipython kernel install --name "manim-slides" --user
|
||||
- name: Restore cached media
|
||||
id: cache-media-restore
|
||||
uses: actions/cache/restore@v3
|
||||
|
@ -20,6 +20,7 @@ extensions = [
|
||||
"sphinx.ext.intersphinx",
|
||||
"sphinx.ext.viewcode",
|
||||
# Additional
|
||||
"nbsphinx",
|
||||
"myst_parser",
|
||||
"sphinxext.opengraph",
|
||||
"sphinx_click",
|
||||
|
@ -10,6 +10,7 @@ cli
|
||||
examples
|
||||
gui
|
||||
html
|
||||
IPython magic <ipython_magic>
|
||||
sharing
|
||||
Sphinx Extension <sphinx_extension>
|
||||
```
|
||||
@ -26,6 +27,11 @@ Slides' executable.
|
||||
|
||||
[HTML Presentation](./html): an alternative way of presenting your animations.
|
||||
|
||||
[IPython Magic](./ipython_magic): a magic to render and display Manim Slides inside notebooks.
|
||||
|
||||
+ [Example](./magic_example): example notebook using the magics.
|
||||
|
||||
[Sharing](./sharing): how to share your presentation with others.
|
||||
|
||||
|
||||
[Sphinx Extension](./sphinx_extension): a Sphinx extension for diplaying Manim Slides animations within your documentation.
|
||||
|
6
docs/source/reference/ipython_magic.md
Normal file
6
docs/source/reference/ipython_magic.md
Normal file
@ -0,0 +1,6 @@
|
||||
# Manim Slides' IPython magic
|
||||
|
||||
```{eval-rst}
|
||||
.. automodule:: manim_slides.ipython.ipython_magic
|
||||
:members: ManimSlidesMagic
|
||||
```
|
99
docs/source/reference/magic_example.ipynb
Normal file
99
docs/source/reference/magic_example.ipynb
Normal file
@ -0,0 +1,99 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6896875b-34ce-4fc5-809c-669c295067e7",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Jupyter Magic Example\n",
|
||||
"\n",
|
||||
"This small example shows how to use the Manim Slides cell (`%%manim-slides`) and line (`%manim-slides`) magics:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a73f1c06-c7f8-4f19-a90e-e283bfb8c7c5",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from manim import *\n",
|
||||
"from manim_slides import *"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "68dda1a0-74ff-4d9e-9575-5b25a98f21e7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%manim_slides -v WARNING --progress_bar None MySlide --manim-slides controls=true\n",
|
||||
"\n",
|
||||
"config.media_embed = True\n",
|
||||
"\n",
|
||||
"class MySlide(Slide):\n",
|
||||
" def construct(self):\n",
|
||||
" square = Square()\n",
|
||||
" circle = Circle()\n",
|
||||
" \n",
|
||||
" self.play(Create(square))\n",
|
||||
" self.next_slide()\n",
|
||||
" self.play(Transform(square, circle))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "54fa2d3e-bfee-417d-b64b-f3f30a8749ea",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class MyOtherSlide(Slide):\n",
|
||||
" def construct(self):\n",
|
||||
" square = Square()\n",
|
||||
" square.save_state()\n",
|
||||
" circle = Circle()\n",
|
||||
" \n",
|
||||
" self.play(Create(square))\n",
|
||||
" self.next_slide()\n",
|
||||
" self.start_loop()\n",
|
||||
" self.play(Transform(square, circle))\n",
|
||||
" self.play(Restore(square))\n",
|
||||
" self.end_loop()\n",
|
||||
" self.play(FadeOut(square))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "7d8ad450-1487-4ca7-8d89-bf8ac344e1fa",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%manim_slides -v WARNING --progress_bar None MyOtherSlide --manim-slides controls=true"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "manim-slides",
|
||||
"language": "python",
|
||||
"name": "manim-slides"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.6"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
@ -13,6 +13,20 @@ class module(ModuleType):
|
||||
"manim_slides.slide", None, None, ["Slide", "ThreeDSlide"]
|
||||
)
|
||||
return getattr(module, name)
|
||||
elif name == "ManimSlidesMagic":
|
||||
module = __import__(
|
||||
"manim_slides.ipython.ipython_magic", None, None, ["ManimSlidesMagic"]
|
||||
)
|
||||
magic = getattr(module, name)
|
||||
|
||||
from IPython import get_ipython
|
||||
|
||||
ipy = get_ipython()
|
||||
|
||||
if ipy is not None:
|
||||
ipy.register_magics(magic)
|
||||
|
||||
return magic
|
||||
|
||||
return ModuleType.__getattribute__(self, name)
|
||||
|
||||
@ -43,6 +57,6 @@ new_module.__dict__.update(
|
||||
"__path__": __path__,
|
||||
"__doc__": __doc__,
|
||||
"__version__": __version__,
|
||||
"__all__": ("__version__", "Slide", "ThreeDSlide"),
|
||||
"__all__": ("__version__", "ManimSlidesMagic", "Slide", "ThreeDSlide"),
|
||||
}
|
||||
)
|
||||
|
@ -44,7 +44,7 @@ def config_options(function: F) -> F:
|
||||
def verbosity_option(function: F) -> F:
|
||||
"""Wraps a function to add verbosity option."""
|
||||
|
||||
def callback(ctx: Context, param: Parameter, value: bool) -> None:
|
||||
def callback(ctx: Context, param: Parameter, value: str) -> None:
|
||||
if not value or ctx.resilient_parsing:
|
||||
return
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import mimetypes
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
@ -85,21 +86,12 @@ def validate_config_option(
|
||||
return config
|
||||
|
||||
|
||||
def get_file_mime_type(file: Path) -> str:
|
||||
ext = file.suffix.lower()
|
||||
|
||||
if ext == ".mp4":
|
||||
return "video/mp4"
|
||||
else:
|
||||
return "video/webm"
|
||||
|
||||
|
||||
def data_uri(file: Path) -> str:
|
||||
"""
|
||||
Reads a video and returns the corresponding data-uri.
|
||||
"""
|
||||
b64 = b64encode(file.read_bytes()).decode("ascii")
|
||||
mime_type = get_file_mime_type(file)
|
||||
mime_type = mimetypes.guess_type(file)[0] or "video/mp4"
|
||||
|
||||
return f"data:{mime_type};base64,{b64}"
|
||||
|
||||
@ -407,6 +399,9 @@ class RevealJS(Converter):
|
||||
"""Converts this configuration into a RevealJS HTML presentation, saved to DEST."""
|
||||
if self.data_uri:
|
||||
assets_dir = Path("") # Actually we won't care.
|
||||
|
||||
for presentation_config in self.presentation_configs:
|
||||
presentation_config.concat_animations()
|
||||
else:
|
||||
dirname = dest.parent
|
||||
basename = dest.stem
|
||||
@ -419,7 +414,7 @@ class RevealJS(Converter):
|
||||
|
||||
logger.debug(f"Assets will be saved to: {full_assets_dir}")
|
||||
|
||||
os.makedirs(full_assets_dir, exist_ok=True)
|
||||
full_assets_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for presentation_config in self.presentation_configs:
|
||||
presentation_config.concat_animations().copy_to(full_assets_dir)
|
||||
@ -556,7 +551,7 @@ class PowerPoint(Converter):
|
||||
):
|
||||
file = presentation_config.files[slide_config.start_animation]
|
||||
|
||||
mime_type = get_file_mime_type(file)
|
||||
mime_type = mimetypes.guess_type(file)[0]
|
||||
|
||||
if self.poster_frame_image is None:
|
||||
poster_frame_image = save_first_image_from_video_file(file)
|
||||
|
@ -16,6 +16,17 @@ A directive for including Manim slides in a Sphinx document
|
||||
When rendering the HTML documentation, the ``.. manim-slides::``
|
||||
directive implemented here allows to include rendered videos.
|
||||
|
||||
This directive requires three additional dependencies:
|
||||
``manim``, ``docutils`` and ``jinja2``. The last two are usually bundled
|
||||
with Sphinx.
|
||||
You can install them manually, or with the extra keyword:
|
||||
|
||||
pip install manim-slides[sphinx-directive]
|
||||
|
||||
Note that you will still need to install Manim's platform-specific dependencies,
|
||||
see
|
||||
`their installation page <https://docs.manim.community/en/stable/installation.html>`_.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
|
265
manim_slides/ipython/ipython_magic.py
Normal file
265
manim_slides/ipython/ipython_magic.py
Normal file
@ -0,0 +1,265 @@
|
||||
"""
|
||||
Utilities for using Manim Slides with IPython (in particular: Jupyter notebooks).
|
||||
=================================================================================
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
magic_example
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
The current implementation is highly inspired from Manim's own
|
||||
IPython magics, from v0.17.3.
|
||||
|
||||
This magic requires two additional dependencies: ``manim`` and ``IPython``.
|
||||
You can install them manually, or with the extra keyword:
|
||||
|
||||
pip install manim-slides[magic]
|
||||
|
||||
Note that you will still need to install Manim's platform-specific dependencies,
|
||||
see
|
||||
`their installation page <https://docs.manim.community/en/stable/installation.html>`_.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import mimetypes
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from IPython import get_ipython
|
||||
from IPython.core.interactiveshell import InteractiveShell
|
||||
from IPython.core.magic import Magics, line_cell_magic, magics_class, needs_local_scope
|
||||
from IPython.display import HTML, display
|
||||
from manim import config, logger, tempconfig
|
||||
from manim.__main__ import main
|
||||
from manim.constants import RendererType
|
||||
from manim.renderer.shader import shader_program_cache
|
||||
|
||||
from ..convert import RevealJS
|
||||
from ..present import get_scenes_presentation_config
|
||||
|
||||
|
||||
@magics_class
|
||||
class ManimSlidesMagic(Magics): # type: ignore
|
||||
def __init__(self, shell: InteractiveShell) -> None:
|
||||
super().__init__(shell)
|
||||
self.rendered_files: Dict[Path, Path] = {}
|
||||
|
||||
@needs_local_scope
|
||||
@line_cell_magic
|
||||
def manim_slides(
|
||||
self,
|
||||
line: str,
|
||||
cell: Optional[str] = None,
|
||||
local_ns: Dict[str, Any] = {},
|
||||
) -> None:
|
||||
r"""Render Manim Slides contained in IPython cells.
|
||||
Works as a line or cell magic.
|
||||
|
||||
.. note::
|
||||
|
||||
This magic works pretty much like the one from Manim, except that it
|
||||
will render Manim Slides using RevealJS. For passing arguments to
|
||||
Manim Slides' convert module, use ``-manim-slides key=value``.
|
||||
|
||||
Everything that is after ``--manim-slides`` will be send to
|
||||
Manim Slides' command. E.g., use ``--manim-slides controls=true``
|
||||
to display control buttons.
|
||||
|
||||
.. hint::
|
||||
|
||||
This line and cell magic works best when used in a JupyterLab
|
||||
environment: while all of the functionality is available for
|
||||
classic Jupyter notebooks as well, it is possible that videos
|
||||
sometimes don't update on repeated execution of the same cell
|
||||
if the scene name stays the same.
|
||||
|
||||
This problem does not occur when using JupyterLab.
|
||||
|
||||
Please refer to `<https://jupyter.org/>`_ for more information about JupyterLab
|
||||
and Jupyter notebooks.
|
||||
|
||||
Usage in line mode::
|
||||
|
||||
%manim_slides [CLI options] MyAwesomeSlide
|
||||
|
||||
Usage in cell mode::
|
||||
|
||||
%%manim_slides [CLI options] MyAwesomeSlide
|
||||
|
||||
class MyAweseomeSlide(Slide):
|
||||
def construct(self):
|
||||
...
|
||||
|
||||
Run ``%manim_slides --help`` and ``%manim_slides render --help``
|
||||
for possible command line interface options.
|
||||
|
||||
.. note::
|
||||
|
||||
The maximal width of the rendered videos that are displayed in the notebook can be
|
||||
configured via the ``media_width`` configuration option. The default is set to ``25vw``,
|
||||
which is 25% of your current viewport width. To allow the output to become as large
|
||||
as possible, set ``config.media_width = "100%"``.
|
||||
|
||||
The ``media_embed`` option will embed the image/video output in the notebook. This is
|
||||
generally undesirable as it makes the notebooks very large, but is required on some
|
||||
platforms (notably Google's CoLab, where it is automatically enabled unless suppressed
|
||||
by ``config.embed = False``) and needed in cases when the notebook (or converted HTML
|
||||
file) will be moved relative to the video locations. Use-cases include building
|
||||
documentation with Sphinx and JupyterBook. See also the
|
||||
:mod:`Manim Slides directive for Sphinx
|
||||
<manim_slides.docs.manim_slides_directive>`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
First make sure to put ``from manim_slides import ManimSlidesMagic``,
|
||||
or even ``from manim_slides import *``
|
||||
in a cell and evaluate it. Then, a typical Jupyter notebook cell for Manim Slides
|
||||
could look as follows::
|
||||
|
||||
%%manim_slides -v WARNING --progress_bar None MySlide --manim-slides controls=true data_uri=true
|
||||
|
||||
class MySlide(Slide):
|
||||
def construct(self):
|
||||
square = Square()
|
||||
circle = Circle()
|
||||
|
||||
self.play(Create(square))
|
||||
self.next_slide()
|
||||
self.play(Transform(square, circle))
|
||||
|
||||
Evaluating this cell will render and display the ``MySlide`` slide
|
||||
defined in the body of the cell.
|
||||
|
||||
.. note::
|
||||
|
||||
In case you want to hide the red box containing the output progress bar, the ``progress_bar`` config
|
||||
option should be set to ``None``. This can also be done by passing ``--progress_bar None`` as a
|
||||
CLI flag.
|
||||
|
||||
"""
|
||||
if cell:
|
||||
exec(cell, local_ns)
|
||||
|
||||
split_args = line.split("--manim-slides", 2)
|
||||
manim_args = split_args[0].split()
|
||||
|
||||
if len(split_args) == 2:
|
||||
manim_slides_args = split_args[1].split()
|
||||
else:
|
||||
manim_slides_args = []
|
||||
|
||||
args = manim_args
|
||||
if not len(args) or "-h" in args or "--help" in args or "--version" in args:
|
||||
main(args, standalone_mode=False, prog_name="manim")
|
||||
return
|
||||
|
||||
modified_args = self.add_additional_args(args)
|
||||
args = main(modified_args, standalone_mode=False, prog_name="manim")
|
||||
with tempconfig(local_ns.get("config", {})):
|
||||
config.digest_args(args)
|
||||
logging.getLogger("manim-slides").setLevel(logging.getLogger("manim").level)
|
||||
|
||||
renderer = None
|
||||
if config.renderer == RendererType.OPENGL:
|
||||
from manim.renderer.opengl_renderer import OpenGLRenderer
|
||||
|
||||
renderer = OpenGLRenderer()
|
||||
|
||||
try:
|
||||
SceneClass = local_ns[config["scene_names"][0]]
|
||||
scene = SceneClass(renderer=renderer)
|
||||
scene.render()
|
||||
finally:
|
||||
# Shader cache becomes invalid as the context is destroyed
|
||||
shader_program_cache.clear()
|
||||
|
||||
# Close OpenGL window here instead of waiting for the main thread to
|
||||
# finish causing the window to stay open and freeze
|
||||
if renderer is not None and renderer.window is not None:
|
||||
renderer.window.close()
|
||||
|
||||
if config["output_file"] is None:
|
||||
logger.info("No output file produced")
|
||||
return
|
||||
|
||||
local_path = Path(config["output_file"]).relative_to(Path.cwd())
|
||||
tmpfile = (
|
||||
Path(config["media_dir"]) / "jupyter" / f"{_generate_file_name()}.html"
|
||||
)
|
||||
|
||||
if local_path in self.rendered_files:
|
||||
self.rendered_files[local_path].unlink()
|
||||
pass
|
||||
self.rendered_files[local_path] = tmpfile
|
||||
tmpfile.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy(local_path, tmpfile)
|
||||
|
||||
file_type = mimetypes.guess_type(config["output_file"])[0] or "video/mp4"
|
||||
embed = config["media_embed"]
|
||||
if embed is None:
|
||||
# videos need to be embedded when running in google colab.
|
||||
# do this automatically in case config.media_embed has not been
|
||||
# set explicitly.
|
||||
embed = "google.colab" in str(get_ipython())
|
||||
|
||||
if not file_type.startswith("video"):
|
||||
raise ValueError(
|
||||
f"Manim Slides only supports video files, not {file_type}"
|
||||
)
|
||||
|
||||
clsname = config["scene_names"][0]
|
||||
|
||||
kwargs = dict(arg.split("=", 1) for arg in manim_slides_args)
|
||||
|
||||
if embed: # Embedding implies data-uri
|
||||
kwargs["data_uri"] = "true"
|
||||
|
||||
# TODO: FIXME
|
||||
# Seems like files are blocked so date-uri is the only working option...
|
||||
if kwargs.get("data_uri", "false").lower().strip() == "false":
|
||||
logger.warn(
|
||||
"data_uri option is currently automatically enabled, "
|
||||
"because using local video files does not seem to work properly."
|
||||
)
|
||||
kwargs["data_uri"] = "true"
|
||||
|
||||
presentation_configs = get_scenes_presentation_config(
|
||||
[clsname], Path("./slides")
|
||||
)
|
||||
RevealJS(presentation_configs=presentation_configs, **kwargs).convert_to(
|
||||
tmpfile
|
||||
)
|
||||
|
||||
if embed:
|
||||
result = HTML(
|
||||
"""<div style="position:relative;padding-bottom:56.25%;"><iframe style="width:100%;height:100%;position:absolute;left:0px;top:0px;" frameborder="0" width="100%" height="100%" allowfullscreen allow="autoplay" srcdoc="{srcdoc}"></iframe></div>""".format(
|
||||
srcdoc=tmpfile.read_text().replace('"', "'")
|
||||
)
|
||||
)
|
||||
else:
|
||||
result = HTML(
|
||||
"""<div style="position:relative;padding-bottom:56.25%;"><iframe style="width:100%;height:100%;position:absolute;left:0px;top:0px;" frameborder="0" width="100%" height="100%" allowfullscreen allow="autoplay" src="{src}"></iframe></div>""".format(
|
||||
src=tmpfile.as_posix()
|
||||
)
|
||||
)
|
||||
|
||||
display(result)
|
||||
|
||||
def add_additional_args(self, args: list[str]) -> list[str]:
|
||||
additional_args = ["--jupyter"]
|
||||
# Use webm to support transparency
|
||||
if "-t" in args and "--format" not in args:
|
||||
additional_args += ["--format", "webm"]
|
||||
return additional_args + args[:-1] + [""] + [args[-1]]
|
||||
|
||||
|
||||
def _generate_file_name() -> str:
|
||||
return config["scene_names"][0] + "@" + datetime.now().strftime("%Y-%m-%d@%H-%M-%S") # type: ignore
|
@ -40,6 +40,7 @@ def make_logger() -> logging.Logger:
|
||||
)
|
||||
logging.addLevelName(5, "PERF")
|
||||
logger = logging.getLogger("manim-slides")
|
||||
logger.setLevel(logging.getLogger("manim").level)
|
||||
logger.addHandler(rich_handler)
|
||||
|
||||
return logger
|
||||
|
@ -1,8 +1,6 @@
|
||||
import sys
|
||||
from importlib.util import find_spec
|
||||
|
||||
from .utils import suppress_stdout
|
||||
|
||||
__all__ = [
|
||||
# Constants
|
||||
"FFMPEG_BIN",
|
||||
@ -77,20 +75,19 @@ if MANIMGL:
|
||||
from manimlib.logger import log as logger
|
||||
|
||||
else:
|
||||
with suppress_stdout(): # Avoids printing "Manim Community v..."
|
||||
from manim import (
|
||||
LEFT,
|
||||
AnimationGroup,
|
||||
FadeIn,
|
||||
FadeOut,
|
||||
Mobject,
|
||||
Scene,
|
||||
ThreeDScene,
|
||||
config,
|
||||
logger,
|
||||
)
|
||||
from manim import (
|
||||
LEFT,
|
||||
AnimationGroup,
|
||||
FadeIn,
|
||||
FadeOut,
|
||||
Mobject,
|
||||
Scene,
|
||||
ThreeDScene,
|
||||
config,
|
||||
logger,
|
||||
)
|
||||
|
||||
try: # For manim<v0.16.0.post0
|
||||
from manim.constants import FFMPEG_BIN
|
||||
except ImportError:
|
||||
FFMPEG_BIN = config.ffmpeg_executable
|
||||
try: # For manim<v0.16.0.post0
|
||||
from manim.constants import FFMPEG_BIN
|
||||
except ImportError:
|
||||
FFMPEG_BIN = config.ffmpeg_executable
|
||||
|
@ -1,15 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
from typing import Iterator
|
||||
|
||||
|
||||
@contextmanager
|
||||
def suppress_stdout() -> Iterator[None]:
|
||||
with open(os.devnull, "w") as devnull:
|
||||
old_stdout = sys.stdout
|
||||
sys.stdout = devnull
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
sys.stdout = old_stdout
|
4893
poetry.lock
generated
4893
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -48,9 +48,13 @@ version = "4.15.0"
|
||||
[tool.poetry.dependencies]
|
||||
click = "^8.1.3"
|
||||
click-default-group = "^1.2.2"
|
||||
docutils = {version = "^0.20.1", optional = true}
|
||||
ipython = {version = ">=8.12.2", optional = true}
|
||||
jinja2 = {version = "^3.1.2", optional = true}
|
||||
lxml = "^4.9.2"
|
||||
manim = {version = "^0.17.3", optional = true}
|
||||
manimgl = {version = "^1.6.1", optional = true}
|
||||
notebook = {version = "^7.0.2", optional = true}
|
||||
numpy = "^1.19"
|
||||
opencv-python = "^4.6.0.66"
|
||||
pillow = "^9.5.0"
|
||||
@ -65,8 +69,10 @@ rtoml = "^0.9.0"
|
||||
tqdm = "^4.64.1"
|
||||
|
||||
[tool.poetry.extras]
|
||||
magic = ["manim", "ipython"]
|
||||
manim = ["manim"]
|
||||
manimgl = ["manimgl"]
|
||||
sphinx-directive = ["docutils", "jinja2", "manim"]
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
black = "^22.10.0"
|
||||
@ -78,8 +84,11 @@ ruff = "^0.0.219"
|
||||
|
||||
[tool.poetry.group.docs.dependencies]
|
||||
furo = "^2023.5.20"
|
||||
ipykernel = "^6.25.1"
|
||||
manim = "^0.17.3"
|
||||
myst-parser = "^2.0.0"
|
||||
nbsphinx = "^0.9.2"
|
||||
pandoc = "^2.3"
|
||||
sphinx = "^7.0.1"
|
||||
sphinx-click = "^4.4.0"
|
||||
sphinx-copybutton = "^0.5.1"
|
||||
|
Reference in New Issue
Block a user