v1.0.1 Feature Update and Polish
Full Changelog: [New Features] - Added Native Translation Mode: - Whisper model now fully supports Translating any language to English - Added 'task' and 'language' parameters to Transcriber core - Dual Hotkey Support: - Added separate Global Hotkeys for Transcribe (default F8) and Translate (default F10) - Both hotkeys are fully customizable in Settings - Engine dynamically switches modes based on which key is pressed [UI/UX Improvements] - Settings Window: - Widened Hotkey Input fields (240px) to accommodate long combinations - Added Pretty-Printing for hotkey sequences (e.g. 'ctrl+f9' display as 'Ctrl + F9') - Replaced Country Code dropdown with Full Language Names (99+ languages) - Made Language Dropdown scrollable (max height 300px) to prevent screen overflow - Removed redundant 'Task' selector (replaced by dedicated hotkeys) - System Tray: - Tooltip now displays both Transcribe and Translate hotkeys - Tooltip hotkeys are formatted readably [Core & Performance] - Bootstrapper: - Implemented Smart Incremental Sync - Now checks filesize and content hash before copying files - Drastically reduces startup time for subsequent runs - Preserves user settings.json during updates - Backend: - Fixed HotkeyManager to support dynamic configuration keys - Fixed Language Lock: selecting a language now correctly forces the model to use it - Refactored bridge/main connection for language list handling
This commit is contained in:
74
main.py
74
main.py
@@ -118,13 +118,14 @@ class DownloadWorker(QThread):
|
||||
|
||||
class TranscriptionWorker(QThread):
|
||||
finished = Signal(str)
|
||||
def __init__(self, transcriber, audio_data, is_file=False, parent=None):
|
||||
def __init__(self, transcriber, audio_data, is_file=False, parent=None, task_override=None):
|
||||
super().__init__(parent)
|
||||
self.transcriber = transcriber
|
||||
self.audio_data = audio_data
|
||||
self.is_file = is_file
|
||||
self.task_override = task_override
|
||||
def run(self):
|
||||
text = self.transcriber.transcribe(self.audio_data, is_file=self.is_file)
|
||||
text = self.transcriber.transcribe(self.audio_data, is_file=self.is_file, task=self.task_override)
|
||||
self.finished.emit(text)
|
||||
|
||||
class WhisperApp(QObject):
|
||||
@@ -166,13 +167,18 @@ class WhisperApp(QObject):
|
||||
self.tray.transcribe_file_requested.connect(self.transcribe_file)
|
||||
|
||||
# Init Tooltip
|
||||
hotkey = self.config.get("hotkey")
|
||||
self.tray.setToolTip(f"Whisper Voice - Press {hotkey} to Record")
|
||||
from src.utils.formatters import format_hotkey
|
||||
self.format_hotkey = format_hotkey # Store ref
|
||||
|
||||
hk1 = self.format_hotkey(self.config.get("hotkey"))
|
||||
hk2 = self.format_hotkey(self.config.get("hotkey_translate"))
|
||||
self.tray.setToolTip(f"Whisper Voice\nTranscribe: {hk1}\nTranslate: {hk2}")
|
||||
|
||||
# 3. Logic Components Placeholders
|
||||
self.audio_engine = None
|
||||
self.transcriber = None
|
||||
self.hotkey_manager = None
|
||||
self.hk_transcribe = None
|
||||
self.hk_translate = None
|
||||
self.overlay_root = None
|
||||
|
||||
# 4. Start Loader
|
||||
@@ -266,9 +272,16 @@ class WhisperApp(QObject):
|
||||
self.audio_engine.set_visualizer_callback(self.bridge.update_amplitude)
|
||||
self.audio_engine.set_silence_callback(self.on_silence_detected)
|
||||
self.transcriber = WhisperTranscriber()
|
||||
self.hotkey_manager = HotkeyManager()
|
||||
self.hotkey_manager.triggered.connect(self.toggle_recording)
|
||||
self.hotkey_manager.start()
|
||||
|
||||
# Dual Hotkey Managers
|
||||
self.hk_transcribe = HotkeyManager(config_key="hotkey")
|
||||
self.hk_transcribe.triggered.connect(lambda: self.toggle_recording(task_override="transcribe"))
|
||||
self.hk_transcribe.start()
|
||||
|
||||
self.hk_translate = HotkeyManager(config_key="hotkey_translate")
|
||||
self.hk_translate.triggered.connect(lambda: self.toggle_recording(task_override="translate"))
|
||||
self.hk_translate.start()
|
||||
|
||||
self.bridge.update_status("Ready")
|
||||
|
||||
def run(self):
|
||||
@@ -286,7 +299,8 @@ class WhisperApp(QObject):
|
||||
except: pass
|
||||
self.bridge.stats_worker.stop()
|
||||
|
||||
if self.hotkey_manager: self.hotkey_manager.stop()
|
||||
if self.hk_transcribe: self.hk_transcribe.stop()
|
||||
if self.hk_translate: self.hk_translate.stop()
|
||||
|
||||
# Close all QML windows to ensure bindings stop before Python objects die
|
||||
if self.overlay_root:
|
||||
@@ -361,10 +375,14 @@ class WhisperApp(QObject):
|
||||
print(f"Setting Changed: {key} = {value}")
|
||||
|
||||
# 1. Hotkey Reload
|
||||
if key == "hotkey":
|
||||
if self.hotkey_manager: self.hotkey_manager.reload_hotkey()
|
||||
if key in ["hotkey", "hotkey_translate"]:
|
||||
if self.hk_transcribe: self.hk_transcribe.reload_hotkey()
|
||||
if self.hk_translate: self.hk_translate.reload_hotkey()
|
||||
|
||||
if self.tray:
|
||||
self.tray.setToolTip(f"Whisper Voice - Press {value} to Record")
|
||||
hk1 = self.format_hotkey(self.config.get("hotkey"))
|
||||
hk2 = self.format_hotkey(self.config.get("hotkey_translate"))
|
||||
self.tray.setToolTip(f"Whisper Voice\nTranscribe: {hk1}\nTranslate: {hk2}")
|
||||
|
||||
# 2. AI Model Reload (Heavy)
|
||||
if key in ["model_size", "compute_device", "compute_type"]:
|
||||
@@ -467,6 +485,8 @@ class WhisperApp(QObject):
|
||||
file_path, _ = QFileDialog.getOpenFileName(None, "Select Audio", "", "Audio (*.mp3 *.wav *.flac *.m4a *.ogg)")
|
||||
if file_path:
|
||||
self.bridge.update_status("Thinking...")
|
||||
# Files use the default configured task usually, or we could ask?
|
||||
# Default to config setting for files.
|
||||
self.worker = TranscriptionWorker(self.transcriber, file_path, is_file=True, parent=self)
|
||||
self.worker.finished.connect(self.on_transcription_done)
|
||||
self.worker.start()
|
||||
@@ -474,10 +494,13 @@ class WhisperApp(QObject):
|
||||
@Slot()
|
||||
def on_silence_detected(self):
|
||||
from PySide6.QtCore import QMetaObject, Qt
|
||||
# Silence detection always triggers the task that was active?
|
||||
# Since silence stops recording, it just calls toggle_recording with no arg, using the stored current_task?
|
||||
# Let's ensure toggle_recording handles no arg calls by stopping the CURRENT task.
|
||||
QMetaObject.invokeMethod(self, "toggle_recording", Qt.QueuedConnection)
|
||||
|
||||
@Slot()
|
||||
def toggle_recording(self):
|
||||
@Slot() # Modified to allow lambda override
|
||||
def toggle_recording(self, task_override=None):
|
||||
if not self.audio_engine: return
|
||||
|
||||
# Prevent starting a new recording while we are still transcribing the last one
|
||||
@@ -485,23 +508,36 @@ class WhisperApp(QObject):
|
||||
logging.warning("Ignored toggle request: Transcription in progress.")
|
||||
return
|
||||
|
||||
# Determine which task we are entering
|
||||
if task_override:
|
||||
intended_task = task_override
|
||||
else:
|
||||
intended_task = self.config.get("task")
|
||||
|
||||
if self.audio_engine.recording:
|
||||
# STOP RECORDING
|
||||
self.bridge.update_status("Thinking...")
|
||||
self.bridge.isRecording = False
|
||||
self.bridge.isProcessing = True # Start Processing
|
||||
audio_data = self.audio_engine.stop_recording()
|
||||
self.worker = TranscriptionWorker(self.transcriber, audio_data, parent=self)
|
||||
|
||||
# Use the task that started this session, or the override if provided (though usually override is for starting)
|
||||
final_task = getattr(self, "current_recording_task", self.config.get("task"))
|
||||
|
||||
self.worker = TranscriptionWorker(self.transcriber, audio_data, parent=self, task_override=final_task)
|
||||
self.worker.finished.connect(self.on_transcription_done)
|
||||
self.worker.start()
|
||||
else:
|
||||
self.bridge.update_status("Recording")
|
||||
# START RECORDING
|
||||
self.current_recording_task = intended_task
|
||||
self.bridge.update_status(f"Recording ({intended_task})...")
|
||||
self.bridge.isRecording = True
|
||||
self.audio_engine.start_recording()
|
||||
|
||||
@Slot(bool)
|
||||
def on_ui_toggle_request(self, state):
|
||||
if state != self.audio_engine.recording:
|
||||
self.toggle_recording()
|
||||
self.toggle_recording() # Default behavior for UI clicks
|
||||
|
||||
@Slot(str)
|
||||
def on_transcription_done(self, text: str):
|
||||
@@ -514,8 +550,8 @@ class WhisperApp(QObject):
|
||||
|
||||
@Slot(bool)
|
||||
def on_hotkeys_enabled_toggle(self, state):
|
||||
if self.hotkey_manager:
|
||||
self.hotkey_manager.set_enabled(state)
|
||||
if self.hk_transcribe: self.hk_transcribe.set_enabled(state)
|
||||
if self.hk_translate: self.hk_translate.set_enabled(state)
|
||||
|
||||
@Slot(str)
|
||||
def on_download_requested(self, size):
|
||||
|
||||
Reference in New Issue
Block a user