193 lines
7.1 KiB
Python
193 lines
7.1 KiB
Python
"""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)
|