Compare commits

...

40 Commits

Author SHA1 Message Date
14df6eb55a chore(docs): update changelog 2024-04-18 22:14:35 +02:00
ef014ebac6 chore(version): bump 5.1.4 to 5.1.5 2024-04-18 22:13:11 +02:00
d5d1513d94 chore(dev): move to Rye instead of PDM (#420)
* test: re-add opencv-python

* chore(dev): move to Rye instead of PDM

* try fix

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix: build backend

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix: string quotes?

* small fixes

* upgrade typing

* fix(ci): rye install on Windows

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix(ci): typos

* fix

* fix(ci): actually use right python version

* fix(deps): manimgl

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix docs

* another fix

* cleanup

* make sure to use trusted publisher

* chore(docs): remove PDM

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-04-18 22:12:45 +02:00
bd04dae2bf fix(cli): broken --show-config command (#419)
Fix and improve the command.
2024-04-17 20:33:44 +02:00
d5c20d1791 chore(docs): update faq page 2024-04-17 20:07:15 +02:00
527ce2767e chore(docs): add faq page (#418)
* chore(docs): add faq page

Closes #405

* chore(docs): add faq page

Closes #405

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* update changelog

* fix

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-04-17 18:10:46 +02:00
04bca458f1 chore(ci): checking links and spell checking (#417)
* chore(ci): checking links and spell checking

* chore(ci): move markdown-link-check to GitHub ci

Because pre-commit.ci does not have access to the internet...

* fix(lib): revert `reverse-...` utils because of warnings

* chore(ci): checking links and spell checking

* chore(ci): move markdown-link-check to GitHub ci

Because pre-commit.ci does not have access to the internet...

* fix(docs): myst-parser xref cannot end with .html

* fix(docs): oops
2024-04-17 17:59:40 +02:00
4e7abe8706 fix(lib): revert reverse-... utils because of warnings 2024-04-17 17:02:43 +02:00
49e2c31d9a chore(version): bump 5.1.3 to 5.1.4 2024-04-16 17:35:42 +02:00
5920a843f5 Merge remote-tracking branch 'origin/main' into main 2024-04-16 17:35:26 +02:00
59dd365291 chore(deps): remove OpenCV python (#415)
Closes #413
2024-04-16 17:35:23 +02:00
3e2e64b09f chore(docs): update changelog 2024-04-16 17:25:03 +02:00
8a3bf87db8 chore(lib): filter out video files without video stream (#416)
* chore(lib): filter out video files without video stream

This is a ( hopefully temporary) fix to #390.

Closes #390

* fix: Windows issue
2024-04-16 17:17:42 +02:00
498e9af2bf fix(lib): correctly retrieve background color (#414)
Closes #409
2024-04-16 14:33:27 +02:00
24ee23af11 chore(deps): update RevealJS version to 5.1.0 (#412)
Closes #384
2024-04-16 13:53:08 +02:00
a775c4989b [pre-commit.ci] pre-commit autoupdate (#411)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.3.5 → v0.3.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.3.5...v0.3.7)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-04-16 13:23:08 +02:00
04f6ee7f9b [pre-commit.ci] pre-commit autoupdate (#403)
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.5.0 → v4.6.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.5.0...v4.6.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-04-11 09:52:12 +02:00
bbc539b461 chore(docs): add nixpkgs installation documentation (#404) 2024-04-11 09:30:40 +02:00
2d9d263c9c chore(docs): add favicon (#399)
* chore(docs): add favicon

* fix(docs): typo
2024-04-04 14:40:36 +02:00
cfa9c082ab [pre-commit.ci] pre-commit autoupdate (#374)
* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/macisamuele/language-formatters-pre-commit-hooks: v2.12.0 → v2.13.0](https://github.com/macisamuele/language-formatters-pre-commit-hooks/compare/v2.12.0...v2.13.0)
- [github.com/astral-sh/ruff-pre-commit: v0.2.1 → v0.3.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.2.1...v0.3.5)
- [github.com/pre-commit/mirrors-mypy: v1.8.0 → v1.9.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.8.0...v1.9.0)

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-04-04 11:14:40 +02:00
67533c460e feat(cli): added separate option for info window screen (#389)
Closes #388.
2024-04-04 11:14:25 +02:00
a85f1c4036 fix(docs): README url 2024-03-08 11:59:08 +01:00
b3fe6f17b9 chore(cite): update citation file 2024-03-07 10:26:04 +01:00
e7182a445d feat(present): add audio output (#382)
* feat(present): add audio output

This PR adds the necessary audio output to the presenter mode, so it can play audio contained in video files, see #375.

However, this does not fix the issue that slides do not contain audio by default, but should be solved by Manim, see #375 for updates on that topic.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix: PR number

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-03-05 11:08:30 +01:00
1dbd2fdde5 chore(version): bump 5.1.2 to 5.1.3 2024-02-16 13:58:43 +01:00
07fd2bdcf1 chore(lib): warn about non-preferred-qt-version (#373)
Closes #371
2024-02-16 13:57:54 +01:00
d586dab102 [pre-commit.ci] pre-commit autoupdate (#372)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.2.0 → v0.2.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.2.0...v0.2.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-02-13 08:48:54 +01:00
0ff1f37475 [pre-commit.ci] pre-commit autoupdate (#369)
* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.1.14 → v0.2.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.14...v0.2.0)

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-02-06 08:48:42 +01:00
92c569950c Fix link to documentation in README. (#368)
* Fix link to documentation in README.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-02-02 09:54:47 +01:00
648d7ff921 chore(version): bump 5.1.1 to 5.1.2 2024-02-01 13:02:40 +01:00
b47068ede5 fix(rtd): remove flyout in iframes (#367)
* fix(rtd): remove flyout in iframes

* fix(rtd): remove dirhtml and default to html

Because dirhtml seems to have issues with iframes

* fix(html): correct css style
2024-02-01 12:27:24 +01:00
973522a2ac chore(deps): bump codecov/codecov-action from 3 to 4 (#366)
* chore(deps): bump codecov/codecov-action from 3 to 4

Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-02-01 08:01:22 +01:00
2f82ca3409 chore(rtd): change builder 2024-01-31 15:55:36 +01:00
e208cced03 chore(README): update docs links 2024-01-31 14:50:30 +01:00
abbe577aae chore(version): bump 5.1.0 to 5.1.1 2024-01-31 14:43:25 +01:00
38ef91d30c chore(CHANGELOG): add entry about RTD 2024-01-31 14:41:42 +01:00
b17fd5409f chore(docs): use RTD (#365)
* chore(docs): use RTD

* chore(ci): remove pages deployment
2024-01-31 13:36:53 +01:00
186badba03 chore(docs): add Binder badge 2024-01-29 09:12:55 +01:00
39816d4994 chore(docs): update changelog 2024-01-26 18:21:38 +01:00
9cb1dae990 chore(version): bump to 5.1.0 2024-01-26 18:19:09 +01:00
58 changed files with 1673 additions and 3986 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 5.1.0-rc4
current_version = 5.1.5
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(-rc(?P<release>\d+))?
serialize =
{major}.{minor}.{patch}-rc{release}

27
.github/scripts/check_github_issues.py vendored Normal file
View File

@ -0,0 +1,27 @@
"""Check that GitHub issues (and PR) links match the number in Markdown link."""
import glob
import re
import sys
if __name__ == "__main__":
p = re.compile(
r"\[#(?P<number1>[0-9]+)\]"
r"\(https://github\.com/"
r"(?:[a-zA-Z0-9_-]+)/(?:[a-zA-Z0-9_-]+)/"
r"(?:(?:issues)|(?:pull))/(?P<number2>[0-9]+)\)"
)
ret_code = 0
for glob_pattern in sys.argv[1:]:
for file in glob.glob(glob_pattern, recursive=True):
with open(file) as f:
for i, line in enumerate(f):
for m in p.finditer(line):
if m.group("number1") != m.group("number2"):
start, end = m.span()
print(f"{file}:{i}: ", line[start:end], file=sys.stderr) # noqa: T201
ret_code = 1
sys.exit(ret_code)

View File

@ -1,97 +0,0 @@
# Simple workflow for deploying static content to GitHub Pages
name: Deploy static content to Pages
on:
# Runs on pushes targeting the default branch
push:
branches: [main]
pull_request:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow one concurrent deployment
concurrency:
group: pages
cancel-in-progress: true
jobs:
# Single deploy job since we're just deploying
deploy:
permissions: write-all
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install PDM
uses: pdm-project/setup-pdm@v4
with:
python-version: '3.10'
cache: true
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Install Linux Dependencies
run: |
sudo apt-get update
sudo apt-get install libcairo2-dev libpango1.0-dev ffmpeg freeglut3-dev
- name: Setup Pandoc
uses: nikeee/setup-pandoc@v1
- name: Install local Python package
run: pdm sync -Gdocs -Ggithub-action
- name: Install IPython kernel
run: pdm run ipython kernel install --name "manim-slides" --user
- name: Restore cached media
id: cache-media-restore
uses: actions/cache/restore@v4
with:
path: docs/media
key: ${{ runner.os }}-docs-media
- name: Clear cache
run: |
gh extension install actions/gh-actions-cache
gh actions-cache delete ${{ steps.cache-media-restore.outputs.cache-primary-key }} --confirm || true
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Save media to cache
id: cache-media-save
uses: actions/cache/save@v4
with:
path: docs/media
key: ${{ steps.cache-media-restore.outputs.cache-primary-key }}
- name: Build docs
run: cd docs && pdm run make html
- name: Upload artifact
if: github.event_name != 'pull_request'
uses: actions/upload-pages-artifact@v3
with:
# Upload docs/build/html dir
path: docs/build/html/
- name: Show docs/build/html/_static/ dir content (video only)
run: tree -L 3 docs/build/html/_static/ -P '*.mp4'
- name: Deploy to GitHub Pages
id: deployment
if: github.event_name != 'pull_request'
uses: actions/deploy-pages@v4

View File

@ -18,15 +18,22 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install PDM
uses: pdm-project/setup-pdm@v4
with:
python-version: '3.10'
cache: true
- name: Setup Rye
env:
RYE_INSTALL_OPTION: --yes
run: |
curl -sSf https://rye-up.com/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
- name: Configure Rye
run: rye config --set-bool behavior.use-uv=true
- name: Build package
run: rye build
- name: Publish to PyPI
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
run: pdm publish
uses: pypa/gh-action-pypi-publish@release/v1
publish-docker:
name: Publish Docker image

View File

@ -25,28 +25,30 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install PDM
uses: pdm-project/setup-pdm@v4
with:
python-version: ${{ matrix.pyversion }}
cache: true
# Path related stuff
- name: Append to Path on MacOS
if: matrix.os == 'macos-latest'
- name: Setup Rye
if: matrix.os != 'windows-latest'
env:
RYE_TOOLCHAIN_VERSION: ${{ matrix.pyversion}}
RYE_INSTALL_OPTION: --yes
run: |
echo "${HOME}/.local/bin" >> $GITHUB_PATH
echo "/Users/runner/Library/Python/${{ matrix.pyversion }}/bin" >> $GITHUB_PATH
curl -sSf https://rye-up.com/get | bash
echo "$HOME/.rye/shims" >> $GITHUB_PATH
- name: Append to Path on Ubuntu
if: matrix.os == 'ubuntu-latest'
run: echo "${HOME}/.local/bin" >> $GITHUB_PATH
- name: Append to Path on Windows
# Stolen from https://github.com/bluss/pyproject-local-kernel/blob/2b641290694adc998fb6bceea58d3737523a68b7/.github/workflows/ci.yaml
- name: Install Rye (Windows)
if: matrix.os == 'windows-latest'
run: echo "${HOME}/.local/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
shell: bash
run: |
C:/msys64/usr/bin/wget.exe -q 'https://github.com/astral-sh/rye/releases/latest/download/rye-x86_64-windows.exe' -O rye-x86_64-windows.exe
./rye-x86_64-windows.exe self install --toolchain-version ${{ matrix.pyversion }} --modify-path -y
echo "$HOME\\.rye\\shims" >> $GITHUB_PATH
- name: Configure Rye
shell: bash
run: |
rye config --set-bool behavior.use-uv=true
rye pin ${{ matrix.pyversion }}
# OS depedencies
- name: Install manim dependencies on MacOS
if: matrix.os == 'macos-latest'
run: brew install ffmpeg py3cairo
@ -68,21 +70,35 @@ jobs:
uses: ssciwr/setup-mesa-dist-win@v2
- name: Install Manim Slides
run: |
pdm sync -Ggithub-action -Gtest
shell: bash
run: rye sync
- name: Run pytest
shell: bash
if: matrix.os != 'ubuntu-latest' || matrix.pyversion != '3.11'
run: pdm run pytest
run: rye run pytest
- name: Run pytest and coverage
if: matrix.os == 'ubuntu-latest' && matrix.pyversion == '3.11'
run: pdm run pytest --cov-report xml --cov=manim_slides tests/
run: rye run pytest --cov-report xml --cov=manim_slides tests/
- name: Upload to codecov.io
if: matrix.os == 'ubuntu-latest' && matrix.pyversion == '3.11'
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
fail_ci_if_error: true
markdown-link-check:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Check links
uses: gaurav-nelson/github-action-markdown-link-check@v1
with:
use-quiet-mode: yes
use-verbose-mode: yes
config-file: .mdlc.json

8
.mdlc.json Normal file
View File

@ -0,0 +1,8 @@
{
"replacementPatterns": [
{
"pattern": "^/(?<path>.*)$",
"replacement": "https://eertmans.be/manim-slides/latest/$<path>.html"
}
]
}

View File

@ -1,13 +1,13 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v4.6.0
hooks:
- id: check-yaml
- id: check-toml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
rev: v2.12.0
rev: v2.13.0
hooks:
- id: pretty-format-yaml
args: [--autofix]
@ -19,13 +19,27 @@ repos:
hooks:
- id: blackdoc
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.14
rev: v0.3.7
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
rev: v1.9.0
hooks:
- id: mypy
additional_dependencies: [types-requests, types-setuptools]
- repo: https://github.com/codespell-project/codespell
rev: v2.2.6
hooks:
- id: codespell
additional_dependencies:
- tomli
- repo: local
hooks:
- id: github-issues
name: GitHub issues link check
description: Check issues (and PR) links are matching number.
entry: python .github/scripts/check_github_issues.py
language: system
types: [markdown]

1
.python-version Normal file
View File

@ -0,0 +1 @@
3.11.8

23
.readthedocs.yaml Normal file
View File

@ -0,0 +1,23 @@
version: 2
build:
os: ubuntu-22.04
tools:
python: '3.10'
apt_packages:
- libpango1.0-dev
- ffmpeg
jobs:
post_install:
- ipython kernel install --name "manim-slides" --user
sphinx:
builder: html
configuration: docs/source/conf.py
fail_on_warning: true
python:
install:
- method: pip
path: .
extra_requirements:
- docs
- magic
- sphinx-directive

View File

@ -7,8 +7,98 @@ 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.1.5...HEAD)
(v5.1.5)=
## [v5.1.5](https://github.com/jeertmans/manim-slides/compare/v5.1.4...v5.1.5)
(v5.1.5)=
### Chore
- Added CI for broken HTML links and fixed, plus spell checking.
[#417](https://github.com/jeertmans/manim-slides/pull/417)
- Create FAQ page and clear FAQ from README.md.
[#418](https://github.com/jeertmans/manim-slides/pull/418)
- Used Rye instead of PDM for faster development.
[#420](https://github.com/jeertmans/manim-slides/pull/420)
(v5.1.5)=
### Fixed
- Fixed broken `--show-config` command.
[#419](https://github.com/jeertmans/manim-slides/pull/419)
(v5.1.4)=
## [v5.1.4](https://github.com/jeertmans/manim-slides/compare/v5.1.3...v5.1.4)
(v5.1.4-added)=
### Added
- Added audio output to `manim-slides present`.
[#382](https://github.com/jeertmans/manim-slides/pull/382)
(v5.1.4-changed)=
### Changed
- Added `--info-window-screen` option and change `--screen-number`
to not move the info window.
[#389](https://github.com/jeertmans/manim-slides/pull/389)
(v5.1.4-chore)=
### Chore
- Created a favicon for the website/documentation.
[#399](https://github.com/jeertmans/manim-slides/pull/399)
- Documented the Nixpkg installation.
[#404](https://github.com/jeertmans/manim-slides/pull/404 )
- Updated the default RevealJS version to 5.1.0.
[#412](https://github.com/jeertmans/manim-slides/pull/412)
- Removed the `opencv-python` dependency.
[#415](https://github.com/jeertmans/manim-slides/pull/415)
(v5.1.4-fixed)=
### Fixed
- Fixed the retrieval of `background_color` with ManimCE.
[#414](https://github.com/jeertmans/manim-slides/pull/414)
- Fixed #390 issue caused by empty media created by ManimCE.
[#416](https://github.com/jeertmans/manim-slides/pull/416)
(v5.1.3)=
## [v5.1.3](https://github.com/jeertmans/manim-slides/compare/v5.1.2...v5.1.3)
(v5.1.3-chore)=
### Chore
- Fix link in documentation.
[#368](https://github.com/jeertmans/manim-slides/pull/368)
- Warn users if not using recommended Qt bindings.
[#373](https://github.com/jeertmans/manim-slides/pull/373)
(v5.1.2)=
## [v5.1.2](https://github.com/jeertmans/manim-slides/compare/v5.1.1...v5.1.2)
(v5.1.2-chore)=
### Chore
- Fix ReadTheDocs version flyout in iframes.
[#367](https://github.com/jeertmans/manim-slides/pull/367)
(v5.1.1)=
## [v5.1.1](https://github.com/jeertmans/manim-slides/compare/v5.1.0...v5.1.1)
(v5.1.1-chore)=
### Chore
- Move documentation to ReadTheDocs for better versioning.
[#365](https://github.com/jeertmans/manim-slides/pull/365)
(v5.1)=
## [v5.1 (Unreleased)](https://github.com/jeertmans/manim-slides/compare/v5.0.0...HEAD)
## [v5.1](https://github.com/jeertmans/manim-slides/compare/v5.0.0...v5.1.0)
(v5.1-added)=
### Added
@ -47,7 +137,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
(v5.1-chore)=
### Chore
- Removed subrocess calls to FFMPEG with direct `libav` bindings using
- Removed subrocess calls to FFmpeg with direct `libav` bindings using
the `av` Python module. This should enhance rendering speed and security.
[#335](https://github.com/jeertmans/manim-slides/pull/335)
- Changed build backend to PDM and reflected on docs.
@ -121,7 +211,7 @@ In an effort to better document changes, this CHANGELOG document is now created.
but the new player should be much easier to maintain and more performant,
than its predecessor.
[#243](https://github.com/jeertmans/manim-slides/pull/243)
- Changed the slide config format to exclude unecessary information.
- Changed the slide config format to exclude unnecessary information.
`StypeType` is removed in favor to one boolean `loop` field. This is
a **breaking change** and one should re-render the slides to apply changes.
[#243](https://github.com/jeertmans/manim-slides/pull/243)

View File

@ -3,25 +3,22 @@
cff-version: 1.2.0
title: Manim Slides
message: A Python package for presenting Manim content anywhere
message: >-
If you use this software, please cite it using the
metadata from this file.
type: software
authors:
- name: Jérome Eertmans
orcid: 'https://orcid.org/0000-0002-5579-5360'
website: 'https://eertmans.be'
identifiers:
- type: doi
value: 10.21105/jose.00206
description: The paper presenting the software.
repository-code: 'https://github.com/jeertmans/manim-slides'
url: 'https://eertmans.be/manim-slides'
abstract: >-
Manim Slides is a Python package that makes presenting
Manim animations straightforward. With minimal changes
required to pre-existing code, one can slide through
animations in a PowerPoint-like manner, or share its
slides online using ReavealJS power.
slides online using ReavealJS' power.
keywords:
- Education
- Math Animations
@ -29,4 +26,19 @@ keywords:
- PowerPoint
- Python
license: MIT
version: v5.1.0-rc4
version: v5.1.5
preferred-citation:
publisher:
name: The Open Journal
type: article
authors:
- name: Jérome Eertmans
orcid: 'https://orcid.org/0000-0002-5579-5360'
doi: 10.21105/jose.00206
journal: Journal of Open Source Education
month: 8
year: 2023
title: 'Manim Slides: A Python package for presenting Manim content anywhere'
volume: 6
number: 66
pages: 206

View File

@ -11,6 +11,7 @@
[![DOI][doi-badge]][doi-url]
[![JOSE Paper][jose-badge]][jose-url]
[![codecov][codecov-badge]][codecov-url]
[![Binder][binder-badge]][binder-url]
# Manim Slides
@ -27,8 +28,7 @@ Manim Slides will *automatically* detect the one you are using!
- [Installation](#installation)
- [Usage](#usage)
- [Comparison with Similar Tools](#comparison-with-similar-tools)
- [F.A.Q](#faq)
* [How to increase quality on Windows](#how-to-increase-quality-on-windows)
- [F.A.Q](https://eertmans.be/manim-slides/latest/faq.html)
- [Contributing](#contributing)
* [Reporting an Issue](#reporting-an-issue)
* [Seeking for Help](#seeking-for-help)
@ -38,7 +38,7 @@ Manim Slides will *automatically* detect the one you are using!
Manim Slides requires either Manim or ManimGL to be installed, along
with their dependencies. Please checkout the
[documentation](https://eertmans.be/manim-slides/installation.html)
[documentation](https://eertmans.be/manim-slides/latest/installation.html)
for detailed install instructions.
## Usage
@ -48,7 +48,7 @@ for detailed install instructions.
Using Manim Slides is a two-step process:
1. Render animations using `Slide` (resp. `ThreeDSlide`) as a base class instead
of `Scene` (resp. `ThreeDScene`), and add calls to `self.next_slide()`
everytime you want to create a new slide.
every time you want to create a new slide.
2. Run `manim-slides` on rendered animations and display them like a
*PowerPoint* presentation.
@ -56,7 +56,7 @@ The documentation is available [online](https://eertmans.be/manim-slides/).
### Basic Example
Call `self.next_slide()` everytime you want to create a pause between
Call `self.next_slide()` every time you want to create a pause between
animations, and `self.next_slide(loop=True)` if you want the next slide to loop
over animations until the user presses continue:
@ -117,7 +117,7 @@ manim-slides BasicExample
</p>
For detailed usage documentation, run `manim-slides --help`, or go to the
[documentation](https://eertmans.be/manim-slides/reference/cli.html).
[documentation](https://eertmans.be/manim-slides/latest/reference/cli.html).
## Interactive Tutorial
@ -149,27 +149,10 @@ Below is a comparison of the most used ones with Manim Slides:
| Web Browser presentations | Yes | No | Yes | No |
| Offline presentations | Yes, with Qt | Yes, with OpenCV | No | No
## F.A.Q
### How to increase quality on Windows
On Windows platform, one may encounter a lower image resolution than expected.
Usually, this is observed because Windows rescales every application to
fit the screen.
As found by [@arashash](https://github.com/arashash),
in [#20](https://github.com/jeertmans/manim-slides/issues/20),
the problem can be addressed by changing the scaling factor to 100%:
<p align="center">
<img alt="Windows Fix Scaling" src="https://raw.githubusercontent.com/jeertmans/manim-slides/main/static/windows_quality_fix.png">
</p>
in *Settings*->*Display*.
## Contributing
Contributions are more than welcome! Please read through
[our contributing section](https://eertmans.be/manim-slides/contributing/index.html).
[our contributing section](https://eertmans.be/manim-slides/latest/contributing/index.html).
### Reporting an Issue
@ -201,12 +184,10 @@ be able to help you!
Sometimes, you may have a question about Manim Slides,
not necessarily an issue.
There are two ways you can reach us for questions:
- via the `Question/Help/Support` topic when
[choosing an issue template](https://github.com/jeertmans/manim-slides/issues/new/choose);
- or via
[GitHub discussions](https://github.com/jeertmans/manim-slides/discussions).
First, make sure to read the
[F.A.Q](https://eertmans.be/manim-slides/latest/faq.html) to see if
your question has already been answered. If not, please follow the
recommendation (from that page) to reach us for questions.
<!-- end seeking-for-help -->
@ -224,12 +205,13 @@ you can do so at: [jeertmans@icloud.com](mailto:jeertmans@icloud.com).
[pypi-version-url]: https://pypi.org/project/manim-slides/
[pypi-python-version-badge]: https://img.shields.io/pypi/pyversions/manim-slides
[pypi-download-badge]: https://img.shields.io/pypi/dm/manim-slides
[documentation-badge]: https://img.shields.io/website?down_color=lightgrey&down_message=offline&label=documentation&up_color=green&up_message=online&url=https%3A%2F%2Feertmans.be%2Fmanim-slides%2F
[documentation-url]: https://eertmans.be/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
[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
[codecov-url]: https://codecov.io/gh/jeertmans/manim-slides
[binder-badge]: https://mybinder.org/badge_logo.svg
[binder-url]: https://mybinder.org/v2/gh/jeertmans/manim-slides-binder/HEAD?filepath=getting_started.ipynb

View File

@ -0,0 +1 @@
../../../static/favicon.png

View File

@ -261,7 +261,7 @@
mouseWheel: {{ mouse_wheel }},
// Opens links in an iframe preview overlay
// Add `data-preview-link` and `data-preview-link="false"` to customise each link
// Add `data-preview-link` and `data-preview-link="false"` to customize each link
// individually
previewLinks: {{ preview_links }},

View File

@ -55,6 +55,7 @@ add_module_names = False
html_theme = "furo"
html_static_path = ["_static"]
html_favicon = "_static/favicon.png"
html_theme_options = {
"light_logo": "logo_light_transparent.png",

View File

@ -18,10 +18,10 @@ workflow
internals
```
[Workflow](./workflow)
[Workflow](/contributing/workflow)
: how to work on this project. Start here if you're a new contributor.
[Internals](./internals)
[Internals](/contributing/internals)
: how Manim Slides is built and how the various parts of it work.
## Reporting an Issue

View File

@ -18,65 +18,31 @@ Useful links:
* [GitHub's Hello World](https://docs.github.com/en/get-started/quickstart/hello-world).
* [GitHub Pull Request in 100 Seconds](https://www.youtube.com/watch?v=8lGpZkjnkt4&ab_channel=Fireship).
Once you feel comfortable with git and GitHub, [fork](https://github.com/jeertmans/manim-slides/fork) the repository, and clone it locally.
Once you feel comfortable with git and GitHub,
[fork](https://github.com/jeertmans/manim-slides/fork)
the repository, and clone it locally.
As for every Python project, using virtual environment is recommended to avoid
conflicts between modules.
For this project, we use [PDM](https://pdm-project.org/) to easily manage project
For this project, we use [Rye](https://rye-up.com/) to easily manage project
and development dependencies. If not already, please install this tool.
## Installing Python modules
With PDM, installation becomes straightforward:
With Rye, installation becomes straightforward:
```bash
pdm install
rye sync --all-features
```
This, however, only installs the minimal set of dependencies to run the package.
If you would like to install Manim or ManimGL,
as documented in the [quickstart](../quickstart),
you can use the `-G|--group` option:
```bash
pdm install -Gmanim # For Manim
# or
pdm install -Gmanimgl # For ManimGL
```
Additionnally, Manim Slides comes with groups of dependencies for development purposes:
```bash
pdm install -Gdev # For linters and formatters
# or
pdm install -Gdocs # To build the documentation locally
# or
pdm install -Gtest # To run tests
```
:::{note}
You can combine any number of groups or extras when installing the package locally.
You can also install everything with `pdm install -G:all`.
:::
## Running commands
Because modules are installed in a new Python environment,
you cannot use them directly in the shell.
Instead, you either need to prepend `pdm run` to any command, e.g.:
Instead, you either need to prepend `rye run` to any command, e.g.:
```bash
pdm run manim-slides wizard
```
or [enter a new shell](https://pdm-project.org/latest/usage/venv/#activate-a-virtualenv)
that uses this new Python environment:
```bash
eval $(pdm venv activate) # Click on the link above to see shell-specific command
manim-slides wizard
rye run manim-slides wizard
```
## Testing your code
@ -85,7 +51,7 @@ Most of the tests are done with GitHub actions, thus not on your computer.
The only command you should run locally is:
```bash
pdm run pre-commit run --all-files
rye run pre-commit run --all-files
```
This runs a few linter and formatter to make sure the code quality and style stay
@ -95,7 +61,7 @@ If a warning or an error is displayed, please fix it before going to next step.
For testing your code, simply run:
```bash
pdm run pytest
rye run pytest
```
## Building the documentation
@ -107,7 +73,7 @@ To generate the documentation, run the following:
```bash
cd docs
pdm run make html
rye run make html
```
Then, the output index file is located at `docs/build/html/index.html` and

122
docs/source/faq.md Normal file
View File

@ -0,0 +1,122 @@
# Frequently Asked Questions
This page summarizes a few of the most frequently asked questions
when using Manim Slides.
They are organized by topic.
If your question is not here, please first look through the
[open **and closed** issues on GitHub](https://github.com/jeertmans/manim-slides/issues?q=is%3Aissue)
or within the [discussions](https://github.com/jeertmans/manim-slides/discussions).
If you still cannot find help after that, do not hesitate to create
your own issue or discussion on GitHub!
## Installing
Everything related to installing Manim-Slides.
Please do not forget the carefully read through
the [installation](/installation) page!
## Rendering
Questions related to `manim-slides render [SCENES]...`,
### I cannot render with ManimGL
ManimGL support is only guaranteed to work
on a very minimal set of versions, because it differs quite a lot from ManimCE,
and its development is not very active.
The typical issues are that (1) ManimGL needs an outdated NumPy version
and (2) ManimGL **should not** be installed from the GitHub repository,
at least not from the `main` branch, but from a version released to PyPI.
To solve the NumPy issue, you can safely downgrade NumPy to a version supported
by ManimGL,
while ignoring the possible *conflicting dependencies* messages from `pip` (or else).
### Presenting
Questions related to `manim-slides present [SCENES]...`,
or `manim-slides [SCENES]...` for short.
### Can I have interactive slides
No. Slides are pre-rendered static videos files
and cannot be modified on the fly.
If you need new to have some kind of interactive, look
at the preview feature coupled with the OpenGL renderer
with ManimCE or ManimGL.
### Slides go black when video finishes
This is an issue with Qt,
which cannot be solve on all platforms and Python versions,
see [#293](https://github.com/jeertmans/manim-slides/issues/293).
### How to increase quality on Windows
On Windows platform, one may encounter a lower image resolution than expected.
Usually, this is observed because Windows rescales every application to
fit the screen.
As found by [@arashash](https://github.com/arashash),
in [#20](https://github.com/jeertmans/manim-slides/issues/20),
the problem can be addressed by changing the scaling factor to 100%:
<p align="center">
<img
alt="Windows Fix Scaling"
src="https://raw.githubusercontent.com/jeertmans/manim-slides/main/static/windows_quality_fix.png"
>
</p>
in *Settings*->*Display*.
## Converting to any format
Questions that apply to all output formats when using
`manim-slides convert [SCENES]...`.
### What are all possible configuration options
Configuration options can be specified with the syntax
`-c<option_name>=<option_value>`.
To list all accepted options, use `manim-slides convert --to=FORMAT --show-config`,
where `FORMAT` is one of the supported formats.
This will also show the default value for each option.
### How to retrieve the current template
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)
or use the `manim-slides convert --to=FORMAT --show-template` command,
where `FORMAT` is one of the supported formats.
## Converting to HTML
Questions related to `manim-slides convert [SCENES]... output.html`.
### I moved my `.html` file and it stopped working
If you did not specify `-cdata_uri=true` when converting,
then Manim Slides generated a folder containing all
the video files, in the same folder as the HTML
output. As the path to video files is a relative path,
you need to move the HTML **and its assets** altogether.
## Converting to PPTX
Questions related to `manim-slides convert [SCENES]... output.pptx`.
### My media stop playing after a few slides
This issue is (probably) caused by PowerPoint never freeing
memory, causing memory allocation errors, and can be partially
solved by reducing the video quality or the number of slides,
see [#392](https://github.com/jeertmans/manim-slides/issues/392).

View File

@ -23,7 +23,7 @@ og:description: Manim Slides makes creating slides with Manim super easy!
Manim Slides makes creating slides with Manim super easy!
In a [very few steps](./quickstart),
In a [very few steps](/quickstart),
you can create slides and present them either using the GUI, or your browser.
Slide through the demo below to get a quick glimpse on what you can do with
@ -43,6 +43,7 @@ installation
reference/index
features_table
manim_or_manimgl
faq
```
```{toctree}

View File

@ -12,7 +12,7 @@ The benefit of using pipx is that it will automatically create a new virtual
environment for every package you install.
:::{note}
Everytime you read `pipx install`, you can use `pip install` instead,
Every time you read `pipx install`, you can use `pip install` instead,
if you are working in a virtual environment or else.
:::
@ -106,7 +106,9 @@ Along with the optional dependencies for Manim and ManimGL,
Manim Slides offers additional *extras*, that can be activated
using optional dependencies:
- `full`, to include `magic`, `manim`, `manimgl`, and
- `full`, to include `magic`, `manim`, and
`sphinx-directive` extras (see below);
- `full-gl`, to include `magic`, `manimgl`, and
`sphinx-directive` extras (see below);
- `magic`, to include a Jupyter magic to render
animations inside notebooks. This automatically installs `manim`,
@ -134,6 +136,38 @@ pipx install -U "manim-slides[extra1,extra2]"
[^2]: Actually, PySide6 can be installed on Python 3.12, but you will then
observe the same visual bug as with PyQt6.
## Nixpkgs installation
Manim Slides is distributed under Nixpkgs >=24.05.
If you are using Nix or NixOS, you can find Manim Slides under:
- `nixpkgs.manim-slides`, which is meant to be a stand alone application and
includes pyqt6 (see above);
- `nixpkgs.python3Packages.manim-slides`, which is meant to be used as a
module (for notebook magics), and includes IPython but not does not include
any Qt bindings.
You can try out the Manim Slides package with
```sh
nix-shell -p manim ffmpeg manim-slides
```
or by adding it to your
[configuration file](https://nixos.org/manual/nixos/stable/#sec-package-management).
Alternatively, you can try Manim Slides in a Python environment with:
```sh
nix-shell -p manim ffmpeg "python3.withPackages(ps: with ps; [ manim-slides, ...])"
```
or bundle this into [your Nix environment](https://wiki.nixos.org/wiki/Python).
:::{note}
Nix current does not support `manimgl`.
:::
## When you need a Qt backend
Before `v5.1`, Manim Slides automatically included PySide6 as
@ -153,5 +187,5 @@ install those are via optional dependencies, as explained above.
An alternative way to install Manim Slides is to clone the git repository,
and build the package from source. Read the
[contributing guide](./contributing/workflow)
[contributing guide](/contributing/workflow)
to know how to process.

View File

@ -15,7 +15,6 @@ the slides.
If both modules are present in {py:data}`sys.modules`, then Manim Slides will
prefer using `manim`.
### Usage
The simplest way to use Manim Slides with the correct Manim API is to:

View File

@ -1,7 +1,7 @@
# Quickstart
If not already, install Manim Slides, along with either Manim or ManimGL,
see [installation](./installation).
see [installation](/installation).
## Creating your first slides
@ -31,4 +31,5 @@ The output slides should look this this:
:quality: high
```
For more advanced examples, see the [Examples](reference/examples) section.
For more advanced examples,
see the [Examples](/reference/examples) section.

View File

@ -2,7 +2,8 @@
Manim Slides' graphical user interface (GUI) is the *de facto* way to present slides.
If you do not specify one of the commands listed in the [CLI reference](./cli),
If you do not specify one of the commands listed in the
[CLI reference](/reference/cli),
Manim Slides will use **present** by default, which launches a GUI window,
playing your scene(s) like so:
@ -25,7 +26,7 @@ directory, you should not worry about that :-)
## Configuration File
It is possible to configure Manim Slides via a configuration file, even though
this feature is currently limited. You may initiliaze the default configuration
this feature is currently limited. You may initialize the default configuration
file with:
```bash

View File

@ -30,11 +30,11 @@ 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/data/revealjs_template.html)
[GitHub](https://github.com/jeertmans/manim-slides/blob/main/manim_slides/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.
## More about HTML Slides
You can read more about HTML slides in the [sharing](./sharing) section.
You can read more about HTML slides in the [sharing](/reference/sharing) section.

View File

@ -16,23 +16,23 @@ sharing
Sphinx Extension <sphinx_extension>
```
[Application Programming Interface](./api): list of classes and methods that may
[Application Programming Interface](/reference/api): list of classes and methods that may
be useful to the end-user.
[Command Line Interface](./cli): list of all commands available using Manim
[Command Line Interface](/reference/cli): list of all commands available using Manim
Slides' executable.
[Examples](./examples): curated list of examples and their output.
[Examples](/reference/examples): curated list of examples and their output.
[Graphical User Interface](./gui): details about the main Manim Slide' feature.
[Graphical User Interface](/reference/gui): details about the main Manim Slide' feature.
[HTML Presentation](./html): an alternative way of presenting your animations.
[HTML Presentation](/reference/html): an alternative way of presenting your animations.
[IPython Magic](./ipython_magic): a magic to render and display Manim Slides inside notebooks.
[IPython Magic](/reference/ipython_magic): a magic to render and display Manim Slides inside notebooks.
+ [Example](./magic_example): example notebook using the magics.
+ [Example](/reference/magic_example): example notebook using the magics.
[Sharing](./sharing): how to share your presentation with others.
[Sharing](/reference/sharing): how to share your presentation with others.
[Sphinx Extension](./sphinx_extension): a Sphinx extension for diplaying Manim Slides animations within your documentation.
[Sphinx Extension](/reference/sphinx_extension): a Sphinx extension for displaying Manim Slides animations within your documentation.

View File

@ -18,6 +18,7 @@
"outputs": [],
"source": [
"from manim import *\n",
"\n",
"from manim_slides import *"
]
},

View File

@ -126,10 +126,15 @@ to use an `iframe`:
</div>
```
<!-- markdown-link-check-disable -->
<!-- see why: https://github.com/tcort/markdown-link-check/discussions/189 -->
The additional code comes from
[this article](https://faq.dailymotion.com/hc/en-us/articles/360022841393-How-to-preserve-the-player-aspect-ratio-on-a-responsive-page)
and it there to preserve the original aspect ratio (16:9).
<!-- markdown-link-check-enable -->
### Sharing ONE HTML file
If you set the `data_uri` option to `true` (with `-cdata_uri=true`),
@ -142,8 +147,8 @@ HTML conversion makes it convenient to play your presentation on a
remote server.
This is how your are able to watch all the examples on this website. If you want
to know how to share your slide with GitHub pages, see the
[workflow file](https://github.com/jeertmans/manim-slides/blob/main/.github/workflows/pages.yml).
to know how to share your slide with GitHub pages, check out the
[Manim Slides Starter GitHub repository template](https://github.com/jeertmans/manim-slides-starter).
:::{warning}
Keep in mind that playing large video files over the internet network

View File

@ -29,7 +29,7 @@ class Module(ModuleType):
return ModuleType.__getattribute__(self, name)
def __dir__(self) -> List[str]:
def __dir__(self) -> list[str]:
result = list(new_module.__all__)
result.extend(
(

View File

@ -1 +1 @@
__version__ = "5.1.0-rc4"
__version__ = "5.1.5"

View File

@ -4,7 +4,7 @@ from functools import wraps
from inspect import Parameter, signature
from pathlib import Path
from textwrap import dedent
from typing import Any, Callable, Dict, List, Optional, Set, Tuple
from typing import Any, Callable, Optional
import rtoml
from pydantic import (
@ -24,7 +24,7 @@ Receiver = Callable[..., Any]
class Signal(BaseModel): # type: ignore[misc]
__receivers: List[Receiver] = PrivateAttr(default_factory=list)
__receivers: list[Receiver] = PrivateAttr(default_factory=list)
def connect(self, receiver: Receiver) -> None:
self.__receivers.append(receiver)
@ -47,14 +47,14 @@ def key_id(name: str) -> PositiveInt:
class Key(BaseModel): # type: ignore[misc]
"""Represents a list of key codes, with optionally a name."""
ids: List[PositiveInt] = Field(unique=True)
ids: list[PositiveInt] = Field(unique=True)
name: Optional[str] = None
__signal: Signal = PrivateAttr(default_factory=Signal)
@field_validator("ids")
@classmethod
def ids_is_non_empty_set(cls, ids: Set[Any]) -> Set[Any]:
def ids_is_non_empty_set(cls, ids: set[Any]) -> set[Any]:
if len(ids) <= 0:
raise ValueError("Key's ids must be a non-empty set")
return ids
@ -98,8 +98,8 @@ class Keys(BaseModel): # type: ignore[misc]
@model_validator(mode="before")
@classmethod
def ids_are_unique_across_keys(cls, values: Dict[str, Key]) -> Dict[str, Key]:
ids: Set[int] = set()
def ids_are_unique_across_keys(cls, values: dict[str, Key]) -> dict[str, Key]:
ids: set[int] = set()
for key in values.values():
if len(ids.intersection(key["ids"])) != 0:
@ -249,7 +249,11 @@ class PreSlideConfig(BaseSlideConfig):
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 approriate 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(...)`."
"You have to play at least one animation (e.g., `self.wait()`) "
"before pausing. If you want to start paused, use the appropriate "
"command-line option when presenting. "
"IMPORTANT: when using ManimGL, `self.wait()` is not considered "
"to be an animation, so prefer to directly use `self.play(...)`."
)
raise ValueError(
@ -292,8 +296,8 @@ class SlideConfig(BaseSlideConfig):
class PresentationConfig(BaseModel): # type: ignore[misc]
slides: List[SlideConfig] = Field(min_length=1)
resolution: Tuple[PositiveInt, PositiveInt] = (1920, 1080)
slides: list[SlideConfig] = Field(min_length=1)
resolution: tuple[PositiveInt, PositiveInt] = (1920, 1080)
background_color: Color = "black"
@classmethod

View File

@ -2,17 +2,17 @@ import mimetypes
import os
import platform
import subprocess
import sys
import tempfile
import webbrowser
from base64 import b64encode
from collections import deque
from enum import Enum
from importlib import resources
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Type, Union
from typing import Any, Callable, Optional, Union
import av
import click
import cv2
import pptx
from click import Context, Parameter
from jinja2 import Template
@ -52,7 +52,7 @@ def open_with_default(file: Path) -> None:
def validate_config_option(
ctx: Context, param: Parameter, value: Any
) -> Dict[str, str]:
) -> dict[str, str]:
config = {}
for c_option in value:
@ -79,11 +79,23 @@ def file_to_data_uri(file: Path) -> str:
def get_duration_ms(file: Path) -> float:
"""Read a video and return its duration in milliseconds."""
cap = cv2.VideoCapture(str(file))
fps: int = cap.get(cv2.CAP_PROP_FPS)
frame_count: int = cap.get(cv2.CAP_PROP_FRAME_COUNT)
with av.open(str(file)) as container:
video = container.streams.video[0]
return 1000 * frame_count / fps
return float(1000 * video.duration * video.time_base)
def read_image_from_video_file(file: Path, frame_index: "FrameIndex") -> Image:
"""Read a image from a video file at a given index."""
with av.open(str(file)) as container:
frames = container.decode(video=0)
if frame_index == FrameIndex.last:
(frame,) = deque(frames, 1)
else:
frame = next(frames)
return frame.to_image()
class Converter(BaseModel): # type: ignore
@ -108,7 +120,7 @@ class Converter(BaseModel): # type: ignore
raise NotImplementedError
@classmethod
def from_string(cls, s: str) -> Type["Converter"]:
def from_string(cls, s: str) -> type["Converter"]:
"""Return the appropriate converter from a string name."""
return {
"html": RevealJS,
@ -316,7 +328,7 @@ class RevealJS(Converter):
auto_animate_easing: AutoAnimateEasing = AutoAnimateEasing.ease
auto_animate_duration: float = 1.0
auto_animate_unmatched: JsBool = JsBool.true
auto_animate_styles: List[str] = Field(
auto_animate_styles: list[str] = Field(
default_factory=lambda: [
"opacity",
"color",
@ -355,7 +367,7 @@ class RevealJS(Converter):
hide_cursor_time: int = 5000
# Appearance options from RevealJS
background_color: Color = "black"
reveal_version: str = "4.6.1"
reveal_version: str = "5.1.0"
reveal_theme: RevealTheme = RevealTheme.black
title: str = "Manim Slides"
# Pydantic options
@ -366,9 +378,6 @@ class RevealJS(Converter):
if isinstance(self.template, Path):
return self.template.read_text()
if sys.version_info < (3, 9):
return resources.read_text(templates, "revealjs.html")
return resources.files(templates).joinpath("revealjs.html").read_text()
def open(self, file: Path) -> bool:
@ -416,6 +425,7 @@ class RevealJS(Converter):
file_to_data_uri=file_to_data_uri,
get_duration_ms=get_duration_ms,
has_notes=has_notes,
env=os.environ,
**options,
)
@ -437,23 +447,6 @@ class PDF(Converter):
def convert_to(self, dest: Path) -> None:
"""Convert this configuration into a PDF presentation, saved to DEST."""
def read_image_from_video_file(file: Path, frame_index: FrameIndex) -> Image:
cap = cv2.VideoCapture(str(file))
if frame_index == FrameIndex.last:
index = cap.get(cv2.CAP_PROP_FRAME_COUNT)
cap.set(cv2.CAP_PROP_POS_FRAMES, index - 1)
ret, frame = cap.read()
cap.release()
if ret:
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
return Image.fromarray(frame)
else:
raise ValueError("Failed to read {image_index} image from video file")
images = []
for i, presentation_config in enumerate(self.presentation_configs):
@ -489,7 +482,7 @@ class PowerPoint(Converter):
def open(self, file: Path) -> None:
return open_with_default(file)
def convert_to(self, dest: Path) -> None: # noqa: C901
def convert_to(self, dest: Path) -> None:
"""Convert this configuration into a PowerPoint presentation, saved to DEST."""
prs = pptx.Presentation()
prs.slide_width = self.width * 9525
@ -518,53 +511,48 @@ class PowerPoint(Converter):
nsmap = {"p": "http://schemas.openxmlformats.org/presentationml/2006/main"}
return etree.ElementBase.xpath(el, query, namespaces=nsmap)
def save_first_image_from_video_file(file: Path) -> Optional[str]:
cap = cv2.VideoCapture(file.as_posix())
ret, frame = cap.read()
cap.release()
with tempfile.TemporaryDirectory() as directory_name:
directory = Path(directory_name)
frame_number = 0
for i, presentation_config in enumerate(self.presentation_configs):
for slide_config in tqdm(
presentation_config.slides,
desc=f"Generating video slides for config {i + 1}",
leave=False,
):
file = slide_config.file
if ret:
f = tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".png")
cv2.imwrite(f.name, frame)
f.close()
return f.name
else:
logger.warn("Failed to read first image from video file")
return None
mime_type = mimetypes.guess_type(file)[0]
for i, presentation_config in enumerate(self.presentation_configs):
for slide_config in tqdm(
presentation_config.slides,
desc=f"Generating video slides for config {i + 1}",
leave=False,
):
file = slide_config.file
if self.poster_frame_image is None:
poster_frame_image = str(directory / f"{frame_number}.png")
image = read_image_from_video_file(
file, frame_index=FrameIndex.first
)
image.save(poster_frame_image)
mime_type = mimetypes.guess_type(file)[0]
frame_number += 1
else:
poster_frame_image = str(self.poster_frame_image)
if self.poster_frame_image is None:
poster_frame_image = save_first_image_from_video_file(file)
else:
poster_frame_image = str(self.poster_frame_image)
slide = prs.slides.add_slide(layout)
movie = slide.shapes.add_movie(
str(file),
self.left,
self.top,
self.width * 9525,
self.height * 9525,
poster_frame_image=poster_frame_image,
mime_type=mime_type,
)
if slide_config.notes != "":
slide.notes_slide.notes_text_frame.text = slide_config.notes
slide = prs.slides.add_slide(layout)
movie = slide.shapes.add_movie(
str(file),
self.left,
self.top,
self.width * 9525,
self.height * 9525,
poster_frame_image=poster_frame_image,
mime_type=mime_type,
)
if slide_config.notes != "":
slide.notes_slide.notes_text_frame.text = slide_config.notes
if self.auto_play_media:
auto_play_media(movie, loop=slide_config.loop)
if self.auto_play_media:
auto_play_media(movie, loop=slide_config.loop)
dest.parent.mkdir(parents=True, exist_ok=True)
prs.save(dest)
dest.parent.mkdir(parents=True, exist_ok=True)
prs.save(dest)
def show_config_options(function: Callable[..., Any]) -> Callable[..., Any]:
@ -576,11 +564,14 @@ def show_config_options(function: Callable[..., Any]) -> Callable[..., Any]:
to = ctx.params.get("to", "html")
converter = Converter.from_string(to)(
presentation_configs=[PresentationConfig()]
)
for key, value in converter.dict().items():
click.echo(f"{key}: {value!r}")
converter = Converter.from_string(to)
for key, field in converter.model_fields.items():
if field.is_required():
continue
default = field.get_default(call_default_factory=True)
click.echo(f"{key}: {default}")
ctx.exit()
@ -639,7 +630,7 @@ def show_template_option(function: Callable[..., Any]) -> Callable[..., Any]:
"--open",
"open_result",
is_flag=True,
help="Open the newly created file using the approriate application.",
help="Open the newly created file using the appropriate application.",
)
@click.option("-f", "--force", is_flag=True, help="Overwrite any existing file.")
@click.option(
@ -663,13 +654,13 @@ def show_template_option(function: Callable[..., Any]) -> Callable[..., Any]:
@show_config_options
@verbosity_option
def convert(
scenes: List[str],
scenes: list[str],
folder: Path,
dest: Path,
to: str,
open_result: bool,
force: bool,
config_options: Dict[str, str],
config_options: dict[str, str],
template: Optional[Path],
) -> None:
"""Convert SCENE(s) into a given format and writes the result in DEST."""
@ -703,7 +694,7 @@ def convert(
errors = e.errors()
msg = [
f"{len(errors)} error(s) occured with configuration options for '{to}', see below."
f"{len(errors)} error(s) occurred with configuration options for '{to}', see below."
]
for error in errors:

View File

@ -174,7 +174,9 @@ Renders as follows:
self.next_slide()
self.zoom(text)
""" # noqa: D400, D415
from __future__ import annotations
import csv

View File

@ -144,6 +144,7 @@ class ManimSlidesMagic(Magics): # type: ignore
In case you want to hide the red box containing the output progress bar, the ``progress_bar`` config
option should be set to ``None``. This can also be done by passing ``--progress_bar None`` as a
CLI flag.
"""
if local_ns is None:
local_ns = {}
@ -248,9 +249,7 @@ class ManimSlidesMagic(Magics): # type: ignore
)
else:
result = HTML(
"""<div style="position:relative;padding-bottom:56.25%;"><iframe style="width:100%;height:100%;position:absolute;left:0px;top:0px;" frameborder="0" width="100%" height="100%" allowfullscreen allow="autoplay" src="{src}"></iframe></div>""".format(
src=tmpfile.as_posix()
)
f"""<div style="position:relative;padding-bottom:56.25%;"><iframe style="width:100%;height:100%;position:absolute;left:0px;top:0px;" frameborder="0" width="100%" height="100%" allowfullscreen allow="autoplay" src="{tmpfile.as_posix()}"></iframe></div>"""
)
display(result)

View File

@ -11,6 +11,23 @@ from ..commons import config_path_option, folder_path_option, verbosity_option
from ..config import Config, PresentationConfig
from ..logger import logger
PREFERRED_QT_VERSIONS = ("6.5.1", "6.5.2")
def warn_if_non_desirable_pyside6_version() -> None:
from qtpy import API, QT_VERSION
if sys.version_info < (3, 12) and (
API != "pyside6" or QT_VERSION not in PREFERRED_QT_VERSIONS
):
logger.warn(
f"You are using {API = }, {QT_VERSION = }, "
"but we recommend installing 'PySide6==6.5.2', mainly to avoid "
"flashing screens between slides, "
"see issue https://github.com/jeertmans/manim-slides/issues/293. "
"You can do so with `pip install 'manim-slides[pyside6]'`."
)
@click.command()
@folder_path_option
@ -22,7 +39,7 @@ def list_scenes(folder: Path) -> None:
click.secho(f"{i}: {scene}", fg="green")
def _list_scenes(folder: Path) -> List[str]:
def _list_scenes(folder: Path) -> list[str]:
"""List available scenes in given directory."""
scenes = []
@ -42,7 +59,7 @@ def _list_scenes(folder: Path) -> List[str]:
return scenes
def prompt_for_scenes(folder: Path) -> List[str]:
def prompt_for_scenes(folder: Path) -> list[str]:
"""Prompt the user to select scenes within a given folder."""
scene_choices = dict(enumerate(_list_scenes(folder), start=1))
@ -54,7 +71,7 @@ def prompt_for_scenes(folder: Path) -> List[str]:
click.echo("Choose number corresponding to desired scene/arguments.")
click.echo("(Use comma separated list for multiple entries)")
def value_proc(value: Optional[str]) -> List[str]:
def value_proc(value: Optional[str]) -> list[str]:
indices = list(map(int, (value or "").strip().replace(" ", "").split(",")))
if not all(0 < i <= len(scene_choices) for i in indices):
@ -76,8 +93,8 @@ def prompt_for_scenes(folder: Path) -> List[str]:
def get_scenes_presentation_config(
scenes: List[str], folder: Path
) -> List[PresentationConfig]:
scenes: list[str], folder: Path
) -> list[PresentationConfig]:
"""Return a list of presentation configurations based on the user input."""
if len(scenes) == 0:
scenes = prompt_for_scenes(folder)
@ -99,7 +116,7 @@ def get_scenes_presentation_config(
def start_at_callback(
ctx: Context, param: Parameter, values: str
) -> Tuple[Optional[int], ...]:
) -> tuple[Optional[int], ...]:
if values == "(None, None)":
return (None, None)
@ -225,10 +242,18 @@ def start_at_callback(
is_flag=True,
help="Hide info window.",
)
@click.option(
"--info-window-screen",
"info_window_screen_number",
metavar="NUMBER",
type=int,
default=None,
help="Put info window on the given screen (a.k.a. display).",
)
@click.help_option("-h", "--help")
@verbosity_option
def present(
scenes: List[str],
scenes: list[str],
config_path: Path,
folder: Path,
start_paused: bool,
@ -237,13 +262,14 @@ def present(
exit_after_last_slide: bool,
hide_mouse: bool,
aspect_ratio: str,
start_at: Tuple[Optional[int], Optional[int], Optional[int]],
start_at: tuple[Optional[int], Optional[int], Optional[int]],
start_at_scene_number: int,
start_at_slide_number: int,
screen_number: Optional[int],
playback_rate: float,
next_terminates_loop: bool,
hide_info_window: bool,
info_window_screen_number: Optional[int],
) -> None:
"""
Present SCENE(s), one at a time, in order.
@ -276,32 +302,42 @@ def present(
if start_at[1]:
start_at_slide_number = start_at[1]
warn_if_non_desirable_pyside6_version()
from qtpy.QtCore import Qt
from qtpy.QtGui import QScreen
from ..qt_utils import qapp
from .player import Player
app = qapp()
app.setApplicationName("Manim Slides")
if screen_number is not None:
def get_screen(number: int) -> Optional[QScreen]:
try:
screen = app.screens()[screen_number]
return app.screens()[number]
except IndexError:
logger.error(
f"Invalid screen number {screen_number}, "
f"Invalid screen number {number}, "
f"allowed values are from 0 to {len(app.screens())-1} (incl.)"
)
screen = None
return None
if screen_number is not None:
screen = get_screen(screen_number)
else:
screen = None
from qtpy.QtCore import Qt
if info_window_screen_number is not None:
info_window_screen = get_screen(info_window_screen_number)
else:
info_window_screen = None
aspect_ratio_modes = {
"keep": Qt.KeepAspectRatio,
"ignore": Qt.IgnoreAspectRatio,
}
from .player import Player
player = Player(
config,
presentation_configs,
@ -317,6 +353,7 @@ def present(
playback_rate=playback_rate,
next_terminates_loop=next_terminates_loop,
hide_info_window=hide_info_window,
info_window_screen=info_window_screen,
)
player.show()

View File

@ -1,10 +1,10 @@
from datetime import datetime
from pathlib import Path
from typing import List, Optional
from typing import Optional
from qtpy.QtCore import Qt, QTimer, QUrl, Signal, Slot
from qtpy.QtGui import QCloseEvent, QIcon, QKeyEvent, QScreen
from qtpy.QtMultimedia import QMediaPlayer
from qtpy.QtMultimedia import QAudioOutput, QMediaPlayer
from qtpy.QtMultimediaWidgets import QVideoWidget
from qtpy.QtWidgets import (
QHBoxLayout,
@ -56,7 +56,7 @@ class Info(QWidget): # type: ignore[misc]
self.video_sink = main_video_widget.videoSink()
left_layout.addWidget(main_video_widget)
# Current slide informations
# Current slide information
self.scene_label = QLabel()
self.slide_label = QLabel()
@ -169,7 +169,7 @@ class Player(QMainWindow): # type: ignore[misc]
def __init__(
self,
config: Config,
presentation_configs: List[PresentationConfig],
presentation_configs: list[PresentationConfig],
*,
start_paused: bool = False,
full_screen: bool = False,
@ -183,6 +183,7 @@ class Player(QMainWindow): # type: ignore[misc]
playback_rate: float = 1.0,
next_terminates_loop: bool = False,
hide_info_window: bool = False,
info_window_screen: Optional[QScreen] = None,
):
super().__init__()
@ -225,12 +226,14 @@ class Player(QMainWindow): # type: ignore[misc]
self.icon = QIcon(":/icon.png")
self.setWindowIcon(self.icon)
self.audio_output = QAudioOutput()
self.video_widget = QVideoWidget()
self.video_sink = self.video_widget.videoSink()
self.video_widget.setAspectRatioMode(aspect_ratio_mode)
self.setCentralWidget(self.video_widget)
self.media_player = QMediaPlayer(self)
self.media_player.setAudioOutput(self.audio_output)
self.media_player.setVideoOutput(self.video_widget)
self.playback_rate = playback_rate
@ -238,7 +241,9 @@ class Player(QMainWindow): # type: ignore[misc]
self.slide_changed.connect(self.slide_changed_callback)
self.info = Info(
full_screen=full_screen, aspect_ratio_mode=aspect_ratio_mode, screen=screen
full_screen=full_screen,
aspect_ratio_mode=aspect_ratio_mode,
screen=info_window_screen,
)
self.info.close_event.connect(self.closeEvent)
self.info.key_press_event.connect(self.keyPressEvent)

View File

@ -12,7 +12,6 @@ This is especially useful for two reasons:
import subprocess
import sys
from typing import Tuple
import click
@ -39,7 +38,7 @@ import click
help="If set, use ManimGL renderer.",
)
@click.argument("args", metavar="[RENDERER_ARGS]...", nargs=-1, type=click.UNPROCESSED)
def render(ce: bool, gl: bool, args: Tuple[str, ...]) -> None:
def render(ce: bool, gl: bool, args: tuple[str, ...]) -> None:
"""
Render SCENE(s) from the input FILE, using the specified renderer.

View File

@ -11,7 +11,8 @@ that directly calls ``self.play(Animation(...))``, see
__all__ = ["Wipe", "Zoom"]
from typing import Any, Mapping, Optional, Sequence
from collections.abc import Mapping, Sequence
from typing import Any, Optional
import numpy as np
@ -58,6 +59,7 @@ class Wipe(AnimationGroup): # type: ignore[misc]
self.next_slide()
self.play(Wipe(circle, square, shift=3 * LEFT))
"""
def __init__(
@ -122,6 +124,7 @@ class Zoom(AnimationGroup): # type: ignore[misc]
for i in range(2):
self.play(Zoom(circles[i], circles[i+1]))
self.next_slide()
"""
def __init__(

View File

@ -4,13 +4,11 @@ __all__ = ["BaseSlide"]
import platform
from abc import abstractmethod
from collections.abc import MutableMapping, Sequence, ValuesView
from pathlib import Path
from typing import (
TYPE_CHECKING,
Any,
MutableMapping,
Sequence,
ValuesView,
)
import numpy as np
@ -156,6 +154,7 @@ class BaseSlide:
self.remove_from_canvas("title", "slide_number")
self.wipe(self.mobjects_without_canvas, [])
"""
return self._canvas
@ -248,6 +247,7 @@ class BaseSlide:
self.next_slide()
self.play(FadeOut(circle))
"""
return self._wait_time_between_slides
@ -410,6 +410,7 @@ class BaseSlide:
self.next_slide(notes="Bye bye")
self.zoom(square)
"""
if self._current_animation > self._start_animation:
if self.wait_time_between_slides > 0.0:
@ -564,6 +565,7 @@ class BaseSlide:
return_animation=True
)
self.play(anim)
"""
from .animation import Wipe
@ -628,6 +630,7 @@ class BaseSlide:
return_animation=True
)
self.play(anim)
"""
from .animation import Zoom

View File

@ -1,5 +1,5 @@
from pathlib import Path
from typing import Any, List, Optional, Tuple
from typing import Any, Optional
from manim import Scene, ThreeDScene, config
@ -23,18 +23,18 @@ class Slide(BaseSlide, Scene): # type: ignore[misc]
@property
def _background_color(self) -> str:
color = config["background_color"]
color = self.camera.background_color
if hex_color := getattr(color, "hex", None):
return hex_color # type: ignore
else: # manim>=0.18, see https://github.com/ManimCommunity/manim/pull/3020
return color.to_hex() # type: ignore
@property
def _resolution(self) -> Tuple[int, int]:
def _resolution(self) -> tuple[int, int]:
return config["pixel_width"], config["pixel_height"]
@property
def _partial_movie_files(self) -> List[Path]:
def _partial_movie_files(self) -> list[Path]:
# When rendering with -na,b (manim only)
# the animations not in [a,b] will be skipped,
# but animation before a will have a None source file.
@ -144,6 +144,7 @@ class ThreeDSlide(Slide, ThreeDScene): # type: ignore[misc]
self.next_slide()
self.play(*[FadeOut(mobject) for mobject in self.mobjects])
"""
pass

View File

@ -1,5 +1,5 @@
from pathlib import Path
from typing import Any, ClassVar, Dict, List, Optional, Tuple
from typing import Any, ClassVar, Optional
from manimlib import Scene, ThreeDCamera
from manimlib.utils.file_ops import get_sorted_integer_files
@ -31,11 +31,11 @@ class Slide(BaseSlide, Scene): # type: ignore[misc]
return self.camera_config["background_color"].hex # type: ignore
@property
def _resolution(self) -> Tuple[int, int]:
def _resolution(self) -> tuple[int, int]:
return self.camera_config["pixel_width"], self.camera_config["pixel_height"]
@property
def _partial_movie_files(self) -> List[Path]:
def _partial_movie_files(self) -> list[Path]:
kwargs = {
"remove_non_integer_files": True,
"extension": self.file_writer.movie_file_extension,
@ -66,7 +66,7 @@ class Slide(BaseSlide, Scene): # type: ignore[misc]
class ThreeDSlide(Slide):
CONFIG: ClassVar[Dict[str, Any]] = {
CONFIG: ClassVar[dict[str, Any]] = {
"camera_class": ThreeDCamera,
}
pass

View File

@ -262,7 +262,7 @@
mouseWheel: {{ mouse_wheel }},
// Opens links in an iframe preview overlay
// Add `data-preview-link` and `data-preview-link="false"` to customise each link
// Add `data-preview-link` and `data-preview-link="false"` to customize each link
// individually
previewLinks: {{ preview_links }},
@ -316,28 +316,36 @@
hideCursorTime: {{ hide_cursor_time }}
});
{% if data_uri %}
// Fix found by @t-fritsch on GitHub
// see: https://github.com/hakimel/reveal.js/discussions/3362#discussioncomment-6651475.
function fixBase64VideoBackground(event) {
// event.previousSlide, event.currentSlide, event.indexh, event.indexv
if (event.currentSlide.getAttribute('data-background-video')) {
const background = Reveal.getSlideBackground(event.indexh, event.indexv),
video = background.querySelector('video'),
sources = video.querySelectorAll('source');
{% if data_uri -%}
// Fix found by @t-fritsch on GitHub
// see: https://github.com/hakimel/reveal.js/discussions/3362#discussioncomment-6651475.
function fixBase64VideoBackground(event) {
// event.previousSlide, event.currentSlide, event.indexh, event.indexv
if (event.currentSlide.getAttribute('data-background-video')) {
const background = Reveal.getSlideBackground(event.indexh, event.indexv),
video = background.querySelector('video'),
sources = video.querySelectorAll('source');
sources.forEach((source, i) => {
const src = source.getAttribute('src');
if(src.match(/^data:video.*;base64$/)) {
const nextSrc = sources[i+1]?.getAttribute('src');
video.setAttribute('src', `${src},${nextSrc}`);
}
});
}
sources.forEach((source, i) => {
const src = source.getAttribute('src');
if(src.match(/^data:video.*;base64$/)) {
const nextSrc = sources[i+1]?.getAttribute('src');
video.setAttribute('src', `${src},${nextSrc}`);
}
});
}
Reveal.on( 'ready', fixBase64VideoBackground );
Reveal.on( 'slidechanged', fixBase64VideoBackground );
{% endif %}
}
Reveal.on( 'ready', fixBase64VideoBackground );
Reveal.on( 'slidechanged', fixBase64VideoBackground );
{%- endif %}
</script>
{% if env['READTHEDOCS'] -%}
<style>
readthedocs-flyout, readthedocs-notification {
display: none;
}
</style>
{%- endif %}
</body>
</html>

View File

@ -1,40 +1,68 @@
import hashlib
import os
import tempfile
from collections.abc import Iterator
from pathlib import Path
from typing import List
import av
from .logger import logger
def concatenate_video_files(files: List[Path], dest: Path) -> None:
def concatenate_video_files(files: list[Path], dest: Path) -> None:
"""Concatenate multiple video files into one."""
f = tempfile.NamedTemporaryFile(mode="w", delete=False)
f.writelines(f"file '{path.absolute()}'\n" for path in files)
f.close()
input_ = av.open(f.name, options={"safe": "0"}, format="concat")
input_stream = input_.streams.video[0]
output = av.open(str(dest), mode="w")
output_stream = output.add_stream(
template=input_stream,
)
def _filter(files: list[Path]) -> Iterator[Path]:
"""Patch possibly empty video files."""
for file in files:
with av.open(str(file)) as container:
if len(container.streams.video) > 0:
yield file
else:
logger.warn(
f"Skipping video file {file} because it does "
"not contain any video stream. "
"This is probably caused by Manim, see: "
"https://github.com/jeertmans/manim-slides/issues/390."
)
for packet in input_.demux(input_stream):
# We need to skip the "flushing" packets that `demux` generates.
if packet.dts is None:
continue
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
f.writelines(f"file '{file}'\n" for file in _filter(files))
tmp_file = f.name
# We need to assign the packet to the new stream.
packet.stream = output_stream
output.mux(packet)
with (
av.open(tmp_file, format="concat", options={"safe": "0"}) as input_container,
av.open(str(dest), mode="w") as output_container,
):
input_video_stream = input_container.streams.video[0]
output_video_stream = output_container.add_stream(
template=input_video_stream,
)
input_.close()
output.close()
if len(input_container.streams.audio) > 0:
input_audio_stream = input_container.streams.audio[0]
output_audio_stream = output_container.add_stream(
template=input_audio_stream,
)
for packet in input_container.demux():
if packet.dts is None:
continue
ptype = packet.stream.type
if ptype == "video":
packet.stream = output_video_stream
elif ptype == "audio":
packet.stream = output_audio_stream
else:
continue # We don't support subtitles
output_container.mux(packet)
os.unlink(tmp_file) # https://stackoverflow.com/a/54768241
def merge_basenames(files: List[Path]) -> Path:
def merge_basenames(files: list[Path]) -> Path:
"""Merge multiple filenames by concatenating basenames."""
if len(files) == 0:
raise ValueError("Cannot merge an empty list of files!")
@ -62,37 +90,38 @@ def link_nodes(*nodes: av.filter.context.FilterContext) -> None:
def reverse_video_file(src: Path, dest: Path) -> None:
"""Reverses a video file, writting the result to `dest`."""
input_ = av.open(str(src))
input_stream = input_.streams.video[0]
output = av.open(str(dest), mode="w")
output_stream = output.add_stream(codec_name="libx264", rate=input_stream.base_rate)
output_stream.width = input_stream.width
output_stream.height = input_stream.height
output_stream.pix_fmt = input_stream.pix_fmt
"""Reverses a video file, writing the result to `dest`."""
with (
av.open(str(src)) as input_container,
av.open(str(dest), mode="w") as output_container,
):
input_stream = input_container.streams.video[0]
output_stream = output_container.add_stream(
codec_name="libx264", rate=input_stream.base_rate
)
output_stream.width = input_stream.width
output_stream.height = input_stream.height
output_stream.pix_fmt = input_stream.pix_fmt
graph = av.filter.Graph()
link_nodes(
graph.add_buffer(template=input_stream),
graph.add("reverse"),
graph.add("buffersink"),
)
graph.configure()
graph = av.filter.Graph()
link_nodes(
graph.add_buffer(template=input_stream),
graph.add("reverse"),
graph.add("buffersink"),
)
graph.configure()
frames_count = 0
for frame in input_.decode(video=0):
graph.push(frame)
frames_count += 1
frames_count = 0
for frame in input_container.decode(video=0):
graph.push(frame)
frames_count += 1
graph.push(None) # EOF: https://github.com/PyAV-Org/PyAV/issues/886.
graph.push(None) # EOF: https://github.com/PyAV-Org/PyAV/issues/886.
for _ in range(frames_count):
frame = graph.pull()
frame.pict_type = 5 # Otherwise we get a warning saying it is changed
output.mux(output_stream.encode(frame))
for _ in range(frames_count):
frame = graph.pull()
frame.pict_type = 5 # Otherwise we get a warning saying it is changed
output_container.mux(output_stream.encode(frame))
for packet in output_stream.encode():
output.mux(packet)
input_.close()
output.close()
for packet in output_stream.encode():
output_container.mux(packet)

View File

@ -66,7 +66,7 @@ provide new features on a regular basis.
# Easy to Use Commitment
Manim Slides is commited to be an easy-to-use tool, with minimal installation
Manim Slides is committed to be an easy-to-use tool, with minimal installation
procedure and few modifications required. It can either be used locally with its
graphical user interface (GUI), or shared via one of the two formats it can
convert to:
@ -96,13 +96,13 @@ and posted it on YouTube.
# Stability and releases
Manim Slides is continously tested on most recent Python versions, both ManimCE
Manim Slides is continuously tested on most recent Python versions, both ManimCE
and ManimGL, and on all major platforms: **Ubuntu**, **macOS** and **Windows**. Due to Manim
Slide's exposed API being very minimal, and the variety of tests that are
performed, this tool can be considered stable over time.
New releases are very frequent, as they mostly introduce enhancements or small
documention fixes, and the command-line tool automatically notifies for new
documentation fixes, and the command-line tool automatically notifies for new
available updates. Therefore, regularly updating Manim Slides is highly
recommended.
@ -160,7 +160,7 @@ For new feature requests, we highly encourage users to
[create an issue](https://github.com/jeertmans/manim-slides/issues/new/choose)
with the appropriate template.
# Acknowledgements
# Acknowledgments
We acknowledge the work of @manim-presentation that paved the initial structure
of Manim Slides with the manim-presentation Python package.

3463
pdm.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[build-system]
build-backend = "pdm.backend"
requires = ["pdm-backend", "setuptools"]
build-backend = "hatchling.build"
requires = ["hatchling"]
[project]
authors = [{name = "Jérome Eertmans", email = "jeertmans@icloud.com"}]
@ -23,7 +23,6 @@ dependencies = [
"jinja2>=3.1.2",
"lxml>=4.9.2",
"numpy>=1.19",
"opencv-python>=4.6.0.66",
"pillow>=9.5.0",
"pydantic>=2.0.1",
"pydantic-extra-types>=2.0.0",
@ -40,15 +39,30 @@ keywords = ["manim", "slides", "plugin", "manimgl"]
license = {text = "MIT"}
name = "manim-slides"
readme = "README.md"
requires-python = ">=3.9,<3.13"
requires-python = ">=3.9"
[project.optional-dependencies]
docs = [
"manim-slides[magic,sphinx-directive]",
"furo>=2023.5.20",
"ipykernel>=6.25.1",
"myst-parser>=2.0.0",
"nbsphinx>=0.9.2",
"pandoc>=2.3",
"sphinx>=7.0.1",
"sphinx-click>=4.4.0",
"sphinx-copybutton>=0.5.1",
"sphinxext-opengraph>=0.7.5",
]
full = [
"manim-slides[magic,manim,manimgl,sphinx-directive]",
"manim-slides[magic,manim,sphinx-directive]",
]
full-gl = [
"manim-slides[magic,manimgl,sphinx-directive]",
]
magic = ["manim-slides[manim]", "ipython>=8.12.2"]
manim = ["manim>=0.17.3"]
manimgl = ["manimgl>=1.6.1"]
manimgl = ["manimgl>=1.6.1;python_version<'3.12'"]
pyqt6 = ["pyqt6>=6.6.1"]
pyqt6-full = ["manim-slides[full,pyqt6]"]
pyside6 = ["pyside6>=6.5.1,<6.5.3;python_version<'3.12'"]
@ -65,6 +79,12 @@ Founding = "https://github.com/sponsors/jeertmans"
Homepage = "https://github.com/jeertmans/manim-slides"
Repository = "https://github.com/jeertmans/manim-slides"
[tool.codespell]
builtin = "clear,rare,informal,usage,names,en-GB_to_en-US"
check-hidden = true
ignore-words-list = "master"
skip = "requirements.lock,requirements-dev.lock"
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
@ -74,47 +94,15 @@ exclude_lines = [
]
precision = 2
[tool.hatch.version]
path = "manim_slides/__version__.py"
[tool.mypy]
disallow_untyped_decorators = false
install_types = true
python_version = "3.8"
python_version = "3.9"
strict = true
[tool.pdm.dev-dependencies]
dev = [
"bump2version>=1.0.1",
"pre-commit>=3.5.0",
]
docs = [
"manim-slides[magic,sphinx-directive]",
"furo>=2023.5.20",
"ipykernel>=6.25.1",
"myst-parser>=2.0.0",
"nbsphinx>=0.9.2",
"pandoc>=2.3",
"sphinx>=7.0.1",
"sphinx-click>=4.4.0",
"sphinx-copybutton>=0.5.1",
"sphinxext-opengraph>=0.7.5",
]
github-action = ["setuptools"]
test = [
"manim-slides[manim,manimgl,pyqt6]",
"pytest>=7.4.0",
"pytest-cov>=4.1.0",
"pytest-env>=0.8.2",
"pytest-qt>=4.2.0",
"pytest-xdist>=3.3.1",
]
[tool.pdm.resolution.overrides]
manimpango = "<1.0.0,>=0.5.0" # This conflicts with ManimGL, hopefully not an issue
skia-pathops = "0.8.0.post1" # From manim 0.18.0 (Python 3.12 support)
[tool.pdm.version]
path = "manim_slides/__version__.py"
source = "file"
[tool.pytest.ini_options]
env = [
"QT_QPA_PLATFORM=offscreen",
@ -126,6 +114,11 @@ filterwarnings = [
[tool.ruff]
extend-exclude = ["manim_slides/resources.py"]
extend-include = ["*.ipynb"]
line-length = 88
target-version = "py39"
[tool.ruff.lint]
extend-ignore = [
"D100",
"D101",
@ -140,8 +133,18 @@ extend-ignore = [
"D212",
"E501",
]
extend-include = ["*.ipynb"]
extend-select = ["B", "C90", "D", "I", "N", "RUF", "UP", "T"]
isort = {known-first-party = ["manim_slides", "tests"]}
line-length = 88
target-version = "py38"
[tool.rye]
dev-dependencies = [
"bump2version>=1.0.1",
"manim-slides[manim,manimgl,pyqt6]",
"pre-commit>=3.5.0",
"pytest>=7.4.0",
"pytest-cov>=4.1.0",
"pytest-env>=0.8.2",
"pytest-qt>=4.2.0",
"pytest-xdist>=3.3.1",
]
managed = true

419
requirements-dev.lock Normal file
View File

@ -0,0 +1,419 @@
# generated by rye
# use `rye lock` or `rye sync` to update this lockfile
#
# last locked with the following flags:
# pre: false
# features: []
# all-features: true
# with-sources: false
-e file:.
alabaster==0.7.16
# via sphinx
annotated-types==0.6.0
# via pydantic
asttokens==2.4.1
# via stack-data
attrs==23.2.0
# via jsonschema
# via referencing
av==12.0.0
# via manim-slides
babel==2.14.0
# via sphinx
beautifulsoup4==4.12.3
# via furo
# via nbconvert
bleach==6.1.0
# via nbconvert
bump2version==1.0.1
certifi==2024.2.2
# via requests
cfgv==3.4.0
# via pre-commit
charset-normalizer==3.3.2
# via requests
click==8.1.7
# via click-default-group
# via cloup
# via manim
# via manim-slides
# via sphinx-click
click-default-group==1.2.4
# via manim
# via manim-slides
cloup==0.13.1
# via manim
colour==0.1.5
# via manim
# via manimgl
comm==0.2.2
# via ipykernel
contourpy==1.2.1
# via matplotlib
coverage==7.4.4
# via pytest-cov
cycler==0.12.1
# via matplotlib
debugpy==1.8.1
# via ipykernel
decorator==5.1.1
# via ipython
# via manim
defusedxml==0.7.1
# via nbconvert
distlib==0.3.8
# via virtualenv
docutils==0.20.1
# via manim-slides
# via myst-parser
# via nbsphinx
# via sphinx
# via sphinx-click
execnet==2.1.1
# via pytest-xdist
executing==2.0.1
# via stack-data
fastjsonschema==2.19.1
# via nbformat
filelock==3.13.4
# via virtualenv
fonttools==4.51.0
# via matplotlib
furo==2024.1.29
# via manim-slides
glcontext==2.5.0
# via moderngl
identify==2.5.35
# via pre-commit
idna==3.7
# via requests
imagesize==1.4.1
# via sphinx
iniconfig==2.0.0
# via pytest
ipykernel==6.29.4
# via manim-slides
ipython==8.18.1
# via ipykernel
# via manim-slides
# via manimgl
isosurfaces==0.1.0
# via manim
# via manimgl
jedi==0.19.1
# via ipython
jinja2==3.1.3
# via manim-slides
# via myst-parser
# via nbconvert
# via nbsphinx
# via sphinx
jsonschema==4.21.1
# via nbformat
jsonschema-specifications==2023.12.1
# via jsonschema
jupyter-client==8.6.1
# via ipykernel
# via nbclient
jupyter-core==5.7.2
# via ipykernel
# via jupyter-client
# via nbclient
# via nbconvert
# via nbformat
jupyterlab-pygments==0.3.0
# via nbconvert
kiwisolver==1.4.5
# via matplotlib
lxml==5.2.1
# via manim-slides
# via python-pptx
manim==0.17.3
# via manim-slides
manimgl==1.6.1
# via manim-slides
manimpango==0.4.4
# via manim
# via manimgl
mapbox-earcut==1.0.1
# via manim
# via manimgl
markdown-it-py==3.0.0
# via mdit-py-plugins
# via myst-parser
# via rich
markupsafe==2.1.5
# via jinja2
# via nbconvert
matplotlib==3.8.4
# via manimgl
matplotlib-inline==0.1.7
# via ipykernel
# via ipython
mdit-py-plugins==0.4.0
# via myst-parser
mdurl==0.1.2
# via markdown-it-py
mistune==3.0.2
# via nbconvert
moderngl==5.10.0
# via manim
# via manimgl
# via moderngl-window
moderngl-window==2.4.4
# via manim
# via manimgl
mpmath==1.3.0
# via sympy
multipledispatch==1.0.0
# via pyrr
myst-parser==2.0.0
# via manim-slides
nbclient==0.10.0
# via nbconvert
nbconvert==7.16.3
# via nbsphinx
nbformat==5.10.4
# via nbclient
# via nbconvert
# via nbsphinx
nbsphinx==0.9.3
# via manim-slides
nest-asyncio==1.6.0
# via ipykernel
networkx==2.8.8
# via manim
nodeenv==1.8.0
# via pre-commit
numpy==1.26.4
# via contourpy
# via isosurfaces
# via manim
# via manim-slides
# via manimgl
# via mapbox-earcut
# via matplotlib
# via moderngl-window
# via pyrr
# via scipy
packaging==24.0
# via ipykernel
# via matplotlib
# via nbconvert
# via pytest
# via qtpy
# via sphinx
pandoc==2.3
# via manim-slides
pandocfilters==1.5.1
# via nbconvert
parso==0.8.4
# via jedi
pexpect==4.9.0
# via ipython
pillow==9.5.0
# via manim
# via manim-slides
# via manimgl
# via matplotlib
# via moderngl-window
# via python-pptx
platformdirs==4.2.0
# via jupyter-core
# via virtualenv
pluggy==1.4.0
# via pytest
# via pytest-qt
plumbum==1.8.2
# via pandoc
ply==3.11
# via pandoc
pre-commit==3.7.0
prompt-toolkit==3.0.43
# via ipython
psutil==5.9.8
# via ipykernel
ptyprocess==0.7.0
# via pexpect
pure-eval==0.2.2
# via stack-data
pycairo==1.26.0
# via manim
pydantic==2.7.0
# via manim-slides
# via pydantic-extra-types
pydantic-core==2.18.1
# via pydantic
pydantic-extra-types==2.6.0
# via manim-slides
pydub==0.25.1
# via manim
# via manimgl
pyglet==2.0.15
# via moderngl-window
pygments==2.17.2
# via furo
# via ipython
# via manim
# via manimgl
# via nbconvert
# via rich
# via sphinx
pyopengl==3.1.7
# via manimgl
pyparsing==3.1.2
# via matplotlib
pyqt6==6.6.1
# via manim-slides
pyqt6-qt6==6.6.3
# via pyqt6
pyqt6-sip==13.6.0
# via pyqt6
pyrr==0.10.3
# via moderngl-window
pyside6==6.5.2
# via manim-slides
pyside6-addons==6.5.2
# via pyside6
pyside6-essentials==6.5.2
# via pyside6
# via pyside6-addons
pytest==8.1.1
# via pytest-cov
# via pytest-env
# via pytest-qt
# via pytest-xdist
pytest-cov==5.0.0
pytest-env==1.1.3
pytest-qt==4.4.0
pytest-xdist==3.5.0
python-dateutil==2.9.0.post0
# via jupyter-client
# via matplotlib
python-pptx==0.6.23
# via manim-slides
pyyaml==6.0.1
# via manimgl
# via myst-parser
# via pre-commit
pyzmq==26.0.0
# via ipykernel
# via jupyter-client
qtpy==2.4.1
# via manim-slides
referencing==0.34.0
# via jsonschema
# via jsonschema-specifications
requests==2.31.0
# via manim
# via manim-slides
# via sphinx
rich==13.7.1
# via manim
# via manim-slides
# via manimgl
rpds-py==0.18.0
# via jsonschema
# via referencing
rtoml==0.10.0
# via manim-slides
scipy==1.13.0
# via manim
# via manimgl
screeninfo==0.8.1
# via manim
# via manimgl
setuptools==69.5.1
# via nodeenv
shiboken6==6.5.2
# via pyside6
# via pyside6-addons
# via pyside6-essentials
six==1.16.0
# via asttokens
# via bleach
# via python-dateutil
skia-pathops==0.7.4
# via manim
# via manimgl
snowballstemmer==2.2.0
# via sphinx
soupsieve==2.5
# via beautifulsoup4
sphinx==7.3.6
# via furo
# via manim-slides
# via myst-parser
# via nbsphinx
# via sphinx-basic-ng
# via sphinx-click
# via sphinx-copybutton
# via sphinxext-opengraph
sphinx-basic-ng==1.0.0b2
# via furo
sphinx-click==5.1.0
# via manim-slides
sphinx-copybutton==0.5.2
# via manim-slides
sphinxcontrib-applehelp==1.0.8
# via sphinx
sphinxcontrib-devhelp==1.0.6
# via sphinx
sphinxcontrib-htmlhelp==2.0.5
# via sphinx
sphinxcontrib-jsmath==1.0.1
# via sphinx
sphinxcontrib-qthelp==1.0.7
# via sphinx
sphinxcontrib-serializinghtml==1.1.10
# via sphinx
sphinxext-opengraph==0.9.1
# via manim-slides
srt==3.5.3
# via manim
stack-data==0.6.3
# via ipython
svgelements==1.9.6
# via manim
# via manimgl
sympy==1.12
# via manimgl
tinycss2==1.2.1
# via nbconvert
tornado==6.4
# via ipykernel
# via jupyter-client
tqdm==4.66.2
# via manim
# via manim-slides
# via manimgl
traitlets==5.14.2
# via comm
# via ipykernel
# via ipython
# via jupyter-client
# via jupyter-core
# via matplotlib-inline
# via nbclient
# via nbconvert
# via nbformat
# via nbsphinx
typing-extensions==4.11.0
# via pydantic
# via pydantic-core
urllib3==2.2.1
# via requests
validators==0.28.0
# via manimgl
virtualenv==20.25.3
# via pre-commit
watchdog==2.3.1
# via manim
wcwidth==0.2.13
# via prompt-toolkit
webencodings==0.5.1
# via bleach
# via tinycss2
xlsxwriter==3.2.0
# via python-pptx

382
requirements.lock Normal file
View File

@ -0,0 +1,382 @@
# generated by rye
# use `rye lock` or `rye sync` to update this lockfile
#
# last locked with the following flags:
# pre: false
# features: []
# all-features: true
# with-sources: false
-e file:.
alabaster==0.7.16
# via sphinx
annotated-types==0.6.0
# via pydantic
asttokens==2.4.1
# via stack-data
attrs==23.2.0
# via jsonschema
# via referencing
av==12.0.0
# via manim-slides
babel==2.14.0
# via sphinx
beautifulsoup4==4.12.3
# via furo
# via nbconvert
bleach==6.1.0
# via nbconvert
certifi==2024.2.2
# via requests
charset-normalizer==3.3.2
# via requests
click==8.1.7
# via click-default-group
# via cloup
# via manim
# via manim-slides
# via sphinx-click
click-default-group==1.2.4
# via manim
# via manim-slides
cloup==0.13.1
# via manim
colour==0.1.5
# via manim
# via manimgl
comm==0.2.2
# via ipykernel
contourpy==1.2.1
# via matplotlib
cycler==0.12.1
# via matplotlib
debugpy==1.8.1
# via ipykernel
decorator==5.1.1
# via ipython
# via manim
defusedxml==0.7.1
# via nbconvert
docutils==0.20.1
# via manim-slides
# via myst-parser
# via nbsphinx
# via sphinx
# via sphinx-click
executing==2.0.1
# via stack-data
fastjsonschema==2.19.1
# via nbformat
fonttools==4.51.0
# via matplotlib
furo==2024.1.29
# via manim-slides
glcontext==2.5.0
# via moderngl
idna==3.7
# via requests
imagesize==1.4.1
# via sphinx
ipykernel==6.29.4
# via manim-slides
ipython==8.18.1
# via ipykernel
# via manim-slides
# via manimgl
isosurfaces==0.1.0
# via manim
# via manimgl
jedi==0.19.1
# via ipython
jinja2==3.1.3
# via manim-slides
# via myst-parser
# via nbconvert
# via nbsphinx
# via sphinx
jsonschema==4.21.1
# via nbformat
jsonschema-specifications==2023.12.1
# via jsonschema
jupyter-client==8.6.1
# via ipykernel
# via nbclient
jupyter-core==5.7.2
# via ipykernel
# via jupyter-client
# via nbclient
# via nbconvert
# via nbformat
jupyterlab-pygments==0.3.0
# via nbconvert
kiwisolver==1.4.5
# via matplotlib
lxml==5.2.1
# via manim-slides
# via python-pptx
manim==0.17.3
# via manim-slides
manimgl==1.6.1
# via manim-slides
manimpango==0.4.4
# via manim
# via manimgl
mapbox-earcut==1.0.1
# via manim
# via manimgl
markdown-it-py==3.0.0
# via mdit-py-plugins
# via myst-parser
# via rich
markupsafe==2.1.5
# via jinja2
# via nbconvert
matplotlib==3.8.4
# via manimgl
matplotlib-inline==0.1.7
# via ipykernel
# via ipython
mdit-py-plugins==0.4.0
# via myst-parser
mdurl==0.1.2
# via markdown-it-py
mistune==3.0.2
# via nbconvert
moderngl==5.10.0
# via manim
# via manimgl
# via moderngl-window
moderngl-window==2.4.4
# via manim
# via manimgl
mpmath==1.3.0
# via sympy
multipledispatch==1.0.0
# via pyrr
myst-parser==2.0.0
# via manim-slides
nbclient==0.10.0
# via nbconvert
nbconvert==7.16.3
# via nbsphinx
nbformat==5.10.4
# via nbclient
# via nbconvert
# via nbsphinx
nbsphinx==0.9.3
# via manim-slides
nest-asyncio==1.6.0
# via ipykernel
networkx==2.8.8
# via manim
numpy==1.26.4
# via contourpy
# via isosurfaces
# via manim
# via manim-slides
# via manimgl
# via mapbox-earcut
# via matplotlib
# via moderngl-window
# via pyrr
# via scipy
packaging==24.0
# via ipykernel
# via matplotlib
# via nbconvert
# via qtpy
# via sphinx
pandoc==2.3
# via manim-slides
pandocfilters==1.5.1
# via nbconvert
parso==0.8.4
# via jedi
pexpect==4.9.0
# via ipython
pillow==9.5.0
# via manim
# via manim-slides
# via manimgl
# via matplotlib
# via moderngl-window
# via python-pptx
platformdirs==4.2.0
# via jupyter-core
plumbum==1.8.2
# via pandoc
ply==3.11
# via pandoc
prompt-toolkit==3.0.43
# via ipython
psutil==5.9.8
# via ipykernel
ptyprocess==0.7.0
# via pexpect
pure-eval==0.2.2
# via stack-data
pycairo==1.26.0
# via manim
pydantic==2.7.0
# via manim-slides
# via pydantic-extra-types
pydantic-core==2.18.1
# via pydantic
pydantic-extra-types==2.6.0
# via manim-slides
pydub==0.25.1
# via manim
# via manimgl
pyglet==2.0.15
# via moderngl-window
pygments==2.17.2
# via furo
# via ipython
# via manim
# via manimgl
# via nbconvert
# via rich
# via sphinx
pyopengl==3.1.7
# via manimgl
pyparsing==3.1.2
# via matplotlib
pyqt6==6.6.1
# via manim-slides
pyqt6-qt6==6.6.3
# via pyqt6
pyqt6-sip==13.6.0
# via pyqt6
pyrr==0.10.3
# via moderngl-window
pyside6==6.5.2
# via manim-slides
pyside6-addons==6.5.2
# via pyside6
pyside6-essentials==6.5.2
# via pyside6
# via pyside6-addons
python-dateutil==2.9.0.post0
# via jupyter-client
# via matplotlib
python-pptx==0.6.23
# via manim-slides
pyyaml==6.0.1
# via manimgl
# via myst-parser
pyzmq==26.0.0
# via ipykernel
# via jupyter-client
qtpy==2.4.1
# via manim-slides
referencing==0.34.0
# via jsonschema
# via jsonschema-specifications
requests==2.31.0
# via manim
# via manim-slides
# via sphinx
rich==13.7.1
# via manim
# via manim-slides
# via manimgl
rpds-py==0.18.0
# via jsonschema
# via referencing
rtoml==0.10.0
# via manim-slides
scipy==1.13.0
# via manim
# via manimgl
screeninfo==0.8.1
# via manim
# via manimgl
shiboken6==6.5.2
# via pyside6
# via pyside6-addons
# via pyside6-essentials
six==1.16.0
# via asttokens
# via bleach
# via python-dateutil
skia-pathops==0.7.4
# via manim
# via manimgl
snowballstemmer==2.2.0
# via sphinx
soupsieve==2.5
# via beautifulsoup4
sphinx==7.3.6
# via furo
# via manim-slides
# via myst-parser
# via nbsphinx
# via sphinx-basic-ng
# via sphinx-click
# via sphinx-copybutton
# via sphinxext-opengraph
sphinx-basic-ng==1.0.0b2
# via furo
sphinx-click==5.1.0
# via manim-slides
sphinx-copybutton==0.5.2
# via manim-slides
sphinxcontrib-applehelp==1.0.8
# via sphinx
sphinxcontrib-devhelp==1.0.6
# via sphinx
sphinxcontrib-htmlhelp==2.0.5
# via sphinx
sphinxcontrib-jsmath==1.0.1
# via sphinx
sphinxcontrib-qthelp==1.0.7
# via sphinx
sphinxcontrib-serializinghtml==1.1.10
# via sphinx
sphinxext-opengraph==0.9.1
# via manim-slides
srt==3.5.3
# via manim
stack-data==0.6.3
# via ipython
svgelements==1.9.6
# via manim
# via manimgl
sympy==1.12
# via manimgl
tinycss2==1.2.1
# via nbconvert
tornado==6.4
# via ipykernel
# via jupyter-client
tqdm==4.66.2
# via manim
# via manim-slides
# via manimgl
traitlets==5.14.2
# via comm
# via ipykernel
# via ipython
# via jupyter-client
# via jupyter-core
# via matplotlib-inline
# via nbclient
# via nbconvert
# via nbformat
# via nbsphinx
typing-extensions==4.11.0
# via pydantic
# via pydantic-core
urllib3==2.2.1
# via requests
validators==0.28.0
# via manimgl
watchdog==2.3.1
# via manim
wcwidth==0.2.13
# via prompt-toolkit
webencodings==0.5.1
# via bleach
# via tinycss2
xlsxwriter==3.2.0
# via python-pptx

BIN
static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -51,3 +51,28 @@ class ManimSlidesLogo(Scene):
) # order matters
logo.move_to(ORIGIN)
self.add(logo)
class ManimSlidesFavicon(Scene):
def construct(self):
tex_template = TexTemplate()
tex_template.add_to_preamble(r"\usepackage{graphicx}\usepackage{fontawesome5}")
fill_color = "#c9d1d9"
stroke_color = "#343434"
play = Tex(
r"\faStepBackward\faStepForward",
fill_color=fill_color,
stroke_color=stroke_color,
tex_template=tex_template,
).scale(4)
comment = Tex(
r"\reflectbox{\faComment*[regular]}",
fill_color=fill_color,
stroke_color=stroke_color,
tex_template=tex_template,
).scale(9)
comment.move_to(play)
comment.shift(0.4 * DOWN)
favicon = VGroup(comment, play).scale(3)
favicon.move_to(ORIGIN)
self.add(favicon)

5
static/make_favicon.sh Executable file
View File

@ -0,0 +1,5 @@
#! /bin/bash
rye run manim-slides render -t -qk -s --format png --resolution 64,64 static/logo.py ManimSlidesFavicon && mv media/images/logo/*.png static/favicon.png
ln -f -r -s static/favicon.png docs/source/_static/favicon.png

View File

@ -1,21 +1,21 @@
#! /bin/bash
MANIM_SLIDES_THEME=light pdm run manim-slides render -qk -s --format png --resolution 2560,1280 static/logo.py && mv media/images/logo/*.png static/logo.png
MANIM_SLIDES_THEME=light rye run manim-slides render -qk -s --format png --resolution 2560,1280 static/logo.py ManimSlidesLogo && mv media/images/logo/*.png static/logo.png
ln -f -r -s static/logo.png docs/source/_static/logo.png
MANIM_SLIDES_THEME=dark_docs pdm run manim-slides render -qk -s --format png --resolution 2560,1280 static/logo.py && mv media/images/logo/*.png static/logo_dark_docs.png
MANIM_SLIDES_THEME=dark_docs rye run manim-slides render -qk -s --format png --resolution 2560,1280 static/logo.py ManimSlidesLogo && mv media/images/logo/*.png static/logo_dark_docs.png
ln -f -r -s static/logo_dark_docs.png docs/source/_static/logo_dark_docs.png
MANIM_SLIDES_THEME=dark_github pdm run manim-slides render -qk -s --format png --resolution 2560,1280 static/logo.py && mv media/images/logo/*.png static/logo_dark_github.png
MANIM_SLIDES_THEME=dark_github rye run manim-slides render -qk -s --format png --resolution 2560,1280 static/logo.py ManimSlidesLogo && mv media/images/logo/*.png static/logo_dark_github.png
ln -f -r -s static/logo_dark_github.png docs/source/_static/logo_dark_github.png
MANIM_SLIDES_THEME=light pdm run manim-slides render -t -qk -s --format png --resolution 2560,1280 static/logo.py && mv media/images/logo/*.png static/logo_light_transparent.png
MANIM_SLIDES_THEME=light rye run manim-slides render -t -qk -s --format png --resolution 2560,1280 static/logo.py ManimSlidesLogo && mv media/images/logo/*.png static/logo_light_transparent.png
ln -f -r -s static/logo_light_transparent.png docs/source/_static/logo_light_transparent.png
MANIM_SLIDES_THEME=dark_docs pdm run manim-slides render -t -qk -s --format png --resolution 2560,1280 static/logo.py && mv media/images/logo/*.png static/logo_dark_transparent.png
MANIM_SLIDES_THEME=dark_docs rye run manim-slides render -t -qk -s --format png --resolution 2560,1280 static/logo.py ManimSlidesLogo && mv media/images/logo/*.png static/logo_dark_transparent.png
ln -f -r -s static/logo_dark_transparent.png docs/source/_static/logo_dark_transparent.png

View File

@ -1,7 +1,7 @@
import random
import string
from collections.abc import Generator, Iterator
from pathlib import Path
from typing import Generator, Iterator, List
import pytest
@ -65,7 +65,7 @@ def random_path(
@pytest.fixture
def paths() -> Generator[List[Path], None, None]:
def paths() -> Generator[list[Path], None, None]:
random.seed(1234)
yield [random_path() for _ in range(20)]

View File

@ -1,4 +1,4 @@
from typing import MutableMapping
from collections.abc import MutableMapping
import pytest

View File

@ -153,11 +153,14 @@ class TestConverter:
file_contents = Path(out_file).read_text()
assert "manim" in file_contents.casefold()
@pytest.mark.parametrize("frame_index", ("first", "last"))
def test_pdf_converter(
self, tmp_path: Path, presentation_config: PresentationConfig
self, frame_index: str, tmp_path: Path, presentation_config: PresentationConfig
) -> None:
out_file = tmp_path / "slides.pdf"
PDF(presentation_configs=[presentation_config]).convert_to(out_file)
PDF(
presentation_configs=[presentation_config], frame_index=frame_index
).convert_to(out_file)
assert out_file.exists()
def test_converter_no_presentation_config(self) -> None:

View File

@ -21,6 +21,7 @@ def assert_import(
def test_force_api() -> None:
pytest.importorskip("manimlib")
import manim # noqa: F401
if "manimlib" in sys.modules:
@ -54,6 +55,7 @@ def test_invalid_api() -> None:
@pytest.mark.filterwarnings("ignore:assert_import")
def test_manim_and_manimgl_imported() -> None:
pytest.importorskip("manimlib")
import manim # noqa: F401
import manimlib # noqa: F401
@ -78,6 +80,7 @@ def test_manim_imported() -> None:
def test_manimgl_imported() -> None:
pytest.importorskip("manimlib")
import manimlib # noqa: F401
if "manim" in sys.modules:

View File

@ -1,5 +1,5 @@
from collections.abc import Iterator
from pathlib import Path
from typing import Iterator, Tuple
import pytest
from click.testing import CliRunner
@ -20,11 +20,11 @@ def auto_shutdown_qapp() -> Iterator[None]:
@pytest.fixture(scope="session")
def args(slides_folder: Path) -> Iterator[Tuple[str, ...]]:
def args(slides_folder: Path) -> Iterator[tuple[str, ...]]:
yield ("--folder", str(slides_folder), "--skip-all", "--playback-rate", "25")
def test_present(args: Tuple[str, ...]) -> None:
def test_present(args: tuple[str, ...]) -> None:
runner = CliRunner()
with runner.isolated_filesystem():
@ -34,7 +34,7 @@ def test_present(args: Tuple[str, ...]) -> None:
assert results.stdout == ""
def test_present_unexisting_slide(args: Tuple[str, ...]) -> None:
def test_present_unexisting_slide(args: tuple[str, ...]) -> None:
runner = CliRunner()
with runner.isolated_filesystem():
@ -44,7 +44,7 @@ def test_present_unexisting_slide(args: Tuple[str, ...]) -> None:
assert "UnexistingSlide.json does not exist" in results.stdout
def test_present_full_screen(args: Tuple[str, ...]) -> None:
def test_present_full_screen(args: tuple[str, ...]) -> None:
runner = CliRunner()
with runner.isolated_filesystem():
@ -54,7 +54,7 @@ def test_present_full_screen(args: Tuple[str, ...]) -> None:
assert results.stdout == ""
def test_present_hide_mouse(args: Tuple[str, ...]) -> None:
def test_present_hide_mouse(args: tuple[str, ...]) -> None:
runner = CliRunner()
with runner.isolated_filesystem():
@ -64,7 +64,7 @@ def test_present_hide_mouse(args: Tuple[str, ...]) -> None:
assert results.stdout == ""
def test_present_ignore_aspect_ratio(args: Tuple[str, ...]) -> None:
def test_present_ignore_aspect_ratio(args: tuple[str, ...]) -> None:
runner = CliRunner()
with runner.isolated_filesystem():
@ -76,7 +76,7 @@ def test_present_ignore_aspect_ratio(args: Tuple[str, ...]) -> None:
assert results.stdout == ""
def test_present_start_at(args: Tuple[str, ...]) -> None:
def test_present_start_at(args: tuple[str, ...]) -> None:
runner = CliRunner()
with runner.isolated_filesystem():
@ -86,7 +86,7 @@ def test_present_start_at(args: Tuple[str, ...]) -> None:
assert results.stdout == ""
def test_present_start_at_invalid(args: Tuple[str, ...]) -> None:
def test_present_start_at_invalid(args: tuple[str, ...]) -> None:
runner = CliRunner()
with runner.isolated_filesystem():
@ -96,7 +96,7 @@ def test_present_start_at_invalid(args: Tuple[str, ...]) -> None:
assert "Could not set presentation index to 1234"
def test_present_start_at_scene_number(args: Tuple[str, ...]) -> None:
def test_present_start_at_scene_number(args: tuple[str, ...]) -> None:
runner = CliRunner()
with runner.isolated_filesystem():
@ -108,7 +108,7 @@ def test_present_start_at_scene_number(args: Tuple[str, ...]) -> None:
assert results.stdout == ""
def test_present_start_at_slide_number(args: Tuple[str, ...]) -> None:
def test_present_start_at_slide_number(args: tuple[str, ...]) -> None:
runner = CliRunner()
with runner.isolated_filesystem():
@ -120,7 +120,7 @@ def test_present_start_at_slide_number(args: Tuple[str, ...]) -> None:
assert results.stdout == ""
def test_present_set_screen(args: Tuple[str, ...]) -> None:
def test_present_set_screen(args: tuple[str, ...]) -> None:
runner = CliRunner()
with runner.isolated_filesystem():
@ -131,7 +131,7 @@ def test_present_set_screen(args: Tuple[str, ...]) -> None:
@pytest.mark.skip(reason="Fails when running the whole test suite.")
def test_present_set_invalid_screen(args: Tuple[str, ...]) -> None:
def test_present_set_invalid_screen(args: tuple[str, ...]) -> None:
runner = CliRunner()
with runner.isolated_filesystem():

View File

@ -1,11 +1,14 @@
import random
import shutil
import sys
from pathlib import Path
import manim
import numpy as np
import pytest
from click.testing import CliRunner
from manim import (
BLACK,
BLUE,
DOWN,
LEFT,
@ -34,8 +37,9 @@ from manim_slides.slide.manim import Slide
pytest.param(
"--GL",
marks=pytest.mark.skipif(
version.parse(np.__version__) >= version.parse("1.25"),
reason="ManimGL requires numpy<1.25, which is outdate",
version.parse(np.__version__) >= version.parse("1.25")
or sys.version_info >= (3, 12),
reason="ManimGL requires numpy<1.25, which is outdated and Python < 3.12",
),
),
],
@ -108,6 +112,17 @@ class TestSlide:
assert len(self._canvas) == 0
assert self._wait_time_between_slides == 0.0
@pytest.mark.skipif(
version.parse(manim.__version__) < version.parse("0.18"),
reason="Manim change how color are represented in 0.18",
)
@assert_constructs
class TestBackgroundColor(Slide):
def construct(self) -> None:
assert self._background_color == BLACK.to_hex() # DEFAULT
self.camera.background_color = BLUE
assert self._background_color == BLUE.to_hex()
@assert_renders
class TestMultipleAnimationsInLastSlide(Slide):
"""Check against solution for issue #161."""

View File

@ -1,17 +1,16 @@
from pathlib import Path
from typing import List
from manim_slides.utils import merge_basenames
def test_merge_basenames(paths: List[Path]) -> None:
def test_merge_basenames(paths: list[Path]) -> None:
path = merge_basenames(paths)
assert path.suffix == paths[0].suffix
assert path.parent == paths[0].parent
def test_merge_basenames_same_with_different_parent_directories(
paths: List[Path],
paths: list[Path],
) -> None:
d1 = Path("a/b/c")
d2 = Path("d/e/f")