mirror of
https://github.com/jeertmans/manim-slides.git
synced 2025-08-06 14:19:52 +08:00
feat(convert): add to PDF conversion (#197)
* feat(convert): add to PDF conversion Basic PDF conversion. It takes the last frame (by default) for each animation, and prints out a PDF page. Closes #196 * chore(ci): remove experimental installer * feat(convert): add to PDF conversion Basic PDF conversion. It takes the last frame (by default) for each animation, and prints out a PDF page. Closes #196 * feat(convert): add to PDF conversion Basic PDF conversion. It takes the last frame (by default) for each animation, and prints out a PDF page. Closes #196 * chore(deps): update lockfile
This commit is contained in:
@ -6,21 +6,21 @@ The following summarizes the different presentation features Manim Slides offers
|
|||||||
:widths: auto
|
:widths: auto
|
||||||
:align: center
|
:align: center
|
||||||
|
|
||||||
| Feature / Constraint | [`present`](reference/cli.md) | [`convert --to=html`](reference/cli.md) | [`convert --to=pptx`](reference/cli.md) |
|
| Feature / Constraint | [`present`](reference/cli.md) | [`convert --to=html`](reference/cli.md) | [`convert --to=pptx`](reference/cli.md) | [`convert --to=pdf`](reference/cli.md)
|
||||||
| :--- | :---: | :---: | :---: |
|
| :--- | :---: | :---: | :---: | :---: |
|
||||||
| Basic navigation through slides | Yes | Yes | Yes |
|
| Basic navigation through slides | Yes | Yes | Yes | Yes (static image) |
|
||||||
| Replay slide | Yes | No | No |
|
| Replay slide | Yes | No | No | N/A |
|
||||||
| Pause animation | Yes | No | No |
|
| Pause animation | Yes | No | No | N/A |
|
||||||
| Play slide in reverse | Yes | No | No |
|
| Play slide in reverse | Yes | No | No | N/A |
|
||||||
| Slide count | Yes | Yes (optional) | Yes (optional) |
|
| Slide count | Yes | Yes (optional) | Yes (optional) | N/A |
|
||||||
| Animation count | Yes | No | No |
|
| Animation count | Yes | No | No | N/A |
|
||||||
| Needs Python with Manim Slides installed | Yes | No | No |
|
| Needs Python with Manim Slides installed | Yes | No | No | No
|
||||||
| Requires internet access | No | Yes | No |
|
| Requires internet access | No | Yes | No | No |
|
||||||
| Auto. play slides | Yes | Yes | Yes |
|
| Auto. play slides | Yes | Yes | Yes | N/A |
|
||||||
| Loops support | Yes | Yes | Yes |
|
| Loops support | Yes | Yes | Yes | N/A |
|
||||||
| Fully customizable | No | Yes (`--use-template` option) | No |
|
| Fully customizable | No | Yes (`--use-template` option) | No | No |
|
||||||
| Other dependencies | None | A modern web browser | PowerPoint or LibreOffice Impress[^1]
|
| Other dependencies | None | A modern web browser | PowerPoint or LibreOffice Impress[^1] | None |
|
||||||
| Works cross-platforms | Yes | Yes | Partly[^1][^2] |
|
| Works cross-platforms | Yes | Yes | Partly[^1][^2] | Yes |
|
||||||
:::
|
:::
|
||||||
|
|
||||||
[^1]: If you encounter a problem where slides do not automatically play or loops do not work, please [file an issue on GitHub](https://github.com/jeertmans/manim-slides/issues/new/choose).
|
[^1]: If you encounter a problem where slides do not automatically play or loops do not work, please [file an issue on GitHub](https://github.com/jeertmans/manim-slides/issues/new/choose).
|
||||||
|
@ -150,7 +150,10 @@ reason.
|
|||||||
|
|
||||||
### With PowerPoint (*EXPERIMENTAL*)
|
### With PowerPoint (*EXPERIMENTAL*)
|
||||||
|
|
||||||
A recent conversion feature is to the PowerPoint format, thanks to the `python-pptx` package. Even though it is fully working, it is still considered in an *EXPERIMENTAL* status because we do not exactly know what versions of PowerPoint (or LibreOffice Impress) are supported.
|
A recent conversion feature is to the PowerPoint format, thanks to the
|
||||||
|
`python-pptx` package. Even though it is fully working,
|
||||||
|
it is still considered in an *EXPERIMENTAL* status because we do not
|
||||||
|
exactly know what versions of PowerPoint (or LibreOffice Impress) are supported.
|
||||||
|
|
||||||
Basically, you can create a PowerPoint in a single command:
|
Basically, you can create a PowerPoint in a single command:
|
||||||
|
|
||||||
@ -158,6 +161,24 @@ Basically, you can create a PowerPoint in a single command:
|
|||||||
manim-slides convert --to=pptx BasicExample basic_example.pptx
|
manim-slides convert --to=pptx BasicExample basic_example.pptx
|
||||||
```
|
```
|
||||||
|
|
||||||
All the videos and necessary files will be contained inside the `.pptx` file, so you can safely share it with anyone. By default, the `poster_frame_image`, i.e., what is displayed by PowerPoint when the video is not playing, is the first frame of each slide. This allows for smooth transitions.
|
All the videos and necessary files will be contained inside the `.pptx` file, so
|
||||||
|
you can safely share it with anyone. By default, the `poster_frame_image`, i.e.,
|
||||||
|
what is displayed by PowerPoint when the video is not playing, is the first
|
||||||
|
frame of each slide. This allows for smooth transitions.
|
||||||
|
|
||||||
In the future, we hope to provide more features to this format, so feel free to suggest new features too!
|
In the future, we hope to provide more features to this format,
|
||||||
|
so feel free to suggest new features too!
|
||||||
|
|
||||||
|
### Static PDF presentation
|
||||||
|
|
||||||
|
If you ever need backup slides, that are only made of PDF pages
|
||||||
|
with static images, you can generate such a PDF with the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
manim-slides convert --to=pdf BasicExample basic_example.pdf
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that you will lose all the benefits from animated slides. Therefore,
|
||||||
|
this is only recommended to be used as a backup plan. By default, the last frame
|
||||||
|
of each slide will be printed. This can be changed to be the first one with
|
||||||
|
`-cframe_index=first`.
|
||||||
|
@ -14,7 +14,8 @@ import cv2
|
|||||||
import pptx
|
import pptx
|
||||||
from click import Context, Parameter
|
from click import Context, Parameter
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from pydantic import BaseModel, FilePath, PositiveInt, ValidationError
|
from PIL import Image
|
||||||
|
from pydantic import BaseModel, FilePath, PositiveFloat, PositiveInt, ValidationError
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
from . import data
|
from . import data
|
||||||
@ -75,6 +76,7 @@ class Converter(BaseModel): # type: ignore
|
|||||||
"""Returns the appropriate converter from a string name."""
|
"""Returns the appropriate converter from a string name."""
|
||||||
return {
|
return {
|
||||||
"html": RevealJS,
|
"html": RevealJS,
|
||||||
|
"pdf": PDF,
|
||||||
"pptx": PowerPoint,
|
"pptx": PowerPoint,
|
||||||
}[s]
|
}[s]
|
||||||
|
|
||||||
@ -367,6 +369,62 @@ class RevealJS(Converter):
|
|||||||
f.write(content)
|
f.write(content)
|
||||||
|
|
||||||
|
|
||||||
|
class FrameIndex(str, Enum):
|
||||||
|
first = "first"
|
||||||
|
last = "last"
|
||||||
|
|
||||||
|
|
||||||
|
class PDF(Converter):
|
||||||
|
frame_index: FrameIndex = FrameIndex.last
|
||||||
|
resolution: PositiveFloat = 100.0
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
use_enum_values = True
|
||||||
|
extra = "forbid"
|
||||||
|
|
||||||
|
def open(self, file: Path) -> None:
|
||||||
|
return open_with_default(file)
|
||||||
|
|
||||||
|
def convert_to(self, dest: Path) -> None:
|
||||||
|
"""Converts 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()
|
||||||
|
|
||||||
|
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):
|
||||||
|
presentation_config.concat_animations()
|
||||||
|
for slide_config in tqdm(
|
||||||
|
presentation_config.slides,
|
||||||
|
desc=f"Generating video slides for config {i + 1}",
|
||||||
|
leave=False,
|
||||||
|
):
|
||||||
|
file = presentation_config.files[slide_config.start_animation]
|
||||||
|
|
||||||
|
images.append(read_image_from_video_file(file, self.frame_index))
|
||||||
|
|
||||||
|
images[0].save(
|
||||||
|
dest,
|
||||||
|
"PDF",
|
||||||
|
resolution=self.resolution,
|
||||||
|
save_all=True,
|
||||||
|
append_images=images[1:],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PowerPoint(Converter):
|
class PowerPoint(Converter):
|
||||||
left: PositiveInt = 0
|
left: PositiveInt = 0
|
||||||
top: PositiveInt = 0
|
top: PositiveInt = 0
|
||||||
@ -513,7 +571,7 @@ def show_template_option(function: Callable[..., Any]) -> Callable[..., Any]:
|
|||||||
@click.argument("dest", type=click.Path(dir_okay=False, path_type=Path))
|
@click.argument("dest", type=click.Path(dir_okay=False, path_type=Path))
|
||||||
@click.option(
|
@click.option(
|
||||||
"--to",
|
"--to",
|
||||||
type=click.Choice(["html", "pptx"], case_sensitive=False),
|
type=click.Choice(["html", "pdf", "pptx"], case_sensitive=False),
|
||||||
default="html",
|
default="html",
|
||||||
show_default=True,
|
show_default=True,
|
||||||
help="Set the conversion format to use.",
|
help="Set the conversion format to use.",
|
||||||
|
3529
poetry.lock
generated
3529
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -53,6 +53,7 @@ manim = {version = "^0.17.0", optional = true}
|
|||||||
manimgl = {version = "^1.6.1", optional = true}
|
manimgl = {version = "^1.6.1", optional = true}
|
||||||
numpy = "^1.19"
|
numpy = "^1.19"
|
||||||
opencv-python = "^4.6.0.66"
|
opencv-python = "^4.6.0.66"
|
||||||
|
pillow = "^9.5.0"
|
||||||
pydantic = "^1.10.2"
|
pydantic = "^1.10.2"
|
||||||
pyside6 = "^6.5.1.1"
|
pyside6 = "^6.5.1.1"
|
||||||
python = ">=3.8.1,<3.12"
|
python = ">=3.8.1,<3.12"
|
||||||
|
Reference in New Issue
Block a user