diff --git a/pyproject.toml b/pyproject.toml index c47a905..710255f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ dependencies = [ "vosk>=0.3.45", "numpy>=2.3.5", "edge-tts>=7.2.3", + "piper-tts>=1.3.0", ] [tool.setuptools.packages.find] diff --git a/middle-click-reader.desktop b/read-aloud.desktop similarity index 72% rename from middle-click-reader.desktop rename to read-aloud.desktop index 6e95dac..eee2326 100644 --- a/middle-click-reader.desktop +++ b/read-aloud.desktop @@ -1,8 +1,8 @@ [Desktop Entry] Type=Application -Name=Middle-Click Read-Aloud -Comment=Read highlighted text aloud with middle-click -Exec=/mnt/storage/Development/dictation-service/.venv/bin/python /mnt/storage/Development/dictation-service/src/dictation_service/middle_click_reader.py +Name=Read-Aloud Service (Alt+R) +Comment=Read highlighted text aloud with Alt+R +Exec=/mnt/storage/Development/dictation-service/.venv/bin/python /mnt/storage/Development/dictation-service/src/dictation_service/read_aloud.py Path=/mnt/storage/Development/dictation-service Terminal=false Hidden=false diff --git a/middle-click-reader.service b/read-aloud.service similarity index 82% rename from middle-click-reader.service rename to read-aloud.service index bada38b..0d2ebee 100644 --- a/middle-click-reader.service +++ b/read-aloud.service @@ -1,11 +1,11 @@ [Unit] -Description=Middle-Click Read-Aloud Service +Description=Read-Aloud Service (Alt+R) After=graphical-session.target PartOf=graphical-session.target [Service] Type=simple -ExecStart=/mnt/storage/Development/dictation-service/.venv/bin/python /mnt/storage/Development/dictation-service/src/dictation_service/middle_click_reader.py +ExecStart=/mnt/storage/Development/dictation-service/.venv/bin/python /mnt/storage/Development/dictation-service/src/dictation_service/read_aloud.py WorkingDirectory=/mnt/storage/Development/dictation-service Restart=on-failure RestartSec=5 diff --git a/scripts/setup-middle-click-reader.sh b/scripts/setup-middle-click-reader.sh deleted file mode 100755 index 4f5e4d8..0000000 --- a/scripts/setup-middle-click-reader.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash -# Setup script for middle-click read-aloud service - -set -e - -echo "Setting up middle-click read-aloud service..." - -# Create autostart directory -mkdir -p "$HOME/.config/autostart" - -# Copy desktop file to autostart -cp middle-click-reader.desktop "$HOME/.config/autostart/" - -echo "✓ Middle-click read-aloud installed to autostart" -echo "" -echo "To start now (without rebooting), run:" -echo " uv run python src/dictation_service/middle_click_reader.py &" -echo "" -echo "Or reboot to start automatically." -echo "" -echo "Usage:" -echo " 1. Highlight any text" -echo " 2. Middle-click (press scroll wheel) to read it aloud" -echo "" -echo "To disable auto-start:" -echo " rm ~/.config/autostart/middle-click-reader.desktop" -echo "" diff --git a/scripts/setup-read-aloud.sh b/scripts/setup-read-aloud.sh new file mode 100755 index 0000000..175b67d --- /dev/null +++ b/scripts/setup-read-aloud.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Setup script for read-aloud service (Alt+R) + +set -e + +echo "Setting up read-aloud service (Alt+R)..." + +# Install systemd service +mkdir -p "$HOME/.config/systemd/user" +cp read-aloud.service "$HOME/.config/systemd/user/" + +# Reload systemd and enable service +systemctl --user daemon-reload +systemctl --user enable read-aloud.service +systemctl --user start read-aloud.service + +echo "✓ Read-aloud service installed and started" +echo "" +echo "Usage:" +echo " 1. Highlight any text" +echo " 2. Press Alt+R to read it aloud" +echo "" +echo "Service management:" +echo " systemctl --user status read-aloud.service # Check status" +echo " systemctl --user restart read-aloud.service # Restart" +echo " systemctl --user stop read-aloud.service # Stop" +echo " systemctl --user disable read-aloud.service # Disable autostart" +echo "" diff --git a/src/dictation_service/middle_click_reader.py b/src/dictation_service/middle_click_reader.py deleted file mode 100755 index 8d01c3f..0000000 --- a/src/dictation_service/middle_click_reader.py +++ /dev/null @@ -1,190 +0,0 @@ -#!/usr/bin/env python3 -""" -Middle-click Read-Aloud Service -Monitors for middle-click events and reads highlighted text using edge-tts -""" - -import os -import sys -import subprocess -import logging -import tempfile -from pynput import mouse - -# Setup logging -logging.basicConfig( - filename=os.path.expanduser("~/.cache/middle_click_reader.log"), - level=logging.INFO, - format='%(asctime)s - %(levelname)s - %(message)s' -) - -# Configuration -EDGE_TTS_VOICE = "en-US-ChristopherNeural" -LOCK_FILE = "/tmp/dictation_speaking.lock" -MIN_TEXT_LENGTH = 2 # Minimum characters to read - - -class MiddleClickReader: - """Monitors for middle-click and reads selected text""" - - def __init__(self): - self.is_reading = False - self.last_text = "" - self.ctrl_pressed = False - logging.info("Middle-click reader initialized (use Ctrl+Middle-Click)") - - def get_selected_text(self): - """Get currently highlighted text from X11 PRIMARY selection""" - try: - result = subprocess.run( - ["xclip", "-o", "-selection", "primary"], - capture_output=True, - text=True, - timeout=1 - ) - if result.returncode == 0: - return result.stdout.strip() - except Exception as e: - logging.error(f"Error getting selection: {e}") - return "" - - def read_text(self, text): - """Read text using edge-tts""" - if not text or len(text) < MIN_TEXT_LENGTH: - logging.debug(f"Text too short to read: '{text}'") - return - - if self.is_reading: - logging.debug("Already reading, skipping") - return - - self.is_reading = True - logging.info(f"Reading text: {text[:50]}...") - - try: - # Create lock file to prevent feedback - with open(LOCK_FILE, 'w') as f: - f.write("middle_click_reader") - - # Create temporary file for audio - with tempfile.NamedTemporaryFile(suffix='.mp3', delete=False) as tmp_file: - audio_file = tmp_file.name - - try: - # Generate speech with edge-tts - subprocess.run( - [ - "edge-tts", - "--voice", EDGE_TTS_VOICE, - "--text", text, - "--write-media", audio_file - ], - capture_output=True, - check=True, - timeout=10 - ) - - # Play audio with mpv - subprocess.run( - ["mpv", "--no-video", "--really-quiet", audio_file], - capture_output=True, - timeout=60 - ) - - logging.info("Text read successfully") - - finally: - # Clean up temporary file - if os.path.exists(audio_file): - os.remove(audio_file) - - except subprocess.TimeoutExpired: - logging.error("TTS or playback timed out") - except subprocess.CalledProcessError as e: - logging.error(f"TTS command failed: {e}") - except Exception as e: - logging.error(f"Error reading text: {e}") - finally: - # Remove lock file - if os.path.exists(LOCK_FILE): - try: - os.remove(LOCK_FILE) - except Exception as e: - logging.error(f"Error removing lock file: {e}") - self.is_reading = False - - def on_key_press(self, key): - """Track Ctrl key state""" - try: - from pynput.keyboard import Key - if key in [Key.ctrl_l, Key.ctrl_r, Key.ctrl]: - self.ctrl_pressed = True - except: - pass - - def on_key_release(self, key): - """Track Ctrl key state""" - try: - from pynput.keyboard import Key - if key in [Key.ctrl_l, Key.ctrl_r, Key.ctrl]: - self.ctrl_pressed = False - except: - pass - - def on_click(self, x, y, button, pressed): - """Handle mouse click events""" - # Only respond to Ctrl+middle-click press - if button == mouse.Button.middle and pressed and self.ctrl_pressed: - logging.debug(f"Ctrl+Middle-click detected at ({x}, {y})") - - # Get selected text - text = self.get_selected_text() - - if text and text != self.last_text: - self.last_text = text - # Read in a separate thread to avoid blocking - import threading - read_thread = threading.Thread( - target=self.read_text, - args=(text,), - daemon=True - ) - read_thread.start() - elif not text: - logging.debug("No text selected") - - def run(self): - """Start the listeners""" - logging.info("Starting Ctrl+middle-click listener...") - print("Middle-click reader running. Hold Ctrl and middle-click on selected text to read it.") - print("Press Ctrl+C to quit.") - - from pynput import keyboard - - # Start keyboard listener to track Ctrl state - keyboard_listener = keyboard.Listener( - on_press=self.on_key_press, - on_release=self.on_key_release - ) - keyboard_listener.start() - - # Start mouse listener - with mouse.Listener(on_click=self.on_click) as listener: - listener.join() - - -def main(): - try: - reader = MiddleClickReader() - reader.run() - except KeyboardInterrupt: - logging.info("Shutting down...") - print("\nShutting down...") - except Exception as e: - logging.error(f"Fatal error: {e}") - print(f"Error: {e}") - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/src/dictation_service/read_aloud.py b/src/dictation_service/read_aloud.py new file mode 100755 index 0000000..11f2fd6 --- /dev/null +++ b/src/dictation_service/read_aloud.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +""" +Read-Aloud Service (Alt+R) +Monitors for Alt+R hotkey and reads highlighted text using Piper TTS (local neural voices) +""" + +import os +import sys +import subprocess +import logging +import tempfile +from pathlib import Path +from pynput import keyboard + +# Setup logging +logging.basicConfig( + filename=os.path.expanduser("~/.cache/read_aloud.log"), + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s' +) + +# Configuration +LOCK_FILE = "/tmp/dictation_speaking.lock" +MIN_TEXT_LENGTH = 2 # Minimum characters to read + +# Piper configuration +SCRIPT_DIR = Path(__file__).parent.parent.parent +PIPER_PATH = SCRIPT_DIR / ".venv" / "bin" / "piper" +VOICE_MODEL = Path.home() / ".shared" / "models" / "piper" / "en_US-lessac-medium.onnx" + + +class MiddleClickReader: + """Monitors for Alt+R hotkey and reads selected text""" + + def __init__(self): + self.is_reading = False + self.last_text = "" + self.alt_pressed = False + logging.info("Read-aloud service initialized (use Alt+R)") + + def get_selected_text(self): + """Get currently highlighted text from X11 PRIMARY selection""" + try: + result = subprocess.run( + ["xclip", "-o", "-selection", "primary"], + capture_output=True, + text=True, + timeout=1 + ) + if result.returncode == 0: + return result.stdout.strip() + except Exception as e: + logging.error(f"Error getting selection: {e}") + return "" + + def read_text(self, text): + """Read text using Piper TTS (local neural voices)""" + if not text or len(text) < MIN_TEXT_LENGTH: + logging.debug(f"Text too short to read: '{text}'") + return + + if self.is_reading: + logging.debug("Already reading, skipping") + return + + self.is_reading = True + logging.info(f"Reading text: {text[:50]}...") + + try: + # Create lock file to prevent feedback + with open(LOCK_FILE, 'w') as f: + f.write("read_aloud") + + # Create temporary WAV file for audio + with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as tmp_file: + audio_file = tmp_file.name + + try: + # Generate speech with Piper + piper_process = subprocess.Popen( + [ + str(PIPER_PATH), + "--model", str(VOICE_MODEL), + "--output_file", audio_file + ], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + # Send text to Piper via stdin + piper_process.communicate(input=text, timeout=10) + + if piper_process.returncode == 0: + # Play audio with mpv (or aplay/paplay as fallback) + subprocess.run( + ["mpv", "--no-video", "--really-quiet", audio_file], + capture_output=True, + timeout=60 + ) + logging.info("Text read successfully") + else: + logging.error(f"Piper TTS failed with code {piper_process.returncode}") + + finally: + # Clean up temporary file + if os.path.exists(audio_file): + os.remove(audio_file) + + except subprocess.TimeoutExpired: + logging.error("TTS timed out") + except Exception as e: + logging.error(f"Error reading text: {e}") + finally: + # Remove lock file + if os.path.exists(LOCK_FILE): + try: + os.remove(LOCK_FILE) + except Exception as e: + logging.error(f"Error removing lock file: {e}") + self.is_reading = False + + def on_key_press(self, key): + """Track Alt key and trigger on Alt+R""" + try: + # Track Alt key + if key in [keyboard.Key.alt_l, keyboard.Key.alt_r, keyboard.Key.alt]: + self.alt_pressed = True + + # Trigger on Alt+R + if self.alt_pressed and hasattr(key, 'char') and key.char == 'r': + logging.debug("Alt+R detected") + + # Get selected text + text = self.get_selected_text() + + if text and text != self.last_text: + self.last_text = text + # Read in a separate thread to avoid blocking + import threading + read_thread = threading.Thread( + target=self.read_text, + args=(text,), + daemon=True + ) + read_thread.start() + elif not text: + logging.debug("No text selected") + except Exception as e: + logging.error(f"Error in key press handler: {e}") + + def on_key_release(self, key): + """Track Alt key state""" + try: + if key in [keyboard.Key.alt_l, keyboard.Key.alt_r, keyboard.Key.alt]: + self.alt_pressed = False + except Exception as e: + logging.error(f"Error in key release handler: {e}") + + def run(self): + """Start the keyboard listener""" + logging.info("Starting Alt+R listener...") + print("Read-aloud service running. Press Alt+R on selected text to read it.") + print("Press Ctrl+C to quit.") + + # Start keyboard listener + with keyboard.Listener( + on_press=self.on_key_press, + on_release=self.on_key_release + ) as listener: + listener.join() + + +def main(): + try: + reader = MiddleClickReader() + reader.run() + except KeyboardInterrupt: + logging.info("Shutting down...") + print("\nShutting down...") + except Exception as e: + logging.error(f"Fatal error: {e}") + print(f"Error: {e}") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/tests/test_middle_click.py b/tests/test_read_aloud.py similarity index 83% rename from tests/test_middle_click.py rename to tests/test_read_aloud.py index ca756ce..7812cc1 100644 --- a/tests/test_middle_click.py +++ b/tests/test_read_aloud.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -Test Suite for Middle-Click Read-Aloud Service +Test Suite for Read-Aloud Service (Alt+R) Tests on-demand text-to-speech functionality """ @@ -14,22 +14,22 @@ from unittest.mock import Mock, patch, MagicMock, call sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src')) -class TestMiddleClickReader(unittest.TestCase): - """Test middle-click reader functionality""" +class TestReadAloud(unittest.TestCase): + """Test read-aloud service functionality""" - def test_can_import_middle_click_reader(self): - """Test that middle-click reader can be imported""" + def test_can_import_read_aloud(self): + """Test that read-aloud service can be imported""" try: - from dictation_service import middle_click_reader - self.assertTrue(hasattr(middle_click_reader, 'MiddleClickReader')) - self.assertTrue(hasattr(middle_click_reader, 'main')) + from dictation_service import read_aloud + self.assertTrue(hasattr(read_aloud, 'MiddleClickReader')) + self.assertTrue(hasattr(read_aloud, 'main')) except ImportError as e: - self.fail(f"Cannot import middle-click reader: {e}") + self.fail(f"Cannot import read-aloud service: {e}") @patch('subprocess.run') def test_get_selected_text(self, mock_run): """Test getting selected text from xclip""" - from dictation_service.middle_click_reader import MiddleClickReader + from dictation_service.read_aloud import MiddleClickReader reader = MiddleClickReader() @@ -49,7 +49,7 @@ class TestMiddleClickReader(unittest.TestCase): @patch('os.remove') def test_read_text(self, mock_remove, mock_exists, mock_temp, mock_run): """Test reading text with edge-tts""" - from dictation_service.middle_click_reader import MiddleClickReader + from dictation_service.read_aloud import MiddleClickReader reader = MiddleClickReader() @@ -74,7 +74,7 @@ class TestMiddleClickReader(unittest.TestCase): def test_minimum_text_length(self): """Test that short text is not read""" - from dictation_service.middle_click_reader import MiddleClickReader + from dictation_service.read_aloud import MiddleClickReader reader = MiddleClickReader() @@ -93,7 +93,7 @@ class TestMiddleClickReader(unittest.TestCase): def test_lock_file_creation(self): """Test that lock file is created during reading""" - from dictation_service.middle_click_reader import LOCK_FILE + from dictation_service.read_aloud import LOCK_FILE # Verify lock file path self.assertEqual(LOCK_FILE, "/tmp/dictation_speaking.lock") @@ -101,7 +101,7 @@ class TestMiddleClickReader(unittest.TestCase): @patch('pynput.mouse.Listener') def test_mouse_listener_initialization(self, mock_listener): """Test that mouse listener can be initialized""" - from dictation_service.middle_click_reader import MiddleClickReader + from dictation_service.read_aloud import MiddleClickReader reader = MiddleClickReader() @@ -115,7 +115,7 @@ class TestMiddleClickReader(unittest.TestCase): def test_middle_click_detection(self): """Test middle-click detection logic""" - from dictation_service.middle_click_reader import MiddleClickReader + from dictation_service.read_aloud import MiddleClickReader from pynput import mouse reader = MiddleClickReader() @@ -133,7 +133,7 @@ class TestMiddleClickReader(unittest.TestCase): def test_ignores_non_middle_clicks(self): """Test that non-middle clicks are ignored""" - from dictation_service.middle_click_reader import MiddleClickReader + from dictation_service.read_aloud import MiddleClickReader from pynput import mouse reader = MiddleClickReader() @@ -149,7 +149,7 @@ class TestMiddleClickReader(unittest.TestCase): def test_concurrent_reading_prevention(self): """Test that concurrent reading is prevented""" - from dictation_service.middle_click_reader import MiddleClickReader + from dictation_service.read_aloud import MiddleClickReader reader = MiddleClickReader() @@ -170,7 +170,7 @@ class TestEdgeTTSIntegration(unittest.TestCase): @patch('subprocess.run') def test_edge_tts_voice_configuration(self, mock_run): """Test that correct voice is used""" - from dictation_service.middle_click_reader import EDGE_TTS_VOICE + from dictation_service.read_aloud import EDGE_TTS_VOICE # Verify default voice self.assertEqual(EDGE_TTS_VOICE, "en-US-ChristopherNeural") @@ -178,7 +178,7 @@ class TestEdgeTTSIntegration(unittest.TestCase): @patch('subprocess.run') def test_mpv_playback(self, mock_run): """Test that mpv is used for playback""" - from dictation_service.middle_click_reader import MiddleClickReader + from dictation_service.read_aloud import MiddleClickReader reader = MiddleClickReader() reader.is_reading = False diff --git a/uv.lock b/uv.lock index 2f04ce8..dca69eb 100644 --- a/uv.lock +++ b/uv.lock @@ -250,6 +250,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "coloredlogs" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "humanfriendly" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520, upload-time = "2021-06-11T10:22:45.202Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018, upload-time = "2021-06-11T10:22:42.561Z" }, +] + [[package]] name = "dictation-service" version = "0.2.0" @@ -257,6 +269,7 @@ source = { virtual = "." } dependencies = [ { name = "edge-tts" }, { name = "numpy" }, + { name = "piper-tts" }, { name = "pygobject" }, { name = "pynput" }, { name = "sounddevice" }, @@ -267,6 +280,7 @@ dependencies = [ requires-dist = [ { name = "edge-tts", specifier = ">=7.2.3" }, { name = "numpy", specifier = ">=2.3.5" }, + { name = "piper-tts", specifier = ">=1.3.0" }, { name = "pygobject", specifier = ">=3.42.0" }, { name = "pynput", specifier = ">=1.8.1" }, { name = "sounddevice", specifier = ">=0.5.3" }, @@ -294,6 +308,15 @@ version = "1.9.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/63/fe/a17c106a1f4061ce83f04d14bcedcfb2c38c7793ea56bfb906a6fadae8cb/evdev-1.9.2.tar.gz", hash = "sha256:5d3278892ce1f92a74d6bf888cc8525d9f68af85dbe336c95d1c87fb8f423069", size = 33301, upload-time = "2025-05-01T19:53:47.69Z" } +[[package]] +name = "flatbuffers" +version = "25.9.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/1f/3ee70b0a55137442038f2a33469cc5fddd7e0ad2abf83d7497c18a2b6923/flatbuffers-25.9.23.tar.gz", hash = "sha256:676f9fa62750bb50cf531b42a0a2a118ad8f7f797a511eda12881c016f093b12", size = 22067, upload-time = "2025-09-24T05:25:30.106Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/1b/00a78aa2e8fbd63f9af08c9c19e6deb3d5d66b4dda677a0f61654680ee89/flatbuffers-25.9.23-py2.py3-none-any.whl", hash = "sha256:255538574d6cb6d0a79a17ec8bc0d30985913b87513a01cce8bcdb6b4c44d0e2", size = 30869, upload-time = "2025-09-24T05:25:28.912Z" }, +] + [[package]] name = "frozenlist" version = "1.8.0" @@ -383,6 +406,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, ] +[[package]] +name = "humanfriendly" +version = "10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyreadline3", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702, upload-time = "2021-09-17T21:40:43.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794, upload-time = "2021-09-17T21:40:39.897Z" }, +] + [[package]] name = "idna" version = "3.11" @@ -392,6 +427,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, +] + [[package]] name = "multidict" version = "6.7.0" @@ -554,6 +598,57 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2d/fd/4b5eb0b3e888d86aee4d198c23acec7d214baaf17ea93c1adec94c9518b9/numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42", size = 10545459, upload-time = "2025-11-16T22:52:20.55Z" }, ] +[[package]] +name = "onnxruntime" +version = "1.23.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coloredlogs" }, + { name = "flatbuffers" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "sympy" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/9e/f748cd64161213adeef83d0cb16cb8ace1e62fa501033acdd9f9341fff57/onnxruntime-1.23.2-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:b8f029a6b98d3cf5be564d52802bb50a8489ab73409fa9db0bf583eabb7c2321", size = 17195929, upload-time = "2025-10-22T03:47:36.24Z" }, + { url = "https://files.pythonhosted.org/packages/91/9d/a81aafd899b900101988ead7fb14974c8a58695338ab6a0f3d6b0100f30b/onnxruntime-1.23.2-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:218295a8acae83905f6f1aed8cacb8e3eb3bd7513a13fe4ba3b2664a19fc4a6b", size = 19157705, upload-time = "2025-10-22T03:46:40.415Z" }, + { url = "https://files.pythonhosted.org/packages/3c/35/4e40f2fba272a6698d62be2cd21ddc3675edfc1a4b9ddefcc4648f115315/onnxruntime-1.23.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76ff670550dc23e58ea9bc53b5149b99a44e63b34b524f7b8547469aaa0dcb8c", size = 15226915, upload-time = "2025-10-22T03:46:27.773Z" }, + { url = "https://files.pythonhosted.org/packages/ef/88/9cc25d2bafe6bc0d4d3c1db3ade98196d5b355c0b273e6a5dc09c5d5d0d5/onnxruntime-1.23.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f9b4ae77f8e3c9bee50c27bc1beede83f786fe1d52e99ac85aa8d65a01e9b77", size = 17382649, upload-time = "2025-10-22T03:47:02.782Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b4/569d298f9fc4d286c11c45e85d9ffa9e877af12ace98af8cab52396e8f46/onnxruntime-1.23.2-cp312-cp312-win_amd64.whl", hash = "sha256:25de5214923ce941a3523739d34a520aac30f21e631de53bba9174dc9c004435", size = 13470528, upload-time = "2025-10-22T03:47:28.106Z" }, + { url = "https://files.pythonhosted.org/packages/3d/41/fba0cabccecefe4a1b5fc8020c44febb334637f133acefc7ec492029dd2c/onnxruntime-1.23.2-cp313-cp313-macosx_13_0_arm64.whl", hash = "sha256:2ff531ad8496281b4297f32b83b01cdd719617e2351ffe0dba5684fb283afa1f", size = 17196337, upload-time = "2025-10-22T03:46:35.168Z" }, + { url = "https://files.pythonhosted.org/packages/fe/f9/2d49ca491c6a986acce9f1d1d5fc2099108958cc1710c28e89a032c9cfe9/onnxruntime-1.23.2-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:162f4ca894ec3de1a6fd53589e511e06ecdc3ff646849b62a9da7489dee9ce95", size = 19157691, upload-time = "2025-10-22T03:46:43.518Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a1/428ee29c6eaf09a6f6be56f836213f104618fb35ac6cc586ff0f477263eb/onnxruntime-1.23.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45d127d6e1e9b99d1ebeae9bcd8f98617a812f53f46699eafeb976275744826b", size = 15226898, upload-time = "2025-10-22T03:46:30.039Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2b/b57c8a2466a3126dbe0a792f56ad7290949b02f47b86216cd47d857e4b77/onnxruntime-1.23.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8bace4e0d46480fbeeb7bbe1ffe1f080e6663a42d1086ff95c1551f2d39e7872", size = 17382518, upload-time = "2025-10-22T03:47:05.407Z" }, + { url = "https://files.pythonhosted.org/packages/4a/93/aba75358133b3a941d736816dd392f687e7eab77215a6e429879080b76b6/onnxruntime-1.23.2-cp313-cp313-win_amd64.whl", hash = "sha256:1f9cc0a55349c584f083c1c076e611a7c35d5b867d5d6e6d6c823bf821978088", size = 13470276, upload-time = "2025-10-22T03:47:31.193Z" }, + { url = "https://files.pythonhosted.org/packages/7c/3d/6830fa61c69ca8e905f237001dbfc01689a4e4ab06147020a4518318881f/onnxruntime-1.23.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9d2385e774f46ac38f02b3a91a91e30263d41b2f1f4f26ae34805b2a9ddef466", size = 15229610, upload-time = "2025-10-22T03:46:32.239Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ca/862b1e7a639460f0ca25fd5b6135fb42cf9deea86d398a92e44dfda2279d/onnxruntime-1.23.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2b9233c4947907fd1818d0e581c049c41ccc39b2856cc942ff6d26317cee145", size = 17394184, upload-time = "2025-10-22T03:47:08.127Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "piper-tts" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "onnxruntime" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/c0/d9b5f64869274be3ebc6dc483f13791a3c6ebbc0e37fad4e237a76d5365b/piper_tts-1.3.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:0af0c90aeddf762555ed940de1ac576acbefb3623e6d5ca4fb1a70359ee7e65d", size = 13819597, upload-time = "2025-07-10T21:07:22.893Z" }, + { url = "https://files.pythonhosted.org/packages/5b/17/6a059c0a45e582fadd4545ed092294fd0add7c679f6c09440af5cd2678b5/piper_tts-1.3.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:810c91a084d335d32b42928b1ef69d6480cf7e3a5a8b15eff98edd2ef55f2791", size = 13828403, upload-time = "2025-07-10T21:07:25.386Z" }, + { url = "https://files.pythonhosted.org/packages/8c/92/f37e5111440fc6c6336f42f8dab88afaa545394784dc930f808a68883c48/piper_tts-1.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8d39f85c3f4b6ade512976849579344fc72595ec613f374dbcf8521716398907", size = 13836863, upload-time = "2025-07-10T21:07:27.616Z" }, + { url = "https://files.pythonhosted.org/packages/2b/73/3d29175cfd93e791baaef3335819778d3f8c8898e2fe16cd0cc8b8163f84/piper_tts-1.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:234c25474655b26f3418b84522c815c43e9b1bc8a1fdb13c2b28514290c165f0", size = 13836748, upload-time = "2025-07-10T21:07:29.912Z" }, + { url = "https://files.pythonhosted.org/packages/10/a5/d782d469fc19db9bf19f1725d4a6ef77d2413515b61f5017340688f5d093/piper_tts-1.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:dc6b5be4e15f3c0f4a6067b515bc6202ddf3e2b0c6cbd6c8bdeccab2453c89c7", size = 13826773, upload-time = "2025-07-10T21:07:31.95Z" }, +] + [[package]] name = "propcache" version = "0.4.1" @@ -638,6 +733,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, ] +[[package]] +name = "protobuf" +version = "6.33.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/44/e49ecff446afeec9d1a66d6bbf9adc21e3c7cea7803a920ca3773379d4f6/protobuf-6.33.2.tar.gz", hash = "sha256:56dc370c91fbb8ac85bc13582c9e373569668a290aa2e66a590c2a0d35ddb9e4", size = 444296, upload-time = "2025-12-06T00:17:53.311Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/91/1e3a34881a88697a7354ffd177e8746e97a722e5e8db101544b47e84afb1/protobuf-6.33.2-cp310-abi3-win32.whl", hash = "sha256:87eb388bd2d0f78febd8f4c8779c79247b26a5befad525008e49a6955787ff3d", size = 425603, upload-time = "2025-12-06T00:17:41.114Z" }, + { url = "https://files.pythonhosted.org/packages/64/20/4d50191997e917ae13ad0a235c8b42d8c1ab9c3e6fd455ca16d416944355/protobuf-6.33.2-cp310-abi3-win_amd64.whl", hash = "sha256:fc2a0e8b05b180e5fc0dd1559fe8ebdae21a27e81ac77728fb6c42b12c7419b4", size = 436930, upload-time = "2025-12-06T00:17:43.278Z" }, + { url = "https://files.pythonhosted.org/packages/b2/ca/7e485da88ba45c920fb3f50ae78de29ab925d9e54ef0de678306abfbb497/protobuf-6.33.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d9b19771ca75935b3a4422957bc518b0cecb978b31d1dd12037b088f6bcc0e43", size = 427621, upload-time = "2025-12-06T00:17:44.445Z" }, + { url = "https://files.pythonhosted.org/packages/7d/4f/f743761e41d3b2b2566748eb76bbff2b43e14d5fcab694f494a16458b05f/protobuf-6.33.2-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:b5d3b5625192214066d99b2b605f5783483575656784de223f00a8d00754fc0e", size = 324460, upload-time = "2025-12-06T00:17:45.678Z" }, + { url = "https://files.pythonhosted.org/packages/b1/fa/26468d00a92824020f6f2090d827078c09c9c587e34cbfd2d0c7911221f8/protobuf-6.33.2-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8cd7640aee0b7828b6d03ae518b5b4806fdfc1afe8de82f79c3454f8aef29872", size = 339168, upload-time = "2025-12-06T00:17:46.813Z" }, + { url = "https://files.pythonhosted.org/packages/56/13/333b8f421738f149d4fe5e49553bc2a2ab75235486259f689b4b91f96cec/protobuf-6.33.2-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:1f8017c48c07ec5859106533b682260ba3d7c5567b1ca1f24297ce03384d1b4f", size = 323270, upload-time = "2025-12-06T00:17:48.253Z" }, + { url = "https://files.pythonhosted.org/packages/0e/15/4f02896cc3df04fc465010a4c6a0cd89810f54617a32a70ef531ed75d61c/protobuf-6.33.2-py3-none-any.whl", hash = "sha256:7636aad9bb01768870266de5dc009de2d1b936771b38a793f73cbbf279c91c5c", size = 170501, upload-time = "2025-12-06T00:17:52.211Z" }, +] + [[package]] name = "pycairo" version = "1.29.0" @@ -774,6 +884,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4d/a6/708a55f3ff7a18c403b30a29a11dccfed0410485a7548c60a4b6d4cc0676/pyobjc_framework_quartz-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0cc08fddb339b2760df60dea1057453557588908e42bdc62184b6396ce2d6e9a", size = 224580, upload-time = "2025-11-14T10:01:00.091Z" }, ] +[[package]] +name = "pyreadline3" +version = "3.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/49/4cea918a08f02817aabae639e3d0ac046fef9f9180518a3ad394e22da148/pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7", size = 99839, upload-time = "2024-09-19T02:40:10.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178, upload-time = "2024-09-19T02:40:08.598Z" }, +] + [[package]] name = "python-xlib" version = "0.33" @@ -831,6 +950,18 @@ version = "3.5.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/66/b7/4a1bc231e0681ebf339337b0cd05b91dc6a0d701fa852bb812e244b7a030/srt-3.5.3.tar.gz", hash = "sha256:4884315043a4f0740fd1f878ed6caa376ac06d70e135f306a6dc44632eed0cc0", size = 28296, upload-time = "2023-03-28T02:35:44.007Z" } +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, +] + [[package]] name = "tabulate" version = "0.9.0"