Raspberry Pi 5: Sprachübersetzer mit Schutz der Privatsphäre

Heute widmen wir uns einem Projekt, das nicht nur wahnsinnig praktisch ist, sondern auch komplett ohne Internet auskommt. Wir bauen einen eigenen, lokalen KI-Sprachübersetzer, der die gesprochenen Wörter in die gewünschte Sprache übersetzt – und umgekehrt. Derzeit über 30 Sprachen möglich: rhasspy/piper-voices at main

Das Beste daran ist, das die Privatsphäre erhalten bleibt, da keine persönlichen Inforationen in das unbekannte Internet, Cloud oder KI verschickt. Alles bleibt schön lokal auf deinem Raspberry

Wie funktioniert das Ganze?

Hinter der gesamten Lösung stecken mehrere Programm die hintereinander ablaufen. Beispiel von Deutsch ins Englisch:

  • Das Ohr (Audio-Aufnahme): Zuerst wird die deutsche Sprache mit „arecord“ über das Mikrofon aufgenommen und als kleine Tondatei zwischengespeichert.
  • Sprache zu Text: Das Programm Whisper tritt danach in Aktion. Es hört sich die Tondatei (Aufnahme) an und tippt sie sozusagen als deutschen Text ab (STT – Speech to Text).
  • Das Gehirn (Übersetzen): Nun kommt die eigentliche Künstliche Intelligenz (KI) ins Spiel. Die Software Ollama nutzt ein Modell namens Gemma (genauer: gemma3:1b), um den abgetippten deutschen Text ins Englische zu übersetzen.
  • Der Mund (Text zu Sprache): Das englische Übersetzungsergebnis liegt nun als Text vor. Das Programm Piper TTS (Text-to-Speech / Text-zu-Sprache) nimmt diesen Text und erzeugt daraus eine neue Audiodatei mit einer verständlichen englischen Stimme.
  • Die Ausgabe: Ganz am Ende wird diese neue Tondatei einfach über euren Lautsprecher (oder Monitor) abgespielt.

Was wir für dieses Projekt brauchen

  • Raspberry Pi 5 (ab 4 GB RAM, aktive Kühlung dringend empfohlen!)
  • USB-Mikrofon (in meinem Fall ein Q1 USB-Mikrofon und auch alte Web-Cam mit Mikro funktionierte)
  • Sprachausgabe: HDMI Monitor mit Lautsprecher
  • Eine MicroSD-Karte (mindestens 64 GB) mit aktuellem Raspberry Pi OS (64 Bit)
  • Internetverbindung (wird nur für die Installation benötigt!)

Optional:

Teil 1: Das Basissystem und die Audio-Einrichtung

Mit Raspberry Imager ein aktuelles Linux Betreibsystem auf SD Karte oder NVME SSD bringen. Siehe Beitrag:

Bevor wir loslegen, bringen wir den Raspberry Pi per sicherer Netzwerkverbindung (Secure Shell, kurz SSH) auf den neuesten Stand und installieren alle wichtigen Werkzeuge in einem Rutsch:

sudo apt update
sudo apt full-upgrade -y
sudo apt install -y git cmake build-essential curl jq python3 python3-venv python3-pip alsa-utils pipewire-alsa ffmpeg libopenblas-dev rfkill

Nach dem Update empfehle ich einen Neustart mit sudo reboot.

Danach legen wir die Ordnerstruktur an und suchen die genauen internen Namen unserer Geräte:

mkdir -p ~/translator/{app,log,test,voices/de,voices/en,work}

1.1 Audio-Geräte ermitteln:
Mikrofon und Lautsprecher einstellen.

Damit der Übersetzer etwas hört und spricht, müssen wir die korrekten Adressen deiner USB-Hardware herausfinden.

  1. Aufnahmegeräte (Mikrofone) auflisten:arecord -l
  2. Wiedergabegeräte (Lautsprecher) auflisten:aplay -l

Notiere dir aus der Liste jeweils die Card– und Device-Nummer (z.B. card 2, device 0). Daraus setzt sich später im Skript die Adresse zusammen (in diesem Beispiel: plughw:2,0).

Audio-Test: Nimm eine kurze Testdatei auf, um das Mikrofon zu prüfen:

arecord -D plughw:2,0 -f S16_LE -r 16000 -c 1 -d 10 ~/translator/test/mic-test.wav

und höre dir die Aufnahme an mit:

aplay ~/translator/test/mic-test.wav

