From f1167f07c226d9d82aade25869dc216ae5517194 Mon Sep 17 00:00:00 2001 From: Guido van Dijk Date: Mon, 21 Sep 2026 18:15:22 +0200 Subject: [PATCH] feat(video): Azure Speech als stemroute voor publicatie (zelfde stem, sleutel uit de omgeving) Co-Authored-By: Claude Opus 5 --- README.md | 4 +++- _gereedschap/maak_video.py | 33 +++++++++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b299380..c6feb7a 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,9 @@ ondertitels ingesloten en apart als `.srt`): de schermafbeeldingen uit de handle door een Nederlandse computerstem, als lossere vertelling van dezelfde stappen en valkuilen. De tekst staat in `video-script.md`; `_gereedschap/maak_video.py` bouwt de video opnieuw (ffmpeg, Pillow, edge-tts). Wie de stem of het tempo wil veranderen, past de kopregels van het -script aan. +script aan. De stem is nl-NL-Fenna van Microsoft, standaard via de Edge-leesfunctie; met een +Azure Speech-sleutel in de omgeving (`AZURE_SPEECH_KEY`) gebruikt het script dezelfde stem via +Azure, de route voor publicatie buiten deze repository. | Video | Duur | | --- | --- | diff --git a/_gereedschap/maak_video.py b/_gereedschap/maak_video.py index ba18726..1281149 100644 --- a/_gereedschap/maak_video.py +++ b/_gereedschap/maak_video.py @@ -6,8 +6,10 @@ Gebruik: python3 ../_gereedschap/maak_video.py video-script.md Uitvoer: .mp4 (1920x1080, H.264, AAC, met ingesloten ondertitels) en .srt Vereist: ffmpeg, Pillow en het pakket edge-tts (pip install edge-tts). De stem komt van de - neurale Microsoft-stemmen (nl-NL-FennaNeural standaard); voor publicatie dezelfde - stem via Azure Speech, zie README. + neurale Microsoft-stemmen (nl-NL-FennaNeural standaard) via de Edge-leesfunctie. + Voor publicatie dezelfde stem via Azure Speech: zet AZURE_SPEECH_KEY (en eventueel + AZURE_SPEECH_REGION, standaard westeurope) in de omgeving; de sleutel wordt nergens + opgeslagen. Zonder sleutel gebruikt het script edge-tts. Opbouw van het script (regels die met [ beginnen zijn koppen, de rest is de gesproken tekst): # titel: ... titel op de eerste kaart @@ -24,7 +26,8 @@ Opbouw van het script (regels die met [ beginnen zijn koppen, de rest is de gesp [slot] de slotkaart Lege regels tussen de tekst zijn pauzes van een halve seconde. """ -import asyncio, json, re, subprocess, sys, tempfile +import asyncio, json, os, re, subprocess, sys, tempfile, urllib.request +from xml.sax.saxutils import escape from pathlib import Path from PIL import Image, ImageDraw, ImageFont @@ -162,6 +165,24 @@ async def spreek(tekst, stem, tempo, mp3): return woorden +def spreek_azure(tekst, stem, tempo, mp3): + """Dezelfde stem via Azure Speech (REST). Geeft geen woordgrenzen; de ondertitels worden + dan naar tekstlengte verdeeld.""" + key = os.environ["AZURE_SPEECH_KEY"]; regio = os.environ.get("AZURE_SPEECH_REGION", "westeurope") + taal = "-".join(stem.split("-")[:2]) + alineas = "".join(f"

{escape(a.strip())}

" for a in tekst.split("\n\n") if a.strip()) + ssml = (f'' + f'{alineas}') + req = urllib.request.Request(f"https://{regio}.tts.speech.microsoft.com/cognitiveservices/v1", + data=ssml.encode("utf-8"), method="POST", + headers={"Ocp-Apim-Subscription-Key": key, "Content-Type": "application/ssml+xml", + "X-Microsoft-OutputFormat": "audio-24khz-96kbitrate-mono-mp3", + "User-Agent": "maak_video"}) + with urllib.request.urlopen(req, timeout=120) as r: + Path(mp3).write_bytes(r.read()) + return [] + + def _norm(w): return re.sub(r"[^\w]", "", w.lower()) @@ -210,6 +231,7 @@ def main(): stem = meta.get("stem", "nl-NL-FennaNeural"); tempo = meta.get("tempo", "-5%") naam = meta.get("uitvoer", script.stem.replace("video-script", "video")) pauze = 0.8 # seconden stilte na elk segment + print("stem via", "Azure Speech" if os.environ.get("AZURE_SPEECH_KEY") else "edge-tts", stem, tempo) tmp = Path(tempfile.mkdtemp(prefix="video-")) clips, cues, t0 = [], [], 0.0 for n, s in enumerate(segs, 1): @@ -224,7 +246,10 @@ def main(): else: delen = [d.strip() for d in s["arg"].split("|")] kaart_beeld(map_ / "beelden" / delen[0], delen[1] if len(delen) > 1 else "", png) - woorden = asyncio.run(spreek(s["spraak"], stem, tempo, mp3)) + if os.environ.get("AZURE_SPEECH_KEY"): + woorden = spreek_azure(s["spraak"], stem, tempo, mp3) + else: + woorden = asyncio.run(spreek(s["spraak"], stem, tempo, mp3)) d = duur(mp3) for a, b, z in cues_uit_woorden(s["spraak"], woorden, d): cues.append((t0 + a, t0 + min(b + 0.15, d), z))