Text-to-Speech für Asterisk: Texte mit Google gTTS in Sprache umwandeln

10. Januar 2025

Eine neue Ansage für deine Asterisk-Telefonanlage wird gebraucht, und schon beginnt die Suche nach der passenden Datei – oder noch schlimmer, du musst sie selbst aufnehmen. Das geht viel einfacher! Mit Google Text-to-Speech (gTTS) und ein bisschen Python kannst du Texte in natürlich klingende Sprachansagen umwandeln und direkt in Asterisk nutzen.

Die beiden neuen Funktionen, mit denen du Texte in gesprochene Sprache umwandeln kannst, habe ich in einem Modul untergebracht. Diese Modul vereinfacht das Schreiben von AGI-Skripten mit Python.

Testumgebung: Asterisk 18.10.0, Python 3.10.0, Ubuntu Mate (also Linux Debian), 10 Jahre alter Laptop.

Dall·e 2025 01 10 06.43.05 A Humorous Illustration Of A Programmer Immersed In Coding Asterisk And Agi Scripts With Python. The Scene Shows A Desk Cluttered With Phones, Cables,
Die Verknüpfung von Python und Asterisk bietet ungeahnte Möglickeiten. Jetzt lernst du wie man Texte in gesprochene Sprache umwandeln kann.

In diesem Beitrag zeige ich dir also zwei praktische Funktionen, mit denen du dynamische Sprachansagen ganz leicht erstellen und einfügen kannst. Unter

Stressige Asterisk Programmierung150px
Module helfen Programmierstress zu vermeiden
AGI-Entwicklung für Asterisk mit einem Python-Modul vereinfacht – 29.12.2024: Wenn du dich bereits mit der Telefonsoftware Asterisk auskennst und etwas Erfahrung mit Python mitbringst, hast du den perfekten Startpunkt, um AGI (Asterisk Gateway Interface) zu erkunden. In diesem Beitrag stelle ich dir mein Python-Modul `asterisk_agi.py` vor. Es ist das Herzstück für die einfache und effektive Entwicklung von AGI-Skripten und ermöglicht dir, dynamische und interaktive Funktionen in Asterisk zu integrieren. – weiter

