mirror of
https://github.com/openai/whisper.git
synced 2025-11-24 14:35:57 +00:00
- Create styles.py module with comprehensive stylesheet - Implement color palette and typography configuration - Apply consistent styling across all UI elements - Improve button, text input, and progress bar appearance - Use monospace font for transcription results display - Add hover and active states for interactive elements - Phase 5 complete: Professional UI styling applied
286 lines
10 KiB
Python
286 lines
10 KiB
Python
"""
|
|
Main application window for Farsi Transcriber
|
|
|
|
Provides PyQt6-based GUI for selecting files and transcribing Farsi audio/video.
|
|
"""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
|
|
from PyQt6.QtCore import Qt, QThread, pyqtSignal
|
|
from PyQt6.QtWidgets import (
|
|
QMainWindow,
|
|
QWidget,
|
|
QVBoxLayout,
|
|
QHBoxLayout,
|
|
QPushButton,
|
|
QLabel,
|
|
QTextEdit,
|
|
QProgressBar,
|
|
QFileDialog,
|
|
QMessageBox,
|
|
)
|
|
from PyQt6.QtGui import QFont
|
|
|
|
from farsi_transcriber.models.whisper_transcriber import FarsiTranscriber
|
|
from farsi_transcriber.utils.export import TranscriptionExporter
|
|
from farsi_transcriber.ui.styles import get_stylesheet, get_color
|
|
|
|
|
|
class TranscriptionWorker(QThread):
|
|
"""Worker thread for transcription to prevent UI freezing"""
|
|
|
|
# Signals
|
|
progress_update = pyqtSignal(str) # Status messages
|
|
transcription_complete = pyqtSignal(dict) # Results with timestamps
|
|
error_occurred = pyqtSignal(str) # Error messages
|
|
|
|
def __init__(self, file_path: str, model_name: str = "medium"):
|
|
super().__init__()
|
|
self.file_path = file_path
|
|
self.model_name = model_name
|
|
self.transcriber = None
|
|
|
|
def run(self):
|
|
"""Run transcription in background thread"""
|
|
try:
|
|
# Initialize Whisper transcriber
|
|
self.progress_update.emit("Loading Whisper model...")
|
|
self.transcriber = FarsiTranscriber(model_name=self.model_name)
|
|
|
|
# Perform transcription
|
|
self.progress_update.emit(f"Transcribing: {Path(self.file_path).name}")
|
|
result = self.transcriber.transcribe(self.file_path)
|
|
|
|
# Format result for display with timestamps
|
|
display_text = self.transcriber.format_result_for_display(result)
|
|
|
|
# Add full text for export
|
|
result["full_text"] = result.get("text", "")
|
|
|
|
self.progress_update.emit("Transcription complete!")
|
|
self.transcription_complete.emit(
|
|
{
|
|
"text": display_text,
|
|
"segments": result.get("segments", []),
|
|
"full_text": result.get("text", ""),
|
|
}
|
|
)
|
|
|
|
except Exception as e:
|
|
self.error_occurred.emit(f"Error: {str(e)}")
|
|
|
|
|
|
class MainWindow(QMainWindow):
|
|
"""Main application window for Farsi Transcriber"""
|
|
|
|
# Supported audio and video formats
|
|
SUPPORTED_FORMATS = (
|
|
"Audio Files (*.mp3 *.wav *.m4a *.flac *.ogg *.aac *.wma);;",
|
|
"Video Files (*.mp4 *.mkv *.mov *.webm *.avi *.flv *.wmv);;",
|
|
"All Files (*.*)",
|
|
)
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.selected_file = None
|
|
self.transcription_worker = None
|
|
self.last_result = None
|
|
# Apply stylesheet
|
|
self.setStyleSheet(get_stylesheet())
|
|
self.init_ui()
|
|
|
|
def init_ui(self):
|
|
"""Initialize the user interface"""
|
|
self.setWindowTitle("Farsi Transcriber")
|
|
self.setGeometry(100, 100, 900, 700)
|
|
|
|
# Create central widget and main layout
|
|
central_widget = QWidget()
|
|
self.setCentralWidget(central_widget)
|
|
main_layout = QVBoxLayout(central_widget)
|
|
main_layout.setSpacing(10)
|
|
main_layout.setContentsMargins(20, 20, 20, 20)
|
|
|
|
# Title
|
|
title_label = QLabel("Farsi Audio/Video Transcriber")
|
|
title_font = QFont()
|
|
title_font.setPointSize(16)
|
|
title_font.setBold(True)
|
|
title_label.setFont(title_font)
|
|
main_layout.addWidget(title_label)
|
|
|
|
# File selection section
|
|
file_section_layout = QHBoxLayout()
|
|
self.file_label = QLabel("No file selected")
|
|
self.file_label.setStyleSheet("color: gray;")
|
|
file_section_layout.addWidget(self.file_label, 1)
|
|
|
|
self.select_button = QPushButton("Select File")
|
|
self.select_button.clicked.connect(self.on_select_file)
|
|
file_section_layout.addWidget(self.select_button)
|
|
|
|
main_layout.addLayout(file_section_layout)
|
|
|
|
# Transcribe button
|
|
self.transcribe_button = QPushButton("Transcribe")
|
|
self.transcribe_button.clicked.connect(self.on_transcribe)
|
|
self.transcribe_button.setEnabled(False)
|
|
main_layout.addWidget(self.transcribe_button)
|
|
|
|
# Progress bar
|
|
self.progress_bar = QProgressBar()
|
|
self.progress_bar.setRange(0, 0) # Indeterminate progress
|
|
self.progress_bar.setVisible(False)
|
|
main_layout.addWidget(self.progress_bar)
|
|
|
|
# Status label
|
|
self.status_label = QLabel("Ready")
|
|
self.status_label.setStyleSheet("color: #666; font-style: italic;")
|
|
main_layout.addWidget(self.status_label)
|
|
|
|
# Results text area
|
|
results_title = QLabel("Transcription Results:")
|
|
results_title_font = QFont()
|
|
results_title_font.setBold(True)
|
|
results_title.setFont(results_title_font)
|
|
main_layout.addWidget(results_title)
|
|
|
|
self.results_text = QTextEdit()
|
|
self.results_text.setReadOnly(True)
|
|
self.results_text.setPlaceholderText(
|
|
"Transcription results will appear here..."
|
|
)
|
|
# Set monospace font for results
|
|
mono_font = QFont("Courier New", 10)
|
|
self.results_text.setFont(mono_font)
|
|
main_layout.addWidget(self.results_text)
|
|
|
|
# Buttons layout (Export, Clear)
|
|
buttons_layout = QHBoxLayout()
|
|
buttons_layout.addStretch()
|
|
|
|
self.export_button = QPushButton("Export Results")
|
|
self.export_button.clicked.connect(self.on_export)
|
|
self.export_button.setEnabled(False)
|
|
buttons_layout.addWidget(self.export_button)
|
|
|
|
self.clear_button = QPushButton("Clear")
|
|
self.clear_button.clicked.connect(self.on_clear)
|
|
buttons_layout.addWidget(self.clear_button)
|
|
|
|
main_layout.addLayout(buttons_layout)
|
|
|
|
def on_select_file(self):
|
|
"""Handle file selection dialog"""
|
|
file_path, _ = QFileDialog.getOpenFileName(
|
|
self, "Select Audio or Video File", "", "".join(self.SUPPORTED_FORMATS)
|
|
)
|
|
|
|
if file_path:
|
|
self.selected_file = file_path
|
|
file_name = Path(file_path).name
|
|
self.file_label.setText(f"Selected: {file_name}")
|
|
self.file_label.setStyleSheet("color: #333;")
|
|
self.transcribe_button.setEnabled(True)
|
|
self.export_button.setEnabled(False)
|
|
self.results_text.clear()
|
|
self.status_label.setText("File selected. Click 'Transcribe' to start.")
|
|
|
|
def on_transcribe(self):
|
|
"""Handle transcription button click"""
|
|
if not self.selected_file:
|
|
QMessageBox.warning(self, "Error", "Please select a file first.")
|
|
return
|
|
|
|
# Disable buttons during transcription
|
|
self.transcribe_button.setEnabled(False)
|
|
self.select_button.setEnabled(False)
|
|
self.export_button.setEnabled(False)
|
|
|
|
# Show progress
|
|
self.progress_bar.setVisible(True)
|
|
self.status_label.setText("Transcribing...")
|
|
|
|
# Create and start worker thread
|
|
self.transcription_worker = TranscriptionWorker(self.selected_file)
|
|
self.transcription_worker.progress_update.connect(self.on_progress_update)
|
|
self.transcription_worker.transcription_complete.connect(
|
|
self.on_transcription_complete
|
|
)
|
|
self.transcription_worker.error_occurred.connect(self.on_error)
|
|
self.transcription_worker.start()
|
|
|
|
def on_progress_update(self, message: str):
|
|
"""Handle progress updates from worker thread"""
|
|
self.status_label.setText(message)
|
|
|
|
def on_transcription_complete(self, result: dict):
|
|
"""Handle completed transcription"""
|
|
self.progress_bar.setVisible(False)
|
|
self.transcribe_button.setEnabled(True)
|
|
self.select_button.setEnabled(True)
|
|
self.export_button.setEnabled(True)
|
|
self.status_label.setText("Transcription complete!")
|
|
|
|
# Display results with timestamps
|
|
self.results_text.setText(result.get("text", "No transcription available"))
|
|
|
|
# Store result for export
|
|
self.last_result = result
|
|
|
|
def on_error(self, error_message: str):
|
|
"""Handle errors from worker thread"""
|
|
self.progress_bar.setVisible(False)
|
|
self.transcribe_button.setEnabled(True)
|
|
self.select_button.setEnabled(True)
|
|
self.status_label.setText("Error occurred. Check message below.")
|
|
QMessageBox.critical(self, "Transcription Error", error_message)
|
|
|
|
def on_export(self):
|
|
"""Handle export button click"""
|
|
if not self.last_result:
|
|
QMessageBox.warning(self, "Warning", "No transcription to export.")
|
|
return
|
|
|
|
file_path, file_filter = QFileDialog.getSaveFileName(
|
|
self,
|
|
"Export Transcription",
|
|
"",
|
|
"Text Files (*.txt);;SRT Subtitles (*.srt);;WebVTT Subtitles (*.vtt);;JSON (*.json);;TSV (*.tsv)",
|
|
)
|
|
|
|
if file_path:
|
|
try:
|
|
file_path = Path(file_path)
|
|
|
|
# Determine format from file extension
|
|
suffix = file_path.suffix.lower().lstrip(".")
|
|
if not suffix:
|
|
# Default to txt if no extension
|
|
suffix = "txt"
|
|
file_path = file_path.with_suffix(".txt")
|
|
|
|
# Export using the appropriate format
|
|
TranscriptionExporter.export(self.last_result, file_path, suffix)
|
|
|
|
QMessageBox.information(
|
|
self,
|
|
"Success",
|
|
f"Transcription exported successfully to:\n{file_path.name}",
|
|
)
|
|
except Exception as e:
|
|
QMessageBox.critical(
|
|
self, "Export Error", f"Failed to export: {str(e)}"
|
|
)
|
|
|
|
def on_clear(self):
|
|
"""Clear all results and reset UI"""
|
|
self.selected_file = None
|
|
self.file_label.setText("No file selected")
|
|
self.file_label.setStyleSheet("color: gray;")
|
|
self.results_text.clear()
|
|
self.status_label.setText("Ready")
|
|
self.transcribe_button.setEnabled(False)
|
|
self.export_button.setEnabled(False)
|