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.

Files changed (85) hide show
  1. Supervertaler.py +47886 -0
  2. modules/__init__.py +10 -0
  3. modules/ai_actions.py +964 -0
  4. modules/ai_attachment_manager.py +343 -0
  5. modules/ai_file_viewer_dialog.py +210 -0
  6. modules/autofingers_engine.py +466 -0
  7. modules/cafetran_docx_handler.py +379 -0
  8. modules/config_manager.py +469 -0
  9. modules/database_manager.py +1878 -0
  10. modules/database_migrations.py +417 -0
  11. modules/dejavurtf_handler.py +779 -0
  12. modules/document_analyzer.py +427 -0
  13. modules/docx_handler.py +689 -0
  14. modules/encoding_repair.py +319 -0
  15. modules/encoding_repair_Qt.py +393 -0
  16. modules/encoding_repair_ui.py +481 -0
  17. modules/feature_manager.py +350 -0
  18. modules/figure_context_manager.py +340 -0
  19. modules/file_dialog_helper.py +148 -0
  20. modules/find_replace.py +164 -0
  21. modules/find_replace_qt.py +457 -0
  22. modules/glossary_manager.py +433 -0
  23. modules/image_extractor.py +188 -0
  24. modules/keyboard_shortcuts_widget.py +571 -0
  25. modules/llm_clients.py +1211 -0
  26. modules/llm_leaderboard.py +737 -0
  27. modules/llm_superbench_ui.py +1401 -0
  28. modules/local_llm_setup.py +1104 -0
  29. modules/model_update_dialog.py +381 -0
  30. modules/model_version_checker.py +373 -0
  31. modules/mqxliff_handler.py +638 -0
  32. modules/non_translatables_manager.py +743 -0
  33. modules/pdf_rescue_Qt.py +1822 -0
  34. modules/pdf_rescue_tkinter.py +909 -0
  35. modules/phrase_docx_handler.py +516 -0
  36. modules/project_home_panel.py +209 -0
  37. modules/prompt_assistant.py +357 -0
  38. modules/prompt_library.py +689 -0
  39. modules/prompt_library_migration.py +447 -0
  40. modules/quick_access_sidebar.py +282 -0
  41. modules/ribbon_widget.py +597 -0
  42. modules/sdlppx_handler.py +874 -0
  43. modules/setup_wizard.py +353 -0
  44. modules/shortcut_manager.py +932 -0
  45. modules/simple_segmenter.py +128 -0
  46. modules/spellcheck_manager.py +727 -0
  47. modules/statuses.py +207 -0
  48. modules/style_guide_manager.py +315 -0
  49. modules/superbench_ui.py +1319 -0
  50. modules/superbrowser.py +329 -0
  51. modules/supercleaner.py +600 -0
  52. modules/supercleaner_ui.py +444 -0
  53. modules/superdocs.py +19 -0
  54. modules/superdocs_viewer_qt.py +382 -0
  55. modules/superlookup.py +252 -0
  56. modules/tag_cleaner.py +260 -0
  57. modules/tag_manager.py +333 -0
  58. modules/term_extractor.py +270 -0
  59. modules/termbase_entry_editor.py +842 -0
  60. modules/termbase_import_export.py +488 -0
  61. modules/termbase_manager.py +1060 -0
  62. modules/termview_widget.py +1172 -0
  63. modules/theme_manager.py +499 -0
  64. modules/tm_editor_dialog.py +99 -0
  65. modules/tm_manager_qt.py +1280 -0
  66. modules/tm_metadata_manager.py +545 -0
  67. modules/tmx_editor.py +1461 -0
  68. modules/tmx_editor_qt.py +2784 -0
  69. modules/tmx_generator.py +284 -0
  70. modules/tracked_changes.py +900 -0
  71. modules/trados_docx_handler.py +430 -0
  72. modules/translation_memory.py +715 -0
  73. modules/translation_results_panel.py +2134 -0
  74. modules/translation_services.py +282 -0
  75. modules/unified_prompt_library.py +659 -0
  76. modules/unified_prompt_manager_qt.py +3951 -0
  77. modules/voice_commands.py +920 -0
  78. modules/voice_dictation.py +477 -0
  79. modules/voice_dictation_lite.py +249 -0
  80. supervertaler-1.9.153.dist-info/METADATA +896 -0
  81. supervertaler-1.9.153.dist-info/RECORD +85 -0
  82. supervertaler-1.9.153.dist-info/WHEEL +5 -0
  83. supervertaler-1.9.153.dist-info/entry_points.txt +2 -0
  84. supervertaler-1.9.153.dist-info/licenses/LICENSE +21 -0
  85. 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