habe ich bereits ein Asterisk-Modul vorgestellt, dass viele wiederkehrende Funktionen enthält, die immer wieder vorkommen, um mit Hilfe von Python AGI-Skripte für Asterisk zu schreiben. Dieses Modul habe ich nun erweitert und asterisk2_agi.py genannt:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Asterisk AGI Python Module with gTTS Integration
Place this file in the same directory as your main script.
Ensure the file has execution permissions using: chmod +x asterisk_agi.py
"""

import sys
import re
from gtts import gTTS
import os

def init_env():
    """
    Initializes the AGI environment and returns it as a dictionary.
    """
    env = {}
    while True:
        line = sys.stdin.readline().strip()
        if line == '':
            break
        key, data = line.split(':', 1)
        if key[:4] != 'agi_':
            # Skip input that doesn't begin with agi_
            sys.stderr.write("Did not work!\n")
            sys.stderr.flush()
            continue
        key = key.strip()
        data = data.strip()
        if key != '':
            env[key] = data

    sys.stderr.write("AGI Environment Dump:\n")
    sys.stderr.flush()
    for key in env.keys():
        sys.stderr.write(f" -- {key} = {env[key]}\n")
        sys.stderr.flush()
    
    return env


def checkresult(params):
    params = params.rstrip()
    if re.search('^200', params):
        result = re.search('result=(\d+)', params)
        if not result:
            sys.stderr.write(f"FAIL ('{params}')\n")
            sys.stderr.flush()
            return -1
        else:
            result = result.group(1)
            sys.stderr.write(f"PASS ({result})\n")
            sys.stderr.flush()
            return int(result)
    else:
        error_code = re.search('result=(-?\d+)', params)
        if error_code:
            error_code = int(error_code.group(1))
            sys.stderr.write(f"FAIL (unexpected result '{params}', error code {error_code})\n")
            sys.stderr.flush()
            return error_code
        else:
            sys.stderr.write(f"FAIL (unexpected result '{params}')\n")
            sys.stderr.flush()
            return -2


def saynumber(params):
    sys.stderr.write(f"SAY NUMBER {params} \"\"\n")
    sys.stderr.flush()
    sys.stdout.write(f"SAY NUMBER {params} \"\"\n")
    sys.stdout.flush()
    result = sys.stdin.readline().strip()
    return checkresult(result)


def sayit(params):
    sys.stderr.write(f"STREAM FILE {str(params)} \"\"\n")
    sys.stderr.flush()
    sys.stdout.write(f"STREAM FILE {str(params)} \"\"\n")
    sys.stdout.flush()
    result = sys.stdin.readline().strip()
    checkresult(result)


def getnumber(prompt, timelimit, digcount):
    sys.stderr.write(f"GET DATA {prompt} {timelimit} {digcount}\n")
    sys.stderr.flush()
    sys.stdout.write(f"GET DATA {prompt} {timelimit} {digcount}\n")
    sys.stdout.flush()
    result = sys.stdin.readline().strip()
    return checkresult(result)


def say_digits(number):
    """
    Says each digit of a number separately.
    """
    for digit in str(number):
        saynumber(digit)


def text_to_speech(text, output_file="output"):
    """
    Converts a given text to speech and saves it as a WAV file.
    """
    try:
        # Create a gTTS object
        output_file = f"/usr/share/asterisk/sounds/test/{output_file}"
        temp_mp3 = f"{output_file}.mp3"
        temp_wav = f"{output_file}.wav"
        tts = gTTS(text=text, lang="de")

        # Save the audio as an MP3 file
        tts.save(temp_mp3)

        # Convert MP3 to WAV (requires ffmpeg or similar tool installed)
        os.system(f"ffmpeg -i {temp_mp3} -ar 8000 -ac 1 -y {temp_wav}")

        # Cleanup MP3 file
        os.remove(temp_mp3)

        sys.stderr.write(f"Text-to-Speech file saved as {temp_wav}\n")
        sys.stderr.flush()
        return temp_wav
    except Exception as e:
        sys.stderr.write(f"Error during text-to-speech conversion: {e}\n")
        sys.stderr.flush()
        return None


def play_text_as_audio(text, output_file="output"):
    """
    Converts text to speech, saves the audio, and streams it to Asterisk.
    """
    wav_file = text_to_speech(text, output_file)
    if wav_file:
        sayit(wav_file.replace(".wav", ""))  # Asterisk expects file path without extension

Es enthält zwei neue Funktionen, die nachfolgend vorgestellt werden.

Neue Funktionen: Text-zu-Sprache im Asterisk AGI: Dieses Python-Modul wurde um zwei leistungsstarke Funktionen erweitert, die die Text-zu-Sprache-Integration für Asterisk erleichtern: text_to_speech und play_text_as_audio. Beide nutzen die Google Text-to-Speech API (gTTS) und ermöglichen es, Texte in gesprochene Sprache umzuwandeln und direkt in Asterisk-Anrufen wiederzugeben.

1. Funktion: text_to_speech: Die Funktion text_to_speech konvertiert einen eingegebenen Text in eine Audiodatei im WAV-Format, die im Asterisk-Sounds-Verzeichnis gespeichert wird. Hier der Code dieser Funktion:

def text_to_speech(text, output_file="output"):
    """
    Converts a given text to speech and saves it as a WAV file.
    """
    try:
        # Create a gTTS object
        output_file = f"/usr/share/asterisk/sounds/test/{output_file}"
        temp_mp3 = f"{output_file}.mp3"
        temp_wav = f"{output_file}.wav"
        tts = gTTS(text=text, lang="de")

        # Save the audio as an MP3 file
        tts.save(temp_mp3)

        # Convert MP3 to WAV (requires ffmpeg or similar tool installed)
        os.system(f"ffmpeg -i {temp_mp3} -ar 8000 -ac 1 -y {temp_wav}")

        # Cleanup MP3 file
        os.remove(temp_mp3)

        sys.stderr.write(f"Text-to-Speech file saved as {temp_wav}\n")
        sys.stderr.flush()
        return temp_wav
    except Exception as e:
        sys.stderr.write(f"Error during text-to-speech conversion: {e}\n")
        sys.stderr.flush()
        return None

Funktionsweise:

    • Erstellt mit gTTS eine MP3-Datei aus dem eingegebenen Text.
    • Konvertiert die MP3-Datei mithilfe von ffmpeg in eine WAV-Datei, die mit Asterisk kompatibel ist.
    • Speichert die Datei im Verzeichnis /usr/share/asterisk/sounds/test.

Voraussetzungen:

    • ffmpeg muss installiert sein, um die Konvertierung von MP3 zu WAV durchzuführen.
    • Das Verzeichnis /usr/share/asterisk/sounds/test sollte Schreibrechte für das Skript haben.

2. Funktion: play_text_as_audio: Die Funktion play_text_as_audio kombiniert text_to_speech mit der direkten Wiedergabe der erzeugten Audiodatei über Asterisk. Der Code:

def play_text_as_audio(text, output_file="output"):
    """
    Converts text to speech, saves the audio, and streams it to Asterisk.
    """
    wav_file = text_to_speech(text, output_file)
    if wav_file:
        sayit(wav_file.replace(".wav", ""))  # Asterisk expects file path without extension

Funktionsweise:

    1. Ruft text_to_speech auf, um eine WAV-Datei zu erstellen.
    2. Nutzt die bereits im Modul vorhandene Funktion sayit, um die WAV-Datei im Asterisk-System abzuspielen.

Abhängigkeiten installieren: Damit die Funktionen text_to_speech und play_text_as_audio einwandfrei funktionieren, sind folgende Abhängigkeiten notwendig:

1. Python-Abhängigkeiten:  Das Modul gTTS wird benötigt, um Texte in eine Audiodatei im MP3-Format umzuwandeln. Installation:

pip install gTTS

2. Systemabhängigkeiten: ffmpeg wird verwendet, um die generierte MP3-Datei in eine Asterisk-kompatible WAV-Datei zu konvertieren. Installation:

sudo apt update
sudo apt install ffmpeg

Testen, ob ffmpeg funktioniert:

ffmpeg -version

Schreibrechte für das Zielverzeichnis: Die erzeugten Sound-Dateien (WAV-Dateien) müssen in meinem Beispiel im Verzeichnis /usr/share/asterisk/sounds/test gespeichert werden. Das Zielverzeichnis muss noch angelegt werden. Man kann natürlich einen anderen Pfad festlegen. Stelle sicher, dass das Python-Skript Schreibrechte in diesem Verzeichnis hat. Schreibrechte setzen:

sudo mkdir -p /usr/share/asterisk/sounds/test
sudo chmod 775 /usr/share/asterisk/sounds/test
sudo chown <dein_benutzername>:asterisk /usr/share/asterisk/sounds/test

Ich habe jedoch die Zugriffsrechte mit der GUI (Grafischen Benutzeroberfläche) von Ubuntu Mate gewählt und für den Ordner /usr/share/asterisk/sounds/test folgende Einstellungen gewählt:

Rechteordnertestsoundfilesgtty
Zugriffsrechte des Ordners „Test“. In diesem werden die WAV-Dateien abgelegt, die GTTY erzeugt. Mit „root“ klappt es übrigens nicht.

Die Python-Skripte scheinen bezüglich der Rechte nicht so kritisch zu sein. Auf jeden Fall müssen sie ausführbar sein. Mit „root“ oder meinem Benutzernamen hat es bei mir funktioniert. So machst du ein Skript ausführbar:

chmod +x asterisk2_agi.py

Die Python-Skripte für AGI sind bei mir alle im Ordner /usr/share/asterisk/agi-bin/untergebracht.

Testen der neuen Funktion: Dieses Python-Skript sagt mit Hilfe der beiden neuen Funktionen einen Text in deutscher Sprache mit einer weiblichen Stimme auf:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Test Script for asterisk2_agi.py
Ensure the module 'asterisk2_agi.py' is in the same directory and executable.
"""