Du solltest nun deine eigene Stimme hören (16 kHz, Mono, PCM 16 Bit).

Teil 2: Installation und Test der KI-Komponenten

Ich beginne mit der Spracherkennung und teste danach jeden weiteren Baustein einzeln, bevor wir alles zusammensetzen.

2.1 Whisper.cpp (Spracherkennung)

Whisper sorgt dafür, dass das gesprochene Wort in Text umgewandelt wird. Wir laden den Code herunter und kompilieren ihn direkt auf dem Pi:

git clone https://github.com/ggml-org/whisper.cpp.git ~/whisper.cpp
cd ~/whisper.cpp
cmake -B build -DGGML_BLAS=1 -DCMAKE_BUILD_TYPE=Release
cmake --build build -j4 --config Release
bash models/download-ggml-model.sh base

Test: Wir lassen Whisper unsere zuvor aufgenommene Test-Datei übersetzen:

./build/bin/whisper-cli -m models/ggml-base.bin -f ~/translator/test/mic-test.wav -l de -nt -otxt -of ~/translator/test/transkript
cat ~/translator/test/transkript.txt

Soll-Ergebnis: Der von dir gesprochene deutsche Satz sollte nun als Text in der Konsole stehen.

2.2 Ollama und Gemma 3 (Übersetzung)

Ollama ist unsere lokale KI-Umgebung. Wir nutzen das Modell gemma3:1b, da es perfekt für unsere 4 GB RAM geeignet ist.

curl -fsSL https://ollama.com/install.sh | sh
sudo systemctl enable --now ollama
ollama pull gemma3:1b

Test: Wir schicken einen Test-Satz direkt an die KI, um die Übersetzung zu prüfen:

curl -s http://127.0.0.1:11434/api/generate -H 'Content-Type: application/json' -d '{"model":"gemma3:1b","prompt":"Übersetze exakt ins Englische. Gib nur die Übersetzung aus: Wo befindet sich der Bahnhof?","stream":false,"options":{"temperature":0}}' | jq -r '.response'

Soll-Ergebnis: Die Ausgabe sollte „Where is the train station?“ lauten.

2.3 Piper TTS (Sprachausgabe)

Piper liest den übersetzten Text am Ende laut vor. Dafür richten wir eine geschützte Python-Umgebung (Virtual Environment) ein:

python3 -m venv ~/translator/venv
source ~/translator/venv/bin/activate
python -m pip install --upgrade pip
python -m pip install piper-tts requests
cd ~/translator/voices/de
python -m piper.download_voices de_DE-thorsten-medium
cd ~/translator/voices/en
python -m piper.download_voices en_GB-alan-medium
find ~/translator/voices -type f   \( -name "*.onnx" -o -name "*.onnx.json" \) -print

Test: Lass dir einen Text von der deutschen Stimme vorlesen:

echo 'Guten Tag. Der Übersetzer ist bereit.' | piper --model ~/translator/voices/de/de_DE-thorsten-medium.onnx --output_file ~/translator/test/tts-de.wav

aplay ~/translator/test/tts-de.wav

Soll-Ergebnis: Eine klare deutsche Computerstimme liest den Satz vor.

Optional: Weitere Sprachen (siehe Anhang)

Teil 3: Das Hauptprogramm (translator.py)

Erstelle die Datei ~/translator/app/translator.py und füge den folgenden Code ein.
Wichtig:
Passe oben im Code bei MIC_DEVICE und PLAYBACK_DEVICE die Namen an, die du in Teil 1 herausgefunden hast!
Und… achte darauf das die Zeilen so wie hier vorgehen auch eingerückt bleiben.

Hier klicken zum öffnen
#!/usr/bin/env python3
import argparse, logging, subprocess, sys
from pathlib import Path
import requests

BASE = Path.home() / "translator"
WORK = BASE / "work"
LOG = BASE / "log/translator.log"
WHISPER = Path.home() / "whisper.cpp/build/bin/whisper-cli"
WHISPER_MODEL = Path.home() / "whisper.cpp/models/ggml-base.bin"
OLLAMA_URL = "http://127.0.0.1:11434/api/generate"
GEMMA_MODEL = "gemma3:1b"

# HIER DEINE AUDIO-GERÄTE EINTRAGEN:
MIC_DEVICE = "plughw:2,0"   
PLAYBACK_DEVICE = "default" 

