excalidraw-convert 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- excalidraw_convert-0.1.0/LICENSE +21 -0
- excalidraw_convert-0.1.0/PKG-INFO +191 -0
- excalidraw_convert-0.1.0/README.md +156 -0
- excalidraw_convert-0.1.0/excalidraw_convert/__init__.py +11 -0
- excalidraw_convert-0.1.0/excalidraw_convert/__main__.py +4 -0
- excalidraw_convert-0.1.0/excalidraw_convert/cli.py +186 -0
- excalidraw_convert-0.1.0/excalidraw_convert/converter.py +598 -0
- excalidraw_convert-0.1.0/excalidraw_convert/fetcher.py +201 -0
- excalidraw_convert-0.1.0/excalidraw_convert/lucid_converter.py +538 -0
- excalidraw_convert-0.1.0/excalidraw_convert/utils.py +170 -0
- excalidraw_convert-0.1.0/excalidraw_convert.egg-info/PKG-INFO +191 -0
- excalidraw_convert-0.1.0/excalidraw_convert.egg-info/SOURCES.txt +18 -0
- excalidraw_convert-0.1.0/excalidraw_convert.egg-info/dependency_links.txt +1 -0
- excalidraw_convert-0.1.0/excalidraw_convert.egg-info/entry_points.txt +2 -0
- excalidraw_convert-0.1.0/excalidraw_convert.egg-info/requires.txt +12 -0
- excalidraw_convert-0.1.0/excalidraw_convert.egg-info/top_level.txt +1 -0
- excalidraw_convert-0.1.0/pyproject.toml +51 -0
- excalidraw_convert-0.1.0/setup.cfg +4 -0
- excalidraw_convert-0.1.0/tests/test_lucid_converter.py +332 -0
- excalidraw_convert-0.1.0/tests/test_pptx_converter.py +298 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Lyndon Liang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: excalidraw-convert
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Convert Excalidraw diagrams to PowerPoint or Lucid Package Format
|
|
5
|
+
Author: Lyndon Liang
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/lliang17/excalidraw-convert
|
|
8
|
+
Project-URL: Issues, https://github.com/lliang17/excalidraw-convert/issues
|
|
9
|
+
Keywords: excalidraw,powerpoint,pptx,diagram,convert
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Multimedia :: Graphics :: Presentation
|
|
19
|
+
Classifier: Topic :: Office/Business :: Office Suites
|
|
20
|
+
Classifier: Topic :: Utilities
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: python-pptx>=0.6.21
|
|
25
|
+
Requires-Dist: requests>=2.28.0
|
|
26
|
+
Requires-Dist: cryptography>=41.0.0
|
|
27
|
+
Requires-Dist: click>=8.0.0
|
|
28
|
+
Requires-Dist: Pillow>=10.0.0
|
|
29
|
+
Provides-Extra: svg
|
|
30
|
+
Requires-Dist: cairosvg>=2.7.0; extra == "svg"
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# excalidraw-convert
|
|
37
|
+
|
|
38
|
+
Convert Excalidraw diagrams to **editable PowerPoint vector objects** or **Lucid Package Format** (`.lucid`).
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
### PowerPoint output
|
|
45
|
+
|
|
46
|
+
Each shape, arrow, and text box becomes a native PowerPoint object you can move, resize, recolour, and animate individually — not a flat image.
|
|
47
|
+
|
|
48
|
+
| Excalidraw element | PowerPoint output |
|
|
49
|
+
|---|---|
|
|
50
|
+
| Rectangle (with/without roundness) | Auto-shape (Rectangle / Rounded Rectangle) |
|
|
51
|
+
| Ellipse | Auto-shape (Oval) |
|
|
52
|
+
| Diamond | Auto-shape (Diamond) |
|
|
53
|
+
| Arrow (2-point) | Straight Connector with arrowheads |
|
|
54
|
+
| Line / Arrow (multi-point) | Freeform path |
|
|
55
|
+
| Freedraw | Freeform path (approximate) |
|
|
56
|
+
| Text (standalone) | Text Box |
|
|
57
|
+
| Text (bound to shape) | Text inside the parent shape |
|
|
58
|
+
| Image (PNG/JPEG/WEBP) | Embedded picture |
|
|
59
|
+
| Image (SVG) | PNG fallback _(requires `cairosvg`)_ |
|
|
60
|
+
| Frame | Separate slide |
|
|
61
|
+
| No frames | Single slide (whole canvas) |
|
|
62
|
+
|
|
63
|
+
### Lucid output
|
|
64
|
+
|
|
65
|
+
Produces a `.lucid` file (ZIP archive in [Lucid Package Format](https://lucid.readme.io/docs/overview-si)) that can be opened directly in LucidChart or LucidSpark.
|
|
66
|
+
|
|
67
|
+
| Excalidraw element | Lucid output |
|
|
68
|
+
|---|---|
|
|
69
|
+
| Rectangle (with/without roundness) | `rectangle` shape |
|
|
70
|
+
| Ellipse | `circle` shape |
|
|
71
|
+
| Diamond | `diamond` shape |
|
|
72
|
+
| Arrow / Line (2-point) | `elbow` line connector |
|
|
73
|
+
| Arrow / Line (multi-point) | `straight` line with intermediate joints |
|
|
74
|
+
| Freedraw | `flexiblePolygon` (downsampled to ≤100 vertices) |
|
|
75
|
+
| Text (standalone) | `text` shape |
|
|
76
|
+
| Text (bound to shape) | Text property on the parent shape |
|
|
77
|
+
| Image | `image` shape with file embedded in the archive |
|
|
78
|
+
| Frame | Separate page |
|
|
79
|
+
| No frames | Single page (whole canvas) |
|
|
80
|
+
|
|
81
|
+
### Styling carried over (both formats)
|
|
82
|
+
|
|
83
|
+
Fill colour, stroke colour, stroke width, dashed/dotted strokes, rounded corners, arrowhead types, opacity, background colour, multi-line text, font family, font size, and text alignment.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Installation
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
pip install excalidraw-convert
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
For SVG image support in PowerPoint output (optional):
|
|
94
|
+
```bash
|
|
95
|
+
pip install "excalidraw-convert[svg]"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
To install from source:
|
|
99
|
+
```bash
|
|
100
|
+
pip install -e ".[dev]"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Usage
|
|
106
|
+
|
|
107
|
+
### Command line
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# Convert to PowerPoint
|
|
111
|
+
excalidraw-convert convert "https://excalidraw.com/#json=<id>,<key>" -o output.pptx
|
|
112
|
+
|
|
113
|
+
# Convert to Lucid Package Format
|
|
114
|
+
excalidraw-convert convert-lucid "https://excalidraw.com/#json=<id>,<key>" -o output.lucid
|
|
115
|
+
|
|
116
|
+
# From a direct JSON / .excalidraw file URL
|
|
117
|
+
excalidraw-convert convert "https://raw.githubusercontent.com/you/repo/main/diagram.excalidraw" -o output.pptx
|
|
118
|
+
|
|
119
|
+
# Append slides to an existing presentation
|
|
120
|
+
excalidraw-convert convert "https://..." -o existing.pptx --embed
|
|
121
|
+
|
|
122
|
+
# Inspect a scene before converting
|
|
123
|
+
excalidraw-convert inspect "https://..."
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Also callable as a module:
|
|
127
|
+
```bash
|
|
128
|
+
python -m excalidraw_convert convert "https://..." -o output.pptx
|
|
129
|
+
python -m excalidraw_convert convert-lucid "https://..." -o output.lucid
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Python API
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from excalidraw_convert import fetch_scene, convert, convert_to_lucid
|
|
136
|
+
|
|
137
|
+
scene = fetch_scene("https://excalidraw.com/#json=<id>,<key>")
|
|
138
|
+
|
|
139
|
+
# PowerPoint
|
|
140
|
+
convert(scene, "output.pptx")
|
|
141
|
+
|
|
142
|
+
# Lucid Package Format
|
|
143
|
+
convert_to_lucid(scene, "output.lucid")
|
|
144
|
+
|
|
145
|
+
# Append to an existing PowerPoint file
|
|
146
|
+
convert(scene, "existing.pptx", embed_path="existing.pptx")
|
|
147
|
+
|
|
148
|
+
# Custom slide dimensions (inches, new files only)
|
|
149
|
+
convert(scene, "output.pptx", slide_width=10, slide_height=7.5)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Supported URL formats
|
|
155
|
+
|
|
156
|
+
| Format | Example |
|
|
157
|
+
|---|---|
|
|
158
|
+
| Excalidraw share link | `https://excalidraw.com/#json=abc123,base64key` |
|
|
159
|
+
| Raw `.excalidraw` file URL | `https://raw.githubusercontent.com/.../diagram.excalidraw` |
|
|
160
|
+
| Any URL returning JSON | `https://your-server.com/scene.json` |
|
|
161
|
+
|
|
162
|
+
> **Note on Excalidraw share links:** the scene is end-to-end encrypted in the browser. This tool replicates the AES-GCM decryption that the Excalidraw web app performs client-side. If decryption fails for a link, try exporting the scene as a `.excalidraw` file and hosting it somewhere accessible.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Limitations / known gaps
|
|
167
|
+
|
|
168
|
+
### Both formats
|
|
169
|
+
- **Roughness / hand-drawn style**: Excalidraw's sketchy aesthetic is not reproduced — shapes are rendered as clean geometric shapes.
|
|
170
|
+
- **Hachure fill**: approximated as a solid fill.
|
|
171
|
+
- **Curved / elbow arrows**: rendered as straight connectors or straight segments.
|
|
172
|
+
- **`freedraw` pressures**: variable stroke-width pressure data is ignored; a fixed width is used.
|
|
173
|
+
- **Grouped elements**: group membership is preserved in rendering order but not as native groups.
|
|
174
|
+
- **Embeddable / iframe elements**: skipped with a warning.
|
|
175
|
+
- **Fonts**: Excalidraw's *Virgil* hand-drawn font is mapped to *Caveat* (install it for best results; falls back to the system default).
|
|
176
|
+
|
|
177
|
+
### PowerPoint only
|
|
178
|
+
- `freedraw` elements are rendered as freeform polygon paths (not true brush strokes).
|
|
179
|
+
|
|
180
|
+
### Lucid only
|
|
181
|
+
- `freedraw` elements are approximated as `flexiblePolygon` shapes with up to 100 sampled vertices.
|
|
182
|
+
- Arrow connection points snap to the nearest edge of the target shape.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Development
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
pip install -e ".[dev]"
|
|
190
|
+
pytest tests/ -v
|
|
191
|
+
```
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# excalidraw-convert
|
|
2
|
+
|
|
3
|
+
Convert Excalidraw diagrams to **editable PowerPoint vector objects** or **Lucid Package Format** (`.lucid`).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
### PowerPoint output
|
|
10
|
+
|
|
11
|
+
Each shape, arrow, and text box becomes a native PowerPoint object you can move, resize, recolour, and animate individually — not a flat image.
|
|
12
|
+
|
|
13
|
+
| Excalidraw element | PowerPoint output |
|
|
14
|
+
|---|---|
|
|
15
|
+
| Rectangle (with/without roundness) | Auto-shape (Rectangle / Rounded Rectangle) |
|
|
16
|
+
| Ellipse | Auto-shape (Oval) |
|
|
17
|
+
| Diamond | Auto-shape (Diamond) |
|
|
18
|
+
| Arrow (2-point) | Straight Connector with arrowheads |
|
|
19
|
+
| Line / Arrow (multi-point) | Freeform path |
|
|
20
|
+
| Freedraw | Freeform path (approximate) |
|
|
21
|
+
| Text (standalone) | Text Box |
|
|
22
|
+
| Text (bound to shape) | Text inside the parent shape |
|
|
23
|
+
| Image (PNG/JPEG/WEBP) | Embedded picture |
|
|
24
|
+
| Image (SVG) | PNG fallback _(requires `cairosvg`)_ |
|
|
25
|
+
| Frame | Separate slide |
|
|
26
|
+
| No frames | Single slide (whole canvas) |
|
|
27
|
+
|
|
28
|
+
### Lucid output
|
|
29
|
+
|
|
30
|
+
Produces a `.lucid` file (ZIP archive in [Lucid Package Format](https://lucid.readme.io/docs/overview-si)) that can be opened directly in LucidChart or LucidSpark.
|
|
31
|
+
|
|
32
|
+
| Excalidraw element | Lucid output |
|
|
33
|
+
|---|---|
|
|
34
|
+
| Rectangle (with/without roundness) | `rectangle` shape |
|
|
35
|
+
| Ellipse | `circle` shape |
|
|
36
|
+
| Diamond | `diamond` shape |
|
|
37
|
+
| Arrow / Line (2-point) | `elbow` line connector |
|
|
38
|
+
| Arrow / Line (multi-point) | `straight` line with intermediate joints |
|
|
39
|
+
| Freedraw | `flexiblePolygon` (downsampled to ≤100 vertices) |
|
|
40
|
+
| Text (standalone) | `text` shape |
|
|
41
|
+
| Text (bound to shape) | Text property on the parent shape |
|
|
42
|
+
| Image | `image` shape with file embedded in the archive |
|
|
43
|
+
| Frame | Separate page |
|
|
44
|
+
| No frames | Single page (whole canvas) |
|
|
45
|
+
|
|
46
|
+
### Styling carried over (both formats)
|
|
47
|
+
|
|
48
|
+
Fill colour, stroke colour, stroke width, dashed/dotted strokes, rounded corners, arrowhead types, opacity, background colour, multi-line text, font family, font size, and text alignment.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install excalidraw-convert
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
For SVG image support in PowerPoint output (optional):
|
|
59
|
+
```bash
|
|
60
|
+
pip install "excalidraw-convert[svg]"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
To install from source:
|
|
64
|
+
```bash
|
|
65
|
+
pip install -e ".[dev]"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Usage
|
|
71
|
+
|
|
72
|
+
### Command line
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# Convert to PowerPoint
|
|
76
|
+
excalidraw-convert convert "https://excalidraw.com/#json=<id>,<key>" -o output.pptx
|
|
77
|
+
|
|
78
|
+
# Convert to Lucid Package Format
|
|
79
|
+
excalidraw-convert convert-lucid "https://excalidraw.com/#json=<id>,<key>" -o output.lucid
|
|
80
|
+
|
|
81
|
+
# From a direct JSON / .excalidraw file URL
|
|
82
|
+
excalidraw-convert convert "https://raw.githubusercontent.com/you/repo/main/diagram.excalidraw" -o output.pptx
|
|
83
|
+
|
|
84
|
+
# Append slides to an existing presentation
|
|
85
|
+
excalidraw-convert convert "https://..." -o existing.pptx --embed
|
|
86
|
+
|
|
87
|
+
# Inspect a scene before converting
|
|
88
|
+
excalidraw-convert inspect "https://..."
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Also callable as a module:
|
|
92
|
+
```bash
|
|
93
|
+
python -m excalidraw_convert convert "https://..." -o output.pptx
|
|
94
|
+
python -m excalidraw_convert convert-lucid "https://..." -o output.lucid
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Python API
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from excalidraw_convert import fetch_scene, convert, convert_to_lucid
|
|
101
|
+
|
|
102
|
+
scene = fetch_scene("https://excalidraw.com/#json=<id>,<key>")
|
|
103
|
+
|
|
104
|
+
# PowerPoint
|
|
105
|
+
convert(scene, "output.pptx")
|
|
106
|
+
|
|
107
|
+
# Lucid Package Format
|
|
108
|
+
convert_to_lucid(scene, "output.lucid")
|
|
109
|
+
|
|
110
|
+
# Append to an existing PowerPoint file
|
|
111
|
+
convert(scene, "existing.pptx", embed_path="existing.pptx")
|
|
112
|
+
|
|
113
|
+
# Custom slide dimensions (inches, new files only)
|
|
114
|
+
convert(scene, "output.pptx", slide_width=10, slide_height=7.5)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Supported URL formats
|
|
120
|
+
|
|
121
|
+
| Format | Example |
|
|
122
|
+
|---|---|
|
|
123
|
+
| Excalidraw share link | `https://excalidraw.com/#json=abc123,base64key` |
|
|
124
|
+
| Raw `.excalidraw` file URL | `https://raw.githubusercontent.com/.../diagram.excalidraw` |
|
|
125
|
+
| Any URL returning JSON | `https://your-server.com/scene.json` |
|
|
126
|
+
|
|
127
|
+
> **Note on Excalidraw share links:** the scene is end-to-end encrypted in the browser. This tool replicates the AES-GCM decryption that the Excalidraw web app performs client-side. If decryption fails for a link, try exporting the scene as a `.excalidraw` file and hosting it somewhere accessible.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Limitations / known gaps
|
|
132
|
+
|
|
133
|
+
### Both formats
|
|
134
|
+
- **Roughness / hand-drawn style**: Excalidraw's sketchy aesthetic is not reproduced — shapes are rendered as clean geometric shapes.
|
|
135
|
+
- **Hachure fill**: approximated as a solid fill.
|
|
136
|
+
- **Curved / elbow arrows**: rendered as straight connectors or straight segments.
|
|
137
|
+
- **`freedraw` pressures**: variable stroke-width pressure data is ignored; a fixed width is used.
|
|
138
|
+
- **Grouped elements**: group membership is preserved in rendering order but not as native groups.
|
|
139
|
+
- **Embeddable / iframe elements**: skipped with a warning.
|
|
140
|
+
- **Fonts**: Excalidraw's *Virgil* hand-drawn font is mapped to *Caveat* (install it for best results; falls back to the system default).
|
|
141
|
+
|
|
142
|
+
### PowerPoint only
|
|
143
|
+
- `freedraw` elements are rendered as freeform polygon paths (not true brush strokes).
|
|
144
|
+
|
|
145
|
+
### Lucid only
|
|
146
|
+
- `freedraw` elements are approximated as `flexiblePolygon` shapes with up to 100 sampled vertices.
|
|
147
|
+
- Arrow connection points snap to the nearest edge of the target shape.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Development
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
pip install -e ".[dev]"
|
|
155
|
+
pytest tests/ -v
|
|
156
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
excalidraw-convert: Convert Excalidraw diagrams to editable PowerPoint vector objects
|
|
3
|
+
or Lucid Package Format (.lucid) files.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .converter import convert
|
|
7
|
+
from .fetcher import fetch_scene
|
|
8
|
+
from .lucid_converter import convert_to_lucid
|
|
9
|
+
|
|
10
|
+
__all__ = ["convert", "fetch_scene", "convert_to_lucid"]
|
|
11
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI interface for excalidraw-convert.
|
|
3
|
+
|
|
4
|
+
Usage examples:
|
|
5
|
+
# Convert to PowerPoint
|
|
6
|
+
excalidraw-convert convert "https://excalidraw.com/#json=abc,key" -o output.pptx
|
|
7
|
+
|
|
8
|
+
# Append slides to an existing .pptx
|
|
9
|
+
excalidraw-convert convert "https://..." -o existing.pptx --embed
|
|
10
|
+
|
|
11
|
+
# Convert to Lucid Package Format
|
|
12
|
+
excalidraw-convert convert-lucid "https://excalidraw.com/#json=abc,key" -o output.lucid
|
|
13
|
+
|
|
14
|
+
# Also available as: python -m excalidraw_convert convert "..."
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import sys
|
|
18
|
+
|
|
19
|
+
import click
|
|
20
|
+
|
|
21
|
+
from .fetcher import fetch_scene
|
|
22
|
+
from .converter import convert
|
|
23
|
+
from .lucid_converter import convert_to_lucid
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@click.group()
|
|
27
|
+
@click.version_option(package_name="excalidraw-convert")
|
|
28
|
+
def main():
|
|
29
|
+
"""Convert Excalidraw diagrams to PowerPoint or Lucid Package Format."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@main.command()
|
|
33
|
+
@click.argument("url")
|
|
34
|
+
@click.option(
|
|
35
|
+
"-o",
|
|
36
|
+
"--output",
|
|
37
|
+
default="output.pptx",
|
|
38
|
+
show_default=True,
|
|
39
|
+
help="Path of the output .pptx file.",
|
|
40
|
+
)
|
|
41
|
+
@click.option(
|
|
42
|
+
"--embed",
|
|
43
|
+
is_flag=True,
|
|
44
|
+
default=False,
|
|
45
|
+
help=(
|
|
46
|
+
"Append the converted slides to an existing file instead of creating "
|
|
47
|
+
"a new one. Requires --output to point at an existing .pptx."
|
|
48
|
+
),
|
|
49
|
+
)
|
|
50
|
+
@click.option(
|
|
51
|
+
"--slide-width",
|
|
52
|
+
default=13.33,
|
|
53
|
+
show_default=True,
|
|
54
|
+
type=float,
|
|
55
|
+
help="Slide width in inches (new presentations only).",
|
|
56
|
+
)
|
|
57
|
+
@click.option(
|
|
58
|
+
"--slide-height",
|
|
59
|
+
default=7.5,
|
|
60
|
+
show_default=True,
|
|
61
|
+
type=float,
|
|
62
|
+
help="Slide height in inches (new presentations only).",
|
|
63
|
+
)
|
|
64
|
+
def convert_cmd(url: str, output: str, embed: bool, slide_width: float, slide_height: float):
|
|
65
|
+
"""
|
|
66
|
+
Convert an Excalidraw diagram at URL to a PowerPoint file.
|
|
67
|
+
|
|
68
|
+
URL can be:
|
|
69
|
+
|
|
70
|
+
\b
|
|
71
|
+
• An Excalidraw share link: https://excalidraw.com/#json=<id>,<key>
|
|
72
|
+
• A direct JSON/file URL: https://example.com/diagram.excalidraw
|
|
73
|
+
"""
|
|
74
|
+
click.echo(f"Fetching scene from: {url}")
|
|
75
|
+
try:
|
|
76
|
+
scene = fetch_scene(url)
|
|
77
|
+
except Exception as exc:
|
|
78
|
+
click.echo(f"Error fetching scene: {exc}", err=True)
|
|
79
|
+
sys.exit(1)
|
|
80
|
+
|
|
81
|
+
n_elements = len([e for e in scene.get("elements", []) if not e.get("isDeleted")])
|
|
82
|
+
n_frames = len([e for e in scene.get("elements", []) if e["type"] == "frame" and not e.get("isDeleted")])
|
|
83
|
+
click.echo(
|
|
84
|
+
f"Scene loaded: {n_elements} elements, "
|
|
85
|
+
f"{n_frames} frame(s) -> {max(n_frames, 1)} slide(s)"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
embed_path = output if embed else None
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
convert(
|
|
92
|
+
scene,
|
|
93
|
+
output_path=output,
|
|
94
|
+
embed_path=embed_path,
|
|
95
|
+
slide_width=slide_width,
|
|
96
|
+
slide_height=slide_height,
|
|
97
|
+
)
|
|
98
|
+
except Exception as exc:
|
|
99
|
+
click.echo(f"Error converting scene: {exc}", err=True)
|
|
100
|
+
sys.exit(1)
|
|
101
|
+
|
|
102
|
+
click.echo(f"Saved: {output}")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@main.command()
|
|
106
|
+
@click.argument("url")
|
|
107
|
+
def inspect(url: str):
|
|
108
|
+
"""Print a summary of the Excalidraw scene without converting."""
|
|
109
|
+
click.echo(f"Fetching: {url}")
|
|
110
|
+
try:
|
|
111
|
+
scene = fetch_scene(url)
|
|
112
|
+
except Exception as exc:
|
|
113
|
+
click.echo(f"Error: {exc}", err=True)
|
|
114
|
+
sys.exit(1)
|
|
115
|
+
|
|
116
|
+
elements = [e for e in scene.get("elements", []) if not e.get("isDeleted")]
|
|
117
|
+
|
|
118
|
+
from collections import Counter
|
|
119
|
+
counts = Counter(e["type"] for e in elements)
|
|
120
|
+
|
|
121
|
+
click.echo(f"\nElement counts:")
|
|
122
|
+
for etype, count in sorted(counts.items()):
|
|
123
|
+
click.echo(f" {etype:20s} {count}")
|
|
124
|
+
|
|
125
|
+
frames = [e for e in elements if e["type"] == "frame"]
|
|
126
|
+
if frames:
|
|
127
|
+
click.echo(f"\nFrames ({len(frames)}):")
|
|
128
|
+
for f in frames:
|
|
129
|
+
click.echo(
|
|
130
|
+
f" [{f.get('name', '(unnamed)')}] "
|
|
131
|
+
f"{f['width']:.0f}×{f['height']:.0f} px "
|
|
132
|
+
f"@ ({f['x']:.0f}, {f['y']:.0f})"
|
|
133
|
+
)
|
|
134
|
+
else:
|
|
135
|
+
click.echo("\nNo frames found — whole canvas will become one slide.")
|
|
136
|
+
|
|
137
|
+
files = scene.get("files", {})
|
|
138
|
+
if files:
|
|
139
|
+
click.echo(f"\nEmbedded files: {len(files)}")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@main.command("convert-lucid")
|
|
143
|
+
@click.argument("url")
|
|
144
|
+
@click.option(
|
|
145
|
+
"-o",
|
|
146
|
+
"--output",
|
|
147
|
+
default="output.lucid",
|
|
148
|
+
show_default=True,
|
|
149
|
+
help="Path of the output .lucid file.",
|
|
150
|
+
)
|
|
151
|
+
def convert_lucid_cmd(url: str, output: str):
|
|
152
|
+
"""
|
|
153
|
+
Convert an Excalidraw diagram at URL to a Lucid Package Format (.lucid) file.
|
|
154
|
+
|
|
155
|
+
URL can be:
|
|
156
|
+
|
|
157
|
+
\b
|
|
158
|
+
• An Excalidraw share link: https://excalidraw.com/#json=<id>,<key>
|
|
159
|
+
• A direct JSON/file URL: https://example.com/diagram.excalidraw
|
|
160
|
+
"""
|
|
161
|
+
click.echo(f"Fetching scene from: {url}")
|
|
162
|
+
try:
|
|
163
|
+
scene = fetch_scene(url)
|
|
164
|
+
except Exception as exc:
|
|
165
|
+
click.echo(f"Error fetching scene: {exc}", err=True)
|
|
166
|
+
import sys
|
|
167
|
+
sys.exit(1)
|
|
168
|
+
|
|
169
|
+
n_elements = len([e for e in scene.get("elements", []) if not e.get("isDeleted")])
|
|
170
|
+
n_frames = len([
|
|
171
|
+
e for e in scene.get("elements", [])
|
|
172
|
+
if e["type"] == "frame" and not e.get("isDeleted")
|
|
173
|
+
])
|
|
174
|
+
click.echo(
|
|
175
|
+
f"Scene loaded: {n_elements} elements, "
|
|
176
|
+
f"{n_frames} frame(s) → {max(n_frames, 1)} page(s)"
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
try:
|
|
180
|
+
convert_to_lucid(scene, output_path=output)
|
|
181
|
+
except Exception as exc:
|
|
182
|
+
click.echo(f"Error converting scene: {exc}", err=True)
|
|
183
|
+
import sys
|
|
184
|
+
sys.exit(1)
|
|
185
|
+
|
|
186
|
+
click.echo(f"Saved: {output}")
|