feat: presentaties ook als PowerPoint, met de notities in het notitieveld
- _gereedschap/maak_pptx.py: dia als beeld op 16:9, notities in de notitiepagina - pptx voor de sessie en de lezing; die van de workshop blijft buiten git (privenotities) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,3 +10,4 @@ _gereedschap/extract.py
|
||||
NOTITIES-intern.md
|
||||
00-presentatie/notities-begeleider.json
|
||||
00-presentatie/presentatie-met-notities.pdf
|
||||
00-presentatie/presentatie-workshop-ai-agents.pptx
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Maakt een PowerPoint-bestand van een gebouwde presentatie uit deze repository.
|
||||
|
||||
Elke dia wordt als beeld op een 16:9-slide gezet en de sprekersnotities komen in het
|
||||
notitieveld, zodat PowerPoint ze in de sprekersweergave op jouw scherm toont terwijl de zaal
|
||||
alleen de dia ziet. Het beeld is een afbeelding: de opmaak is precies gelijk aan de PDF, maar
|
||||
de tekst is in PowerPoint niet te bewerken. Wijzigen doe je in maak_dias.py en dan opnieuw
|
||||
bouwen.
|
||||
|
||||
Gebruik: python3 ../_gereedschap/maak_pptx.py <dia-pdf>
|
||||
Bijvoorbeeld in 00-sessie: python3 ../_gereedschap/maak_pptx.py sessie-agents-en-projecten.pdf
|
||||
|
||||
Staat er een notities-begeleider.json naast maak_dias.py, dan gebruikt het script die
|
||||
uitgebreide notities en zegt het erbij; dat bestand is privé, dus deel die pptx niet.
|
||||
Vereist: pdftoppm (Poppler) en python-pptx.
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
from pptx import Presentation
|
||||
from pptx.util import Inches
|
||||
except ImportError:
|
||||
sys.exit("python-pptx ontbreekt. Bijvoorbeeld: ~/.local/venvs/uitlegvideo/bin/python "
|
||||
"../_gereedschap/maak_pptx.py <dia-pdf>")
|
||||
|
||||
pdf = Path(sys.argv[1]).resolve()
|
||||
if not pdf.exists():
|
||||
sys.exit(f"niet gevonden: {pdf}")
|
||||
MAP = pdf.parent
|
||||
P = MAP / "project"
|
||||
PRIVE = (MAP / "notities-begeleider.json").exists()
|
||||
|
||||
# 1. dia's en PDF opnieuw bouwen, zodat beeld en notities bij elkaar horen
|
||||
subprocess.run([sys.executable, str(MAP / "maak_dias.py")] + (["--notities"] if PRIVE else []),
|
||||
check=True, capture_output=True)
|
||||
deck = json.loads((P / "deck.json").read_text(encoding="utf-8"))
|
||||
faces = "".join(f'<link rel="stylesheet" href="{f["href"]}">' for f in deck["faces"].values())
|
||||
tmp = Path(tempfile.mkdtemp(prefix="pptx-"))
|
||||
css = """
|
||||
@page { size: 1920px 1080px; margin: 0; }
|
||||
html, body { margin: 0; padding: 0; }
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
section { position: relative; width: 1920px; height: 1080px; overflow: hidden; page-break-after: always; break-after: page; }
|
||||
section:last-child { page-break-after: auto; }
|
||||
aside { display: none; }
|
||||
ul { padding-left: 1.1em; }
|
||||
table { border-collapse: collapse; width: 100%; }
|
||||
th, td { border: 1px solid #D8D2C4; padding: 12px 18px; text-align: left; vertical-align: top; }
|
||||
img { max-width: 100%; }
|
||||
"""
|
||||
(tmp / "dias.html").write_text(
|
||||
f'<!DOCTYPE html><html lang="nl"><head><meta charset="utf-8">{faces}<style>{css}</style></head><body>'
|
||||
+ "".join((P / "slides" / f"{sid}.html").read_text(encoding="utf-8") for sid in deck["order"])
|
||||
+ "</body></html>", encoding="utf-8")
|
||||
subprocess.run(["google-chrome", "--headless=new", "--disable-gpu", "--no-pdf-header-footer",
|
||||
"--virtual-time-budget=8000", f"--print-to-pdf={tmp / 'dias.pdf'}", (tmp / "dias.html").as_uri()],
|
||||
check=True, capture_output=True)
|
||||
|
||||
# 2. elke pagina als beeld
|
||||
subprocess.run(["pdftoppm", "-r", "100", "-jpeg", "-jpegopt", "quality=88",
|
||||
str(tmp / "dias.pdf"), str(tmp / "dia")], check=True)
|
||||
beelden = sorted(tmp.glob("dia-*.jpg"))
|
||||
if len(beelden) != len(deck["order"]):
|
||||
sys.exit(f"{len(beelden)} beelden voor {len(deck['order'])} dia's; bouw de dia's opnieuw")
|
||||
|
||||
|
||||
def notities(sid):
|
||||
tekst = re.search(r"<aside>(.*?)</aside>",
|
||||
(P / "slides" / f"{sid}.html").read_text(encoding="utf-8"), re.S)
|
||||
tekst = (tekst.group(1).strip() if tekst else "")
|
||||
tekst = re.sub(r"<[^>]+>", "", tekst)
|
||||
# de privénotities staan als BEDOELING: ... | ZEGGEN: ... op één regel; leesbaar maken
|
||||
return "\n\n".join(d.strip() for d in tekst.split(" | ") if d.strip())
|
||||
|
||||
|
||||
# 3. de pptx: 16:9, elk beeld schermvullend, notities in het notitieveld
|
||||
prs = Presentation()
|
||||
prs.slide_width, prs.slide_height = Inches(13.333), Inches(7.5)
|
||||
leeg = prs.slide_layouts[6] # blank
|
||||
for n, sid in enumerate(deck["order"]):
|
||||
dia = prs.slides.add_slide(leeg)
|
||||
dia.shapes.add_picture(str(beelden[n]), 0, 0, width=prs.slide_width, height=prs.slide_height)
|
||||
dia.notes_slide.notes_text_frame.text = notities(sid)
|
||||
uit = pdf.with_suffix(".pptx")
|
||||
prs.save(str(uit))
|
||||
|
||||
# 4. de dia-bestanden terugzetten op de publieke versie
|
||||
subprocess.run([sys.executable, str(MAP / "maak_dias.py")], check=True, capture_output=True)
|
||||
soort = "uitgebreide begeleidersnotities (privé, niet delen)" if PRIVE else "de notities uit de dia's"
|
||||
print(f"{uit.name}: {uit.stat().st_size // 1024} kB, {len(deck['order'])} dia's, met {soort}")
|
||||
Reference in New Issue
Block a user