Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(convert): add to PDF conversion #197

Merged
merged 7 commits into from
Jun 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions docs/source/features_table.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
jeertmans marked this conversation as resolved.
Show resolved Hide resolved
jeertmans marked this conversation as resolved.
Show resolved Hide resolved
jeertmans marked this conversation as resolved.
Show resolved Hide resolved
| :--- | :---: | :---: | :---: | :---: |
| 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).
Expand Down
27 changes: 24 additions & 3 deletions docs/source/reference/sharing.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,35 @@ 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:

```bash
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:
jeertmans marked this conversation as resolved.
Show resolved Hide resolved

```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`.
62 changes: 60 additions & 2 deletions manim_slides/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
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
Expand Down Expand Up @@ -75,6 +76,7 @@ def from_string(cls, s: str) -> Type["Converter"]:
"""Returns the appropriate converter from a string name."""
return {
"html": RevealJS,
"pdf": PDF,
"pptx": PowerPoint,
}[s]

Expand Down Expand Up @@ -367,6 +369,62 @@ def convert_to(self, dest: Path) -> None:
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
Expand Down Expand Up @@ -513,7 +571,7 @@ def callback(ctx: Context, param: Parameter, value: bool) -> None:
@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.",
Expand Down
Loading