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,249 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lightweight Voice Dictation for Supervertaler
|
|
3
|
+
Minimal version for integration into target editors
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# Note: Heavy imports (whisper, sounddevice, numpy) are loaded lazily in run()
|
|
7
|
+
# to avoid slow startup. These libraries add 5+ seconds of import time.
|
|
8
|
+
import tempfile
|
|
9
|
+
import wave
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from PyQt6.QtCore import QThread, pyqtSignal
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def ensure_ffmpeg_available():
|
|
17
|
+
"""
|
|
18
|
+
Ensure FFmpeg is available for Whisper
|
|
19
|
+
Returns True if FFmpeg is found, False otherwise
|
|
20
|
+
"""
|
|
21
|
+
import shutil
|
|
22
|
+
|
|
23
|
+
# Check if ffmpeg is already in system PATH
|
|
24
|
+
if shutil.which('ffmpeg'):
|
|
25
|
+
return True
|
|
26
|
+
|
|
27
|
+
# Check for bundled ffmpeg (for .exe distributions)
|
|
28
|
+
if getattr(sys, 'frozen', False):
|
|
29
|
+
# Running as compiled executable
|
|
30
|
+
bundle_dir = Path(sys._MEIPASS)
|
|
31
|
+
else:
|
|
32
|
+
# Running as script
|
|
33
|
+
bundle_dir = Path(__file__).parent.parent
|
|
34
|
+
|
|
35
|
+
bundled_ffmpeg = bundle_dir / 'binaries' / 'ffmpeg.exe'
|
|
36
|
+
if bundled_ffmpeg.exists():
|
|
37
|
+
# Add bundled ffmpeg directory to PATH
|
|
38
|
+
os.environ['PATH'] = str(bundled_ffmpeg.parent) + os.pathsep + os.environ['PATH']
|
|
39
|
+
return True
|
|
40
|
+
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class QuickDictationThread(QThread):
|
|
45
|
+
"""
|
|
46
|
+
Quick voice dictation thread - records and transcribes in one go
|
|
47
|
+
Minimal UI, fast operation
|
|
48
|
+
"""
|
|
49
|
+
transcription_ready = pyqtSignal(str) # Emits transcribed text
|
|
50
|
+
status_update = pyqtSignal(str) # Status messages
|
|
51
|
+
error_occurred = pyqtSignal(str) # Errors
|
|
52
|
+
model_loading_started = pyqtSignal(str) # Model name being loaded/downloaded
|
|
53
|
+
model_loading_finished = pyqtSignal() # Model loaded successfully
|
|
54
|
+
|
|
55
|
+
def __init__(self, model_name="base", language="auto", duration=10, use_api: bool = False, api_key: str | None = None):
|
|
56
|
+
super().__init__()
|
|
57
|
+
self.model_name = model_name
|
|
58
|
+
self.language = None if language == "auto" else language
|
|
59
|
+
self.duration = duration # Max recording duration
|
|
60
|
+
self.use_api = use_api
|
|
61
|
+
self.api_key = api_key
|
|
62
|
+
self.sample_rate = 16000
|
|
63
|
+
self.is_recording = False
|
|
64
|
+
self.stop_requested = False
|
|
65
|
+
self.recording_stream = None
|
|
66
|
+
|
|
67
|
+
def stop_recording(self):
|
|
68
|
+
"""Stop recording early (called from main thread)"""
|
|
69
|
+
self.stop_requested = True
|
|
70
|
+
|
|
71
|
+
def run(self):
|
|
72
|
+
"""Record and transcribe audio"""
|
|
73
|
+
try:
|
|
74
|
+
# Lazy import heavy libraries to avoid slow startup
|
|
75
|
+
import sounddevice as sd
|
|
76
|
+
import numpy as np
|
|
77
|
+
|
|
78
|
+
# Local Whisper needs FFmpeg; API mode does not.
|
|
79
|
+
if not self.use_api:
|
|
80
|
+
if not ensure_ffmpeg_available():
|
|
81
|
+
self.error_occurred.emit(
|
|
82
|
+
"FFmpeg not found. Local Whisper requires FFmpeg.\n\n"
|
|
83
|
+
"Option A (recommended): Switch to 'OpenAI Whisper API' in Settings → Supervoice.\n\n"
|
|
84
|
+
"Option B: Install FFmpeg (PowerShell as Admin):\n"
|
|
85
|
+
"winget install FFmpeg (or) choco install ffmpeg"
|
|
86
|
+
)
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
# Step 1: Record audio
|
|
90
|
+
self.status_update.emit("🔴 Recording... (Press F9 or click Stop to finish)")
|
|
91
|
+
self.is_recording = True
|
|
92
|
+
self.stop_requested = False
|
|
93
|
+
|
|
94
|
+
# Start recording
|
|
95
|
+
recording = sd.rec(
|
|
96
|
+
int(self.duration * self.sample_rate),
|
|
97
|
+
samplerate=self.sample_rate,
|
|
98
|
+
channels=1,
|
|
99
|
+
dtype='float32'
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Wait for recording to complete OR manual stop
|
|
103
|
+
import time
|
|
104
|
+
elapsed = 0
|
|
105
|
+
check_interval = 0.1 # Check every 100ms
|
|
106
|
+
while elapsed < self.duration and not self.stop_requested:
|
|
107
|
+
time.sleep(check_interval)
|
|
108
|
+
elapsed += check_interval
|
|
109
|
+
|
|
110
|
+
# Stop recording
|
|
111
|
+
sd.stop()
|
|
112
|
+
self.is_recording = False
|
|
113
|
+
self.status_update.emit(f"🛑 Recording stopped ({elapsed:.1f}s recorded)")
|
|
114
|
+
|
|
115
|
+
# Calculate actual recorded samples
|
|
116
|
+
actual_samples = int(min(elapsed, self.duration) * self.sample_rate)
|
|
117
|
+
recording = recording[:actual_samples] # Trim to actual length
|
|
118
|
+
self.status_update.emit(f"📊 Processing {actual_samples} audio samples...")
|
|
119
|
+
|
|
120
|
+
# Convert to int16
|
|
121
|
+
audio_data = np.int16(recording * 32767)
|
|
122
|
+
|
|
123
|
+
# Save to temp WAV
|
|
124
|
+
temp_dir = tempfile.gettempdir()
|
|
125
|
+
temp_path = os.path.join(temp_dir, f"sv_dictation_{os.getpid()}.wav")
|
|
126
|
+
self.status_update.emit(f"💾 Saving audio to {temp_path}")
|
|
127
|
+
|
|
128
|
+
with wave.open(temp_path, 'wb') as wf:
|
|
129
|
+
wf.setnchannels(1)
|
|
130
|
+
wf.setsampwidth(2)
|
|
131
|
+
wf.setframerate(self.sample_rate)
|
|
132
|
+
wf.writeframes(audio_data.tobytes())
|
|
133
|
+
|
|
134
|
+
self.status_update.emit(f"✓ Audio saved ({len(audio_data)} bytes)")
|
|
135
|
+
|
|
136
|
+
# Step 2: Transcribe
|
|
137
|
+
self.status_update.emit("⏳ Transcribing...")
|
|
138
|
+
|
|
139
|
+
if self.use_api:
|
|
140
|
+
if not self.api_key:
|
|
141
|
+
self.error_occurred.emit(
|
|
142
|
+
"OpenAI API key missing.\n\n"
|
|
143
|
+
"Set your OpenAI API key in Settings → AI Settings, or switch to Local Whisper (offline)."
|
|
144
|
+
)
|
|
145
|
+
try:
|
|
146
|
+
Path(temp_path).unlink()
|
|
147
|
+
except:
|
|
148
|
+
pass
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
self.status_update.emit("🎤 Using OpenAI Whisper API (fast & accurate)")
|
|
152
|
+
text = self._transcribe_with_api(temp_path)
|
|
153
|
+
else:
|
|
154
|
+
text = self._transcribe_with_local(temp_path)
|
|
155
|
+
|
|
156
|
+
# Clean up temp file
|
|
157
|
+
try:
|
|
158
|
+
Path(temp_path).unlink()
|
|
159
|
+
except:
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
# Emit result
|
|
163
|
+
if text:
|
|
164
|
+
self.transcription_ready.emit(text)
|
|
165
|
+
self.status_update.emit("✅ Done")
|
|
166
|
+
else:
|
|
167
|
+
self.error_occurred.emit("No speech detected")
|
|
168
|
+
|
|
169
|
+
except Exception as e:
|
|
170
|
+
self.is_recording = False
|
|
171
|
+
import traceback
|
|
172
|
+
error_details = traceback.format_exc()
|
|
173
|
+
self.error_occurred.emit(f"Error: {str(e)}\n\nTraceback:\n{error_details}")
|
|
174
|
+
|
|
175
|
+
def _transcribe_with_api(self, audio_path: str) -> str:
|
|
176
|
+
"""Transcribe using OpenAI Whisper API (no local Whisper required)."""
|
|
177
|
+
try:
|
|
178
|
+
from openai import OpenAI
|
|
179
|
+
|
|
180
|
+
client = OpenAI(api_key=self.api_key)
|
|
181
|
+
with open(audio_path, "rb") as audio_file:
|
|
182
|
+
kwargs = {"model": "whisper-1", "file": audio_file}
|
|
183
|
+
if self.language:
|
|
184
|
+
kwargs["language"] = self.language
|
|
185
|
+
response = client.audio.transcriptions.create(**kwargs)
|
|
186
|
+
|
|
187
|
+
return (response.text or "").strip()
|
|
188
|
+
except Exception as e:
|
|
189
|
+
self.error_occurred.emit(f"OpenAI API error: {e}")
|
|
190
|
+
return ""
|
|
191
|
+
|
|
192
|
+
def _transcribe_with_local(self, audio_path: str) -> str:
|
|
193
|
+
"""Transcribe using Local Whisper (requires optional dependency)."""
|
|
194
|
+
try:
|
|
195
|
+
# Lazy import whisper to avoid slow startup (PyTorch takes seconds to import)
|
|
196
|
+
try:
|
|
197
|
+
import whisper
|
|
198
|
+
except ImportError:
|
|
199
|
+
self.error_occurred.emit(
|
|
200
|
+
"Local Whisper is not installed.\n\n"
|
|
201
|
+
"Install it with:\n"
|
|
202
|
+
" pip install supervertaler[local-whisper]\n\n"
|
|
203
|
+
"Or switch to 'OpenAI Whisper API' in Settings → Supervoice."
|
|
204
|
+
)
|
|
205
|
+
return ""
|
|
206
|
+
|
|
207
|
+
# Check if model needs to be downloaded
|
|
208
|
+
cache_dir = os.path.expanduser("~/.cache/whisper")
|
|
209
|
+
if os.name == 'nt': # Windows
|
|
210
|
+
cache_dir = os.path.join(os.environ.get('USERPROFILE', ''), '.cache', 'whisper')
|
|
211
|
+
|
|
212
|
+
model_files = [
|
|
213
|
+
f"{self.model_name}.pt",
|
|
214
|
+
f"{self.model_name}.en.pt",
|
|
215
|
+
f"{self.model_name}-v3.pt" # For large model
|
|
216
|
+
]
|
|
217
|
+
model_exists = any(os.path.exists(os.path.join(cache_dir, f)) for f in model_files)
|
|
218
|
+
|
|
219
|
+
# Load model (cached after first use, may download on first use)
|
|
220
|
+
self.model_loading_started.emit(self.model_name)
|
|
221
|
+
if not model_exists:
|
|
222
|
+
self.status_update.emit(f"📥 Downloading {self.model_name} model... (this may take several minutes)")
|
|
223
|
+
else:
|
|
224
|
+
self.status_update.emit(f"⏳ Loading {self.model_name} model...")
|
|
225
|
+
|
|
226
|
+
model = whisper.load_model(self.model_name)
|
|
227
|
+
self.model_loading_finished.emit()
|
|
228
|
+
|
|
229
|
+
# Transcribe
|
|
230
|
+
self.status_update.emit("⏳ Transcribing audio...")
|
|
231
|
+
if self.language:
|
|
232
|
+
result = model.transcribe(audio_path, language=self.language)
|
|
233
|
+
else:
|
|
234
|
+
result = model.transcribe(audio_path)
|
|
235
|
+
|
|
236
|
+
return (result.get("text") or "").strip()
|
|
237
|
+
except Exception as e:
|
|
238
|
+
self.error_occurred.emit(f"Local transcription error: {e}")
|
|
239
|
+
return ""
|
|
240
|
+
|
|
241
|
+
def stop(self):
|
|
242
|
+
"""Stop recording"""
|
|
243
|
+
if self.is_recording:
|
|
244
|
+
self.is_recording = False
|
|
245
|
+
try:
|
|
246
|
+
import sounddevice as sd
|
|
247
|
+
sd.stop()
|
|
248
|
+
except:
|
|
249
|
+
pass
|