diff --git a/system/cliptotts/clipboard-to-tts.py b/system/cliptotts/clipboard-to-tts.py index 4ea967b..043fda6 100644 --- a/system/cliptotts/clipboard-to-tts.py +++ b/system/cliptotts/clipboard-to-tts.py @@ -1,12 +1,28 @@ import sys import time import signal +import json +import warnings import pyperclip import requests -from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMenu, QWidget, QVBoxLayout, QLabel, QLineEdit, QPushButton +from PyQt5.QtWidgets import (QApplication, QSystemTrayIcon, QMenu, QWidget, QVBoxLayout, QLabel, + QLineEdit, QPushButton, QMessageBox, QComboBox, QSlider) from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer from PyQt5.QtGui import QIcon +# Suppress the specific warning +warnings.filterwarnings("ignore", category=UserWarning, module="charset_normalizer") + +SETTINGS_FILE = "tts_settings.json" +TTS_SERVER_URL = "http://127.0.0.1:7851" + +LANGUAGES = { + "ar": "Arabic", "zh-cn": "Chinese (Simplified)", "cs": "Czech", "nl": "Dutch", + "en": "English", "fr": "French", "de": "German", "hi": "Hindi (limited support)", + "hu": "Hungarian", "it": "Italian", "ja": "Japanese", "ko": "Korean", + "pl": "Polish", "pt": "Portuguese", "ru": "Russian", "es": "Spanish", "tr": "Turkish" +} + class ClipboardMonitor(QThread): text_changed = pyqtSignal(str) @@ -27,6 +43,8 @@ class ClipboardMonitor(QThread): self.running = False class TTSWorker(QThread): + error_occurred = pyqtSignal(str) + def __init__(self, api_url, params): super().__init__() self.api_url = api_url @@ -47,62 +65,143 @@ class TTSWorker(QThread): def send_to_tts(self, text): params = self.params.copy() params["text_input"] = text + params["language"] = params["language"][:2] # Ensure we only send the 2-letter code try: - response = requests.post(self.api_url, data=params) + response = requests.post(f"{self.api_url}/api/tts-generate", data=params) if response.status_code != 200: - print(f"TTS generation failed with status code: {response.status_code}") + error_message = f"TTS generation failed with status code: {response.status_code}. Response: {response.text}" + print(error_message) + self.error_occurred.emit(error_message) except requests.RequestException as e: - print(f"Error sending request to TTS API: {e}") + error_message = f"Error sending request to TTS API: {e}" + print(error_message) + self.error_occurred.emit(error_message) class SettingsWindow(QWidget): + settings_updated = pyqtSignal(dict) + def __init__(self, tts_worker): super().__init__() self.tts_worker = tts_worker + self.voices = [] self.init_ui() def init_ui(self): layout = QVBoxLayout() self.setLayout(layout) - layout.addWidget(QLabel("TTS API URL:")) + layout.addWidget(QLabel("AllTalk API URL:")) self.api_url_input = QLineEdit(self.tts_worker.api_url) layout.addWidget(self.api_url_input) layout.addWidget(QLabel("Voice:")) - self.voice_input = QLineEdit(self.tts_worker.params["character_voice_gen"]) - layout.addWidget(self.voice_input) + self.voice_combo = QComboBox() + layout.addWidget(self.voice_combo) + + layout.addWidget(QLabel("Language:")) + self.language_combo = QComboBox() + for code, name in LANGUAGES.items(): + self.language_combo.addItem(f"{name} ({code})", code) + current_lang = self.tts_worker.params.get("language", "en") + index = self.language_combo.findData(current_lang) + if index >= 0: + self.language_combo.setCurrentIndex(index) + layout.addWidget(self.language_combo) + + layout.addWidget(QLabel("Text Filtering:")) + self.text_filtering_input = QLineEdit(self.tts_worker.params["text_filtering"]) + layout.addWidget(self.text_filtering_input) + + layout.addWidget(QLabel("Playback Volume:")) + self.volume_slider = QSlider(Qt.Horizontal) + self.volume_slider.setRange(1, 10) + self.volume_slider.setValue(int(float(self.tts_worker.params["autoplay_volume"]) * 10)) + layout.addWidget(self.volume_slider) save_button = QPushButton("Save") save_button.clicked.connect(self.save_settings) layout.addWidget(save_button) - self.setWindowTitle("TTS Settings") - self.setGeometry(300, 300, 300, 150) + refresh_button = QPushButton("Refresh Voices") + refresh_button.clicked.connect(self.refresh_voices) + layout.addWidget(refresh_button) + + self.setWindowTitle("AllTalk Clipboard TTS") + self.setGeometry(300, 300, 300, 400) + + self.refresh_voices() + + def refresh_voices(self): + try: + response = requests.get(f"{self.tts_worker.api_url}/api/voices") + if response.status_code == 200: + data = response.json() + if data["status"] == "success" and "voices" in data: + self.voices = data["voices"] + self.voice_combo.clear() + for voice in self.voices: + self.voice_combo.addItem(voice) + if self.voices: + current_voice = self.tts_worker.params["character_voice_gen"] + if current_voice in self.voices: + self.voice_combo.setCurrentText(current_voice) + else: + self.voice_combo.setCurrentIndex(0) + else: + QMessageBox.warning(self, "Error", "Failed to fetch voices: Unexpected response format") + else: + QMessageBox.warning(self, "Error", f"Failed to fetch voices. Status code: {response.status_code}") + except requests.RequestException as e: + QMessageBox.warning(self, "Error", f"Failed to fetch voices: {str(e)}") def save_settings(self): - self.tts_worker.api_url = self.api_url_input.text() - self.tts_worker.params["character_voice_gen"] = self.voice_input.text() - self.close() + new_settings = { + "api_url": self.api_url_input.text(), + "params": { + "character_voice_gen": self.voice_combo.currentText(), + "language": self.language_combo.currentData(), + "text_filtering": self.text_filtering_input.text(), + "output_file_name": "clipboard_tts", + "output_file_timestamp": "true", + "autoplay": "true", + "autoplay_volume": str(self.volume_slider.value() / 10.0) + } + } + self.settings_updated.emit(new_settings) + self.hide() class TTSApp(QSystemTrayIcon): def __init__(self, app): super().__init__(parent=app) self.app = app + self.load_settings() self.init_ui() - def init_ui(self): - self.tts_worker = TTSWorker( - api_url="http://127.0.0.1:7851/api/tts-generate", - params={ - "text_filtering": "standard", - "character_voice_gen": "female_01.wav", - "language": "en", - "output_file_name": "clipboard_tts", - "output_file_timestamp": "true", - "autoplay": "true", - "autoplay_volume": "0.8" + def load_settings(self): + try: + with open(SETTINGS_FILE, 'r') as f: + self.settings = json.load(f) + except FileNotFoundError: + self.settings = { + "api_url": TTS_SERVER_URL, + "params": { + "text_filtering": "standard", + "character_voice_gen": "", + "language": "en", + "output_file_name": "clipboard_tts", + "output_file_timestamp": "true", + "autoplay": "true", + "autoplay_volume": "0.8" + } } - ) + + def save_settings(self): + with open(SETTINGS_FILE, 'w') as f: + json.dump(self.settings, f) + + def init_ui(self): + self.tts_worker = TTSWorker(self.settings["api_url"], self.settings["params"]) + self.tts_worker.error_occurred.connect(self.show_error_message) self.tts_worker.start() self.clipboard_monitor = ClipboardMonitor() @@ -123,6 +222,7 @@ class TTSApp(QSystemTrayIcon): self.setContextMenu(menu) self.settings_window = SettingsWindow(self.tts_worker) + self.settings_window.settings_updated.connect(self.update_settings) def on_text_changed(self, text): self.tts_worker.add_text(text) @@ -138,23 +238,50 @@ class TTSApp(QSystemTrayIcon): def show_settings(self): self.settings_window.show() + def update_settings(self, new_settings): + self.settings = new_settings + self.tts_worker.api_url = new_settings["api_url"] + self.tts_worker.params = new_settings["params"] + self.save_settings() + self.showMessage("TTS App", "Settings updated successfully") + + def show_error_message(self, message): + QMessageBox.critical(None, "Error", message) + def exit_app(self): self.clipboard_monitor.stop() self.tts_worker.quit() QApplication.instance().quit() +def check_server_ready(url, timeout=10): + start_time = time.time() + while time.time() - start_time < timeout: + try: + response = requests.get(f"{url}/api/ready") + if response.status_code == 200 and response.text == "Ready": + return True + except requests.RequestException: + pass + time.sleep(1) + return False + def signal_handler(signal, frame): + print("[Clipboard to TTS] Exiting...") QApplication.instance().quit() if __name__ == "__main__": - print("[AllTalk TTS] AllTalk Clipboard to TTS is starting...") - print("[AllTalk TTS] Press Ctrl+C to exit") + print("[Clipboard to TTS] AllTalk Clipboard to TTS is starting...") + + if not check_server_ready(TTS_SERVER_URL): + print("[Clipboard to TTS] Error: AllTalk TTS server is not ready. Please start the AllTalk server and try again.") + sys.exit(1) + signal.signal(signal.SIGINT, signal_handler) app = QApplication(sys.argv) if not QSystemTrayIcon.isSystemTrayAvailable(): - print("System tray is not available on this system.") + print("[Clipboard to TTS] System tray is not available on this system.") sys.exit(1) tts_app = TTSApp(app) @@ -164,4 +291,6 @@ if __name__ == "__main__": timer.start(500) timer.timeout.connect(lambda: None) + print("[Clipboard to TTS] AllTalk Clipboard to TTS is running... Check your system tray for the icon.") + print("[Clipboard to TTS] Press Ctrl+C to exit") sys.exit(app.exec_())