Python-Skript für Vosk Speech to Text
Hi allerseits!
Gleich vorweg. Ich habe keine Ahnung vom Programmieren, versuche aber mit Hilfe vom ollen ChatGPT nen Python-Skript für Vosk zu erstellen. Soweit so gut, für meine Verhältnisse bin ich (und die 'Blechkiste') schon weit gekommen :D
Aber der vorgelesene Text erscheint einfach doppelt und dreifach. Es soll aber ein kompakter Text zum Kopieren erscheinen. Also quasi das, was man vorliest z. B.
Hier mal aus Coelhos Jakobsweg vorgelesen :D
Das ist natürlich nicht so der Hit (Spracherkennung mal bei Seite gelassen).
Vielleicht hat jemand bereits was mit Vosk zu tun gehabt und löste bereits das Problem.
Hier der bisherige code:
Und bitte nicht erschlagen, weil ich statt Python richtig zu lernen mit dem ChatGPT rumpfusche :D
Grüße
jim
Gleich vorweg. Ich habe keine Ahnung vom Programmieren, versuche aber mit Hilfe vom ollen ChatGPT nen Python-Skript für Vosk zu erstellen. Soweit so gut, für meine Verhältnisse bin ich (und die 'Blechkiste') schon weit gekommen :D
Aber der vorgelesene Text erscheint einfach doppelt und dreifach. Es soll aber ein kompakter Text zum Kopieren erscheinen. Also quasi das, was man vorliest z. B.
Hier mal aus Coelhos Jakobsweg vorgelesen :D
Spracherkennung gestartet...
buch
buch erzählt
buch erzählt paul
buch erzählt paul corallo
buch erzählt paul corallo vorn
buch erzählt paul corallo vorn seiner
buch erzählt paul corallo vorn seiner pilgerreise
buch erzählt paul corallo vorn seiner pilgerreise auf
buch erzählt paul corallo vorn seiner pilgerreise auf dem jakobsweg
buch erzählt paul corallo vorn seiner pilgerreise auf dem jakobsweg eine
buch erzählt paul corallo vorn seiner pilgerreise auf dem jakobsweg eine mittelalterliche
buch erzählt paul corallo vorn seiner pilgerreise auf dem jakobsweg einem mittelalterlichen
buch erzählt paul corallo vorn seiner pilgerreise auf dem jakobsweg einem mittelalterlichen wallfahrt
buch erzählt paul corallo vorn seiner pilgerreise auf dem jakobsweg einem mittelalterlichen wallfahrt pfad
buch erzählt paul corallo vorn seiner pilgerreise auf dem jakobsweg einem mittelalterlichen wallfahrt pfad der von
buch erzählt paul corallo vorn seiner pilgerreise auf dem jakobsweg einem mittelalterlichen wallfahrt pfad der von den
buch erzählt paul corallo vorn seiner pilgerreise auf dem jakobsweg einem mittelalterlichen wallfahrt pfad der von den pyrenäen
buch erzählt paul corallo vorn seiner pilgerreise auf dem jakobsweg einem mittelalterlichen wallfahrt pfad der von den pyrenäen durch
buch erzählt paul corallo vorn seiner pilgerreise auf dem jakobsweg einem mittelalterlichen wallfahrt pfad der von den pyrenäen durch nordspanien
buch erzählt paul corallo vorn seiner pilgerreise auf dem jakobsweg einem mittelalterlichen wallfahrt pfad der von den pyrenäen durch nordspanien nach
buch erzählt paul corallo vorn seiner pilgerreise auf dem jakobsweg einem mittelalterlichen wallfahrt pfad der von den pyrenäen durch nordspanien nach santiago
buch erzählt paul corallo vorn seiner pilgerreise auf dem jakobsweg einem mittelalterlichen wallfahrt pfad der von den pyrenäen durch nordspanien nach santiago de
buch erzählt paul corallo von seiner pilgerreise auf dem jakobsweg eine mittelalterlichen wallfahrt pfad der von den pyrenäen durch nordspanien nach santiago de compostela
dafür
führt
a führt
Spracherkennung gestoppt.
Das ist natürlich nicht so der Hit (Spracherkennung mal bei Seite gelassen).
Vielleicht hat jemand bereits was mit Vosk zu tun gehabt und löste bereits das Problem.
Hier der bisherige code:
import queue
import sounddevice as sd
import vosk
import json
import numpy as np
import tkinter as tk
import threading
# Sprach-Modell
model = vosk.Model("/home/user/Test/Vosk/vosk-model-de-0.21/")
# Audio-Queue
q = queue.Queue()
# Variable zur Steuerung des Aufnahmeprozesses
is_recording = False
# Callback für das Mikrofon
def callback(indata, frames, time, status):
if status:
print("Status:", status)
if is_recording:
q.put(indata.copy())
# Funktion, um das Textfeld zu aktualisieren
def update_textbox(text):
textbox.insert(tk.END, text + '\n')
textbox.yview(tk.END) # Scrollt zum letzten Eintrag
# Funktion für das Stoppen der Aufnahme
def stop_recording():
global is_recording
is_recording = False
update_textbox("Spracherkennung gestoppt.")
# Funktion für das Starten der Aufnahme
def start_recording():
global is_recording
is_recording = True
update_textbox("Spracherkennung gestartet...")
try:
# Audio streamen und Spracherkennung durchführen
with sd.InputStream(samplerate=16000, blocksize=8000, dtype='int16', channels=1, callback=callback):
rec = vosk.KaldiRecognizer(model, 16000)
last_full_text = "" # Speichert den letzten vollständigen Text
last_partial_text = "" # Speichert das letzte Teilergebnis (für Vergleich)
while is_recording:
data = q.get()
if isinstance(data, np.ndarray):
data = data.tobytes()
# Wenn vollständiges Ergebnis vorhanden ist
if rec.AcceptWaveform(data):
result = json.loads(rec.Result())
text = result["text"]
# Verhindert doppelte Ausgaben von Text
if text != last_full_text:
update_textbox(text)
last_full_text = text
# Wenn nur ein Teilergebnis vorliegt
else:
partial = json.loads(rec.PartialResult())
partial_text = partial["partial"]
# Nur den Text anzeigen, wenn er sich vom letzten Teilergebnis unterscheidet
if partial_text != last_partial_text:
update_textbox(partial_text)
last_partial_text = partial_text
# Fenster aktualisieren
root.update()
except Exception as e:
print("Fehler:", e)
stop_recording()
# Funktion, die das Kopieren des Texts im Textfeld ermöglicht
def copy_text(event=None):
try:
# Holt den aktuell markierten Text und kopiert ihn in die Zwischenablage
root.clipboard_clear()
root.clipboard_append(textbox.get(tk.SEL_FIRST, tk.SEL_LAST))
root.update() # Für den Fall, dass ein event-handler genutzt wird
except tk.TclError:
pass # Fehler, falls kein Text markiert ist
# GUI für die Anzeige
root = tk.Tk()
root.title("Spracherkennung")
# Fenstergröße anpassen
root.geometry("800x600") # Setzt die Fenstergröße auf 800x600 Pixel
# Textfeld und Scrollbar hinzufügen
textbox_frame = tk.Frame(root)
textbox_frame.pack(fill=tk.BOTH, expand=True)
scrollbar = tk.Scrollbar(textbox_frame)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# Textfeld anpassen, damit der Text kopiert werden kann
textbox = tk.Text(textbox_frame, height=20, width=80, wrap=tk.WORD, font=("Courier", 12), state=tk.NORMAL)
textbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# Verbinde die Scrollbar mit dem Textfeld
textbox.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=textbox.yview)
# Rechtsklick-Menü (Context Menu) für das Textfeld
def show_context_menu(event):
context_menu.post(event.x_root, event.y_root)
# Kontextmenü erstellen
context_menu = tk.Menu(root, tearoff=0)
context_menu.add_command(label="Kopieren", command=copy_text)
# Event für den Rechtsklick (Rechtsklick-Menü anzeigen)
textbox.bind("<Button-3>", show_context_menu)
# Start-Button
start_button = tk.Button(root, text="Start", command=lambda: threading.Thread(target=start_recording, daemon=True).start(), font=("Helvetica", 14))
start_button.pack(pady=10)
# Stop-Button
stop_button = tk.Button(root, text="Stop", command=stop_recording, font=("Helvetica", 14))
stop_button.pack(pady=10)
# Beenden-Button
exit_button = tk.Button(root, text="Beenden", command=root.quit, font=("Helvetica", 14))
exit_button.pack(pady=10)
root.mainloop()
Und bitte nicht erschlagen, weil ich statt Python richtig zu lernen mit dem ChatGPT rumpfusche :D
Grüße
jim
Bitte markiere auch die Kommentare, die zur Lösung des Beitrags beigetragen haben
Content-ID: 673925
Url: https://administrator.de/forum/vosk-spracherkennung-python-tkinter-673925.html
Ausgedruckt am: 19.07.2025 um 05:07 Uhr
4 Kommentare
Neuester Kommentar
Hallo,
Dann nicht so oft räuspern damit vosk dich auch versteht.
Gruss,
Peter
Zitat von @jimmmy:
Aber der vorgelesene Text erscheint einfach doppelt und dreifach. Es soll aber ein kompakter Text zum Kopieren erscheinen. Also quasi das, was man vorliest z. B.
Also insgesamt 6 mal (1+2+3)Aber der vorgelesene Text erscheint einfach doppelt und dreifach. Es soll aber ein kompakter Text zum Kopieren erscheinen. Also quasi das, was man vorliest z. B.
Dann nicht so oft räuspern damit vosk dich auch versteht.
Gruss,
Peter
Ich sehe das Problem! Vosk gibt sowohl Teilergebnisse (PartialResult) als auch finale Ergebnisse (Result) aus. Die Teilergebnisse zeigen den fortlaufenden Erkennungsprozess, während das finale Ergebnis den vollständigen erkannten Satz enthält.
Das Problem in deinem Code ist, dass du beide Arten von Ergebnissen anzeigst, was zu den Wiederholungen führt. Hier ist die korrigierte Version:
Der Text sollte jetzt so aussehen:
Hier ist eine erweiterte Version deines Codes mit:
Dein Anliegen, die Wiederholungen loszuwerden, sollte mit dem oberen Code bereits gelöst sein. Mit den folgenden kleinen Änderungen wird das Programm noch stabiler.
Sag mir bitte Bescheid, ob es funktioniert hat
P.S: Keine Sorge wegen ChatGPT - jeder fängt mal an, und wenn es funktioniert, ist das doch super!
@firefly
Das Problem in deinem Code ist, dass du beide Arten von Ergebnissen anzeigst, was zu den Wiederholungen führt. Hier ist die korrigierte Version:
import queue
import sounddevice as sd
import vosk
import json
import numpy as np
import tkinter as tk
import threading
# Sprach-Modell
model = vosk.Model("/home/user/Test/Vosk/vosk-model-de-0.21/")
# Audio-Queue
q = queue.Queue()
# Variable zur Steuerung des Aufnahmeprozesses
is_recording = False
# Callback für das Mikrofon
def callback(indata, frames, time, status):
if status:
print("Status:", status)
if is_recording:
q.put(indata.copy())
# Funktion, um das Textfeld zu aktualisieren
def update_textbox(text):
textbox.config(state=tk.NORMAL)
textbox.insert(tk.END, text + ' ') # Fügt Text mit Leerzeichen hinzu
textbox.yview(tk.END) # Scrollt zum letzten Eintrag
textbox.config(state=tk.NORMAL)
# Funktion für das Stoppen der Aufnahme
def stop_recording():
global is_recording
is_recording = False
textbox.config(state=tk.NORMAL)
textbox.insert(tk.END, "\n\nSpracherkennung gestoppt.")
textbox.config(state=tk.NORMAL)
# Funktion für das Starten der Aufnahme
def start_recording():
global is_recording
is_recording = True
textbox.config(state=tk.NORMAL)
textbox.insert(tk.END, "Spracherkennung gestartet...\n\n")
textbox.config(state=tk.NORMAL)
try:
# Audio streamen und Spracherkennung durchführen
with sd.InputStream(samplerate=16000, blocksize=8000, dtype='int16', channels=1, callback=callback):
rec = vosk.KaldiRecognizer(model, 16000)
while is_recording:
data = q.get()
if isinstance(data, np.ndarray):
data = data.tobytes()
# Nur finale Ergebnisse verarbeiten
if rec.AcceptWaveform(data):
result = json.loads(rec.Result())
text = result["text"]
# Nur nicht-leere Texte hinzufügen
if text.strip():
update_textbox(text)
# Teilergebnisse ignorieren wir jetzt komplett
# Das verhindert die Wiederholungen
# Fenster aktualisieren
root.update()
except Exception as e:
print("Fehler:", e)
stop_recording()
# Funktion für das Löschen des Textfelds
def clear_text():
textbox.config(state=tk.NORMAL)
textbox.delete(1.0, tk.END)
textbox.config(state=tk.NORMAL)
# Funktion, die das Kopieren des Texts im Textfeld ermöglicht
def copy_text(event=None):
try:
# Holt den aktuell markierten Text und kopiert ihn in die Zwischenablage
root.clipboard_clear()
root.clipboard_append(textbox.get(tk.SEL_FIRST, tk.SEL_LAST))
root.update()
except tk.TclError:
# Falls kein Text markiert ist, kopiere den gesamten Text
root.clipboard_clear()
root.clipboard_append(textbox.get(1.0, tk.END))
root.update()
# GUI für die Anzeige
root = tk.Tk()
root.title("Spracherkennung")
# Fenstergröße anpassen
root.geometry("800x600")
# Textfeld und Scrollbar hinzufügen
textbox_frame = tk.Frame(root)
textbox_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
scrollbar = tk.Scrollbar(textbox_frame)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# Textfeld anpassen
textbox = tk.Text(textbox_frame, height=20, width=80, wrap=tk.WORD,
font=("Courier", 12), state=tk.NORMAL, bg="white", fg="black")
textbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# Verbinde die Scrollbar mit dem Textfeld
textbox.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=textbox.yview)
# Rechtsklick-Menü (Context Menu) für das Textfeld
def show_context_menu(event):
context_menu.post(event.x_root, event.y_root)
# Kontextmenü erstellen
context_menu = tk.Menu(root, tearoff=0)
context_menu.add_command(label="Kopieren", command=copy_text)
context_menu.add_command(label="Alles auswählen", command=lambda: textbox.tag_add(tk.SEL, "1.0", tk.END))
context_menu.add_separator()
context_menu.add_command(label="Löschen", command=clear_text)
# Event für den Rechtsklick
textbox.bind("<Button-3>", show_context_menu)
# Button-Frame
button_frame = tk.Frame(root)
button_frame.pack(pady=10)
# Start-Button
start_button = tk.Button(button_frame, text="Start",
command=lambda: threading.Thread(target=start_recording, daemon=True).start(),
font=("Helvetica", 14), bg="green", fg="white", width=10)
start_button.pack(side=tk.LEFT, padx=5)
# Stop-Button
stop_button = tk.Button(button_frame, text="Stop", command=stop_recording,
font=("Helvetica", 14), bg="red", fg="white", width=10)
stop_button.pack(side=tk.LEFT, padx=5)
# Löschen-Button
clear_button = tk.Button(button_frame, text="Löschen", command=clear_text,
font=("Helvetica", 14), bg="blue", fg="white", width=10)
clear_button.pack(side=tk.LEFT, padx=5)
# Beenden-Button
exit_button = tk.Button(button_frame, text="Beenden", command=root.quit,
font=("Helvetica", 14), bg="gray", fg="white", width=10)
exit_button.pack(side=tk.LEFT, padx=5)
# Tastenkombinationen
root.bind('<Control-c>', copy_text)
root.bind('<Control-a>', lambda e: textbox.tag_add(tk.SEL, "1.0", tk.END))
root.mainloop()
Der Text sollte jetzt so aussehen:
Spracherkennung gestartet...
das buch erzählt paul corallo von seiner pilgerreise auf dem jakobsweg einem mittelalterlichen wallfahrt pfad der von den pyrenäen durch nordspanien nach santiago de compostela führt
Spracherkennung gestoppt.
Hier ist eine erweiterte Version deines Codes mit:
- Queue-Leerung beim Stoppen: Beim Stoppen wird die Warteschlange geleert, damit keine alten Audiodaten verarbeitet werden.
- Timeout bei „queue.get()”: Durch einen Timeout von 0,1 Sekunden reagiert das Programm schneller auf den Stop-Button.
- Mehrfachstart-Schutz: Der Start-Button startet nur einen neuen Thread, wenn nicht bereits eine Aufnahme läuft.
Dein Anliegen, die Wiederholungen loszuwerden, sollte mit dem oberen Code bereits gelöst sein. Mit den folgenden kleinen Änderungen wird das Programm noch stabiler.
import queue
import sounddevice as sd
import vosk
import json
import numpy as np
import tkinter as tk
import threading
# Sprach-Modell
model = vosk.Model("/home/user/Test/Vosk/vosk-model-de-0.21/")
# Audio-Queue
q = queue.Queue()
# Variable zur Steuerung des Aufnahmeprozesses
is_recording = False
# Callback für das Mikrofon
def callback(indata, frames, time, status):
if status:
print("Status:", status)
if is_recording:
q.put(indata.copy())
# Funktion, um das Textfeld zu aktualisieren
def update_textbox(text):
textbox.config(state=tk.NORMAL)
textbox.insert(tk.END, text + ' ') # Fügt Text mit Leerzeichen hinzu
textbox.yview(tk.END) # Scrollt zum letzten Eintrag
textbox.config(state=tk.NORMAL)
# Funktion für das Stoppen der Aufnahme
def stop_recording():
global is_recording
is_recording = False
# Queue leeren
while not q.empty():
try:
q.get_nowait()
except queue.Empty:
break
textbox.config(state=tk.NORMAL)
textbox.insert(tk.END, "\n\nSpracherkennung gestoppt.")
textbox.config(state=tk.NORMAL)
# Funktion für das Starten der Aufnahme
def start_recording():
global is_recording
is_recording = True
textbox.config(state=tk.NORMAL)
textbox.insert(tk.END, "Spracherkennung gestartet...\n\n")
textbox.config(state=tk.NORMAL)
try:
# Audio streamen und Spracherkennung durchführen
with sd.InputStream(samplerate=16000, blocksize=8000, dtype='int16', channels=1, callback=callback):
rec = vosk.KaldiRecognizer(model, 16000)
while is_recording:
try:
data = q.get(timeout=0.1) # Timeout hinzugefügt
except queue.Empty:
continue
if isinstance(data, np.ndarray):
data = data.tobytes()
# Nur finale Ergebnisse verarbeiten
if rec.AcceptWaveform(data):
result = json.loads(rec.Result())
text = result["text"]
# Nur nicht-leere Texte hinzufügen
if text.strip():
update_textbox(text)
# Teilergebnisse ignorieren wir jetzt komplett
# Das verhindert die Wiederholungen
# Fenster aktualisieren
root.update()
except Exception as e:
print("Fehler:", e)
stop_recording()
# Funktion für das Löschen des Textfelds
def clear_text():
textbox.config(state=tk.NORMAL)
textbox.delete(1.0, tk.END)
textbox.config(state=tk.NORMAL)
# Funktion, die das Kopieren des Texts im Textfeld ermöglicht
def copy_text(event=None):
try:
# Holt den aktuell markierten Text und kopiert ihn in die Zwischenablage
root.clipboard_clear()
root.clipboard_append(textbox.get(tk.SEL_FIRST, tk.SEL_LAST))
root.update()
except tk.TclError:
# Falls kein Text markiert ist, kopiere den gesamten Text
root.clipboard_clear()
root.clipboard_append(textbox.get(1.0, tk.END))
root.update()
# GUI für die Anzeige
root = tk.Tk()
root.title("Spracherkennung")
# Fenstergröße anpassen
root.geometry("800x600")
# Textfeld und Scrollbar hinzufügen
textbox_frame = tk.Frame(root)
textbox_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
scrollbar = tk.Scrollbar(textbox_frame)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
# Textfeld anpassen
textbox = tk.Text(textbox_frame, height=20, width=80, wrap=tk.WORD,
font=("Courier", 12), state=tk.NORMAL, bg="white", fg="black")
textbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# Verbinde die Scrollbar mit dem Textfeld
textbox.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=textbox.yview)
# Rechtsklick-Menü (Context Menu) für das Textfeld
def show_context_menu(event):
context_menu.post(event.x_root, event.y_root)
# Kontextmenü erstellen
context_menu = tk.Menu(root, tearoff=0)
context_menu.add_command(label="Kopieren", command=copy_text)
context_menu.add_command(label="Alles auswählen", command=lambda: textbox.tag_add(tk.SEL, "1.0", tk.END))
context_menu.add_separator()
context_menu.add_command(label="Löschen", command=clear_text)
# Event für den Rechtsklick
textbox.bind("<Button-3>", show_context_menu)
# Button-Frame
button_frame = tk.Frame(root)
button_frame.pack(pady=10)
# Start-Button
start_button = tk.Button(button_frame, text="Start",
command=lambda: threading.Thread(target=start_recording, daemon=True).start() if not is_recording else None,
font=("Helvetica", 14), bg="green", fg="white", width=10)
start_button.pack(side=tk.LEFT, padx=5)
# Stop-Button
stop_button = tk.Button(button_frame, text="Stop", command=stop_recording,
font=("Helvetica", 14), bg="red", fg="white", width=10)
stop_button.pack(side=tk.LEFT, padx=5)
# Löschen-Button
clear_button = tk.Button(button_frame, text="Löschen", command=clear_text,
font=("Helvetica", 14), bg="blue", fg="white", width=10)
clear_button.pack(side=tk.LEFT, padx=5)
# Beenden-Button
exit_button = tk.Button(button_frame, text="Beenden", command=root.quit,
font=("Helvetica", 14), bg="gray", fg="white", width=10)
exit_button.pack(side=tk.LEFT, padx=5)
# Tastenkombinationen
root.bind('<Control-c>', copy_text)
root.bind('<Control-a>', lambda e: textbox.tag_add(tk.SEL, "1.0", tk.END))
root.mainloop()
Sag mir bitte Bescheid, ob es funktioniert hat
P.S: Keine Sorge wegen ChatGPT - jeder fängt mal an, und wenn es funktioniert, ist das doch super!
@firefly