VOICES = {
    "de": BASE / "voices/de/de_DE-thorsten-medium.onnx",
    "en": BASE / "voices/en/en_GB-alan-medium.onnx",
}

WORK.mkdir(parents=True, exist_ok=True)
LOG.parent.mkdir(parents=True, exist_ok=True)
logging.basicConfig(filename=LOG, level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")

def run(cmd, input_text=None):
    cmd=[str(x) for x in cmd]
    logging.info("CMD: %s", " ".join(cmd))
    r=subprocess.run(cmd,input=input_text,text=True,capture_output=True)
    if r.stdout: logging.info("STDOUT: %s",r.stdout.strip())
    if r.stderr: logging.info("STDERR: %s",r.stderr.strip())
    if r.returncode:
        raise RuntimeError(f"Befehl fehlgeschlagen ({r.returncode}): {' '.join(cmd)}\n{r.stderr.strip()}")
    return r

def record(seconds):
    wav=WORK/"input.wav"; wav.unlink(missing_ok=True)
    run(["arecord","-D",MIC_DEVICE,"-f","S16_LE","-r","16000","-c","1","-d",seconds,wav])
    if not wav.is_file() or wav.stat().st_size<1000: raise RuntimeError("Aufnahme fehlt oder ist zu klein")
    return wav

def transcribe(wav,lang):
    stem=WORK/"transcript"; txt=stem.with_suffix(".txt"); txt.unlink(missing_ok=True)
    run([WHISPER,"-m",WHISPER_MODEL,"-f",wav,"-l",lang,"-nt","-otxt","-of",stem])
    if not txt.is_file(): raise RuntimeError("Whisper erzeugte keine Textdatei")
    text=txt.read_text(encoding="utf-8",errors="replace").strip()
    if not text: raise RuntimeError("Whisper lieferte keinen Text")
    return text

def translate(text,target):
    language="Deutsch" if target=="de" else "Englisch"
    prompt=f"Übersetze exakt ins {language}. Gib ausschließlich die Übersetzung aus, ohne Erklärung, Anführungszeichen oder Präfix. Text: {text}"
    r=requests.post(OLLAMA_URL,json={"model":GEMMA_MODEL,"prompt":prompt,"stream":False,"options":{"temperature":0}},timeout=180)
    r.raise_for_status(); out=r.json()["response"].strip()
    if not out: raise RuntimeError("Gemma lieferte keine Übersetzung")
    return out

def speak(text,target):
    model=VOICES[target]; config=Path(str(model)+".json")
    if not model.is_file(): raise FileNotFoundError(f"TTS-Modell fehlt: {model}")
    if not config.is_file(): raise FileNotFoundError(f"TTS-Konfiguration fehlt: {config}")
    out=WORK/"translation.wav"; out.unlink(missing_ok=True)
    run([BASE/"venv/bin/piper","--model",model,"--output_file",out],text)
    run(["aplay","-D",PLAYBACK_DEVICE,out]); return out

def main():
    ap=argparse.ArgumentParser(); 
    ap.add_argument("--from",dest="source",choices=["de","en"],required=True)
    ap.add_argument("--to",dest="target",choices=["de","en"],required=True); 
    ap.add_argument("--seconds",type=int,default=7)
    a=ap.parse_args()
    if a.source==a.target: sys.exit("Quell- und Zielsprache müssen verschieden sein")
    try:
        print("Aufnahme startet ...")
        text=transcribe(record(a.seconds),a.source)
        print("Erkannt:",text)
        out=translate(text,a.target)
        print("Übersetzt:",out)
        speak(out,a.target)
    except Exception as e:
        logging.exception("Fehler")
        print(f"FEHLER: {e}",file=sys.stderr)
        sys.exit(1)

if __name__=="__main__": main()

Mache das Skript anschließend ausführbar mit:

chmod +x ~/translator/app/translator.py

Erster finaler Test sollte vorab WIFI oder LAN Kabel getrennt werden um zu testen das es lokal sich ausführen lässt. Dann kann nun getestet werden:

Sofern nicht in der virtuellen umgebung hier mit aktivieren:
source ~/translator/venv/bin/activate

und dann einen der beiden Varianten Englisch zu Deutsch oder umgekehrt, mit 7 Sekunden Ausnahme Zeit

python ~/translator/app/translator.py –from de –to en –seconds 7
python ~/translator/app/translator.py –from en –to de –seconds 7

Teil 4: Die grafische Touch-Oberfläche (GUI)

Wir wollen keine Befehle mehr tippen, sondern dicke Buttons auf dem Display drücken. Dazu wird eine neue Datei erstellt:

nano ~/translator/gui.py

Kopiere den folgenden Code hinein. Wichtig: Dieser Code nutzt eine Technik namens „Threading“ (parallele Ausführung). Dadurch friert das Display nicht ein, während im Hintergrund die rechenintensive Übersetzung läuft.

import tkinter as tk
import subprocess
import threading

def starte_uebersetzung(quellsprache, zielsprache):
    # Buttons sperren, damit man nicht doppelt klickt
    button_de_en.config(state=tk.DISABLED)
    button_en_de.config(state=tk.DISABLED)
    
    text_feld.delete(1.0, tk.END)
    text_feld.insert(tk.END, f"Aufnahme läuft... Bitte 7 Sekunden sprechen!\n\n")
    
    def aufgabe():
        # Passe hier /home/admin/ an deinen tatsächlichen Benutzerpfad an!
        befehl = [
            "/home/admin/translator/venv/bin/python",
            "/home/admin/translator/app/translator.py",
            "--from", quellsprache,
            "--to", zielsprache,
            "--seconds", "7"
        ]
        try:
            prozess = subprocess.Popen(
                befehl, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
            )
            for zeile in prozess.stdout:
                root.after(0, text_feld.insert, tk.END, zeile)
                root.after(0, text_feld.see, tk.END)
            prozess.wait()
            root.after(0, text_feld.insert, tk.END, "\nFertig!")
        except Exception as fehler:
            root.after(0, text_feld.insert, tk.END, f"\nFehler: {fehler}")
        finally:
            root.after(0, button_de_en.config, {'state': tk.NORMAL})
            root.after(0, button_en_de.config, {'state': tk.NORMAL})

    threading.Thread(target=aufgabe, daemon=True).start()

root = tk.Tk()
root.title("Prilchen LABS - Übersetzer")
root.geometry("480x320")
# Für den Vollbildmodus auf dem LCD die # in der nächsten Zeile entfernen:
# root.attributes("-fullscreen", True)

frame_buttons = tk.Frame(root)
frame_buttons.pack(pady=10)

button_de_en = tk.Button(frame_buttons, text="DE -> EN", font=("Arial", 20, "bold"), width=10, height=3, command=lambda: starte_uebersetzung("de", "en"))
button_de_en.pack(side=tk.LEFT, padx=10)

button_en_de = tk.Button(frame_buttons, text="EN -> DE", font=("Arial", 20, "bold"), width=10, height=3, command=lambda: starte_uebersetzung("en", "de"))
button_en_de.pack(side=tk.RIGHT, padx=10)

text_feld = tk.Text(root, font=("Arial", 14), wrap=tk.WORD, height=8)
text_feld.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)

