Skip to content

Commit

Permalink
Merge branch 'main' into fix-docs
Browse files Browse the repository at this point in the history
  • Loading branch information
jeertmans authored Nov 1, 2024
2 parents fa12019 + d8acbae commit 087e6ef
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 45 deletions.
3 changes: 0 additions & 3 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ build:
apt_packages:
- libpango1.0-dev
- ffmpeg
jobs:
post_install:
- ipython kernel install --name "manim-slides" --user
sphinx:
builder: html
configuration: docs/source/conf.py
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
(unreleased)=
## [Unreleased](https://github.com/jeertmans/manim-slides/compare/v5.1.9...HEAD)

(unreleased-added)=
### Added

- Added `--offline` option to `manim-slides convert` for offline
HTML presentations.
[#440](https://github.com/jeertmans/manim-slides/pull/440)

(v5.1.9)=
## [v5.1.9](https://github.com/jeertmans/manim-slides/compare/v5.1.8...v5.1.9)

Expand Down
6 changes: 3 additions & 3 deletions docs/source/reference/magic_example.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@
],
"metadata": {
"kernelspec": {
"display_name": "manim-slides",
"display_name": ".venv",
"language": "python",
"name": "manim-slides"
"name": "python3"
},
"language_info": {
"codemirror_mode": {
Expand All @@ -92,7 +92,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.6"
"version": "3.11.8"
}
},
"nbformat": 4,
Expand Down
75 changes: 55 additions & 20 deletions manim_slides/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import av
import click
import pptx
import requests
from bs4 import BeautifulSoup
from click import Context, Parameter
from jinja2 import Template
from lxml import etree
Expand Down Expand Up @@ -287,8 +289,11 @@ class RevealTheme(str, StrEnum):


class RevealJS(Converter):
# Export option: use data-uri
# Export option:
data_uri: bool = False
offline: bool = Field(
False, description="Download remote assets for offline presentation."
)
# Presentation size options from RevealJS
width: Union[Str, int] = Str("100%")
height: Union[Str, int] = Str("100%")
Expand Down Expand Up @@ -385,27 +390,25 @@ def load_template(self) -> str:
def open(self, file: Path) -> None:
webbrowser.open(file.absolute().as_uri())

def convert_to(self, dest: Path) -> None:
def convert_to(self, dest: Path) -> None: # noqa: C901
"""
Convert this configuration into a RevealJS HTML presentation, saved to
DEST.
"""
if self.data_uri:
assets_dir = Path("") # Actually we won't care.
else:
dirname = dest.parent
basename = dest.stem
ext = dest.suffix
dirname = dest.parent
basename = dest.stem
ext = dest.suffix

assets_dir = Path(
self.assets_dir.format(dirname=dirname, basename=basename, ext=ext)
)
full_assets_dir = dirname / assets_dir
assets_dir = Path(
self.assets_dir.format(dirname=dirname, basename=basename, ext=ext)
)
full_assets_dir = dirname / assets_dir

if not self.data_uri or self.offline:
logger.debug(f"Assets will be saved to: {full_assets_dir}")

full_assets_dir.mkdir(parents=True, exist_ok=True)

if not self.data_uri:
num_presentation_configs = len(self.presentation_configs)

if num_presentation_configs > 1:
Expand Down Expand Up @@ -435,7 +438,9 @@ def prefix(i: int) -> str:
revealjs_template = Template(self.load_template())

options = self.model_dump()
options["assets_dir"] = assets_dir

if assets_dir is not None:
options["assets_dir"] = assets_dir

has_notes = any(
slide_config.notes != ""
Expand All @@ -451,6 +456,24 @@ def prefix(i: int) -> str:
**options,
)

if self.offline:
soup = BeautifulSoup(content, "html.parser")
session = requests.Session()

for tag, inner in [("link", "href"), ("script", "src")]:
for item in soup.find_all(tag):
if item.has_attr(inner) and (link := item[inner]).startswith(
"http"
):
asset_name = link.rsplit("/", 1)[1]
asset = session.get(link)
with open(full_assets_dir / asset_name, "wb") as asset_file:
asset_file.write(asset.content)

item[inner] = str(assets_dir / asset_name)

content = str(soup)

f.write(content)


Expand Down Expand Up @@ -590,7 +613,7 @@ def xpath(el: etree.Element, query: str) -> etree.XPath:


def show_config_options(function: Callable[..., Any]) -> Callable[..., Any]:
"""Wrap 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 Down Expand Up @@ -621,7 +644,7 @@ def callback(ctx: Context, param: Parameter, value: bool) -> None:


def show_template_option(function: Callable[..., Any]) -> Callable[..., Any]:
"""Wrap 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 @@ -666,23 +689,28 @@ def callback(ctx: Context, param: Parameter, value: bool) -> None:
is_flag=True,
help="Open the newly created file using the appropriate application.",
)
@click.option("-f", "--force", is_flag=True, help="Overwrite any existing file.")
@click.option(
"-c",
"--config",
"config_options",
multiple=True,
callback=validate_config_option,
help="Configuration options passed to the converter. "
"E.g., pass ``-cslide_number=true`` to display slide numbers.",
"E.g., pass '-cslide_number=true' to display slide numbers.",
)
@click.option(
"--use-template",
"template",
metavar="FILE",
type=click.Path(exists=True, dir_okay=False, path_type=Path),
help="Use the template given by FILE instead of default one. "
"To echo the default template, use ``--show-template``.",
"To echo the default template, use '--show-template'.",
)
@click.option(
"--offline",
is_flag=True,
help="Download any remote content and store it in the assets folder. "
"The is a convenient alias to '-coffline=true'.",
)
@show_template_option
@show_config_options
Expand All @@ -693,9 +721,9 @@ def convert(
dest: Path,
to: str,
open_result: bool,
force: bool,
config_options: dict[str, str],
template: Optional[Path],
offline: bool,
) -> None:
"""Convert SCENE(s) into a given format and writes the result in DEST."""
presentation_configs = get_scenes_presentation_config(scenes, folder)
Expand All @@ -713,6 +741,13 @@ def convert(
else:
cls = Converter.from_string(to)

if (
offline
and issubclass(cls, (RevealJS, HtmlZip))
and "offline" not in config_options
):
config_options["offline"] = "true"

converter = cls(
presentation_configs=presentation_configs,
template=template,
Expand Down
37 changes: 19 additions & 18 deletions manim_slides/templates/revealjs.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,32 @@
<body>
<div class="reveal">
<div class="slides">
{%- for presentation_config in presentation_configs -%}
{% for presentation_config in presentation_configs -%}
{% set outer_loop = loop %}
{%- for slide_config in presentation_config.slides -%}
{%- if data_uri -%}
{% set file = file_to_data_uri(slide_config.file) %}
{%- else -%}
{% set file = assets_dir / slide_config.file.name %}
{%- endif -%}
<section
data-background-size={{ background_size }}
data-background-color="{{ presentation_config.background_color }}"
data-background-video="{{ file }}"
{% if loop.index == 1 and outer_loop.index == 1 -%}
data-background-video-muted
{%- endif %}
{% if slide_config.loop -%}
data-background-video-loop
{%- endif -%}
{% if slide_config.auto_next -%}
data-autoslide="{{ get_duration_ms(slide_config.file) }}"
{%- endif -%}>
{% if slide_config.notes != "" -%}
<aside class="notes" data-markdown>{{ slide_config.notes }}</aside>
{%- endif %}
</section>
<section
data-background-size={{ background_size }}
data-background-color="{{ presentation_config.background_color }}"
data-background-video="{{ file }}"
{% if loop.index == 1 and outer_loop.index == 1 -%}
data-background-video-muted
{%- endif -%}
{% if slide_config.loop -%}
data-background-video-loop
{%- endif -%}
{% if slide_config.auto_next -%}
data-autoslide="{{ get_duration_ms(slide_config.file) }}"
{%- endif %}
>
{%- if slide_config.notes != "" -%}
<aside class="notes" data-markdown>{{ slide_config.notes }}</aside>
{%- endif %}
</section>
{%- endfor -%}
{%- endfor -%}
</div>
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ classifiers = [
]
dependencies = [
"av>=9.0.0",
"beautifulsoup4>=4.12.3",
"click>=8.1.3",
"click-default-group>=1.2.2",
"jinja2>=3.1.2",
Expand Down
18 changes: 18 additions & 0 deletions tests/test_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,24 @@ def test_revealjs_converter(
file_contents = out_file.read_text()
assert "manim" in file_contents.casefold()

def test_revealjs_offline_converter(
self, tmp_path: Path, presentation_config: PresentationConfig
) -> None:
out_file = tmp_path / "slides.html"
RevealJS(presentation_configs=[presentation_config], offline="true").convert_to(
out_file
)
assert out_file.exists()
assets_dir = Path(tmp_path / "slides_assets")
assert assets_dir.is_dir()
for file in [
"black.min.css",
"reveal.min.css",
"reveal.min.js",
"zenburn.min.css",
]:
assert (assets_dir / file).exists()

def test_htmlzip_converter(
self, tmp_path: Path, presentation_config: PresentationConfig
) -> None:
Expand Down
4 changes: 3 additions & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 087e6ef

Please sign in to comment.