From d542e6ba14b2e279e0e7886177eac3a0de8bbb0e Mon Sep 17 00:00:00 2001 From: erew123 <35898566+erew123@users.noreply.github.com> Date: Tue, 16 Jul 2024 16:21:40 +0100 Subject: [PATCH] Clipboard to TTS A very rough bit of code for Windows to generate TTS from any text sent to the WINDOWS clipboard with Ctrl+C --- system/cliptotts/clipboard-to-tts.py | 167 +++++++++++++++++++++++++++ system/cliptotts/icon.png | Bin 0 -> 11162 bytes system/cliptotts/instructions.txt | 11 ++ 3 files changed, 178 insertions(+) create mode 100644 system/cliptotts/clipboard-to-tts.py create mode 100644 system/cliptotts/icon.png create mode 100644 system/cliptotts/instructions.txt diff --git a/system/cliptotts/clipboard-to-tts.py b/system/cliptotts/clipboard-to-tts.py new file mode 100644 index 0000000..4ea967b --- /dev/null +++ b/system/cliptotts/clipboard-to-tts.py @@ -0,0 +1,167 @@ +import sys +import time +import signal +import pyperclip +import requests +from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMenu, QWidget, QVBoxLayout, QLabel, QLineEdit, QPushButton +from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer +from PyQt5.QtGui import QIcon + +class ClipboardMonitor(QThread): + text_changed = pyqtSignal(str) + + def __init__(self): + super().__init__() + self.last_text = pyperclip.paste() + self.running = True + + def run(self): + while self.running: + current_text = pyperclip.paste() + if current_text != self.last_text: + self.text_changed.emit(current_text) + self.last_text = current_text + time.sleep(0.5) + + def stop(self): + self.running = False + +class TTSWorker(QThread): + def __init__(self, api_url, params): + super().__init__() + self.api_url = api_url + self.params = params + self.text_queue = [] + + def add_text(self, text): + self.text_queue.append(text) + + def run(self): + while True: + if self.text_queue: + text = self.text_queue.pop(0) + self.send_to_tts(text) + else: + time.sleep(0.1) + + def send_to_tts(self, text): + params = self.params.copy() + params["text_input"] = text + try: + response = requests.post(self.api_url, data=params) + if response.status_code != 200: + print(f"TTS generation failed with status code: {response.status_code}") + except requests.RequestException as e: + print(f"Error sending request to TTS API: {e}") + +class SettingsWindow(QWidget): + def __init__(self, tts_worker): + super().__init__() + self.tts_worker = tts_worker + self.init_ui() + + def init_ui(self): + layout = QVBoxLayout() + self.setLayout(layout) + + layout.addWidget(QLabel("TTS 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) + + 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) + + 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() + +class TTSApp(QSystemTrayIcon): + def __init__(self, app): + super().__init__(parent=app) + self.app = app + 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" + } + ) + self.tts_worker.start() + + self.clipboard_monitor = ClipboardMonitor() + self.clipboard_monitor.text_changed.connect(self.on_text_changed) + + self.setIcon(QIcon("icon.png")) + self.setVisible(True) + + menu = QMenu() + start_action = menu.addAction("Start Monitoring") + start_action.triggered.connect(self.start_monitoring) + stop_action = menu.addAction("Stop Monitoring") + stop_action.triggered.connect(self.stop_monitoring) + settings_action = menu.addAction("Settings") + settings_action.triggered.connect(self.show_settings) + exit_action = menu.addAction("Exit") + exit_action.triggered.connect(self.exit_app) + self.setContextMenu(menu) + + self.settings_window = SettingsWindow(self.tts_worker) + + def on_text_changed(self, text): + self.tts_worker.add_text(text) + + def start_monitoring(self): + self.clipboard_monitor.start() + self.showMessage("TTS App", "Clipboard monitoring started") + + def stop_monitoring(self): + self.clipboard_monitor.stop() + self.showMessage("TTS App", "Clipboard monitoring stopped") + + def show_settings(self): + self.settings_window.show() + + def exit_app(self): + self.clipboard_monitor.stop() + self.tts_worker.quit() + QApplication.instance().quit() + +def signal_handler(signal, frame): + QApplication.instance().quit() + +if __name__ == "__main__": + print("[AllTalk TTS] AllTalk Clipboard to TTS is starting...") + print("[AllTalk TTS] Press Ctrl+C to exit") + signal.signal(signal.SIGINT, signal_handler) + + app = QApplication(sys.argv) + + if not QSystemTrayIcon.isSystemTrayAvailable(): + print("System tray is not available on this system.") + sys.exit(1) + + tts_app = TTSApp(app) + + # This allows for Ctrl+C to work + timer = QTimer() + timer.start(500) + timer.timeout.connect(lambda: None) + + sys.exit(app.exec_()) diff --git a/system/cliptotts/icon.png b/system/cliptotts/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..adcc4c510d53698aac388cb83c6f540927fc567e GIT binary patch literal 11162 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 literal 0 HcmV?d00001 diff --git a/system/cliptotts/instructions.txt b/system/cliptotts/instructions.txt new file mode 100644 index 0000000..c89aaef --- /dev/null +++ b/system/cliptotts/instructions.txt @@ -0,0 +1,11 @@ +This is a Windows only clipboard to TTS extension. Its a very rough bit of code with no logic or error handling built in. + +When run it will create a system tray icon that you can Start/Stop clipboard monitoring for the text stored in the clipboard changing. When the text changes, it will be sent to AllTalk for TTS generation. You can specify the voice to use in the System Tray icon Settings area. + +In the main folder, run "start_environment.bat" + +Go to the "cliptotts" folder and "python clipboard-to-tts.py" which should start the application. + +Start monitoring of the clipboard on the icon in the system tray. + +When you Ctrl+C text to the clipboard, it will be sent over for TTS generation and it will play back at the AllTalk console. \ No newline at end of file