root.mainloop()

Damit der Aufruf leichter wird, legen wir eine kleine Startdatei an:

echo '#!/bin/bash' > ~/translator/gui.sh
echo 'python3 ~/translator/gui.py' >> ~/translator/gui.sh
chmod +x ~/translator/gui.sh

Teil 5: Das LCD Touch-Display und VNC einrichten

Jetzt ist die Software komplett getestet. Damit wir das Projekt als Standalone-Gerät ohne großen Computermonitor bedienen können, stecken wir das 3.5 Zoll LCD auf den Raspberry Pi. Die Treiber installieren wir mit folgenden Befehlen:

sudo rm -rf LCD-show
git clone https://github.com/goodtft/LCD-show.git
chmod -R 755 LCD-show
cd LCD-show/
sudo ./LCD35-show

Der Pi startet danach automatisch neu.

Falls du das fertige Touch-Display zur Fernwartung auf deinem Hauptrechner spiegeln möchtest, nutzen wir Virtual Network Computing (VNC). Ein normales VNC funktioniert mit dem aufgesteckten Display manchmal nicht direkt. Der Trick ist ein virtueller Bildschirm!

Verbinde dich per SSH (ssh admin@deine-ip-adresse) und starte den Server so:

vncserver-virtual -randr=1920x1080

In deinem VNC-Programm (z. B. RealVNC) gibst du nun die IP-Adresse mit dem angezeigten Port ein (z. B. 10.10.20.20:1). Schon hast du den Desktop vor dir und das Touch-Display funktioniert ebenfalls!

Teil 6: Autostart einrichten (Optional)