import sys
from asterisk2_agi import init_env, play_text_as_audio

def main():
    # Initialize the AGI environment
    env = init_env()

    # Print environment details (for debugging)
    sys.stderr.write("Initialized AGI Environment:\n")
    for key, value in env.items():
        sys.stderr.write(f"{key}: {value}\n")
    sys.stderr.flush()

    # Text-to-Speech Test
    text_message = (
        "Gratulation zu der gelungenen Installation der Sprachsynthese mit Hilfe von Google."
        "Dies bietet dir ganz neue Möglichkeiten in der Programmierung mit Asterisk."
    )   
    output_filename = "test_message"

    sys.stderr.write(f"Converting text to speech: \"{text_message}\"\n")
    sys.stderr.flush()

    # Play the text as audio through Asterisk
    play_text_as_audio(text_message, output_file=output_filename)

if __name__ == "__main__":
    main()

Dieses Pythonskript zum Testen habe ich test_atsterisk2_agi.py genannt. Im selben Ordner muss sich auch das Python-Modul asterisk2_agi.py befinden, die am Anfang dieses Artikels vorgestellt wurde. In der extensions.conf von Asterisk rufst du die Testnummer wie folgt auf:

exten => 514,1,Noop(Anruf erfolgt von: "${CALLERID(name)}" <${CALLERID(num)}>) 
 same => n,Noop(gtts mp3 test, Sprache: DEUTSCH)
 same => n,Set(CHANNEL(language)=de) ; Optionale Zeile. Deutsche Voice Prompts verwenden.
 same => n,Answer()    
 same => n,AGI(test_asterisk2_agi.py)
 same => n,Hangup()

