Ich habe eine leistungsstarke Anwendung namens „Audio Transcriber“ entwickelt, die Audiodaten aufzeichnet, transkribiert und vieles mehr – alles mit einer benutzerfreundlichen GUI basierend auf PyQt5. Sie nutzt das KI-Modell Whisper von OpenAI für präzise Transkripte und bietet erweiterte Funktionen wie Sprachauswahl, Modellwahl und Export. In diesem Beitrag erkläre ich euch, wie ihr sie installiert, zeige den vollständigen Code und beschreibe mögliche Einsatzgebiete.

Funktionen der Anwendung

  • Audioaufnahme: Nimmt Sprache über ein Mikrofon auf (maximal 20 Sekunden).
  • Einzeltranskript: Transkribiert eine einzelne Audiodatei (WAV, MP3, M4A).
  • Massentranskript: Verarbeitet mehrere Audiodateien aus einem Ordner.
  • Sprachauswahl: Wählt die Sprache für die Transkription (z. B. Deutsch, Englisch).
  • Modellwahl: Passt die Whisper-Modellgröße an (von tiny bis large).
  • Export: Speichert Transkripte als Textdateien.
  • Vorlesen: Liest Ergebnisse mit pyttsx3 vor.
  • Zwischenablage: Kopiert Text mit pyperclip und fügt ihn ein.

Voraussetzungen und Installation

Um die Anwendung zu nutzen, braucht ihr ein paar Tools:

1. Python installieren

Ladet euch Python 3.8+ herunter und installiert es.

2. Abhängigkeiten installieren

Öffnet ein Terminal und führt diesen Befehl aus:

bash

pip install sounddevice numpy scipy pyttsx3 pyperclip pyautogui PyQt5

3. Whisper installieren

Installiert Whisper mit:

bash

pip install -U openai-whisper

Falls ihr eine GPU nutzen wollt (empfohlen für große Modelle), installiert CUDA und die passenden PyTorch-Versionen.

4. Mikrofon prüfen

Stellt sicher, dass ein Mikrofon angeschlossen ist und in den Systemeinstellungen erkannt wird.


Der vollständige Code

Hier ist mein optimierter Code:

python

import sys
import os
import subprocess
import sounddevice as sd
import numpy as np
from scipy.io.wavfile import write
import pyttsx3
import pyperclip
import pyautogui
from PyQt5.QtWidgets import (QApplication, QMainWindow, QPushButton, QTextEdit, 
                             QLabel, QVBoxLayout, QWidget, QFileDialog, QMessageBox, 
                             QComboBox, QCheckBox)
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from PyQt5.QtGui import QFont

