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:
Jérome Eertmans
2023-06-12 14:28:20 +02:00
committed by GitHub
parent 421cad3038
commit c7e38bfb38
5 changed files with 1956 additions and 1693 deletions

View File

@ -6,21 +6,21 @@ The following summarizes the different presentation features Manim Slides offers
:widths: auto
:align: center
| Feature / Constraint | [`present`](reference/cli.md) | [`convert --to=html`](reference/cli.md) | [`convert --to=pptx`](reference/cli.md) |
| :--- | :---: | :---: | :---: |
| Basic navigation through slides | Yes | Yes | Yes |
| Replay slide | Yes | No | No |
| Pause animation | Yes | No | No |
| Play slide in reverse | Yes | No | No |
| Slide count | Yes | Yes (optional) | Yes (optional) |
| Animation count | Yes | No | No |
| Needs Python with Manim Slides installed | Yes | No | No |
| Requires internet access | No | Yes | No |
| Auto. play slides | Yes | Yes | Yes |
| Loops support | Yes | Yes | Yes |
| Fully customizable | No | Yes (`--use-template` option) | No |
| Other dependencies | None | A modern web browser | PowerPoint or LibreOffice Impress[^1]
| Works cross-platforms | Yes | Yes | Partly[^1][^2] |
| 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 | Yes (static image) |
| Replay slide | Yes | No | No | N/A |
| Pause animation | Yes | No | No | N/A |
| Play slide in reverse | Yes | No | No | N/A |
| Slide count | Yes | Yes (optional) | Yes (optional) | N/A |
| Animation count | Yes | No | No | N/A |
| Needs Python with Manim Slides installed | Yes | No | No | No
| Requires internet access | No | Yes | No | No |
| Auto. play slides | Yes | Yes | Yes | N/A |
| Loops support | Yes | Yes | Yes | N/A |
| Fully customizable | No | Yes (`--use-template` option) | No | No |
| Other dependencies | None | A modern web browser | PowerPoint or LibreOffice Impress[^1] | None |
| Works cross-platforms | Yes | Yes | Partly[^1][^2] | Yes |
:::
[^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).

View File

@ -150,7 +150,10 @@ reason.
### 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:
@ -158,6 +161,24 @@ Basically, you can create a PowerPoint in a single command:
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`.

View File

@ -14,7 +14,8 @@ import cv2
import pptx
from click import Context, Parameter
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 . import data
@ -75,6 +76,7 @@ class Converter(BaseModel): # type: ignore
"""Returns the appropriate converter from a string name."""
return {
"html": RevealJS,
"pdf": PDF,
"pptx": PowerPoint,
}[s]
@ -367,6 +369,62 @@ class RevealJS(Converter):
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):
left: 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.option(
"--to",
type=click.Choice(["html", "pptx"], case_sensitive=False),
type=click.Choice(["html", "pdf", "pptx"], case_sensitive=False),
default="html",
show_default=True,
help="Set the conversion format to use.",

3529
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -53,6 +53,7 @@ manim = {version = "^0.17.0", optional = true}
manimgl = {version = "^1.6.1", optional = true}
numpy = "^1.19"
opencv-python = "^4.6.0.66"
pillow = "^9.5.0"
pydantic = "^1.10.2"
pyside6 = "^6.5.1.1"
python = ">=3.8.1,<3.12"