Rufe die 514 an und du hörst  den Text in fast natürlicher Sprachqualität.

Download der beiden Python-Skripte in einer Zip-Datei: AGI-Python-Skripte-gTTS.zip

Datensicherheit: Bitte sei dir bewusst, dass mit GTTS alle Texte zu Google gesendet werden, die Google auf seinen Servern dann zu WAV-Dateien umwandelt und dir zuschickt. Google kennt dadurch einen Teil deiner Aktivitäten und könnte daraus Rückschlüsse ziehen. Dafür ist GTTS kostenlos und bedarf keiner Anmeldung. Du brauchst nur eine halbwegs stabile Internetverbindung und wenig Rechenleistung.

Stell dir vor eine Firma benutzt für sein Auswahlmenü GTTS. Immer wenn ein Kunde oder sonst jemand anruft, bekommt Google mit welche Sounddateien erzeugt werden und weiß somit welche Abteilungen die Kunden aus welchen Gründen zu welcher Zeit anrufen. Daraus lassen sich sehr interessante Rückschlüsse ziehen, die einen Wettbewerbsvorteil für Mitbewerber liefern könnten. Geheimdienste besitzen sicherlich noch mehr Möglichkeiten.

Ausblick: Die hier vorgestellte relativ einfache Technik lässt sich mit einer Spracherkennung, einer noch besseren Sprachausgabe und einer KI wie ChatGPT kombinieren. Damit wäre die telefonische Kundenberatung komplett automatisiert. Schon jetzt ist ChatGPT in der Lage ganz natürlich erscheinende Gespräche in Echtzeit zu führen. Der Anrufer kann nicht mehr unterscheiden, ob es sich um einen echten Menschen oder um eine Künstliche Intelligenz  handelt. Die meisten Kunden werden damit meines Erachtens keine Schwierigkeiten haben, da sie sich ohne zwischenmenschliche Interaktion eine schnelle Lösung und eine kompetente  Auskunft wünschen. Vielleicht ist ihnen eine KI sogar lieber, die immer hilfbereit und freundlich ist, ohne sich genervt zu fühlen.

Anwendungsbeispiel:

Wettertelefon150px
Wie in alten Zeiten. Der telefonische Wetterbericht als Anwendungsbeispiel.
Aktuellen Wetterbericht aus einem RSS-Feed mit Asterisk vorsprechen lassen – 11. Januar 2025: Hast Du Dich jemals gefragt, wie man zum Beispiel automatisch aktuelle Wetterberichte am Telefon wiedergeben kann? In diesem Artikel zeige ich Dir als Beispiel für Unterrichtszwecke, wie ein kleines Python-Skript namens ***.py genau das als fiktives Beispiel ermöglicht. Es nutzt die Kraft von RSS-Feeds und Text-to-Speech-Diensten, um zum Beispiel einen Wetterbericht aus einer XML-Datei zu holen, in eine Audio-Datei zu verwandeln und direkt abzuspielen. – weiter

Weiterführend:

Auf einem Raspberry Pi lässt sich ein kompletter Telefonserver betreiben, der seinen Teilnehmern über Landesgrenzen hinweg kostenloses Telefonieren von unterwegs oder daheim ermöglicht.
Asterisk-Telefonserver auf einem Raspberry Pi – Installation, Konfiguration, Programmierung, SIP, IAX2, AGI-Skripte, Sicherheit und Tipps zum praktischen Betrieb – 2.11.2022: Diese Seite richtet sich an jene, welche einen Asterisk-Telefon-Server auf einem Raspberry Pi betreiben möchten und später ein kleines Netzwerk aus Asterisk-Servern planen, um ein eigenständiges Telefonnetz aufzubauen. Los geht es mit der Installation von Raspbian und Asterisk auf einem Raspberry Pi und dann nach Lust und Laune immer tiefer in die Programmierung von Asterisk. Die Themen werden laufend erweitert.

Selbstverständlich muss es nicht unbedingt ein Raspberry Pi sein. Andere Linux-Rechner gehen auch. – weiter