Initialer Push, Grundsätzlich funktioniert das Programm

This commit is contained in:
2026-07-09 23:00:31 +02:00
parent 70088f7861
commit 952bbb6158
51 changed files with 3066 additions and 0 deletions
+192
View File
@@ -0,0 +1,192 @@
"""Tab 2 - Transcript: Metadaten-Kopf, Miniplayer, Sprecher, Transkription, Freitext."""
from __future__ import annotations
import logging
from PySide6.QtCore import Signal
from PySide6.QtWidgets import (
QHBoxLayout,
QLabel,
QLineEdit,
QMessageBox,
QPushButton,
QTextEdit,
QVBoxLayout,
QWidget,
)
from notarius.gui.controller import AppController
from notarius.gui.format_utils import format_duration
from notarius.gui.widgets.audio_player import MiniAudioPlayer
from notarius.gui.widgets.editable_field import EditableDateTimeField, EditableTextField
from notarius.models.recording import Recording
from notarius.transcription.worker import STAGE_LABELS, TranscriptionWorker
logger = logging.getLogger(__name__)
class TranscriptTab(QWidget):
# Wird ausgelöst, sobald ein Transkript neu erzeugt wurde - MainWindow prüft
# daraufhin erneut, ob Tab 3 aktiviert werden kann.
transcription_finished = Signal()
def __init__(self, controller: AppController, parent: QWidget | None = None) -> None:
super().__init__(parent)
self._controller = controller
self._recording: Recording | None = None
self._worker: TranscriptionWorker | None = None
self._title_field = EditableTextField("Titel")
self._title_field.value_changed.connect(self._on_title_changed)
self._timestamp_field = EditableDateTimeField("Aufzeichnungszeitpunkt")
self._timestamp_field.value_changed.connect(self._on_timestamp_changed)
self._duration_label = QLabel()
self._player = MiniAudioPlayer()
self._speaker_field = QLineEdit()
self._speaker_field.setPlaceholderText("Sprecher, kommagetrennt (z.B. Anna, Max)")
self._speaker_field.editingFinished.connect(self._on_speakers_edited)
self._transcribe_button = QPushButton("Transcribe")
self._transcribe_button.clicked.connect(self._on_transcribe_clicked)
self._status_label = QLabel("idle")
self._text_edit = QTextEdit()
self._text_edit.setPlaceholderText("Noch kein Transkript vorhanden.")
self._save_button = QPushButton("Save")
self._save_button.clicked.connect(self._on_save_clicked)
self._delete_button = QPushButton("Delete")
self._delete_button.clicked.connect(self._on_delete_clicked)
transcribe_row = QHBoxLayout()
transcribe_row.addWidget(self._transcribe_button)
transcribe_row.addWidget(self._status_label)
transcribe_row.addStretch(1)
layout = QVBoxLayout(self)
layout.addWidget(self._title_field)
layout.addWidget(self._timestamp_field)
layout.addWidget(self._duration_label)
layout.addWidget(self._player)
layout.addWidget(self._speaker_field)
layout.addLayout(transcribe_row)
layout.addWidget(self._text_edit, stretch=1)
layout.addWidget(self._save_button)
layout.addStretch(0)
layout.addWidget(self._delete_button)
self.set_recording(None)
def set_recording(self, recording: Recording | None) -> None:
self._recording = recording
has_recording = recording is not None
self.setEnabled(has_recording)
if not has_recording:
return
self._title_field.set_value(recording.meta.title)
self._timestamp_field.set_value(recording.meta.created_at)
self._duration_label.setText(f"<b>Länge:</b> {format_duration(recording.meta.duration_seconds)}")
self._player.load(recording.audio_path)
self._speaker_field.setText(", ".join(recording.meta.speakers))
if recording.has_transcript:
self._text_edit.setPlainText(recording.transcript_path.read_text(encoding="utf-8"))
self._status_label.setText("done")
else:
self._text_edit.clear()
self._status_label.setText("idle")
self._transcribe_button.setEnabled(True)
# -- Metadaten-Bearbeitung ----------------------------------------------------
def _on_title_changed(self, new_title: str) -> None:
if self._recording is None:
return
self._recording.meta.title = new_title
self._recording.save_meta()
self._controller.recordings_changed.emit()
def _on_timestamp_changed(self, new_iso: str) -> None:
if self._recording is None:
return
self._recording.meta.created_at = new_iso
self._recording.save_meta()
self._controller.recordings_changed.emit()
def _on_speakers_edited(self) -> None:
if self._recording is None:
return
speakers = [s.strip() for s in self._speaker_field.text().split(",") if s.strip()]
self._recording.meta.speakers = speakers
self._recording.save_meta()
# -- Transkription -------------------------------------------------------------
def _on_transcribe_clicked(self) -> None:
if self._recording is None:
return
if self._recording.has_transcript:
answer = QMessageBox.question(
self, "Transkript überschreiben?", "Es existiert bereits ein Transkript. Überschreiben?"
)
if answer != QMessageBox.StandardButton.Yes:
return
self._transcribe_button.setEnabled(False)
self._status_label.setText(STAGE_LABELS["loading_models"])
self._worker = TranscriptionWorker(self)
self._worker.stage_changed.connect(self._on_stage_changed)
self._worker.finished.connect(self._on_transcription_worker_finished)
self._worker.start(
self._recording.audio_path, self._recording.transcript_path, self._recording.meta.title
)
def _on_stage_changed(self, stage: str) -> None:
self._status_label.setText(STAGE_LABELS.get(stage, stage))
def _on_transcription_worker_finished(self, success: bool, error_message: str) -> None:
self._transcribe_button.setEnabled(True)
self._worker = None
if not self._recording:
return
if success:
self._status_label.setText("done")
self._text_edit.setPlainText(self._recording.transcript_path.read_text(encoding="utf-8"))
self.transcription_finished.emit()
else:
self._status_label.setText("error")
QMessageBox.critical(self, "Transkription fehlgeschlagen", error_message)
# -- Freitext / Löschen ----------------------------------------------------------
def _on_save_clicked(self) -> None:
if self._recording is None:
return
self._recording.transcript_path.write_text(self._text_edit.toPlainText(), encoding="utf-8")
def _on_delete_clicked(self) -> None:
if self._recording is None:
return
answer = QMessageBox.question(
self,
"Aufnahme löschen?",
"Aufnahme und alle zugehörigen Dateien (Audio, Transkript, Notizen, Summary) "
"werden unwiderruflich gelöscht. Fortfahren?",
)
if answer != QMessageBox.StandardButton.Yes:
return
self._controller.delete_recording(self._recording)