class AudioWorker(QThread):
    finished = pyqtSignal(str)
    status_update = pyqtSignal(str)
    
    def __init__(self, fs=44100, mode="record", input_file=None, input_dir=None, 
                 language="auto", model="medium", export=False):
        super().__init__()
        self.fs = fs
        self.mode = mode
        self.recording = False
        self.buffer = []
        self.input_file = input_file
        self.input_dir = input_dir
        self.language = language
        self.model = model
        self.export = export
    
    def run(self):
        if self.mode == "record":
            self.record_audio()
        elif self.mode == "single":
            self.transcribe_single()
        elif self.mode == "batch":
            self.transcribe_batch()
    
    def record_audio(self):
        self.audio_stream = sd.InputStream(samplerate=self.fs, channels=1, dtype='int16')
        self.audio_stream.start()
        
        while self.recording:
            try:
                audiodaten, _ = self.audio_stream.read(self.fs)
                self.buffer.extend(audiodaten)
                if len(self.buffer) > self.fs * 20:
                    self.stop_recording()
            except Exception as e:
                self.status_update.emit(f"Fehler bei der Aufnahme: {str(e)}")
                break
                
        self.audio_stream.stop()
        self.audio_stream.close()
        
        buffer_array = np.array(self.buffer)
        output_file = "aufnahme.wav"
        write(output_file, self.fs, buffer_array)
        
        self.status_update.emit("Verarbeite Aufnahme...")
        result = self.process_audio(output_file)
        self.finished.emit(result)
    
    def transcribe_single(self):
        if not self.input_file or not os.path.exists(self.input_file):
            self.finished.emit("Fehler: Datei nicht gefunden.")
            return
        self.status_update.emit(f"Transkribiere {os.path.basename(self.input_file)}...")
        result = self.process_audio(self.input_file)
        self.finished.emit(result)
    
    def transcribe_batch(self):
        if not self.input_dir or not os.path.isdir(self.input_dir):
            self.finished.emit("Fehler: Ordner nicht gefunden.")
            return
        
        audio_files = [f for f in os.listdir(self.input_dir) if f.endswith(('.wav', '.mp3', '.m4a'))]
        if not audio_files:
            self.finished.emit("Keine Audiodateien im Ordner gefunden.")
            return
        
        results = []
        for audio_file in audio_files:
            full_path = os.path.join(self.input_dir, audio_file)
            self.status_update.emit(f"Transkribiere {audio_file}...")
            result = self.process_audio(full_path)
            results.append(f"{audio_file}: {result}")
        
        self.finished.emit("\n\n".join(results))
    
    def process_audio(self, audio_file):
        try:
            lang_param = f"--language {self.language}" if self.language != "auto" else ""
            cmd = f"whisper {audio_file} --device cuda --model {self.model} {lang_param} --output_dir transkrip"
            subprocess.run(cmd, shell=True, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            
            transkrip_file = os.path.join("transkrip", f"{os.path.splitext(os.path.basename(audio_file))[0]}.txt")
            if os.path.exists(transkrip_file):
                with open(transkrip_file, 'r', encoding='utf-8') as file:
                    content = file.read()
                
                if self.export:
                    export_path = os.path.splitext(audio_file)[0] + "_transkript.txt"
                    with open(export_path, 'w', encoding='utf-8') as export_file:
                        export_file.write(content)
                    self.status_update.emit(f"Exportiert nach: {export_path}")
                
                return content
            return "Transkription fehlgeschlagen."
        except subprocess.CalledProcessError as e:
            return f"Fehler bei der Verarbeitung: {e.stderr.decode()}"
        except Exception as e:
            return f"Fehler: {str(e)}"
    
    def stop_recording(self):
        self.recording = False

class AudioTranscriberWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.fs = 44100
        self.worker = None
        self.init_ui()
    
    def init_ui(self):
        self.setWindowTitle("Audio Transcriber")
        self.setGeometry(100, 100, 600, 600)
        
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        layout = QVBoxLayout(central_widget)
        
        self.setStyleSheet("""
            QMainWindow { background-color: #f0f0f0; }
            QPushButton { 
                background-color: #4CAF50; 
                color: white; 
                padding: 8px; 
                border: none; 
                border-radius: 4px;
            }
            QPushButton:disabled { background-color: #cccccc; }
            QTextEdit { border: 1px solid #ddd; border-radius: 4px; }
            QComboBox { padding: 4px; }
        """)
        
        title = QLabel("Audio Transcriber")
        title.setFont(QFont("Helvetica", 16, QFont.Bold))
        title.setAlignment(Qt.AlignCenter)
        layout.addWidget(title)
        
        self.status_label = QLabel("Bereit")
        self.status_label.setAlignment(Qt.AlignCenter)
        layout.addWidget(self.status_label)
        
        self.language_combo = QComboBox()
        self.language_combo.addItems(["auto", "de", "en", "fr", "es", "it"])
        self.language_combo.setCurrentText("auto")
        layout.addWidget(QLabel("Sprache:"))
        layout.addWidget(self.language_combo)
        
        self.model_combo = QComboBox()
        self.model_combo.addItems(["tiny", "base", "small", "medium", "large"])
        self.model_combo.setCurrentText("medium")
        layout.addWidget(QLabel("Modell:"))
        layout.addWidget(self.model_combo)
        
        self.export_check = QCheckBox("Transkript exportieren")
        layout.addWidget(self.export_check)
        
        self.record_btn = QPushButton("Aufnahme starten")
        self.record_btn.clicked.connect(self.start_recording)
        layout.addWidget(self.record_btn)
        
        self.stop_btn = QPushButton("Aufnahme stoppen")
        self.stop_btn.clicked.connect(self.stop_recording)
        self.stop_btn.setEnabled(False)
        layout.addWidget(self.stop_btn)
        
        self.single_btn = QPushButton("Einzeltranskript")
        self.single_btn.clicked.connect(self.transcribe_single_file)
        layout.addWidget(self.single_btn)
        
        self.batch_btn = QPushButton("Massentranskript")
        self.batch_btn.clicked.connect(self.transcribe_batch_files)
        layout.addWidget(self.batch_btn)
        
        self.result_text = QTextEdit()
        self.result_text.setReadOnly(True)
        layout.addWidget(self.result_text)
        
        self.copy_btn = QPushButton("In Zwischenablage kopieren")
        self.copy_btn.clicked.connect(self.copy_to_clipboard)
        self.copy_btn.setEnabled(False)
        layout.addWidget(self.copy_btn)
    
    def start_recording(self):
        self.worker = AudioWorker(
            fs=self.fs, mode="record", 
            language=self.language_combo.currentText(),
            model=self.model_combo.currentText(),
            export=self.export_check.isChecked()
        )
        self.worker.recording = True
        self.worker.finished.connect(self.on_task_finished)
        self.worker.status_update.connect(self.update_status)
        self.worker.start()
        
        self.toggle_buttons(recording=True)
        self.status_label.setText("Aufnahme läuft...")
    
    def stop_recording(self):
        if self.worker and self.worker.mode == "record":
            self.worker.stop_recording()
    
    def transcribe_single_file(self):
        file_path, _ = QFileDialog.getOpenFileName(self, "Audiodatei auswählen", "", "Audio Files (*.wav *.mp3 *.m4a)")
        if file_path:
            self.worker = AudioWorker(
                mode="single", input_file=file_path,
                language=self.language_combo.currentText(),
                model=self.model_combo.currentText(),
                export=self.export_check.isChecked()
            )
            self.worker.finished.connect(self.on_task_finished)
            self.worker.status_update.connect(self.update_status)
            self.worker.start()
            self.toggle_buttons(recording=True)
    
    def transcribe_batch_files(self):
        dir_path = QFileDialog.getExistingDirectory(self, "Ordner auswählen")
        if dir_path:
            self.worker = AudioWorker(
                mode="batch", input_dir=dir_path,
                language=self.language_combo.currentText(),
                model=self.model_combo.currentText(),
                export=self.export_check.isChecked()
            )
            self.worker.finished.connect(self.on_task_finished)
            self.worker.status_update.connect(self.update_status)
            self.worker.start()
            self.toggle_buttons(recording=True)
    
    def on_task_finished(self, result):
        self.result_text.setText(result)
        self.status_label.setText("Abgeschlossen")
        self.toggle_buttons(recording=False)
        self.copy_btn.setEnabled(True)
        
        try:
            tts = pyttsx3.init()
            tts.say(result)
            tts.runAndWait()
        except Exception as e:
            QMessageBox.warning(self, "Fehler", f"Text-to-Speech fehlgeschlagen: {str(e)}")
    
    def update_status(self, status):
        self.status_label.setText(status)
    
    def copy_to_clipboard(self):
        content = self.result_text.toPlainText()
        pyperclip.copy(content)
        pyautogui.hotkey('ctrl', 'v')
        self.status_label.setText("Text kopiert und eingefügt!")
    
    def toggle_buttons(self, recording):
        self.record_btn.setEnabled(not recording)
        self.stop_btn.setEnabled(recording and self.worker.mode == "record")
        self.single_btn.setEnabled(not recording)
        self.batch_btn.setEnabled(not recording)
        self.language_combo.setEnabled(not recording)
        self.model_combo.setEnabled(not recording)
        self.export_check.setEnabled(not recording)

def main():
    app = QApplication(sys.argv)
    window = AudioTranscriberWindow()
    window.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

Speichert den Code als audio_transcriber.py und startet ihn mit python audio_transcriber.py.


Wie funktioniert die Anwendung?

  1. GUI: Die Oberfläche basiert auf PyQt5 und bietet Buttons, Dropdowns und eine Checkbox.
  2. Aufnahme: Mit sounddevice zeichne ich Audio auf und speichere es als WAV-Datei.
  3. Transkription: Whisper verarbeitet die Audiodaten – live, einzeln oder im Batch.
  4. Erweiterungen: Sprache und Modell sind wählbar, Ergebnisse können exportiert werden.

Einsatzgebiete

Meine Anwendung ist vielseitig einsetzbar:

  • Journalismus: Transkribiert Interviews schnell und präzise – ideal für Artikel oder Podcasts. Tools wie Audacity ergänzen sie perfekt.
  • Bildung: Wandelt Vorlesungen oder Seminare in Text um, z. B. für Notizen oder Lernplattformen wie Moodle.
  • Content Creation: Erstellt Untertitel für Videos – kombinierbar mit FFmpeg für die Nachbearbeitung.
  • Forschung: Analysiert gesprochene Daten, z. B. in der Linguistik oder Soziologie, unterstützt durch Praat.
  • Barrierefreiheit: Macht Audioinhalte für Hörgeschädigte zugänglich – ein Thema, das auch die W3C betont.
  • Dokumentation: Protokolliert Meetings oder Telefonate, z. B. mit Zoom oder Microsoft Teams.

Nützliche Links


Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert