mirror of
https://github.com/jeertmans/manim-slides.git
synced 2025-05-17 18:55:53 +08:00
Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
d3396d3a01 | |||
7a922db6f1 | |||
9b9593985d | |||
c915af19e8 | |||
a8897552d8 | |||
b02dbbcc8c | |||
c6ba210797 | |||
b1212a49d3 | |||
f7ce5fc115 | |||
5f9bbf2a79 | |||
ccbe9d558c | |||
a2bd1ffb67 | |||
e911ec3096 | |||
daf547414c | |||
528952dbc3 | |||
dbced6e62e | |||
941b895083 | |||
289b7c1683 | |||
b07a83898b | |||
074a029759 | |||
b4af76050e | |||
adce58e1b7 | |||
32ab690932 | |||
df31345f83 | |||
ef282300f1 | |||
a9ba1b4fad | |||
4cc6c2865d | |||
0483e2f861 | |||
a58ff6c388 | |||
bf512f2f73 | |||
541b175360 |
4
.github/ISSUE_TEMPLATE/bug.yml
vendored
4
.github/ISSUE_TEMPLATE/bug.yml
vendored
@ -29,7 +29,7 @@ body:
|
|||||||
options:
|
options:
|
||||||
- label: Checked the [existing issues](https://github.com/jeertmans/manim-slides/issues?q=is%3Aissue+label%3Abug+) and [discussions](https://github.com/jeertmans/manim-slides/discussions) to see if my issue had not already been reported;
|
- label: Checked the [existing issues](https://github.com/jeertmans/manim-slides/issues?q=is%3Aissue+label%3Abug+) and [discussions](https://github.com/jeertmans/manim-slides/discussions) to see if my issue had not already been reported;
|
||||||
required: true
|
required: true
|
||||||
- label: Checked the [frequently asked questions]](https://manim-slides.eertmans.be/latest/faq.html);
|
- label: Checked the [frequently asked questions](https://manim-slides.eertmans.be/latest/faq.html);
|
||||||
required: true
|
required: true
|
||||||
- label: Read the [installation instructions](https://manim-slides.eertmans.be/latest/installation.html);
|
- label: Read the [installation instructions](https://manim-slides.eertmans.be/latest/installation.html);
|
||||||
required: true
|
required: true
|
||||||
@ -122,7 +122,7 @@ body:
|
|||||||
This will be automatically formatted into code, so no need for backticks.
|
This will be automatically formatted into code, so no need for backticks.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
from manim import *
|
from manim import *
|
||||||
from manim_slides.slide import Slide
|
from manim_slides import Slide
|
||||||
|
|
||||||
|
|
||||||
class MWE(Slide):
|
class MWE(Slide):
|
||||||
|
@ -21,18 +21,18 @@ repos:
|
|||||||
exclude: poetry.lock
|
exclude: poetry.lock
|
||||||
args: [--autofix, --trailing-commas]
|
args: [--autofix, --trailing-commas]
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.8.4
|
rev: v0.11.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: [--fix]
|
args: [--fix]
|
||||||
- id: ruff-format
|
- id: ruff-format
|
||||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
rev: v1.14.0
|
rev: v1.15.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: mypy
|
- id: mypy
|
||||||
additional_dependencies: [types-requests, types-setuptools]
|
additional_dependencies: [types-requests, types-setuptools]
|
||||||
- repo: https://github.com/codespell-project/codespell
|
- repo: https://github.com/codespell-project/codespell
|
||||||
rev: v2.3.0
|
rev: v2.4.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: codespell
|
- id: codespell
|
||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
|
@ -1 +1 @@
|
|||||||
3.11.8
|
3.11
|
||||||
|
@ -2,7 +2,7 @@ version: 2
|
|||||||
build:
|
build:
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
tools:
|
tools:
|
||||||
python: '3.10'
|
python: '3.11'
|
||||||
apt_packages:
|
apt_packages:
|
||||||
- libpango1.0-dev
|
- libpango1.0-dev
|
||||||
- ffmpeg
|
- ffmpeg
|
||||||
|
93
CHANGELOG.md
93
CHANGELOG.md
@ -8,7 +8,86 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
<!-- start changelog -->
|
<!-- start changelog -->
|
||||||
|
|
||||||
(unreleased)=
|
(unreleased)=
|
||||||
## [Unreleased](https://github.com/jeertmans/manim-slides/compare/v5.3.0...HEAD)
|
## [Unreleased](https://github.com/jeertmans/manim-slides/compare/v5.5.0...HEAD)
|
||||||
|
|
||||||
|
(v5.5.0)=
|
||||||
|
## [v5.5.0](https://github.com/jeertmans/manim-slides/compare/v5.4.2...v5.5.0)
|
||||||
|
|
||||||
|
(v5.5.0-added)=
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added `max_duration_before_split_reverse` and `num_processes` class variables.
|
||||||
|
[#439](https://github.com/jeertmans/manim-slides/pull/439)
|
||||||
|
- Added `src = ...` filepath argument to allow inserting external
|
||||||
|
videos as slides.
|
||||||
|
[#526](https://github.com/jeertmans/manim-slides/pull/526)
|
||||||
|
|
||||||
|
(v5.5.0-changed)=
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Automatically split large video animations into smaller chunks
|
||||||
|
for lightweight (and potentially faster) reversed animations generation.
|
||||||
|
[#439](https://github.com/jeertmans/manim-slides/pull/439)
|
||||||
|
|
||||||
|
(v5.5.0-chore)=
|
||||||
|
### Chore
|
||||||
|
|
||||||
|
- Trimmed whitespaces in HTML template.
|
||||||
|
[#443](https://github.com/jeertmans/manim-slides/pull/443)
|
||||||
|
- Bumped RevealJS' version to 5.2 to allow video playing in speaker view.
|
||||||
|
[#536](https://github.com/jeertmans/manim-slides/pull/536)
|
||||||
|
|
||||||
|
(v5.4.2)=
|
||||||
|
## [v5.4.2](https://github.com/jeertmans/manim-slides/compare/v5.4.1...v5.4.2)
|
||||||
|
|
||||||
|
(v5.4.2-fixed)=
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed `start_skip_animations` to actually pass argument to ManimCE,
|
||||||
|
otherwise video animations were still rendered, just excluded from
|
||||||
|
the final output.
|
||||||
|
[#524](https://github.com/jeertmans/manim-slides/pull/524)
|
||||||
|
|
||||||
|
(v5.4.1)=
|
||||||
|
## [v5.4.1](https://github.com/jeertmans/manim-slides/compare/v5.4.0...v5.4.1)
|
||||||
|
|
||||||
|
(v5.4.1-added)=
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added `start_skip_animations` and `stop_skip_animations` methods.
|
||||||
|
[#523](https://github.com/jeertmans/manim-slides/pull/523)
|
||||||
|
|
||||||
|
(v5.4.0)=
|
||||||
|
## [v5.4.0](https://github.com/jeertmans/manim-slides/compare/v5.3.1...v5.4.0)
|
||||||
|
|
||||||
|
(v5.4.0-added)=
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added `skip_animations` compatibility with ManimCE.
|
||||||
|
[@Rapsssito](https://github.com/Rapsssito) [#516](https://github.com/jeertmans/manim-slides/pull/516)
|
||||||
|
|
||||||
|
(v5.4.0-chore)=
|
||||||
|
### Chore
|
||||||
|
|
||||||
|
- Bumped Manim to `>=0.19`, as it fixed OpenGL renderer issue.
|
||||||
|
[#522](https://github.com/jeertmans/manim-slides/pull/522)
|
||||||
|
|
||||||
|
(v5.4.0-fixed)=
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed OpenGL renderer having no partial movie files with Manim bindings.
|
||||||
|
[#522](https://github.com/jeertmans/manim-slides/pull/522)
|
||||||
|
- Fixed `ConvertExample` example as `manim>=0.19` changed the `Code` class.
|
||||||
|
[#522](https://github.com/jeertmans/manim-slides/pull/522)
|
||||||
|
|
||||||
|
(v5.3.1)=
|
||||||
|
## [v5.3.1](https://github.com/jeertmans/manim-slides/compare/v5.3.0...v5.3.1)
|
||||||
|
|
||||||
|
(v5.3.1-fixed)=
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed HTML template to avoid missing slides when exporting with `--one-file`.
|
||||||
|
[@Rapsssito](https://github.com/Rapsssito) [#515](https://github.com/jeertmans/manim-slides/pull/515)
|
||||||
|
|
||||||
(v5.3.0)=
|
(v5.3.0)=
|
||||||
## [v5.3.0](https://github.com/jeertmans/manim-slides/compare/v5.2.0...v5.3.0)
|
## [v5.3.0](https://github.com/jeertmans/manim-slides/compare/v5.2.0...v5.3.0)
|
||||||
@ -39,17 +118,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
was added to `manim-slides present` to force the info window.
|
was added to `manim-slides present` to force the info window.
|
||||||
When there are multiple monitors, the info window will no longer
|
When there are multiple monitors, the info window will no longer
|
||||||
be on the same monitor as the main window, unless overridden.
|
be on the same monitor as the main window, unless overridden.
|
||||||
[@PeculiarProgrammer](https://github.com/PeculiarProgrammer)
|
[@taibeled](https://github.com/taibeled)
|
||||||
[#482](https://github.com/jeertmans/manim-slides/pull/482)
|
[#482](https://github.com/jeertmans/manim-slides/pull/482)
|
||||||
|
|
||||||
(v5.2.0-chore)=
|
(v5.2.0-chore)=
|
||||||
### Chore
|
### Chore
|
||||||
|
|
||||||
- Bump ManimGL to `>=1.7.1`, to remove conflicting dependencies
|
- Bumped ManimGL to `>=1.7.1`, to remove conflicting dependencies
|
||||||
with Manim's.
|
with Manim's.
|
||||||
[#499](https://github.com/jeertmans/manim-slides/pull/499)
|
[#499](https://github.com/jeertmans/manim-slides/pull/499)
|
||||||
|
|
||||||
- Bump ManimGL to `>=1.7.2`, to remove `pyrr` from dependencies,
|
- Bumped ManimGL to `>=1.7.2`, to remove `pyrr` from dependencies,
|
||||||
and to avoid complex code for supporting both `1.7.1` and `>=1.7.2`,
|
and to avoid complex code for supporting both `1.7.1` and `>=1.7.2`,
|
||||||
as the latter includes many breaking changes.
|
as the latter includes many breaking changes.
|
||||||
[#506](https://github.com/jeertmans/manim-slides/pull/506)
|
[#506](https://github.com/jeertmans/manim-slides/pull/506)
|
||||||
@ -70,7 +149,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
(v5.1.10-changed)=
|
(v5.1.10-changed)=
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Allow multiple slide reverses by going backward [@PeculiarProgrammer](https://github.com/PeculiarProgrammer).
|
- Allow multiple slide reverses by going backward [@taibeled](https://github.com/taibeled).
|
||||||
[#488](https://github.com/jeertmans/manim-slides/pull/488)
|
[#488](https://github.com/jeertmans/manim-slides/pull/488)
|
||||||
|
|
||||||
(v5.1.10-fixed)=
|
(v5.1.10-fixed)=
|
||||||
@ -112,7 +191,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
(v5.1.8-chore)=
|
(v5.1.8-chore)=
|
||||||
### Chore
|
### Chore
|
||||||
|
|
||||||
- Pin `rtoml==0.9.0` on Windows platforms,
|
- Pinned `rtoml==0.9.0` on Windows platforms,
|
||||||
see [#398](https://github.com/jeertmans/manim-slides/pull/398),
|
see [#398](https://github.com/jeertmans/manim-slides/pull/398),
|
||||||
until
|
until
|
||||||
[samuelcolvin/rtoml#74](https://github.com/samuelcolvin/rtoml/issues/74)
|
[samuelcolvin/rtoml#74](https://github.com/samuelcolvin/rtoml/issues/74)
|
||||||
@ -143,7 +222,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Fixed whitespace issue in default RevealJS template.
|
- Fixed whitespace issue in default RevealJS template.
|
||||||
[#442](https://github.com/jeertmans/manim-slides/pull/442)
|
[#442](https://github.com/jeertmans/manim-slides/pull/442)
|
||||||
- Fixed black screen issue on recent Qt versions and device loss detected,
|
- Fixed black screen issue on recent Qt versions and device loss detected,
|
||||||
thanks to [@PeculiarProgrammer](https://github.com/PeculiarProgrammer)!
|
thanks to [@taibeled](https://github.com/taibeled)!
|
||||||
[#465](https://github.com/jeertmans/manim-slides/pull/465)
|
[#465](https://github.com/jeertmans/manim-slides/pull/465)
|
||||||
|
|
||||||
(v5.1.8-removed)=
|
(v5.1.8-removed)=
|
||||||
|
@ -4,13 +4,14 @@
|
|||||||
cff-version: 1.2.0
|
cff-version: 1.2.0
|
||||||
title: Manim Slides
|
title: Manim Slides
|
||||||
message: >-
|
message: >-
|
||||||
If you use this software, please cite it using the
|
If you use this software, please cite it using our article
|
||||||
metadata from this file.
|
in the Journal of Open Source Education.
|
||||||
type: software
|
type: software
|
||||||
authors:
|
authors:
|
||||||
- name: Jérome Eertmans
|
- name: Jérome Eertmans
|
||||||
orcid: 'https://orcid.org/0000-0002-5579-5360'
|
orcid: 'https://orcid.org/0000-0002-5579-5360'
|
||||||
website: 'https://eertmans.be'
|
website: 'https://eertmans.be'
|
||||||
|
doi: 10.5281/zenodo.7971360
|
||||||
repository-code: 'https://github.com/jeertmans/manim-slides'
|
repository-code: 'https://github.com/jeertmans/manim-slides'
|
||||||
url: 'https://eertmans.be/manim-slides'
|
url: 'https://eertmans.be/manim-slides'
|
||||||
abstract: >-
|
abstract: >-
|
||||||
@ -26,7 +27,7 @@ keywords:
|
|||||||
- PowerPoint
|
- PowerPoint
|
||||||
- Python
|
- Python
|
||||||
license: MIT
|
license: MIT
|
||||||
version: v5.3.0
|
version: v5.5.0
|
||||||
preferred-citation:
|
preferred-citation:
|
||||||
publisher:
|
publisher:
|
||||||
name: The Open Journal
|
name: The Open Journal
|
||||||
|
10
README.md
10
README.md
@ -1,9 +1,3 @@
|
|||||||
> [!IMPORTANT]
|
|
||||||
> Take the [**Manim Slides Survey**](https://forms.gle/i4scrwPQghbTQwQs5)
|
|
||||||
> to help improve this tool! Thanks in advance to all the people taking the time
|
|
||||||
> to answer this short survey! The form is open until **January 31st 2025**,
|
|
||||||
> and results will be communicated in the GitHub discussions.
|
|
||||||
|
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jeertmans/manim-slides/main/static/logo_dark_transparent.png">
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jeertmans/manim-slides/main/static/logo_dark_transparent.png">
|
||||||
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/jeertmans/manim-slides/main/static/logo_light_transparent.png">
|
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/jeertmans/manim-slides/main/static/logo_light_transparent.png">
|
||||||
@ -234,8 +228,8 @@ you can do so at: [jeertmans@icloud.com](mailto:jeertmans@icloud.com).
|
|||||||
[pypi-download-badge]: https://img.shields.io/pypi/dm/manim-slides
|
[pypi-download-badge]: https://img.shields.io/pypi/dm/manim-slides
|
||||||
[documentation-badge]: https://readthedocs.org/projects/manim-slides/badge/?version=latest
|
[documentation-badge]: https://readthedocs.org/projects/manim-slides/badge/?version=latest
|
||||||
[documentation-url]: https://manim-slides.readthedocs.io/
|
[documentation-url]: https://manim-slides.readthedocs.io/
|
||||||
[doi-badge]: https://zenodo.org/badge/DOI/10.5281/zenodo.8215167.svg
|
[doi-badge]: https://zenodo.org/badge/DOI/10.5281/zenodo.7971360.svg
|
||||||
[doi-url]: https://doi.org/10.5281/zenodo.8215167
|
[doi-url]: https://doi.org/10.5281/zenodo.7971360
|
||||||
[jose-badge]: https://jose.theoj.org/papers/10.21105/jose.00206/status.svg
|
[jose-badge]: https://jose.theoj.org/papers/10.21105/jose.00206/status.svg
|
||||||
[jose-url]: https://doi.org/10.21105/jose.00206
|
[jose-url]: https://doi.org/10.21105/jose.00206
|
||||||
[codecov-badge]: https://codecov.io/gh/jeertmans/manim-slides/branch/main/graph/badge.svg?token=8P4DY9JCE4
|
[codecov-badge]: https://codecov.io/gh/jeertmans/manim-slides/branch/main/graph/badge.svg?token=8P4DY9JCE4
|
||||||
|
@ -31,9 +31,8 @@ RUN wget -O /tmp/install-tl-unx.tar.gz http://mirror.ctan.org/systems/texlive/tl
|
|||||||
# clone and build manim-slides
|
# clone and build manim-slides
|
||||||
COPY . /opt/manim-slides
|
COPY . /opt/manim-slides
|
||||||
WORKDIR /opt/manim-slides
|
WORKDIR /opt/manim-slides
|
||||||
ENV UV_PYTHON=/usr/local/bin/python
|
|
||||||
RUN pip install --no-cache-dir uv
|
RUN pip install --no-cache-dir manim[jupyterlab] .[sphinx-directive]
|
||||||
RUN uv pip install --no-cache-dir manim[jupyterlab] .[sphinx-directive]
|
|
||||||
|
|
||||||
ARG NB_USER=manimslidesuser
|
ARG NB_USER=manimslidesuser
|
||||||
ARG NB_UID=1000
|
ARG NB_UID=1000
|
||||||
|
@ -18,82 +18,75 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="reveal">
|
<div class="reveal">
|
||||||
<div class="slides">
|
<div class="slides">
|
||||||
{%- for presentation_config in presentation_configs -%}
|
{% for presentation_config in presentation_configs -%}
|
||||||
{% set outer_loop = loop %}
|
{%- set outer_loop = loop %}
|
||||||
{%- for slide_config in presentation_config.slides -%}
|
{% for slide_config in presentation_config.slides %}
|
||||||
{%- if one_file -%}
|
{% if one_file %}
|
||||||
{% set file = file_to_data_uri(slide_config.file) %}
|
{% set file = file_to_data_uri(slide_config.file) %}
|
||||||
{%- else -%}
|
{% else %}
|
||||||
{% set file = assets_dir / slide_config.file.name %}
|
{% set file = assets_dir / (prefix(outer_loop.index0) + slide_config.file.name) %}
|
||||||
{%- endif -%}
|
{% endif %}
|
||||||
<section
|
<section
|
||||||
data-background-size={{ background_size }}
|
data-background-size={{ background_size }}
|
||||||
data-background-color="{{ presentation_config.background_color }}"
|
data-background-color="{{ presentation_config.background_color }}"
|
||||||
data-background-video="{{ file }}"
|
data-background-video="{{ file }}"
|
||||||
{% if loop.index == 1 and outer_loop.index == 1 -%}
|
{% if loop.index == 1 and outer_loop.index == 1 %}
|
||||||
data-background-video-muted
|
data-background-video-muted
|
||||||
{%- endif %}
|
{% endif %}
|
||||||
{% if slide_config.loop -%}
|
{% if slide_config.loop %}
|
||||||
data-background-video-loop
|
data-background-video-loop
|
||||||
{%- endif -%}
|
{% endif %}
|
||||||
{% if slide_config.auto_next -%}
|
{% if slide_config.auto_next %}
|
||||||
data-autoslide="{{ get_duration_ms(slide_config.file) }}"
|
data-autoslide="{{ get_duration_ms(slide_config.file) }}"
|
||||||
{%- endif -%}>
|
{% endif %}
|
||||||
{% if slide_config.notes != "" -%}
|
>
|
||||||
<aside class="notes" data-markdown>{{ slide_config.notes }}</aside>
|
{% if slide_config.notes != "" %}
|
||||||
{%- endif %}
|
<aside class="notes" data-markdown>{{ slide_config.notes }}</aside>
|
||||||
</section>
|
{% endif %}
|
||||||
{%- endfor -%}
|
</section>
|
||||||
{%- endfor -%}
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/{{ reveal_version }}/reveal.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/{{ reveal_version }}/reveal.min.js"></script>
|
||||||
|
|
||||||
<!-- To include plugins, see: https://revealjs.com/plugins/ -->
|
<!-- To include plugins, see: https://revealjs.com/plugins/ -->
|
||||||
|
{% if has_notes %}
|
||||||
{% if has_notes -%}
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/{{ reveal_version }}/plugin/markdown/markdown.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/{{ reveal_version }}/plugin/markdown/markdown.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/{{ reveal_version }}/plugin/notes/notes.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/{{ reveal_version }}/plugin/notes/notes.min.js"></script>
|
||||||
{%- endif -%}
|
{% endif %}
|
||||||
|
|
||||||
<!-- <script src="index.js"></script> -->
|
<!-- <script src="index.js"></script> -->
|
||||||
<script>
|
<script>
|
||||||
Reveal.initialize({
|
Reveal.initialize({
|
||||||
{% if has_notes -%}
|
{% if has_notes %}
|
||||||
plugins: [ RevealMarkdown, RevealNotes ],
|
/// The list of RevealJS plugins.
|
||||||
{%- endif %}
|
plugins: [ RevealMarkdown, RevealNotes ],
|
||||||
|
{% endif %}
|
||||||
// The "normal" size of the presentation, aspect ratio will
|
// The "normal" size of the presentation, aspect ratio will
|
||||||
// be preserved when the presentation is scaled to fit different
|
// be preserved when the presentation is scaled to fit different
|
||||||
// resolutions. Can be specified using percentage units.
|
// resolutions. Can be specified using percentage units.
|
||||||
width: {{ width }},
|
width: {{ width }},
|
||||||
height: {{ height }},
|
height: {{ height }},
|
||||||
|
|
||||||
// Factor of the display size that should remain empty around
|
// Factor of the display size that should remain empty around
|
||||||
// the content
|
// the content
|
||||||
margin: {{ margin }},
|
margin: {{ margin }},
|
||||||
|
|
||||||
// Bounds for smallest/largest possible scale to apply to content
|
// Bounds for smallest/largest possible scale to apply to content
|
||||||
minScale: {{ min_scale }},
|
minScale: {{ min_scale }},
|
||||||
maxScale: {{ max_scale }},
|
maxScale: {{ max_scale }},
|
||||||
|
|
||||||
// Display presentation control arrows
|
// Display presentation control arrows
|
||||||
controls: {{ controls }},
|
controls: {{ controls }},
|
||||||
|
|
||||||
// Help the user learn the controls by providing hints, for example by
|
// Help the user learn the controls by providing hints, for example by
|
||||||
// bouncing the down arrow when they first encounter a vertical slide
|
// bouncing the down arrow when they first encounter a vertical slide
|
||||||
controlsTutorial: {{ controls_tutorial }},
|
controlsTutorial: {{ controls_tutorial }},
|
||||||
|
|
||||||
// Determines where controls appear, "edges" or "bottom-right"
|
// Determines where controls appear, "edges" or "bottom-right"
|
||||||
controlsLayout: {{ controls_layout }},
|
controlsLayout: {{ controls_layout }},
|
||||||
|
|
||||||
// Visibility rule for backwards navigation arrows; "faded", "hidden"
|
// Visibility rule for backwards navigation arrows; "faded", "hidden"
|
||||||
// or "visible"
|
// or "visible"
|
||||||
controlsBackArrows: {{ controls_back_arrows }},
|
controlsBackArrows: {{ controls_back_arrows }},
|
||||||
|
|
||||||
// Display a presentation progress bar
|
// Display a presentation progress bar
|
||||||
progress: {{ progress }},
|
progress: {{ progress }},
|
||||||
|
|
||||||
// Display the page number of the current slide
|
// Display the page number of the current slide
|
||||||
// - true: Show slide number
|
// - true: Show slide number
|
||||||
// - false: Hide slide number
|
// - false: Hide slide number
|
||||||
@ -109,55 +102,43 @@
|
|||||||
// object and return an array with one string [slideNumber] or
|
// object and return an array with one string [slideNumber] or
|
||||||
// three strings [n1,delimiter,n2]. See #formatSlideNumber().
|
// three strings [n1,delimiter,n2]. See #formatSlideNumber().
|
||||||
slideNumber: {{ slide_number }},
|
slideNumber: {{ slide_number }},
|
||||||
|
|
||||||
// Can be used to limit the contexts in which the slide number appears
|
// Can be used to limit the contexts in which the slide number appears
|
||||||
// - "all": Always show the slide number
|
// - "all": Always show the slide number
|
||||||
// - "print": Only when printing to PDF
|
// - "print": Only when printing to PDF
|
||||||
// - "speaker": Only in the speaker view
|
// - "speaker": Only in the speaker view
|
||||||
showSlideNumber: {{ show_slide_number }},
|
showSlideNumber: {{ show_slide_number }},
|
||||||
|
|
||||||
// Use 1 based indexing for # links to match slide number (default is zero
|
// Use 1 based indexing for # links to match slide number (default is zero
|
||||||
// based)
|
// based)
|
||||||
hashOneBasedIndex: {{ hash_one_based_index }},
|
hashOneBasedIndex: {{ hash_one_based_index }},
|
||||||
|
|
||||||
// Add the current slide number to the URL hash so that reloading the
|
// Add the current slide number to the URL hash so that reloading the
|
||||||
// page/copying the URL will return you to the same slide
|
// page/copying the URL will return you to the same slide
|
||||||
hash: {{ hash }},
|
hash: {{ hash }},
|
||||||
|
|
||||||
// Flags if we should monitor the hash and change slides accordingly
|
// Flags if we should monitor the hash and change slides accordingly
|
||||||
respondToHashChanges: {{ respond_to_hash_changes }},
|
respondToHashChanges: {{ respond_to_hash_changes }},
|
||||||
|
// Enable support for jump-to-slide navigation shortcuts
|
||||||
|
jumpToSlide: {{ jump_to_slide }},
|
||||||
// Push each slide change to the browser history. Implies `hash: true`
|
// Push each slide change to the browser history. Implies `hash: true`
|
||||||
history: {{ history }},
|
history: {{ history }},
|
||||||
|
|
||||||
// Enable keyboard shortcuts for navigation
|
// Enable keyboard shortcuts for navigation
|
||||||
keyboard: {{ keyboard }},
|
keyboard: {{ keyboard }},
|
||||||
|
|
||||||
// Optional function that blocks keyboard events when retuning false
|
// Optional function that blocks keyboard events when retuning false
|
||||||
//
|
//
|
||||||
// If you set this to 'focused', we will only capture keyboard events
|
// If you set this to 'focused', we will only capture keyboard events
|
||||||
// for embedded decks when they are in focus
|
// for embedded decks when they are in focus
|
||||||
keyboardCondition: {{ keyboard_condition }},
|
keyboardCondition: {{ keyboard_condition }},
|
||||||
|
|
||||||
// Disables the default reveal.js slide layout (scaling and centering)
|
// Disables the default reveal.js slide layout (scaling and centering)
|
||||||
// so that you can use custom CSS layout
|
// so that you can use custom CSS layout
|
||||||
disableLayout: {{ disable_layout }},
|
disableLayout: {{ disable_layout }},
|
||||||
|
|
||||||
// Enable the slide overview mode
|
// Enable the slide overview mode
|
||||||
overview: {{ overview }},
|
overview: {{ overview }},
|
||||||
|
|
||||||
// Vertical centering of slides
|
// Vertical centering of slides
|
||||||
center: {{ center }},
|
center: {{ center }},
|
||||||
|
|
||||||
// Enables touch navigation on devices with touch input
|
// Enables touch navigation on devices with touch input
|
||||||
touch: {{ touch }},
|
touch: {{ touch }},
|
||||||
|
|
||||||
// Loop the presentation
|
// Loop the presentation
|
||||||
loop: {{ loop }},
|
loop: {{ loop }},
|
||||||
|
|
||||||
// Change the presentation direction to be RTL
|
// Change the presentation direction to be RTL
|
||||||
rtl: {{ rtl }},
|
rtl: {{ rtl }},
|
||||||
|
|
||||||
// Changes the behavior of our navigation directions.
|
// Changes the behavior of our navigation directions.
|
||||||
//
|
//
|
||||||
// "default"
|
// "default"
|
||||||
@ -183,159 +164,146 @@
|
|||||||
// from 1.3 -> 2.1. If "grid" is used, the same navigation takes you
|
// from 1.3 -> 2.1. If "grid" is used, the same navigation takes you
|
||||||
// from 1.3 -> 2.3.
|
// from 1.3 -> 2.3.
|
||||||
navigationMode: {{ navigation_mode }},
|
navigationMode: {{ navigation_mode }},
|
||||||
|
|
||||||
// Randomizes the order of slides each time the presentation loads
|
// Randomizes the order of slides each time the presentation loads
|
||||||
shuffle: {{ shuffle }},
|
shuffle: {{ shuffle }},
|
||||||
|
|
||||||
// Turns fragments on and off globally
|
// Turns fragments on and off globally
|
||||||
fragments: {{ fragments }},
|
fragments: {{ fragments }},
|
||||||
|
|
||||||
// Flags whether to include the current fragment in the URL,
|
// Flags whether to include the current fragment in the URL,
|
||||||
// so that reloading brings you to the same fragment position
|
// so that reloading brings you to the same fragment position
|
||||||
fragmentInURL: {{ fragment_in_url }},
|
fragmentInURL: {{ fragment_in_url }},
|
||||||
|
|
||||||
// Flags if the presentation is running in an embedded mode,
|
// Flags if the presentation is running in an embedded mode,
|
||||||
// i.e. contained within a limited portion of the screen
|
// i.e. contained within a limited portion of the screen
|
||||||
embedded: {{ embedded }},
|
embedded: {{ embedded }},
|
||||||
|
|
||||||
// Flags if we should show a help overlay when the question-mark
|
// Flags if we should show a help overlay when the question-mark
|
||||||
// key is pressed
|
// key is pressed
|
||||||
help: {{ help }},
|
help: {{ help }},
|
||||||
|
|
||||||
// Flags if it should be possible to pause the presentation (blackout)
|
// Flags if it should be possible to pause the presentation (blackout)
|
||||||
pause: {{ pause }},
|
pause: {{ pause }},
|
||||||
|
|
||||||
// Flags if speaker notes should be visible to all viewers
|
// Flags if speaker notes should be visible to all viewers
|
||||||
showNotes: {{ show_notes }},
|
showNotes: {{ show_notes }},
|
||||||
|
|
||||||
// Global override for autolaying embedded media (video/audio/iframe)
|
// Global override for autolaying embedded media (video/audio/iframe)
|
||||||
// - null: Media will only autoplay if data-autoplay is present
|
// - null: Media will only autoplay if data-autoplay is present
|
||||||
// - true: All media will autoplay, regardless of individual setting
|
// - true: All media will autoplay, regardless of individual setting
|
||||||
// - false: No media will autoplay, regardless of individual setting
|
// - false: No media will autoplay, regardless of individual setting
|
||||||
autoPlayMedia: {{ auto_play_media }},
|
autoPlayMedia: {{ auto_play_media }},
|
||||||
|
|
||||||
// Global override for preloading lazy-loaded iframes
|
// Global override for preloading lazy-loaded iframes
|
||||||
// - null: Iframes with data-src AND data-preload will be loaded when within
|
// - null: Iframes with data-src AND data-preload will be loaded when within
|
||||||
// the viewDistance, iframes with only data-src will be loaded when visible
|
// the viewDistance, iframes with only data-src will be loaded when visible
|
||||||
// - true: All iframes with data-src will be loaded when within the viewDistance
|
// - true: All iframes with data-src will be loaded when within the viewDistance
|
||||||
// - false: All iframes with data-src will be loaded only when visible
|
// - false: All iframes with data-src will be loaded only when visible
|
||||||
preloadIframes: {{ preload_iframes }},
|
preloadIframes: {{ preload_iframes }},
|
||||||
|
|
||||||
// Can be used to globally disable auto-animation
|
// Can be used to globally disable auto-animation
|
||||||
autoAnimate: {{ auto_animate }},
|
autoAnimate: {{ auto_animate }},
|
||||||
|
|
||||||
// Optionally provide a custom element matcher that will be
|
// Optionally provide a custom element matcher that will be
|
||||||
// used to dictate which elements we can animate between.
|
// used to dictate which elements we can animate between.
|
||||||
autoAnimateMatcher: {{ auto_animate_matcher }},
|
autoAnimateMatcher: {{ auto_animate_matcher }},
|
||||||
|
|
||||||
// Default settings for our auto-animate transitions, can be
|
// Default settings for our auto-animate transitions, can be
|
||||||
// overridden per-slide or per-element via data arguments
|
// overridden per-slide or per-element via data arguments
|
||||||
autoAnimateEasing: {{ auto_animate_easing }},
|
autoAnimateEasing: {{ auto_animate_easing }},
|
||||||
autoAnimateDuration: {{ auto_animate_duration }},
|
autoAnimateDuration: {{ auto_animate_duration }},
|
||||||
autoAnimateUnmatched: {{ auto_animate_unmatched }},
|
autoAnimateUnmatched: {{ auto_animate_unmatched }},
|
||||||
|
|
||||||
// CSS properties that can be auto-animated. Position & scale
|
// CSS properties that can be auto-animated. Position & scale
|
||||||
// is matched separately so there's no need to include styles
|
// is matched separately so there's no need to include styles
|
||||||
// like top/right/bottom/left, width/height or margin.
|
// like top/right/bottom/left, width/height or margin.
|
||||||
autoAnimateStyles: {{ auto_animate_styles }},
|
autoAnimateStyles: {{ auto_animate_styles }},
|
||||||
|
|
||||||
// Controls automatic progression to the next slide
|
// Controls automatic progression to the next slide
|
||||||
// - 0: Auto-sliding only happens if the data-autoslide HTML attribute
|
// - 0: Auto-sliding only happens if the data-autoslide HTML attribute
|
||||||
// is present on the current slide or fragment
|
// is present on the current slide or fragment
|
||||||
// - 1+: All slides will progress automatically at the given interval
|
// - 1+: All slides will progress automatically at the given interval
|
||||||
// - false: No auto-sliding, even if data-autoslide is present
|
// - false: No auto-sliding, even if data-autoslide is present
|
||||||
autoSlide: {{ auto_slide }},
|
autoSlide: {{ auto_slide }},
|
||||||
|
|
||||||
// Stop auto-sliding after user input
|
// Stop auto-sliding after user input
|
||||||
autoSlideStoppable: {{ auto_slide_stoppable }},
|
autoSlideStoppable: {{ auto_slide_stoppable }},
|
||||||
|
|
||||||
// Use this method for navigation when auto-sliding (defaults to navigateNext)
|
// Use this method for navigation when auto-sliding (defaults to navigateNext)
|
||||||
autoSlideMethod: {{ auto_slide_method }},
|
autoSlideMethod: {{ auto_slide_method }},
|
||||||
|
|
||||||
// Specify the average time in seconds that you think you will spend
|
// Specify the average time in seconds that you think you will spend
|
||||||
// presenting each slide. This is used to show a pacing timer in the
|
// presenting each slide. This is used to show a pacing timer in the
|
||||||
// speaker view
|
// speaker view
|
||||||
defaultTiming: {{ default_timing }},
|
defaultTiming: {{ default_timing }},
|
||||||
|
|
||||||
// Enable slide navigation via mouse wheel
|
// Enable slide navigation via mouse wheel
|
||||||
mouseWheel: {{ mouse_wheel }},
|
mouseWheel: {{ mouse_wheel }},
|
||||||
|
|
||||||
// Opens links in an iframe preview overlay
|
// Opens links in an iframe preview overlay
|
||||||
// Add `data-preview-link` and `data-preview-link="false"` to customize each link
|
// Add `data-preview-link` and `data-preview-link="false"` to customize each link
|
||||||
// individually
|
// individually
|
||||||
previewLinks: {{ preview_links }},
|
previewLinks: {{ preview_links }},
|
||||||
|
|
||||||
// Exposes the reveal.js API through window.postMessage
|
// Exposes the reveal.js API through window.postMessage
|
||||||
postMessage: {{ post_message }},
|
postMessage: {{ post_message }},
|
||||||
|
|
||||||
// Dispatches all reveal.js events to the parent window through postMessage
|
// Dispatches all reveal.js events to the parent window through postMessage
|
||||||
postMessageEvents: {{ post_message_events }},
|
postMessageEvents: {{ post_message_events }},
|
||||||
|
|
||||||
// Focuses body when page changes visibility to ensure keyboard shortcuts work
|
// Focuses body when page changes visibility to ensure keyboard shortcuts work
|
||||||
focusBodyOnPageVisibilityChange: {{ focus_body_on_page_visibility_change }},
|
focusBodyOnPageVisibilityChange: {{ focus_body_on_page_visibility_change }},
|
||||||
|
|
||||||
// Transition style
|
// Transition style
|
||||||
transition: {{ transition }}, // none/fade/slide/convex/concave/zoom
|
transition: {{ transition }}, // none/fade/slide/convex/concave/zoom
|
||||||
|
|
||||||
// Transition speed
|
// Transition speed
|
||||||
transitionSpeed: {{ transition_speed }}, // default/fast/slow
|
transitionSpeed: {{ transition_speed }}, // default/fast/slow
|
||||||
|
|
||||||
// Transition style for full page slide backgrounds
|
// Transition style for full page slide backgrounds
|
||||||
backgroundTransition: {{ background_transition }}, // none/fade/slide/convex/concave/zoom
|
backgroundTransition: {{ background_transition }}, // none/fade/slide/convex/concave/zoom
|
||||||
|
|
||||||
// The maximum number of pages a single slide can expand onto when printing
|
// The maximum number of pages a single slide can expand onto when printing
|
||||||
// to PDF, unlimited by default
|
// to PDF, unlimited by default
|
||||||
pdfMaxPagesPerSlide: {{ pdf_max_pages_per_slide }},
|
pdfMaxPagesPerSlide: {{ pdf_max_pages_per_slide }},
|
||||||
|
|
||||||
// Prints each fragment on a separate slide
|
// Prints each fragment on a separate slide
|
||||||
pdfSeparateFragments: {{ pdf_separate_fragments }},
|
pdfSeparateFragments: {{ pdf_separate_fragments }},
|
||||||
|
|
||||||
// Offset used to reduce the height of content within exported PDF pages.
|
// Offset used to reduce the height of content within exported PDF pages.
|
||||||
// This exists to account for environment differences based on how you
|
// This exists to account for environment differences based on how you
|
||||||
// print to PDF. CLI printing options, like phantomjs and wkpdf, can end
|
// print to PDF. CLI printing options, like phantomjs and wkpdf, can end
|
||||||
// on precisely the total height of the document whereas in-browser
|
// on precisely the total height of the document whereas in-browser
|
||||||
// printing has to end one pixel before.
|
// printing has to end one pixel before.
|
||||||
pdfPageHeightOffset: {{ pdf_page_height_offset }},
|
pdfPageHeightOffset: {{ pdf_page_height_offset }},
|
||||||
|
|
||||||
// Number of slides away from the current that are visible
|
// Number of slides away from the current that are visible
|
||||||
viewDistance: {{ view_distance }},
|
viewDistance: {{ view_distance }},
|
||||||
|
|
||||||
// Number of slides away from the current that are visible on mobile
|
// Number of slides away from the current that are visible on mobile
|
||||||
// devices. It is advisable to set this to a lower number than
|
// devices. It is advisable to set this to a lower number than
|
||||||
// viewDistance in order to save resources.
|
// viewDistance in order to save resources.
|
||||||
mobileViewDistance: {{ mobile_view_distance }},
|
mobileViewDistance: {{ mobile_view_distance }},
|
||||||
|
|
||||||
// The display mode that will be used to show slides
|
// The display mode that will be used to show slides
|
||||||
display: {{ display }},
|
display: {{ display }},
|
||||||
|
|
||||||
// Hide cursor if inactive
|
// Hide cursor if inactive
|
||||||
hideInactiveCursor: {{ hide_inactive_cursor }},
|
hideInactiveCursor: {{ hide_inactive_cursor }},
|
||||||
|
|
||||||
// Time before the cursor is hidden (in ms)
|
// Time before the cursor is hidden (in ms)
|
||||||
hideCursorTime: {{ hide_cursor_time }}
|
hideCursorTime: {{ hide_cursor_time }}
|
||||||
});
|
});
|
||||||
|
|
||||||
{% if one_file %}
|
{% if one_file %}
|
||||||
// Fix found by @t-fritsch and @Rapsssito on GitHub
|
// Fix found by @t-fritsch and @Rapsssito on GitHub
|
||||||
// see: https://github.com/hakimel/reveal.js/discussions/3362#discussioncomment-11733074.
|
// see: https://github.com/hakimel/reveal.js/discussions/3362#discussioncomment-11733074.
|
||||||
function fixBase64VideoBackground(event) {
|
function setVideoBase64(video) {
|
||||||
// Analyze all slides backgrounds
|
const sources = video.querySelectorAll('source');
|
||||||
for (const slide of Reveal.getBackgroundsElement().querySelectorAll('.slide-background')) {
|
// Update the source of the video
|
||||||
// Get the slide video and its sources for each background
|
sources.forEach((source, i) => {
|
||||||
const video = slide.querySelector('video');
|
const src = source.getAttribute('src');
|
||||||
const sources = video.querySelectorAll('source');
|
if(src.match(/^data:video.*;base64$/)) {
|
||||||
// Update the source of the video
|
const nextSrc = sources[i+1]?.getAttribute('src');
|
||||||
sources.forEach((source, i) => {
|
video.setAttribute('src', `${src},${nextSrc}`);
|
||||||
const src = source.getAttribute('src');
|
}
|
||||||
if(src.match(/^data:video.*;base64$/)) {
|
});
|
||||||
const nextSrc = sources[i+1]?.getAttribute('src');
|
}
|
||||||
video.setAttribute('src', `${src},${nextSrc}`);
|
|
||||||
|
function fixBase64VideoBackground(event) {
|
||||||
|
// Analyze all slides backgrounds
|
||||||
|
for (const slide of Reveal.getBackgroundsElement().querySelectorAll('.slide-background')) {
|
||||||
|
// Get the slide video and its sources for each background
|
||||||
|
const video = slide.querySelector('video');
|
||||||
|
if (video) {
|
||||||
|
setVideoBase64(video);
|
||||||
|
} else {
|
||||||
|
// Listen to the creation of the video element
|
||||||
|
const observer = new MutationObserver((mutationsList) => {
|
||||||
|
for (const mutation of mutationsList) {
|
||||||
|
if (mutation.type === 'childList') {
|
||||||
|
for (const addedNode of mutation.addedNodes) {
|
||||||
|
if (addedNode.tagName === 'VIDEO') {
|
||||||
|
setVideoBase64(addedNode);
|
||||||
|
observer.disconnect(); // Stop observing once the video is handled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
observer.observe(slide, { childList: true, subtree: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Setup base64 videos
|
}
|
||||||
Reveal.on( 'ready', fixBase64VideoBackground );
|
// Setup base64 videos
|
||||||
|
Reveal.on( 'ready', fixBase64VideoBackground );
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
# For the full list of built-in configuration values, see the documentation:
|
# For the full list of built-in configuration values, see the documentation:
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||||
|
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
@ -55,6 +56,7 @@ add_module_names = False
|
|||||||
# -- Options for HTML output -------------------------------------------------
|
# -- Options for HTML output -------------------------------------------------
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||||
|
|
||||||
|
html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "/")
|
||||||
html_theme = "furo"
|
html_theme = "furo"
|
||||||
html_static_path = ["_static"]
|
html_static_path = ["_static"]
|
||||||
html_favicon = "_static/favicon.png"
|
html_favicon = "_static/favicon.png"
|
||||||
|
@ -18,6 +18,8 @@ use, not the methods used internally when rendering.
|
|||||||
next_section,
|
next_section,
|
||||||
next_slide,
|
next_slide,
|
||||||
remove_from_canvas,
|
remove_from_canvas,
|
||||||
|
start_skip_animations,
|
||||||
|
stop_skip_animations,
|
||||||
wait_time_between_slides,
|
wait_time_between_slides,
|
||||||
wipe,
|
wipe,
|
||||||
zoom,
|
zoom,
|
||||||
|
16
example.py
16
example.py
@ -53,7 +53,7 @@ class ConvertExample(Slide):
|
|||||||
self.next_slide()
|
self.next_slide()
|
||||||
|
|
||||||
code = Code(
|
code = Code(
|
||||||
code="""from manim import *
|
code_string="""from manim import *
|
||||||
|
|
||||||
|
|
||||||
class Example(Scene):
|
class Example(Scene):
|
||||||
@ -72,7 +72,7 @@ class Example(Scene):
|
|||||||
)
|
)
|
||||||
|
|
||||||
code_step_1 = Code(
|
code_step_1 = Code(
|
||||||
code="""from manim import *
|
code_string="""from manim import *
|
||||||
from manim_slides import Slide
|
from manim_slides import Slide
|
||||||
|
|
||||||
class Example(Scene):
|
class Example(Scene):
|
||||||
@ -91,7 +91,7 @@ class Example(Scene):
|
|||||||
)
|
)
|
||||||
|
|
||||||
code_step_2 = Code(
|
code_step_2 = Code(
|
||||||
code="""from manim import *
|
code_string="""from manim import *
|
||||||
from manim_slides import Slide
|
from manim_slides import Slide
|
||||||
|
|
||||||
class Example(Slide):
|
class Example(Slide):
|
||||||
@ -110,7 +110,7 @@ class Example(Slide):
|
|||||||
)
|
)
|
||||||
|
|
||||||
code_step_3 = Code(
|
code_step_3 = Code(
|
||||||
code="""from manim import *
|
code_string="""from manim import *
|
||||||
from manim_slides import Slide
|
from manim_slides import Slide
|
||||||
|
|
||||||
class Example(Slide):
|
class Example(Slide):
|
||||||
@ -129,7 +129,7 @@ class Example(Slide):
|
|||||||
)
|
)
|
||||||
|
|
||||||
code_step_4 = Code(
|
code_step_4 = Code(
|
||||||
code="""from manim import *
|
code_string="""from manim import *
|
||||||
from manim_slides import Slide
|
from manim_slides import Slide
|
||||||
|
|
||||||
class Example(Slide):
|
class Example(Slide):
|
||||||
@ -148,19 +148,19 @@ class Example(Slide):
|
|||||||
)
|
)
|
||||||
|
|
||||||
code_step_5 = Code(
|
code_step_5 = Code(
|
||||||
code="manim-slide render example.py Example",
|
code_string="manim-slide render example.py Example",
|
||||||
language="console",
|
language="console",
|
||||||
)
|
)
|
||||||
|
|
||||||
code_step_6 = Code(
|
code_step_6 = Code(
|
||||||
code="manim-slides Example",
|
code_string="manim-slides Example",
|
||||||
language="console",
|
language="console",
|
||||||
)
|
)
|
||||||
|
|
||||||
or_text = Text("or generate HTML presentation").scale(0.5)
|
or_text = Text("or generate HTML presentation").scale(0.5)
|
||||||
|
|
||||||
code_step_7 = Code(
|
code_step_7 = Code(
|
||||||
code="manim-slides convert Example slides.html --open",
|
code_string="manim-slides convert Example slides.html --open",
|
||||||
language="console",
|
language="console",
|
||||||
).shift(DOWN)
|
).shift(DOWN)
|
||||||
|
|
||||||
|
@ -1 +1 @@
|
|||||||
__version__ = "5.3.0"
|
__version__ = "5.5.0"
|
||||||
|
@ -160,6 +160,8 @@ class BaseSlideConfig(BaseModel): # type: ignore
|
|||||||
reversed_playback_rate: float = 1.0
|
reversed_playback_rate: float = 1.0
|
||||||
notes: str = ""
|
notes: str = ""
|
||||||
dedent_notes: bool = True
|
dedent_notes: bool = True
|
||||||
|
skip_animations: bool = False
|
||||||
|
src: Optional[FilePath] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def wrapper(cls, arg_name: str) -> Callable[..., Any]:
|
def wrapper(cls, arg_name: str) -> Callable[..., Any]:
|
||||||
@ -204,14 +206,13 @@ class BaseSlideConfig(BaseModel): # type: ignore
|
|||||||
return _wrapper_
|
return _wrapper_
|
||||||
|
|
||||||
@model_validator(mode="after")
|
@model_validator(mode="after")
|
||||||
@classmethod
|
|
||||||
def apply_dedent_notes(
|
def apply_dedent_notes(
|
||||||
cls, base_slide_config: "BaseSlideConfig"
|
self,
|
||||||
) -> "BaseSlideConfig":
|
) -> "BaseSlideConfig":
|
||||||
if base_slide_config.dedent_notes:
|
if self.dedent_notes:
|
||||||
base_slide_config.notes = dedent(base_slide_config.notes)
|
self.notes = dedent(self.notes)
|
||||||
|
|
||||||
return base_slide_config
|
return self
|
||||||
|
|
||||||
|
|
||||||
class PreSlideConfig(BaseSlideConfig):
|
class PreSlideConfig(BaseSlideConfig):
|
||||||
@ -241,25 +242,33 @@ class PreSlideConfig(BaseSlideConfig):
|
|||||||
return v
|
return v
|
||||||
|
|
||||||
@model_validator(mode="after")
|
@model_validator(mode="after")
|
||||||
@classmethod
|
|
||||||
def start_animation_is_before_end(
|
def start_animation_is_before_end(
|
||||||
cls, pre_slide_config: "PreSlideConfig"
|
self,
|
||||||
) -> "PreSlideConfig":
|
) -> "PreSlideConfig":
|
||||||
if pre_slide_config.start_animation >= pre_slide_config.end_animation:
|
if self.start_animation > self.end_animation:
|
||||||
if pre_slide_config.start_animation == pre_slide_config.end_animation == 0:
|
|
||||||
raise ValueError(
|
|
||||||
"You have to play at least one animation (e.g., `self.wait()`) "
|
|
||||||
"before pausing. If you want to start paused, use the appropriate "
|
|
||||||
"command-line option when presenting. "
|
|
||||||
"IMPORTANT: when using ManimGL, `self.wait()` is not considered "
|
|
||||||
"to be an animation, so prefer to directly use `self.play(...)`."
|
|
||||||
)
|
|
||||||
|
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Start animation index must be strictly lower than end animation index"
|
"Start animation index must be strictly lower than end animation index"
|
||||||
)
|
)
|
||||||
|
return self
|
||||||
|
|
||||||
return pre_slide_config
|
@model_validator(mode="after")
|
||||||
|
def has_src_or_more_than_zero_animations(
|
||||||
|
self,
|
||||||
|
) -> "PreSlideConfig":
|
||||||
|
if self.src is not None and self.start_animation != self.end_animation:
|
||||||
|
raise ValueError(
|
||||||
|
"A slide cannot have 'src=...' and more than zero animations at the same time."
|
||||||
|
)
|
||||||
|
elif self.src is None and self.start_animation == self.end_animation:
|
||||||
|
raise ValueError(
|
||||||
|
"You have to play at least one animation (e.g., 'self.wait()') "
|
||||||
|
"before pausing. If you want to start paused, use the appropriate "
|
||||||
|
"command-line option when presenting. "
|
||||||
|
"IMPORTANT: when using ManimGL, 'self.wait()' is not considered "
|
||||||
|
"to be an animation, so prefer to directly use 'self.play(...)'."
|
||||||
|
)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def slides_slice(self) -> slice:
|
def slides_slice(self) -> slice:
|
||||||
|
@ -531,7 +531,7 @@ class RevealJS(Converter):
|
|||||||
"black",
|
"black",
|
||||||
description="Background color used in slides, not relevant if videos fill the whole area.",
|
description="Background color used in slides, not relevant if videos fill the whole area.",
|
||||||
)
|
)
|
||||||
reveal_version: str = Field("5.1.0", description="RevealJS version.")
|
reveal_version: str = Field("5.2.0", description="RevealJS version.")
|
||||||
reveal_theme: RevealTheme = Field(
|
reveal_theme: RevealTheme = Field(
|
||||||
RevealTheme.black, description="RevealJS version."
|
RevealTheme.black, description="RevealJS version."
|
||||||
)
|
)
|
||||||
@ -594,7 +594,9 @@ class RevealJS(Converter):
|
|||||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
with open(dest, "w") as f:
|
with open(dest, "w") as f:
|
||||||
revealjs_template = Template(self.load_template())
|
revealjs_template = Template(
|
||||||
|
self.load_template(), trim_blocks=True, lstrip_blocks=True
|
||||||
|
)
|
||||||
|
|
||||||
options = self.model_dump()
|
options = self.model_dump()
|
||||||
|
|
||||||
|
@ -339,7 +339,7 @@ class ManimSlidesDirective(Directive):
|
|||||||
ref_block = ""
|
ref_block = ""
|
||||||
|
|
||||||
if "quality" in self.options:
|
if "quality" in self.options:
|
||||||
quality = f'{self.options["quality"]}_quality'
|
quality = f"{self.options['quality']}_quality"
|
||||||
else:
|
else:
|
||||||
quality = "example_quality"
|
quality = "example_quality"
|
||||||
frame_rate = QUALITIES[quality]["frame_rate"]
|
frame_rate = QUALITIES[quality]["frame_rate"]
|
||||||
@ -483,7 +483,7 @@ def _log_rendering_times(*args):
|
|||||||
)
|
)
|
||||||
for row in group:
|
for row in group:
|
||||||
print( # noqa: T201
|
print( # noqa: T201
|
||||||
f"{' '*(max_file_length)} {row[2].rjust(7)}s {row[1]}"
|
f"{' ' * (max_file_length)} {row[2].rjust(7)}s {row[1]}"
|
||||||
)
|
)
|
||||||
print("") # noqa: T201
|
print("") # noqa: T201
|
||||||
|
|
||||||
|
@ -310,7 +310,7 @@ def present( # noqa: C901
|
|||||||
except IndexError:
|
except IndexError:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"Invalid screen number {number}, "
|
f"Invalid screen number {number}, "
|
||||||
f"allowed values are from 0 to {len(screens)-1} (incl.)"
|
f"allowed values are from 0 to {len(screens) - 1} (incl.)"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -463,13 +463,13 @@ class Player(QMainWindow): # type: ignore[misc]
|
|||||||
def presentation_changed_callback(self) -> None:
|
def presentation_changed_callback(self) -> None:
|
||||||
index = self.current_presentation_index
|
index = self.current_presentation_index
|
||||||
count = self.presentations_count
|
count = self.presentations_count
|
||||||
self.info.scene_label.setText(f"{index+1:4d}/{count:4<d}")
|
self.info.scene_label.setText(f"{index + 1:4d}/{count:4<d}")
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def slide_changed_callback(self) -> None:
|
def slide_changed_callback(self) -> None:
|
||||||
index = self.current_slide_index
|
index = self.current_slide_index
|
||||||
count = self.current_slides_count
|
count = self.current_slides_count
|
||||||
self.info.slide_label.setText(f"{index+1:4d}/{count:4<d}")
|
self.info.slide_label.setText(f"{index + 1:4d}/{count:4<d}")
|
||||||
self.info.slide_notes.setText(self.current_slide_config.notes)
|
self.info.slide_notes.setText(self.current_slide_config.notes)
|
||||||
self.preview_next_slide()
|
self.preview_next_slide()
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ API: str = os.environ.get(MANIM_API, "manim").lower()
|
|||||||
|
|
||||||
if API not in API_NAMES:
|
if API not in API_NAMES:
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
f"Specified MANIM_API={API!r} is not in valid options: " f"{API_NAMES}",
|
f"Specified MANIM_API={API!r} is not in valid options: {API_NAMES}",
|
||||||
)
|
)
|
||||||
|
|
||||||
API_NAME = API_NAMES[API]
|
API_NAME = API_NAMES[API]
|
||||||
|
@ -36,6 +36,8 @@ class BaseSlide:
|
|||||||
disable_caching: bool = False
|
disable_caching: bool = False
|
||||||
flush_cache: bool = False
|
flush_cache: bool = False
|
||||||
skip_reversing: bool = False
|
skip_reversing: bool = False
|
||||||
|
max_duration_before_split_reverse: float | None = 4.0
|
||||||
|
num_processes: int | None = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, *args: Any, output_folder: Path = FOLDER_PATH, **kwargs: Any
|
self, *args: Any, output_folder: Path = FOLDER_PATH, **kwargs: Any
|
||||||
@ -49,6 +51,7 @@ class BaseSlide:
|
|||||||
self._start_animation = 0
|
self._start_animation = 0
|
||||||
self._canvas: MutableMapping[str, Mobject] = {}
|
self._canvas: MutableMapping[str, Mobject] = {}
|
||||||
self._wait_time_between_slides = 0.0
|
self._wait_time_between_slides = 0.0
|
||||||
|
self._skip_animations = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@ -277,7 +280,7 @@ class BaseSlide:
|
|||||||
self._wait_time_between_slides = max(wait_time, 0.0)
|
self._wait_time_between_slides = max(wait_time, 0.0)
|
||||||
|
|
||||||
def play(self, *args: Any, **kwargs: Any) -> None:
|
def play(self, *args: Any, **kwargs: Any) -> None:
|
||||||
"""Overload `self.play` and increment animation count."""
|
"""Overload 'self.play' and increment animation count."""
|
||||||
super().play(*args, **kwargs) # type: ignore[misc]
|
super().play(*args, **kwargs) # type: ignore[misc]
|
||||||
self._current_animation += 1
|
self._current_animation += 1
|
||||||
|
|
||||||
@ -299,6 +302,16 @@ class BaseSlide:
|
|||||||
Positional arguments passed to
|
Positional arguments passed to
|
||||||
:meth:`Scene.next_section<manim.scene.scene.Scene.next_section>`,
|
:meth:`Scene.next_section<manim.scene.scene.Scene.next_section>`,
|
||||||
or ignored if `manimlib` API is used.
|
or ignored if `manimlib` API is used.
|
||||||
|
:param skip_animations:
|
||||||
|
Exclude the next slide from the output.
|
||||||
|
|
||||||
|
If `manim` is used, this is also passed to :meth:`Scene.next_section<manim.scene.scene.Scene.next_section>`,
|
||||||
|
which will avoid rendering the corresponding animations.
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
:meth:`start_skip_animations`
|
||||||
|
:meth:`stop_skip_animations`
|
||||||
:param loop:
|
:param loop:
|
||||||
If set, next slide will be looping.
|
If set, next slide will be looping.
|
||||||
:param auto_next:
|
:param auto_next:
|
||||||
@ -335,6 +348,11 @@ class BaseSlide:
|
|||||||
``manim-slides convert --to=pptx``.
|
``manim-slides convert --to=pptx``.
|
||||||
:param dedent_notes:
|
:param dedent_notes:
|
||||||
If set, apply :func:`textwrap.dedent` to notes.
|
If set, apply :func:`textwrap.dedent` to notes.
|
||||||
|
:param pathlib.Path src:
|
||||||
|
An optional path to a video file to include as next slide.
|
||||||
|
|
||||||
|
The video will be copied into the output folder, but no rescaling
|
||||||
|
is applied.
|
||||||
:param kwargs:
|
:param kwargs:
|
||||||
Keyword arguments passed to
|
Keyword arguments passed to
|
||||||
:meth:`Scene.next_section<manim.scene.scene.Scene.next_section>`,
|
:meth:`Scene.next_section<manim.scene.scene.Scene.next_section>`,
|
||||||
@ -458,6 +476,21 @@ class BaseSlide:
|
|||||||
|
|
||||||
self._current_slide += 1
|
self._current_slide += 1
|
||||||
|
|
||||||
|
if base_slide_config.src is not None:
|
||||||
|
self._slides.append(
|
||||||
|
PreSlideConfig.from_base_slide_config_and_animation_indices(
|
||||||
|
base_slide_config,
|
||||||
|
self._current_animation,
|
||||||
|
self._current_animation,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
base_slide_config = BaseSlideConfig() # default
|
||||||
|
self._current_slide += 1
|
||||||
|
|
||||||
|
if self._skip_animations:
|
||||||
|
base_slide_config.skip_animations = True
|
||||||
|
|
||||||
self._base_slide_config = base_slide_config
|
self._base_slide_config = base_slide_config
|
||||||
self._start_animation = self._current_animation
|
self._start_animation = self._current_animation
|
||||||
|
|
||||||
@ -477,7 +510,7 @@ class BaseSlide:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def _save_slides(
|
def _save_slides( # noqa: C901
|
||||||
self,
|
self,
|
||||||
use_cache: bool = True,
|
use_cache: bool = True,
|
||||||
flush_cache: bool = False,
|
flush_cache: bool = False,
|
||||||
@ -516,14 +549,25 @@ class BaseSlide:
|
|||||||
|
|
||||||
for pre_slide_config in tqdm(
|
for pre_slide_config in tqdm(
|
||||||
self._slides,
|
self._slides,
|
||||||
desc=f"Concatenating animation files to '{scene_files_folder}' and generating reversed animations",
|
desc=f"Concatenating animations to '{scene_files_folder}' and generating reversed animations",
|
||||||
leave=self._leave_progress_bar,
|
leave=self._leave_progress_bar,
|
||||||
ascii=True if platform.system() == "Windows" else None,
|
ascii=True if platform.system() == "Windows" else None,
|
||||||
disable=not self._show_progress_bar,
|
disable=not self._show_progress_bar,
|
||||||
|
unit=" slides",
|
||||||
):
|
):
|
||||||
slide_files = files[pre_slide_config.slides_slice]
|
if pre_slide_config.skip_animations:
|
||||||
|
continue
|
||||||
|
if pre_slide_config.src:
|
||||||
|
slide_files = [pre_slide_config.src]
|
||||||
|
else:
|
||||||
|
slide_files = files[pre_slide_config.slides_slice]
|
||||||
|
|
||||||
file = merge_basenames(slide_files)
|
try:
|
||||||
|
file = merge_basenames(slide_files)
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(
|
||||||
|
f"Failed to merge basenames of files for slide: {pre_slide_config!r}"
|
||||||
|
) from e
|
||||||
dst_file = scene_files_folder / file.name
|
dst_file = scene_files_folder / file.name
|
||||||
rev_file = scene_files_folder / f"{file.stem}_reversed{file.suffix}"
|
rev_file = scene_files_folder / f"{file.stem}_reversed{file.suffix}"
|
||||||
|
|
||||||
@ -536,7 +580,15 @@ class BaseSlide:
|
|||||||
if skip_reversing:
|
if skip_reversing:
|
||||||
rev_file = dst_file
|
rev_file = dst_file
|
||||||
else:
|
else:
|
||||||
reverse_video_file(dst_file, rev_file)
|
reverse_video_file(
|
||||||
|
dst_file,
|
||||||
|
rev_file,
|
||||||
|
max_segment_duration=self.max_duration_before_split_reverse,
|
||||||
|
num_processes=self.num_processes,
|
||||||
|
leave=self._leave_progress_bar,
|
||||||
|
ascii=True if platform.system() == "Windows" else None,
|
||||||
|
disable=not self._show_progress_bar,
|
||||||
|
)
|
||||||
|
|
||||||
slides.append(
|
slides.append(
|
||||||
SlideConfig.from_pre_slide_config_and_files(
|
SlideConfig.from_pre_slide_config_and_files(
|
||||||
@ -560,6 +612,22 @@ class BaseSlide:
|
|||||||
f"Slide '{scene_name}' configuration written in '{slide_path.absolute()}'"
|
f"Slide '{scene_name}' configuration written in '{slide_path.absolute()}'"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def start_skip_animations(self) -> None:
|
||||||
|
"""
|
||||||
|
Start skipping animations.
|
||||||
|
|
||||||
|
This automatically applies ``skip_animations=True``
|
||||||
|
to all subsequent calls to :meth:`next_slide`.
|
||||||
|
|
||||||
|
This is useful when you want to skip animations from multiple slides in a row,
|
||||||
|
without having to manually set ``skip_animations=True``.
|
||||||
|
"""
|
||||||
|
self._skip_animations = True
|
||||||
|
|
||||||
|
def stop_skip_animations(self) -> None:
|
||||||
|
"""Stop skipping animations."""
|
||||||
|
self._skip_animations = False
|
||||||
|
|
||||||
def wipe(
|
def wipe(
|
||||||
self,
|
self,
|
||||||
*args: Any,
|
*args: Any,
|
||||||
|
@ -11,26 +11,43 @@ from .base import BaseSlide
|
|||||||
|
|
||||||
class Slide(BaseSlide, Scene): # type: ignore[misc]
|
class Slide(BaseSlide, Scene): # type: ignore[misc]
|
||||||
"""
|
"""
|
||||||
Inherits from :class:`Scene<manim.scene.scene.Scene>` and provide necessary tools
|
Inherits from :class:`Scene<manim.scene.scene.Scene>` and provides necessary tools
|
||||||
for slides rendering.
|
for slides rendering.
|
||||||
|
|
||||||
:param args: Positional arguments passed to scene object.
|
:param args: Positional arguments passed to scene object.
|
||||||
:param output_folder: Where the slide animation files should be written.
|
:param pathlib.Path output_folder: Where the slide animation files should be written.
|
||||||
:param kwargs: Keyword arguments passed to scene object.
|
:param kwargs: Keyword arguments passed to scene object.
|
||||||
:cvar bool disable_caching: :data:`False`: Whether to disable the use of
|
:cvar bool disable_caching: :data:`False`: Whether to disable the use of
|
||||||
cached animation files.
|
cached animation files.
|
||||||
:cvar bool flush_cache: :data:`False`: Whether to flush the cache.
|
:cvar bool flush_cache: :data:`False`: Whether to flush the cache.
|
||||||
|
|
||||||
Unlike with Manim, flushing is performed before rendering.
|
Unlike with Manim, flushing is performed before rendering.
|
||||||
:cvar bool skip_reversing: :data:`False`: Whether to generate reversed animations.
|
:cvar bool skip_reversing: :data:`False`: Whether to generate reversed animations.
|
||||||
|
|
||||||
If set to :data:`False`, and no cached reversed animation
|
If set to :data:`False`, and no cached reversed animation
|
||||||
exists (or caching is disabled) for a given slide,
|
exists (or caching is disabled) for a given slide,
|
||||||
then the reversed animation will be simply the same
|
then the reversed animation will be simply the same
|
||||||
as the original one, i.e., ``rev_file = file``,
|
as the original one, i.e., ``rev_file = file``,
|
||||||
for the current slide config.
|
for the current slide config.
|
||||||
|
:cvar typing.Optional[float] max_duration_before_split_reverse: :data:`4.0`: Maximum duration
|
||||||
|
before of a video animation before it is reversed by splitting the file into smaller chunks.
|
||||||
|
Generating reversed animations can require an important amount of
|
||||||
|
memory (because the whole video needs to be kept in memory),
|
||||||
|
and splitting the video into multiple chunks usually speeds
|
||||||
|
up the process (because it can be done in parallel) while taking
|
||||||
|
less memory.
|
||||||
|
Set this to :data:`None` to disable splitting the file into chunks.
|
||||||
|
:cvar typing.Optional[int] num_processes: :data:`None`: Number of processes
|
||||||
|
to use for parallelizable operations.
|
||||||
|
If :data:`None`, defaults to :func:`os.process_cpu_count`.
|
||||||
|
This is currently used when generating reversed animations, and can
|
||||||
|
increase memory consumption.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
|
# OpenGL renderer disables 'write_to_movie' by default
|
||||||
|
# which is required for saving the animations
|
||||||
|
config["write_to_movie"] = True
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _frame_shape(self) -> tuple[float, float]:
|
def _frame_shape(self) -> tuple[float, float]:
|
||||||
if isinstance(self.renderer, OpenGLRenderer):
|
if isinstance(self.renderer, OpenGLRenderer):
|
||||||
@ -89,6 +106,15 @@ class Slide(BaseSlide, Scene): # type: ignore[misc]
|
|||||||
def _start_at_animation_number(self) -> Optional[int]:
|
def _start_at_animation_number(self) -> Optional[int]:
|
||||||
return config["from_animation_number"] # type: ignore
|
return config["from_animation_number"] # type: ignore
|
||||||
|
|
||||||
|
def play(self, *args: Any, **kwargs: Any) -> None:
|
||||||
|
"""Overload 'self.play' and increment animation count."""
|
||||||
|
super().play(*args, **kwargs)
|
||||||
|
|
||||||
|
if self._base_slide_config.skip_animations:
|
||||||
|
# Manim will not render the animations, so we reset the animation
|
||||||
|
# counter to the previous value
|
||||||
|
self._current_animation -= 1
|
||||||
|
|
||||||
def next_section(self, *args: Any, **kwargs: Any) -> None:
|
def next_section(self, *args: Any, **kwargs: Any) -> None:
|
||||||
"""
|
"""
|
||||||
Alias to :meth:`next_slide`.
|
Alias to :meth:`next_slide`.
|
||||||
@ -111,7 +137,12 @@ class Slide(BaseSlide, Scene): # type: ignore[misc]
|
|||||||
base_slide_config: BaseSlideConfig,
|
base_slide_config: BaseSlideConfig,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
Scene.next_section(self, *args, **kwargs)
|
Scene.next_section(
|
||||||
|
self,
|
||||||
|
*args,
|
||||||
|
skip_animations=base_slide_config.skip_animations | self._skip_animations,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
BaseSlide.next_slide.__wrapped__(
|
BaseSlide.next_slide.__wrapped__(
|
||||||
self,
|
self,
|
||||||
base_slide_config=base_slide_config,
|
base_slide_config=base_slide_config,
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
<!-- Theme used for syntax highlighting of code -->
|
<!-- Theme used for syntax highlighting of code -->
|
||||||
<!-- <link rel="stylesheet" href="lib/css/zenburn.css"> -->
|
<!-- <link rel="stylesheet" href="lib/css/zenburn.css"> -->
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/styles/zenburn.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/styles/zenburn.min.css">
|
||||||
|
|
||||||
<!-- <link rel="stylesheet" href="index.css"> -->
|
<!-- <link rel="stylesheet" href="index.css"> -->
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@ -20,82 +19,74 @@
|
|||||||
<div class="reveal">
|
<div class="reveal">
|
||||||
<div class="slides">
|
<div class="slides">
|
||||||
{% for presentation_config in presentation_configs -%}
|
{% for presentation_config in presentation_configs -%}
|
||||||
{% set outer_loop = loop %}
|
{%- set outer_loop = loop %}
|
||||||
{%- for slide_config in presentation_config.slides -%}
|
{% for slide_config in presentation_config.slides %}
|
||||||
{%- if one_file -%}
|
{% if one_file %}
|
||||||
{% set file = file_to_data_uri(slide_config.file) %}
|
{% set file = file_to_data_uri(slide_config.file) %}
|
||||||
{%- else -%}
|
{% else %}
|
||||||
{% set file = assets_dir / (prefix(outer_loop.index0) + slide_config.file.name) %}
|
{% set file = assets_dir / (prefix(outer_loop.index0) + slide_config.file.name) %}
|
||||||
{%- endif -%}
|
{% endif %}
|
||||||
<section
|
<section
|
||||||
data-background-size={{ background_size }}
|
data-background-size={{ background_size }}
|
||||||
data-background-color="{{ presentation_config.background_color }}"
|
data-background-color="{{ presentation_config.background_color }}"
|
||||||
data-background-video="{{ file }}"
|
data-background-video="{{ file }}"
|
||||||
{% if loop.index == 1 and outer_loop.index == 1 -%}
|
{% if loop.index == 1 and outer_loop.index == 1 %}
|
||||||
data-background-video-muted
|
data-background-video-muted
|
||||||
{%- endif -%}
|
{% endif %}
|
||||||
{% if slide_config.loop -%}
|
{% if slide_config.loop %}
|
||||||
data-background-video-loop
|
data-background-video-loop
|
||||||
{%- endif -%}
|
{% endif %}
|
||||||
{% if slide_config.auto_next -%}
|
{% if slide_config.auto_next %}
|
||||||
data-autoslide="{{ get_duration_ms(slide_config.file) }}"
|
data-autoslide="{{ get_duration_ms(slide_config.file) }}"
|
||||||
{%- endif %}
|
{% endif %}
|
||||||
>
|
>
|
||||||
{%- if slide_config.notes != "" -%}
|
{% if slide_config.notes != "" %}
|
||||||
<aside class="notes" data-markdown>{{ slide_config.notes }}</aside>
|
<aside class="notes" data-markdown>{{ slide_config.notes }}</aside>
|
||||||
{%- endif %}
|
{% endif %}
|
||||||
</section>
|
</section>
|
||||||
{%- endfor -%}
|
{% endfor %}
|
||||||
{%- endfor -%}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/{{ reveal_version }}/reveal.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/{{ reveal_version }}/reveal.min.js"></script>
|
||||||
|
|
||||||
<!-- To include plugins, see: https://revealjs.com/plugins/ -->
|
<!-- To include plugins, see: https://revealjs.com/plugins/ -->
|
||||||
|
{% if has_notes %}
|
||||||
{% if has_notes -%}
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/{{ reveal_version }}/plugin/markdown/markdown.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/{{ reveal_version }}/plugin/markdown/markdown.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/{{ reveal_version }}/plugin/notes/notes.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/{{ reveal_version }}/plugin/notes/notes.min.js"></script>
|
||||||
{%- endif -%}
|
{% endif %}
|
||||||
|
|
||||||
<!-- <script src="index.js"></script> -->
|
<!-- <script src="index.js"></script> -->
|
||||||
<script>
|
<script>
|
||||||
Reveal.initialize({
|
Reveal.initialize({
|
||||||
{% if has_notes -%}
|
{% if has_notes %}
|
||||||
plugins: [ RevealMarkdown, RevealNotes ],
|
/// The list of RevealJS plugins.
|
||||||
{%- endif %}
|
plugins: [ RevealMarkdown, RevealNotes ],
|
||||||
|
{% endif %}
|
||||||
// The "normal" size of the presentation, aspect ratio will
|
// The "normal" size of the presentation, aspect ratio will
|
||||||
// be preserved when the presentation is scaled to fit different
|
// be preserved when the presentation is scaled to fit different
|
||||||
// resolutions. Can be specified using percentage units.
|
// resolutions. Can be specified using percentage units.
|
||||||
width: {{ width }},
|
width: {{ width }},
|
||||||
height: {{ height }},
|
height: {{ height }},
|
||||||
|
|
||||||
// Factor of the display size that should remain empty around
|
// Factor of the display size that should remain empty around
|
||||||
// the content
|
// the content
|
||||||
margin: {{ margin }},
|
margin: {{ margin }},
|
||||||
|
|
||||||
// Bounds for smallest/largest possible scale to apply to content
|
// Bounds for smallest/largest possible scale to apply to content
|
||||||
minScale: {{ min_scale }},
|
minScale: {{ min_scale }},
|
||||||
maxScale: {{ max_scale }},
|
maxScale: {{ max_scale }},
|
||||||
|
|
||||||
// Display presentation control arrows
|
// Display presentation control arrows
|
||||||
controls: {{ controls }},
|
controls: {{ controls }},
|
||||||
|
|
||||||
// Help the user learn the controls by providing hints, for example by
|
// Help the user learn the controls by providing hints, for example by
|
||||||
// bouncing the down arrow when they first encounter a vertical slide
|
// bouncing the down arrow when they first encounter a vertical slide
|
||||||
controlsTutorial: {{ controls_tutorial }},
|
controlsTutorial: {{ controls_tutorial }},
|
||||||
|
|
||||||
// Determines where controls appear, "edges" or "bottom-right"
|
// Determines where controls appear, "edges" or "bottom-right"
|
||||||
controlsLayout: {{ controls_layout }},
|
controlsLayout: {{ controls_layout }},
|
||||||
|
|
||||||
// Visibility rule for backwards navigation arrows; "faded", "hidden"
|
// Visibility rule for backwards navigation arrows; "faded", "hidden"
|
||||||
// or "visible"
|
// or "visible"
|
||||||
controlsBackArrows: {{ controls_back_arrows }},
|
controlsBackArrows: {{ controls_back_arrows }},
|
||||||
|
|
||||||
// Display a presentation progress bar
|
// Display a presentation progress bar
|
||||||
progress: {{ progress }},
|
progress: {{ progress }},
|
||||||
|
|
||||||
// Display the page number of the current slide
|
// Display the page number of the current slide
|
||||||
// - true: Show slide number
|
// - true: Show slide number
|
||||||
// - false: Hide slide number
|
// - false: Hide slide number
|
||||||
@ -111,58 +102,43 @@
|
|||||||
// object and return an array with one string [slideNumber] or
|
// object and return an array with one string [slideNumber] or
|
||||||
// three strings [n1,delimiter,n2]. See #formatSlideNumber().
|
// three strings [n1,delimiter,n2]. See #formatSlideNumber().
|
||||||
slideNumber: {{ slide_number }},
|
slideNumber: {{ slide_number }},
|
||||||
|
|
||||||
// Can be used to limit the contexts in which the slide number appears
|
// Can be used to limit the contexts in which the slide number appears
|
||||||
// - "all": Always show the slide number
|
// - "all": Always show the slide number
|
||||||
// - "print": Only when printing to PDF
|
// - "print": Only when printing to PDF
|
||||||
// - "speaker": Only in the speaker view
|
// - "speaker": Only in the speaker view
|
||||||
showSlideNumber: {{ show_slide_number }},
|
showSlideNumber: {{ show_slide_number }},
|
||||||
|
|
||||||
// Use 1 based indexing for # links to match slide number (default is zero
|
// Use 1 based indexing for # links to match slide number (default is zero
|
||||||
// based)
|
// based)
|
||||||
hashOneBasedIndex: {{ hash_one_based_index }},
|
hashOneBasedIndex: {{ hash_one_based_index }},
|
||||||
|
|
||||||
// Add the current slide number to the URL hash so that reloading the
|
// Add the current slide number to the URL hash so that reloading the
|
||||||
// page/copying the URL will return you to the same slide
|
// page/copying the URL will return you to the same slide
|
||||||
hash: {{ hash }},
|
hash: {{ hash }},
|
||||||
|
|
||||||
// Flags if we should monitor the hash and change slides accordingly
|
// Flags if we should monitor the hash and change slides accordingly
|
||||||
respondToHashChanges: {{ respond_to_hash_changes }},
|
respondToHashChanges: {{ respond_to_hash_changes }},
|
||||||
|
|
||||||
// Enable support for jump-to-slide navigation shortcuts
|
// Enable support for jump-to-slide navigation shortcuts
|
||||||
jumpToSlide: {{ jump_to_slide }},
|
jumpToSlide: {{ jump_to_slide }},
|
||||||
|
|
||||||
// Push each slide change to the browser history. Implies `hash: true`
|
// Push each slide change to the browser history. Implies `hash: true`
|
||||||
history: {{ history }},
|
history: {{ history }},
|
||||||
|
|
||||||
// Enable keyboard shortcuts for navigation
|
// Enable keyboard shortcuts for navigation
|
||||||
keyboard: {{ keyboard }},
|
keyboard: {{ keyboard }},
|
||||||
|
|
||||||
// Optional function that blocks keyboard events when retuning false
|
// Optional function that blocks keyboard events when retuning false
|
||||||
//
|
//
|
||||||
// If you set this to 'focused', we will only capture keyboard events
|
// If you set this to 'focused', we will only capture keyboard events
|
||||||
// for embedded decks when they are in focus
|
// for embedded decks when they are in focus
|
||||||
keyboardCondition: {{ keyboard_condition }},
|
keyboardCondition: {{ keyboard_condition }},
|
||||||
|
|
||||||
// Disables the default reveal.js slide layout (scaling and centering)
|
// Disables the default reveal.js slide layout (scaling and centering)
|
||||||
// so that you can use custom CSS layout
|
// so that you can use custom CSS layout
|
||||||
disableLayout: {{ disable_layout }},
|
disableLayout: {{ disable_layout }},
|
||||||
|
|
||||||
// Enable the slide overview mode
|
// Enable the slide overview mode
|
||||||
overview: {{ overview }},
|
overview: {{ overview }},
|
||||||
|
|
||||||
// Vertical centering of slides
|
// Vertical centering of slides
|
||||||
center: {{ center }},
|
center: {{ center }},
|
||||||
|
|
||||||
// Enables touch navigation on devices with touch input
|
// Enables touch navigation on devices with touch input
|
||||||
touch: {{ touch }},
|
touch: {{ touch }},
|
||||||
|
|
||||||
// Loop the presentation
|
// Loop the presentation
|
||||||
loop: {{ loop }},
|
loop: {{ loop }},
|
||||||
|
|
||||||
// Change the presentation direction to be RTL
|
// Change the presentation direction to be RTL
|
||||||
rtl: {{ rtl }},
|
rtl: {{ rtl }},
|
||||||
|
|
||||||
// Changes the behavior of our navigation directions.
|
// Changes the behavior of our navigation directions.
|
||||||
//
|
//
|
||||||
// "default"
|
// "default"
|
||||||
@ -188,168 +164,154 @@
|
|||||||
// from 1.3 -> 2.1. If "grid" is used, the same navigation takes you
|
// from 1.3 -> 2.1. If "grid" is used, the same navigation takes you
|
||||||
// from 1.3 -> 2.3.
|
// from 1.3 -> 2.3.
|
||||||
navigationMode: {{ navigation_mode }},
|
navigationMode: {{ navigation_mode }},
|
||||||
|
|
||||||
// Randomizes the order of slides each time the presentation loads
|
// Randomizes the order of slides each time the presentation loads
|
||||||
shuffle: {{ shuffle }},
|
shuffle: {{ shuffle }},
|
||||||
|
|
||||||
// Turns fragments on and off globally
|
// Turns fragments on and off globally
|
||||||
fragments: {{ fragments }},
|
fragments: {{ fragments }},
|
||||||
|
|
||||||
// Flags whether to include the current fragment in the URL,
|
// Flags whether to include the current fragment in the URL,
|
||||||
// so that reloading brings you to the same fragment position
|
// so that reloading brings you to the same fragment position
|
||||||
fragmentInURL: {{ fragment_in_url }},
|
fragmentInURL: {{ fragment_in_url }},
|
||||||
|
|
||||||
// Flags if the presentation is running in an embedded mode,
|
// Flags if the presentation is running in an embedded mode,
|
||||||
// i.e. contained within a limited portion of the screen
|
// i.e. contained within a limited portion of the screen
|
||||||
embedded: {{ embedded }},
|
embedded: {{ embedded }},
|
||||||
|
|
||||||
// Flags if we should show a help overlay when the question-mark
|
// Flags if we should show a help overlay when the question-mark
|
||||||
// key is pressed
|
// key is pressed
|
||||||
help: {{ help }},
|
help: {{ help }},
|
||||||
|
|
||||||
// Flags if it should be possible to pause the presentation (blackout)
|
// Flags if it should be possible to pause the presentation (blackout)
|
||||||
pause: {{ pause }},
|
pause: {{ pause }},
|
||||||
|
|
||||||
// Flags if speaker notes should be visible to all viewers
|
// Flags if speaker notes should be visible to all viewers
|
||||||
showNotes: {{ show_notes }},
|
showNotes: {{ show_notes }},
|
||||||
|
|
||||||
// Global override for autolaying embedded media (video/audio/iframe)
|
// Global override for autolaying embedded media (video/audio/iframe)
|
||||||
// - null: Media will only autoplay if data-autoplay is present
|
// - null: Media will only autoplay if data-autoplay is present
|
||||||
// - true: All media will autoplay, regardless of individual setting
|
// - true: All media will autoplay, regardless of individual setting
|
||||||
// - false: No media will autoplay, regardless of individual setting
|
// - false: No media will autoplay, regardless of individual setting
|
||||||
autoPlayMedia: {{ auto_play_media }},
|
autoPlayMedia: {{ auto_play_media }},
|
||||||
|
|
||||||
// Global override for preloading lazy-loaded iframes
|
// Global override for preloading lazy-loaded iframes
|
||||||
// - null: Iframes with data-src AND data-preload will be loaded when within
|
// - null: Iframes with data-src AND data-preload will be loaded when within
|
||||||
// the viewDistance, iframes with only data-src will be loaded when visible
|
// the viewDistance, iframes with only data-src will be loaded when visible
|
||||||
// - true: All iframes with data-src will be loaded when within the viewDistance
|
// - true: All iframes with data-src will be loaded when within the viewDistance
|
||||||
// - false: All iframes with data-src will be loaded only when visible
|
// - false: All iframes with data-src will be loaded only when visible
|
||||||
preloadIframes: {{ preload_iframes }},
|
preloadIframes: {{ preload_iframes }},
|
||||||
|
|
||||||
// Can be used to globally disable auto-animation
|
// Can be used to globally disable auto-animation
|
||||||
autoAnimate: {{ auto_animate }},
|
autoAnimate: {{ auto_animate }},
|
||||||
|
|
||||||
// Optionally provide a custom element matcher that will be
|
// Optionally provide a custom element matcher that will be
|
||||||
// used to dictate which elements we can animate between.
|
// used to dictate which elements we can animate between.
|
||||||
autoAnimateMatcher: {{ auto_animate_matcher }},
|
autoAnimateMatcher: {{ auto_animate_matcher }},
|
||||||
|
|
||||||
// Default settings for our auto-animate transitions, can be
|
// Default settings for our auto-animate transitions, can be
|
||||||
// overridden per-slide or per-element via data arguments
|
// overridden per-slide or per-element via data arguments
|
||||||
autoAnimateEasing: {{ auto_animate_easing }},
|
autoAnimateEasing: {{ auto_animate_easing }},
|
||||||
autoAnimateDuration: {{ auto_animate_duration }},
|
autoAnimateDuration: {{ auto_animate_duration }},
|
||||||
autoAnimateUnmatched: {{ auto_animate_unmatched }},
|
autoAnimateUnmatched: {{ auto_animate_unmatched }},
|
||||||
|
|
||||||
// CSS properties that can be auto-animated. Position & scale
|
// CSS properties that can be auto-animated. Position & scale
|
||||||
// is matched separately so there's no need to include styles
|
// is matched separately so there's no need to include styles
|
||||||
// like top/right/bottom/left, width/height or margin.
|
// like top/right/bottom/left, width/height or margin.
|
||||||
autoAnimateStyles: {{ auto_animate_styles }},
|
autoAnimateStyles: {{ auto_animate_styles }},
|
||||||
|
|
||||||
// Controls automatic progression to the next slide
|
// Controls automatic progression to the next slide
|
||||||
// - 0: Auto-sliding only happens if the data-autoslide HTML attribute
|
// - 0: Auto-sliding only happens if the data-autoslide HTML attribute
|
||||||
// is present on the current slide or fragment
|
// is present on the current slide or fragment
|
||||||
// - 1+: All slides will progress automatically at the given interval
|
// - 1+: All slides will progress automatically at the given interval
|
||||||
// - false: No auto-sliding, even if data-autoslide is present
|
// - false: No auto-sliding, even if data-autoslide is present
|
||||||
autoSlide: {{ auto_slide }},
|
autoSlide: {{ auto_slide }},
|
||||||
|
|
||||||
// Stop auto-sliding after user input
|
// Stop auto-sliding after user input
|
||||||
autoSlideStoppable: {{ auto_slide_stoppable }},
|
autoSlideStoppable: {{ auto_slide_stoppable }},
|
||||||
|
|
||||||
// Use this method for navigation when auto-sliding (defaults to navigateNext)
|
// Use this method for navigation when auto-sliding (defaults to navigateNext)
|
||||||
autoSlideMethod: {{ auto_slide_method }},
|
autoSlideMethod: {{ auto_slide_method }},
|
||||||
|
|
||||||
// Specify the average time in seconds that you think you will spend
|
// Specify the average time in seconds that you think you will spend
|
||||||
// presenting each slide. This is used to show a pacing timer in the
|
// presenting each slide. This is used to show a pacing timer in the
|
||||||
// speaker view
|
// speaker view
|
||||||
defaultTiming: {{ default_timing }},
|
defaultTiming: {{ default_timing }},
|
||||||
|
|
||||||
// Enable slide navigation via mouse wheel
|
// Enable slide navigation via mouse wheel
|
||||||
mouseWheel: {{ mouse_wheel }},
|
mouseWheel: {{ mouse_wheel }},
|
||||||
|
|
||||||
// Opens links in an iframe preview overlay
|
// Opens links in an iframe preview overlay
|
||||||
// Add `data-preview-link` and `data-preview-link="false"` to customize each link
|
// Add `data-preview-link` and `data-preview-link="false"` to customize each link
|
||||||
// individually
|
// individually
|
||||||
previewLinks: {{ preview_links }},
|
previewLinks: {{ preview_links }},
|
||||||
|
|
||||||
// Exposes the reveal.js API through window.postMessage
|
// Exposes the reveal.js API through window.postMessage
|
||||||
postMessage: {{ post_message }},
|
postMessage: {{ post_message }},
|
||||||
|
|
||||||
// Dispatches all reveal.js events to the parent window through postMessage
|
// Dispatches all reveal.js events to the parent window through postMessage
|
||||||
postMessageEvents: {{ post_message_events }},
|
postMessageEvents: {{ post_message_events }},
|
||||||
|
|
||||||
// Focuses body when page changes visibility to ensure keyboard shortcuts work
|
// Focuses body when page changes visibility to ensure keyboard shortcuts work
|
||||||
focusBodyOnPageVisibilityChange: {{ focus_body_on_page_visibility_change }},
|
focusBodyOnPageVisibilityChange: {{ focus_body_on_page_visibility_change }},
|
||||||
|
|
||||||
// Transition style
|
// Transition style
|
||||||
transition: {{ transition }}, // none/fade/slide/convex/concave/zoom
|
transition: {{ transition }}, // none/fade/slide/convex/concave/zoom
|
||||||
|
|
||||||
// Transition speed
|
// Transition speed
|
||||||
transitionSpeed: {{ transition_speed }}, // default/fast/slow
|
transitionSpeed: {{ transition_speed }}, // default/fast/slow
|
||||||
|
|
||||||
// Transition style for full page slide backgrounds
|
// Transition style for full page slide backgrounds
|
||||||
backgroundTransition: {{ background_transition }}, // none/fade/slide/convex/concave/zoom
|
backgroundTransition: {{ background_transition }}, // none/fade/slide/convex/concave/zoom
|
||||||
|
|
||||||
// The maximum number of pages a single slide can expand onto when printing
|
// The maximum number of pages a single slide can expand onto when printing
|
||||||
// to PDF, unlimited by default
|
// to PDF, unlimited by default
|
||||||
pdfMaxPagesPerSlide: {{ pdf_max_pages_per_slide }},
|
pdfMaxPagesPerSlide: {{ pdf_max_pages_per_slide }},
|
||||||
|
|
||||||
// Prints each fragment on a separate slide
|
// Prints each fragment on a separate slide
|
||||||
pdfSeparateFragments: {{ pdf_separate_fragments }},
|
pdfSeparateFragments: {{ pdf_separate_fragments }},
|
||||||
|
|
||||||
// Offset used to reduce the height of content within exported PDF pages.
|
// Offset used to reduce the height of content within exported PDF pages.
|
||||||
// This exists to account for environment differences based on how you
|
// This exists to account for environment differences based on how you
|
||||||
// print to PDF. CLI printing options, like phantomjs and wkpdf, can end
|
// print to PDF. CLI printing options, like phantomjs and wkpdf, can end
|
||||||
// on precisely the total height of the document whereas in-browser
|
// on precisely the total height of the document whereas in-browser
|
||||||
// printing has to end one pixel before.
|
// printing has to end one pixel before.
|
||||||
pdfPageHeightOffset: {{ pdf_page_height_offset }},
|
pdfPageHeightOffset: {{ pdf_page_height_offset }},
|
||||||
|
|
||||||
// Number of slides away from the current that are visible
|
// Number of slides away from the current that are visible
|
||||||
viewDistance: {{ view_distance }},
|
viewDistance: {{ view_distance }},
|
||||||
|
|
||||||
// Number of slides away from the current that are visible on mobile
|
// Number of slides away from the current that are visible on mobile
|
||||||
// devices. It is advisable to set this to a lower number than
|
// devices. It is advisable to set this to a lower number than
|
||||||
// viewDistance in order to save resources.
|
// viewDistance in order to save resources.
|
||||||
mobileViewDistance: {{ mobile_view_distance }},
|
mobileViewDistance: {{ mobile_view_distance }},
|
||||||
|
|
||||||
// The display mode that will be used to show slides
|
// The display mode that will be used to show slides
|
||||||
display: {{ display }},
|
display: {{ display }},
|
||||||
|
|
||||||
// Hide cursor if inactive
|
// Hide cursor if inactive
|
||||||
hideInactiveCursor: {{ hide_inactive_cursor }},
|
hideInactiveCursor: {{ hide_inactive_cursor }},
|
||||||
|
|
||||||
// Time before the cursor is hidden (in ms)
|
// Time before the cursor is hidden (in ms)
|
||||||
hideCursorTime: {{ hide_cursor_time }}
|
hideCursorTime: {{ hide_cursor_time }}
|
||||||
});
|
});
|
||||||
|
{% if one_file %}
|
||||||
{% if one_file -%}
|
|
||||||
// Fix found by @t-fritsch and @Rapsssito on GitHub
|
// Fix found by @t-fritsch and @Rapsssito on GitHub
|
||||||
// see: https://github.com/hakimel/reveal.js/discussions/3362#discussioncomment-11733074.
|
// see: https://github.com/hakimel/reveal.js/discussions/3362#discussioncomment-11733074.
|
||||||
|
function setVideoBase64(video) {
|
||||||
|
const sources = video.querySelectorAll('source');
|
||||||
|
// Update the source of the video
|
||||||
|
sources.forEach((source, i) => {
|
||||||
|
const src = source.getAttribute('src');
|
||||||
|
if(src.match(/^data:video.*;base64$/)) {
|
||||||
|
const nextSrc = sources[i+1]?.getAttribute('src');
|
||||||
|
video.setAttribute('src', `${src},${nextSrc}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function fixBase64VideoBackground(event) {
|
function fixBase64VideoBackground(event) {
|
||||||
// Analyze all slides backgrounds
|
// Analyze all slides backgrounds
|
||||||
for (const slide of Reveal.getBackgroundsElement().querySelectorAll('.slide-background')) {
|
for (const slide of Reveal.getBackgroundsElement().querySelectorAll('.slide-background')) {
|
||||||
// Get the slide video and its sources for each background
|
// Get the slide video and its sources for each background
|
||||||
const video = slide.querySelector('video');
|
const video = slide.querySelector('video');
|
||||||
const sources = video.querySelectorAll('source');
|
if (video) {
|
||||||
// Update the source of the video
|
setVideoBase64(video);
|
||||||
sources.forEach((source, i) => {
|
} else {
|
||||||
const src = source.getAttribute('src');
|
// Listen to the creation of the video element
|
||||||
if(src.match(/^data:video.*;base64$/)) {
|
const observer = new MutationObserver((mutationsList) => {
|
||||||
const nextSrc = sources[i+1]?.getAttribute('src');
|
for (const mutation of mutationsList) {
|
||||||
video.setAttribute('src', `${src},${nextSrc}`);
|
if (mutation.type === 'childList') {
|
||||||
}
|
for (const addedNode of mutation.addedNodes) {
|
||||||
});
|
if (addedNode.tagName === 'VIDEO') {
|
||||||
|
setVideoBase64(addedNode);
|
||||||
|
observer.disconnect(); // Stop observing once the video is handled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
observer.observe(slide, { childList: true, subtree: true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Setup base64 videos
|
// Setup base64 videos
|
||||||
Reveal.on( 'ready', fixBase64VideoBackground );
|
Reveal.on( 'ready', fixBase64VideoBackground );
|
||||||
{%- endif %}
|
{% endif %}
|
||||||
</script>
|
</script>
|
||||||
|
{% if env['READTHEDOCS'] %}
|
||||||
{% if env['READTHEDOCS'] -%}
|
<style>
|
||||||
<style>
|
readthedocs-flyout, readthedocs-notification {
|
||||||
readthedocs-flyout, readthedocs-notification {
|
display: none;
|
||||||
display: none;
|
}
|
||||||
}
|
</style>
|
||||||
</style>
|
{% endif %}
|
||||||
{%- endif %}
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,16 +1,23 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
|
from multiprocessing import Pool
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
import av
|
import av
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
from .logger import logger
|
from .logger import logger
|
||||||
|
|
||||||
|
|
||||||
def concatenate_video_files(files: list[Path], dest: Path) -> None:
|
def concatenate_video_files(files: list[Path], dest: Path) -> None:
|
||||||
"""Concatenate multiple video files into one."""
|
"""Concatenate multiple video files into one."""
|
||||||
|
if len(files) == 1:
|
||||||
|
shutil.copy(files[0], dest)
|
||||||
|
return
|
||||||
|
|
||||||
def _filter(files: list[Path]) -> Iterator[Path]:
|
def _filter(files: list[Path]) -> Iterator[Path]:
|
||||||
"""Patch possibly empty video files."""
|
"""Patch possibly empty video files."""
|
||||||
@ -89,8 +96,9 @@ def link_nodes(*nodes: av.filter.context.FilterContext) -> None:
|
|||||||
c.link_to(n)
|
c.link_to(n)
|
||||||
|
|
||||||
|
|
||||||
def reverse_video_file(src: Path, dest: Path) -> None:
|
def reverse_video_file_in_one_chunk(src_and_dest: tuple[Path, Path]) -> None:
|
||||||
"""Reverses a video file, writing the result to `dest`."""
|
"""Reverses a video file, writing the result to `dest`."""
|
||||||
|
src, dest = src_and_dest
|
||||||
with (
|
with (
|
||||||
av.open(str(src)) as input_container,
|
av.open(str(src)) as input_container,
|
||||||
av.open(str(dest), mode="w") as output_container,
|
av.open(str(dest), mode="w") as output_container,
|
||||||
@ -120,8 +128,70 @@ def reverse_video_file(src: Path, dest: Path) -> None:
|
|||||||
|
|
||||||
for _ in range(frames_count):
|
for _ in range(frames_count):
|
||||||
frame = graph.pull()
|
frame = graph.pull()
|
||||||
frame.pict_type = 5 # Otherwise we get a warning saying it is changed
|
frame.pict_type = "NONE" # Otherwise we get a warning saying it is changed
|
||||||
output_container.mux(output_stream.encode(frame))
|
output_container.mux(output_stream.encode(frame))
|
||||||
|
|
||||||
for packet in output_stream.encode():
|
for packet in output_stream.encode():
|
||||||
output_container.mux(packet)
|
output_container.mux(packet)
|
||||||
|
|
||||||
|
|
||||||
|
def reverse_video_file(
|
||||||
|
src: Path,
|
||||||
|
dest: Path,
|
||||||
|
max_segment_duration: Optional[float] = 4.0,
|
||||||
|
num_processes: Optional[int] = None,
|
||||||
|
**tqdm_kwargs: Any,
|
||||||
|
) -> None:
|
||||||
|
"""Reverses a video file, writing the result to `dest`."""
|
||||||
|
with av.open(str(src)) as input_container: # Fast path if file is short enough
|
||||||
|
input_stream = input_container.streams.video[0]
|
||||||
|
if max_segment_duration is None:
|
||||||
|
return reverse_video_file_in_one_chunk((src, dest))
|
||||||
|
elif input_stream.duration:
|
||||||
|
if (
|
||||||
|
float(input_stream.duration * input_stream.time_base)
|
||||||
|
<= max_segment_duration
|
||||||
|
):
|
||||||
|
return reverse_video_file_in_one_chunk((src, dest))
|
||||||
|
else: # pragma: no cover
|
||||||
|
logger.debug(
|
||||||
|
f"Could not determine duration of {src}, falling back to segmentation."
|
||||||
|
)
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdirname:
|
||||||
|
tmpdir = Path(tmpdirname)
|
||||||
|
with av.open(
|
||||||
|
str(tmpdir / f"%04d.{src.suffix}"),
|
||||||
|
"w",
|
||||||
|
format="segment",
|
||||||
|
options={"segment_time": str(max_segment_duration)},
|
||||||
|
) as output_container:
|
||||||
|
output_stream = output_container.add_stream(
|
||||||
|
template=input_stream,
|
||||||
|
)
|
||||||
|
|
||||||
|
for packet in input_container.demux(input_stream):
|
||||||
|
if packet.dts is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
packet.stream = output_stream
|
||||||
|
output_container.mux(packet)
|
||||||
|
|
||||||
|
src_files = list(tmpdir.iterdir())
|
||||||
|
rev_files = [
|
||||||
|
src_file.with_stem("rev_" + src_file.stem) for src_file in src_files
|
||||||
|
]
|
||||||
|
|
||||||
|
with Pool(num_processes, maxtasksperchild=1) as pool:
|
||||||
|
for _ in tqdm(
|
||||||
|
pool.imap_unordered(
|
||||||
|
reverse_video_file_in_one_chunk, zip(src_files, rev_files)
|
||||||
|
),
|
||||||
|
desc="Reversing large file by cutting it in segments",
|
||||||
|
total=len(src_files),
|
||||||
|
unit=" files",
|
||||||
|
**tqdm_kwargs,
|
||||||
|
):
|
||||||
|
pass # We just consume the iterator
|
||||||
|
|
||||||
|
concatenate_video_files(rev_files[::-1], dest)
|
||||||
|
@ -49,6 +49,7 @@ docs = [
|
|||||||
"myst-parser>=2.0.0",
|
"myst-parser>=2.0.0",
|
||||||
"nbsphinx>=0.9.2",
|
"nbsphinx>=0.9.2",
|
||||||
"pandoc>=2.3",
|
"pandoc>=2.3",
|
||||||
|
"pygments<2.19", # See: https://github.com/ManimCommunity/manim/issues/4104
|
||||||
"sphinx>=7.0.1",
|
"sphinx>=7.0.1",
|
||||||
"sphinxcontrib-programoutput>=0.18",
|
"sphinxcontrib-programoutput>=0.18",
|
||||||
"sphinx-design>=0.6.1",
|
"sphinx-design>=0.6.1",
|
||||||
@ -60,7 +61,7 @@ full = [
|
|||||||
"manim-slides[magic,manim,sphinx-directive]",
|
"manim-slides[magic,manim,sphinx-directive]",
|
||||||
]
|
]
|
||||||
magic = ["manim-slides[manim]", "ipython>=8.12.2"]
|
magic = ["manim-slides[manim]", "ipython>=8.12.2"]
|
||||||
manim = ["manim>=0.18.0"]
|
manim = ["manim>=0.19"]
|
||||||
manimgl = ["manimgl>=1.7.2"]
|
manimgl = ["manimgl>=1.7.2"]
|
||||||
pyqt6 = ["pyqt6>=6.7.0"]
|
pyqt6 = ["pyqt6>=6.7.0"]
|
||||||
pyqt6-full = ["manim-slides[full,pyqt6]"]
|
pyqt6-full = ["manim-slides[full,pyqt6]"]
|
||||||
@ -68,6 +69,7 @@ pyside6 = ["pyside6>=6.6.1,!=6.8.1.1"]
|
|||||||
pyside6-full = ["manim-slides[full,pyside6]"]
|
pyside6-full = ["manim-slides[full,pyside6]"]
|
||||||
sphinx-directive = ["docutils>=0.20.1", "manim-slides[manim]"]
|
sphinx-directive = ["docutils>=0.20.1", "manim-slides[manim]"]
|
||||||
tests = [
|
tests = [
|
||||||
|
"importlib-metadata>=8.6.1;python_version<'3.10'",
|
||||||
"manim-slides[full,manimgl,pyqt6,pyside6,sphinx-directive]",
|
"manim-slides[full,manimgl,pyqt6,pyside6,sphinx-directive]",
|
||||||
"pytest>=7.4.0",
|
"pytest>=7.4.0",
|
||||||
"pytest-cov>=4.1.0",
|
"pytest-cov>=4.1.0",
|
||||||
@ -90,7 +92,7 @@ Repository = "https://github.com/jeertmans/manim-slides"
|
|||||||
allow_dirty = false
|
allow_dirty = false
|
||||||
commit = true
|
commit = true
|
||||||
commit_args = ""
|
commit_args = ""
|
||||||
current_version = "5.3.0"
|
current_version = "5.5.0"
|
||||||
ignore_missing_version = false
|
ignore_missing_version = false
|
||||||
message = "chore(deps): bump version from {current_version} to {new_version}"
|
message = "chore(deps): bump version from {current_version} to {new_version}"
|
||||||
parse = '(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(-rc(?P<release>\d+))?'
|
parse = '(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(-rc(?P<release>\d+))?'
|
||||||
|
@ -86,6 +86,15 @@ class TestBaseSlide:
|
|||||||
|
|
||||||
assert base_slide.wait_time_between_slides == 0.0
|
assert base_slide.wait_time_between_slides == 0.0
|
||||||
|
|
||||||
|
def test_skip_animations(self, base_slide: BaseSlide) -> None:
|
||||||
|
assert not base_slide._skip_animations
|
||||||
|
|
||||||
|
def test_start_and_stop_skip_animations(self, base_slide: BaseSlide) -> None:
|
||||||
|
base_slide.start_skip_animations()
|
||||||
|
assert base_slide._skip_animations
|
||||||
|
base_slide.stop_skip_animations()
|
||||||
|
assert not base_slide._skip_animations
|
||||||
|
|
||||||
def test_play(self) -> None:
|
def test_play(self) -> None:
|
||||||
pass # This method should be tested in test_slide.py
|
pass # This method should be tested in test_slide.py
|
||||||
|
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
|
import contextlib
|
||||||
|
import os
|
||||||
import random
|
import random
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from collections.abc import Iterator
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Union
|
from typing import Any, Union
|
||||||
|
|
||||||
@ -17,6 +21,7 @@ from manim import (
|
|||||||
Dot,
|
Dot,
|
||||||
FadeIn,
|
FadeIn,
|
||||||
GrowFromCenter,
|
GrowFromCenter,
|
||||||
|
Square,
|
||||||
Text,
|
Text,
|
||||||
)
|
)
|
||||||
from manim.renderer.opengl_renderer import OpenGLRenderer
|
from manim.renderer.opengl_renderer import OpenGLRenderer
|
||||||
@ -65,7 +70,9 @@ Slide = Union[CESlide, _GLSlide, CEGLSlide]
|
|||||||
reason="See https://github.com/3b1b/manim/issues/2263.",
|
reason="See https://github.com/3b1b/manim/issues/2263.",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
"--CE --renderer=opengl",
|
||||||
],
|
],
|
||||||
|
ids=("CE", "GL", "CE(GL)"),
|
||||||
)
|
)
|
||||||
def test_render_basic_slide(
|
def test_render_basic_slide(
|
||||||
renderer: str,
|
renderer: str,
|
||||||
@ -78,7 +85,7 @@ def test_render_basic_slide(
|
|||||||
with runner.isolated_filesystem() as tmp_dir:
|
with runner.isolated_filesystem() as tmp_dir:
|
||||||
shutil.copy(manimgl_config, tmp_dir)
|
shutil.copy(manimgl_config, tmp_dir)
|
||||||
results = runner.invoke(
|
results = runner.invoke(
|
||||||
render, [renderer, str(slides_file), "BasicSlide", "-ql"]
|
render, [*renderer.split(" "), str(slides_file), "BasicSlide", "-ql"]
|
||||||
)
|
)
|
||||||
|
|
||||||
assert results.exit_code == 0, results
|
assert results.exit_code == 0, results
|
||||||
@ -229,8 +236,22 @@ def assert_constructs(cls: SlideType) -> None:
|
|||||||
init_slide(cls).construct()
|
init_slide(cls).construct()
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def tmp_cwd() -> Iterator[str]:
|
||||||
|
cwd = os.getcwd()
|
||||||
|
tmp_dir = tempfile.mkdtemp()
|
||||||
|
|
||||||
|
os.chdir(tmp_dir)
|
||||||
|
|
||||||
|
try:
|
||||||
|
yield tmp_dir
|
||||||
|
finally:
|
||||||
|
os.chdir(cwd)
|
||||||
|
|
||||||
|
|
||||||
def assert_renders(cls: SlideType) -> None:
|
def assert_renders(cls: SlideType) -> None:
|
||||||
init_slide(cls).render()
|
with tmp_cwd():
|
||||||
|
init_slide(cls).render()
|
||||||
|
|
||||||
|
|
||||||
class TestSlide:
|
class TestSlide:
|
||||||
@ -293,6 +314,26 @@ class TestSlide:
|
|||||||
self.play(dot.animate.move_to(LEFT))
|
self.play(dot.animate.move_to(LEFT))
|
||||||
self.play(dot.animate.move_to(DOWN))
|
self.play(dot.animate.move_to(DOWN))
|
||||||
|
|
||||||
|
def test_split_reverse(self) -> None:
|
||||||
|
@assert_renders
|
||||||
|
class _(CESlide):
|
||||||
|
max_duration_before_split_reverse = 3.0
|
||||||
|
|
||||||
|
def construct(self) -> None:
|
||||||
|
self.wait(2.0)
|
||||||
|
for _ in range(3):
|
||||||
|
self.next_slide()
|
||||||
|
self.wait(10.0)
|
||||||
|
|
||||||
|
@assert_renders
|
||||||
|
class __(CESlide):
|
||||||
|
max_duration_before_split_reverse = None
|
||||||
|
|
||||||
|
def construct(self) -> None:
|
||||||
|
self.wait(5.0)
|
||||||
|
self.next_slide()
|
||||||
|
self.wait(5.0)
|
||||||
|
|
||||||
def test_file_too_long(self) -> None:
|
def test_file_too_long(self) -> None:
|
||||||
@assert_renders
|
@assert_renders
|
||||||
class _(CESlide):
|
class _(CESlide):
|
||||||
@ -479,6 +520,120 @@ class TestSlide:
|
|||||||
self.next_slide()
|
self.next_slide()
|
||||||
assert self._current_slide == 2
|
assert self._current_slide == 2
|
||||||
|
|
||||||
|
def test_next_slide_skip_animations(self) -> None:
|
||||||
|
class Foo(CESlide):
|
||||||
|
def construct(self) -> None:
|
||||||
|
circle = Circle(color=BLUE)
|
||||||
|
self.play(GrowFromCenter(circle))
|
||||||
|
assert not self._base_slide_config.skip_animations
|
||||||
|
self.next_slide(skip_animations=True)
|
||||||
|
square = Square(color=BLUE)
|
||||||
|
self.play(GrowFromCenter(square))
|
||||||
|
assert self._base_slide_config.skip_animations
|
||||||
|
self.next_slide()
|
||||||
|
assert not self._base_slide_config.skip_animations
|
||||||
|
self.play(GrowFromCenter(square))
|
||||||
|
|
||||||
|
class Bar(CESlide):
|
||||||
|
def construct(self) -> None:
|
||||||
|
circle = Circle(color=BLUE)
|
||||||
|
self.play(GrowFromCenter(circle))
|
||||||
|
assert not self._base_slide_config.skip_animations
|
||||||
|
self.next_slide(skip_animations=False)
|
||||||
|
square = Square(color=BLUE)
|
||||||
|
self.play(GrowFromCenter(square))
|
||||||
|
assert not self._base_slide_config.skip_animations
|
||||||
|
self.next_slide()
|
||||||
|
assert not self._base_slide_config.skip_animations
|
||||||
|
self.play(GrowFromCenter(square))
|
||||||
|
|
||||||
|
class Baz(CESlide):
|
||||||
|
def construct(self) -> None:
|
||||||
|
circle = Circle(color=BLUE)
|
||||||
|
self.play(GrowFromCenter(circle))
|
||||||
|
assert not self._base_slide_config.skip_animations
|
||||||
|
self.start_skip_animations()
|
||||||
|
self.next_slide()
|
||||||
|
square = Square(color=BLUE)
|
||||||
|
self.play(GrowFromCenter(square))
|
||||||
|
assert self._base_slide_config.skip_animations
|
||||||
|
self.next_slide()
|
||||||
|
assert self._base_slide_config.skip_animations
|
||||||
|
self.play(GrowFromCenter(square))
|
||||||
|
self.stop_skip_animations()
|
||||||
|
|
||||||
|
with tmp_cwd() as tmp_dir:
|
||||||
|
init_slide(Foo).render()
|
||||||
|
init_slide(Bar).render()
|
||||||
|
init_slide(Baz).render()
|
||||||
|
|
||||||
|
slides_folder = Path(tmp_dir) / "slides"
|
||||||
|
|
||||||
|
assert slides_folder.exists()
|
||||||
|
|
||||||
|
slide_file = slides_folder / "Foo.json"
|
||||||
|
|
||||||
|
config = PresentationConfig.from_file(slide_file)
|
||||||
|
|
||||||
|
assert len(config.slides) == 2
|
||||||
|
|
||||||
|
slide_file = slides_folder / "Bar.json"
|
||||||
|
|
||||||
|
config = PresentationConfig.from_file(slide_file)
|
||||||
|
|
||||||
|
assert len(config.slides) == 3
|
||||||
|
|
||||||
|
slide_file = slides_folder / "Baz.json"
|
||||||
|
|
||||||
|
config = PresentationConfig.from_file(slide_file)
|
||||||
|
|
||||||
|
assert len(config.slides) == 1
|
||||||
|
|
||||||
|
def test_next_slide_include_video(self) -> None:
|
||||||
|
class Foo(CESlide):
|
||||||
|
def construct(self) -> None:
|
||||||
|
circle = Circle(color=BLUE)
|
||||||
|
self.play(GrowFromCenter(circle))
|
||||||
|
self.next_slide()
|
||||||
|
square = Square(color=BLUE)
|
||||||
|
self.play(GrowFromCenter(square))
|
||||||
|
self.next_slide()
|
||||||
|
self.wait(2)
|
||||||
|
|
||||||
|
with tmp_cwd() as tmp_dir:
|
||||||
|
init_slide(Foo).render()
|
||||||
|
|
||||||
|
slides_folder = Path(tmp_dir) / "slides"
|
||||||
|
|
||||||
|
assert slides_folder.exists()
|
||||||
|
|
||||||
|
slide_file = slides_folder / "Foo.json"
|
||||||
|
|
||||||
|
config = PresentationConfig.from_file(slide_file)
|
||||||
|
|
||||||
|
assert len(config.slides) == 3
|
||||||
|
|
||||||
|
class Bar(CESlide):
|
||||||
|
def construct(self) -> None:
|
||||||
|
self.next_slide(src=config.slides[0].file)
|
||||||
|
self.wait(2)
|
||||||
|
self.next_slide()
|
||||||
|
self.wait(2)
|
||||||
|
self.next_slide() # Dummy
|
||||||
|
self.next_slide(src=config.slides[1].file, loop=True)
|
||||||
|
self.next_slide() # Dummy
|
||||||
|
self.wait(2)
|
||||||
|
self.next_slide(src=config.slides[2].file)
|
||||||
|
|
||||||
|
init_slide(Bar).render()
|
||||||
|
|
||||||
|
slide_file = slides_folder / "Bar.json"
|
||||||
|
|
||||||
|
config = PresentationConfig.from_file(slide_file)
|
||||||
|
|
||||||
|
assert len(config.slides) == 6
|
||||||
|
assert config.slides[-3].loop
|
||||||
|
|
||||||
def test_canvas(self) -> None:
|
def test_canvas(self) -> None:
|
||||||
@assert_constructs
|
@assert_constructs
|
||||||
class _(CESlide):
|
class _(CESlide):
|
||||||
|
Reference in New Issue
Block a user