Damit sich unsere grafische Oberfläche (GUI) beim Einschalten des Pi von ganz alleine öffnet, legen wir einen Autostart an.

Erstelle zuerst den nötigen Ordner (falls er noch nicht existiert):

mkdir -p ~/.config/autostart

Lege nun die Autostart-Datei an:

nano ~/.config/autostart/translator_gui.desktop

Fülle die Datei mit diesem Inhalt (Pfad bei Exec= ggf. an deinen Benutzernamen anpassen!):

[Desktop Entry]
Type=Application
Name=Translator GUI Autostart
Exec=/home/admin/translator/gui.sh
Terminal=false

Wenn du nun den Raspberry Pi neu startest (sudo reboot), lädt die normale Desktop-Oberfläche und startet sofort unsere beiden großen Übersetzer-Buttons in den Vordergrund!

Ein wichtiger Hinweis zum Datenschutz

Da unser System temporäre WAV-Dateien (Audios) und transkribierte Texte auf der SD-Karte ablegt, denkt an ein Löschkonzept, falls ihr vertrauliche Gespräche übersetzt. Für Daueraufnahmen in öffentlichen Bereichen benötigt ihr zudem eine Einwilligung!

Fazit

Ein 4-GB-Raspberry, ein kleines Touch-Display und quelloffene KI-Modelle genügen, um einen völlig autarken Universalübersetzer zu bauen. Keine Cloud, keine Abos, keine versteckten Datenübertragungen. Die Privatsphäre ist somit erhalten geblieben. Manche umständlichen Sätze hat jedoch die gemma3:4b auf dem RPi mit 8GB schöner Übersetzt.

Wenn euch das Tutorial geholfen hat oder ihr eigene Anpassungen für das Gehäuse bastelt (vielleicht etwas Schickes in OpenSCAD?), lasst es mich in den Kommentaren wissen oder schaut auf meinem YouTube-Kanal vorbei!

Youtube Video:

Optional: Weitere Sprachen

So ergänzt du weitere Sprachen – beispielsweise Spanisch und Französisch

Unser Übersetzer kann weit mehr als nur Deutsch und Englisch. Das Programm Piper TTS (Text-to-Speech / Text-zu-Sprache) unterstützt wie oben erwähnt viele weitere Sprachen. Um zum Beispiel Spanisch und Französisch hinzuzufügen, müssen wir nur die passenden Stimmen herunterladen und unserem Hauptprogramm mitteilen, dass es diese neuen Sprachen gibt.

1. Ordner für die neuen Sprachen anlegen 
Zuerst erstellen wir zwei neue Ordner für die spanischen und französischen Stimmen-Dateien. Gib dafür folgenden Befehl in deine Konsole ein:

mkdir -p ~/translator/voices/es ~/translator/voices/fr

2. Die Sprachmodelle herunterladen
Jetzt laden wir die passenden Stimmen herunter. Dafür müssen wir zuerst wieder unsere geschützte Python-Umgebung (Virtual Environment, kurz venv) aktivieren, falls du sie zwischenzeitlich geschlossen hast:

source ~/translator/venv/bin/activate

Wechsle nun in den neuen spanischen Ordner und lade die Stimme herunter:

cd ~/translator/voices/es

python -m piper.download_voices es_ES-sharvard-medium

Danach machst du genau das Gleiche für Französisch:

cd ~/translator/voices/fr

python -m piper.download_voices fr_FR-siwis-medium

3. Das Hauptprogramm anpassen
Damit das System die neuen Sprachen auch nutzt, musst du die Datei translator.py aktualisieren. Ersetze deinen alten Code einfach durch den unten stehenden, überarbeiteten Code.

Darin sind die wichtigen Dinge bereits angepasst:

  • Im Bereich VOICES (Stimmen) sind die genauen Dateipfade zu den neuen spanischen und französischen Stimmen-Dateien hinterlegt.
  • Die Funktion translate (Übersetzen) weiß nun, dass sie die Künstliche Intelligenz (KI) anweisen muss, exakt ins „Spanische“ oder „Französische“ zu übersetzen.
Hier klicken zum öffnen
#!/usr/bin/env python3
import argparse, logging, subprocess, sys
from pathlib import Path
import requests

BASE = Path.home() / "translator"
WORK = BASE / "work"
LOG = BASE / "log/translator.log"
WHISPER = Path.home() / "whisper.cpp/build/bin/whisper-cli"
WHISPER_MODEL = Path.home() / "whisper.cpp/models/ggml-base.bin"
OLLAMA_URL = "http://127.0.0.1:11434/api/generate"
GEMMA_MODEL = "gemma3:1b"

