mirror of
https://github.com/jeertmans/manim-slides.git
synced 2025-05-18 03:05:21 +08:00
Compare commits
27 Commits
v5.4.2
...
reorganize
Author | SHA1 | Date | |
---|---|---|---|
880cba3064 | |||
fbf5f42f31 | |||
0c6cd67038 | |||
a5412a8df2 | |||
e9480c9bc7 | |||
3e0268a431 | |||
6e14dc9051 | |||
4a400398b8 | |||
d641d2d82c | |||
d3396d3a01 | |||
7a922db6f1 | |||
9b9593985d | |||
c915af19e8 | |||
a8897552d8 | |||
b02dbbcc8c | |||
c6ba210797 | |||
b1212a49d3 | |||
f7ce5fc115 | |||
5f9bbf2a79 | |||
3c8384a908 | |||
4bf4d38fd8 | |||
ccbe9d558c | |||
a2bd1ffb67 | |||
e911ec3096 | |||
daf547414c | |||
f02ef630cf | |||
ae8d5b6aab |
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@ -96,7 +96,7 @@ jobs:
|
||||
uses: ssciwr/setup-mesa-dist-win@v2
|
||||
|
||||
- name: Run pytest
|
||||
run: uv run --python ${{ matrix.pyversion }} --frozen --extra tests pytest
|
||||
run: uv run --python ${{ matrix.pyversion }} --frozen --group tests --no-dev pytest
|
||||
|
||||
- name: Upload to codecov.io
|
||||
uses: codecov/codecov-action@v5
|
||||
|
@ -21,18 +21,18 @@ repos:
|
||||
exclude: poetry.lock
|
||||
args: [--autofix, --trailing-commas]
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.9.2
|
||||
rev: v0.11.5
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix]
|
||||
- id: ruff-format
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v1.14.1
|
||||
rev: v1.15.0
|
||||
hooks:
|
||||
- id: mypy
|
||||
additional_dependencies: [types-requests, types-setuptools]
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.3.0
|
||||
rev: v2.4.1
|
||||
hooks:
|
||||
- id: codespell
|
||||
additional_dependencies:
|
||||
|
@ -6,13 +6,13 @@ build:
|
||||
apt_packages:
|
||||
- libpango1.0-dev
|
||||
- ffmpeg
|
||||
jobs:
|
||||
post_create_environment:
|
||||
- asdf plugin add uv
|
||||
- asdf install uv latest
|
||||
- asdf global uv latest
|
||||
- UV_PROJECT_ENVIRONMENT=$READTHEDOCS_VIRTUALENV_PATH uv sync --group docs --no-dev --no-cache
|
||||
sphinx:
|
||||
builder: html
|
||||
configuration: docs/source/conf.py
|
||||
fail_on_warning: true
|
||||
python:
|
||||
install:
|
||||
- method: pip
|
||||
path: .
|
||||
extra_requirements:
|
||||
- docs
|
||||
|
63
CHANGELOG.md
63
CHANGELOG.md
@ -8,7 +8,62 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
<!-- start changelog -->
|
||||
|
||||
(unreleased)=
|
||||
## [Unreleased](https://github.com/jeertmans/manim-slides/compare/v5.4.2...HEAD)
|
||||
## [Unreleased](https://github.com/jeertmans/manim-slides/compare/v5.5.1...HEAD)
|
||||
|
||||
(unreleased-chore)=
|
||||
### Chore
|
||||
|
||||
- Moved `docs` and `tests` extras, as well as `dev-dependencies`,
|
||||
inside groups in `dependency-groups`. This could break existing code
|
||||
when using one of those extras, but as they were not part of the public API,
|
||||
we do not consider this to be a **breaking change**.
|
||||
[#542](https://github.com/jeertmans/manim-slides/pull/542)
|
||||
- Moved `manim_slides.docs.manim_slides_directive` to `manim_slides.sphinxext.manim_slides_directive`.
|
||||
This is a **breaking change** because documentation configs have
|
||||
to be updated.
|
||||
[#242](https://github.com/jeertmans/manim-slides/pull/242)
|
||||
|
||||
(v5.5.1)=
|
||||
## [v5.5.1](https://github.com/jeertmans/manim-slides/compare/v5.5.0...v5.5.1)
|
||||
|
||||
(v5.5.1-changed)=
|
||||
### Changed
|
||||
|
||||
- HTML template now always includes the *notes* plugin so that the speaker
|
||||
view is always available. Previously, it was only included if the slides
|
||||
had notes.
|
||||
[#538](https://github.com/jeertmans/manim-slides/pull/538)
|
||||
- Pressing <kbd>SPACE</kbd> key now pauses the slides, instead of skipping it.
|
||||
Previously, it was not possible to pause HTML slides, which can be very annoying
|
||||
when trying to explain something.
|
||||
[#539](https://github.com/jeertmans/manim-slides/pull/539)
|
||||
|
||||
(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)
|
||||
@ -91,7 +146,7 @@ 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.
|
||||
When there are multiple monitors, the info window will no longer
|
||||
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)
|
||||
|
||||
(v5.2.0-chore)=
|
||||
@ -122,7 +177,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
(v5.1.10-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)
|
||||
|
||||
(v5.1.10-fixed)=
|
||||
@ -195,7 +250,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Fixed whitespace issue in default RevealJS template.
|
||||
[#442](https://github.com/jeertmans/manim-slides/pull/442)
|
||||
- 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)
|
||||
|
||||
(v5.1.8-removed)=
|
||||
|
@ -4,13 +4,14 @@
|
||||
cff-version: 1.2.0
|
||||
title: Manim Slides
|
||||
message: >-
|
||||
If you use this software, please cite it using the
|
||||
metadata from this file.
|
||||
If you use this software, please cite it using our article
|
||||
in the Journal of Open Source Education.
|
||||
type: software
|
||||
authors:
|
||||
- name: Jérome Eertmans
|
||||
orcid: 'https://orcid.org/0000-0002-5579-5360'
|
||||
website: 'https://eertmans.be'
|
||||
doi: 10.5281/zenodo.7971360
|
||||
repository-code: 'https://github.com/jeertmans/manim-slides'
|
||||
url: 'https://eertmans.be/manim-slides'
|
||||
abstract: >-
|
||||
@ -26,7 +27,7 @@ keywords:
|
||||
- PowerPoint
|
||||
- Python
|
||||
license: MIT
|
||||
version: v5.4.2
|
||||
version: v5.5.1
|
||||
preferred-citation:
|
||||
publisher:
|
||||
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>
|
||||
<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">
|
||||
@ -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
|
||||
[documentation-badge]: https://readthedocs.org/projects/manim-slides/badge/?version=latest
|
||||
[documentation-url]: https://manim-slides.readthedocs.io/
|
||||
[doi-badge]: https://zenodo.org/badge/DOI/10.5281/zenodo.8215167.svg
|
||||
[doi-url]: https://doi.org/10.5281/zenodo.8215167
|
||||
[doi-badge]: https://zenodo.org/badge/DOI/10.5281/zenodo.7971360.svg
|
||||
[doi-url]: https://doi.org/10.5281/zenodo.7971360
|
||||
[jose-badge]: https://jose.theoj.org/papers/10.21105/jose.00206/status.svg
|
||||
[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
|
||||
|
@ -18,82 +18,75 @@
|
||||
<body>
|
||||
<div class="reveal">
|
||||
<div class="slides">
|
||||
{%- for presentation_config in presentation_configs -%}
|
||||
{% set outer_loop = loop %}
|
||||
{%- for slide_config in presentation_config.slides -%}
|
||||
{%- if one_file -%}
|
||||
{% for presentation_config in presentation_configs -%}
|
||||
{%- set outer_loop = loop %}
|
||||
{% for slide_config in presentation_config.slides %}
|
||||
{% if one_file %}
|
||||
{% set file = file_to_data_uri(slide_config.file) %}
|
||||
{%- else -%}
|
||||
{% set file = assets_dir / slide_config.file.name %}
|
||||
{%- endif -%}
|
||||
<section
|
||||
data-background-size={{ background_size }}
|
||||
data-background-color="{{ presentation_config.background_color }}"
|
||||
data-background-video="{{ file }}"
|
||||
{% if loop.index == 1 and outer_loop.index == 1 -%}
|
||||
data-background-video-muted
|
||||
{%- endif %}
|
||||
{% if slide_config.loop -%}
|
||||
data-background-video-loop
|
||||
{%- endif -%}
|
||||
{% if slide_config.auto_next -%}
|
||||
data-autoslide="{{ get_duration_ms(slide_config.file) }}"
|
||||
{%- endif -%}>
|
||||
{% if slide_config.notes != "" -%}
|
||||
<aside class="notes" data-markdown>{{ slide_config.notes }}</aside>
|
||||
{%- endif %}
|
||||
</section>
|
||||
{%- endfor -%}
|
||||
{%- endfor -%}
|
||||
{% else %}
|
||||
{% set file = assets_dir / (prefix(outer_loop.index0) + slide_config.file.name) %}
|
||||
{% endif %}
|
||||
<section
|
||||
data-background-size={{ background_size }}
|
||||
data-background-color="{{ presentation_config.background_color }}"
|
||||
data-background-video="{{ file }}"
|
||||
{% if loop.index == 1 and outer_loop.index == 1 %}
|
||||
data-background-video-muted
|
||||
{% endif %}
|
||||
{% if slide_config.loop %}
|
||||
data-background-video-loop
|
||||
{% endif %}
|
||||
{% if slide_config.auto_next %}
|
||||
data-autoslide="{{ get_duration_ms(slide_config.file) }}"
|
||||
{% endif %}
|
||||
>
|
||||
{% if slide_config.notes != "" %}
|
||||
<aside class="notes" data-markdown>{{ slide_config.notes }}</aside>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/{{ reveal_version }}/reveal.min.js"></script>
|
||||
|
||||
<!-- 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/notes/notes.min.js"></script>
|
||||
{%- endif -%}
|
||||
{% endif %}
|
||||
|
||||
<!-- <script src="index.js"></script> -->
|
||||
<script>
|
||||
Reveal.initialize({
|
||||
{% if has_notes -%}
|
||||
plugins: [ RevealMarkdown, RevealNotes ],
|
||||
{%- endif %}
|
||||
{% if has_notes %}
|
||||
/// The list of RevealJS plugins.
|
||||
plugins: [ RevealMarkdown, RevealNotes ],
|
||||
{% endif %}
|
||||
// The "normal" size of the presentation, aspect ratio will
|
||||
// be preserved when the presentation is scaled to fit different
|
||||
// resolutions. Can be specified using percentage units.
|
||||
width: {{ width }},
|
||||
height: {{ height }},
|
||||
|
||||
// Factor of the display size that should remain empty around
|
||||
// the content
|
||||
margin: {{ margin }},
|
||||
|
||||
// Bounds for smallest/largest possible scale to apply to content
|
||||
minScale: {{ min_scale }},
|
||||
maxScale: {{ max_scale }},
|
||||
|
||||
// Display presentation control arrows
|
||||
controls: {{ controls }},
|
||||
|
||||
// Help the user learn the controls by providing hints, for example by
|
||||
// bouncing the down arrow when they first encounter a vertical slide
|
||||
controlsTutorial: {{ controls_tutorial }},
|
||||
|
||||
// Determines where controls appear, "edges" or "bottom-right"
|
||||
controlsLayout: {{ controls_layout }},
|
||||
|
||||
// Visibility rule for backwards navigation arrows; "faded", "hidden"
|
||||
// or "visible"
|
||||
controlsBackArrows: {{ controls_back_arrows }},
|
||||
|
||||
// Display a presentation progress bar
|
||||
progress: {{ progress }},
|
||||
|
||||
// Display the page number of the current slide
|
||||
// - true: Show slide number
|
||||
// - false: Hide slide number
|
||||
@ -109,55 +102,43 @@
|
||||
// object and return an array with one string [slideNumber] or
|
||||
// three strings [n1,delimiter,n2]. See #formatSlideNumber().
|
||||
slideNumber: {{ slide_number }},
|
||||
|
||||
// Can be used to limit the contexts in which the slide number appears
|
||||
// - "all": Always show the slide number
|
||||
// - "print": Only when printing to PDF
|
||||
// - "speaker": Only in the speaker view
|
||||
showSlideNumber: {{ show_slide_number }},
|
||||
|
||||
// Use 1 based indexing for # links to match slide number (default is zero
|
||||
// based)
|
||||
hashOneBasedIndex: {{ hash_one_based_index }},
|
||||
|
||||
// Add the current slide number to the URL hash so that reloading the
|
||||
// page/copying the URL will return you to the same slide
|
||||
hash: {{ hash }},
|
||||
|
||||
// Flags if we should monitor the hash and change slides accordingly
|
||||
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`
|
||||
history: {{ history }},
|
||||
|
||||
// Enable keyboard shortcuts for navigation
|
||||
keyboard: {{ keyboard }},
|
||||
|
||||
// Optional function that blocks keyboard events when retuning false
|
||||
//
|
||||
// If you set this to 'focused', we will only capture keyboard events
|
||||
// for embedded decks when they are in focus
|
||||
keyboardCondition: {{ keyboard_condition }},
|
||||
|
||||
// Disables the default reveal.js slide layout (scaling and centering)
|
||||
// so that you can use custom CSS layout
|
||||
disableLayout: {{ disable_layout }},
|
||||
|
||||
// Enable the slide overview mode
|
||||
overview: {{ overview }},
|
||||
|
||||
// Vertical centering of slides
|
||||
center: {{ center }},
|
||||
|
||||
// Enables touch navigation on devices with touch input
|
||||
touch: {{ touch }},
|
||||
|
||||
// Loop the presentation
|
||||
loop: {{ loop }},
|
||||
|
||||
// Change the presentation direction to be RTL
|
||||
rtl: {{ rtl }},
|
||||
|
||||
// Changes the behavior of our navigation directions.
|
||||
//
|
||||
// "default"
|
||||
@ -183,138 +164,104 @@
|
||||
// from 1.3 -> 2.1. If "grid" is used, the same navigation takes you
|
||||
// from 1.3 -> 2.3.
|
||||
navigationMode: {{ navigation_mode }},
|
||||
|
||||
// Randomizes the order of slides each time the presentation loads
|
||||
shuffle: {{ shuffle }},
|
||||
|
||||
// Turns fragments on and off globally
|
||||
fragments: {{ fragments }},
|
||||
|
||||
// Flags whether to include the current fragment in the URL,
|
||||
// so that reloading brings you to the same fragment position
|
||||
fragmentInURL: {{ fragment_in_url }},
|
||||
|
||||
// Flags if the presentation is running in an embedded mode,
|
||||
// i.e. contained within a limited portion of the screen
|
||||
embedded: {{ embedded }},
|
||||
|
||||
// Flags if we should show a help overlay when the question-mark
|
||||
// key is pressed
|
||||
help: {{ help }},
|
||||
|
||||
// Flags if it should be possible to pause the presentation (blackout)
|
||||
pause: {{ pause }},
|
||||
|
||||
// Flags if speaker notes should be visible to all viewers
|
||||
showNotes: {{ show_notes }},
|
||||
|
||||
// Global override for autolaying embedded media (video/audio/iframe)
|
||||
// - null: Media will only autoplay if data-autoplay is present
|
||||
// - true: All media will autoplay, regardless of individual setting
|
||||
// - false: No media will autoplay, regardless of individual setting
|
||||
autoPlayMedia: {{ auto_play_media }},
|
||||
|
||||
// Global override for preloading lazy-loaded iframes
|
||||
// - 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
|
||||
// - 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
|
||||
preloadIframes: {{ preload_iframes }},
|
||||
|
||||
// Can be used to globally disable auto-animation
|
||||
autoAnimate: {{ auto_animate }},
|
||||
|
||||
// Optionally provide a custom element matcher that will be
|
||||
// used to dictate which elements we can animate between.
|
||||
autoAnimateMatcher: {{ auto_animate_matcher }},
|
||||
|
||||
// Default settings for our auto-animate transitions, can be
|
||||
// overridden per-slide or per-element via data arguments
|
||||
autoAnimateEasing: {{ auto_animate_easing }},
|
||||
autoAnimateDuration: {{ auto_animate_duration }},
|
||||
autoAnimateUnmatched: {{ auto_animate_unmatched }},
|
||||
|
||||
// CSS properties that can be auto-animated. Position & scale
|
||||
// is matched separately so there's no need to include styles
|
||||
// like top/right/bottom/left, width/height or margin.
|
||||
autoAnimateStyles: {{ auto_animate_styles }},
|
||||
|
||||
// Controls automatic progression to the next slide
|
||||
// - 0: Auto-sliding only happens if the data-autoslide HTML attribute
|
||||
// is present on the current slide or fragment
|
||||
// - 1+: All slides will progress automatically at the given interval
|
||||
// - false: No auto-sliding, even if data-autoslide is present
|
||||
autoSlide: {{ auto_slide }},
|
||||
|
||||
// Stop auto-sliding after user input
|
||||
autoSlideStoppable: {{ auto_slide_stoppable }},
|
||||
|
||||
// Use this method for navigation when auto-sliding (defaults to navigateNext)
|
||||
autoSlideMethod: {{ auto_slide_method }},
|
||||
|
||||
// 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
|
||||
// speaker view
|
||||
defaultTiming: {{ default_timing }},
|
||||
|
||||
// Enable slide navigation via mouse wheel
|
||||
mouseWheel: {{ mouse_wheel }},
|
||||
|
||||
// Opens links in an iframe preview overlay
|
||||
// Add `data-preview-link` and `data-preview-link="false"` to customize each link
|
||||
// individually
|
||||
previewLinks: {{ preview_links }},
|
||||
|
||||
// Exposes the reveal.js API through window.postMessage
|
||||
postMessage: {{ post_message }},
|
||||
|
||||
// Dispatches all reveal.js events to the parent window through postMessage
|
||||
postMessageEvents: {{ post_message_events }},
|
||||
|
||||
// Focuses body when page changes visibility to ensure keyboard shortcuts work
|
||||
focusBodyOnPageVisibilityChange: {{ focus_body_on_page_visibility_change }},
|
||||
|
||||
// Transition style
|
||||
transition: {{ transition }}, // none/fade/slide/convex/concave/zoom
|
||||
|
||||
// Transition speed
|
||||
transitionSpeed: {{ transition_speed }}, // default/fast/slow
|
||||
|
||||
// Transition style for full page slide backgrounds
|
||||
backgroundTransition: {{ background_transition }}, // none/fade/slide/convex/concave/zoom
|
||||
|
||||
// The maximum number of pages a single slide can expand onto when printing
|
||||
// to PDF, unlimited by default
|
||||
pdfMaxPagesPerSlide: {{ pdf_max_pages_per_slide }},
|
||||
|
||||
// Prints each fragment on a separate slide
|
||||
pdfSeparateFragments: {{ pdf_separate_fragments }},
|
||||
|
||||
// Offset used to reduce the height of content within exported PDF pages.
|
||||
// This exists to account for environment differences based on how you
|
||||
// print to PDF. CLI printing options, like phantomjs and wkpdf, can end
|
||||
// on precisely the total height of the document whereas in-browser
|
||||
// printing has to end one pixel before.
|
||||
pdfPageHeightOffset: {{ pdf_page_height_offset }},
|
||||
|
||||
// Number of slides away from the current that are visible
|
||||
viewDistance: {{ view_distance }},
|
||||
|
||||
// Number of slides away from the current that are visible on mobile
|
||||
// devices. It is advisable to set this to a lower number than
|
||||
// viewDistance in order to save resources.
|
||||
mobileViewDistance: {{ mobile_view_distance }},
|
||||
|
||||
// The display mode that will be used to show slides
|
||||
display: {{ display }},
|
||||
|
||||
// Hide cursor if inactive
|
||||
hideInactiveCursor: {{ hide_inactive_cursor }},
|
||||
|
||||
// Time before the cursor is hidden (in ms)
|
||||
hideCursorTime: {{ hide_cursor_time }}
|
||||
});
|
||||
|
||||
{% if one_file %}
|
||||
// Fix found by @t-fritsch and @Rapsssito on GitHub
|
||||
// see: https://github.com/hakimel/reveal.js/discussions/3362#discussioncomment-11733074.
|
||||
|
@ -4,6 +4,7 @@
|
||||
# For the full list of built-in configuration values, see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
import os
|
||||
import sys
|
||||
from datetime import date
|
||||
|
||||
@ -36,7 +37,7 @@ extensions = [
|
||||
"sphinx_copybutton",
|
||||
"sphinx_design",
|
||||
# Custom
|
||||
"manim_slides.docs.manim_slides_directive",
|
||||
"manim_slides.sphinxext.manim_slides_directive",
|
||||
]
|
||||
|
||||
autodoc_typehints = "both"
|
||||
@ -55,6 +56,7 @@ add_module_names = False
|
||||
# -- 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_static_path = ["_static"]
|
||||
html_favicon = "_static/favicon.png"
|
||||
|
@ -32,7 +32,7 @@ and development dependencies. If not already, please install this tool.
|
||||
With uv, installation becomes straightforward:
|
||||
|
||||
```bash
|
||||
uv sync --all-extras
|
||||
uv sync
|
||||
```
|
||||
|
||||
:::{note}
|
||||
|
@ -92,7 +92,7 @@ This will also show the default value for each option.
|
||||
If you want to create your own template, the best is to start from the default one.
|
||||
|
||||
You can either download it from the
|
||||
[template folder](https://github.com/jeertmans/manim-slides/tree/main/manim_slides/templates)
|
||||
[template folder](https://github.com/jeertmans/manim-slides/tree/main/manim_slides/cli/convert/templates)
|
||||
or use the `manim-slides convert --to=FORMAT --show-template` command,
|
||||
where `FORMAT` is one of the supported formats.
|
||||
|
||||
|
@ -10,20 +10,22 @@ The following summarizes the different presentation features Manim Slides offers
|
||||
| :--- | :---: | :---: | :---: | :---: |
|
||||
| Basic navigation through slides | Yes | Yes | Yes | Yes (static image) |
|
||||
| Replay slide | Yes | No | No | N/A |
|
||||
| Pause animation | Yes | No | No | N/A |
|
||||
| Pause animation | Yes | Yes | No | N/A |
|
||||
| Play slide in reverse | Yes | No | No | N/A |
|
||||
| Slide count | Yes | Yes (optional) | Yes (optional) | N/A |
|
||||
| Needs Python with Manim Slides installed | Yes | No | No | No
|
||||
| Requires internet access | No | Yes | No | No |
|
||||
| Requires internet access | No | Depends[^1] | No | No |
|
||||
| Auto. play slides | Yes | Yes | Yes | N/A |
|
||||
| Loops support | Yes | Yes | Yes | N/A |
|
||||
| Fully customizable | No | Yes (`--use-template` option) | No | No |
|
||||
| Other dependencies | None | A modern web browser | PowerPoint or LibreOffice Impress[^1] | None |
|
||||
| Works cross-platforms | Yes | Yes | Partly[^1][^2] | Yes |
|
||||
| Other dependencies | None | A modern web browser | PowerPoint or LibreOffice Impress[^2] | None |
|
||||
| Works cross-platforms | Yes | Yes | Partly[^2][^3] | Yes |
|
||||
:::
|
||||
|
||||
[^1]: If you encounter a problem where slides do not automatically play or loops do not work,
|
||||
[^1]: By default, HTML assets are loaded from the internet, but they can be
|
||||
pre-downloaded and embedded in the HTML file at conversion time.
|
||||
[^2]: If you encounter a problem where slides do not automatically play or loops do not work,
|
||||
please
|
||||
[file an issue on GitHub](https://github.com/jeertmans/manim-slides/issues/new/choose).
|
||||
[^2]: PowerPoint online does not seem to support automatic playing of videos,
|
||||
[^3]: PowerPoint online does not seem to support automatic playing of videos,
|
||||
so you need LibreOffice Impress on Linux platforms.
|
||||
|
@ -4,7 +4,7 @@ This page contains an exhaustive list of all the commands available with `manim-
|
||||
|
||||
|
||||
```{eval-rst}
|
||||
.. click:: manim_slides.__main__:cli
|
||||
.. click:: manim_slides.cli.commands:main
|
||||
:prog: manim-slides
|
||||
:nested: full
|
||||
```
|
||||
|
@ -4,7 +4,7 @@ One of the benefits of the `convert` command is the use of template files.
|
||||
|
||||
Currently, only the HTML export uses one. If not specified, the template
|
||||
will be the one shipped with Manim Slides, see
|
||||
[`manim_slides/templates/revealjs.html`](https://github.com/jeertmans/manim-slides/blob/main/manim_slides/templates/revealjs.html).
|
||||
[`manim_slides/cli/convert/templates/revealjs.html`](https://github.com/jeertmans/manim-slides/blob/main/manim_slides/cli/convert/templates/revealjs.html).
|
||||
|
||||
Because you can actually use your own template with the `--use-template`
|
||||
option, possibilities are infinite!
|
||||
|
@ -30,7 +30,7 @@ manim-slides convert --show-config
|
||||
## Using a Custom Template
|
||||
|
||||
The default template used for HTML conversion can be found on
|
||||
[GitHub](https://github.com/jeertmans/manim-slides/blob/main/manim_slides/templates/revealjs.html)
|
||||
[GitHub](https://github.com/jeertmans/manim-slides/blob/main/manim_slides/cli/convert/templates/revealjs.html)
|
||||
or printed with the `--show-template` option.
|
||||
If you wish to use another template, you can do so with the
|
||||
`--use-template FILE` option.
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Manim Slides' Sphinx directive
|
||||
|
||||
```{eval-rst}
|
||||
.. automodule:: manim_slides.docs.manim_slides_directive
|
||||
.. automodule:: manim_slides.sphinxext.manim_slides_directive
|
||||
:members: ManimSlidesDirective
|
||||
```
|
||||
|
@ -1,3 +1,10 @@
|
||||
"""
|
||||
Manim Slides module.
|
||||
|
||||
Submodules are lazily imported, in order to provide a faster import experience
|
||||
in some cases.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from types import ModuleType
|
||||
from typing import Any
|
||||
@ -8,9 +15,7 @@ from .__version__ import __version__
|
||||
class Module(ModuleType):
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
if name == "Slide" or name == "ThreeDSlide":
|
||||
module = __import__(
|
||||
"manim_slides.slide", None, None, ["Slide", "ThreeDSlide"]
|
||||
)
|
||||
module = __import__("manim_slides.slide", None, None, [name])
|
||||
return getattr(module, name)
|
||||
elif name == "ManimSlidesMagic":
|
||||
module = __import__(
|
||||
|
@ -1,11 +1,8 @@
|
||||
import json
|
||||
|
||||
import click
|
||||
import requests
|
||||
from click_default_group import DefaultGroup
|
||||
"""Manim Slides' main entrypoint."""
|
||||
|
||||
from .__version__ import __version__
|
||||
from .checkhealth import checkhealth
|
||||
from .cli.commands import main
|
||||
from .convert import convert
|
||||
from .logger import logger
|
||||
from .present import list_scenes, present
|
||||
@ -72,4 +69,4 @@ cli.add_command(render)
|
||||
cli.add_command(wizard)
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
||||
main()
|
||||
|
@ -1 +1,3 @@
|
||||
"""Manim Slides' version."""
|
||||
|
||||
__version__ = "5.4.2"
|
||||
|
72
manim_slides/cli/commands.py
Normal file
72
manim_slides/cli/commands.py
Normal file
@ -0,0 +1,72 @@
|
||||
"""Manim Slides' CLI."""
|
||||
|
||||
import json
|
||||
|
||||
import click
|
||||
import requests
|
||||
from click_default_group import DefaultGroup
|
||||
|
||||
from ..__version__ import __version__
|
||||
from ..core.logger import logger
|
||||
from .convert.commands import convert
|
||||
from .present.commands import list_scenes, present
|
||||
from .render.commands import render
|
||||
from .wizard.commands import init, wizard
|
||||
|
||||
|
||||
@click.group(cls=DefaultGroup, default="present", default_if_no_args=True)
|
||||
@click.option(
|
||||
"--notify-outdated-version/--silent",
|
||||
" /-S",
|
||||
is_flag=True,
|
||||
default=True,
|
||||
help="Check if a new version of Manim Slides is available.",
|
||||
)
|
||||
@click.version_option(__version__, "-v", "--version")
|
||||
@click.help_option("-h", "--help")
|
||||
def main(notify_outdated_version: bool) -> None:
|
||||
"""
|
||||
Manim Slides command-line utilities.
|
||||
|
||||
If no command is specified, defaults to `present`.
|
||||
"""
|
||||
# Code below is mostly a copy from:
|
||||
# https://github.com/ManimCommunity/manim/blob/main/manim/cli/render/commands.py
|
||||
if notify_outdated_version:
|
||||
manim_info_url = "https://pypi.org/pypi/manim-slides/json"
|
||||
warn_prompt = "Cannot check if latest release of Manim Slides is installed"
|
||||
try:
|
||||
req_info: requests.models.Response = requests.get(manim_info_url, timeout=2)
|
||||
req_info.raise_for_status()
|
||||
stable = req_info.json()["info"]["version"]
|
||||
if stable != __version__:
|
||||
click.echo(
|
||||
"You are using Manim Slides version "
|
||||
+ click.style(f"v{__version__}", fg="red")
|
||||
+ ", but version "
|
||||
+ click.style(f"v{stable}", fg="green")
|
||||
+ " is available."
|
||||
)
|
||||
click.echo(
|
||||
"You should consider upgrading via "
|
||||
+ click.style("pip install -U manim-slides", fg="yellow")
|
||||
)
|
||||
except requests.exceptions.HTTPError:
|
||||
logger.debug(f"HTTP Error: {warn_prompt}")
|
||||
except requests.exceptions.ConnectionError:
|
||||
logger.debug(f"Connection Error: {warn_prompt}")
|
||||
except requests.exceptions.Timeout:
|
||||
logger.debug(f"Timed Out: {warn_prompt}")
|
||||
except json.JSONDecodeError:
|
||||
logger.debug(warn_prompt)
|
||||
logger.debug(f"Error decoding JSON from {manim_info_url}")
|
||||
except Exception:
|
||||
logger.debug(f"Something went wrong: {warn_prompt}")
|
||||
|
||||
|
||||
main.add_command(convert)
|
||||
main.add_command(init)
|
||||
main.add_command(list_scenes)
|
||||
main.add_command(present)
|
||||
main.add_command(render)
|
||||
main.add_command(wizard)
|
@ -4,8 +4,9 @@ from typing import Any, Callable
|
||||
import click
|
||||
from click import Context, Parameter
|
||||
|
||||
from .defaults import CONFIG_PATH, FOLDER_PATH
|
||||
from .logger import logger
|
||||
from ..core.config import list_presentation_configs
|
||||
from ..core.defaults import CONFIG_PATH, FOLDER_PATH
|
||||
from ..core.logger import logger
|
||||
|
||||
F = Callable[..., Any]
|
||||
Wrapper = Callable[[F], F]
|
||||
@ -88,6 +89,68 @@ def folder_path_option(function: F) -> F:
|
||||
callback=callback,
|
||||
help="Set slides folder.",
|
||||
show_default=True,
|
||||
is_eager=True, # Needed to expose its value to other callbacks
|
||||
)
|
||||
|
||||
return wrapper(function)
|
||||
|
||||
|
||||
def scenes_argument(function: F) -> F:
|
||||
"""
|
||||
Wrap a function to add a scenes arguments.
|
||||
|
||||
This function assumes that :func:`folder_path_option` is also used
|
||||
on the same decorated function.
|
||||
"""
|
||||
|
||||
def callback(ctx: Context, param: Parameter, value: tuple[str]) -> list[Path]:
|
||||
folder: Path = ctx.params.get("folder")
|
||||
|
||||
presentation_config_paths = list_presentation_configs(folder)
|
||||
scene_names = [path.stem for path in presentation_config_paths]
|
||||
num_scenes = len(scene_names)
|
||||
num_digits = len(str(num_scenes))
|
||||
|
||||
if num_scenes == 0:
|
||||
raise click.UsageError(
|
||||
f"Folder {folder} does not contain "
|
||||
"any valid config file, did you render the animations first?"
|
||||
)
|
||||
|
||||
paths = []
|
||||
|
||||
if value:
|
||||
for scene_name in value:
|
||||
try:
|
||||
i = scene_names.index(scene_name)
|
||||
paths.append(presentation_config_paths[i])
|
||||
except ValueError:
|
||||
raise click.UsageError(
|
||||
f"Could not find scene `{scene_name}` in: "
|
||||
+ ", ".join(scene_names)
|
||||
+ ". Did you make a typo or forgot to render the animations first?"
|
||||
) from None
|
||||
else:
|
||||
click.echo(
|
||||
"Choose at least one or more scenes from "
|
||||
"(enter the corresponding number):\n"
|
||||
+ "\n".join(
|
||||
f"- {i:{num_digits}d}: {name}"
|
||||
for i, name in enumerate(scene_names, start=1)
|
||||
)
|
||||
)
|
||||
continue_prompt = True
|
||||
while continue_prompt:
|
||||
index = click.prompt(
|
||||
"Please enter a value", type=click.IntRange(1, num_scenes)
|
||||
)
|
||||
paths.append(presentation_config_paths[index - 1])
|
||||
continue_prompt = click.confirm(
|
||||
"Do you want to enter an additional scene?"
|
||||
)
|
||||
|
||||
return paths
|
||||
|
||||
wrapper: Wrapper = click.argument("scenes", nargs=-1, callback=callback)
|
||||
|
||||
return wrapper(function)
|
@ -37,14 +37,18 @@ from pydantic_core import CoreSchema, core_schema
|
||||
from pydantic_extra_types.color import Color
|
||||
from tqdm import tqdm
|
||||
|
||||
from ...core.config import PresentationConfig
|
||||
from ...core.logger import logger
|
||||
from ..commons import folder_path_option, scenes_argument, verbosity_option
|
||||
from . import templates
|
||||
from .commons import folder_path_option, verbosity_option
|
||||
from .config import PresentationConfig
|
||||
from .logger import logger
|
||||
from .present import get_scenes_presentation_config
|
||||
|
||||
|
||||
def open_with_default(file: Path) -> None:
|
||||
"""
|
||||
Open a file with the default application.
|
||||
|
||||
:param file: The file to open.
|
||||
"""
|
||||
system = platform.system()
|
||||
if system == "Darwin":
|
||||
subprocess.call(("open", str(file)))
|
||||
@ -142,6 +146,7 @@ class Str(str):
|
||||
|
||||
# This fixes pickling issue on Python 3.8
|
||||
__reduce_ex__ = str.__reduce_ex__
|
||||
# TODO: do we still need this?
|
||||
|
||||
@classmethod
|
||||
def __get_pydantic_core_schema__(
|
||||
@ -531,7 +536,7 @@ class RevealJS(Converter):
|
||||
"black",
|
||||
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(
|
||||
RevealTheme.black, description="RevealJS version."
|
||||
)
|
||||
@ -547,6 +552,11 @@ class RevealJS(Converter):
|
||||
return resources.files(templates).joinpath("revealjs.html").read_text()
|
||||
|
||||
def open(self, file: Path) -> None:
|
||||
"""
|
||||
Open the HTML file inside a web browser.
|
||||
|
||||
:param path: The path to the HTML file.
|
||||
"""
|
||||
webbrowser.open(file.absolute().as_uri())
|
||||
|
||||
def convert_to(self, dest: Path) -> None: # noqa: C901
|
||||
@ -594,7 +604,9 @@ class RevealJS(Converter):
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
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()
|
||||
|
||||
@ -908,7 +920,7 @@ def show_template_option(function: Callable[..., Any]) -> Callable[..., Any]:
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.argument("scenes", nargs=-1)
|
||||
@scenes_argument
|
||||
@folder_path_option
|
||||
@click.argument("dest", type=click.Path(dir_okay=False, path_type=Path))
|
||||
@click.option(
|
||||
@ -958,7 +970,7 @@ def show_template_option(function: Callable[..., Any]) -> Callable[..., Any]:
|
||||
@show_config_options
|
||||
@verbosity_option
|
||||
def convert(
|
||||
scenes: list[str],
|
||||
scenes: list[Path],
|
||||
folder: Path,
|
||||
dest: Path,
|
||||
to: str,
|
||||
@ -969,7 +981,7 @@ def convert(
|
||||
one_file: bool,
|
||||
) -> None:
|
||||
"""Convert SCENE(s) into a given format and writes the result in DEST."""
|
||||
presentation_configs = get_scenes_presentation_config(scenes, folder)
|
||||
presentation_configs = [PresentationConfig.from_file(scene) for scene in scenes]
|
||||
|
||||
try:
|
||||
if to == "auto":
|
1
manim_slides/cli/convert/templates/__init__.py
Normal file
1
manim_slides/cli/convert/templates/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Manim Slides conversion templates."""
|
@ -12,7 +12,6 @@
|
||||
<!-- Theme used for syntax highlighting of code -->
|
||||
<!-- <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="index.css"> -->
|
||||
</head>
|
||||
|
||||
@ -20,82 +19,74 @@
|
||||
<div class="reveal">
|
||||
<div class="slides">
|
||||
{% for presentation_config in presentation_configs -%}
|
||||
{% set outer_loop = loop %}
|
||||
{%- for slide_config in presentation_config.slides -%}
|
||||
{%- if one_file -%}
|
||||
{%- set outer_loop = loop %}
|
||||
{% for slide_config in presentation_config.slides %}
|
||||
{% if one_file %}
|
||||
{% set file = file_to_data_uri(slide_config.file) %}
|
||||
{%- else -%}
|
||||
{% else %}
|
||||
{% set file = assets_dir / (prefix(outer_loop.index0) + slide_config.file.name) %}
|
||||
{%- endif -%}
|
||||
{% endif %}
|
||||
<section
|
||||
data-background-size={{ background_size }}
|
||||
data-background-color="{{ presentation_config.background_color }}"
|
||||
data-background-video="{{ file }}"
|
||||
{% if loop.index == 1 and outer_loop.index == 1 -%}
|
||||
data-background-video-muted
|
||||
{%- endif -%}
|
||||
{% if slide_config.loop -%}
|
||||
data-background-video-loop
|
||||
{%- endif -%}
|
||||
{% if slide_config.auto_next -%}
|
||||
data-autoslide="{{ get_duration_ms(slide_config.file) }}"
|
||||
{%- endif %}
|
||||
{% if loop.index == 1 and outer_loop.index == 1 %}
|
||||
data-background-video-muted
|
||||
{% endif %}
|
||||
{% if slide_config.loop %}
|
||||
data-background-video-loop
|
||||
{% endif %}
|
||||
{% if slide_config.auto_next %}
|
||||
data-autoslide="{{ get_duration_ms(slide_config.file) }}"
|
||||
{% endif %}
|
||||
>
|
||||
{%- if slide_config.notes != "" -%}
|
||||
{% if slide_config.notes != "" %}
|
||||
<aside class="notes" data-markdown>{{ slide_config.notes }}</aside>
|
||||
{%- endif %}
|
||||
{% endif %}
|
||||
</section>
|
||||
{%- endfor -%}
|
||||
{%- endfor -%}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</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 }}/plugin/notes/notes.min.js"></script>
|
||||
|
||||
<!-- To include plugins, see: https://revealjs.com/plugins/ -->
|
||||
|
||||
{% 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/notes/notes.min.js"></script>
|
||||
{%- endif -%}
|
||||
{% if has_notes %}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/{{ reveal_version }}/plugin/markdown/markdown.min.js"></script>
|
||||
{% endif %}
|
||||
|
||||
<!-- <script src="index.js"></script> -->
|
||||
<script>
|
||||
Reveal.initialize({
|
||||
{% if has_notes -%}
|
||||
plugins: [ RevealMarkdown, RevealNotes ],
|
||||
{%- endif %}
|
||||
{% if has_notes %}
|
||||
/// The list of RevealJS plugins.
|
||||
plugins: [ RevealMarkdown, RevealNotes ],
|
||||
{% endif %}
|
||||
// The "normal" size of the presentation, aspect ratio will
|
||||
// be preserved when the presentation is scaled to fit different
|
||||
// resolutions. Can be specified using percentage units.
|
||||
width: {{ width }},
|
||||
height: {{ height }},
|
||||
|
||||
// Factor of the display size that should remain empty around
|
||||
// the content
|
||||
margin: {{ margin }},
|
||||
|
||||
// Bounds for smallest/largest possible scale to apply to content
|
||||
minScale: {{ min_scale }},
|
||||
maxScale: {{ max_scale }},
|
||||
|
||||
// Display presentation control arrows
|
||||
controls: {{ controls }},
|
||||
|
||||
// Help the user learn the controls by providing hints, for example by
|
||||
// bouncing the down arrow when they first encounter a vertical slide
|
||||
controlsTutorial: {{ controls_tutorial }},
|
||||
|
||||
// Determines where controls appear, "edges" or "bottom-right"
|
||||
controlsLayout: {{ controls_layout }},
|
||||
|
||||
// Visibility rule for backwards navigation arrows; "faded", "hidden"
|
||||
// or "visible"
|
||||
controlsBackArrows: {{ controls_back_arrows }},
|
||||
|
||||
// Display a presentation progress bar
|
||||
progress: {{ progress }},
|
||||
|
||||
// Display the page number of the current slide
|
||||
// - true: Show slide number
|
||||
// - false: Hide slide number
|
||||
@ -111,58 +102,43 @@
|
||||
// object and return an array with one string [slideNumber] or
|
||||
// three strings [n1,delimiter,n2]. See #formatSlideNumber().
|
||||
slideNumber: {{ slide_number }},
|
||||
|
||||
// Can be used to limit the contexts in which the slide number appears
|
||||
// - "all": Always show the slide number
|
||||
// - "print": Only when printing to PDF
|
||||
// - "speaker": Only in the speaker view
|
||||
showSlideNumber: {{ show_slide_number }},
|
||||
|
||||
// Use 1 based indexing for # links to match slide number (default is zero
|
||||
// based)
|
||||
hashOneBasedIndex: {{ hash_one_based_index }},
|
||||
|
||||
// Add the current slide number to the URL hash so that reloading the
|
||||
// page/copying the URL will return you to the same slide
|
||||
hash: {{ hash }},
|
||||
|
||||
// Flags if we should monitor the hash and change slides accordingly
|
||||
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`
|
||||
history: {{ history }},
|
||||
|
||||
// Enable keyboard shortcuts for navigation
|
||||
keyboard: {{ keyboard }},
|
||||
|
||||
// Optional function that blocks keyboard events when retuning false
|
||||
//
|
||||
// If you set this to 'focused', we will only capture keyboard events
|
||||
// for embedded decks when they are in focus
|
||||
keyboardCondition: {{ keyboard_condition }},
|
||||
|
||||
// Disables the default reveal.js slide layout (scaling and centering)
|
||||
// so that you can use custom CSS layout
|
||||
disableLayout: {{ disable_layout }},
|
||||
|
||||
// Enable the slide overview mode
|
||||
overview: {{ overview }},
|
||||
|
||||
// Vertical centering of slides
|
||||
center: {{ center }},
|
||||
|
||||
// Enables touch navigation on devices with touch input
|
||||
touch: {{ touch }},
|
||||
|
||||
// Loop the presentation
|
||||
loop: {{ loop }},
|
||||
|
||||
// Change the presentation direction to be RTL
|
||||
rtl: {{ rtl }},
|
||||
|
||||
// Changes the behavior of our navigation directions.
|
||||
//
|
||||
// "default"
|
||||
@ -188,139 +164,122 @@
|
||||
// from 1.3 -> 2.1. If "grid" is used, the same navigation takes you
|
||||
// from 1.3 -> 2.3.
|
||||
navigationMode: {{ navigation_mode }},
|
||||
|
||||
// Randomizes the order of slides each time the presentation loads
|
||||
shuffle: {{ shuffle }},
|
||||
|
||||
// Turns fragments on and off globally
|
||||
fragments: {{ fragments }},
|
||||
|
||||
// Flags whether to include the current fragment in the URL,
|
||||
// so that reloading brings you to the same fragment position
|
||||
fragmentInURL: {{ fragment_in_url }},
|
||||
|
||||
// Flags if the presentation is running in an embedded mode,
|
||||
// i.e. contained within a limited portion of the screen
|
||||
embedded: {{ embedded }},
|
||||
|
||||
// Flags if we should show a help overlay when the question-mark
|
||||
// key is pressed
|
||||
help: {{ help }},
|
||||
|
||||
// Flags if it should be possible to pause the presentation (blackout)
|
||||
pause: {{ pause }},
|
||||
|
||||
// Flags if speaker notes should be visible to all viewers
|
||||
showNotes: {{ show_notes }},
|
||||
|
||||
// Global override for autolaying embedded media (video/audio/iframe)
|
||||
// - null: Media will only autoplay if data-autoplay is present
|
||||
// - true: All media will autoplay, regardless of individual setting
|
||||
// - false: No media will autoplay, regardless of individual setting
|
||||
autoPlayMedia: {{ auto_play_media }},
|
||||
|
||||
// Global override for preloading lazy-loaded iframes
|
||||
// - 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
|
||||
// - 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
|
||||
preloadIframes: {{ preload_iframes }},
|
||||
|
||||
// Can be used to globally disable auto-animation
|
||||
autoAnimate: {{ auto_animate }},
|
||||
|
||||
// Optionally provide a custom element matcher that will be
|
||||
// used to dictate which elements we can animate between.
|
||||
autoAnimateMatcher: {{ auto_animate_matcher }},
|
||||
|
||||
// Default settings for our auto-animate transitions, can be
|
||||
// overridden per-slide or per-element via data arguments
|
||||
autoAnimateEasing: {{ auto_animate_easing }},
|
||||
autoAnimateDuration: {{ auto_animate_duration }},
|
||||
autoAnimateUnmatched: {{ auto_animate_unmatched }},
|
||||
|
||||
// CSS properties that can be auto-animated. Position & scale
|
||||
// is matched separately so there's no need to include styles
|
||||
// like top/right/bottom/left, width/height or margin.
|
||||
autoAnimateStyles: {{ auto_animate_styles }},
|
||||
|
||||
// Controls automatic progression to the next slide
|
||||
// - 0: Auto-sliding only happens if the data-autoslide HTML attribute
|
||||
// is present on the current slide or fragment
|
||||
// - 1+: All slides will progress automatically at the given interval
|
||||
// - false: No auto-sliding, even if data-autoslide is present
|
||||
autoSlide: {{ auto_slide }},
|
||||
|
||||
// Stop auto-sliding after user input
|
||||
autoSlideStoppable: {{ auto_slide_stoppable }},
|
||||
|
||||
// Use this method for navigation when auto-sliding (defaults to navigateNext)
|
||||
autoSlideMethod: {{ auto_slide_method }},
|
||||
|
||||
// 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
|
||||
// speaker view
|
||||
defaultTiming: {{ default_timing }},
|
||||
|
||||
// Enable slide navigation via mouse wheel
|
||||
mouseWheel: {{ mouse_wheel }},
|
||||
|
||||
// Opens links in an iframe preview overlay
|
||||
// Add `data-preview-link` and `data-preview-link="false"` to customize each link
|
||||
// individually
|
||||
previewLinks: {{ preview_links }},
|
||||
|
||||
// Exposes the reveal.js API through window.postMessage
|
||||
postMessage: {{ post_message }},
|
||||
|
||||
// Dispatches all reveal.js events to the parent window through postMessage
|
||||
postMessageEvents: {{ post_message_events }},
|
||||
|
||||
// Focuses body when page changes visibility to ensure keyboard shortcuts work
|
||||
focusBodyOnPageVisibilityChange: {{ focus_body_on_page_visibility_change }},
|
||||
|
||||
// Transition style
|
||||
transition: {{ transition }}, // none/fade/slide/convex/concave/zoom
|
||||
|
||||
// Transition speed
|
||||
transitionSpeed: {{ transition_speed }}, // default/fast/slow
|
||||
|
||||
// Transition style for full page slide backgrounds
|
||||
backgroundTransition: {{ background_transition }}, // none/fade/slide/convex/concave/zoom
|
||||
|
||||
// The maximum number of pages a single slide can expand onto when printing
|
||||
// to PDF, unlimited by default
|
||||
pdfMaxPagesPerSlide: {{ pdf_max_pages_per_slide }},
|
||||
|
||||
// Prints each fragment on a separate slide
|
||||
pdfSeparateFragments: {{ pdf_separate_fragments }},
|
||||
|
||||
// Offset used to reduce the height of content within exported PDF pages.
|
||||
// This exists to account for environment differences based on how you
|
||||
// print to PDF. CLI printing options, like phantomjs and wkpdf, can end
|
||||
// on precisely the total height of the document whereas in-browser
|
||||
// printing has to end one pixel before.
|
||||
pdfPageHeightOffset: {{ pdf_page_height_offset }},
|
||||
|
||||
// Number of slides away from the current that are visible
|
||||
viewDistance: {{ view_distance }},
|
||||
|
||||
// Number of slides away from the current that are visible on mobile
|
||||
// devices. It is advisable to set this to a lower number than
|
||||
// viewDistance in order to save resources.
|
||||
mobileViewDistance: {{ mobile_view_distance }},
|
||||
|
||||
// The display mode that will be used to show slides
|
||||
display: {{ display }},
|
||||
|
||||
// Hide cursor if inactive
|
||||
hideInactiveCursor: {{ hide_inactive_cursor }},
|
||||
|
||||
// Time before the cursor is hidden (in ms)
|
||||
hideCursorTime: {{ hide_cursor_time }}
|
||||
});
|
||||
|
||||
{% if one_file -%}
|
||||
// Override SPACE to play / pause the video
|
||||
Reveal.addKeyBinding(
|
||||
{
|
||||
keyCode: 32,
|
||||
key: 'SPACE',
|
||||
description: 'Play / pause video'
|
||||
},
|
||||
() => {
|
||||
var currentVideos = Reveal.getCurrentSlide().slideBackgroundContentElement.getElementsByTagName("video");
|
||||
if (currentVideos.length > 0) {
|
||||
if (currentVideos[0].paused == true) currentVideos[0].play();
|
||||
else currentVideos[0].pause();
|
||||
} else {
|
||||
Reveal.next();
|
||||
}
|
||||
}
|
||||
);
|
||||
{% if one_file %}
|
||||
// Fix found by @t-fritsch and @Rapsssito on GitHub
|
||||
// see: https://github.com/hakimel/reveal.js/discussions/3362#discussioncomment-11733074.
|
||||
function setVideoBase64(video) {
|
||||
@ -362,15 +321,14 @@
|
||||
}
|
||||
// Setup base64 videos
|
||||
Reveal.on( 'ready', fixBase64VideoBackground );
|
||||
{%- endif %}
|
||||
{% endif %}
|
||||
</script>
|
||||
|
||||
{% if env['READTHEDOCS'] -%}
|
||||
<style>
|
||||
readthedocs-flyout, readthedocs-notification {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
{%- endif %}
|
||||
{% if env['READTHEDOCS'] %}
|
||||
<style>
|
||||
readthedocs-flyout, readthedocs-notification {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
{% endif %}
|
||||
</body>
|
||||
</html>
|
1
manim_slides/cli/present/__init__.py
Normal file
1
manim_slides/cli/present/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Manim Slides' presentation commands."""
|
@ -7,9 +7,14 @@ import click
|
||||
from click import Context, Parameter
|
||||
from pydantic import ValidationError
|
||||
|
||||
from ..commons import config_path_option, folder_path_option, verbosity_option
|
||||
from ..config import Config, PresentationConfig
|
||||
from ..logger import logger
|
||||
from ...core.config import Config, PresentationConfig, list_presentation_configs
|
||||
from ...core.logger import logger
|
||||
from ..commons import (
|
||||
config_path_option,
|
||||
folder_path_option,
|
||||
scenes_argument,
|
||||
verbosity_option,
|
||||
)
|
||||
|
||||
|
||||
@click.command()
|
||||
@ -18,8 +23,10 @@ from ..logger import logger
|
||||
@verbosity_option
|
||||
def list_scenes(folder: Path) -> None:
|
||||
"""List available scenes."""
|
||||
for i, scene in enumerate(_list_scenes(folder), start=1):
|
||||
click.secho(f"{i}: {scene}", fg="green")
|
||||
scene_names = [path.stem for path in list_presentation_configs(folder)]
|
||||
num_digits = len(str(len(scene_names)))
|
||||
for i, scene_name in enumerate(scene_names, start=1):
|
||||
click.secho(f"{i:{num_digits}d}: {scene_name}", fg="green")
|
||||
|
||||
|
||||
def _list_scenes(folder: Path) -> list[str]:
|
||||
@ -130,7 +137,7 @@ def start_at_callback(
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.argument("scenes", nargs=-1)
|
||||
@scenes_argument
|
||||
@config_path_option
|
||||
@folder_path_option
|
||||
@click.option("--start-paused", is_flag=True, help="Start paused.")
|
||||
@ -276,7 +283,7 @@ def present( # noqa: C901
|
||||
if skip_all:
|
||||
exit_after_last_slide = True
|
||||
|
||||
presentation_configs = get_scenes_presentation_config(scenes, folder)
|
||||
presentation_configs = [PresentationConfig.from_file(path) for path in scenes]
|
||||
|
||||
if config_path.exists():
|
||||
try:
|
@ -1,14 +1,4 @@
|
||||
"""
|
||||
Alias command to either
|
||||
``manim render [OPTIONS] [ARGS]...`` or
|
||||
``manimgl -w [OPTIONS] [ARGS]...``.
|
||||
|
||||
This is especially useful for two reasons:
|
||||
|
||||
1. You can are sure to execute the rendering command with the same Python environment
|
||||
as for ``manim-slides``.
|
||||
2. You can pass options to the config.
|
||||
"""
|
||||
"""Manim Slides' rendering commands."""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
@ -44,10 +34,22 @@ def render(ce: bool, gl: bool, args: tuple[str, ...]) -> None:
|
||||
|
||||
Use ``manim-slides render --help`` to see help information for
|
||||
a specific renderer.
|
||||
|
||||
Alias command to either
|
||||
``manim render [OPTIONS] [ARGS]...`` or
|
||||
``manimgl [OPTIONS] [ARGS]...``.
|
||||
|
||||
This is especially useful for two reasons:
|
||||
|
||||
1. You can are sure to execute the rendering command with the same Python environment
|
||||
as for ``manim-slides``.
|
||||
2. You can pass options to the config.
|
||||
"""
|
||||
if ce and gl:
|
||||
raise click.UsageError("You cannot specify both --CE and --GL renderers.")
|
||||
if gl:
|
||||
subprocess.run([sys.executable, "-m", "manimlib", "-w", *args])
|
||||
else:
|
||||
subprocess.run([sys.executable, "-m", "manim", "render", *args])
|
||||
from manim.cli.render.commands import render as render_ce
|
||||
|
||||
render_ce(args, standalone_mode=False)
|
1
manim_slides/cli/wizard/__init__.py
Normal file
1
manim_slides/cli/wizard/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Manim Slides' wizard."""
|
@ -3,10 +3,9 @@ from pathlib import Path
|
||||
|
||||
import click
|
||||
|
||||
from ...core.config import Config
|
||||
from ...core.logger import logger
|
||||
from ..commons import config_options, verbosity_option
|
||||
from ..config import Config
|
||||
from ..defaults import CONFIG_PATH
|
||||
from ..logger import logger
|
||||
|
||||
|
||||
@click.command()
|
||||
@ -37,7 +36,7 @@ def _init(
|
||||
mode.
|
||||
"""
|
||||
if config_path.exists():
|
||||
click.secho(f"The `{CONFIG_PATH}` configuration file exists")
|
||||
logger.debug(f"The `{config_path}` configuration file exists")
|
||||
|
||||
if not force and not merge:
|
||||
choice = click.prompt(
|
||||
@ -57,7 +56,7 @@ def _init(
|
||||
if force:
|
||||
logger.debug(f"Overwriting `{config_path}` if exists")
|
||||
elif merge:
|
||||
logger.debug("Merging new config into `{config_path}`")
|
||||
logger.debug(f"Merging new config into `{config_path}`")
|
||||
|
||||
if not skip_interactive:
|
||||
if config_path.exists():
|
||||
@ -82,4 +81,4 @@ def _init(
|
||||
|
||||
config.to_file(config_path)
|
||||
|
||||
click.secho(f"Configuration file successfully saved to `{config_path}`")
|
||||
logger.debug(f"Configuration file successfully saved to `{config_path}`")
|
@ -1,3 +1,5 @@
|
||||
"""Manim Slides' configuration tools."""
|
||||
|
||||
import json
|
||||
import shutil
|
||||
from functools import wraps
|
||||
@ -13,6 +15,7 @@ from pydantic import (
|
||||
FilePath,
|
||||
PositiveInt,
|
||||
PrivateAttr,
|
||||
ValidationError,
|
||||
conset,
|
||||
field_serializer,
|
||||
field_validator,
|
||||
@ -26,28 +29,54 @@ Receiver = Callable[..., Any]
|
||||
|
||||
|
||||
class Signal(BaseModel): # type: ignore[misc]
|
||||
__receivers: list[Receiver] = PrivateAttr(default_factory=list)
|
||||
"""Signal that notifies a list of receivers when it is emitted."""
|
||||
|
||||
__receivers: set[Receiver] = PrivateAttr(default_factory=set)
|
||||
|
||||
def connect(self, receiver: Receiver) -> None:
|
||||
self.__receivers.append(receiver)
|
||||
"""
|
||||
Connect a receiver to this signal.
|
||||
|
||||
This is a no-op if the receiver was already connected to this signal.
|
||||
|
||||
:param receiver: The receiver to connect.
|
||||
"""
|
||||
self.__receivers.add(receiver)
|
||||
|
||||
def disconnect(self, receiver: Receiver) -> None:
|
||||
self.__receivers.remove(receiver)
|
||||
"""
|
||||
Disconnect a receiver from this signal.
|
||||
|
||||
This is a no-op if the receiver was not connected to this signal.
|
||||
|
||||
:param receiver: The receiver to disconnect.
|
||||
"""
|
||||
self.__receivers.discard(receiver)
|
||||
|
||||
def emit(self, *args: Any) -> None:
|
||||
"""
|
||||
Emit this signal and call each of the attached receivers.
|
||||
|
||||
:param args: Positional arguments passed to each receiver.
|
||||
"""
|
||||
for receiver in self.__receivers:
|
||||
receiver(*args)
|
||||
|
||||
|
||||
def key_id(name: str) -> PositiveInt:
|
||||
"""Avoid importing Qt too early."""
|
||||
from qtpy.QtCore import Qt
|
||||
"""
|
||||
Return the id corresponding to the given key name.
|
||||
|
||||
:param str: The name of the key, e.g., 'Q'.
|
||||
:return: The corresponding id.
|
||||
"""
|
||||
from qtpy.QtCore import Qt # Avoid importing Qt too early."""
|
||||
|
||||
return getattr(Qt, f"Key_{name}")
|
||||
|
||||
|
||||
class Key(BaseModel): # type: ignore[misc]
|
||||
"""Represents a list of key codes, with optionally a name."""
|
||||
"""Represent a list of key codes, with optionally a name."""
|
||||
|
||||
ids: conset(PositiveInt, min_length=1) # type: ignore[valid-type]
|
||||
name: Optional[str] = None
|
||||
@ -58,6 +87,7 @@ class Key(BaseModel): # type: ignore[misc]
|
||||
self.ids = set(ids)
|
||||
|
||||
def match(self, key_id: int) -> bool:
|
||||
"""Return whether a given key id matches this key."""
|
||||
m = key_id in self.ids
|
||||
|
||||
if m:
|
||||
@ -135,6 +165,7 @@ class Config(BaseModel): # type: ignore[misc]
|
||||
"""General Manim Slides config."""
|
||||
|
||||
keys: Keys = Field(default_factory=Keys)
|
||||
"""The key mapping."""
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, path: Path) -> "Config":
|
||||
@ -142,11 +173,16 @@ class Config(BaseModel): # type: ignore[misc]
|
||||
return cls.model_validate(rtoml.load(path)) # type: ignore
|
||||
|
||||
def to_file(self, path: Path) -> None:
|
||||
"""Dump the configuration to a file."""
|
||||
"""Dump this configuration to a file."""
|
||||
rtoml.dump(self.model_dump(), path, pretty=True)
|
||||
|
||||
def merge_with(self, other: "Config") -> "Config":
|
||||
"""Merge with another config."""
|
||||
"""
|
||||
Merge with another config.
|
||||
|
||||
:param other: The other config to be merged with.
|
||||
:return: This config, updated.
|
||||
"""
|
||||
self.keys = self.keys.merge_with(other.keys)
|
||||
return self
|
||||
|
||||
@ -155,12 +191,19 @@ class BaseSlideConfig(BaseModel): # type: ignore
|
||||
"""Base class for slide config."""
|
||||
|
||||
loop: bool = False
|
||||
"""Whether this slide should loop."""
|
||||
auto_next: bool = False
|
||||
"""Whether this slide is skipped upon completion."""
|
||||
playback_rate: float = 1.0
|
||||
"""The speed at which the animation is played (1.0 is normal)."""
|
||||
reversed_playback_rate: float = 1.0
|
||||
"""The speed at which the reversed animation is played."""
|
||||
notes: str = ""
|
||||
"""The notes attached to this slide."""
|
||||
dedent_notes: bool = True
|
||||
"""Whether to automatically remove any leading indentation in the notes."""
|
||||
skip_animations: bool = False
|
||||
src: Optional[FilePath] = None
|
||||
|
||||
@classmethod
|
||||
def wrapper(cls, arg_name: str) -> Callable[..., Any]:
|
||||
@ -172,7 +215,11 @@ class BaseSlideConfig(BaseModel): # type: ignore
|
||||
The wrapped function must follow two criteria:
|
||||
- its last parameter must be ``**kwargs`` (or equivalent);
|
||||
- and its second last parameter must be ``<arg_name>``.
|
||||
|
||||
:param arg_name: The name of the argument.
|
||||
:return: The wrapped function.
|
||||
"""
|
||||
# TODO: improve docs and (maybe) type-hints too
|
||||
|
||||
def _wrapper_(fun: Callable[..., Any]) -> Callable[..., Any]:
|
||||
@wraps(fun)
|
||||
@ -205,21 +252,27 @@ class BaseSlideConfig(BaseModel): # type: ignore
|
||||
return _wrapper_
|
||||
|
||||
@model_validator(mode="after")
|
||||
@classmethod
|
||||
def apply_dedent_notes(
|
||||
cls, base_slide_config: "BaseSlideConfig"
|
||||
self,
|
||||
) -> "BaseSlideConfig":
|
||||
if base_slide_config.dedent_notes:
|
||||
base_slide_config.notes = dedent(base_slide_config.notes)
|
||||
"""
|
||||
Remove indentation from notes, if specified.
|
||||
|
||||
return base_slide_config
|
||||
:return: The config, optionally modified.
|
||||
"""
|
||||
if self.dedent_notes:
|
||||
self.notes = dedent(self.notes)
|
||||
|
||||
return self
|
||||
|
||||
|
||||
class PreSlideConfig(BaseSlideConfig):
|
||||
"""Slide config to be used prior to rendering."""
|
||||
|
||||
start_animation: int
|
||||
"""The index of the first animation."""
|
||||
end_animation: int
|
||||
"""The index after the last animation."""
|
||||
|
||||
@classmethod
|
||||
def from_base_slide_config_and_animation_indices(
|
||||
@ -228,6 +281,13 @@ class PreSlideConfig(BaseSlideConfig):
|
||||
start_animation: int,
|
||||
end_animation: int,
|
||||
) -> "PreSlideConfig":
|
||||
"""
|
||||
Create a config from a base config and animation indices.
|
||||
|
||||
:param base_slide_config: The base config.
|
||||
:param start_animation: The index of the first animation.
|
||||
:param end_animation: The index after the last animation.
|
||||
"""
|
||||
return cls(
|
||||
start_animation=start_animation,
|
||||
end_animation=end_animation,
|
||||
@ -237,30 +297,48 @@ class PreSlideConfig(BaseSlideConfig):
|
||||
@field_validator("start_animation", "end_animation")
|
||||
@classmethod
|
||||
def index_is_posint(cls, v: int) -> int:
|
||||
"""
|
||||
Validate that animation indices are positive integers.
|
||||
|
||||
:param v: An animation index.
|
||||
:return: The animation index, if valid.
|
||||
"""
|
||||
if v < 0:
|
||||
raise ValueError("Animation index (start or end) cannot be negative")
|
||||
return v
|
||||
|
||||
@model_validator(mode="after")
|
||||
@classmethod
|
||||
def start_animation_is_before_end(
|
||||
cls, pre_slide_config: "PreSlideConfig"
|
||||
self,
|
||||
) -> "PreSlideConfig":
|
||||
if pre_slide_config.start_animation >= pre_slide_config.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(...)`."
|
||||
)
|
||||
"""
|
||||
Validate that start and end animation indices satisfy 'start < end'.
|
||||
|
||||
:return: The config, if indices are valid.
|
||||
"""
|
||||
raise ValueError(
|
||||
"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
|
||||
def slides_slice(self) -> slice:
|
||||
@ -271,7 +349,9 @@ class SlideConfig(BaseSlideConfig):
|
||||
"""Slide config to be used after rendering."""
|
||||
|
||||
file: FilePath
|
||||
"""The file containing the animation."""
|
||||
rev_file: FilePath
|
||||
"""The file containing the reversed animation."""
|
||||
|
||||
@classmethod
|
||||
def from_pre_slide_config_and_files(
|
||||
@ -281,13 +361,22 @@ class SlideConfig(BaseSlideConfig):
|
||||
|
||||
|
||||
class PresentationConfig(BaseModel): # type: ignore[misc]
|
||||
"""Presentation config that contains all necessary information for a presentation."""
|
||||
|
||||
slides: list[SlideConfig] = Field(min_length=1)
|
||||
"""The non-empty list of slide configs."""
|
||||
resolution: tuple[PositiveInt, PositiveInt] = (1920, 1080)
|
||||
"""The resolution of the animation files."""
|
||||
background_color: Color = "black"
|
||||
"""The background color of the animation files."""
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, path: Path) -> "PresentationConfig":
|
||||
"""Read a presentation configuration from a file."""
|
||||
"""
|
||||
Read a presentation configuration from a file.
|
||||
|
||||
:param path: The path where the config is read from.
|
||||
"""
|
||||
with open(path) as f:
|
||||
obj = json.load(f)
|
||||
|
||||
@ -304,7 +393,11 @@ class PresentationConfig(BaseModel): # type: ignore[misc]
|
||||
return cls.model_validate(obj) # type: ignore
|
||||
|
||||
def to_file(self, path: Path) -> None:
|
||||
"""Dump the presentation configuration to a file."""
|
||||
"""
|
||||
Dump the presentation configuration to a file.
|
||||
|
||||
:param path: The path to save this config.
|
||||
"""
|
||||
with open(path, "w") as f:
|
||||
f.write(self.model_dump_json(indent=2))
|
||||
|
||||
@ -315,7 +408,14 @@ class PresentationConfig(BaseModel): # type: ignore[misc]
|
||||
include_reversed: bool = True,
|
||||
prefix: str = "",
|
||||
) -> None:
|
||||
"""Copy the files to a given directory."""
|
||||
"""
|
||||
Copy the files to a given directory and return the corresponding configuration.
|
||||
|
||||
:param folder: The folder that will contain the animation files.
|
||||
:param use_cached: Whether caching should be used to avoid copies when possible.
|
||||
:param include_reversed: Whether to also copy reversed animation to the folder.
|
||||
:param prefix: Optional prefix added to each file name.
|
||||
"""
|
||||
for slide_config in self.slides:
|
||||
file = slide_config.file
|
||||
rev_file = slide_config.rev_file
|
||||
@ -327,4 +427,6 @@ class PresentationConfig(BaseModel): # type: ignore[misc]
|
||||
shutil.copy(file, dest)
|
||||
|
||||
if include_reversed and (not use_cached or not rev_dest.exists()):
|
||||
# TODO: if include_reversed is False, then rev_dev will likely not exist
|
||||
# and this will cause an issue when decoding.
|
||||
shutil.copy(rev_file, rev_dest)
|
@ -1,4 +1,8 @@
|
||||
"""Manim Slides' defaults."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
FOLDER_PATH: Path = Path("./slides")
|
||||
"""Folder where slides are stored."""
|
||||
CONFIG_PATH: Path = Path(".manim-slides.toml")
|
||||
"""Path to local Manim Slides config."""
|
@ -31,7 +31,11 @@ HIGHLIGHTED_KEYWORDS = [ # these keywords are highlighted specially
|
||||
|
||||
|
||||
def make_logger() -> logging.Logger:
|
||||
"""Make a logger similar to the one used by Manim."""
|
||||
"""
|
||||
Make a logger similar to the one used by Manim.
|
||||
|
||||
:return: The logger instance.
|
||||
"""
|
||||
RichHandler.KEYWORDS = HIGHLIGHTED_KEYWORDS
|
||||
rich_handler = RichHandler(
|
||||
show_time=True,
|
||||
@ -47,6 +51,5 @@ def make_logger() -> logging.Logger:
|
||||
return logger
|
||||
|
||||
|
||||
make_logger()
|
||||
|
||||
logger = logging.getLogger("manim-slides")
|
||||
logger = make_logger()
|
||||
"""The logger instance used across this project."""
|
@ -1,16 +1,23 @@
|
||||
import hashlib
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from collections.abc import Iterator
|
||||
from multiprocessing import Pool
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
import av
|
||||
from tqdm import tqdm
|
||||
|
||||
from .logger import logger
|
||||
|
||||
|
||||
def concatenate_video_files(files: list[Path], dest: Path) -> None:
|
||||
"""Concatenate multiple video files into one."""
|
||||
if len(files) == 1:
|
||||
shutil.copy(files[0], dest)
|
||||
return
|
||||
|
||||
def _filter(files: list[Path]) -> Iterator[Path]:
|
||||
"""Patch possibly empty video files."""
|
||||
@ -89,8 +96,9 @@ def link_nodes(*nodes: av.filter.context.FilterContext) -> None:
|
||||
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`."""
|
||||
src, dest = src_and_dest
|
||||
with (
|
||||
av.open(str(src)) as input_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):
|
||||
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))
|
||||
|
||||
for packet in output_stream.encode():
|
||||
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)
|
@ -116,7 +116,7 @@ class ManimSlidesMagic(Magics): # type: ignore
|
||||
file) will be moved relative to the video locations. Use-cases include building
|
||||
documentation with Sphinx and JupyterBook. See also the
|
||||
:mod:`Manim Slides directive for Sphinx
|
||||
<manim_slides.docs.manim_slides_directive>`.
|
||||
<manim_slides.sphinxext.manim_slides_directive>`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
@ -1,10 +1,12 @@
|
||||
__all__ = [
|
||||
"""Slides module with logic to either import ManimCE or ManimGL."""
|
||||
|
||||
__all__ = (
|
||||
"API_NAME",
|
||||
"MANIM",
|
||||
"MANIMGL",
|
||||
"Slide",
|
||||
"ThreeDSlide",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
import os
|
||||
@ -14,10 +16,10 @@ import sys
|
||||
class ManimApiNotFoundError(ImportError):
|
||||
"""Error raised if specified manim API could be imported."""
|
||||
|
||||
_msg = "Could not import the specified manim API"
|
||||
_msg = "Could not import the specified manim API: `{api}`."
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(self._msg)
|
||||
def __init__(self, api: str) -> None:
|
||||
super().__init__(self._msg.format(api=api))
|
||||
|
||||
|
||||
API_NAMES = {
|
||||
@ -26,9 +28,12 @@ API_NAMES = {
|
||||
"manimlib": "manimlib",
|
||||
"manimgl": "manimlib",
|
||||
}
|
||||
"""Allowed values for API."""
|
||||
|
||||
MANIM_API: str = "MANIM_API"
|
||||
"""API environ variable name."""
|
||||
FORCE_MANIM_API: str = "FORCE_" + MANIM_API
|
||||
"""FORCE API environ variable name."""
|
||||
|
||||
API: str = os.environ.get(MANIM_API, "manim").lower()
|
||||
|
||||
@ -53,11 +58,14 @@ if MANIM:
|
||||
try:
|
||||
from .manim import Slide, ThreeDSlide
|
||||
except ImportError as e:
|
||||
raise ManimApiNotFoundError from e
|
||||
raise ManimApiNotFoundError("manim") from e
|
||||
elif MANIMGL:
|
||||
try:
|
||||
from .manimlib import Slide, ThreeDSlide
|
||||
except ImportError as e:
|
||||
raise ManimApiNotFoundError from e
|
||||
raise ManimApiNotFoundError("manimlib") from e
|
||||
else:
|
||||
raise ManimApiNotFoundError
|
||||
raise ValueError(
|
||||
"This error should never occur. "
|
||||
"Please report an issue on GitHub if you encounter it."
|
||||
)
|
||||
|
@ -1,6 +1,8 @@
|
||||
"""Base class for the Slide class."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
__all__ = ["BaseSlide"]
|
||||
__all__ = ("BaseSlide",)
|
||||
|
||||
import platform
|
||||
import shutil
|
||||
@ -15,10 +17,15 @@ from typing import (
|
||||
import numpy as np
|
||||
from tqdm import tqdm
|
||||
|
||||
from ..config import BaseSlideConfig, PresentationConfig, PreSlideConfig, SlideConfig
|
||||
from ..defaults import FOLDER_PATH
|
||||
from ..logger import logger
|
||||
from ..utils import concatenate_video_files, merge_basenames, reverse_video_file
|
||||
from ..core.config import (
|
||||
BaseSlideConfig,
|
||||
PresentationConfig,
|
||||
PreSlideConfig,
|
||||
SlideConfig,
|
||||
)
|
||||
from ..core.defaults import FOLDER_PATH
|
||||
from ..core.logger import logger
|
||||
from ..core.utils import concatenate_video_files, merge_basenames, reverse_video_file
|
||||
from . import MANIM
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -36,6 +43,8 @@ class BaseSlide:
|
||||
disable_caching: bool = False
|
||||
flush_cache: bool = False
|
||||
skip_reversing: bool = False
|
||||
max_duration_before_split_reverse: float | None = 4.0
|
||||
num_processes: int | None = None
|
||||
|
||||
def __init__(
|
||||
self, *args: Any, output_folder: Path = FOLDER_PATH, **kwargs: Any
|
||||
@ -303,7 +312,7 @@ class BaseSlide:
|
||||
: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>`,
|
||||
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::
|
||||
@ -346,6 +355,11 @@ class BaseSlide:
|
||||
``manim-slides convert --to=pptx``.
|
||||
:param dedent_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:
|
||||
Keyword arguments passed to
|
||||
:meth:`Scene.next_section<manim.scene.scene.Scene.next_section>`,
|
||||
@ -469,6 +483,18 @@ class BaseSlide:
|
||||
|
||||
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
|
||||
|
||||
@ -491,7 +517,7 @@ class BaseSlide:
|
||||
)
|
||||
)
|
||||
|
||||
def _save_slides(
|
||||
def _save_slides( # noqa: C901
|
||||
self,
|
||||
use_cache: bool = True,
|
||||
flush_cache: bool = False,
|
||||
@ -530,14 +556,18 @@ class BaseSlide:
|
||||
|
||||
for pre_slide_config in tqdm(
|
||||
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,
|
||||
ascii=True if platform.system() == "Windows" else None,
|
||||
disable=not self._show_progress_bar,
|
||||
unit=" slides",
|
||||
):
|
||||
if pre_slide_config.skip_animations:
|
||||
continue
|
||||
slide_files = files[pre_slide_config.slides_slice]
|
||||
if pre_slide_config.src:
|
||||
slide_files = [pre_slide_config.src]
|
||||
else:
|
||||
slide_files = files[pre_slide_config.slides_slice]
|
||||
|
||||
try:
|
||||
file = merge_basenames(slide_files)
|
||||
@ -557,7 +587,15 @@ class BaseSlide:
|
||||
if skip_reversing:
|
||||
rev_file = dst_file
|
||||
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(
|
||||
SlideConfig.from_pre_slide_config_and_files(
|
||||
|
@ -1,3 +1,5 @@
|
||||
"""Manim's implementation of the Slide class."""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
@ -5,30 +7,41 @@ from manim import Scene, ThreeDScene, config
|
||||
from manim.renderer.opengl_renderer import OpenGLRenderer
|
||||
from manim.utils.color import rgba_to_color
|
||||
|
||||
from ..config import BaseSlideConfig
|
||||
from ..core.config import BaseSlideConfig
|
||||
from .base import BaseSlide
|
||||
|
||||
|
||||
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.
|
||||
|
||||
: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.
|
||||
:cvar bool disable_caching: :data:`False`: Whether to disable the use of
|
||||
cached animation files.
|
||||
:cvar bool flush_cache: :data:`False`: Whether to flush the cache.
|
||||
|
||||
Unlike with Manim, flushing is performed before rendering.
|
||||
:cvar bool skip_reversing: :data:`False`: Whether to generate reversed animations.
|
||||
|
||||
If set to :data:`False`, and no cached reversed animation
|
||||
exists (or caching is disabled) for a given slide,
|
||||
then the reversed animation will be simply the same
|
||||
as the original one, i.e., ``rev_file = file``,
|
||||
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:
|
||||
|
@ -1,3 +1,5 @@
|
||||
"""ManimGL's implementation of the Slide class."""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, ClassVar, Optional
|
||||
|
||||
|
0
manim_slides/sphinxext/__init__.py
Normal file
0
manim_slides/sphinxext/__init__.py
Normal file
@ -40,7 +40,7 @@ First, you must include the directive in the Sphinx configuration file:
|
||||
|
||||
extensions = [
|
||||
# ...
|
||||
"manim_slides.docs.manim_slides_directive",
|
||||
"manim_slides.sphinxext.manim_slides_directive",
|
||||
]
|
||||
|
||||
Its basic usage that allows processing **inline content**
|
@ -2,6 +2,39 @@
|
||||
build-backend = "hatchling.build"
|
||||
requires = ["hatchling", "hatch-fancy-pypi-readme"]
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
{include-group = "docs"},
|
||||
{include-group = "tests"},
|
||||
"bump-my-version>=0.20.3",
|
||||
"pre-commit>=3.5.0",
|
||||
]
|
||||
docs = [
|
||||
"manim-slides[magic,manim,pyqt6,sphinx-directive]",
|
||||
"furo>=2023.5.20",
|
||||
"ipykernel>=6.25.1",
|
||||
"myst-parser>=2.0.0",
|
||||
"nbsphinx>=0.9.2",
|
||||
"pandoc>=2.3",
|
||||
"pygments<2.19", # See: https://github.com/ManimCommunity/manim/issues/4104
|
||||
"sphinx>=7.0.1",
|
||||
"sphinxcontrib-programoutput>=0.18",
|
||||
"sphinx-design>=0.6.1",
|
||||
"sphinx-click>=4.4.0",
|
||||
"sphinx-copybutton>=0.5.1",
|
||||
"sphinxext-opengraph>=0.7.5",
|
||||
]
|
||||
tests = [
|
||||
"importlib-metadata>=8.6.1;python_version<'3.10'",
|
||||
"manim-slides[full,manimgl,pyqt6,pyside6,sphinx-directive]",
|
||||
"pytest>=7.4.0",
|
||||
"pytest-cov>=4.1.0",
|
||||
"pytest-env>=0.8.2",
|
||||
"pytest-missing-modules>=0.1.0",
|
||||
"pytest-qt>=4.2.0",
|
||||
"setuptools>=73.0.1",
|
||||
]
|
||||
|
||||
[project]
|
||||
authors = [{name = "Jérome Eertmans", email = "jeertmans@icloud.com"}]
|
||||
classifiers = [
|
||||
@ -42,21 +75,6 @@ name = "manim-slides"
|
||||
requires-python = ">=3.9"
|
||||
|
||||
[project.optional-dependencies]
|
||||
docs = [
|
||||
"manim-slides[magic,manim,pyqt6,sphinx-directive]",
|
||||
"furo>=2023.5.20",
|
||||
"ipykernel>=6.25.1",
|
||||
"myst-parser>=2.0.0",
|
||||
"nbsphinx>=0.9.2",
|
||||
"pandoc>=2.3",
|
||||
"pygments<2.19", # See: https://github.com/ManimCommunity/manim/issues/4104
|
||||
"sphinx>=7.0.1",
|
||||
"sphinxcontrib-programoutput>=0.18",
|
||||
"sphinx-design>=0.6.1",
|
||||
"sphinx-click>=4.4.0",
|
||||
"sphinx-copybutton>=0.5.1",
|
||||
"sphinxext-opengraph>=0.7.5",
|
||||
]
|
||||
full = [
|
||||
"manim-slides[magic,manim,sphinx-directive]",
|
||||
]
|
||||
@ -68,17 +86,9 @@ pyqt6-full = ["manim-slides[full,pyqt6]"]
|
||||
pyside6 = ["pyside6>=6.6.1,!=6.8.1.1"]
|
||||
pyside6-full = ["manim-slides[full,pyside6]"]
|
||||
sphinx-directive = ["docutils>=0.20.1", "manim-slides[manim]"]
|
||||
tests = [
|
||||
"manim-slides[full,manimgl,pyqt6,pyside6,sphinx-directive]",
|
||||
"pytest>=7.4.0",
|
||||
"pytest-cov>=4.1.0",
|
||||
"pytest-env>=0.8.2",
|
||||
"pytest-missing-modules>=0.1.0",
|
||||
"pytest-qt>=4.2.0",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
manim-slides = "manim_slides.__main__:cli"
|
||||
manim-slides = "manim_slides.cli.commands:main"
|
||||
|
||||
[project.urls]
|
||||
Changelog = "https://github.com/jeertmans/manim-slides/releases"
|
||||
@ -91,7 +101,7 @@ Repository = "https://github.com/jeertmans/manim-slides"
|
||||
allow_dirty = false
|
||||
commit = true
|
||||
commit_args = ""
|
||||
current_version = "5.4.2"
|
||||
current_version = "5.5.1"
|
||||
ignore_missing_version = false
|
||||
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+))?'
|
||||
@ -198,10 +208,9 @@ filterwarnings = [
|
||||
]
|
||||
|
||||
[tool.ruff]
|
||||
extend-exclude = ["manim_slides/resources.py"]
|
||||
extend-exclude = ["manim_slides/cli/resources.py"]
|
||||
extend-include = ["*.ipynb"]
|
||||
line-length = 88
|
||||
target-version = "py39"
|
||||
|
||||
[tool.ruff.lint]
|
||||
extend-ignore = [
|
||||
@ -224,10 +233,3 @@ isort = {known-first-party = ["manim_slides", "tests"]}
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"docs/source/reference/magic_example.ipynb" = ["F403", "F405"]
|
||||
"tests/test_slide.py" = ["N801"]
|
||||
|
||||
[tool.uv]
|
||||
dev-dependencies = [
|
||||
"bump-my-version>=0.20.3",
|
||||
"pre-commit>=3.5.0",
|
||||
"setuptools>=73.0.1",
|
||||
]
|
||||
|
@ -314,6 +314,26 @@ class TestSlide:
|
||||
self.play(dot.animate.move_to(LEFT))
|
||||
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:
|
||||
@assert_renders
|
||||
class _(CESlide):
|
||||
@ -569,6 +589,51 @@ class TestSlide:
|
||||
|
||||
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:
|
||||
@assert_constructs
|
||||
class _(CESlide):
|
||||
|
298
uv.lock
generated
298
uv.lock
generated
@ -1,4 +1,5 @@
|
||||
version = 1
|
||||
revision = 1
|
||||
requires-python = ">=3.9"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.10' and python_full_version < '3.13'",
|
||||
@ -78,11 +79,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "attrs"
|
||||
version = "24.3.0"
|
||||
version = "25.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/48/c8/6260f8ccc11f0917360fc0da435c5c9c7504e3db174d5a12a1494887b045/attrs-24.3.0.tar.gz", hash = "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff", size = 805984 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/49/7c/fdf464bcc51d23881d110abd74b512a42b3d5d376a55a831b44c603ae17f/attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e", size = 810562 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/89/aa/ab0f7891a01eeb2d2e338ae8fecbe57fcebea1a24dbb64d45801bfab481d/attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308", size = 63397 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/30/d4986a882011f9df997a55e6becd864812ccfcd821d64aac8570ee39f719/attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a", size = 63152 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -224,7 +225,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "bump-my-version"
|
||||
version = "0.29.0"
|
||||
version = "0.30.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
@ -236,9 +237,9 @@ dependencies = [
|
||||
{ name = "tomlkit" },
|
||||
{ name = "wcmatch" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a1/53/277e3748f0e45758210e971ed2c54c93f1f768a5bc9c66f8cb58a604a415/bump_my_version-0.29.0.tar.gz", hash = "sha256:e4149ed63b4772f5868b3fcabb8fa5e1191b8abae6d35effd0be980d4b0f55e3", size = 1013425 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e7/4f/57eda33958c5820b462c4c262bc18dc374dca6312bbb63f95606172200cb/bump_my_version-0.30.0.tar.gz", hash = "sha256:d53e784c73abc4bb5759e296f510bc71878e1df078eb525542ec9291b5ceb195", size = 1062228 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/00/59/0c78849320b0dbe17f7e67a125b6c4adcbba069c4993bf4064aea42add13/bump_my_version-0.29.0-py3-none-any.whl", hash = "sha256:6566ab25bd3eeeec109f4ac7e4464227a3ac1fd57f847d259a24800423cd9037", size = 52132 },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/9b/965ad61f85cbde14694516b02dcd38ec0c5cf7132fe33a30fddb4d8b0803/bump_my_version-0.30.0-py3-none-any.whl", hash = "sha256:b0d683a1cb97fbc2f46adf8eb39ff1f0bdd72866c3583fe01f9837d6f031e5e3", size = 55257 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -407,7 +408,7 @@ name = "click"
|
||||
version = "8.1.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "platform_system == 'Windows'" },
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 }
|
||||
wheels = [
|
||||
@ -823,51 +824,51 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "fonttools"
|
||||
version = "4.55.4"
|
||||
version = "4.55.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/13/8d/8912cdde6a2b4c19ced69ea5790cd17d1c095a3c0104c1c936a1de804a64/fonttools-4.55.4.tar.gz", hash = "sha256:9598af0af85073659facbe9612fcc56b071ef2f26e3819ebf9bd8c5d35f958c5", size = 3498560 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/55/55/3b1566c6186a5e58a17a19ad63195f87c6ca4039ef10ff5318a1b9fc5639/fonttools-4.55.7.tar.gz", hash = "sha256:6899e3d97225a8218f525e9754da0376e1c62953a0d57a76c5abaada51e0d140", size = 3458372 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/06/84/da14576ce30bbed3c882bfc4de84d2e4348c65b1382688812357cb21416a/fonttools-4.55.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3b332ea7b7f5f3d99f9bc5a28a23c3824ae72711abf7c4e1d62fa21699fdebe7", size = 2774346 },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/1d/3da7148a5552871c5dbe368de755602a0df0672e339edc133ed3e9704f2a/fonttools-4.55.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d8f925909256e62152e7c3e192655dbca3ab8c3cdef7d7b436732727e80feb6", size = 2302782 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/9d/6b5be027fbfc8eab302d89608fc158b37531f3116506062e0d7183546465/fonttools-4.55.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a58af9b98e39bcd773aa352b4512be79b472830b799cb1d3cafb2b4796b71cd", size = 4584269 },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/6f/c5ccd4c8f90fd7f6964a1b8981e58f5cc6361acedb0a473a8dae4e1ac3c6/fonttools-4.55.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:736d750d2ab4523067d8058e5294b40b01f2eee521e0fd401bec0d5e21e80b12", size = 4626917 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/ea/53c4c75212b30d257e0865d6905eb6747ec7450b414caff742ff031eb758/fonttools-4.55.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1a9a2e7e8a9d3bfa9589db3e6c4e4c127fec252493924b2f87a67a25f9430057", size = 4581220 },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/4f/05d9bf9595d75ece4d65e52bd994431cff575e11f00a9444ac8b2781091e/fonttools-4.55.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:87824368e994af34a95cb4279a8c711e51974b3c28d052d39d768531cc9e8e59", size = 4750636 },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/21/d91c8d4583e0f6ee8e08868d5ab3de44f78af8da37d47e265f5b433bd0e2/fonttools-4.55.4-cp310-cp310-win32.whl", hash = "sha256:6c36dcbfe64bce38c4d4f1d436cdc6445e969eee96eb98d98be603b5abf8c3f2", size = 2177793 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/33/c26363a57f5e766f38c84fb4e34d26d32a26398804f72e12a00c007166a1/fonttools-4.55.4-cp310-cp310-win_amd64.whl", hash = "sha256:3c53a467e5cf629acdbefc98b0f554859539fb6447bbeae4117b9ab51464ccc5", size = 2222313 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/22/cf0707f681486bf91f998c3a6a6492d806d1cf09445ce01b26a724917439/fonttools-4.55.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1605b28165c785bf26c2cbd205dc0822463e3f9f56f187049eb214dc5f4a59cb", size = 2775483 },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/79/11a07753a7b9ef46eaaa5e85b72558095713060aeca1393057a081fb21e3/fonttools-4.55.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d851d8b2fdb676507365d1430c3285d62c4039d0d7760d8cf2f2e5ea3aa19d73", size = 2303701 },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/67/173994471ddb0ff8cd45b0a2ff9fa03416152ca90bd14d1cbe1ff75fb66c/fonttools-4.55.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fb3cf1cddf08cec0338f238f950cb76fabab23a324a579e3e1f9b2ef2578329", size = 4891469 },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/b9/22e8be0fceaed86187ba35a1035b309e47575c68ee6ace3b66f146300f43/fonttools-4.55.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddd3208b06186ca00fbd329c0d0fed5ba209c99017cc46e2c4ea42233c2fbd00", size = 4920672 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/15/ed0f0a9d303419e7c885b3a71bfe70bb71c8f964e5b1d515056e38551c69/fonttools-4.55.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9bd98819cb585a894dda9dcb337afeb2601abf17da17de7bfbfc1bc2e4a062c7", size = 4899903 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/02/bd0da57dac3f44f37898b058659cf3beedbfd89b7d0f4b10761c9602dc1b/fonttools-4.55.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4877376c10541e8dccf14876c8476d5082338fa5d21103894894382cc245144b", size = 5067979 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/b9/c232b07c0ecaba9e522695780ca8d711b099bf87889a19a6b35a4ebfde90/fonttools-4.55.4-cp311-cp311-win32.whl", hash = "sha256:3a5e466894ec6d8a009b0eb8e02a6eb26959a318d5b7a906280c26bdadce6423", size = 2176681 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/50/2aa1cf2492e6aded4320122aed690268e97076aba1f418c0b4c68fb11a50/fonttools-4.55.4-cp311-cp311-win_amd64.whl", hash = "sha256:f595129e6f9c6402965d6295fe8c18c1945d27af0f90bdb52ff426226e647afc", size = 2223239 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/ee/c7f06da45f60c076677291470599eb9f8aae6605cbfbebbcb8ee12428e26/fonttools-4.55.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b3db72ad2d26a0e9ec694cbfb4485a8da9c095d29f66561cf935dbd19f3efcea", size = 2769913 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/a9/19aa6a9685d0bb285678850bfa22365a8376c590a7aaacc9f03d3a43beaa/fonttools-4.55.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:87717808fd5953588c3ffaf512e8cab0e43c09c1da04e42ba87fa4c07d8170c7", size = 2301168 },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/63/88740f4333008336844aadbc9f7ef85d50e2eed779a5c33e13907a2439eb/fonttools-4.55.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f49dac626ad5bc1a0147b88e6157e3211fd440d00007f0da6c9e5f91dd5cb88e", size = 4806195 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/fa/1d103fe6e9bf174afd1c04772ca4f88e8f577f44d37b7cc8644fe5ff2620/fonttools-4.55.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2d0ac8656ada8b604ae5da15d9aa075232f2181b95b51a3a2a55195222df7e7", size = 4877282 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/53/1cdd447f30598950e4bf8a2de8cd1f6573e6cb34b726cf23713a3cd8fb1e/fonttools-4.55.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:013c8b45873fa77a4ff6d25e43fecf1046cb7e8c6b32f1843117f98f3f8eac60", size = 4784688 },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/21/edfdcd85c1cce918d410909759a8db667f95bf3faed88141b1abfa2cefe1/fonttools-4.55.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:94caad375d254a0332926512f06791f5e66c24a913ebecd6178b14f61d27c62f", size = 5012253 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/e7/7c16717b75e40f735e01d899ee152a0573e90be0e6b8fc2d47c16ba8239c/fonttools-4.55.4-cp312-cp312-win32.whl", hash = "sha256:cb3eb4bf3a0c4e431e1ccab7a33ef4f1bb32657133fff4a61dc4fcbd54b94d29", size = 2165283 },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/ff/85d1c1d396a3ceaabcf7cb543da56d2223d9b76429bafd6c87f4a4e880df/fonttools-4.55.4-cp312-cp312-win_amd64.whl", hash = "sha256:6914269f6ff6b20c6b5a9b19d0b752880bd8ee218d9a7d6afe9960bbf1922d98", size = 2212080 },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/9b/e7505e7f08c291ab28e6b5c7ae9fe92aab10f5c4b3666fc67eb59f6e454b/fonttools-4.55.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:699dd32da7258a89939567a3d71b3f8decf84da54488a2526693f0d981a76479", size = 2757277 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/15/a26ae0e5be690038cf1d62277f1007282d4d355dc30dbf0a95224fe69b0e/fonttools-4.55.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f374b18ac04fbf78f20940418aee7882be3cdcb328ded80e16c3356499f64cf", size = 2294678 },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/6a/20863c8ddf4dc7fd290b5ffddfc83d5918447523001b67a2dc81a0899b0d/fonttools-4.55.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b18792529ca3c24259090b6faa60bd0bdfcc4a06312e8f06d6fccab007f07193", size = 4784624 },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/2f/c74fa21fddd6a4c22c80f2f86820a0c960a5c0f8f46407bc9c1e1c9b9f50/fonttools-4.55.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e91d25261ebc9ff2143b95e6272f46b9f28e260b8f40feda07c80b66ff7e61d", size = 4856618 },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/37/1e9f1cb3b2454adac0b5fe85e940ea8d4eb174a9338e47020ec7d3cf1057/fonttools-4.55.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2695781a897395d03504fd24b60c944726b5e7b7af9ea3d922f7319d70c6fc37", size = 4765002 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/fa/36b815132a71b9df13e9c52cd198194b48eb31f9a6d041f3ec6476d8b74f/fonttools-4.55.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21de3ef5b8e5361fd01d6aef2c09dda4ede139d6b3a1f5cf621d6bea48840dfd", size = 4985906 },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/9a/51eb1cdc08d0883c40a3ea6d9a8ecd862bac587371bc92e0f35315688994/fonttools-4.55.4-cp313-cp313-win32.whl", hash = "sha256:0ef33fda14e39aabb892a18ed16805b0b5b4e8a801fd1815a694be9dc7f30024", size = 2163286 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/a0/6fdeb063dfb401e3efc342ef8ff6cd9e290d9895c3777dbc3850842eb1ad/fonttools-4.55.4-cp313-cp313-win_amd64.whl", hash = "sha256:e953b1614e32b6da828ae7659c8f330a593b6c4b7a4a31f8f63c01b12f0d3680", size = 2209022 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/9d/09954e57e4c06237ba0dee2565feeb31e49113320504b93e759c967bcb0e/fonttools-4.55.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:dcc08dcb2be554073a72f3a8cecbc4226602ccdd0187b8f37a03a731cb931864", size = 2777045 },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/68/d8416c7709a30b7b3eff0b7d51fd366508ebe2d22f1f20e7c40029ce0c48/fonttools-4.55.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7b9b414ce50f09cb692e97ff82b041ea1a21076ed9c1923206560c15ce9ad03a", size = 2304043 },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/21/2737e0e65e51c4f5cc07cfb2f23afee20096ed5a4d596a9f7ad9bcf3e148/fonttools-4.55.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8807a1357d434ef1f4aed9bdfee7077f52dbc040b18ac98f6e417f69a48afbb5", size = 4589859 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/2e/e3d7f01f03424aa7b8cbf2b99825a28ba9df556bbb51b31464185d870682/fonttools-4.55.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93a3ec7cba2e71edbc999ce3d48d34ef87cc30a36af6ff90dfc0dbc131f705fc", size = 4634830 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/6c/536159ebc77f88504124e79091c40c0663f9f048585ff7be8ab582204045/fonttools-4.55.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2964b9fe6b4a892a41a8a517bac232072a821cf2288fad1d19c6c1d19c34b0dd", size = 4583331 },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/d6/3c59783c4beab00d13af1bff3121f354c6c1b1fc41f22e18ac2d08f2375a/fonttools-4.55.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0b9f4f032295adeb39a8c0eefb08a7b1e90f4b7571506e5d84bb923a7afa8247", size = 4753600 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/7b/c55f7a001722bbb52b9eb8506ca1c5fe797bfc5434ad60df8945a649c572/fonttools-4.55.4-cp39-cp39-win32.whl", hash = "sha256:ee4e86280dc637a17e926cbdd32c2de148c013c3468777ae6e94c8b4449c8e93", size = 2178356 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/bf/e664a9c3d61cb1fd91cdd2d332d8039292d8d4a07d81b8675026cc2d4556/fonttools-4.55.4-cp39-cp39-win_amd64.whl", hash = "sha256:82a03920f0f524abab375dcfac8926d9596986503ee00ae435bdd71b1498f214", size = 2222836 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/5d/29b126e12df844432e188d19e74f47c2578fa5a72a122b4f41819e1e0923/fonttools-4.55.4-py3-none-any.whl", hash = "sha256:d07ad8f31038c6394a0945752458313367a0ef8125d284ee59f99e68393a3c2d", size = 1111964 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/5c/ce2fce845af9696d043ac912f15b9fac4b9002fcd9ff66b80aa513a6c43f/fonttools-4.55.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c2680a3e6e2e2d104a7ea81fb89323e1a9122c23b03d6569d0768887d0d76e69", size = 2752048 },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/9b/f7f9409adcf22763263c6327d2d31d538babd9ad2d63d1732c9e85d60a78/fonttools-4.55.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a7831d16c95b60866772a15fdcc03772625c4bb6d858e0ad8ef3d6e48709b2ef", size = 2280495 },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/df/348cf4ff1becd63ed952e35e436de3f9fd3245edb74c070457b465c40a58/fonttools-4.55.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:833927d089e6585019f2c85e3f8f7d87733e3fe81cd704ebaca7afa27e2e7113", size = 4561947 },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/fe/48b808bdf14bb9467e4a5aaa8aa89f8aba9979d52be3f7f1962f065e933e/fonttools-4.55.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7858dc6823296a053d85b831fa8428781c6c6f06fca44582bf7b6b2ff32a9089", size = 4604618 },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/25/305d88761aa15a8b2761869a15db34c070e72756d166a163756c53d07b35/fonttools-4.55.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:05568a66b090ed9d79aefdce2ceb180bb64fc856961deaedc29f5ad51355ce2c", size = 4558896 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/0b/c6f7877611940ab75dbe50f035d16ca5ce6d9ff2e5e65b9c76da830286ff/fonttools-4.55.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2dbc08e227fbeb716776905a7bd3c4fc62c8e37c8ef7d481acd10cb5fde12222", size = 4728347 },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/2c/490223b8cfaeccdef3d8819945a455aa8cc57f12f49233a3d40556b739cc/fonttools-4.55.7-cp310-cp310-win32.whl", hash = "sha256:6eb93cbba484a463b5ee83f7dd3211905f27a3871d20d90fb72de84c6c5056e3", size = 2155437 },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/f8/ee47526b3f03596cbed9dc7f38519cb650e7769bf9365e04bd81ff4a5302/fonttools-4.55.7-cp310-cp310-win_amd64.whl", hash = "sha256:7ff8e606f905048dc91a55a06d994b68065bf35752ae199df54a9bf30013dcaa", size = 2199898 },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/cb/f1dd2e31553bd03dcb4eb3af1ac6acc7fe41f26067d1bba104005ec1bb04/fonttools-4.55.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:916e1d926823b4b3b3815c59fc79f4ed670696fdd5fd9a5e690a0503eef38f79", size = 2753201 },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/84/f9f82093789947547b4bc86242669cde816ef4d949b23f472e47e85f125d/fonttools-4.55.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b89da448e0073408d7b2c44935f9fdae4fdc93644899f99f6102ef883ecf083c", size = 2281418 },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/e1/e0398d2aa7bf5400c84650fc7d85708502289bb92a40f8090e6e71cfe315/fonttools-4.55.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:087ace2d06894ccdb03e6975d05da6bb9cec0c689b2a9983c059880e33a1464a", size = 4869132 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/2d/9d86cd653c758334285a5c95d1bc0a7f13b6a72fc674c6b33fef3b8e3f77/fonttools-4.55.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:775ed0700ee6f781436641f18a0c61b1846a8c1aecae6da6b395c4417e2cb567", size = 4898375 },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/ce/f49fccb7d9f7c9c6d239434fc48546a0b37a91ba8310c7bcd5127cfeb5f6/fonttools-4.55.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9ec71d0cc0242899f87e4c230ed0b22c7b8681f288fb80e3d81c2c54c5bd2c79", size = 4877574 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/85/afe73e96a1572ba0acc86e82d52554bf69f384b431acd7a15b8c3890833b/fonttools-4.55.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d4b1c5939c0521525f45522823508e6fad21175bca978583688ea3b3736e6625", size = 5045681 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/37/dc59bc5a2f049d39b62996c806c147ae2eee5316f047a37bcf4cb9dbc4ef/fonttools-4.55.7-cp311-cp311-win32.whl", hash = "sha256:23df0f1003abaf8a435543f59583fc247e7ae1b047ee2263510e0654a5f207e0", size = 2154302 },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/33/281989403a57945c7871df144af3512ad3d1cd223e025b08b7f377847e6d/fonttools-4.55.7-cp311-cp311-win_amd64.whl", hash = "sha256:82163d58b43eff6e2025a25c32905fdb9042a163cc1ff82dab393e7ffc77a7d5", size = 2200818 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/f5/80ba2cef5358b0984ac1ad576daba6449f81bc44ecc0244db78210c1dc38/fonttools-4.55.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:12e81d44f762156d28b5c93a6b65d98ed73678be45b22546de8ed29736c3cb96", size = 2747631 },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/a3/ed291ca43193c6b4e0c69ebd01505a9a0f16c06b18d2d3f2560397a4813f/fonttools-4.55.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c26445a7be689f8b70df7d5d2e2c85ec4407bdb769902a23dd45ac44f767575d", size = 2278884 },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/d6/a6dbce3eb296eecba1cac8f5957863d4329d174c8e96cf6453ba82e1e11f/fonttools-4.55.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2cbafedb9462be7cf68c66b6ca1d8309842fe36b729f1b1969595f5d660e5c2", size = 4783859 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/3b/a6f66be6dc6c056bd57443d2f02b6cc123b15d67d7952f7fecfa1da64a19/fonttools-4.55.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4bde87985012adbd7559bc363d802fb335e92a07ff86a76cf02bebb0b8566d1", size = 4854977 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/a9/cc5ca0681177a47c155df732a94e7f0ef4331641dcc66250fd382505ce8f/fonttools-4.55.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:69ed0660750993150f7c4d966c0c1ffaa0385f23ccef85c2ff108062d80dd7ea", size = 4762366 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/5f/a4fb68c13e0ffffc3ad732e955a45ec1f01047fdadf8adcb48eac6a1330b/fonttools-4.55.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3098355e7a7b5ac48d5dc29684a65271187b865b85675033958b57c40364ee34", size = 4989955 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/58/325d278535405f99ca1b39d360dc255126fa9dd0e3648a046982f10f4246/fonttools-4.55.7-cp312-cp312-win32.whl", hash = "sha256:ee7aa8bb716318e3d835ef473978e22b7a39c0f1b3b08cc0b0ee1bba6f73bc1e", size = 2142771 },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/2f/806c4b86ccfc0a5e48bc78ea3730ca9f6207c6deeab5a57bf87e6b8596f0/fonttools-4.55.7-cp312-cp312-win_amd64.whl", hash = "sha256:e696d6e2baf4cc57ded34bb87e5d3a9e4da9732f3d9e8e2c6db0746e57a6dc0b", size = 2189608 },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/19/6d2f97d52a3355b713d01854f35c01d712489af6b764432697c76dc042f8/fonttools-4.55.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e10c7fb80cdfdc32244514cbea0906e9f53e3cc80d64d3389da09502fd999b55", size = 2734996 },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/28/1f5753fb43eb9ee3919027d1f8de52ea19698083a17ff4013e636b2d82fb/fonttools-4.55.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1101976c703ff4008a928fc3fef42caf06d035bfc4614230d7e797cbe356feb0", size = 2272393 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/2f/0a1cd55dac0ec49e42c9a03a56114ae863bc3b652e2f40743151c45b1569/fonttools-4.55.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e6dffe9cbcd163ef617fab1f81682e4d1629b7a5b9c5e598274dc2d03e88bcd", size = 4762292 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/59/30c3842759e051fc100d3fc2f9722cf9b3d1db7b1fa803c7237fd2f552fc/fonttools-4.55.7-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77e5115a425d53be6e31cd0fe9210f62a488bccf81eb113ab5dd7f4fa88e4d81", size = 4834310 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/10/91a9e18315116bff26c4e96abcfbdb279b147fe54e55772b119dce5d8963/fonttools-4.55.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f0c45eae32d090763820756b18322a70571dada3f1cbe003debc37a9c35bc260", size = 4742671 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/6c/117aac028ad47ac375033e6113930a096db685461cbd28909d3a246b1191/fonttools-4.55.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd4ebc475d43f3de2b26e0cf551eff92c24e22d1aee03dc1b33adb52fc2e6cb2", size = 4963611 },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/28/47518cf6f69ac07184c6d61d1ef663e22b871d549d7f2b97b08bd104859b/fonttools-4.55.7-cp313-cp313-win32.whl", hash = "sha256:371197de1283cc99f5f10eb91496520eb0e2d079312d014fd6cef9e802174c6a", size = 2140803 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/9d/df15fc73e33b30eaa3c1b1304952d7b0b6a64e0b10dc4778b65b46506d38/fonttools-4.55.7-cp313-cp313-win_amd64.whl", hash = "sha256:418ece624fbc04e199f58398ffef3eaad645baba65434871b09eb7350a3a346b", size = 2186591 },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/f4/23890d90cf7ea1ea67f4be7adf2055763d2b7ff32a66d1557830e245dd0c/fonttools-4.55.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8ef5ee98fc320c158e4e459a5ee40d1ac3728d4ce11c3c8dfd854aa0aa5c042f", size = 2754754 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/23/a2a55b3cb4dcc678f17f9fb47249e115f1a82ab29456ba380e12a7f706ef/fonttools-4.55.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09740feed51f9ed816aebf5d82071b7fecf693ac3a7e0fc8ea433f5dc3bd92f5", size = 2281759 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/57/c4f06df5cba3f5e315014989b16a962df370d0c1b730d1e090afc30f15ea/fonttools-4.55.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3d19ea483b3cd8833e9e2ee8115f3d2044d55d3743d84f9c23b48b52d7516d8", size = 4567533 },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/81/505923e0c9409528fb52fb361c4ea5eff5fedcc979f1c5c6a4a43e308c9a/fonttools-4.55.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c135c91d47351b84893fb6fcbb8f178eba14f7cb195850264c0675c85e4238b6", size = 4612524 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/5b/fd9a21e80b5c17f512fff838f32ed02ed07b7fedb94024b86144774ac13c/fonttools-4.55.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bee4920ebeb540849bc3555d871e2a8487e39ce8263c281f74d5b6d44d2bf1df", size = 4561004 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/cd/899ffcce6b1846fc6ef4b33c3d1ee0b87f847825ad2f0525980408f6e586/fonttools-4.55.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f3b63648600dd0081bdd6856a86d014a7f1d2d11c3c974542f866478d832e103", size = 4731298 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/85/aec7ac6f15d82ad0716eee4a3484065928168b6ffec30d850e5830d9ea50/fonttools-4.55.7-cp39-cp39-win32.whl", hash = "sha256:d4bd27f0fa5120aaa39f76de5768959bc97300e0f59a3160d466b51436a38aea", size = 2155985 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/79/f45dc7aea6f4306b29c746282d4a309a568145a4a8526f944c915626c84c/fonttools-4.55.7-cp39-cp39-win_amd64.whl", hash = "sha256:c665df9c9d99937a5bf807bace1c0c95bd13f55de8c82aaf9856b868dcbfe5d9", size = 2200420 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/6d/304a16caf63a8c193ec387b1fae1cb10072a59d34549f2eefe7e3fa9f364/fonttools-4.55.7-py3-none-any.whl", hash = "sha256:3304dfcf9ca204dd0ef691a287bd851ddd8e8250108658c0677c3fdfec853a20", size = 1089677 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1009,7 +1010,7 @@ name = "ipykernel"
|
||||
version = "6.29.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "appnope", marker = "platform_system == 'Darwin'" },
|
||||
{ name = "appnope", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "comm" },
|
||||
{ name = "debugpy" },
|
||||
{ name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
@ -1526,7 +1527,6 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "manim-slides"
|
||||
version = "5.4.1"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "av" },
|
||||
@ -1549,27 +1549,6 @@ dependencies = [
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
docs = [
|
||||
{ name = "docutils" },
|
||||
{ name = "furo" },
|
||||
{ name = "ipykernel" },
|
||||
{ name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "ipython", version = "8.31.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "manim" },
|
||||
{ name = "myst-parser", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "myst-parser", version = "4.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "nbsphinx" },
|
||||
{ name = "pandoc" },
|
||||
{ name = "pygments" },
|
||||
{ name = "pyqt6" },
|
||||
{ name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "sphinx-click" },
|
||||
{ name = "sphinx-copybutton" },
|
||||
{ name = "sphinx-design" },
|
||||
{ name = "sphinxcontrib-programoutput" },
|
||||
{ name = "sphinxext-opengraph" },
|
||||
]
|
||||
full = [
|
||||
{ name = "docutils" },
|
||||
{ name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
@ -1611,25 +1590,59 @@ sphinx-directive = [
|
||||
{ name = "docutils" },
|
||||
{ name = "manim" },
|
||||
]
|
||||
tests = [
|
||||
{ name = "docutils" },
|
||||
{ name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "ipython", version = "8.31.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "manim" },
|
||||
{ name = "manimgl" },
|
||||
{ name = "pyqt6" },
|
||||
{ name = "pyside6" },
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "bump-my-version" },
|
||||
{ name = "furo" },
|
||||
{ name = "importlib-metadata", marker = "python_full_version < '3.10'" },
|
||||
{ name = "ipykernel" },
|
||||
{ name = "manim-slides", extra = ["full", "magic", "manim", "manimgl", "pyqt6", "pyside6", "sphinx-directive"] },
|
||||
{ name = "myst-parser", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "myst-parser", version = "4.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "nbsphinx" },
|
||||
{ name = "pandoc" },
|
||||
{ name = "pre-commit" },
|
||||
{ name = "pygments" },
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-cov" },
|
||||
{ name = "pytest-env" },
|
||||
{ name = "pytest-missing-modules" },
|
||||
{ name = "pytest-qt" },
|
||||
{ name = "setuptools" },
|
||||
{ name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "sphinx-click" },
|
||||
{ name = "sphinx-copybutton" },
|
||||
{ name = "sphinx-design" },
|
||||
{ name = "sphinxcontrib-programoutput" },
|
||||
{ name = "sphinxext-opengraph" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "bump-my-version" },
|
||||
{ name = "pre-commit" },
|
||||
docs = [
|
||||
{ name = "furo" },
|
||||
{ name = "ipykernel" },
|
||||
{ name = "manim-slides", extra = ["magic", "manim", "pyqt6", "sphinx-directive"] },
|
||||
{ name = "myst-parser", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "myst-parser", version = "4.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "nbsphinx" },
|
||||
{ name = "pandoc" },
|
||||
{ name = "pygments" },
|
||||
{ name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "sphinx-click" },
|
||||
{ name = "sphinx-copybutton" },
|
||||
{ name = "sphinx-design" },
|
||||
{ name = "sphinxcontrib-programoutput" },
|
||||
{ name = "sphinxext-opengraph" },
|
||||
]
|
||||
tests = [
|
||||
{ name = "importlib-metadata", marker = "python_full_version < '3.10'" },
|
||||
{ name = "manim-slides", extra = ["full", "manimgl", "pyqt6", "pyside6", "sphinx-directive"] },
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-cov" },
|
||||
{ name = "pytest-env" },
|
||||
{ name = "pytest-missing-modules" },
|
||||
{ name = "pytest-qt" },
|
||||
{ name = "setuptools" },
|
||||
]
|
||||
|
||||
@ -1639,70 +1652,89 @@ requires-dist = [
|
||||
{ name = "beautifulsoup4", specifier = ">=4.12.3" },
|
||||
{ name = "click", specifier = ">=8.1.3" },
|
||||
{ name = "click-default-group", specifier = ">=1.2.2" },
|
||||
{ name = "docutils", marker = "extra == 'docs'", specifier = ">=0.20.1" },
|
||||
{ name = "docutils", marker = "extra == 'full'", specifier = ">=0.20.1" },
|
||||
{ name = "docutils", marker = "extra == 'pyqt6-full'", specifier = ">=0.20.1" },
|
||||
{ name = "docutils", marker = "extra == 'pyside6-full'", specifier = ">=0.20.1" },
|
||||
{ name = "docutils", marker = "extra == 'sphinx-directive'", specifier = ">=0.20.1" },
|
||||
{ name = "docutils", marker = "extra == 'tests'", specifier = ">=0.20.1" },
|
||||
{ name = "furo", marker = "extra == 'docs'", specifier = ">=2023.5.20" },
|
||||
{ name = "ipykernel", marker = "extra == 'docs'", specifier = ">=6.25.1" },
|
||||
{ name = "ipython", marker = "extra == 'docs'", specifier = ">=8.12.2" },
|
||||
{ name = "ipython", marker = "extra == 'full'", specifier = ">=8.12.2" },
|
||||
{ name = "ipython", marker = "extra == 'magic'", specifier = ">=8.12.2" },
|
||||
{ name = "ipython", marker = "extra == 'pyqt6-full'", specifier = ">=8.12.2" },
|
||||
{ name = "ipython", marker = "extra == 'pyside6-full'", specifier = ">=8.12.2" },
|
||||
{ name = "ipython", marker = "extra == 'tests'", specifier = ">=8.12.2" },
|
||||
{ name = "jinja2", specifier = ">=3.1.2" },
|
||||
{ name = "lxml", specifier = ">=4.9.2" },
|
||||
{ name = "manim", marker = "extra == 'docs'", specifier = ">=0.19" },
|
||||
{ name = "manim", marker = "extra == 'full'", specifier = ">=0.19" },
|
||||
{ name = "manim", marker = "extra == 'magic'", specifier = ">=0.19" },
|
||||
{ name = "manim", marker = "extra == 'manim'", specifier = ">=0.19" },
|
||||
{ name = "manim", marker = "extra == 'pyqt6-full'", specifier = ">=0.19" },
|
||||
{ name = "manim", marker = "extra == 'pyside6-full'", specifier = ">=0.19" },
|
||||
{ name = "manim", marker = "extra == 'sphinx-directive'", specifier = ">=0.19" },
|
||||
{ name = "manim", marker = "extra == 'tests'", specifier = ">=0.19" },
|
||||
{ name = "manimgl", marker = "extra == 'manimgl'", specifier = ">=1.7.2" },
|
||||
{ name = "manimgl", marker = "extra == 'tests'", specifier = ">=1.7.2" },
|
||||
{ name = "myst-parser", marker = "extra == 'docs'", specifier = ">=2.0.0" },
|
||||
{ name = "nbsphinx", marker = "extra == 'docs'", specifier = ">=0.9.2" },
|
||||
{ name = "numpy", specifier = ">=1.19" },
|
||||
{ name = "pandoc", marker = "extra == 'docs'", specifier = ">=2.3" },
|
||||
{ name = "pillow", specifier = ">=9.5.0" },
|
||||
{ name = "pydantic", specifier = ">=2.0.1" },
|
||||
{ name = "pydantic-extra-types", specifier = ">=2.0.0" },
|
||||
{ name = "pygments", marker = "extra == 'docs'", specifier = "<2.19" },
|
||||
{ name = "pyqt6", marker = "extra == 'docs'", specifier = ">=6.7.0" },
|
||||
{ name = "pyqt6", marker = "extra == 'pyqt6'", specifier = ">=6.7.0" },
|
||||
{ name = "pyqt6", marker = "extra == 'pyqt6-full'", specifier = ">=6.7.0" },
|
||||
{ name = "pyqt6", marker = "extra == 'tests'", specifier = ">=6.7.0" },
|
||||
{ name = "pyside6", marker = "extra == 'pyside6'", specifier = ">=6.6.1,!=6.8.1.1" },
|
||||
{ name = "pyside6", marker = "extra == 'pyside6-full'", specifier = ">=6.6.1,!=6.8.1.1" },
|
||||
{ name = "pyside6", marker = "extra == 'tests'", specifier = ">=6.6.1,!=6.8.1.1" },
|
||||
{ name = "pytest", marker = "extra == 'tests'", specifier = ">=7.4.0" },
|
||||
{ name = "pytest-cov", marker = "extra == 'tests'", specifier = ">=4.1.0" },
|
||||
{ name = "pytest-env", marker = "extra == 'tests'", specifier = ">=0.8.2" },
|
||||
{ name = "pytest-missing-modules", marker = "extra == 'tests'", specifier = ">=0.1.0" },
|
||||
{ name = "pytest-qt", marker = "extra == 'tests'", specifier = ">=4.2.0" },
|
||||
{ name = "python-pptx", specifier = ">=0.6.21" },
|
||||
{ name = "qtpy", specifier = ">=2.4.1" },
|
||||
{ name = "requests", specifier = ">=2.28.1" },
|
||||
{ name = "rich", specifier = ">=13.3.2" },
|
||||
{ name = "rtoml", specifier = ">=0.11.0" },
|
||||
{ name = "sphinx", marker = "extra == 'docs'", specifier = ">=7.0.1" },
|
||||
{ name = "sphinx-click", marker = "extra == 'docs'", specifier = ">=4.4.0" },
|
||||
{ name = "sphinx-copybutton", marker = "extra == 'docs'", specifier = ">=0.5.1" },
|
||||
{ name = "sphinx-design", marker = "extra == 'docs'", specifier = ">=0.6.1" },
|
||||
{ name = "sphinxcontrib-programoutput", marker = "extra == 'docs'", specifier = ">=0.18" },
|
||||
{ name = "sphinxext-opengraph", marker = "extra == 'docs'", specifier = ">=0.7.5" },
|
||||
{ name = "tqdm", specifier = ">=4.64.1" },
|
||||
]
|
||||
provides-extras = ["full", "magic", "manim", "manimgl", "pyqt6", "pyqt6-full", "pyside6", "pyside6-full", "sphinx-directive"]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "bump-my-version", specifier = ">=0.20.3" },
|
||||
{ name = "furo", specifier = ">=2023.5.20" },
|
||||
{ name = "importlib-metadata", marker = "python_full_version < '3.10'", specifier = ">=8.6.1" },
|
||||
{ name = "ipykernel", specifier = ">=6.25.1" },
|
||||
{ name = "manim-slides", extras = ["full", "manimgl", "pyqt6", "pyside6", "sphinx-directive"] },
|
||||
{ name = "manim-slides", extras = ["magic", "manim", "pyqt6", "sphinx-directive"] },
|
||||
{ name = "myst-parser", specifier = ">=2.0.0" },
|
||||
{ name = "nbsphinx", specifier = ">=0.9.2" },
|
||||
{ name = "pandoc", specifier = ">=2.3" },
|
||||
{ name = "pre-commit", specifier = ">=3.5.0" },
|
||||
{ name = "pygments", specifier = "<2.19" },
|
||||
{ name = "pytest", specifier = ">=7.4.0" },
|
||||
{ name = "pytest-cov", specifier = ">=4.1.0" },
|
||||
{ name = "pytest-env", specifier = ">=0.8.2" },
|
||||
{ name = "pytest-missing-modules", specifier = ">=0.1.0" },
|
||||
{ name = "pytest-qt", specifier = ">=4.2.0" },
|
||||
{ name = "setuptools", specifier = ">=73.0.1" },
|
||||
{ name = "sphinx", specifier = ">=7.0.1" },
|
||||
{ name = "sphinx-click", specifier = ">=4.4.0" },
|
||||
{ name = "sphinx-copybutton", specifier = ">=0.5.1" },
|
||||
{ name = "sphinx-design", specifier = ">=0.6.1" },
|
||||
{ name = "sphinxcontrib-programoutput", specifier = ">=0.18" },
|
||||
{ name = "sphinxext-opengraph", specifier = ">=0.7.5" },
|
||||
]
|
||||
docs = [
|
||||
{ name = "furo", specifier = ">=2023.5.20" },
|
||||
{ name = "ipykernel", specifier = ">=6.25.1" },
|
||||
{ name = "manim-slides", extras = ["magic", "manim", "pyqt6", "sphinx-directive"] },
|
||||
{ name = "myst-parser", specifier = ">=2.0.0" },
|
||||
{ name = "nbsphinx", specifier = ">=0.9.2" },
|
||||
{ name = "pandoc", specifier = ">=2.3" },
|
||||
{ name = "pygments", specifier = "<2.19" },
|
||||
{ name = "sphinx", specifier = ">=7.0.1" },
|
||||
{ name = "sphinx-click", specifier = ">=4.4.0" },
|
||||
{ name = "sphinx-copybutton", specifier = ">=0.5.1" },
|
||||
{ name = "sphinx-design", specifier = ">=0.6.1" },
|
||||
{ name = "sphinxcontrib-programoutput", specifier = ">=0.18" },
|
||||
{ name = "sphinxext-opengraph", specifier = ">=0.7.5" },
|
||||
]
|
||||
tests = [
|
||||
{ name = "importlib-metadata", marker = "python_full_version < '3.10'", specifier = ">=8.6.1" },
|
||||
{ name = "manim-slides", extras = ["full", "manimgl", "pyqt6", "pyside6", "sphinx-directive"] },
|
||||
{ name = "pytest", specifier = ">=7.4.0" },
|
||||
{ name = "pytest-cov", specifier = ">=4.1.0" },
|
||||
{ name = "pytest-env", specifier = ">=0.8.2" },
|
||||
{ name = "pytest-missing-modules", specifier = ">=0.1.0" },
|
||||
{ name = "pytest-qt", specifier = ">=4.2.0" },
|
||||
{ name = "setuptools", specifier = ">=73.0.1" },
|
||||
]
|
||||
|
||||
@ -2061,14 +2093,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "mistune"
|
||||
version = "3.1.0"
|
||||
version = "3.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/79/6e/96fc7cb3288666c5de2c396eb0e338dc95f7a8e4920e43e38783a22d0084/mistune-3.1.0.tar.gz", hash = "sha256:dbcac2f78292b9dc066cd03b7a3a26b62d85f8159f2ea5fd28e55df79908d667", size = 94401 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c6/1d/6b2b634e43bacc3239006e61800676aa6c41ac1836b2c57497ed27a7310b/mistune-3.1.1.tar.gz", hash = "sha256:e0740d635f515119f7d1feb6f9b192ee60f0cc649f80a8f944f905706a21654c", size = 94645 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/b3/743ffc3f59da380da504d84ccd1faf9a857a1445991ff19bf2ec754163c2/mistune-3.1.0-py3-none-any.whl", hash = "sha256:b05198cf6d671b3deba6c87ec6cf0d4eb7b72c524636eddb6dbf13823b52cee1", size = 53694 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/02/c66bdfdadbb021adb642ca4e8a5ed32ada0b4a3e4b39c5d076d19543452f/mistune-3.1.1-py3-none-any.whl", hash = "sha256:02106ac2aa4f66e769debbfa028509a275069dcffce0dfa578edd7b991ee700a", size = 53696 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2206,7 +2238,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "nbconvert"
|
||||
version = "7.16.5"
|
||||
version = "7.16.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "beautifulsoup4" },
|
||||
@ -2225,9 +2257,9 @@ dependencies = [
|
||||
{ name = "pygments" },
|
||||
{ name = "traitlets" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/46/2c/d026c0367f2be2463d4c2f5b538e28add2bc67bc13730abb7f364ae4eb8b/nbconvert-7.16.5.tar.gz", hash = "sha256:c83467bb5777fdfaac5ebbb8e864f300b277f68692ecc04d6dab72f2d8442344", size = 856367 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/59/f28e15fc47ffb73af68a8d9b47367a8630d76e97ae85ad18271b9db96fdf/nbconvert-7.16.6.tar.gz", hash = "sha256:576a7e37c6480da7b8465eefa66c17844243816ce1ccc372633c6b71c3c0f582", size = 857715 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/9e/2dcc9fe00cf55d95a8deae69384e9cea61816126e345754f6c75494d32ec/nbconvert-7.16.5-py3-none-any.whl", hash = "sha256:e12eac052d6fd03040af4166c563d76e7aeead2e9aadf5356db552a1784bd547", size = 258061 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl", hash = "sha256:1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b", size = 258525 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2577,7 +2609,7 @@ name = "plumbum"
|
||||
version = "1.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pywin32", marker = "platform_python_implementation != 'PyPy' and platform_system == 'Windows'" },
|
||||
{ name = "pywin32", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f0/5d/49ba324ad4ae5b1a4caefafbce7a1648540129344481f2ed4ef6bb68d451/plumbum-1.9.0.tar.gz", hash = "sha256:e640062b72642c3873bd5bdc3effed75ba4d3c70ef6b6a7b907357a84d909219", size = 319083 }
|
||||
wheels = [
|
||||
@ -2683,16 +2715,16 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.10.5"
|
||||
version = "2.10.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
{ name = "pydantic-core" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6a/c7/ca334c2ef6f2e046b1144fe4bb2a5da8a4c574e7f2ebf7e16b34a6a2fa92/pydantic-2.10.5.tar.gz", hash = "sha256:278b38dbbaec562011d659ee05f63346951b3a248a6f3642e1bc68894ea2b4ff", size = 761287 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/58/26/82663c79010b28eddf29dcdd0ea723439535fa917fce5905885c0e9ba562/pydantic-2.10.5-py3-none-any.whl", hash = "sha256:4dd4e322dbe55472cb7ca7e73f4b63574eecccf2835ffa2af9021ce113c83c53", size = 431426 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2829,11 +2861,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pyglet"
|
||||
version = "2.1.1"
|
||||
version = "2.1.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/20/60/bf154aba98e66bcc9d58e3d8bbea3d68a960f05b968a81693b17a76ece99/pyglet-2.1.1.tar.gz", hash = "sha256:47f49890a00e9fefc4d0ea74dc5b9d6b9be1c5455bb5746b2df118012cfa3124", size = 6515139 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ea/4b/44b4dc01ffac9f8950d540c99159f83e2c398df2a600394feb6d2dbf7340/pyglet-2.1.2.tar.gz", hash = "sha256:6fc1fed55eb6dc80c87a7a45ac62c2a61be08cd31114b27abef8619959be7845", size = 6515611 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/2d/82c81bfc3de48a8225a594346c50eb27c819d3788fccc6c7d654f27dbbc3/pyglet-2.1.1-py3-none-any.whl", hash = "sha256:30562bfe9971a9ebeb6bb826f2c0d1649dd3d41bc8109ecc103425621f54c802", size = 961927 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/aa/edc16d04f6de8e833b4cba3068185fd2aad9e6f3a22fbc89afec36414f12/pyglet-2.1.2-py3-none-any.whl", hash = "sha256:2819fa9d66ead4b1682d1b7fa7170692d46277e1a6661b329bdba3e65288c036", size = 961981 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3393,16 +3425,16 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "referencing"
|
||||
version = "0.36.1"
|
||||
version = "0.36.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "attrs" },
|
||||
{ name = "rpds-py" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/27/32/fd98246df7a0f309b58cae68b10b6b219ef2eb66747f00dfb34422687087/referencing-0.36.1.tar.gz", hash = "sha256:ca2e6492769e3602957e9b831b94211599d2aade9477f5d44110d2530cf9aade", size = 74661 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/fa/9f193ef0c9074b659009f06d7cbacc6f25b072044815bcf799b76533dbb8/referencing-0.36.1-py3-none-any.whl", hash = "sha256:363d9c65f080d0d70bc41c721dce3c7f3e77fc09f269cd5c8813da18069a6794", size = 26777 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4148,7 +4180,7 @@ name = "tqdm"
|
||||
version = "4.67.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "platform_system == 'Windows'" },
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 }
|
||||
wheels = [
|
||||
@ -4274,11 +4306,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "xlsxwriter"
|
||||
version = "3.2.0"
|
||||
version = "3.2.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a6/c3/b36fa44a0610a0f65d2e65ba6a262cbe2554b819f1449731971f7c16ea3c/XlsxWriter-3.2.0.tar.gz", hash = "sha256:9977d0c661a72866a61f9f7a809e25ebbb0fb7036baa3b9fe74afcfca6b3cb8c", size = 198732 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a1/08/26f69d1e9264e8107253018de9fc6b96f9219817d01c5f021e927384a8d1/xlsxwriter-3.2.2.tar.gz", hash = "sha256:befc7f92578a85fed261639fb6cde1fd51b79c5e854040847dde59d4317077dc", size = 205202 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/ea/53d1fe468e63e092cf16e2c18d16f50c29851242f9dd12d6a66e0d7f0d02/XlsxWriter-3.2.0-py3-none-any.whl", hash = "sha256:ecfd5405b3e0e228219bcaf24c2ca0915e012ca9464a14048021d21a995d490e", size = 159925 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/07/df054f7413bdfff5e98f75056e4ed0977d0c8716424011fac2587864d1d3/XlsxWriter-3.2.2-py3-none-any.whl", hash = "sha256:272ce861e7fa5e82a4a6ebc24511f2cb952fde3461f6c6e1a1e81d3272db1471", size = 165121 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
Reference in New Issue
Block a user