"""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"Länge: {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)