feat(video): Azure Speech als stemroute voor publicatie (zelfde stem, sleutel uit de omgeving)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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 |
|
||||
| --- | --- |
|
||||
|
||||
@@ -6,8 +6,10 @@ Gebruik: python3 ../_gereedschap/maak_video.py video-script.md
|
||||
Uitvoer: <naam>.mp4 (1920x1080, H.264, AAC, met ingesloten ondertitels) en <naam>.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"<p>{escape(a.strip())}</p>" for a in tekst.split("\n\n") if a.strip())
|
||||
ssml = (f'<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="{taal}">'
|
||||
f'<voice name="{stem}"><prosody rate="{tempo}">{alineas}</prosody></voice></speak>')
|
||||
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))
|
||||
|
||||
Reference in New Issue
Block a user