# HIER DEINE AUDIO-GERÄTE EINTRAGEN:
MIC_DEVICE = "plughw:2,0"
PLAYBACK_DEVICE = "default"

# Absoluter Pfad zur Piper-Datei in der virtuellen Umgebung (Virtual Environment / venv)
PIPER = BASE / "venv/bin/piper"

# Stimmen für die verschiedenen Sprachen
VOICES = {
    "de": BASE / "voices/de/de_DE-thorsten-medium.onnx",
    "en": BASE / "voices/en/en_GB-alan-medium.onnx",
    "es": BASE / "voices/es/es_ES-sharvard-medium.onnx",
    "fr": BASE / "voices/fr/fr_FR-siwis-medium.onnx",
}

WORK.mkdir(parents=True, exist_ok=True)
LOG.parent.mkdir(parents=True, exist_ok=True)
logging.basicConfig(filename=LOG, level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")

def run(cmd, input_text=None):
    cmd=[str(x) for x in cmd]
    logging.info("CMD: %s", " ".join(cmd))
    r=subprocess.run(cmd,input=input_text,text=True,capture_output=True)
    if r.stdout: logging.info("STDOUT: %s",r.stdout.strip())
    if r.stderr: logging.info("STDERR: %s",r.stderr.strip())
    if r.returncode:
        raise RuntimeError(f"Befehl fehlgeschlagen ({r.returncode}): {' '.join(cmd)}\n{r.stderr.strip()}")
    return r

def record(seconds):
    wav=WORK/"input.wav"
    wav.unlink(missing_ok=True)
    run(["arecord","-D",MIC_DEVICE,"-f","S16_LE","-r","16000","-c","1","-d",seconds,wav])
    if not wav.is_file() or wav.stat().st_size<1000: 
        raise RuntimeError("Aufnahme fehlt oder ist zu klein")
    return wav

def transcribe(wav,lang):
    stem=WORK/"transcript"
    txt=stem.with_suffix(".txt")
    txt.unlink(missing_ok=True)
    run([WHISPER,"-m",WHISPER_MODEL,"-f",wav,"-l",lang,"-nt","-otxt","-of",stem])
    if not txt.is_file(): 
        raise RuntimeError("Whisper erzeugte keine Textdatei (Text file)")
    text=txt.read_text(encoding="utf-8",errors="replace").strip()
    if not text: 
        raise RuntimeError("Kein Text erkannt")
    return text

def translate(text, target_lang):
    lang_names = {"en": "Englische", "de": "Deutsche", "es": "Spanische", "fr": "Französische"}
    prompt = f"Übersetze exakt ins {lang_names[target_lang]}. Gib nur die Übersetzung aus: {text}"
    
    payload = {
        "model": GEMMA_MODEL,
        "prompt": prompt,
        "stream": False,
        "options": {"temperature": 0}
    }
    r = requests.post(OLLAMA_URL, json=payload)
    r.raise_for_status()
    return r.json().get("response", "").strip()

def speak(text, lang):
    voice = VOICES.get(lang)
    if not voice: 
        raise RuntimeError(f"Keine Stimme für die Sprache {lang} gefunden")
    
    out_wav = WORK / "output.wav"
    out_wav.unlink(missing_ok=True)
    
    piper_cmd = [str(PIPER), "--model", str(voice), "--output_file", str(out_wav)]
    logging.info("CMD: echo %s | %s", text, " ".join(piper_cmd))
    
    p = subprocess.run(piper_cmd, input=text, text=True, capture_output=True)
    if not out_wav.is_file():
        raise RuntimeError(f"Piper TTS (Text-zu-Sprache) Fehler: {p.stderr}")
        
    run(["aplay", "-D", PLAYBACK_DEVICE, out_wav])

def main():
    parser = argparse.ArgumentParser(description="Lokaler KI Sprachübersetzer")
    parser.add_argument("--from", dest="from_lang", choices=["de", "en", "es", "fr"], default="de", help="Quellsprache")
    parser.add_argument("--to", dest="to_lang", choices=["de", "en", "es", "fr"], default="en", help="Zielsprache")
    parser.add_argument("--seconds", type=int, default=5, help="Aufnahmedauer in Sekunden")
    args = parser.parse_args()

    print(f"[{args.from_lang.upper()} -> {args.to_lang.upper()}] Aufnahme startet für {args.seconds} Sekunden...")
    wav = record(args.seconds)
    
    print("Spracherkennung (Speech-to-Text / STT) läuft...")
    text = transcribe(wav, args.from_lang)
    print(f"Erkannt: {text}")
    
    print("Übersetzung durch Künstliche Intelligenz (KI) läuft...")
    translated = translate(text, args.to_lang)
    print(f"Übersetzt: {translated}")
    
    print("Sprachausgabe (Text-to-Speech / TTS) läuft...")
    speak(translated, args.to_lang)
    
    print("Fertig.")

if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        logging.error("Fehler: %s", e)
        print(f"Fehler: {e}", file=sys.stderr)
        sys.exit(1)

So startest du den Übersetzer mit den neuen Sprachen: 
Möchtest du nun von Deutsch nach Spanisch übersetzen, übergibst du beim Starten des Programms einfach die entsprechenden Sprachkürzel:

~/translator/venv/bin/python3 ~/translator/app/translator.py –from de –to es

(Tipp: Durch die direkte Angabe von venv/bin/python3 nutzen wir automatisch die richtige Umgebung, ohne sie vorher per Hand aktivieren zu müssen!)

Erste Ausgabe:

Hier die Anpassung der GUI.py:

Hier klicken zum öffnen
#!/usr/bin/env python3
import tkinter as tk
from tkinter import font
import subprocess
from pathlib import Path
import threading

# --- FARBANPASSUNGEN (Prilchen LABS Design) ---
BG_COLOR = "#ecefef"      
BTN_COLOR = "#1fbc9c"     
BTN_ACTIVE = "#17997f"    
FRAME_BG = "#747474"      
TEXT_COLOR = "#222222"    
BTN_TEXT = "#ffffff"      
FONT_NAME = "Helvetica"

# --- PFADE ZU DEN PROGRAMMEN ---
BASE_DIR = Path.home() / "translator"
TRANSLATOR_SCRIPT = BASE_DIR / "app/translator.py"
PYTHON_EXEC = BASE_DIR / "venv/bin/python3"

# --- SPRACHEN DEFINIEREN ---
LANGUAGES = {
    "Deutsch": "de",
    "Englisch": "en",
    "Spanisch": "es",
    "Französisch": "fr"
}

class TranslatorGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("Prilchen LABS - Sprachübersetzer")
        
        self.root.geometry("480x320")
        self.root.configure(bg=BG_COLOR)
        
        # Schriften verkleinert (size 12 und 14) für mehr Platz
        self.custom_font = font.Font(family=FONT_NAME, size=12, weight="bold")
        self.title_font = font.Font(family=FONT_NAME, size=14, weight="bold")
        
        self.from_lang_name = tk.StringVar(value="Deutsch")
        self.to_lang_name = tk.StringVar(value="Englisch")
        
        self.build_interface()
        
    def build_interface(self):
        # Titel (Kompakter)
        title = tk.Label(self.root, text="Offline Übersetzer", bg=BG_COLOR, fg=TEXT_COLOR, font=self.title_font)
        title.pack(pady=5)
        
        # Rahmen für die Sprachauswahl (Kompakter)
        lang_frame = tk.Frame(self.root, bg=FRAME_BG, padx=5, pady=5)
        lang_frame.pack(pady=2, fill="x", padx=10)
        
        # Linke Seite: Von Sprache
        from_frame = tk.Frame(lang_frame, bg=FRAME_BG)
        from_frame.pack(side="left", expand=True)
        tk.Label(from_frame, text="Von:", bg=FRAME_BG, fg=BTN_TEXT, font=self.custom_font).pack(side="left")
        
        from_menu = tk.OptionMenu(from_frame, self.from_lang_name, *LANGUAGES.keys())
        from_menu.config(bg=BTN_COLOR, fg=BTN_TEXT, font=self.custom_font, activebackground=BTN_ACTIVE, highlightthickness=0)
        from_menu["menu"].config(bg=BG_COLOR, fg=TEXT_COLOR, font=self.custom_font)
        from_menu.pack(side="left", padx=5)
        
        # Rechte Seite: Zu Sprache
        to_frame = tk.Frame(lang_frame, bg=FRAME_BG)
        to_frame.pack(side="right", expand=True)
        tk.Label(to_frame, text="Nach:", bg=FRAME_BG, fg=BTN_TEXT, font=self.custom_font).pack(side="left")
        
        to_menu = tk.OptionMenu(to_frame, self.to_lang_name, *LANGUAGES.keys())
        to_menu.config(bg=BTN_COLOR, fg=BTN_TEXT, font=self.custom_font, activebackground=BTN_ACTIVE, highlightthickness=0)
        to_menu["menu"].config(bg=BG_COLOR, fg=TEXT_COLOR, font=self.custom_font)
        to_menu.pack(side="left", padx=5)

        # Statusanzeige
        self.status_label = tk.Label(self.root, text="Bereit für die Aufnahme.", bg=BG_COLOR, fg=TEXT_COLOR, font=self.custom_font)
        self.status_label.pack(pady=2)

        # Aufnahme-Button (Kompakter, in der Mitte platziert)
        self.record_btn = tk.Button(self.root, text="🎤 Aufnahme", bg=BTN_COLOR, fg=BTN_TEXT, font=self.title_font, 
                                    activebackground=BTN_ACTIVE, command=self.start_translation, height=1, width=15, relief="flat")
        self.record_btn.pack(pady=2)

        # Neues Textfeld für die Terminal-Ausgabe (Texterkennung / Fehler)
        self.output_text = tk.Text(self.root, height=6, bg="#ffffff", fg=TEXT_COLOR, font=("Courier", 10))
        self.output_text.pack(pady=5, padx=10, fill="both", expand=True)
        self.output_text.insert(tk.END, "Hier erscheint die Texterkennung...\n")
        # Textfeld vorübergehend schreibgeschützt machen
        self.output_text.config(state="disabled")

    def log_output(self, text):
        """Hilfsfunktion, um Text in das untere Textfeld zu schreiben"""
        self.output_text.config(state="normal")
        self.output_text.insert(tk.END, text + "\n")
        self.output_text.see(tk.END) # Automatisch nach unten scrollen
        self.output_text.config(state="disabled")

    def start_translation(self):
        self.record_btn.config(state="disabled", text="Arbeitet...", bg=FRAME_BG)
        self.status_label.config(text="Aufnahme läuft...")
        
        # Textfeld für neuen Vorgang leeren
        self.output_text.config(state="normal")
        self.output_text.delete(1.0, tk.END)
        self.output_text.config(state="disabled")
        
        thread = threading.Thread(target=self.run_translator_script)
        thread.start()

    def run_translator_script(self):
        from_code = LANGUAGES.get(self.from_lang_name.get(), "de")
        to_code = LANGUAGES.get(self.to_lang_name.get(), "en")

        cmd = [str(PYTHON_EXEC), str(TRANSLATOR_SCRIPT), "--from", from_code, "--to", to_code, "--seconds", "5"]
        
        try:
            # subprocess mit stdout=subprocess.PIPE starten, um die Text-Ausgabe der translator.py abzufangen
            process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
            
            # Die Textausgaben Zeile für Zeile abfangen und ins GUI-Textfeld schreiben
            for line in process.stdout:
                # after() nutzen, da wir aus einem Hintergrund-Thread die GUI aktualisieren
                self.root.after(0, self.log_output, line.strip())
                
            process.wait() # Warten, bis das Hauptprogramm fertig ist
            
            if process.returncode == 0:
                self.root.after(0, lambda: self.status_label.config(text="Übersetzung abgeschlossen."))
            else:
                self.root.after(0, lambda: self.status_label.config(text="Fehler bei der Ausführung!"))
                
        except Exception as e:
            self.root.after(0, lambda: self.status_label.config(text="Ein Fehler ist aufgetreten."))
            self.root.after(0, self.log_output, f"Fehler: {str(e)}")
            
        finally:
            self.root.after(3000, lambda: self.status_label.config(text="Bereit für die Aufnahme."))
            self.root.after(0, lambda: self.record_btn.config(state="normal", text="Aufnahme", bg=BTN_COLOR))

if __name__ == "__main__":
    root = tk.Tk()
    app = TranslatorGUI(root)
    root.mainloop()

, ,
Datenschutz-Übersicht

Diese Website verwendet Cookies, damit wir dir die bestmögliche Benutzerkomfort bieten können. Cookie-Informationen werden in deinem Browser gespeichert und führen Funktionen aus, wie das Wiedererkennen von dir, wenn du auf unsere Website zurückkehrst und hilft uns zu verstehen, welche Abschnitte der Website für dich am interessantesten und nützlichsten sind.