Update clipboard-to-tts.py
This commit is contained in:
1 parent
c31e1f68b5
commit
b03e08405e
1 file changed
+156
-27
@@ -1,12 +1,28 @@
|
|||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import signal
|
import signal
|
||||||
|
import json
|
||||||
|
import warnings
|
||||||
import pyperclip
|
import pyperclip
|
||||||
import requests
|
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.QtCore import Qt, QThread, pyqtSignal, QTimer
|
||||||
from PyQt5.QtGui import QIcon
|
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):
|
class ClipboardMonitor(QThread):
|
||||||
text_changed = pyqtSignal(str)
|
text_changed = pyqtSignal(str)
|
||||||
|
|
||||||
@@ -27,6 +43,8 @@ class ClipboardMonitor(QThread):
|
|||||||
self.running = False
|
self.running = False
|
||||||
|
|
||||||
class TTSWorker(QThread):
|
class TTSWorker(QThread):
|
||||||
|
error_occurred = pyqtSignal(str)
|
||||||
|
|
||||||
def __init__(self, api_url, params):
|
def __init__(self, api_url, params):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.api_url = api_url
|
self.api_url = api_url
|
||||||
@@ -47,62 +65,143 @@ class TTSWorker(QThread):
|
|||||||
def send_to_tts(self, text):
|
def send_to_tts(self, text):
|
||||||
params = self.params.copy()
|
params = self.params.copy()
|
||||||
params["text_input"] = text
|
params["text_input"] = text
|
||||||
|
params["language"] = params["language"][:2] # Ensure we only send the 2-letter code
|
||||||
try:
|
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:
|
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:
|
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):
|
class SettingsWindow(QWidget):
|
||||||
|
settings_updated = pyqtSignal(dict)
|
||||||
|
|
||||||
def __init__(self, tts_worker):
|
def __init__(self, tts_worker):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.tts_worker = tts_worker
|
self.tts_worker = tts_worker
|
||||||
|
self.voices = []
|
||||||
self.init_ui()
|
self.init_ui()
|
||||||
|
|
||||||
def init_ui(self):
|
def init_ui(self):
|
||||||
layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
self.setLayout(layout)
|
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)
|
self.api_url_input = QLineEdit(self.tts_worker.api_url)
|
||||||
layout.addWidget(self.api_url_input)
|
layout.addWidget(self.api_url_input)
|
||||||
|
|
||||||
layout.addWidget(QLabel("Voice:"))
|
layout.addWidget(QLabel("Voice:"))
|
||||||
self.voice_input = QLineEdit(self.tts_worker.params["character_voice_gen"])
|
self.voice_combo = QComboBox()
|
||||||
layout.addWidget(self.voice_input)
|
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 = QPushButton("Save")
|
||||||
save_button.clicked.connect(self.save_settings)
|
save_button.clicked.connect(self.save_settings)
|
||||||
layout.addWidget(save_button)
|
layout.addWidget(save_button)
|
||||||
|
|
||||||
self.setWindowTitle("TTS Settings")
|
refresh_button = QPushButton("Refresh Voices")
|
||||||
self.setGeometry(300, 300, 300, 150)
|
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):
|
def save_settings(self):
|
||||||
self.tts_worker.api_url = self.api_url_input.text()
|
new_settings = {
|
||||||
self.tts_worker.params["character_voice_gen"] = self.voice_input.text()
|
"api_url": self.api_url_input.text(),
|
||||||
self.close()
|
"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):
|
class TTSApp(QSystemTrayIcon):
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
super().__init__(parent=app)
|
super().__init__(parent=app)
|
||||||
self.app = app
|
self.app = app
|
||||||
|
self.load_settings()
|
||||||
self.init_ui()
|
self.init_ui()
|
||||||
|
|
||||||
def init_ui(self):
|
def load_settings(self):
|
||||||
self.tts_worker = TTSWorker(
|
try:
|
||||||
api_url="http://127.0.0.1:7851/api/tts-generate",
|
with open(SETTINGS_FILE, 'r') as f:
|
||||||
params={
|
self.settings = json.load(f)
|
||||||
"text_filtering": "standard",
|
except FileNotFoundError:
|
||||||
"character_voice_gen": "female_01.wav",
|
self.settings = {
|
||||||
"language": "en",
|
"api_url": TTS_SERVER_URL,
|
||||||
"output_file_name": "clipboard_tts",
|
"params": {
|
||||||
"output_file_timestamp": "true",
|
"text_filtering": "standard",
|
||||||
"autoplay": "true",
|
"character_voice_gen": "",
|
||||||
"autoplay_volume": "0.8"
|
"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.tts_worker.start()
|
||||||
|
|
||||||
self.clipboard_monitor = ClipboardMonitor()
|
self.clipboard_monitor = ClipboardMonitor()
|
||||||
@@ -123,6 +222,7 @@ class TTSApp(QSystemTrayIcon):
|
|||||||
self.setContextMenu(menu)
|
self.setContextMenu(menu)
|
||||||
|
|
||||||
self.settings_window = SettingsWindow(self.tts_worker)
|
self.settings_window = SettingsWindow(self.tts_worker)
|
||||||
|
self.settings_window.settings_updated.connect(self.update_settings)
|
||||||
|
|
||||||
def on_text_changed(self, text):
|
def on_text_changed(self, text):
|
||||||
self.tts_worker.add_text(text)
|
self.tts_worker.add_text(text)
|
||||||
@@ -138,23 +238,50 @@ class TTSApp(QSystemTrayIcon):
|
|||||||
def show_settings(self):
|
def show_settings(self):
|
||||||
self.settings_window.show()
|
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):
|
def exit_app(self):
|
||||||
self.clipboard_monitor.stop()
|
self.clipboard_monitor.stop()
|
||||||
self.tts_worker.quit()
|
self.tts_worker.quit()
|
||||||
QApplication.instance().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):
|
def signal_handler(signal, frame):
|
||||||
|
print("[Clipboard to TTS] Exiting...")
|
||||||
QApplication.instance().quit()
|
QApplication.instance().quit()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print("[AllTalk TTS] AllTalk Clipboard to TTS is starting...")
|
print("[Clipboard to TTS] AllTalk Clipboard to TTS is starting...")
|
||||||
print("[AllTalk TTS] Press Ctrl+C to exit")
|
|
||||||
|
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)
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
|
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
|
|
||||||
if not QSystemTrayIcon.isSystemTrayAvailable():
|
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)
|
sys.exit(1)
|
||||||
|
|
||||||
tts_app = TTSApp(app)
|
tts_app = TTSApp(app)
|
||||||
@@ -164,4 +291,6 @@ if __name__ == "__main__":
|
|||||||
timer.start(500)
|
timer.start(500)
|
||||||
timer.timeout.connect(lambda: None)
|
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_())
|
sys.exit(app.exec_())
|
||||||
Reference in new issue
Block a user