LeX-logo op titelpagina en in de voet van elk document
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Maakt van sectieoverleg-script.txt een testopname met drie verschillende computerstemmen,
|
||||
zodat de sprekerherkenning (gpt-4o-transcribe-diarize) iets te onderscheiden heeft.
|
||||
|
||||
Gebruik: OPENAI_API_KEY=sk-... python3 maak_opname_drie_stemmen.py
|
||||
Vereist: ffmpeg (aanwezig) en een OpenAI-sleutel in de omgevingsvariabele; de sleutel wordt
|
||||
nergens opgeslagen. Kosten: circa 4.000 tekens bij tts-1, ongeveer 6 cent.
|
||||
|
||||
Uitvoer: sectieoverleg-drie-stemmen.mp3 (128 kbps, om te beluisteren)
|
||||
sectieoverleg-drie-stemmen-klein.mp3 (32 kbps mono, onder de 5 MB-grens van de tool)
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
SCRIPT = HERE / "sectieoverleg-script.txt"
|
||||
STEMMEN = {"Karin": "nova", "Bram": "echo", "Fatima": "alloy"} # drie duidelijk verschillende stemmen
|
||||
VERTELLER = "onyx" # voor de openingsregel
|
||||
|
||||
key = os.environ.get("OPENAI_API_KEY")
|
||||
if not key:
|
||||
sys.exit("Zet de sleutel in de omgevingsvariabele OPENAI_API_KEY en start opnieuw.")
|
||||
|
||||
|
||||
def spreek(tekst: str, stem: str, doel: Path) -> None:
|
||||
req = urllib.request.Request(
|
||||
"https://api.openai.com/v1/audio/speech",
|
||||
data=json.dumps({"model": "tts-1", "voice": stem, "input": tekst, "response_format": "mp3"}).encode(),
|
||||
headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=120) as r:
|
||||
doel.write_bytes(r.read())
|
||||
|
||||
|
||||
regels = [r.strip() for r in SCRIPT.read_text(encoding="utf-8").splitlines() if r.strip()]
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
tmp = Path(tmp)
|
||||
delen = []
|
||||
for i, regel in enumerate(regels):
|
||||
m = re.match(r"^(Karin|Bram|Fatima):\s*(.+)$", regel)
|
||||
if m:
|
||||
stem, tekst = STEMMEN[m.group(1)], m.group(2)
|
||||
else:
|
||||
stem, tekst = VERTELLER, regel
|
||||
deel = tmp / f"{i:03d}.mp3"
|
||||
spreek(tekst, stem, deel)
|
||||
delen.append(deel)
|
||||
print(f"{i + 1}/{len(regels)} {stem}: {tekst[:50]}...")
|
||||
lijst = tmp / "lijst.txt"
|
||||
lijst.write_text("".join(f"file '{d}'\n" for d in delen))
|
||||
# Korte stilte tussen sprekers komt vanzelf door de pauze aan het eind van elke zin.
|
||||
subprocess.run(["ffmpeg", "-y", "-loglevel", "error", "-f", "concat", "-safe", "0", "-i", str(lijst),
|
||||
"-c:a", "libmp3lame", "-b:a", "128k", str(HERE / "sectieoverleg-drie-stemmen.mp3")], check=True)
|
||||
subprocess.run(["ffmpeg", "-y", "-loglevel", "error", "-i", str(HERE / "sectieoverleg-drie-stemmen.mp3"),
|
||||
"-ac", "1", "-ar", "16000", "-b:a", "32k", str(HERE / "sectieoverleg-drie-stemmen-klein.mp3")], check=True)
|
||||
for naam in ("sectieoverleg-drie-stemmen.mp3", "sectieoverleg-drie-stemmen-klein.mp3"):
|
||||
p = HERE / naam
|
||||
print(f"{naam}: {p.stat().st_size // 1024} kB")
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3
-3
@@ -38,10 +38,10 @@ de handleidingen vallen onder de MIT-licentie:
|
||||
## Uitzonderingen
|
||||
|
||||
- Het logo van AI Netwerk Limburg (`_gereedschap/logo-ai-netwerk-limburg.png` en de kopie in
|
||||
`00-presentatie/beelden/`) en het logo van LeX Consultancy (`00-presentatie/beelden/logo-lex*.png`)
|
||||
zijn merken en vallen niet onder deze licentie. Wie de
|
||||
`00-presentatie/beelden/`) en het logo van LeX Consultancy (`_gereedschap/logo-lex.png`,
|
||||
`00-presentatie/beelden/logo-lex*.png`) zijn merken en vallen niet onder deze licentie. Wie de
|
||||
handleidingen of dia's bewerkt, vervangt het logo door het eigen logo of laat het weg
|
||||
(`build_handleiding.py` en `maak_dias.py` verwachten een PNG op dat pad).
|
||||
(`build_handleiding.py` en `maak_dias.py` verwachten PNG's op die paden).
|
||||
- `08-chatflow-studiecoach/stercollectie-voedselweb-hv3.pdf` en de kopie in `06-inclusief-lesmateriaal/`
|
||||
zijn van VO-content (Stercollecties, https://maken.wikiwijs.nl/157495), CC BY-SA 4.0. De bronvermelding
|
||||
van VO-content blijft staan.
|
||||
|
||||
@@ -73,5 +73,5 @@ De PDF's staan naast de bronnen in git; HTML en Word maak je zelf met build.py.
|
||||
|
||||
Opmaak in de bronnen: genummerde lijst = handelingen, tabel "Wat je intypt" = veldwaarden,
|
||||
codeblok = letterlijk overtypen (label "Intypen"), oranje kader = valkuil, groen kader = "Zelf kiezen"
|
||||
(blockquote die met **Zelf kiezen** begint). Opmaak en logo staan in `_gereedschap/build_handleiding.py`;
|
||||
vervang het logo door dat van je eigen school of netwerk.
|
||||
(blockquote die met **Zelf kiezen** begint). Opmaak en logo's staan in `_gereedschap/build_handleiding.py`;
|
||||
vervang de logo's door die van je eigen school of netwerk.
|
||||
|
||||
@@ -18,6 +18,7 @@ import markdown
|
||||
|
||||
TOOLS = Path(__file__).resolve().parent
|
||||
LOGO = TOOLS / "logo-ai-netwerk-limburg.png"
|
||||
LOGO_LEX = TOOLS / "logo-lex.png" # kleiner, naast het LAI-logo
|
||||
|
||||
# Licentie en contact, komen als colofon onderaan elk document (HTML, PDF en Word).
|
||||
EIGENAAR = "Guido van Dijk, LeX Consultancy B.V. en AI Netwerk Limburg"
|
||||
@@ -33,7 +34,9 @@ body { font-family: "Noto Sans", "DejaVu Sans", Arial, sans-serif; color: #1a1a1
|
||||
.kop { display: flex; align-items: flex-start; justify-content: space-between; gap: 1.5em;
|
||||
margin-bottom: 1em; }
|
||||
.kop-tekst { flex: 1 1 auto; }
|
||||
.kop img { flex: 0 0 auto; width: 3.4cm; height: auto; }
|
||||
.kop .logos { flex: 0 0 auto; display: flex; flex-direction: column; align-items: center; gap: .3em; }
|
||||
.kop img { width: 3.4cm; height: auto; }
|
||||
.kop img.lex { width: 2cm; }
|
||||
h1 { font-size: 22pt; margin: 0 0 .3em; line-height: 1.2; }
|
||||
h2 { font-size: 15pt; margin: 1.6em 0 .5em; padding-top: .3em; border-top: 2px solid #1d4ed8;
|
||||
page-break-after: avoid; break-after: avoid; }
|
||||
@@ -73,6 +76,7 @@ ol li { margin: .25em 0; }
|
||||
.voet { margin-top: 3em; padding-top: .6em; border-top: 1px solid #ddd; font-size: .85em; color: #555;
|
||||
display: flex; align-items: center; gap: .8em; }
|
||||
.voet img { width: 1.6cm; height: auto; }
|
||||
.voet img.lex { width: 1.1cm; }
|
||||
"""
|
||||
|
||||
|
||||
@@ -85,16 +89,16 @@ def read_source(src: Path) -> str:
|
||||
return re.sub(r"(\n> [^\n]*\n)\n(?=> )", r"\1\n<!-- -->\n\n", text)
|
||||
|
||||
|
||||
def logo_src(embed: bool) -> str:
|
||||
def logo_src(embed: bool, pad: Path = LOGO) -> str:
|
||||
if embed:
|
||||
return "data:image/png;base64," + base64.b64encode(LOGO.read_bytes()).decode()
|
||||
return LOGO.as_uri()
|
||||
return "data:image/png;base64," + base64.b64encode(pad.read_bytes()).decode()
|
||||
return pad.as_uri()
|
||||
|
||||
|
||||
COMPACT_CSS = """
|
||||
html { font-size: 9.2pt; } h1 { font-size: 17pt; } h2 { margin-top: 1em; } @page { margin: 1.5cm 2cm 1.5cm 2cm; }
|
||||
p { margin: .35em 0; } table { margin: .5em 0; } th, td { padding: .25em .5em; }
|
||||
.colofon { margin-top: .5em; padding: .3em .5em; font-size: .75em; border: 0; border-top: 1px solid #ddd; border-radius: 0; background: none; } .voet { margin-top: .6em; } .voet img { width: 1.1cm; } .kop img { width: 2.8cm; } ul, ol { margin: .3em 0 .3em 1.3em; }
|
||||
.colofon { margin-top: .5em; padding: .3em .5em; font-size: .75em; border: 0; border-top: 1px solid #ddd; border-radius: 0; background: none; } .voet { margin-top: .6em; } .voet img { width: 1.1cm; } .voet img.lex { width: .8cm; } .kop img { width: 2.8cm; } .kop img.lex { width: 1.6cm; } ul, ol { margin: .3em 0 .3em 1.3em; }
|
||||
"""
|
||||
|
||||
|
||||
@@ -102,6 +106,7 @@ def to_html(md_text: str, here: Path, embed_images: bool, compact: bool = False)
|
||||
body = markdown.markdown(md_text, extensions=["tables", "sane_lists", "smarty", "fenced_code"])
|
||||
title = re.search(r"<h1>(.*?)</h1>", body).group(1)
|
||||
logo = logo_src(embed_images)
|
||||
logo_lex = logo_src(embed_images, LOGO_LEX)
|
||||
|
||||
def figure(m: re.Match) -> str:
|
||||
alt, src = m.group(1), m.group(2)
|
||||
@@ -159,13 +164,15 @@ def to_html(md_text: str, here: Path, embed_images: bool, compact: bool = False)
|
||||
lambda m: (f'<table border="0" width="100%"><tr>'
|
||||
f'<td width="78%" valign="top">{m.group(1)}</td>'
|
||||
f'<td width="22%" valign="top" align="right">'
|
||||
f'<img src="{logo}" width="120" alt="AI Netwerk Limburg" /></td></tr></table>'),
|
||||
f'<img src="{logo}" width="120" alt="AI Netwerk Limburg" /><br />'
|
||||
f'<img src="{logo_lex}" width="70" alt="LeX Consultancy" /></td></tr></table>'),
|
||||
body, count=1, flags=re.S)
|
||||
else:
|
||||
body = re.sub(
|
||||
r"^(<h1>.*?</h1>\s*<p>.*?</p>)",
|
||||
lambda m: (f'<div class="kop"><div class="kop-tekst">{m.group(1)}</div>'
|
||||
f'<img src="{logo}" alt="AI Netwerk Limburg" /></div>'),
|
||||
f'<div class="logos"><img src="{logo}" alt="AI Netwerk Limburg" />'
|
||||
f'<img src="{logo_lex}" class="lex" alt="LeX Consultancy" /></div></div>'),
|
||||
body, count=1, flags=re.S)
|
||||
|
||||
colofon = (
|
||||
@@ -188,8 +195,8 @@ def to_html(md_text: str, here: Path, embed_images: bool, compact: bool = False)
|
||||
return (f'<!DOCTYPE html><html lang="nl"><head><meta charset="utf-8">'
|
||||
f"<title>{title}</title><style>{CSS}{COMPACT_CSS}</style></head><body>{body}</body></html>")
|
||||
|
||||
voet = (f'<div class="voet"><img src="{logo}" alt="" />'
|
||||
f'<span>AI Netwerk Limburg · ai-netwerk-limburg.nl · {title}</span></div>')
|
||||
voet = (f'<div class="voet"><img src="{logo}" alt="" /><img src="{logo_lex}" class="lex" alt="" />'
|
||||
f'<span>AI Netwerk Limburg · LeX Consultancy B.V. · {title}</span></div>')
|
||||
|
||||
return (f'<!DOCTYPE html><html lang="nl"><head><meta charset="utf-8">'
|
||||
f"<title>{title}</title><style>{CSS}{COMPACT_CSS if compact else ''}</style></head>"
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
Reference in New Issue
Block a user