supervertaler 1.9.153__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of supervertaler might be problematic. Click here for more details.
- Supervertaler.py +47886 -0
- modules/__init__.py +10 -0
- modules/ai_actions.py +964 -0
- modules/ai_attachment_manager.py +343 -0
- modules/ai_file_viewer_dialog.py +210 -0
- modules/autofingers_engine.py +466 -0
- modules/cafetran_docx_handler.py +379 -0
- modules/config_manager.py +469 -0
- modules/database_manager.py +1878 -0
- modules/database_migrations.py +417 -0
- modules/dejavurtf_handler.py +779 -0
- modules/document_analyzer.py +427 -0
- modules/docx_handler.py +689 -0
- modules/encoding_repair.py +319 -0
- modules/encoding_repair_Qt.py +393 -0
- modules/encoding_repair_ui.py +481 -0
- modules/feature_manager.py +350 -0
- modules/figure_context_manager.py +340 -0
- modules/file_dialog_helper.py +148 -0
- modules/find_replace.py +164 -0
- modules/find_replace_qt.py +457 -0
- modules/glossary_manager.py +433 -0
- modules/image_extractor.py +188 -0
- modules/keyboard_shortcuts_widget.py +571 -0
- modules/llm_clients.py +1211 -0
- modules/llm_leaderboard.py +737 -0
- modules/llm_superbench_ui.py +1401 -0
- modules/local_llm_setup.py +1104 -0
- modules/model_update_dialog.py +381 -0
- modules/model_version_checker.py +373 -0
- modules/mqxliff_handler.py +638 -0
- modules/non_translatables_manager.py +743 -0
- modules/pdf_rescue_Qt.py +1822 -0
- modules/pdf_rescue_tkinter.py +909 -0
- modules/phrase_docx_handler.py +516 -0
- modules/project_home_panel.py +209 -0
- modules/prompt_assistant.py +357 -0
- modules/prompt_library.py +689 -0
- modules/prompt_library_migration.py +447 -0
- modules/quick_access_sidebar.py +282 -0
- modules/ribbon_widget.py +597 -0
- modules/sdlppx_handler.py +874 -0
- modules/setup_wizard.py +353 -0
- modules/shortcut_manager.py +932 -0
- modules/simple_segmenter.py +128 -0
- modules/spellcheck_manager.py +727 -0
- modules/statuses.py +207 -0
- modules/style_guide_manager.py +315 -0
- modules/superbench_ui.py +1319 -0
- modules/superbrowser.py +329 -0
- modules/supercleaner.py +600 -0
- modules/supercleaner_ui.py +444 -0
- modules/superdocs.py +19 -0
- modules/superdocs_viewer_qt.py +382 -0
- modules/superlookup.py +252 -0
- modules/tag_cleaner.py +260 -0
- modules/tag_manager.py +333 -0
- modules/term_extractor.py +270 -0
- modules/termbase_entry_editor.py +842 -0
- modules/termbase_import_export.py +488 -0
- modules/termbase_manager.py +1060 -0
- modules/termview_widget.py +1172 -0
- modules/theme_manager.py +499 -0
- modules/tm_editor_dialog.py +99 -0
- modules/tm_manager_qt.py +1280 -0
- modules/tm_metadata_manager.py +545 -0
- modules/tmx_editor.py +1461 -0
- modules/tmx_editor_qt.py +2784 -0
- modules/tmx_generator.py +284 -0
- modules/tracked_changes.py +900 -0
- modules/trados_docx_handler.py +430 -0
- modules/translation_memory.py +715 -0
- modules/translation_results_panel.py +2134 -0
- modules/translation_services.py +282 -0
- modules/unified_prompt_library.py +659 -0
- modules/unified_prompt_manager_qt.py +3951 -0
- modules/voice_commands.py +920 -0
- modules/voice_dictation.py +477 -0
- modules/voice_dictation_lite.py +249 -0
- supervertaler-1.9.153.dist-info/METADATA +896 -0
- supervertaler-1.9.153.dist-info/RECORD +85 -0
- supervertaler-1.9.153.dist-info/WHEEL +5 -0
- supervertaler-1.9.153.dist-info/entry_points.txt +2 -0
- supervertaler-1.9.153.dist-info/licenses/LICENSE +21 -0
- supervertaler-1.9.153.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Voice Dictation Module for Supervertaler
|
|
3
|
+
Uses OpenAI Whisper for multilingual speech recognition
|
|
4
|
+
Supports English, Dutch, and 90+ other languages
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sounddevice as sd
|
|
8
|
+
import numpy as np
|
|
9
|
+
import tempfile
|
|
10
|
+
import wave
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from PyQt6.QtWidgets import (
|
|
13
|
+
QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel,
|
|
14
|
+
QComboBox, QTextEdit, QGroupBox, QMessageBox, QProgressBar
|
|
15
|
+
)
|
|
16
|
+
from PyQt6.QtCore import Qt, QThread, pyqtSignal
|
|
17
|
+
from PyQt6.QtGui import QFont, QShortcut, QKeySequence
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class RecordingThread(QThread):
|
|
21
|
+
"""Background thread for audio recording"""
|
|
22
|
+
finished = pyqtSignal(str) # Emits path to recorded file
|
|
23
|
+
error = pyqtSignal(str)
|
|
24
|
+
|
|
25
|
+
def __init__(self, duration=30, sample_rate=16000):
|
|
26
|
+
super().__init__()
|
|
27
|
+
self.duration = duration
|
|
28
|
+
self.sample_rate = sample_rate
|
|
29
|
+
self.is_recording = False
|
|
30
|
+
|
|
31
|
+
def run(self):
|
|
32
|
+
"""Record audio in background"""
|
|
33
|
+
try:
|
|
34
|
+
self.is_recording = True
|
|
35
|
+
|
|
36
|
+
# Record audio
|
|
37
|
+
recording = sd.rec(
|
|
38
|
+
int(self.duration * self.sample_rate),
|
|
39
|
+
samplerate=self.sample_rate,
|
|
40
|
+
channels=1,
|
|
41
|
+
dtype='float32'
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Wait for recording to complete
|
|
45
|
+
sd.wait()
|
|
46
|
+
|
|
47
|
+
if not self.is_recording:
|
|
48
|
+
# Recording was stopped early
|
|
49
|
+
sd.stop()
|
|
50
|
+
|
|
51
|
+
# Convert to int16
|
|
52
|
+
audio_data = np.int16(recording * 32767)
|
|
53
|
+
|
|
54
|
+
# Save to temporary WAV file with explicit directory
|
|
55
|
+
import os
|
|
56
|
+
temp_dir = tempfile.gettempdir()
|
|
57
|
+
temp_path = os.path.join(temp_dir, f"voice_recording_{os.getpid()}.wav")
|
|
58
|
+
|
|
59
|
+
with wave.open(temp_path, 'wb') as wf:
|
|
60
|
+
wf.setnchannels(1)
|
|
61
|
+
wf.setsampwidth(2) # 16-bit
|
|
62
|
+
wf.setframerate(self.sample_rate)
|
|
63
|
+
wf.writeframes(audio_data.tobytes())
|
|
64
|
+
|
|
65
|
+
self.finished.emit(temp_path)
|
|
66
|
+
|
|
67
|
+
except Exception as e:
|
|
68
|
+
import traceback
|
|
69
|
+
self.error.emit(f"{str(e)}\n{traceback.format_exc()}")
|
|
70
|
+
|
|
71
|
+
def stop(self):
|
|
72
|
+
"""Stop recording"""
|
|
73
|
+
self.is_recording = False
|
|
74
|
+
sd.stop()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TranscriptionThread(QThread):
|
|
78
|
+
"""Background thread for transcription"""
|
|
79
|
+
finished = pyqtSignal(str) # Emits transcribed text
|
|
80
|
+
error = pyqtSignal(str)
|
|
81
|
+
progress = pyqtSignal(str) # Progress updates
|
|
82
|
+
|
|
83
|
+
def __init__(self, audio_path, model_name="base", language=None):
|
|
84
|
+
super().__init__()
|
|
85
|
+
self.audio_path = audio_path
|
|
86
|
+
self.model_name = model_name
|
|
87
|
+
self.language = language
|
|
88
|
+
|
|
89
|
+
def run(self):
|
|
90
|
+
"""Transcribe audio in background"""
|
|
91
|
+
try:
|
|
92
|
+
import os
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
import whisper # Local Whisper (optional)
|
|
96
|
+
except ImportError:
|
|
97
|
+
self.error.emit(
|
|
98
|
+
"Local Whisper is not installed.\n\n"
|
|
99
|
+
"To use offline speech recognition, install:\n"
|
|
100
|
+
" pip install supervertaler[local-whisper]\n\n"
|
|
101
|
+
"Or switch to 'OpenAI Whisper API' in Supervoice settings."
|
|
102
|
+
)
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
# Verify file exists
|
|
106
|
+
if not os.path.exists(self.audio_path):
|
|
107
|
+
self.error.emit(f"Audio file not found: {self.audio_path}")
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
self.progress.emit("Loading Whisper model...")
|
|
111
|
+
|
|
112
|
+
# Load model
|
|
113
|
+
model = whisper.load_model(self.model_name)
|
|
114
|
+
|
|
115
|
+
self.progress.emit("Transcribing audio...")
|
|
116
|
+
|
|
117
|
+
# Transcribe
|
|
118
|
+
if self.language:
|
|
119
|
+
result = model.transcribe(self.audio_path, language=self.language)
|
|
120
|
+
else:
|
|
121
|
+
result = model.transcribe(self.audio_path)
|
|
122
|
+
|
|
123
|
+
# Clean up temp file
|
|
124
|
+
try:
|
|
125
|
+
Path(self.audio_path).unlink()
|
|
126
|
+
except:
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
self.finished.emit(result["text"].strip())
|
|
130
|
+
|
|
131
|
+
except Exception as e:
|
|
132
|
+
import traceback
|
|
133
|
+
self.error.emit(f"{str(e)}\n\nFull error:\n{traceback.format_exc()}")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class VoiceDictationWidget(QWidget):
|
|
137
|
+
"""
|
|
138
|
+
Voice Dictation Widget using Whisper
|
|
139
|
+
|
|
140
|
+
Features:
|
|
141
|
+
- Push-to-record button
|
|
142
|
+
- Multilingual support (100+ languages)
|
|
143
|
+
- Multiple model sizes (tiny, base, small, medium, large)
|
|
144
|
+
- Copy to clipboard functionality
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
MODELS = {
|
|
148
|
+
"tiny": "Tiny (fastest, ~1GB RAM)",
|
|
149
|
+
"base": "Base (good balance, ~1GB RAM)",
|
|
150
|
+
"small": "Small (better quality, ~2GB RAM)",
|
|
151
|
+
"medium": "Medium (high quality, ~5GB RAM)",
|
|
152
|
+
"large": "Large (best quality, ~10GB RAM)"
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
LANGUAGES = {
|
|
156
|
+
"auto": "Auto-detect",
|
|
157
|
+
"en": "English",
|
|
158
|
+
"nl": "Dutch",
|
|
159
|
+
"de": "German",
|
|
160
|
+
"fr": "French",
|
|
161
|
+
"es": "Spanish",
|
|
162
|
+
"it": "Italian",
|
|
163
|
+
"pt": "Portuguese",
|
|
164
|
+
"pl": "Polish",
|
|
165
|
+
"ru": "Russian",
|
|
166
|
+
"zh": "Chinese",
|
|
167
|
+
"ja": "Japanese",
|
|
168
|
+
"ko": "Korean",
|
|
169
|
+
# Add more as needed
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
def __init__(self, parent=None):
|
|
173
|
+
super().__init__(parent)
|
|
174
|
+
self.recording_thread = None
|
|
175
|
+
self.transcription_thread = None
|
|
176
|
+
self.init_ui()
|
|
177
|
+
self.setup_shortcuts()
|
|
178
|
+
|
|
179
|
+
def init_ui(self):
|
|
180
|
+
"""Initialize the user interface"""
|
|
181
|
+
layout = QVBoxLayout(self)
|
|
182
|
+
layout.setContentsMargins(10, 10, 10, 10)
|
|
183
|
+
|
|
184
|
+
# Title
|
|
185
|
+
title = QLabel("đ¤ Voice Dictation")
|
|
186
|
+
title.setFont(QFont("Segoe UI", 14, QFont.Weight.Bold))
|
|
187
|
+
title.setStyleSheet("color: #1976D2;")
|
|
188
|
+
layout.addWidget(title)
|
|
189
|
+
|
|
190
|
+
# Description
|
|
191
|
+
desc = QLabel(
|
|
192
|
+
"Record your voice and get instant transcription in 100+ languages.\n"
|
|
193
|
+
"Powered by OpenAI Whisper."
|
|
194
|
+
)
|
|
195
|
+
desc.setStyleSheet("color: #666; padding: 5px; background-color: #E3F2FD; border-radius: 3px;")
|
|
196
|
+
desc.setWordWrap(True)
|
|
197
|
+
layout.addWidget(desc)
|
|
198
|
+
|
|
199
|
+
# Settings group
|
|
200
|
+
settings_group = QGroupBox("Settings")
|
|
201
|
+
settings_layout = QHBoxLayout()
|
|
202
|
+
|
|
203
|
+
# Model selection
|
|
204
|
+
settings_layout.addWidget(QLabel("Model:"))
|
|
205
|
+
self.model_combo = QComboBox()
|
|
206
|
+
for key, label in self.MODELS.items():
|
|
207
|
+
self.model_combo.addItem(label, key)
|
|
208
|
+
self.model_combo.setCurrentIndex(1) # Default to "base"
|
|
209
|
+
settings_layout.addWidget(self.model_combo)
|
|
210
|
+
|
|
211
|
+
# Language selection
|
|
212
|
+
settings_layout.addWidget(QLabel("Language:"))
|
|
213
|
+
self.language_combo = QComboBox()
|
|
214
|
+
for key, label in self.LANGUAGES.items():
|
|
215
|
+
self.language_combo.addItem(label, key)
|
|
216
|
+
settings_layout.addWidget(self.language_combo)
|
|
217
|
+
|
|
218
|
+
settings_group.setLayout(settings_layout)
|
|
219
|
+
layout.addWidget(settings_group)
|
|
220
|
+
|
|
221
|
+
# Recording controls
|
|
222
|
+
controls_layout = QHBoxLayout()
|
|
223
|
+
|
|
224
|
+
self.record_btn = QPushButton("đī¸ Start Recording")
|
|
225
|
+
self.record_btn.setStyleSheet("""
|
|
226
|
+
QPushButton {
|
|
227
|
+
background-color: #2196F3;
|
|
228
|
+
color: white;
|
|
229
|
+
border: none;
|
|
230
|
+
padding: 10px 20px;
|
|
231
|
+
font-size: 14px;
|
|
232
|
+
border-radius: 5px;
|
|
233
|
+
}
|
|
234
|
+
QPushButton:hover {
|
|
235
|
+
background-color: #1976D2;
|
|
236
|
+
}
|
|
237
|
+
QPushButton:pressed {
|
|
238
|
+
background-color: #0D47A1;
|
|
239
|
+
}
|
|
240
|
+
QPushButton:disabled {
|
|
241
|
+
background-color: #BDBDBD;
|
|
242
|
+
}
|
|
243
|
+
""")
|
|
244
|
+
self.record_btn.clicked.connect(self.toggle_recording)
|
|
245
|
+
controls_layout.addWidget(self.record_btn)
|
|
246
|
+
|
|
247
|
+
self.copy_btn = QPushButton("đ Copy to Clipboard")
|
|
248
|
+
self.copy_btn.setEnabled(False)
|
|
249
|
+
self.copy_btn.clicked.connect(self.copy_to_clipboard)
|
|
250
|
+
controls_layout.addWidget(self.copy_btn)
|
|
251
|
+
|
|
252
|
+
self.clear_btn = QPushButton("đī¸ Clear")
|
|
253
|
+
self.clear_btn.clicked.connect(self.clear_text)
|
|
254
|
+
controls_layout.addWidget(self.clear_btn)
|
|
255
|
+
|
|
256
|
+
layout.addLayout(controls_layout)
|
|
257
|
+
|
|
258
|
+
# Progress bar
|
|
259
|
+
self.progress_bar = QProgressBar()
|
|
260
|
+
self.progress_bar.setVisible(False)
|
|
261
|
+
self.progress_bar.setTextVisible(False)
|
|
262
|
+
layout.addWidget(self.progress_bar)
|
|
263
|
+
|
|
264
|
+
# Status label
|
|
265
|
+
self.status_label = QLabel("Ready")
|
|
266
|
+
self.status_label.setStyleSheet("color: #666; font-style: italic;")
|
|
267
|
+
layout.addWidget(self.status_label)
|
|
268
|
+
|
|
269
|
+
# Transcription output
|
|
270
|
+
output_group = QGroupBox("Transcription")
|
|
271
|
+
output_layout = QVBoxLayout()
|
|
272
|
+
|
|
273
|
+
self.transcription_text = QTextEdit()
|
|
274
|
+
self.transcription_text.setPlaceholderText("Transcribed text will appear here...")
|
|
275
|
+
self.transcription_text.setMinimumHeight(200)
|
|
276
|
+
output_layout.addWidget(self.transcription_text)
|
|
277
|
+
|
|
278
|
+
output_group.setLayout(output_layout)
|
|
279
|
+
layout.addWidget(output_group)
|
|
280
|
+
|
|
281
|
+
# Help text
|
|
282
|
+
help_text = QLabel(
|
|
283
|
+
"đĄ Tip: Use 'base' model for quick transcription, 'medium' or 'large' for best quality. "
|
|
284
|
+
"First use downloads the model (~1-10GB depending on size)."
|
|
285
|
+
)
|
|
286
|
+
help_text.setWordWrap(True)
|
|
287
|
+
help_text.setStyleSheet("color: #666; font-size: 10px; padding: 5px;")
|
|
288
|
+
layout.addWidget(help_text)
|
|
289
|
+
|
|
290
|
+
# Keyboard shortcuts info
|
|
291
|
+
shortcuts_text = QLabel(
|
|
292
|
+
"â¨ī¸ Shortcuts: F9 = Start/Stop Recording | Esc = Cancel | Ctrl+C = Copy"
|
|
293
|
+
)
|
|
294
|
+
shortcuts_text.setWordWrap(True)
|
|
295
|
+
shortcuts_text.setStyleSheet(
|
|
296
|
+
"color: #1976D2; font-size: 10px; font-weight: bold; "
|
|
297
|
+
"padding: 8px; background-color: #E3F2FD; border-radius: 3px; margin-top: 5px;"
|
|
298
|
+
)
|
|
299
|
+
layout.addWidget(shortcuts_text)
|
|
300
|
+
|
|
301
|
+
def setup_shortcuts(self):
|
|
302
|
+
"""Setup keyboard shortcuts"""
|
|
303
|
+
# F9 - Start/Stop recording
|
|
304
|
+
self.shortcut_record = QShortcut(QKeySequence("F9"), self)
|
|
305
|
+
self.shortcut_record.activated.connect(self.toggle_recording)
|
|
306
|
+
|
|
307
|
+
# Esc - Cancel recording
|
|
308
|
+
self.shortcut_cancel = QShortcut(QKeySequence(Qt.Key.Key_Escape), self)
|
|
309
|
+
self.shortcut_cancel.activated.connect(self.cancel_recording)
|
|
310
|
+
|
|
311
|
+
# Ctrl+C - Copy to clipboard
|
|
312
|
+
self.shortcut_copy = QShortcut(QKeySequence("Ctrl+C"), self)
|
|
313
|
+
self.shortcut_copy.activated.connect(self.copy_to_clipboard)
|
|
314
|
+
|
|
315
|
+
def cancel_recording(self):
|
|
316
|
+
"""Cancel ongoing recording"""
|
|
317
|
+
if self.recording_thread and self.recording_thread.is_recording:
|
|
318
|
+
self.stop_recording()
|
|
319
|
+
self.status_label.setText("â ī¸ Recording cancelled")
|
|
320
|
+
self.status_label.setStyleSheet("color: #FF9800;")
|
|
321
|
+
|
|
322
|
+
def toggle_recording(self):
|
|
323
|
+
"""Start or stop recording"""
|
|
324
|
+
if self.recording_thread and self.recording_thread.is_recording:
|
|
325
|
+
# Stop recording
|
|
326
|
+
self.stop_recording()
|
|
327
|
+
else:
|
|
328
|
+
# Start recording
|
|
329
|
+
self.start_recording()
|
|
330
|
+
|
|
331
|
+
def start_recording(self):
|
|
332
|
+
"""Start recording audio"""
|
|
333
|
+
self.status_label.setText("đ´ Recording... (max 30 seconds)")
|
|
334
|
+
self.status_label.setStyleSheet("color: #D32F2F; font-weight: bold;")
|
|
335
|
+
self.record_btn.setText("âšī¸ Stop Recording")
|
|
336
|
+
self.record_btn.setStyleSheet("""
|
|
337
|
+
QPushButton {
|
|
338
|
+
background-color: #D32F2F;
|
|
339
|
+
color: white;
|
|
340
|
+
border: none;
|
|
341
|
+
padding: 10px 20px;
|
|
342
|
+
font-size: 14px;
|
|
343
|
+
border-radius: 5px;
|
|
344
|
+
}
|
|
345
|
+
QPushButton:hover {
|
|
346
|
+
background-color: #C62828;
|
|
347
|
+
}
|
|
348
|
+
""")
|
|
349
|
+
|
|
350
|
+
# Disable controls
|
|
351
|
+
self.model_combo.setEnabled(False)
|
|
352
|
+
self.language_combo.setEnabled(False)
|
|
353
|
+
|
|
354
|
+
# Start recording thread
|
|
355
|
+
self.recording_thread = RecordingThread()
|
|
356
|
+
self.recording_thread.finished.connect(self.on_recording_finished)
|
|
357
|
+
self.recording_thread.error.connect(self.on_recording_error)
|
|
358
|
+
self.recording_thread.start()
|
|
359
|
+
|
|
360
|
+
def stop_recording(self):
|
|
361
|
+
"""Stop recording audio"""
|
|
362
|
+
if self.recording_thread:
|
|
363
|
+
self.recording_thread.stop()
|
|
364
|
+
self.status_label.setText("Processing...")
|
|
365
|
+
self.status_label.setStyleSheet("color: #FF9800; font-weight: bold;")
|
|
366
|
+
|
|
367
|
+
def on_recording_finished(self, audio_path):
|
|
368
|
+
"""Handle recording completion"""
|
|
369
|
+
# Reset button
|
|
370
|
+
self.record_btn.setText("đī¸ Start Recording")
|
|
371
|
+
self.record_btn.setStyleSheet("""
|
|
372
|
+
QPushButton {
|
|
373
|
+
background-color: #2196F3;
|
|
374
|
+
color: white;
|
|
375
|
+
border: none;
|
|
376
|
+
padding: 10px 20px;
|
|
377
|
+
font-size: 14px;
|
|
378
|
+
border-radius: 5px;
|
|
379
|
+
}
|
|
380
|
+
QPushButton:hover {
|
|
381
|
+
background-color: #1976D2;
|
|
382
|
+
}
|
|
383
|
+
""")
|
|
384
|
+
|
|
385
|
+
# Start transcription
|
|
386
|
+
self.transcribe_audio(audio_path)
|
|
387
|
+
|
|
388
|
+
def on_recording_error(self, error_msg):
|
|
389
|
+
"""Handle recording error"""
|
|
390
|
+
self.status_label.setText(f"â Recording error: {error_msg}")
|
|
391
|
+
self.status_label.setStyleSheet("color: #D32F2F;")
|
|
392
|
+
self.record_btn.setEnabled(True)
|
|
393
|
+
self.model_combo.setEnabled(True)
|
|
394
|
+
self.language_combo.setEnabled(True)
|
|
395
|
+
|
|
396
|
+
def transcribe_audio(self, audio_path):
|
|
397
|
+
"""Transcribe recorded audio"""
|
|
398
|
+
model_name = self.model_combo.currentData()
|
|
399
|
+
language = self.language_combo.currentData()
|
|
400
|
+
if language == "auto":
|
|
401
|
+
language = None
|
|
402
|
+
|
|
403
|
+
# Show progress
|
|
404
|
+
self.progress_bar.setVisible(True)
|
|
405
|
+
self.progress_bar.setRange(0, 0) # Indeterminate
|
|
406
|
+
self.record_btn.setEnabled(False)
|
|
407
|
+
|
|
408
|
+
# Start transcription thread
|
|
409
|
+
self.transcription_thread = TranscriptionThread(audio_path, model_name, language)
|
|
410
|
+
self.transcription_thread.finished.connect(self.on_transcription_finished)
|
|
411
|
+
self.transcription_thread.error.connect(self.on_transcription_error)
|
|
412
|
+
self.transcription_thread.progress.connect(self.on_transcription_progress)
|
|
413
|
+
self.transcription_thread.start()
|
|
414
|
+
|
|
415
|
+
def on_transcription_progress(self, message):
|
|
416
|
+
"""Update progress message"""
|
|
417
|
+
self.status_label.setText(message)
|
|
418
|
+
|
|
419
|
+
def on_transcription_finished(self, text):
|
|
420
|
+
"""Handle transcription completion"""
|
|
421
|
+
self.progress_bar.setVisible(False)
|
|
422
|
+
self.status_label.setText("â
Transcription complete")
|
|
423
|
+
self.status_label.setStyleSheet("color: #388E3C; font-weight: bold;")
|
|
424
|
+
|
|
425
|
+
# Add text to output
|
|
426
|
+
current_text = self.transcription_text.toPlainText()
|
|
427
|
+
if current_text:
|
|
428
|
+
self.transcription_text.setPlainText(current_text + "\n\n" + text)
|
|
429
|
+
else:
|
|
430
|
+
self.transcription_text.setPlainText(text)
|
|
431
|
+
|
|
432
|
+
# Re-enable controls
|
|
433
|
+
self.record_btn.setEnabled(True)
|
|
434
|
+
self.model_combo.setEnabled(True)
|
|
435
|
+
self.language_combo.setEnabled(True)
|
|
436
|
+
self.copy_btn.setEnabled(True)
|
|
437
|
+
|
|
438
|
+
def on_transcription_error(self, error_msg):
|
|
439
|
+
"""Handle transcription error"""
|
|
440
|
+
self.progress_bar.setVisible(False)
|
|
441
|
+
self.status_label.setText(f"â Transcription error: {error_msg}")
|
|
442
|
+
self.status_label.setStyleSheet("color: #D32F2F;")
|
|
443
|
+
|
|
444
|
+
QMessageBox.critical(self, "Error", f"Transcription failed:\n{error_msg}")
|
|
445
|
+
|
|
446
|
+
self.record_btn.setEnabled(True)
|
|
447
|
+
self.model_combo.setEnabled(True)
|
|
448
|
+
self.language_combo.setEnabled(True)
|
|
449
|
+
|
|
450
|
+
def copy_to_clipboard(self):
|
|
451
|
+
"""Copy transcription to clipboard"""
|
|
452
|
+
from PyQt6.QtWidgets import QApplication
|
|
453
|
+
text = self.transcription_text.toPlainText()
|
|
454
|
+
if text:
|
|
455
|
+
QApplication.clipboard().setText(text)
|
|
456
|
+
self.status_label.setText("â
Copied to clipboard")
|
|
457
|
+
self.status_label.setStyleSheet("color: #388E3C;")
|
|
458
|
+
|
|
459
|
+
def clear_text(self):
|
|
460
|
+
"""Clear transcription text"""
|
|
461
|
+
self.transcription_text.clear()
|
|
462
|
+
self.copy_btn.setEnabled(False)
|
|
463
|
+
self.status_label.setText("Ready")
|
|
464
|
+
self.status_label.setStyleSheet("color: #666; font-style: italic;")
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
# Standalone test application
|
|
468
|
+
if __name__ == "__main__":
|
|
469
|
+
import sys
|
|
470
|
+
from PyQt6.QtWidgets import QApplication
|
|
471
|
+
|
|
472
|
+
app = QApplication(sys.argv)
|
|
473
|
+
window = VoiceDictationWidget()
|
|
474
|
+
window.setWindowTitle("Voice Dictation - Supervertaler")
|
|
475
|
+
window.resize(600, 700)
|
|
476
|
+
window.show()
|
|
477
|
+
sys.exit(app.exec())
|