From 3cfcfa347b0acd0224989fb0a67695204ac91d43 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 25 Jan 2026 05:13:49 -0800 Subject: [PATCH 01/16] feat(tts): add Qwen3-TTS as third TTS backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Qwen3-TTS as a new TTS backend option alongside Kokoro and Piper. Features: - High-quality multilingual neural TTS with 10+ language support - 9 built-in voices (Vivian, Ryan, Serena, Dylan, Eric, Aiden, etc.) - OpenAI voice name mapping (alloy, echo, fable, etc. → Qwen speakers) - Subprocess isolation for clean memory management on TTL unload - GPU acceleration (CUDA/MPS) with CPU fallback Usage: pip install "agent-cli[qwen-tts]" agent-cli server tts --backend qwen The model (~3GB) auto-downloads from HuggingFace on first use. --- agent_cli/_extras.json | 84 +++- agent_cli/_requirements/kokoro.txt | 2 +- agent_cli/_requirements/memory.txt | 2 +- agent_cli/_requirements/qwen-tts.txt | 423 +++++++++++++++++ agent_cli/_requirements/rag.txt | 2 +- agent_cli/constants.py | 1 + agent_cli/server/cli.py | 49 +- agent_cli/server/tts/backends/__init__.py | 7 +- agent_cli/server/tts/backends/qwen.py | 330 +++++++++++++ docs/commands/server/tts.md | 44 +- pyproject.toml | 6 + scripts/sync_extras.py | 6 + uv.lock | 541 +++++++++++++++++++++- 13 files changed, 1466 insertions(+), 31 deletions(-) create mode 100644 agent_cli/_requirements/qwen-tts.txt create mode 100644 agent_cli/server/tts/backends/qwen.py diff --git a/agent_cli/_extras.json b/agent_cli/_extras.json index 718ecfa70..84dad9e59 100644 --- a/agent_cli/_extras.json +++ b/agent_cli/_extras.json @@ -1,13 +1,75 @@ { - "audio": ["Audio recording/playback with Wyoming protocol", ["numpy", "sounddevice", "wyoming"]], - "llm": ["LLM framework (pydantic-ai)", ["pydantic_ai"]], - "memory": ["Long-term memory proxy", ["chromadb", "yaml"]], - "rag": ["RAG proxy (ChromaDB, embeddings)", ["chromadb"]], - "server": ["FastAPI server components", ["fastapi"]], - "speed": ["Audio speed adjustment (audiostretchy)", ["audiostretchy"]], - "piper": ["Local Piper TTS", ["piper"]], - "kokoro": ["Kokoro neural TTS", ["kokoro"]], - "vad": ["Voice Activity Detection (silero-vad)", ["silero_vad"]], - "faster-whisper": ["Whisper ASR (CUDA/CPU)", ["faster_whisper"]], - "mlx-whisper": ["Whisper ASR (Apple Silicon)", ["mlx_whisper"]] + "audio": [ + "Audio recording/playback", + [ + "sounddevice" + ] + ], + "faster-whisper": [ + "Faster Whisper ASR backend", + [ + "faster_whisper" + ] + ], + "kokoro": [ + "Kokoro TTS backend (GPU-accelerated)", + [ + "kokoro" + ] + ], + "llm": [ + "LLM framework (pydantic-ai)", + [ + "pydantic_ai" + ] + ], + "memory": [ + "Long-term memory proxy", + [ + "chromadb", + "yaml" + ] + ], + "mlx-whisper": [ + "MLX Whisper ASR for Apple Silicon", + [ + "mlx_whisper" + ] + ], + "piper": [ + "Piper TTS backend (CPU-friendly)", + [ + "piper" + ] + ], + "qwen-tts": [ + "Qwen3-TTS backend (multilingual)", + [ + "qwen_tts" + ] + ], + "rag": [ + "RAG proxy (ChromaDB, embeddings)", + [ + "chromadb" + ] + ], + "server": [ + "FastAPI server components", + [ + "fastapi" + ] + ], + "speed": [ + "Audio speed adjustment (audiostretchy)", + [ + "audiostretchy" + ] + ], + "vad": [ + "Voice Activity Detection (silero-vad)", + [ + "silero_vad" + ] + ] } diff --git a/agent_cli/_requirements/kokoro.txt b/agent_cli/_requirements/kokoro.txt index 4857fd38d..f2e0e25c6 100644 --- a/agent_cli/_requirements/kokoro.txt +++ b/agent_cli/_requirements/kokoro.txt @@ -359,7 +359,7 @@ tqdm==4.67.1 # huggingface-hub # spacy # transformers -transformers==4.57.5 +transformers==4.57.3 # via # agent-cli # kokoro diff --git a/agent_cli/_requirements/memory.txt b/agent_cli/_requirements/memory.txt index 82785c8fb..0c35cae28 100644 --- a/agent_cli/_requirements/memory.txt +++ b/agent_cli/_requirements/memory.txt @@ -296,7 +296,7 @@ tqdm==4.67.1 # chromadb # huggingface-hub # transformers -transformers==4.57.5 +transformers==4.57.3 # via agent-cli typer==0.21.1 # via diff --git a/agent_cli/_requirements/qwen-tts.txt b/agent_cli/_requirements/qwen-tts.txt new file mode 100644 index 000000000..b5cc8c76f --- /dev/null +++ b/agent_cli/_requirements/qwen-tts.txt @@ -0,0 +1,423 @@ +# This file was autogenerated by uv via the following command: +# uv export --extra qwen-tts --no-dev --no-emit-project --no-hashes +accelerate==1.12.0 + # via qwen-tts +aiofiles==24.1.0 + # via gradio +annotated-doc==0.0.4 + # via fastapi +annotated-types==0.7.0 + # via pydantic +anyio==4.12.1 + # via + # gradio + # httpx + # starlette + # watchfiles +audioop-lts==0.2.2 ; python_full_version >= '3.13' + # via + # gradio + # standard-aifc + # standard-sunau +audioread==3.1.0 + # via librosa +brotli==1.2.0 + # via gradio +certifi==2026.1.4 + # via + # httpcore + # httpx + # requests + # sentry-sdk +cffi==2.0.0 + # via soundfile +charset-normalizer==3.4.4 + # via requests +click==8.3.1 + # via + # rich-toolkit + # typer + # typer-slim + # uvicorn +colorama==0.4.6 ; sys_platform == 'win32' + # via + # click + # tqdm + # uvicorn +coloredlogs==15.0.1 + # via onnxruntime +decorator==5.2.1 + # via librosa +dnspython==2.8.0 + # via email-validator +dotenv==0.9.9 + # via agent-cli +einops==0.8.1 + # via qwen-tts +email-validator==2.3.0 + # via + # fastapi + # pydantic +fastapi==0.128.0 + # via + # agent-cli + # gradio +fastapi-cli==0.0.20 + # via fastapi +fastapi-cloud-cli==0.10.1 + # via fastapi-cli +fastar==0.8.0 + # via fastapi-cloud-cli +ffmpy==1.0.0 + # via gradio +filelock==3.20.3 + # via + # huggingface-hub + # torch + # transformers +flatbuffers==25.12.19 + # via onnxruntime +fsspec==2026.1.0 + # via + # gradio-client + # huggingface-hub + # torch +gradio==6.4.0 + # via qwen-tts +gradio-client==2.0.3 + # via gradio +groovy==0.1.2 + # via gradio +h11==0.16.0 + # via + # httpcore + # uvicorn +hf-xet==1.2.0 ; platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64' + # via huggingface-hub +httpcore==1.0.9 + # via httpx +httptools==0.7.1 + # via uvicorn +httpx==0.28.1 + # via + # agent-cli + # fastapi + # fastapi-cloud-cli + # gradio + # gradio-client + # safehttpx +huggingface-hub==0.36.0 + # via + # accelerate + # gradio + # gradio-client + # tokenizers + # transformers +humanfriendly==10.0 + # via coloredlogs +idna==3.11 + # via + # anyio + # email-validator + # httpx + # requests +jinja2==3.1.6 + # via + # fastapi + # gradio + # torch +joblib==1.5.3 + # via + # librosa + # scikit-learn +lazy-loader==0.4 + # via librosa +librosa==0.11.0 + # via + # agent-cli + # qwen-tts +llvmlite==0.46.0 + # via numba +markdown-it-py==4.0.0 + # via rich +markupsafe==3.0.3 + # via + # gradio + # jinja2 +mdurl==0.1.2 + # via markdown-it-py +mpmath==1.3.0 + # via sympy +msgpack==1.1.2 + # via librosa +networkx==3.6.1 + # via torch +numba==0.63.1 + # via librosa +numpy==2.3.5 + # via + # accelerate + # gradio + # librosa + # numba + # onnxruntime + # pandas + # scikit-learn + # scipy + # soundfile + # sox + # soxr + # transformers +nvidia-cublas-cu12==12.8.4.1 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via + # nvidia-cudnn-cu12 + # nvidia-cusolver-cu12 + # torch +nvidia-cuda-cupti-cu12==12.8.90 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +nvidia-cuda-nvrtc-cu12==12.8.93 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +nvidia-cuda-runtime-cu12==12.8.90 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +nvidia-cudnn-cu12==9.10.2.21 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +nvidia-cufft-cu12==11.3.3.83 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +nvidia-cufile-cu12==1.13.1.3 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +nvidia-curand-cu12==10.3.9.90 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +nvidia-cusolver-cu12==11.7.3.90 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +nvidia-cusparse-cu12==12.5.8.93 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via + # nvidia-cusolver-cu12 + # torch +nvidia-cusparselt-cu12==0.7.1 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +nvidia-nccl-cu12==2.27.5 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +nvidia-nvjitlink-cu12==12.8.93 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via + # nvidia-cufft-cu12 + # nvidia-cusolver-cu12 + # nvidia-cusparse-cu12 + # torch +nvidia-nvshmem-cu12==3.3.20 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +nvidia-nvtx-cu12==12.8.90 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +onnxruntime==1.20.1 + # via qwen-tts +orjson==3.11.5 + # via gradio +packaging==25.0 + # via + # accelerate + # gradio + # gradio-client + # huggingface-hub + # lazy-loader + # onnxruntime + # pooch + # transformers +pandas==2.3.3 + # via gradio +pillow==12.1.0 + # via gradio +platformdirs==4.5.1 + # via pooch +pooch==1.8.2 + # via librosa +protobuf==6.33.4 + # via onnxruntime +psutil==7.2.1 + # via + # accelerate + # agent-cli +pycparser==2.23 ; implementation_name != 'PyPy' + # via cffi +pydantic==2.12.5 + # via + # agent-cli + # fastapi + # fastapi-cloud-cli + # gradio + # pydantic-extra-types + # pydantic-settings +pydantic-core==2.41.5 + # via pydantic +pydantic-extra-types==2.11.0 + # via fastapi +pydantic-settings==2.12.0 + # via fastapi +pydub==0.25.1 + # via gradio +pygments==2.19.2 + # via rich +pyperclip==1.11.0 + # via agent-cli +pyreadline3==3.5.4 ; sys_platform == 'win32' + # via humanfriendly +python-dateutil==2.9.0.post0 + # via pandas +python-dotenv==1.2.1 + # via + # dotenv + # pydantic-settings + # uvicorn +python-multipart==0.0.21 + # via + # fastapi + # gradio +pytz==2025.2 + # via pandas +pyyaml==6.0.3 + # via + # accelerate + # gradio + # huggingface-hub + # transformers + # uvicorn +qwen-tts==0.0.5 + # via agent-cli +regex==2026.1.15 + # via transformers +requests==2.32.5 + # via + # huggingface-hub + # pooch + # transformers +rich==14.2.0 + # via + # agent-cli + # rich-toolkit + # typer + # typer-slim +rich-toolkit==0.17.1 + # via + # fastapi-cli + # fastapi-cloud-cli +rignore==0.7.6 + # via fastapi-cloud-cli +safehttpx==0.1.7 + # via gradio +safetensors==0.7.0 + # via + # accelerate + # transformers +scikit-learn==1.8.0 + # via librosa +scipy==1.17.0 + # via + # librosa + # scikit-learn +semantic-version==2.10.0 + # via gradio +sentry-sdk==2.49.0 + # via fastapi-cloud-cli +setproctitle==1.3.7 + # via agent-cli +setuptools==80.9.0 ; python_full_version >= '3.12' + # via torch +shellingham==1.5.4 + # via + # typer + # typer-slim +six==1.17.0 + # via python-dateutil +soundfile==0.13.1 + # via + # agent-cli + # librosa + # qwen-tts +sox==1.5.0 + # via qwen-tts +soxr==1.0.0 + # via librosa +standard-aifc==3.13.0 ; python_full_version >= '3.13' + # via + # audioread + # librosa +standard-chunk==3.13.0 ; python_full_version >= '3.13' + # via standard-aifc +standard-sunau==3.13.0 ; python_full_version >= '3.13' + # via + # audioread + # librosa +starlette==0.50.0 + # via + # fastapi + # gradio +sympy==1.14.0 + # via + # onnxruntime + # torch +threadpoolctl==3.6.0 + # via scikit-learn +tokenizers==0.22.2 + # via transformers +tomlkit==0.13.3 + # via gradio +torch==2.9.1 + # via + # accelerate + # torchaudio +torchaudio==2.9.1 + # via qwen-tts +tqdm==4.67.1 + # via + # huggingface-hub + # transformers +transformers==4.57.3 + # via qwen-tts +triton==3.5.1 ; platform_machine == 'x86_64' and sys_platform == 'linux' + # via torch +typer==0.21.1 + # via + # agent-cli + # fastapi-cli + # fastapi-cloud-cli + # gradio +typer-slim==0.21.1 + # via agent-cli +typing-extensions==4.15.0 + # via + # anyio + # fastapi + # gradio + # gradio-client + # huggingface-hub + # librosa + # pydantic + # pydantic-core + # pydantic-extra-types + # rich-toolkit + # sox + # starlette + # torch + # typer + # typer-slim + # typing-inspection +typing-inspection==0.4.2 + # via + # pydantic + # pydantic-settings +tzdata==2025.3 + # via pandas +urllib3==2.3.0 + # via + # requests + # sentry-sdk +uvicorn==0.40.0 + # via + # fastapi + # fastapi-cli + # fastapi-cloud-cli + # gradio +uvloop==0.22.1 ; platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32' + # via uvicorn +watchfiles==1.1.1 + # via uvicorn +websockets==15.0.1 + # via uvicorn diff --git a/agent_cli/_requirements/rag.txt b/agent_cli/_requirements/rag.txt index 79663b0fe..81196c01e 100644 --- a/agent_cli/_requirements/rag.txt +++ b/agent_cli/_requirements/rag.txt @@ -339,7 +339,7 @@ tqdm==4.67.1 # chromadb # huggingface-hub # transformers -transformers==4.57.5 +transformers==4.57.3 # via agent-cli typer==0.21.1 # via diff --git a/agent_cli/constants.py b/agent_cli/constants.py index 981e51514..0bb553635 100644 --- a/agent_cli/constants.py +++ b/agent_cli/constants.py @@ -13,6 +13,7 @@ # --- TTS Configuration --- PIPER_DEFAULT_SAMPLE_RATE = 22050 # Piper TTS default sample rate KOKORO_DEFAULT_SAMPLE_RATE = 24000 # Kokoro TTS default sample rate +QWEN_DEFAULT_SAMPLE_RATE = 24000 # Qwen3-TTS default sample rate (12Hz tokenizer) # Standard Wyoming audio configuration WYOMING_AUDIO_CONFIG = { diff --git a/agent_cli/server/cli.py b/agent_cli/server/cli.py index f5c5d856c..0efd5548b 100644 --- a/agent_cli/server/cli.py +++ b/agent_cli/server/cli.py @@ -81,6 +81,16 @@ def _check_tts_deps(backend: str = "auto") -> None: raise typer.Exit(1) return + if backend == "qwen": + if not _has("qwen_tts"): + err_console.print( + "[bold red]Error:[/bold red] Qwen backend requires qwen-tts. " + "Run: [cyan]pip install agent-cli\\[qwen-tts][/cyan] " + "or [cyan]uv sync --extra qwen-tts[/cyan]", + ) + raise typer.Exit(1) + return + # For auto, check if either is available if not _has("piper") and not _has("kokoro"): err_console.print( @@ -119,6 +129,24 @@ def _download_tts_models( console.print("[bold green]Download complete![/bold green]") return + if backend == "qwen": + from huggingface_hub import snapshot_download # noqa: PLC0415 + + from agent_cli.server.tts.backends.base import ( # noqa: PLC0415 + get_backend_cache_dir, + ) + from agent_cli.server.tts.backends.qwen import ( # noqa: PLC0415 + DEFAULT_QWEN_MODEL, + ) + + download_dir = cache_dir or get_backend_cache_dir("qwen-tts") + model_name = models[0] if models and models[0] != "qwen" else DEFAULT_QWEN_MODEL + console.print(f"[bold]Downloading Qwen3-TTS model: {model_name}...[/bold]") + console.print(" This may take a while (model is ~3GB)") + snapshot_download(repo_id=model_name, cache_dir=download_dir) + console.print("[bold green]Download complete![/bold green]") + return + # Piper backend from piper.download_voices import download_voice # noqa: PLC0415 @@ -479,7 +507,7 @@ def transcription_proxy_cmd( @app.command("tts") @requires_extras("server", "piper|kokoro") -def tts_cmd( # noqa: PLR0915 +def tts_cmd( # noqa: PLR0912, PLR0915 model: Annotated[ list[str] | None, typer.Option( @@ -573,7 +601,7 @@ def tts_cmd( # noqa: PLR0915 typer.Option( "--backend", "-b", - help="Backend: auto, piper, kokoro", + help="Backend: auto, piper, kokoro, qwen", ), ] = "auto", ) -> None: @@ -596,6 +624,11 @@ def tts_cmd( # noqa: PLR0915 Voices: af_heart, af_bella, am_adam, bf_emma, bm_george, etc. See https://huggingface.co/hexgrad/Kokoro-82M for all voices. + **Qwen backend** (GPU-accelerated, multilingual): + High-quality Qwen3-TTS with 10+ language support. + Voices: Vivian, Ryan, Serena, Dylan, Eric, Aiden, Ono_Anna, Sohee, etc. + See https://github.com/QwenLM/Qwen3-TTS for details. + Examples: # Run with Kokoro (auto-downloads model and voices) agent-cli server tts --backend kokoro @@ -603,6 +636,9 @@ def tts_cmd( # noqa: PLR0915 # Run with default Piper model agent-cli server tts --backend piper + # Run with Qwen3-TTS (multilingual, GPU-accelerated) + agent-cli server tts --backend qwen + # Run with specific Piper model and 10-minute TTL agent-cli server tts --model en_US-lessac-medium --ttl 600 @@ -616,7 +652,7 @@ def tts_cmd( # noqa: PLR0915 # Setup Rich logging for consistent output setup_rich_logging(log_level, console=console) - valid_backends = ("auto", "piper", "kokoro") + valid_backends = ("auto", "piper", "kokoro", "qwen") if backend not in valid_backends: err_console.print( f"[bold red]Error:[/bold red] --backend must be one of: {', '.join(valid_backends)}", @@ -640,7 +676,12 @@ def tts_cmd( # noqa: PLR0915 # Default model based on backend (Kokoro auto-downloads from HuggingFace) if model is None: - model = ["kokoro"] if resolved_backend == "kokoro" else ["en_US-lessac-medium"] + if resolved_backend == "kokoro": + model = ["kokoro"] + elif resolved_backend == "qwen": + model = ["qwen"] + else: + model = ["en_US-lessac-medium"] # Validate default model against model list if default_model is not None and default_model not in model: diff --git a/agent_cli/server/tts/backends/__init__.py b/agent_cli/server/tts/backends/__init__.py index ac05ca369..b9f925612 100644 --- a/agent_cli/server/tts/backends/__init__.py +++ b/agent_cli/server/tts/backends/__init__.py @@ -16,7 +16,7 @@ logger = logging.getLogger(__name__) -BackendType = Literal["piper", "kokoro", "auto"] +BackendType = Literal["piper", "kokoro", "qwen", "auto"] def detect_backend() -> Literal["piper", "kokoro"]: @@ -69,6 +69,11 @@ def create_backend( return KokoroBackend(config) + if backend_type == "qwen": + from agent_cli.server.tts.backends.qwen import QwenBackend # noqa: PLC0415 + + return QwenBackend(config) + msg = f"Unknown backend type: {backend_type}" raise ValueError(msg) diff --git a/agent_cli/server/tts/backends/qwen.py b/agent_cli/server/tts/backends/qwen.py new file mode 100644 index 000000000..5cf544200 --- /dev/null +++ b/agent_cli/server/tts/backends/qwen.py @@ -0,0 +1,330 @@ +"""Qwen3-TTS backend using qwen-tts library with subprocess isolation.""" + +from __future__ import annotations + +import asyncio +import io +import logging +import time +import wave +from concurrent.futures import ProcessPoolExecutor +from dataclasses import dataclass +from multiprocessing import get_context +from typing import Any, NoReturn + +from agent_cli.core.process import set_process_title +from agent_cli.server.tts.backends.base import ( + BackendConfig, + InvalidTextError, + SynthesisResult, + get_backend_cache_dir, + get_torch_device, +) + +logger = logging.getLogger(__name__) + +# Default model for Qwen3-TTS +DEFAULT_QWEN_MODEL = "Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice" + +# Default voice if none specified +DEFAULT_VOICE = "Vivian" + +# Qwen3-TTS sample rate (24kHz like Kokoro) +QWEN_SAMPLE_RATE = 24000 + +# Voice name mapping: OpenAI-style names -> Qwen speaker names +# Qwen supports: Vivian, Serena, Uncle_Fu, Dylan, Eric, Ryan, Aiden, Ono_Anna, Sohee +VOICE_MAP = { + # OpenAI-compatible names + "alloy": "Vivian", + "echo": "Ryan", + "fable": "Serena", + "onyx": "Uncle_Fu", + "nova": "Dylan", + "shimmer": "Ono_Anna", + # Direct Qwen names (pass-through) + "vivian": "Vivian", + "serena": "Serena", + "uncle_fu": "Uncle_Fu", + "dylan": "Dylan", + "eric": "Eric", + "ryan": "Ryan", + "aiden": "Aiden", + "ono_anna": "Ono_Anna", + "sohee": "Sohee", +} + +# Supported languages +SUPPORTED_LANGUAGES = [ + "Auto", + "English", + "Chinese", + "Japanese", + "Korean", + "German", + "French", + "Spanish", + "Russian", + "Portuguese", + "Italian", +] + + +# --- Subprocess state (only used within subprocess worker) --- + + +@dataclass +class _SubprocessState: + """Container for subprocess-local state. Not shared with main process.""" + + model: Any = None + device: str | None = None + model_name: str | None = None + + +_state = _SubprocessState() + + +# --- Subprocess worker functions (run in isolated process) --- + + +def _load_model_in_subprocess( + model_name: str, + device: str, + cache_dir: str, # noqa: ARG001 - kept for API consistency with other backends +) -> str: + """Load Qwen3-TTS model in subprocess. Returns actual device string.""" + import torch # noqa: PLC0415 + from qwen_tts import Qwen3TTSModel # noqa: PLC0415 + + set_process_title("tts-qwen") + + # Determine actual device + if device == "auto": + device = get_torch_device() + + # Determine dtype based on device + dtype = torch.bfloat16 if device in ("cuda", "mps") else torch.float32 + + logger.info("Loading Qwen3-TTS model '%s' on %s...", model_name, device) + + # Build kwargs for model loading + load_kwargs: dict[str, Any] = { + "dtype": dtype, + } + + # Add device_map for CUDA + if device == "cuda": + load_kwargs["device_map"] = "cuda:0" + # Try to use Flash Attention 2 if available + try: + load_kwargs["attn_implementation"] = "flash_attention_2" + except Exception: + logger.debug("Flash Attention 2 not available, using default attention") + elif device == "mps": + load_kwargs["device_map"] = "mps" + else: + load_kwargs["device_map"] = "cpu" + + # Load model + model = Qwen3TTSModel.from_pretrained(model_name, **load_kwargs) + + # Store in subprocess state for reuse + _state.model = model + _state.device = device + _state.model_name = model_name + + # Enable optimizations for CUDA + if device == "cuda" and torch.cuda.is_available(): + torch.backends.cudnn.benchmark = True + torch.backends.cuda.matmul.allow_tf32 = True + torch.backends.cudnn.allow_tf32 = True + logger.info("Enabled CUDA optimizations (cuDNN benchmark, TF32)") + + return device + + +def _synthesize_in_subprocess( + text: str, + voice: str | None, + language: str, + speed: float, +) -> dict[str, Any]: + """Synthesize text to audio in subprocess.""" + import numpy as np # noqa: PLC0415 + + if _state.model is None: + msg = "Model not loaded in subprocess" + raise RuntimeError(msg) + + # Map voice name + voice_name = voice or DEFAULT_VOICE + voice_lower = voice_name.lower() + speaker = VOICE_MAP.get(voice_lower, voice_name) + + # Generate speech + wavs, sr = _state.model.generate_custom_voice( + text=text, + language=language, + speaker=speaker, + ) + + if not wavs or len(wavs) == 0: + msg = "No audio generated" + raise RuntimeError(msg) + + audio = wavs[0] + + # Apply speed adjustment if needed (using librosa if available) + if speed != 1.0: + try: + import librosa # noqa: PLC0415 + + audio = librosa.effects.time_stretch(audio.astype(np.float32), rate=speed) + except ImportError: + logger.warning("Speed adjustment requested but librosa not available") + + # Convert to int16 WAV + audio_int16 = (audio * 32767).astype(np.int16) + + buffer = io.BytesIO() + with wave.open(buffer, "wb") as wav: + wav.setnchannels(1) + wav.setsampwidth(2) + wav.setframerate(sr) + wav.writeframes(audio_int16.tobytes()) + + return { + "audio": buffer.getvalue(), + "sample_rate": sr, + "duration": len(audio_int16) / sr, + } + + +class QwenBackend: + """Qwen3-TTS backend with subprocess isolation. + + Uses qwen-tts library for high-quality multilingual neural TTS on CUDA, MPS, or CPU. + Models auto-download from HuggingFace on first use. + Subprocess terminates on unload, releasing all GPU/CPU memory. + + Features: + - 9 premium voices with various gender, age, language combinations + - 10+ language support (Chinese, English, Japanese, Korean, German, French, etc.) + - Natural language instruction control (for CustomVoice model) + """ + + def __init__(self, config: BackendConfig) -> None: + """Initialize the Qwen backend.""" + self._config = config + self._executor: ProcessPoolExecutor | None = None + self._device: str | None = None + self._cache_dir = config.cache_dir or get_backend_cache_dir("qwen-tts") + # Use provided model name or default - "qwen" is the CLI placeholder for default + model_name = config.model_name + if not model_name or model_name == "qwen": + model_name = DEFAULT_QWEN_MODEL + self._model_name = model_name + + @property + def is_loaded(self) -> bool: + """Check if the model is currently loaded.""" + return self._executor is not None + + @property + def device(self) -> str | None: + """Get the device the model is loaded on.""" + return self._device + + async def load(self) -> float: + """Load model in subprocess. Downloads from HuggingFace if needed.""" + if self._executor is not None: + return 0.0 + + start_time = time.time() + ctx = get_context("spawn") + self._executor = ProcessPoolExecutor(max_workers=1, mp_context=ctx) + + loop = asyncio.get_running_loop() + self._device = await loop.run_in_executor( + self._executor, + _load_model_in_subprocess, + self._model_name, + self._config.device, + str(self._cache_dir), + ) + + load_duration = time.time() - start_time + logger.info("Loaded Qwen3-TTS model on %s in %.2fs", self._device, load_duration) + return load_duration + + async def unload(self) -> None: + """Shutdown subprocess, releasing all memory.""" + if self._executor is None: + return + self._executor.shutdown(wait=False, cancel_futures=True) + self._executor = None + self._device = None + logger.info("Qwen3-TTS model unloaded (subprocess terminated)") + + async def synthesize( + self, + text: str, + *, + voice: str | None = None, + speed: float = 1.0, + language: str = "Auto", + ) -> SynthesisResult: + """Synthesize text to audio. + + Args: + text: Text to synthesize. + voice: Voice to use (e.g., "Vivian", "Ryan", or OpenAI names like "alloy"). + speed: Speech speed multiplier (0.25 to 4.0). + language: Language hint (Auto, English, Chinese, Japanese, etc.). + + Returns: + SynthesisResult with audio data and metadata. + + """ + if self._executor is None: + msg = "Model not loaded. Call load() first." + raise RuntimeError(msg) + + if not text or not text.strip(): + msg = "Text cannot be empty" + raise InvalidTextError(msg) + + loop = asyncio.get_running_loop() + result = await loop.run_in_executor( + self._executor, + _synthesize_in_subprocess, + text, + voice, + language, + speed, + ) + + return SynthesisResult( + audio=result["audio"], + sample_rate=result["sample_rate"], + sample_width=2, + channels=1, + duration=result["duration"], + ) + + @property + def supports_streaming(self) -> bool: + """Qwen3-TTS backend does not currently support streaming synthesis.""" + return False + + def synthesize_stream( + self, + text: str, + *, + voice: str | None = None, + speed: float = 1.0, + ) -> NoReturn: + """Streaming is not currently supported by Qwen3-TTS backend.""" + msg = "Streaming synthesis is not yet supported by Qwen3-TTS backend" + raise NotImplementedError(msg) diff --git a/docs/commands/server/tts.md b/docs/commands/server/tts.md index a04580a26..cbb95f03f 100644 --- a/docs/commands/server/tts.md +++ b/docs/commands/server/tts.md @@ -4,10 +4,11 @@ icon: lucide/volume-2 # tts -Run a local TTS (Text-to-Speech) server with two backend options: +Run a local TTS (Text-to-Speech) server with three backend options: - **[Kokoro](https://huggingface.co/hexgrad/Kokoro-82M)** - High-quality neural TTS with GPU acceleration (CUDA/MPS/CPU) - **[Piper](https://github.com/rhasspy/piper)** - Fast, CPU-friendly ONNX-based synthesis +- **[Qwen3-TTS](https://github.com/QwenLM/Qwen3-TTS)** - Multilingual neural TTS with 10+ languages (GPU recommended) > [!NOTE] > **Quick Start with Kokoro** (GPU-accelerated, auto-downloads from HuggingFace): @@ -22,6 +23,12 @@ Run a local TTS (Text-to-Speech) server with two backend options: > agent-cli server tts --backend piper > ``` > +> **Quick Start with Qwen3-TTS** (multilingual, GPU-accelerated): +> ```bash +> pip install "agent-cli[qwen-tts]" +> agent-cli server tts --backend qwen +> ``` +> > Server runs at `http://localhost:10201`. Verify with `curl http://localhost:10201/health`. ## Features @@ -29,7 +36,7 @@ Run a local TTS (Text-to-Speech) server with two backend options: - **OpenAI-compatible API** at `/v1/audio/speech` - drop-in replacement for OpenAI's TTS API - **Wyoming protocol** for [Home Assistant](https://www.home-assistant.io/) voice integration - **TTL-based memory management** - models unload after idle period -- **Multiple backends** - Kokoro (GPU) or Piper (CPU) +- **Multiple backends** - Kokoro (GPU), Piper (CPU), or Qwen3-TTS (multilingual GPU) - **Auto-download** - Models and voices download automatically on first use - **Multiple voices** - Run different voices with independent TTLs @@ -48,6 +55,9 @@ agent-cli server tts --backend kokoro # Run with Piper (CPU-friendly) agent-cli server tts --backend piper +# Run with Qwen3-TTS (multilingual, GPU-accelerated) +agent-cli server tts --backend qwen + # Piper with specific voice and 10-minute TTL agent-cli server tts --backend piper --model en_US-ryan-high --ttl 600 @@ -223,6 +233,26 @@ With Piper, the **model name IS the voice**. Use `--model` to specify. Browse all voices at [rhasspy/piper](https://github.com/rhasspy/piper?tab=readme-ov-file#voices). +### Qwen3-TTS Voices + +Qwen voices are specified per-request via the API `voice` parameter. The model auto-downloads from HuggingFace on first use (~3GB). + +| Voice | Gender | Description | +|-------|--------|-------------| +| `Vivian` | Female | Default voice (clear, natural) | +| `Serena` | Female | Warm and friendly | +| `Ryan` | Male | Professional narrator | +| `Dylan` | Male | Casual conversational | +| `Eric` | Male | Clear and articulate | +| `Aiden` | Male | Young adult | +| `Ono_Anna` | Female | Japanese-accented English | +| `Sohee` | Female | Korean-accented English | +| `Uncle_Fu` | Male | Older, authoritative | + +OpenAI-compatible voice names (`alloy`, `echo`, `fable`, `onyx`, `nova`, `shimmer`) are automatically mapped to Qwen voices. + +**Supported Languages**: English, Chinese, Japanese, Korean, German, French, Spanish, Russian, Portuguese, Italian (use "Auto" for automatic detection). + ## Installation ### Kokoro (GPU-accelerated) @@ -244,3 +274,13 @@ pip install "agent-cli[piper]" # or uv sync --extra piper ``` + +### Qwen3-TTS (Multilingual GPU) + +```bash +pip install "agent-cli[qwen-tts]" +# or +uv sync --extra qwen-tts +``` + +Qwen3-TTS requires PyTorch and works best with CUDA. The model is ~3GB and auto-downloads from HuggingFace on first use. diff --git a/pyproject.toml b/pyproject.toml index eb750972c..2d2b273dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,6 +81,12 @@ kokoro = [ "pip", # Kokoro uses spacy which calls `pip install` to download language models at runtime "transformers>=4.40.0", # Ensure pre-built tokenizers wheels for Python 3.13+ ] +qwen-tts = [ + "fastapi[standard]", + "qwen-tts>=0.0.5", + "soundfile>=0.12.0", + "librosa>=0.10.0", # For speed adjustment +] speed = ["audiostretchy>=1.3.0"] # Development extras diff --git a/scripts/sync_extras.py b/scripts/sync_extras.py index c91f81185..67cb617ba 100755 --- a/scripts/sync_extras.py +++ b/scripts/sync_extras.py @@ -43,6 +43,12 @@ "tts-kokoro": ("Kokoro neural TTS", ["kokoro"]), "server": ("FastAPI server components", ["fastapi"]), "speed": ("Audio speed adjustment (audiostretchy)", ["audiostretchy"]), + # Server backend extras + "piper": ("Piper TTS backend (CPU-friendly)", ["piper"]), + "kokoro": ("Kokoro TTS backend (GPU-accelerated)", ["kokoro"]), + "qwen-tts": ("Qwen3-TTS backend (multilingual)", ["qwen_tts"]), + "faster-whisper": ("Faster Whisper ASR backend", ["faster_whisper"]), + "mlx-whisper": ("MLX Whisper ASR for Apple Silicon", ["mlx_whisper"]), } diff --git a/uv.lock b/uv.lock index 136620d5b..d6f89bd52 100644 --- a/uv.lock +++ b/uv.lock @@ -10,6 +10,24 @@ resolution-markers = [ "python_full_version < '3.12' and sys_platform != 'win32'", ] +[[package]] +name = "accelerate" +version = "1.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyyaml" }, + { name = "safetensors" }, + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4a/8e/ac2a9566747a93f8be36ee08532eb0160558b07630a081a6056a9f89bf1d/accelerate-1.12.0.tar.gz", hash = "sha256:70988c352feb481887077d2ab845125024b2a137a5090d6d7a32b57d03a45df6", size = 398399, upload-time = "2025-11-21T11:27:46.973Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/d2/c581486aa6c4fbd7394c23c47b83fa1a919d34194e16944241daf9e762dd/accelerate-1.12.0-py3-none-any.whl", hash = "sha256:3e2091cd341423207e2f084a6654b1efcd250dc326f2a37d6dde446e07cabb11", size = 380935, upload-time = "2025-11-21T11:27:44.522Z" }, +] + [[package]] name = "addict" version = "2.4.0" @@ -86,6 +104,12 @@ piper = [ { name = "fastapi", extra = ["standard"] }, { name = "piper-tts" }, ] +qwen-tts = [ + { name = "fastapi", extra = ["standard"] }, + { name = "librosa" }, + { name = "qwen-tts" }, + { name = "soundfile" }, +] rag = [ { name = "chromadb" }, { name = "fastapi", extra = ["standard"] }, @@ -144,6 +168,7 @@ requires-dist = [ { name = "fastapi", extras = ["standard"], marker = "extra == 'kokoro'" }, { name = "fastapi", extras = ["standard"], marker = "extra == 'memory'" }, { name = "fastapi", extras = ["standard"], marker = "extra == 'piper'" }, + { name = "fastapi", extras = ["standard"], marker = "extra == 'qwen-tts'" }, { name = "fastapi", extras = ["standard"], marker = "extra == 'rag'" }, { name = "fastapi", extras = ["standard"], marker = "extra == 'server'" }, { name = "faster-whisper", marker = "extra == 'faster-whisper'", specifier = ">=1.0.0" }, @@ -152,6 +177,7 @@ requires-dist = [ { name = "huggingface-hub", marker = "extra == 'memory'", specifier = ">=0.20.0" }, { name = "huggingface-hub", marker = "extra == 'rag'", specifier = ">=0.20.0" }, { name = "kokoro", marker = "extra == 'kokoro'", specifier = ">=0.9.0" }, + { name = "librosa", marker = "extra == 'qwen-tts'", specifier = ">=0.10.0" }, { name = "markdown-code-runner", marker = "extra == 'dev'", specifier = ">=2.7.0" }, { name = "markitdown", extras = ["docx", "pdf", "pptx"], marker = "extra == 'rag'", specifier = ">=0.1.3" }, { name = "mlx-whisper", marker = "platform_machine == 'arm64' and sys_platform == 'darwin' and extra == 'mlx-whisper'", specifier = ">=0.4.0" }, @@ -180,12 +206,14 @@ requires-dist = [ { name = "pytest-timeout", marker = "extra == 'dev'" }, { name = "pytest-timeout", marker = "extra == 'test'" }, { name = "pyyaml", marker = "extra == 'memory'", specifier = ">=6.0.0" }, + { name = "qwen-tts", marker = "extra == 'qwen-tts'", specifier = ">=0.0.5" }, { name = "rich" }, { name = "ruff", marker = "extra == 'dev'" }, { name = "setproctitle" }, { name = "silero-vad", marker = "extra == 'vad'", specifier = ">=5.1" }, { name = "sounddevice", marker = "extra == 'audio'", specifier = ">=0.4.6" }, { name = "soundfile", marker = "extra == 'kokoro'", specifier = ">=0.12.0" }, + { name = "soundfile", marker = "extra == 'qwen-tts'", specifier = ">=0.12.0" }, { name = "transformers", marker = "extra == 'kokoro'", specifier = ">=4.40.0" }, { name = "transformers", marker = "extra == 'memory'", specifier = ">=4.30.0" }, { name = "transformers", marker = "extra == 'rag'", specifier = ">=4.30.0" }, @@ -196,7 +224,7 @@ requires-dist = [ { name = "watchfiles", marker = "extra == 'rag'", specifier = ">=0.21.0" }, { name = "wyoming", marker = "extra == 'audio'", specifier = ">=1.5.2" }, ] -provides-extras = ["audio", "dev", "faster-whisper", "kokoro", "llm", "memory", "mlx-whisper", "piper", "rag", "server", "speed", "test", "vad"] +provides-extras = ["audio", "dev", "faster-whisper", "kokoro", "llm", "memory", "mlx-whisper", "piper", "qwen-tts", "rag", "server", "speed", "test", "vad"] [package.metadata.requires-dev] dev = [ @@ -218,6 +246,15 @@ dev = [ { name = "zensical" }, ] +[[package]] +name = "aiofiles" +version = "24.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/03/a88171e277e8caa88a4c77808c20ebb04ba74cc4681bf1e9416c862de237/aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c", size = 30247, upload-time = "2024-06-24T11:02:03.584Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/45/30bb92d442636f570cb5651bc661f52b610e2eec3f891a5dc3a4c3667db0/aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5", size = 15896, upload-time = "2024-06-24T11:02:01.529Z" }, +] + [[package]] name = "annotated-doc" version = "0.0.4" @@ -340,6 +377,59 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, ] +[[package]] +name = "audioop-lts" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/53/946db57842a50b2da2e0c1e34bd37f36f5aadba1a929a3971c5d7841dbca/audioop_lts-0.2.2.tar.gz", hash = "sha256:64d0c62d88e67b98a1a5e71987b7aa7b5bcffc7dcee65b635823dbdd0a8dbbd0", size = 30686, upload-time = "2025-08-05T16:43:17.409Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/d4/94d277ca941de5a507b07f0b592f199c22454eeaec8f008a286b3fbbacd6/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_universal2.whl", hash = "sha256:fd3d4602dc64914d462924a08c1a9816435a2155d74f325853c1f1ac3b2d9800", size = 46523, upload-time = "2025-08-05T16:42:20.836Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5a/656d1c2da4b555920ce4177167bfeb8623d98765594af59702c8873f60ec/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:550c114a8df0aafe9a05442a1162dfc8fec37e9af1d625ae6060fed6e756f303", size = 27455, upload-time = "2025-08-05T16:42:22.283Z" }, + { url = "https://files.pythonhosted.org/packages/1b/83/ea581e364ce7b0d41456fb79d6ee0ad482beda61faf0cab20cbd4c63a541/audioop_lts-0.2.2-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:9a13dc409f2564de15dd68be65b462ba0dde01b19663720c68c1140c782d1d75", size = 26997, upload-time = "2025-08-05T16:42:23.849Z" }, + { url = "https://files.pythonhosted.org/packages/b8/3b/e8964210b5e216e5041593b7d33e97ee65967f17c282e8510d19c666dab4/audioop_lts-0.2.2-cp313-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51c916108c56aa6e426ce611946f901badac950ee2ddaf302b7ed35d9958970d", size = 85844, upload-time = "2025-08-05T16:42:25.208Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2e/0a1c52faf10d51def20531a59ce4c706cb7952323b11709e10de324d6493/audioop_lts-0.2.2-cp313-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47eba38322370347b1c47024defbd36374a211e8dd5b0dcbce7b34fdb6f8847b", size = 85056, upload-time = "2025-08-05T16:42:26.559Z" }, + { url = "https://files.pythonhosted.org/packages/75/e8/cd95eef479656cb75ab05dfece8c1f8c395d17a7c651d88f8e6e291a63ab/audioop_lts-0.2.2-cp313-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba7c3a7e5f23e215cb271516197030c32aef2e754252c4c70a50aaff7031a2c8", size = 93892, upload-time = "2025-08-05T16:42:27.902Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1e/a0c42570b74f83efa5cca34905b3eef03f7ab09fe5637015df538a7f3345/audioop_lts-0.2.2-cp313-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:def246fe9e180626731b26e89816e79aae2276f825420a07b4a647abaa84becc", size = 96660, upload-time = "2025-08-05T16:42:28.9Z" }, + { url = "https://files.pythonhosted.org/packages/50/d5/8a0ae607ca07dbb34027bac8db805498ee7bfecc05fd2c148cc1ed7646e7/audioop_lts-0.2.2-cp313-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e160bf9df356d841bb6c180eeeea1834085464626dc1b68fa4e1d59070affdc3", size = 79143, upload-time = "2025-08-05T16:42:29.929Z" }, + { url = "https://files.pythonhosted.org/packages/12/17/0d28c46179e7910bfb0bb62760ccb33edb5de973052cb2230b662c14ca2e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4b4cd51a57b698b2d06cb9993b7ac8dfe89a3b2878e96bc7948e9f19ff51dba6", size = 84313, upload-time = "2025-08-05T16:42:30.949Z" }, + { url = "https://files.pythonhosted.org/packages/84/ba/bd5d3806641564f2024e97ca98ea8f8811d4e01d9b9f9831474bc9e14f9e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4a53aa7c16a60a6857e6b0b165261436396ef7293f8b5c9c828a3a203147ed4a", size = 93044, upload-time = "2025-08-05T16:42:31.959Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5e/435ce8d5642f1f7679540d1e73c1c42d933331c0976eb397d1717d7f01a3/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:3fc38008969796f0f689f1453722a0f463da1b8a6fbee11987830bfbb664f623", size = 78766, upload-time = "2025-08-05T16:42:33.302Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3b/b909e76b606cbfd53875693ec8c156e93e15a1366a012f0b7e4fb52d3c34/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_s390x.whl", hash = "sha256:15ab25dd3e620790f40e9ead897f91e79c0d3ce65fe193c8ed6c26cffdd24be7", size = 87640, upload-time = "2025-08-05T16:42:34.854Z" }, + { url = "https://files.pythonhosted.org/packages/30/e7/8f1603b4572d79b775f2140d7952f200f5e6c62904585d08a01f0a70393a/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:03f061a1915538fd96272bac9551841859dbb2e3bf73ebe4a23ef043766f5449", size = 86052, upload-time = "2025-08-05T16:42:35.839Z" }, + { url = "https://files.pythonhosted.org/packages/b5/96/c37846df657ccdda62ba1ae2b6534fa90e2e1b1742ca8dcf8ebd38c53801/audioop_lts-0.2.2-cp313-abi3-win32.whl", hash = "sha256:3bcddaaf6cc5935a300a8387c99f7a7fbbe212a11568ec6cf6e4bc458c048636", size = 26185, upload-time = "2025-08-05T16:42:37.04Z" }, + { url = "https://files.pythonhosted.org/packages/34/a5/9d78fdb5b844a83da8a71226c7bdae7cc638861085fff7a1d707cb4823fa/audioop_lts-0.2.2-cp313-abi3-win_amd64.whl", hash = "sha256:a2c2a947fae7d1062ef08c4e369e0ba2086049a5e598fda41122535557012e9e", size = 30503, upload-time = "2025-08-05T16:42:38.427Z" }, + { url = "https://files.pythonhosted.org/packages/34/25/20d8fde083123e90c61b51afb547bb0ea7e77bab50d98c0ab243d02a0e43/audioop_lts-0.2.2-cp313-abi3-win_arm64.whl", hash = "sha256:5f93a5db13927a37d2d09637ccca4b2b6b48c19cd9eda7b17a2e9f77edee6a6f", size = 24173, upload-time = "2025-08-05T16:42:39.704Z" }, + { url = "https://files.pythonhosted.org/packages/58/a7/0a764f77b5c4ac58dc13c01a580f5d32ae8c74c92020b961556a43e26d02/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:73f80bf4cd5d2ca7814da30a120de1f9408ee0619cc75da87d0641273d202a09", size = 47096, upload-time = "2025-08-05T16:42:40.684Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ed/ebebedde1a18848b085ad0fa54b66ceb95f1f94a3fc04f1cd1b5ccb0ed42/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:106753a83a25ee4d6f473f2be6b0966fc1c9af7e0017192f5531a3e7463dce58", size = 27748, upload-time = "2025-08-05T16:42:41.992Z" }, + { url = "https://files.pythonhosted.org/packages/cb/6e/11ca8c21af79f15dbb1c7f8017952ee8c810c438ce4e2b25638dfef2b02c/audioop_lts-0.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fbdd522624141e40948ab3e8cdae6e04c748d78710e9f0f8d4dae2750831de19", size = 27329, upload-time = "2025-08-05T16:42:42.987Z" }, + { url = "https://files.pythonhosted.org/packages/84/52/0022f93d56d85eec5da6b9da6a958a1ef09e80c39f2cc0a590c6af81dcbb/audioop_lts-0.2.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:143fad0311e8209ece30a8dbddab3b65ab419cbe8c0dde6e8828da25999be911", size = 92407, upload-time = "2025-08-05T16:42:44.336Z" }, + { url = "https://files.pythonhosted.org/packages/87/1d/48a889855e67be8718adbc7a01f3c01d5743c325453a5e81cf3717664aad/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfbbc74ec68a0fd08cfec1f4b5e8cca3d3cd7de5501b01c4b5d209995033cde9", size = 91811, upload-time = "2025-08-05T16:42:45.325Z" }, + { url = "https://files.pythonhosted.org/packages/98/a6/94b7213190e8077547ffae75e13ed05edc488653c85aa5c41472c297d295/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cfcac6aa6f42397471e4943e0feb2244549db5c5d01efcd02725b96af417f3fe", size = 100470, upload-time = "2025-08-05T16:42:46.468Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e9/78450d7cb921ede0cfc33426d3a8023a3bda755883c95c868ee36db8d48d/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:752d76472d9804ac60f0078c79cdae8b956f293177acd2316cd1e15149aee132", size = 103878, upload-time = "2025-08-05T16:42:47.576Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e2/cd5439aad4f3e34ae1ee852025dc6aa8f67a82b97641e390bf7bd9891d3e/audioop_lts-0.2.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:83c381767e2cc10e93e40281a04852facc4cd9334550e0f392f72d1c0a9c5753", size = 84867, upload-time = "2025-08-05T16:42:49.003Z" }, + { url = "https://files.pythonhosted.org/packages/68/4b/9d853e9076c43ebba0d411e8d2aa19061083349ac695a7d082540bad64d0/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c0022283e9556e0f3643b7c3c03f05063ca72b3063291834cca43234f20c60bb", size = 90001, upload-time = "2025-08-05T16:42:50.038Z" }, + { url = "https://files.pythonhosted.org/packages/58/26/4bae7f9d2f116ed5593989d0e521d679b0d583973d203384679323d8fa85/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a2d4f1513d63c795e82948e1305f31a6d530626e5f9f2605408b300ae6095093", size = 99046, upload-time = "2025-08-05T16:42:51.111Z" }, + { url = "https://files.pythonhosted.org/packages/b2/67/a9f4fb3e250dda9e9046f8866e9fa7d52664f8985e445c6b4ad6dfb55641/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:c9c8e68d8b4a56fda8c025e538e639f8c5953f5073886b596c93ec9b620055e7", size = 84788, upload-time = "2025-08-05T16:42:52.198Z" }, + { url = "https://files.pythonhosted.org/packages/70/f7/3de86562db0121956148bcb0fe5b506615e3bcf6e63c4357a612b910765a/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:96f19de485a2925314f5020e85911fb447ff5fbef56e8c7c6927851b95533a1c", size = 94472, upload-time = "2025-08-05T16:42:53.59Z" }, + { url = "https://files.pythonhosted.org/packages/f1/32/fd772bf9078ae1001207d2df1eef3da05bea611a87dd0e8217989b2848fa/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e541c3ef484852ef36545f66209444c48b28661e864ccadb29daddb6a4b8e5f5", size = 92279, upload-time = "2025-08-05T16:42:54.632Z" }, + { url = "https://files.pythonhosted.org/packages/4f/41/affea7181592ab0ab560044632571a38edaf9130b84928177823fbf3176a/audioop_lts-0.2.2-cp313-cp313t-win32.whl", hash = "sha256:d5e73fa573e273e4f2e5ff96f9043858a5e9311e94ffefd88a3186a910c70917", size = 26568, upload-time = "2025-08-05T16:42:55.627Z" }, + { url = "https://files.pythonhosted.org/packages/28/2b/0372842877016641db8fc54d5c88596b542eec2f8f6c20a36fb6612bf9ee/audioop_lts-0.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9191d68659eda01e448188f60364c7763a7ca6653ed3f87ebb165822153a8547", size = 30942, upload-time = "2025-08-05T16:42:56.674Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/baf2b9cc7e96c179bb4a54f30fcd83e6ecb340031bde68f486403f943768/audioop_lts-0.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c174e322bb5783c099aaf87faeb240c8d210686b04bd61dfd05a8e5a83d88969", size = 24603, upload-time = "2025-08-05T16:42:57.571Z" }, +] + +[[package]] +name = "audioread" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "standard-aifc", marker = "python_full_version >= '3.13'" }, + { name = "standard-sunau", marker = "python_full_version >= '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/4a/874ecf9b472f998130c2b5e145dcdb9f6131e84786111489103b66772143/audioread-3.1.0.tar.gz", hash = "sha256:1c4ab2f2972764c896a8ac61ac53e261c8d29f0c6ccd652f84e18f08a4cab190", size = 20082, upload-time = "2025-10-26T19:44:13.484Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/16/fbe8e1e185a45042f7cd3a282def5bb8d95bb69ab9e9ef6a5368aa17e426/audioread-3.1.0-py3-none-any.whl", hash = "sha256:b30d1df6c5d3de5dcef0fb0e256f6ea17bdcf5f979408df0297d8a408e2971b4", size = 23143, upload-time = "2025-10-26T19:44:12.016Z" }, +] + [[package]] name = "audiostretchy" version = "1.3.5" @@ -1215,6 +1305,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b0/0d/9feae160378a3553fa9a339b0e9c1a048e147a4127210e286ef18b730f03/durationpy-0.10-py3-none-any.whl", hash = "sha256:3b41e1b601234296b4fb368338fdcd3e13e0b4fb5b67345948f4f2bf9868b286", size = 3922, upload-time = "2025-05-17T13:52:36.463Z" }, ] +[[package]] +name = "einops" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/81/df4fbe24dff8ba3934af99044188e20a98ed441ad17a274539b74e82e126/einops-0.8.1.tar.gz", hash = "sha256:de5d960a7a761225532e0f1959e5315ebeafc0cd43394732f103ca44b9837e84", size = 54805, upload-time = "2025-02-09T03:17:00.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/62/9773de14fe6c45c23649e98b83231fffd7b9892b6cf863251dc2afa73643/einops-0.8.1-py3-none-any.whl", hash = "sha256:919387eb55330f5757c6bea9165c5ff5cfe63a642682ea788a6d472576d81737", size = 64359, upload-time = "2025-02-09T03:17:01.998Z" }, +] + [[package]] name = "email-validator" version = "2.3.0" @@ -1415,6 +1514,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463", size = 24024, upload-time = "2025-08-14T18:49:34.776Z" }, ] +[[package]] +name = "ffmpy" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/d2/1c4c582d71bcc65c76fa69fab85de6257d50fdf6fd4a2317c53917e9a581/ffmpy-1.0.0.tar.gz", hash = "sha256:b12932e95435c8820f1cd041024402765f821971e4bae753b327fc02a6e12f8b", size = 5101, upload-time = "2025-11-11T06:24:23.856Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/56/dd3669eccebb6d8ac81e624542ebd53fe6f08e1b8f2f8d50aeb7e3b83f99/ffmpy-1.0.0-py3-none-any.whl", hash = "sha256:5640e5f0fd03fb6236d0e119b16ccf6522db1c826fdf35dcb87087b60fd7504f", size = 5614, upload-time = "2025-11-11T06:24:22.818Z" }, +] + [[package]] name = "filelock" version = "3.20.3" @@ -1526,6 +1634,61 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c4/ab/09169d5a4612a5f92490806649ac8d41e3ec9129c636754575b3553f4ea4/googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038", size = 297515, upload-time = "2025-11-06T18:29:13.14Z" }, ] +[[package]] +name = "gradio" +version = "6.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiofiles" }, + { name = "anyio" }, + { name = "audioop-lts", marker = "python_full_version >= '3.13'" }, + { name = "brotli" }, + { name = "fastapi" }, + { name = "ffmpy" }, + { name = "gradio-client" }, + { name = "groovy" }, + { name = "httpx" }, + { name = "huggingface-hub" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "numpy" }, + { name = "orjson" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "pydantic" }, + { name = "pydub" }, + { name = "python-multipart" }, + { name = "pyyaml" }, + { name = "safehttpx" }, + { name = "semantic-version" }, + { name = "starlette" }, + { name = "tomlkit" }, + { name = "typer" }, + { name = "typing-extensions" }, + { name = "uvicorn" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/b9/982c467879cc4022bd53da16067a5cd5d70160abbc0f88089750bb148b26/gradio-6.4.0.tar.gz", hash = "sha256:9d01fe8ebbde2c172c6b6f8eab96f70d15b816137a9aefb920903b8b10a096c9", size = 40235414, upload-time = "2026-01-22T22:46:09.785Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/a2/039a41fa7d2b567ee39398059e84b2e7f2143a0463cc372aefde1ac907e8/gradio-6.4.0-py3-none-any.whl", hash = "sha256:4038e16b0561f8002a2769c281873b7cedff9c44ea0f1eba55f08d97bfec07a6", size = 24274909, upload-time = "2026-01-22T22:46:05.945Z" }, +] + +[[package]] +name = "gradio-client" +version = "2.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fsspec" }, + { name = "httpx" }, + { name = "huggingface-hub" }, + { name = "packaging" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d4/75/5c971cc80a6a477f038c66869178684c5010fd61b232277c120c61588d74/gradio_client-2.0.3.tar.gz", hash = "sha256:8f1cec02dccaf64ac0285ed60479a2b0db3778dfe74c85a36d7ec9a95daeccc4", size = 55027, upload-time = "2026-01-10T00:03:56.446Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/11/758b76a14e1783549c71828b36e81c997b99683bc4ec14b28417dff3348f/gradio_client-2.0.3-py3-none-any.whl", hash = "sha256:bcc88da74e3a387bcd41535578abbafe2091bcf4715c9542111804741b9e50b0", size = 55669, upload-time = "2026-01-10T00:03:54.262Z" }, +] + [[package]] name = "griffe" version = "1.15.0" @@ -1538,6 +1701,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl", hash = "sha256:6f6762661949411031f5fcda9593f586e6ce8340f0ba88921a0f2ef7a81eb9a3", size = 150705, upload-time = "2025-11-10T15:03:13.549Z" }, ] +[[package]] +name = "groovy" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/36/bbdede67400277bef33d3ec0e6a31750da972c469f75966b4930c753218f/groovy-0.1.2.tar.gz", hash = "sha256:25c1dc09b3f9d7e292458aa762c6beb96ea037071bf5e917fc81fb78d2231083", size = 17325, upload-time = "2025-02-28T20:24:56.068Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/27/3d6dcadc8a3214d8522c1e7f6a19554e33659be44546d44a2f7572ac7d2a/groovy-0.1.2-py3-none-any.whl", hash = "sha256:7f7975bab18c729a257a8b1ae9dcd70b7cafb1720481beae47719af57c35fa64", size = 14090, upload-time = "2025-02-28T20:24:55.152Z" }, +] + [[package]] name = "grpcio" version = "1.76.0" @@ -2238,6 +2410,44 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl", hash = "sha256:c629b661023a014c37da873b4ff58a817398d12635d3bbb2c5a03be7fe5d1e12", size = 113151, upload-time = "2025-10-27T18:25:54.882Z" }, ] +[[package]] +name = "lazy-loader" +version = "0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6b/c875b30a1ba490860c93da4cabf479e03f584eba06fe5963f6f6644653d8/lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1", size = 15431, upload-time = "2024-04-05T13:03:12.261Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097, upload-time = "2024-04-05T13:03:10.514Z" }, +] + +[[package]] +name = "librosa" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "audioread" }, + { name = "decorator" }, + { name = "joblib" }, + { name = "lazy-loader" }, + { name = "msgpack" }, + { name = "numba" }, + { name = "numpy" }, + { name = "pooch" }, + { name = "scikit-learn" }, + { name = "scipy" }, + { name = "soundfile" }, + { name = "soxr" }, + { name = "standard-aifc", marker = "python_full_version >= '3.13'" }, + { name = "standard-sunau", marker = "python_full_version >= '3.13'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/36/360b5aafa0238e29758729e9486c6ed92a6f37fa403b7875e06c115cdf4a/librosa-0.11.0.tar.gz", hash = "sha256:f5ed951ca189b375bbe2e33b2abd7e040ceeee302b9bbaeeffdfddb8d0ace908", size = 327001, upload-time = "2025-03-11T15:09:54.884Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/ba/c63c5786dfee4c3417094c4b00966e61e4a63efecee22cb7b4c0387dda83/librosa-0.11.0-py3-none-any.whl", hash = "sha256:0b6415c4fd68bff4c29288abe67c6d80b587e0e1e2cfb0aad23e4559504a7fa1", size = 260749, upload-time = "2025-03-11T15:09:52.982Z" }, +] + [[package]] name = "llvmlite" version = "0.46.0" @@ -2245,8 +2455,17 @@ source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/74/cd/08ae687ba099c7e3d21fe2ea536500563ef1943c5105bf6ab4ee3829f68e/llvmlite-0.46.0.tar.gz", hash = "sha256:227c9fd6d09dce2783c18b754b7cd9d9b3b3515210c46acc2d3c5badd9870ceb", size = 193456, upload-time = "2025-12-08T18:15:36.295Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7a/a1/2ad4b2367915faeebe8447f0a057861f646dbf5fbbb3561db42c65659cf3/llvmlite-0.46.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:82f3d39b16f19aa1a56d5fe625883a6ab600d5cc9ea8906cca70ce94cabba067", size = 37232766, upload-time = "2025-12-08T18:14:48.836Z" }, + { url = "https://files.pythonhosted.org/packages/12/b5/99cf8772fdd846c07da4fd70f07812a3c8fd17ea2409522c946bb0f2b277/llvmlite-0.46.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a3df43900119803bbc52720e758c76f316a9a0f34612a886862dfe0a5591a17e", size = 56275175, upload-time = "2025-12-08T18:14:51.604Z" }, + { url = "https://files.pythonhosted.org/packages/38/f2/ed806f9c003563732da156139c45d970ee435bd0bfa5ed8de87ba972b452/llvmlite-0.46.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de183fefc8022d21b0aa37fc3e90410bc3524aed8617f0ff76732fc6c3af5361", size = 55128630, upload-time = "2025-12-08T18:14:55.107Z" }, + { url = "https://files.pythonhosted.org/packages/19/0c/8f5a37a65fc9b7b17408508145edd5f86263ad69c19d3574e818f533a0eb/llvmlite-0.46.0-cp311-cp311-win_amd64.whl", hash = "sha256:e8b10bc585c58bdffec9e0c309bb7d51be1f2f15e169a4b4d42f2389e431eb93", size = 38138652, upload-time = "2025-12-08T18:14:58.171Z" }, { url = "https://files.pythonhosted.org/packages/2b/f8/4db016a5e547d4e054ff2f3b99203d63a497465f81ab78ec8eb2ff7b2304/llvmlite-0.46.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b9588ad4c63b4f0175a3984b85494f0c927c6b001e3a246a3a7fb3920d9a137", size = 37232767, upload-time = "2025-12-08T18:15:00.737Z" }, + { url = "https://files.pythonhosted.org/packages/aa/85/4890a7c14b4fa54400945cb52ac3cd88545bbdb973c440f98ca41591cdc5/llvmlite-0.46.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3535bd2bb6a2d7ae4012681ac228e5132cdb75fefb1bcb24e33f2f3e0c865ed4", size = 56275176, upload-time = "2025-12-08T18:15:03.936Z" }, + { url = "https://files.pythonhosted.org/packages/6a/07/3d31d39c1a1a08cd5337e78299fca77e6aebc07c059fbd0033e3edfab45c/llvmlite-0.46.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cbfd366e60ff87ea6cc62f50bc4cd800ebb13ed4c149466f50cf2163a473d1e", size = 55128630, upload-time = "2025-12-08T18:15:07.196Z" }, + { url = "https://files.pythonhosted.org/packages/2a/6b/d139535d7590a1bba1ceb68751bef22fadaa5b815bbdf0e858e3875726b2/llvmlite-0.46.0-cp312-cp312-win_amd64.whl", hash = "sha256:398b39db462c39563a97b912d4f2866cd37cba60537975a09679b28fbbc0fb38", size = 38138940, upload-time = "2025-12-08T18:15:10.162Z" }, { url = "https://files.pythonhosted.org/packages/e6/ff/3eba7eb0aed4b6fca37125387cd417e8c458e750621fce56d2c541f67fa8/llvmlite-0.46.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:30b60892d034bc560e0ec6654737aaa74e5ca327bd8114d82136aa071d611172", size = 37232767, upload-time = "2025-12-08T18:15:13.22Z" }, + { url = "https://files.pythonhosted.org/packages/0e/54/737755c0a91558364b9200702c3c9c15d70ed63f9b98a2c32f1c2aa1f3ba/llvmlite-0.46.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6cc19b051753368a9c9f31dc041299059ee91aceec81bd57b0e385e5d5bf1a54", size = 56275176, upload-time = "2025-12-08T18:15:16.339Z" }, + { url = "https://files.pythonhosted.org/packages/e6/91/14f32e1d70905c1c0aa4e6609ab5d705c3183116ca02ac6df2091868413a/llvmlite-0.46.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bca185892908f9ede48c0acd547fe4dc1bafefb8a4967d47db6cf664f9332d12", size = 55128629, upload-time = "2025-12-08T18:15:19.493Z" }, + { url = "https://files.pythonhosted.org/packages/4a/a7/d526ae86708cea531935ae777b6dbcabe7db52718e6401e0fb9c5edea80e/llvmlite-0.46.0-cp313-cp313-win_amd64.whl", hash = "sha256:67438fd30e12349ebb054d86a5a1a57fd5e87d264d2451bcfafbbbaa25b82a35", size = 38138941, upload-time = "2025-12-08T18:15:22.536Z" }, ] [[package]] @@ -2692,6 +2911,41 @@ 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 = "msgpack" +version = "1.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/f2/bfb55a6236ed8725a96b0aa3acbd0ec17588e6a2c3b62a93eb513ed8783f/msgpack-1.1.2.tar.gz", hash = "sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e", size = 173581, upload-time = "2025-10-08T09:15:56.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/97/560d11202bcd537abca693fd85d81cebe2107ba17301de42b01ac1677b69/msgpack-1.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2e86a607e558d22985d856948c12a3fa7b42efad264dca8a3ebbcfa2735d786c", size = 82271, upload-time = "2025-10-08T09:14:49.967Z" }, + { url = "https://files.pythonhosted.org/packages/83/04/28a41024ccbd67467380b6fb440ae916c1e4f25e2cd4c63abe6835ac566e/msgpack-1.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:283ae72fc89da59aa004ba147e8fc2f766647b1251500182fac0350d8af299c0", size = 84914, upload-time = "2025-10-08T09:14:50.958Z" }, + { url = "https://files.pythonhosted.org/packages/71/46/b817349db6886d79e57a966346cf0902a426375aadc1e8e7a86a75e22f19/msgpack-1.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61c8aa3bd513d87c72ed0b37b53dd5c5a0f58f2ff9f26e1555d3bd7948fb7296", size = 416962, upload-time = "2025-10-08T09:14:51.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/e0/6cc2e852837cd6086fe7d8406af4294e66827a60a4cf60b86575a4a65ca8/msgpack-1.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:454e29e186285d2ebe65be34629fa0e8605202c60fbc7c4c650ccd41870896ef", size = 426183, upload-time = "2025-10-08T09:14:53.477Z" }, + { url = "https://files.pythonhosted.org/packages/25/98/6a19f030b3d2ea906696cedd1eb251708e50a5891d0978b012cb6107234c/msgpack-1.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7bc8813f88417599564fafa59fd6f95be417179f76b40325b500b3c98409757c", size = 411454, upload-time = "2025-10-08T09:14:54.648Z" }, + { url = "https://files.pythonhosted.org/packages/b7/cd/9098fcb6adb32187a70b7ecaabf6339da50553351558f37600e53a4a2a23/msgpack-1.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bafca952dc13907bdfdedfc6a5f579bf4f292bdd506fadb38389afa3ac5b208e", size = 422341, upload-time = "2025-10-08T09:14:56.328Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ae/270cecbcf36c1dc85ec086b33a51a4d7d08fc4f404bdbc15b582255d05ff/msgpack-1.1.2-cp311-cp311-win32.whl", hash = "sha256:602b6740e95ffc55bfb078172d279de3773d7b7db1f703b2f1323566b878b90e", size = 64747, upload-time = "2025-10-08T09:14:57.882Z" }, + { url = "https://files.pythonhosted.org/packages/2a/79/309d0e637f6f37e83c711f547308b91af02b72d2326ddd860b966080ef29/msgpack-1.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:d198d275222dc54244bf3327eb8cbe00307d220241d9cec4d306d49a44e85f68", size = 71633, upload-time = "2025-10-08T09:14:59.177Z" }, + { url = "https://files.pythonhosted.org/packages/73/4d/7c4e2b3d9b1106cd0aa6cb56cc57c6267f59fa8bfab7d91df5adc802c847/msgpack-1.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:86f8136dfa5c116365a8a651a7d7484b65b13339731dd6faebb9a0242151c406", size = 64755, upload-time = "2025-10-08T09:15:00.48Z" }, + { url = "https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:70a0dff9d1f8da25179ffcf880e10cf1aad55fdb63cd59c9a49a1b82290062aa", size = 81939, upload-time = "2025-10-08T09:15:01.472Z" }, + { url = "https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:446abdd8b94b55c800ac34b102dffd2f6aa0ce643c55dfc017ad89347db3dbdb", size = 85064, upload-time = "2025-10-08T09:15:03.764Z" }, + { url = "https://files.pythonhosted.org/packages/f2/60/a064b0345fc36c4c3d2c743c82d9100c40388d77f0b48b2f04d6041dbec1/msgpack-1.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c63eea553c69ab05b6747901b97d620bb2a690633c77f23feb0c6a947a8a7b8f", size = 417131, upload-time = "2025-10-08T09:15:05.136Z" }, + { url = "https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:372839311ccf6bdaf39b00b61288e0557916c3729529b301c52c2d88842add42", size = 427556, upload-time = "2025-10-08T09:15:06.837Z" }, + { url = "https://files.pythonhosted.org/packages/f5/87/ffe21d1bf7d9991354ad93949286f643b2bb6ddbeab66373922b44c3b8cc/msgpack-1.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2929af52106ca73fcb28576218476ffbb531a036c2adbcf54a3664de124303e9", size = 404920, upload-time = "2025-10-08T09:15:08.179Z" }, + { url = "https://files.pythonhosted.org/packages/ff/41/8543ed2b8604f7c0d89ce066f42007faac1eaa7d79a81555f206a5cdb889/msgpack-1.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be52a8fc79e45b0364210eef5234a7cf8d330836d0a64dfbb878efa903d84620", size = 415013, upload-time = "2025-10-08T09:15:09.83Z" }, + { url = "https://files.pythonhosted.org/packages/41/0d/2ddfaa8b7e1cee6c490d46cb0a39742b19e2481600a7a0e96537e9c22f43/msgpack-1.1.2-cp312-cp312-win32.whl", hash = "sha256:1fff3d825d7859ac888b0fbda39a42d59193543920eda9d9bea44d958a878029", size = 65096, upload-time = "2025-10-08T09:15:11.11Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ec/d431eb7941fb55a31dd6ca3404d41fbb52d99172df2e7707754488390910/msgpack-1.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1de460f0403172cff81169a30b9a92b260cb809c4cb7e2fc79ae8d0510c78b6b", size = 72708, upload-time = "2025-10-08T09:15:12.554Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/5b1a1f70eb0e87d1678e9624908f86317787b536060641d6798e3cf70ace/msgpack-1.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:be5980f3ee0e6bd44f3a9e9dea01054f175b50c3e6cdb692bc9424c0bbb8bf69", size = 64119, upload-time = "2025-10-08T09:15:13.589Z" }, + { url = "https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4efd7b5979ccb539c221a4c4e16aac1a533efc97f3b759bb5a5ac9f6d10383bf", size = 81212, upload-time = "2025-10-08T09:15:14.552Z" }, + { url = "https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42eefe2c3e2af97ed470eec850facbe1b5ad1d6eacdbadc42ec98e7dcf68b4b7", size = 84315, upload-time = "2025-10-08T09:15:15.543Z" }, + { url = "https://files.pythonhosted.org/packages/d3/68/93180dce57f684a61a88a45ed13047558ded2be46f03acb8dec6d7c513af/msgpack-1.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fdf7d83102bf09e7ce3357de96c59b627395352a4024f6e2458501f158bf999", size = 412721, upload-time = "2025-10-08T09:15:16.567Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fac4be746328f90caa3cd4bc67e6fe36ca2bf61d5c6eb6d895b6527e3f05071e", size = 424657, upload-time = "2025-10-08T09:15:17.825Z" }, + { url = "https://files.pythonhosted.org/packages/38/f8/4398c46863b093252fe67368b44edc6c13b17f4e6b0e4929dbf0bdb13f23/msgpack-1.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fffee09044073e69f2bad787071aeec727183e7580443dfeb8556cbf1978d162", size = 402668, upload-time = "2025-10-08T09:15:19.003Z" }, + { url = "https://files.pythonhosted.org/packages/28/ce/698c1eff75626e4124b4d78e21cca0b4cc90043afb80a507626ea354ab52/msgpack-1.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5928604de9b032bc17f5099496417f113c45bc6bc21b5c6920caf34b3c428794", size = 419040, upload-time = "2025-10-08T09:15:20.183Z" }, + { url = "https://files.pythonhosted.org/packages/67/32/f3cd1667028424fa7001d82e10ee35386eea1408b93d399b09fb0aa7875f/msgpack-1.1.2-cp313-cp313-win32.whl", hash = "sha256:a7787d353595c7c7e145e2331abf8b7ff1e6673a6b974ded96e6d4ec09f00c8c", size = 65037, upload-time = "2025-10-08T09:15:21.416Z" }, + { url = "https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:a465f0dceb8e13a487e54c07d04ae3ba131c7c5b95e2612596eafde1dccf64a9", size = 72631, upload-time = "2025-10-08T09:15:22.431Z" }, + { url = "https://files.pythonhosted.org/packages/e5/db/0314e4e2db56ebcf450f277904ffd84a7988b9e5da8d0d61ab2d057df2b6/msgpack-1.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:e69b39f8c0aa5ec24b57737ebee40be647035158f14ed4b40e6f150077e21a84", size = 64118, upload-time = "2025-10-08T09:15:23.402Z" }, +] + [[package]] name = "murmurhash" version = "1.0.15" @@ -2859,14 +3113,23 @@ name = "numba" version = "0.63.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "llvmlite", marker = "sys_platform != 'win32'" }, - { name = "numpy", marker = "sys_platform != 'win32'" }, + { name = "llvmlite" }, + { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/dc/60/0145d479b2209bd8fdae5f44201eceb8ce5a23e0ed54c71f57db24618665/numba-0.63.1.tar.gz", hash = "sha256:b320aa675d0e3b17b40364935ea52a7b1c670c9037c39cf92c49502a75902f4b", size = 2761666, upload-time = "2025-12-10T02:57:39.002Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/70/90/5f8614c165d2e256fbc6c57028519db6f32e4982475a372bbe550ea0454c/numba-0.63.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b33db00f18ccc790ee9911ce03fcdfe9d5124637d1ecc266f5ae0df06e02fec3", size = 2680501, upload-time = "2025-12-10T02:57:09.797Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9d/d0afc4cf915edd8eadd9b2ab5b696242886ee4f97720d9322650d66a88c6/numba-0.63.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7d31ea186a78a7c0f6b1b2a3fe68057fdb291b045c52d86232b5383b6cf4fc25", size = 3744945, upload-time = "2025-12-10T02:57:11.697Z" }, + { url = "https://files.pythonhosted.org/packages/05/a9/d82f38f2ab73f3be6f838a826b545b80339762ee8969c16a8bf1d39395a8/numba-0.63.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed3bb2fbdb651d6aac394388130a7001aab6f4541837123a4b4ab8b02716530c", size = 3450827, upload-time = "2025-12-10T02:57:13.709Z" }, + { url = "https://files.pythonhosted.org/packages/18/3f/a9b106e93c5bd7434e65f044bae0d204e20aa7f7f85d72ceb872c7c04216/numba-0.63.1-cp311-cp311-win_amd64.whl", hash = "sha256:1ecbff7688f044b1601be70113e2fb1835367ee0b28ffa8f3adf3a05418c5c87", size = 2747262, upload-time = "2025-12-10T02:57:15.664Z" }, { url = "https://files.pythonhosted.org/packages/14/9c/c0974cd3d00ff70d30e8ff90522ba5fbb2bcee168a867d2321d8d0457676/numba-0.63.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2819cd52afa5d8d04e057bdfd54367575105f8829350d8fb5e4066fb7591cc71", size = 2680981, upload-time = "2025-12-10T02:57:17.579Z" }, + { url = "https://files.pythonhosted.org/packages/cb/70/ea2bc45205f206b7a24ee68a159f5097c9ca7e6466806e7c213587e0c2b1/numba-0.63.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5cfd45dbd3d409e713b1ccfdc2ee72ca82006860254429f4ef01867fdba5845f", size = 3801656, upload-time = "2025-12-10T02:57:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/0d/82/4f4ba4fd0f99825cbf3cdefd682ca3678be1702b63362011de6e5f71f831/numba-0.63.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69a599df6976c03b7ecf15d05302696f79f7e6d10d620367407517943355bcb0", size = 3501857, upload-time = "2025-12-10T02:57:20.721Z" }, + { url = "https://files.pythonhosted.org/packages/af/fd/6540456efa90b5f6604a86ff50dabefb187e43557e9081adcad3be44f048/numba-0.63.1-cp312-cp312-win_amd64.whl", hash = "sha256:bbad8c63e4fc7eb3cdb2c2da52178e180419f7969f9a685f283b313a70b92af3", size = 2750282, upload-time = "2025-12-10T02:57:22.474Z" }, { url = "https://files.pythonhosted.org/packages/57/f7/e19e6eff445bec52dde5bed1ebb162925a8e6f988164f1ae4b3475a73680/numba-0.63.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:0bd4fd820ef7442dcc07da184c3f54bb41d2bdb7b35bacf3448e73d081f730dc", size = 2680954, upload-time = "2025-12-10T02:57:24.145Z" }, + { url = "https://files.pythonhosted.org/packages/e9/6c/1e222edba1e20e6b113912caa9b1665b5809433cbcb042dfd133c6f1fd38/numba-0.63.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:53de693abe4be3bd4dee38e1c55f01c55ff644a6a3696a3670589e6e4c39cde2", size = 3809736, upload-time = "2025-12-10T02:57:25.836Z" }, + { url = "https://files.pythonhosted.org/packages/76/0a/590bad11a8b3feeac30a24d01198d46bdb76ad15c70d3a530691ce3cae58/numba-0.63.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:81227821a72a763c3d4ac290abbb4371d855b59fdf85d5af22a47c0e86bf8c7e", size = 3508854, upload-time = "2025-12-10T02:57:27.438Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f5/3800384a24eed1e4d524669cdbc0b9b8a628800bb1e90d7bd676e5f22581/numba-0.63.1-cp313-cp313-win_amd64.whl", hash = "sha256:eb227b07c2ac37b09432a9bda5142047a2d1055646e089d4a240a2643e508102", size = 2750228, upload-time = "2025-12-10T02:57:30.36Z" }, ] [[package]] @@ -3274,6 +3537,47 @@ 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 = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, + { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, + { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, +] + [[package]] name = "pandocfilters" version = "1.5.1" @@ -3437,6 +3741,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "pooch" +version = "1.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "platformdirs" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/77/b3d3e00c696c16cf99af81ef7b1f5fe73bd2a307abca41bd7605429fe6e5/pooch-1.8.2.tar.gz", hash = "sha256:76561f0de68a01da4df6af38e9955c4c9d1a5c90da73f7e40276a5728ec83d10", size = 59353, upload-time = "2024-06-06T16:53:46.224Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/87/77cc11c7a9ea9fd05503def69e3d18605852cd0d4b0d3b8f15bbeb3ef1d1/pooch-1.8.2-py3-none-any.whl", hash = "sha256:3529a57096f7198778a5ceefd5ac3ef0e4d06a6ddaf9fc2d609b806f25302c47", size = 64574, upload-time = "2024-06-06T16:53:44.343Z" }, +] + [[package]] name = "posthog" version = "5.4.0" @@ -3910,6 +4228,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" }, ] +[[package]] +name = "pydub" +version = "0.25.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/9a/e6bca0eed82db26562c73b5076539a4a08d3cffd19c3cc5913a3e61145fd/pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f", size = 38326, upload-time = "2021-03-10T02:09:54.659Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/53/d78dc063216e62fc55f6b2eebb447f6a4b0a59f55c8406376f76bf959b08/pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6", size = 32327, upload-time = "2021-03-10T02:09:53.503Z" }, +] + [[package]] name = "pygments" version = "2.19.2" @@ -4116,6 +4443,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/4f/00be2196329ebbff56ce564aa94efb0fbc828d00de250b1980de1a34ab49/python_pptx-1.0.2-py3-none-any.whl", hash = "sha256:160838e0b8565a8b1f67947675886e9fea18aa5e795db7ae531606d68e785cba", size = 472788, upload-time = "2024-08-07T17:33:28.192Z" }, ] +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + [[package]] name = "pywinpty" version = "3.0.2" @@ -4213,6 +4549,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" }, ] +[[package]] +name = "qwen-tts" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "accelerate" }, + { name = "einops" }, + { name = "gradio" }, + { name = "librosa" }, + { name = "onnxruntime" }, + { name = "soundfile" }, + { name = "sox" }, + { name = "torchaudio" }, + { name = "transformers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4a/89/a980e442819ca37b9caf5d8b9b5fc43ad63a33a7909a54218e732205d539/qwen_tts-0.0.5.tar.gz", hash = "sha256:7299bdc081dff8c7a5e5a1827a0225d960957e3349ac949d46018e6ca573db0d", size = 113866, upload-time = "2026-01-23T06:09:56.625Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/15/804f94d4cede62a9306de05d089a7ea9f8e4b7bb00fbad7d8d9937d788b6/qwen_tts-0.0.5-py3-none-any.whl", hash = "sha256:000db7a1ea9f582b5a2bc1af11358134bbf36876f3a270a905b7dbbb658731c8", size = 113320, upload-time = "2026-01-23T06:09:54.512Z" }, +] + [[package]] name = "rdflib" version = "7.5.0" @@ -4590,6 +4946,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c4/1c/1dbe51782c0e1e9cfce1d1004752672d2d4629ea46945d19d731ad772b3b/ruff-0.14.11-py3-none-win_arm64.whl", hash = "sha256:649fb6c9edd7f751db276ef42df1f3df41c38d67d199570ae2a7bd6cbc3590f0", size = 12938644, upload-time = "2026-01-08T19:11:50.027Z" }, ] +[[package]] +name = "safehttpx" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/d1/4282284d9cf1ee873607a46442da977fc3c985059315ab23610be31d5885/safehttpx-0.1.7.tar.gz", hash = "sha256:db201c0978c41eddb8bb480f3eee59dd67304fdd91646035e9d9a720049a9d23", size = 10385, upload-time = "2025-10-24T18:30:09.783Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/a3/0f0b7d78e2f1eb9e8e1afbff1d2bff8d60144aee17aca51c065b516743dd/safehttpx-0.1.7-py3-none-any.whl", hash = "sha256:c4f4a162db6993464d7ca3d7cc4af0ffc6515a606dfd220b9f82c6945d869cde", size = 8959, upload-time = "2025-10-24T18:30:08.733Z" }, +] + [[package]] name = "safetensors" version = "0.7.0" @@ -4612,23 +4980,93 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5d/e6/ec8471c8072382cb91233ba7267fd931219753bb43814cbc71757bfd4dab/safetensors-0.7.0-cp38-abi3-win_amd64.whl", hash = "sha256:d1239932053f56f3456f32eb9625590cc7582e905021f94636202a864d470755", size = 341380, upload-time = "2025-11-19T15:18:44.427Z" }, ] +[[package]] +name = "scikit-learn" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/d4/40988bf3b8e34feec1d0e6a051446b1f66225f8529b9309becaeef62b6c4/scikit_learn-1.8.0.tar.gz", hash = "sha256:9bccbb3b40e3de10351f8f5068e105d0f4083b1a65fa07b6634fbc401a6287fd", size = 7335585, upload-time = "2025-12-10T07:08:53.618Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/92/53ea2181da8ac6bf27170191028aee7251f8f841f8d3edbfdcaf2008fde9/scikit_learn-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:146b4d36f800c013d267b29168813f7a03a43ecd2895d04861f1240b564421da", size = 8595835, upload-time = "2025-12-10T07:07:39.385Z" }, + { url = "https://files.pythonhosted.org/packages/01/18/d154dc1638803adf987910cdd07097d9c526663a55666a97c124d09fb96a/scikit_learn-1.8.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:f984ca4b14914e6b4094c5d52a32ea16b49832c03bd17a110f004db3c223e8e1", size = 8080381, upload-time = "2025-12-10T07:07:41.93Z" }, + { url = "https://files.pythonhosted.org/packages/8a/44/226142fcb7b7101e64fdee5f49dbe6288d4c7af8abf593237b70fca080a4/scikit_learn-1.8.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e30adb87f0cc81c7690a84f7932dd66be5bac57cfe16b91cb9151683a4a2d3b", size = 8799632, upload-time = "2025-12-10T07:07:43.899Z" }, + { url = "https://files.pythonhosted.org/packages/36/4d/4a67f30778a45d542bbea5db2dbfa1e9e100bf9ba64aefe34215ba9f11f6/scikit_learn-1.8.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ada8121bcb4dac28d930febc791a69f7cb1673c8495e5eee274190b73a4559c1", size = 9103788, upload-time = "2025-12-10T07:07:45.982Z" }, + { url = "https://files.pythonhosted.org/packages/89/3c/45c352094cfa60050bcbb967b1faf246b22e93cb459f2f907b600f2ceda5/scikit_learn-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:c57b1b610bd1f40ba43970e11ce62821c2e6569e4d74023db19c6b26f246cb3b", size = 8081706, upload-time = "2025-12-10T07:07:48.111Z" }, + { url = "https://files.pythonhosted.org/packages/3d/46/5416595bb395757f754feb20c3d776553a386b661658fb21b7c814e89efe/scikit_learn-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:2838551e011a64e3053ad7618dda9310175f7515f1742fa2d756f7c874c05961", size = 7688451, upload-time = "2025-12-10T07:07:49.873Z" }, + { url = "https://files.pythonhosted.org/packages/90/74/e6a7cc4b820e95cc38cf36cd74d5aa2b42e8ffc2d21fe5a9a9c45c1c7630/scikit_learn-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5fb63362b5a7ddab88e52b6dbb47dac3fd7dafeee740dc6c8d8a446ddedade8e", size = 8548242, upload-time = "2025-12-10T07:07:51.568Z" }, + { url = "https://files.pythonhosted.org/packages/49/d8/9be608c6024d021041c7f0b3928d4749a706f4e2c3832bbede4fb4f58c95/scikit_learn-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5025ce924beccb28298246e589c691fe1b8c1c96507e6d27d12c5fadd85bfd76", size = 8079075, upload-time = "2025-12-10T07:07:53.697Z" }, + { url = "https://files.pythonhosted.org/packages/dd/47/f187b4636ff80cc63f21cd40b7b2d177134acaa10f6bb73746130ee8c2e5/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4496bb2cf7a43ce1a2d7524a79e40bc5da45cf598dbf9545b7e8316ccba47bb4", size = 8660492, upload-time = "2025-12-10T07:07:55.574Z" }, + { url = "https://files.pythonhosted.org/packages/97/74/b7a304feb2b49df9fafa9382d4d09061a96ee9a9449a7cbea7988dda0828/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0bcfe4d0d14aec44921545fd2af2338c7471de9cb701f1da4c9d85906ab847a", size = 8931904, upload-time = "2025-12-10T07:07:57.666Z" }, + { url = "https://files.pythonhosted.org/packages/9f/c4/0ab22726a04ede56f689476b760f98f8f46607caecff993017ac1b64aa5d/scikit_learn-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:35c007dedb2ffe38fe3ee7d201ebac4a2deccd2408e8621d53067733e3c74809", size = 8019359, upload-time = "2025-12-10T07:07:59.838Z" }, + { url = "https://files.pythonhosted.org/packages/24/90/344a67811cfd561d7335c1b96ca21455e7e472d281c3c279c4d3f2300236/scikit_learn-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:8c497fff237d7b4e07e9ef1a640887fa4fb765647f86fbe00f969ff6280ce2bb", size = 7641898, upload-time = "2025-12-10T07:08:01.36Z" }, + { url = "https://files.pythonhosted.org/packages/03/aa/e22e0768512ce9255eba34775be2e85c2048da73da1193e841707f8f039c/scikit_learn-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d6ae97234d5d7079dc0040990a6f7aeb97cb7fa7e8945f1999a429b23569e0a", size = 8513770, upload-time = "2025-12-10T07:08:03.251Z" }, + { url = "https://files.pythonhosted.org/packages/58/37/31b83b2594105f61a381fc74ca19e8780ee923be2d496fcd8d2e1147bd99/scikit_learn-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:edec98c5e7c128328124a029bceb09eda2d526997780fef8d65e9a69eead963e", size = 8044458, upload-time = "2025-12-10T07:08:05.336Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5a/3f1caed8765f33eabb723596666da4ebbf43d11e96550fb18bdec42b467b/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:74b66d8689d52ed04c271e1329f0c61635bcaf5b926db9b12d58914cdc01fe57", size = 8610341, upload-time = "2025-12-10T07:08:07.732Z" }, + { url = "https://files.pythonhosted.org/packages/38/cf/06896db3f71c75902a8e9943b444a56e727418f6b4b4a90c98c934f51ed4/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8fdf95767f989b0cfedb85f7ed8ca215d4be728031f56ff5a519ee1e3276dc2e", size = 8900022, upload-time = "2025-12-10T07:08:09.862Z" }, + { url = "https://files.pythonhosted.org/packages/1c/f9/9b7563caf3ec8873e17a31401858efab6b39a882daf6c1bfa88879c0aa11/scikit_learn-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:2de443b9373b3b615aec1bb57f9baa6bb3a9bd093f1269ba95c17d870422b271", size = 7989409, upload-time = "2025-12-10T07:08:12.028Z" }, + { url = "https://files.pythonhosted.org/packages/49/bd/1f4001503650e72c4f6009ac0c4413cb17d2d601cef6f71c0453da2732fc/scikit_learn-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:eddde82a035681427cbedded4e6eff5e57fa59216c2e3e90b10b19ab1d0a65c3", size = 7619760, upload-time = "2025-12-10T07:08:13.688Z" }, + { url = "https://files.pythonhosted.org/packages/d2/7d/a630359fc9dcc95496588c8d8e3245cc8fd81980251079bc09c70d41d951/scikit_learn-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7cc267b6108f0a1499a734167282c00c4ebf61328566b55ef262d48e9849c735", size = 8826045, upload-time = "2025-12-10T07:08:15.215Z" }, + { url = "https://files.pythonhosted.org/packages/cc/56/a0c86f6930cfcd1c7054a2bc417e26960bb88d32444fe7f71d5c2cfae891/scikit_learn-1.8.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:fe1c011a640a9f0791146011dfd3c7d9669785f9fed2b2a5f9e207536cf5c2fd", size = 8420324, upload-time = "2025-12-10T07:08:17.561Z" }, + { url = "https://files.pythonhosted.org/packages/46/1e/05962ea1cebc1cf3876667ecb14c283ef755bf409993c5946ade3b77e303/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72358cce49465d140cc4e7792015bb1f0296a9742d5622c67e31399b75468b9e", size = 8680651, upload-time = "2025-12-10T07:08:19.952Z" }, + { url = "https://files.pythonhosted.org/packages/fe/56/a85473cd75f200c9759e3a5f0bcab2d116c92a8a02ee08ccd73b870f8bb4/scikit_learn-1.8.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:80832434a6cc114f5219211eec13dcbc16c2bac0e31ef64c6d346cde3cf054cb", size = 8925045, upload-time = "2025-12-10T07:08:22.11Z" }, + { url = "https://files.pythonhosted.org/packages/cc/b7/64d8cfa896c64435ae57f4917a548d7ac7a44762ff9802f75a79b77cb633/scikit_learn-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ee787491dbfe082d9c3013f01f5991658b0f38aa8177e4cd4bf434c58f551702", size = 8507994, upload-time = "2025-12-10T07:08:23.943Z" }, + { url = "https://files.pythonhosted.org/packages/5e/37/e192ea709551799379958b4c4771ec507347027bb7c942662c7fbeba31cb/scikit_learn-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf97c10a3f5a7543f9b88cbf488d33d175e9146115a451ae34568597ba33dcde", size = 7869518, upload-time = "2025-12-10T07:08:25.71Z" }, +] + [[package]] name = "scipy" version = "1.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", marker = "sys_platform != 'win32'" }, + { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/56/3e/9cca699f3486ce6bc12ff46dc2031f1ec8eb9ccc9a320fdaf925f1417426/scipy-1.17.0.tar.gz", hash = "sha256:2591060c8e648d8b96439e111ac41fd8342fdeff1876be2e19dea3fe8930454e", size = 30396830, upload-time = "2026-01-10T21:34:23.009Z" } wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/4b/c89c131aa87cad2b77a54eb0fb94d633a842420fa7e919dc2f922037c3d8/scipy-1.17.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:2abd71643797bd8a106dff97894ff7869eeeb0af0f7a5ce02e4227c6a2e9d6fd", size = 31381316, upload-time = "2026-01-10T21:24:33.42Z" }, { url = "https://files.pythonhosted.org/packages/5e/5f/a6b38f79a07d74989224d5f11b55267714707582908a5f1ae854cf9a9b84/scipy-1.17.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:ef28d815f4d2686503e5f4f00edc387ae58dfd7a2f42e348bb53359538f01558", size = 27966760, upload-time = "2026-01-10T21:24:38.911Z" }, { url = "https://files.pythonhosted.org/packages/c1/20/095ad24e031ee8ed3c5975954d816b8e7e2abd731e04f8be573de8740885/scipy-1.17.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:272a9f16d6bb4667e8b50d25d71eddcc2158a214df1b566319298de0939d2ab7", size = 20138701, upload-time = "2026-01-10T21:24:43.249Z" }, + { url = "https://files.pythonhosted.org/packages/89/11/4aad2b3858d0337756f3323f8960755704e530b27eb2a94386c970c32cbe/scipy-1.17.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:7204fddcbec2fe6598f1c5fdf027e9f259106d05202a959a9f1aecf036adc9f6", size = 22480574, upload-time = "2026-01-10T21:24:47.266Z" }, + { url = "https://files.pythonhosted.org/packages/85/bd/f5af70c28c6da2227e510875cadf64879855193a687fb19951f0f44cfd6b/scipy-1.17.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc02c37a5639ee67d8fb646ffded6d793c06c5622d36b35cfa8fe5ececb8f042", size = 32862414, upload-time = "2026-01-10T21:24:52.566Z" }, + { url = "https://files.pythonhosted.org/packages/ef/df/df1457c4df3826e908879fe3d76bc5b6e60aae45f4ee42539512438cfd5d/scipy-1.17.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dac97a27520d66c12a34fd90a4fe65f43766c18c0d6e1c0a80f114d2260080e4", size = 35112380, upload-time = "2026-01-10T21:24:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/5f/bb/88e2c16bd1dd4de19d80d7c5e238387182993c2fb13b4b8111e3927ad422/scipy-1.17.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ebb7446a39b3ae0fe8f416a9a3fdc6fba3f11c634f680f16a239c5187bc487c0", size = 34922676, upload-time = "2026-01-10T21:25:04.287Z" }, + { url = "https://files.pythonhosted.org/packages/02/ba/5120242cc735f71fc002cff0303d536af4405eb265f7c60742851e7ccfe9/scipy-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:474da16199f6af66601a01546144922ce402cb17362e07d82f5a6cf8f963e449", size = 37507599, upload-time = "2026-01-10T21:25:09.851Z" }, + { url = "https://files.pythonhosted.org/packages/52/c8/08629657ac6c0da198487ce8cd3de78e02cfde42b7f34117d56a3fe249dc/scipy-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:255c0da161bd7b32a6c898e7891509e8a9289f0b1c6c7d96142ee0d2b114c2ea", size = 36380284, upload-time = "2026-01-10T21:25:15.632Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4a/465f96d42c6f33ad324a40049dfd63269891db9324aa66c4a1c108c6f994/scipy-1.17.0-cp311-cp311-win_arm64.whl", hash = "sha256:85b0ac3ad17fa3be50abd7e69d583d98792d7edc08367e01445a1e2076005379", size = 24370427, upload-time = "2026-01-10T21:25:20.514Z" }, + { url = "https://files.pythonhosted.org/packages/0b/11/7241a63e73ba5a516f1930ac8d5b44cbbfabd35ac73a2d08ca206df007c4/scipy-1.17.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:0d5018a57c24cb1dd828bcf51d7b10e65986d549f52ef5adb6b4d1ded3e32a57", size = 31364580, upload-time = "2026-01-10T21:25:25.717Z" }, { url = "https://files.pythonhosted.org/packages/ed/1d/5057f812d4f6adc91a20a2d6f2ebcdb517fdbc87ae3acc5633c9b97c8ba5/scipy-1.17.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:88c22af9e5d5a4f9e027e26772cc7b5922fab8bcc839edb3ae33de404feebd9e", size = 27969012, upload-time = "2026-01-10T21:25:30.921Z" }, { url = "https://files.pythonhosted.org/packages/e3/21/f6ec556c1e3b6ec4e088da667d9987bb77cc3ab3026511f427dc8451187d/scipy-1.17.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f3cd947f20fe17013d401b64e857c6b2da83cae567adbb75b9dcba865abc66d8", size = 20140691, upload-time = "2026-01-10T21:25:34.802Z" }, + { url = "https://files.pythonhosted.org/packages/7a/fe/5e5ad04784964ba964a96f16c8d4676aa1b51357199014dce58ab7ec5670/scipy-1.17.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e8c0b331c2c1f531eb51f1b4fc9ba709521a712cce58f1aa627bc007421a5306", size = 22463015, upload-time = "2026-01-10T21:25:39.277Z" }, + { url = "https://files.pythonhosted.org/packages/4a/69/7c347e857224fcaf32a34a05183b9d8a7aca25f8f2d10b8a698b8388561a/scipy-1.17.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5194c445d0a1c7a6c1a4a4681b6b7c71baad98ff66d96b949097e7513c9d6742", size = 32724197, upload-time = "2026-01-10T21:25:44.084Z" }, + { url = "https://files.pythonhosted.org/packages/d1/fe/66d73b76d378ba8cc2fe605920c0c75092e3a65ae746e1e767d9d020a75a/scipy-1.17.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9eeb9b5f5997f75507814ed9d298ab23f62cf79f5a3ef90031b1ee2506abdb5b", size = 35009148, upload-time = "2026-01-10T21:25:50.591Z" }, + { url = "https://files.pythonhosted.org/packages/af/07/07dec27d9dc41c18d8c43c69e9e413431d20c53a0339c388bcf72f353c4b/scipy-1.17.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:40052543f7bbe921df4408f46003d6f01c6af109b9e2c8a66dd1cf6cf57f7d5d", size = 34798766, upload-time = "2026-01-10T21:25:59.41Z" }, + { url = "https://files.pythonhosted.org/packages/81/61/0470810c8a093cdacd4ba7504b8a218fd49ca070d79eca23a615f5d9a0b0/scipy-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0cf46c8013fec9d3694dc572f0b54100c28405d55d3e2cb15e2895b25057996e", size = 37405953, upload-time = "2026-01-10T21:26:07.75Z" }, + { url = "https://files.pythonhosted.org/packages/92/ce/672ed546f96d5d41ae78c4b9b02006cedd0b3d6f2bf5bb76ea455c320c28/scipy-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:0937a0b0d8d593a198cededd4c439a0ea216a3f36653901ea1f3e4be949056f8", size = 36328121, upload-time = "2026-01-10T21:26:16.509Z" }, + { url = "https://files.pythonhosted.org/packages/9d/21/38165845392cae67b61843a52c6455d47d0cc2a40dd495c89f4362944654/scipy-1.17.0-cp312-cp312-win_arm64.whl", hash = "sha256:f603d8a5518c7426414d1d8f82e253e454471de682ce5e39c29adb0df1efb86b", size = 24314368, upload-time = "2026-01-10T21:26:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/0c/51/3468fdfd49387ddefee1636f5cf6d03ce603b75205bf439bbf0e62069bfd/scipy-1.17.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:65ec32f3d32dfc48c72df4291345dae4f048749bc8d5203ee0a3f347f96c5ce6", size = 31344101, upload-time = "2026-01-10T21:26:30.25Z" }, { url = "https://files.pythonhosted.org/packages/b2/9a/9406aec58268d437636069419e6977af953d1e246df941d42d3720b7277b/scipy-1.17.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:1f9586a58039d7229ce77b52f8472c972448cded5736eaf102d5658bbac4c269", size = 27950385, upload-time = "2026-01-10T21:26:36.801Z" }, { url = "https://files.pythonhosted.org/packages/4f/98/e7342709e17afdfd1b26b56ae499ef4939b45a23a00e471dfb5375eea205/scipy-1.17.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:9fad7d3578c877d606b1150135c2639e9de9cecd3705caa37b66862977cc3e72", size = 20122115, upload-time = "2026-01-10T21:26:42.107Z" }, + { url = "https://files.pythonhosted.org/packages/fd/0e/9eeeb5357a64fd157cbe0302c213517c541cc16b8486d82de251f3c68ede/scipy-1.17.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:423ca1f6584fc03936972b5f7c06961670dbba9f234e71676a7c7ccf938a0d61", size = 22442402, upload-time = "2026-01-10T21:26:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c9/10/be13397a0e434f98e0c79552b2b584ae5bb1c8b2be95db421533bbca5369/scipy-1.17.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe508b5690e9eaaa9467fc047f833af58f1152ae51a0d0aed67aa5801f4dd7d6", size = 32696338, upload-time = "2026-01-10T21:26:55.521Z" }, + { url = "https://files.pythonhosted.org/packages/63/1e/12fbf2a3bb240161651c94bb5cdd0eae5d4e8cc6eaeceb74ab07b12a753d/scipy-1.17.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6680f2dfd4f6182e7d6db161344537da644d1cf85cf293f015c60a17ecf08752", size = 34977201, upload-time = "2026-01-10T21:27:03.501Z" }, + { url = "https://files.pythonhosted.org/packages/19/5b/1a63923e23ccd20bd32156d7dd708af5bbde410daa993aa2500c847ab2d2/scipy-1.17.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eec3842ec9ac9de5917899b277428886042a93db0b227ebbe3a333b64ec7643d", size = 34777384, upload-time = "2026-01-10T21:27:11.423Z" }, + { url = "https://files.pythonhosted.org/packages/39/22/b5da95d74edcf81e540e467202a988c50fef41bd2011f46e05f72ba07df6/scipy-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d7425fcafbc09a03731e1bc05581f5fad988e48c6a861f441b7ab729a49a55ea", size = 37379586, upload-time = "2026-01-10T21:27:20.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b6/8ac583d6da79e7b9e520579f03007cb006f063642afd6b2eeb16b890bf93/scipy-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:87b411e42b425b84777718cc41516b8a7e0795abfa8e8e1d573bf0ef014f0812", size = 36287211, upload-time = "2026-01-10T21:28:43.122Z" }, + { url = "https://files.pythonhosted.org/packages/55/fb/7db19e0b3e52f882b420417644ec81dd57eeef1bd1705b6f689d8ff93541/scipy-1.17.0-cp313-cp313-win_arm64.whl", hash = "sha256:357ca001c6e37601066092e7c89cca2f1ce74e2a520ca78d063a6d2201101df2", size = 24312646, upload-time = "2026-01-10T21:28:49.893Z" }, + { url = "https://files.pythonhosted.org/packages/20/b6/7feaa252c21cc7aff335c6c55e1b90ab3e3306da3f048109b8b639b94648/scipy-1.17.0-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:ec0827aa4d36cb79ff1b81de898e948a51ac0b9b1c43e4a372c0508c38c0f9a3", size = 31693194, upload-time = "2026-01-10T21:27:27.454Z" }, { url = "https://files.pythonhosted.org/packages/76/bb/bbb392005abce039fb7e672cb78ac7d158700e826b0515cab6b5b60c26fb/scipy-1.17.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:819fc26862b4b3c73a60d486dbb919202f3d6d98c87cf20c223511429f2d1a97", size = 28365415, upload-time = "2026-01-10T21:27:34.26Z" }, { url = "https://files.pythonhosted.org/packages/37/da/9d33196ecc99fba16a409c691ed464a3a283ac454a34a13a3a57c0d66f3a/scipy-1.17.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:363ad4ae2853d88ebcde3ae6ec46ccca903ea9835ee8ba543f12f575e7b07e4e", size = 20537232, upload-time = "2026-01-10T21:27:40.306Z" }, + { url = "https://files.pythonhosted.org/packages/56/9d/f4b184f6ddb28e9a5caea36a6f98e8ecd2a524f9127354087ce780885d83/scipy-1.17.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:979c3a0ff8e5ba254d45d59ebd38cde48fce4f10b5125c680c7a4bfe177aab07", size = 22791051, upload-time = "2026-01-10T21:27:46.539Z" }, + { url = "https://files.pythonhosted.org/packages/9b/9d/025cccdd738a72140efc582b1641d0dd4caf2e86c3fb127568dc80444e6e/scipy-1.17.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:130d12926ae34399d157de777472bf82e9061c60cc081372b3118edacafe1d00", size = 32815098, upload-time = "2026-01-10T21:27:54.389Z" }, + { url = "https://files.pythonhosted.org/packages/48/5f/09b879619f8bca15ce392bfc1894bd9c54377e01d1b3f2f3b595a1b4d945/scipy-1.17.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e886000eb4919eae3a44f035e63f0fd8b651234117e8f6f29bad1cd26e7bc45", size = 35031342, upload-time = "2026-01-10T21:28:03.012Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9a/f0f0a9f0aa079d2f106555b984ff0fbb11a837df280f04f71f056ea9c6e4/scipy-1.17.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:13c4096ac6bc31d706018f06a49abe0485f96499deb82066b94d19b02f664209", size = 34893199, upload-time = "2026-01-10T21:28:10.832Z" }, + { url = "https://files.pythonhosted.org/packages/90/b8/4f0f5cf0c5ea4d7548424e6533e6b17d164f34a6e2fb2e43ffebb6697b06/scipy-1.17.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cacbaddd91fcffde703934897c5cd2c7cb0371fac195d383f4e1f1c5d3f3bd04", size = 37438061, upload-time = "2026-01-10T21:28:19.684Z" }, + { url = "https://files.pythonhosted.org/packages/f9/cc/2bd59140ed3b2fa2882fb15da0a9cb1b5a6443d67cfd0d98d4cec83a57ec/scipy-1.17.0-cp313-cp313t-win_amd64.whl", hash = "sha256:edce1a1cf66298cccdc48a1bdf8fb10a3bf58e8b58d6c3883dd1530e103f87c0", size = 36328593, upload-time = "2026-01-10T21:28:28.007Z" }, + { url = "https://files.pythonhosted.org/packages/13/1b/c87cc44a0d2c7aaf0f003aef2904c3d097b422a96c7e7c07f5efd9073c1b/scipy-1.17.0-cp313-cp313t-win_arm64.whl", hash = "sha256:30509da9dbec1c2ed8f168b8d8aa853bc6723fede1dbc23c7d43a56f5ab72a67", size = 24625083, upload-time = "2026-01-10T21:28:35.188Z" }, ] [[package]] @@ -4644,6 +5082,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/11/18/cb614939ccd46d336013cab705f1e11540ec9c68b08ecbb854ab893fc480/segments-2.3.0-py2.py3-none-any.whl", hash = "sha256:30a5656787071430cd22422e04713b2a9beabe1a97d2ebf37f716a56f90577a3", size = 15705, upload-time = "2025-02-20T07:55:39.755Z" }, ] +[[package]] +name = "semantic-version" +version = "2.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/31/f2289ce78b9b473d582568c234e104d2a342fd658cc288a7553d83bb8595/semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c", size = 52289, upload-time = "2022-05-26T13:35:23.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/23/8146aad7d88f4fcb3a6218f41a60f6c2d4e3a72de72da1825dc7c8f7877c/semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177", size = 15552, upload-time = "2022-05-26T13:35:21.206Z" }, +] + [[package]] name = "send2trash" version = "2.1.0" @@ -4832,6 +5279,37 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/48/f3/b67d6ea49ca9154453b6d70b34ea22f3996b9fa55da105a79d8732227adc/soupsieve-2.8.1-py3-none-any.whl", hash = "sha256:a11fe2a6f3d76ab3cf2de04eb339c1be5b506a8a47f2ceb6d139803177f85434", size = 36710, upload-time = "2025-12-18T13:50:33.267Z" }, ] +[[package]] +name = "sox" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/a2/d8e0d8fd7abf509ead4a2cb0fb24e5758b5330166bf9223d5cb9f98a7e8d/sox-1.5.0.tar.gz", hash = "sha256:12c7be5bb1f548d891fe11e82c08cf5f1a1d74e225298f60082e5aeb2469ada0", size = 63905, upload-time = "2024-03-20T16:59:37.385Z" } + +[[package]] +name = "soxr" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/7e/f4b461944662ad75036df65277d6130f9411002bfb79e9df7dff40a31db9/soxr-1.0.0.tar.gz", hash = "sha256:e07ee6c1d659bc6957034f4800c60cb8b98de798823e34d2a2bba1caa85a4509", size = 171415, upload-time = "2025-09-07T13:22:21.317Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/ce/a3262bc8733d3a4ce5f660ed88c3d97f4b12658b0909e71334cba1721dcb/soxr-1.0.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:28e19d74a5ef45c0d7000f3c70ec1719e89077379df2a1215058914d9603d2d8", size = 206739, upload-time = "2025-09-07T13:21:54.572Z" }, + { url = "https://files.pythonhosted.org/packages/64/dc/e8cbd100b652697cc9865dbed08832e7e135ff533f453eb6db9e6168d153/soxr-1.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8dc69fc18884e53b72f6141fdf9d80997edbb4fec9dc2942edcb63abbe0d023", size = 165233, upload-time = "2025-09-07T13:21:55.887Z" }, + { url = "https://files.pythonhosted.org/packages/75/12/4b49611c9ba5e9fe6f807d0a83352516808e8e573f8b4e712fc0c17f3363/soxr-1.0.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f15450e6f65f22f02fcd4c5a9219c873b1e583a73e232805ff160c759a6b586", size = 208867, upload-time = "2025-09-07T13:21:57.076Z" }, + { url = "https://files.pythonhosted.org/packages/cc/70/92146ab970a3ef8c43ac160035b1e52fde5417f89adb10572f7e788d9596/soxr-1.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f73f57452f9df37b4de7a4052789fcbd474a5b28f38bba43278ae4b489d4384", size = 242633, upload-time = "2025-09-07T13:21:58.621Z" }, + { url = "https://files.pythonhosted.org/packages/b5/a7/628479336206959463d08260bffed87905e7ba9e3bd83ca6b405a0736e94/soxr-1.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:9f417c3d69236051cf5a1a7bad7c4bff04eb3d8fcaa24ac1cb06e26c8d48d8dc", size = 173814, upload-time = "2025-09-07T13:21:59.798Z" }, + { url = "https://files.pythonhosted.org/packages/c5/c7/f92b81f1a151c13afb114f57799b86da9330bec844ea5a0d3fe6a8732678/soxr-1.0.0-cp312-abi3-macosx_10_14_x86_64.whl", hash = "sha256:abecf4e39017f3fadb5e051637c272ae5778d838e5c3926a35db36a53e3a607f", size = 205508, upload-time = "2025-09-07T13:22:01.252Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1d/c945fea9d83ea1f2be9d116b3674dbaef26ed090374a77c394b31e3b083b/soxr-1.0.0-cp312-abi3-macosx_11_0_arm64.whl", hash = "sha256:e973d487ee46aa8023ca00a139db6e09af053a37a032fe22f9ff0cc2e19c94b4", size = 163568, upload-time = "2025-09-07T13:22:03.558Z" }, + { url = "https://files.pythonhosted.org/packages/b5/80/10640970998a1d2199bef6c4d92205f36968cddaf3e4d0e9fe35ddd405bd/soxr-1.0.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e8ce273cca101aff3d8c387db5a5a41001ba76ef1837883438d3c652507a9ccc", size = 204707, upload-time = "2025-09-07T13:22:05.125Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/2726603c13c2126cb8ded9e57381b7377f4f0df6ba4408e1af5ddbfdc3dd/soxr-1.0.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8f2a69686f2856d37823bbb7b78c3d44904f311fe70ba49b893af11d6b6047b", size = 238032, upload-time = "2025-09-07T13:22:06.428Z" }, + { url = "https://files.pythonhosted.org/packages/ce/04/530252227f4d0721a5524a936336485dfb429bb206a66baf8e470384f4a2/soxr-1.0.0-cp312-abi3-win_amd64.whl", hash = "sha256:2a3b77b115ae7c478eecdbd060ed4f61beda542dfb70639177ac263aceda42a2", size = 172070, upload-time = "2025-09-07T13:22:07.62Z" }, +] + [[package]] name = "spacy" version = "3.8.11" @@ -4962,6 +5440,40 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, ] +[[package]] +name = "standard-aifc" +version = "3.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "audioop-lts", marker = "python_full_version >= '3.13'" }, + { name = "standard-chunk", marker = "python_full_version >= '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/53/6050dc3dde1671eb3db592c13b55a8005e5040131f7509cef0215212cb84/standard_aifc-3.13.0.tar.gz", hash = "sha256:64e249c7cb4b3daf2fdba4e95721f811bde8bdfc43ad9f936589b7bb2fae2e43", size = 15240, upload-time = "2024-10-30T16:01:31.772Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/52/5fbb203394cc852334d1575cc020f6bcec768d2265355984dfd361968f36/standard_aifc-3.13.0-py3-none-any.whl", hash = "sha256:f7ae09cc57de1224a0dd8e3eb8f73830be7c3d0bc485de4c1f82b4a7f645ac66", size = 10492, upload-time = "2024-10-30T16:01:07.071Z" }, +] + +[[package]] +name = "standard-chunk" +version = "3.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/06/ce1bb165c1f111c7d23a1ad17204d67224baa69725bb6857a264db61beaf/standard_chunk-3.13.0.tar.gz", hash = "sha256:4ac345d37d7e686d2755e01836b8d98eda0d1a3ee90375e597ae43aaf064d654", size = 4672, upload-time = "2024-10-30T16:18:28.326Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/90/a5c1084d87767d787a6caba615aa50dc587229646308d9420c960cb5e4c0/standard_chunk-3.13.0-py3-none-any.whl", hash = "sha256:17880a26c285189c644bd5bd8f8ed2bdb795d216e3293e6dbe55bbd848e2982c", size = 4944, upload-time = "2024-10-30T16:18:26.694Z" }, +] + +[[package]] +name = "standard-sunau" +version = "3.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "audioop-lts", marker = "python_full_version >= '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/e3/ce8d38cb2d70e05ffeddc28bb09bad77cfef979eb0a299c9117f7ed4e6a9/standard_sunau-3.13.0.tar.gz", hash = "sha256:b319a1ac95a09a2378a8442f403c66f4fd4b36616d6df6ae82b8e536ee790908", size = 9368, upload-time = "2024-10-30T16:01:41.626Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/ae/e3707f6c1bc6f7aa0df600ba8075bfb8a19252140cd595335be60e25f9ee/standard_sunau-3.13.0-py3-none-any.whl", hash = "sha256:53af624a9529c41062f4c2fd33837f297f3baa196b0cfceffea6555654602622", size = 7364, upload-time = "2024-10-30T16:01:28.003Z" }, +] + [[package]] name = "starlette" version = "0.50.0" @@ -5065,6 +5577,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b8/e0/faa1d04a6890ea33b9541727d2a3ca88bad794a89f73b9111af6f9aefe10/thinc-8.3.10-cp313-cp313-win_arm64.whl", hash = "sha256:aa43f9af76781d32f5f9fe29299204c8841d71e64cbb56e0e4f3d1e0387c2783", size = 1641536, upload-time = "2025-11-17T17:21:30.129Z" }, ] +[[package]] +name = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, +] + [[package]] name = "tiktoken" version = "0.12.0" @@ -5181,11 +5702,11 @@ wheels = [ [[package]] name = "tomlkit" -version = "0.14.0" +version = "0.13.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/af/14b24e41977adb296d6bd1fb59402cf7d60ce364f90c890bd2ec65c43b5a/tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064", size = 187167, upload-time = "2026-01-13T01:14:53.304Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680", size = 39310, upload-time = "2026-01-13T01:14:51.965Z" }, + { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" }, ] [[package]] @@ -5304,7 +5825,7 @@ wheels = [ [[package]] name = "transformers" -version = "4.57.5" +version = "4.57.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, @@ -5318,9 +5839,9 @@ dependencies = [ { name = "tokenizers" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/35/3a/7c90ee739871495f1a5cb9bdb074b42fe69357d7ccc1a8818af858d8e63b/transformers-4.57.5.tar.gz", hash = "sha256:d631faea6bd32fc51962e482744afeaa70170c70e5e991cf8e355d7275631524", size = 10138171, upload-time = "2026-01-13T13:28:24.19Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/70/d42a739e8dfde3d92bb2fff5819cbf331fe9657323221e79415cd5eb65ee/transformers-4.57.3.tar.gz", hash = "sha256:df4945029aaddd7c09eec5cad851f30662f8bd1746721b34cc031d70c65afebc", size = 10139680, upload-time = "2025-11-25T15:51:30.139Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/de/4f95d22d9764659d2bd35065f383f3fe099699a9e6e89fa4728dbcd7244a/transformers-4.57.5-py3-none-any.whl", hash = "sha256:5a1e0deb989cd0b8f141b6d8c9b7c956fc029cd288d68844f57dc0acbaf2fe39", size = 11993481, upload-time = "2026-01-13T13:28:16.542Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6b/2f416568b3c4c91c96e5a365d164f8a4a4a88030aa8ab4644181fdadce97/transformers-4.57.3-py3-none-any.whl", hash = "sha256:c77d353a4851b1880191603d36acb313411d3577f6e2897814f333841f7003f4", size = 11993463, upload-time = "2025-11-25T15:51:26.493Z" }, ] [[package]] From 7b699156fc9f8e4a787ba83d4d5bb60c18e3f942 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 25 Jan 2026 13:15:45 +0000 Subject: [PATCH 02/16] Update auto-generated docs --- docs/commands/install-extras.md | 11 ++++++----- docs/commands/server/tts.md | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/commands/install-extras.md b/docs/commands/install-extras.md index 0e48e5c2e..62cd20c69 100644 --- a/docs/commands/install-extras.md +++ b/docs/commands/install-extras.md @@ -31,17 +31,18 @@ Available extras: | Extra | Description | |-------|-------------| -| `audio` | Audio recording/playback with Wyoming protocol | +| `audio` | Audio recording/playback | +| `faster-whisper` | Faster Whisper ASR backend | +| `kokoro` | Kokoro TTS backend (GPU-accelerated) | | `llm` | LLM framework (pydantic-ai) | | `memory` | Long-term memory proxy | +| `mlx-whisper` | MLX Whisper ASR for Apple Silicon | +| `piper` | Piper TTS backend (CPU-friendly) | +| `qwen-tts` | Qwen3-TTS backend (multilingual) | | `rag` | RAG proxy (ChromaDB, embeddings) | | `server` | FastAPI server components | | `speed` | Audio speed adjustment (audiostretchy) | -| `piper` | Local Piper TTS | -| `kokoro` | Kokoro neural TTS | | `vad` | Voice Activity Detection (silero-vad) | -| `faster-whisper` | Whisper ASR (CUDA/CPU) | -| `mlx-whisper` | Whisper ASR (Apple Silicon) | diff --git a/docs/commands/server/tts.md b/docs/commands/server/tts.md index cbb95f03f..b13b2a468 100644 --- a/docs/commands/server/tts.md +++ b/docs/commands/server/tts.md @@ -92,7 +92,7 @@ agent-cli server tts --preload | `--no-wyoming` | `false` | Disable Wyoming server | | `--download-only` | `false` | Download model(s) and exit without starting server | | `--log-level` | `info` | Logging level: debug, info, warning, error | -| `--backend` | `auto` | Backend: auto, piper, kokoro | +| `--backend` | `auto` | Backend: auto, piper, kokoro, qwen | From 6aff91a792f62df1c3d5ff6c25894f541e9db79b Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 25 Jan 2026 05:43:53 -0800 Subject: [PATCH 03/16] fix(tts): add qwen-tts to requires_extras and add tests - Include qwen-tts in @requires_extras decorator so users can install only qwen-tts without piper or kokoro - Remove unused QWEN_SAMPLE_RATE constant (sample rate comes from model) - Add TestQwenBackend test class with 7 tests covering initialization, voice mapping, language support, and streaming status - Add test_create_qwen_backend to TestBackendFactory - Regenerate auto-generated docs to show qwen in --backend options --- agent_cli/server/cli.py | 2 +- agent_cli/server/tts/backends/qwen.py | 3 - tests/test_server_tts.py | 87 +++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 4 deletions(-) diff --git a/agent_cli/server/cli.py b/agent_cli/server/cli.py index 0efd5548b..ec917b65d 100644 --- a/agent_cli/server/cli.py +++ b/agent_cli/server/cli.py @@ -506,7 +506,7 @@ def transcription_proxy_cmd( @app.command("tts") -@requires_extras("server", "piper|kokoro") +@requires_extras("server", "piper|kokoro|qwen-tts") def tts_cmd( # noqa: PLR0912, PLR0915 model: Annotated[ list[str] | None, diff --git a/agent_cli/server/tts/backends/qwen.py b/agent_cli/server/tts/backends/qwen.py index 5cf544200..99ca5bfbb 100644 --- a/agent_cli/server/tts/backends/qwen.py +++ b/agent_cli/server/tts/backends/qwen.py @@ -29,9 +29,6 @@ # Default voice if none specified DEFAULT_VOICE = "Vivian" -# Qwen3-TTS sample rate (24kHz like Kokoro) -QWEN_SAMPLE_RATE = 24000 - # Voice name mapping: OpenAI-style names -> Qwen speaker names # Qwen supports: Vivian, Serena, Uncle_Fu, Dylan, Eric, Ryan, Aiden, Ono_Anna, Sohee VOICE_MAP = { diff --git a/tests/test_server_tts.py b/tests/test_server_tts.py index c05eb2df1..30b3d7030 100644 --- a/tests/test_server_tts.py +++ b/tests/test_server_tts.py @@ -334,6 +334,20 @@ def test_create_kokoro_backend(self) -> None: ) assert backend.__class__.__name__ == "KokoroBackend" + def test_create_qwen_backend(self) -> None: + """Test creating a Qwen backend.""" + from agent_cli.server.tts.backends import BackendConfig, create_backend # noqa: PLC0415 + + with patch( + "agent_cli.server.tts.backends.qwen.QwenBackend.__init__", + return_value=None, + ): + backend = create_backend( + BackendConfig(model_name="qwen"), + backend_type="qwen", + ) + assert backend.__class__.__name__ == "QwenBackend" + def test_create_unknown_backend_raises(self) -> None: """Test that unknown backend type raises ValueError.""" from agent_cli.server.tts.backends import BackendConfig, create_backend # noqa: PLC0415 @@ -455,6 +469,79 @@ def test_resolve_voice_path_default(self, tmp_path: Path) -> None: mock_ensure.assert_called_once_with(DEFAULT_VOICE, tmp_path) +class TestQwenBackend: + """Tests for the Qwen TTS backend.""" + + def test_default_voice(self) -> None: + """Test default voice is set correctly.""" + from agent_cli.server.tts.backends.qwen import DEFAULT_VOICE # noqa: PLC0415 + + assert DEFAULT_VOICE == "Vivian" + + def test_default_model(self) -> None: + """Test default Qwen model constant.""" + from agent_cli.server.tts.backends.qwen import DEFAULT_QWEN_MODEL # noqa: PLC0415 + + assert DEFAULT_QWEN_MODEL == "Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice" + + def test_backend_init(self) -> None: + """Test QwenBackend initialization.""" + from agent_cli.server.tts.backends import BackendConfig # noqa: PLC0415 + from agent_cli.server.tts.backends.qwen import QwenBackend # noqa: PLC0415 + + config = BackendConfig(model_name="qwen", cache_dir=Path("/tmp/test")) # noqa: S108 + backend = QwenBackend(config) + + assert backend.is_loaded is False + assert backend.device is None + assert backend._cache_dir == Path("/tmp/test") # noqa: S108 + + def test_backend_init_with_default_model(self) -> None: + """Test QwenBackend uses default model when 'qwen' is specified.""" + from agent_cli.server.tts.backends import BackendConfig # noqa: PLC0415 + from agent_cli.server.tts.backends.qwen import ( # noqa: PLC0415 + DEFAULT_QWEN_MODEL, + QwenBackend, + ) + + config = BackendConfig(model_name="qwen") + backend = QwenBackend(config) + + assert backend._model_name == DEFAULT_QWEN_MODEL + + def test_voice_mapping(self) -> None: + """Test OpenAI voice names are mapped to Qwen voices.""" + from agent_cli.server.tts.backends.qwen import VOICE_MAP # noqa: PLC0415 + + # Check OpenAI-compatible names are mapped + assert VOICE_MAP["alloy"] == "Vivian" + assert VOICE_MAP["echo"] == "Ryan" + assert VOICE_MAP["nova"] == "Dylan" + + # Check direct Qwen names work (lowercase) + assert VOICE_MAP["vivian"] == "Vivian" + assert VOICE_MAP["ryan"] == "Ryan" + + def test_supported_languages(self) -> None: + """Test supported languages list.""" + from agent_cli.server.tts.backends.qwen import SUPPORTED_LANGUAGES # noqa: PLC0415 + + assert "Auto" in SUPPORTED_LANGUAGES + assert "English" in SUPPORTED_LANGUAGES + assert "Chinese" in SUPPORTED_LANGUAGES + assert "Japanese" in SUPPORTED_LANGUAGES + + def test_streaming_not_supported(self) -> None: + """Test that Qwen backend does not support streaming.""" + from agent_cli.server.tts.backends import BackendConfig # noqa: PLC0415 + from agent_cli.server.tts.backends.qwen import QwenBackend # noqa: PLC0415 + + config = BackendConfig(model_name="qwen") + backend = QwenBackend(config) + + assert backend.supports_streaming is False + + class TestTTSAPI: """Tests for the TTS API endpoints.""" From 58f14a0ddf2c34da1f9ad306885a5c15dba763f0 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 25 Jan 2026 05:53:27 -0800 Subject: [PATCH 04/16] fix(tts): address PR review feedback for Qwen backend - Pass cache_dir to Qwen3TTSModel.from_pretrained() so --cache-dir works - Remove no-op try/except around flash attention dict assignment - Remove unused SUPPORTED_LANGUAGES list and associated test --- agent_cli/server/tts/backends/qwen.py | 25 +++---------------------- tests/test_server_tts.py | 9 --------- 2 files changed, 3 insertions(+), 31 deletions(-) diff --git a/agent_cli/server/tts/backends/qwen.py b/agent_cli/server/tts/backends/qwen.py index 99ca5bfbb..0f0960178 100644 --- a/agent_cli/server/tts/backends/qwen.py +++ b/agent_cli/server/tts/backends/qwen.py @@ -51,21 +51,6 @@ "sohee": "Sohee", } -# Supported languages -SUPPORTED_LANGUAGES = [ - "Auto", - "English", - "Chinese", - "Japanese", - "Korean", - "German", - "French", - "Spanish", - "Russian", - "Portuguese", - "Italian", -] - # --- Subprocess state (only used within subprocess worker) --- @@ -88,7 +73,7 @@ class _SubprocessState: def _load_model_in_subprocess( model_name: str, device: str, - cache_dir: str, # noqa: ARG001 - kept for API consistency with other backends + cache_dir: str, ) -> str: """Load Qwen3-TTS model in subprocess. Returns actual device string.""" import torch # noqa: PLC0415 @@ -113,18 +98,14 @@ def _load_model_in_subprocess( # Add device_map for CUDA if device == "cuda": load_kwargs["device_map"] = "cuda:0" - # Try to use Flash Attention 2 if available - try: - load_kwargs["attn_implementation"] = "flash_attention_2" - except Exception: - logger.debug("Flash Attention 2 not available, using default attention") + load_kwargs["attn_implementation"] = "flash_attention_2" elif device == "mps": load_kwargs["device_map"] = "mps" else: load_kwargs["device_map"] = "cpu" # Load model - model = Qwen3TTSModel.from_pretrained(model_name, **load_kwargs) + model = Qwen3TTSModel.from_pretrained(model_name, cache_dir=cache_dir, **load_kwargs) # Store in subprocess state for reuse _state.model = model diff --git a/tests/test_server_tts.py b/tests/test_server_tts.py index 30b3d7030..85f10f671 100644 --- a/tests/test_server_tts.py +++ b/tests/test_server_tts.py @@ -522,15 +522,6 @@ def test_voice_mapping(self) -> None: assert VOICE_MAP["vivian"] == "Vivian" assert VOICE_MAP["ryan"] == "Ryan" - def test_supported_languages(self) -> None: - """Test supported languages list.""" - from agent_cli.server.tts.backends.qwen import SUPPORTED_LANGUAGES # noqa: PLC0415 - - assert "Auto" in SUPPORTED_LANGUAGES - assert "English" in SUPPORTED_LANGUAGES - assert "Chinese" in SUPPORTED_LANGUAGES - assert "Japanese" in SUPPORTED_LANGUAGES - def test_streaming_not_supported(self) -> None: """Test that Qwen backend does not support streaming.""" from agent_cli.server.tts.backends import BackendConfig # noqa: PLC0415 From eeb2f5aee1e4ce70a5dca77321455b8360a9a030 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 25 Jan 2026 14:54:42 +0100 Subject: [PATCH 05/16] fix(docker): use COPY --chmod to avoid duplicate layer in transcription-proxy (#313) --- docker/transcription-proxy.Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/transcription-proxy.Dockerfile b/docker/transcription-proxy.Dockerfile index 86ac31c35..4ead66638 100644 --- a/docker/transcription-proxy.Dockerfile +++ b/docker/transcription-proxy.Dockerfile @@ -20,12 +20,12 @@ FROM python:3.13-slim # Create non-root user with explicit UID:GID 1000:1000 RUN groupadd -g 1000 transcribe && useradd -m -u 1000 -g transcribe transcribe -# Copy installed tool virtualenv from builder (keep original path for shebang compatibility) -COPY --from=builder /root/.local/share/uv/tools/agent-cli /root/.local/share/uv/tools/agent-cli +# Copy installed tool virtualenv from builder with execute permissions +# Using --chmod=755 avoids a duplicate layer from running chmod after COPY +COPY --from=builder --chmod=755 /root/.local/share/uv/tools/agent-cli /root/.local/share/uv/tools/agent-cli -# Make tool accessible to non-root users and create symlinks +# Make parent directories accessible to non-root users and create symlinks RUN chmod 755 /root /root/.local /root/.local/share /root/.local/share/uv /root/.local/share/uv/tools && \ - chmod -R 755 /root/.local/share/uv/tools/agent-cli && \ ln -s /root/.local/share/uv/tools/agent-cli/bin/agent-cli /usr/local/bin/agent-cli && \ ln -s /root/.local/share/uv/tools/agent-cli/bin/agent /usr/local/bin/agent && \ ln -s /root/.local/share/uv/tools/agent-cli/bin/ag /usr/local/bin/ag From 6c04db28ec5be2bef03ed25d4d51641838ff9a84 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 25 Jan 2026 06:02:04 -0800 Subject: [PATCH 06/16] fix(tts): remove unused QWEN_DEFAULT_SAMPLE_RATE constant The Qwen backend gets sample rate dynamically from model output, so this constant was never used. --- agent_cli/constants.py | 1 - 1 file changed, 1 deletion(-) diff --git a/agent_cli/constants.py b/agent_cli/constants.py index 0bb553635..981e51514 100644 --- a/agent_cli/constants.py +++ b/agent_cli/constants.py @@ -13,7 +13,6 @@ # --- TTS Configuration --- PIPER_DEFAULT_SAMPLE_RATE = 22050 # Piper TTS default sample rate KOKORO_DEFAULT_SAMPLE_RATE = 24000 # Kokoro TTS default sample rate -QWEN_DEFAULT_SAMPLE_RATE = 24000 # Qwen3-TTS default sample rate (12Hz tokenizer) # Standard Wyoming audio configuration WYOMING_AUDIO_CONFIG = { From 7a8c37ed1d09bfeff3795b8758cc4e2df8f2724c Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 25 Jan 2026 15:11:17 +0100 Subject: [PATCH 07/16] docs(skill): emphasize --from flag for branch-based work (#312) --- .claude-plugin/skills/agent-cli-dev/SKILL.md | 13 ++-- .../skills/agent-cli-dev/examples.md | 69 +++++++++++++++++-- .claude/skills/agent-cli-dev/SKILL.md | 13 ++-- .claude/skills/agent-cli-dev/examples.md | 69 +++++++++++++++++-- agent_cli/dev/skill/SKILL.md | 13 ++-- agent_cli/dev/skill/examples.md | 69 +++++++++++++++++-- 6 files changed, 222 insertions(+), 24 deletions(-) diff --git a/.claude-plugin/skills/agent-cli-dev/SKILL.md b/.claude-plugin/skills/agent-cli-dev/SKILL.md index dce8f31fd..9c4e52f55 100644 --- a/.claude-plugin/skills/agent-cli-dev/SKILL.md +++ b/.claude-plugin/skills/agent-cli-dev/SKILL.md @@ -34,14 +34,19 @@ Do NOT spawn when: ## Core command -For short prompts: +For new features (starts from origin/main): ```bash -agent-cli dev new --agent --prompt "Fix the login bug" +agent-cli dev new --agent --prompt "Implement the new feature..." +``` + +For work on current branch (review, test, fix) - use `--from HEAD`: +```bash +agent-cli dev new --from HEAD --agent --prompt "Review/test/fix..." ``` For longer prompts (recommended for multi-line or complex instructions): ```bash -agent-cli dev new --agent --prompt-file path/to/prompt.md +agent-cli dev new --from HEAD --agent --prompt-file path/to/prompt.md ``` This creates: @@ -129,7 +134,7 @@ Each agent works independently in its own branch. Results can be reviewed and me | `--agent` / `-a` | Start AI coding agent after creation | | `--prompt` / `-p` | Initial prompt for the agent (short prompts only) | | `--prompt-file` / `-P` | Read prompt from file (recommended for longer prompts) | -| `--from` / `-f` | Base branch (default: origin/main) | +| `--from` / `-f` | Base ref (default: origin/main). **Use `--from HEAD` when reviewing/testing current branch!** | | `--with-agent` | Specific agent: claude, aider, codex, gemini | | `--agent-args` | Extra arguments for the agent | diff --git a/.claude-plugin/skills/agent-cli-dev/examples.md b/.claude-plugin/skills/agent-cli-dev/examples.md index e833eb96d..5a06a8d2c 100644 --- a/.claude-plugin/skills/agent-cli-dev/examples.md +++ b/.claude-plugin/skills/agent-cli-dev/examples.md @@ -20,7 +20,68 @@ Each prompt for a spawned agent should follow this structure: 5. **Focused scope** - Keep solutions minimal, implement only what's requested 6. **Structured report** - Write conclusions to `.claude/REPORT.md` -## Scenario 1: Multi-feature implementation +## Scenario 1: Code review of current branch + +**User request**: "Review the code on this branch" or "Spawn an agent to review my changes" + +**CRITICAL**: Use `--from HEAD` (or the branch name) so the review agent has access to the changes! + +```bash +# Review the current branch - MUST use --from HEAD +agent-cli dev new review-changes --from HEAD --agent --prompt "Review the code changes on this branch. + + +- Run git diff origin/main...HEAD to identify all changes +- Read changed files in parallel to understand context +- Check CLAUDE.md for project-specific guidelines +- Test changes with real services if applicable + + + +- Use git diff origin/main...HEAD to see the full diff +- Read each changed file completely before judging +- Look at surrounding code to understand patterns +- Check existing tests to understand expected behavior + + + +Code review catches issues before merge. Focus on real problems - not style nitpicks. Apply these criteria: +- Code cleanliness: Is the implementation clean and well-structured? +- DRY principle: Does it avoid duplication? +- Code reuse: Are there parts that should be reused from other places? +- Organization: Is everything in the right place? +- Consistency: Is it in the same style as other parts of the codebase? +- Simplicity: Is it over-engineered? Remember KISS and YAGNI. No dead code paths, no defensive programming. +- No pointless wrappers: Functions that just call another function should be inlined. +- User experience: Does it provide a good user experience? +- Tests: Are tests meaningful or just trivial coverage? +- Live tests: Test changes with real services if applicable. +- Rules: Does the code follow CLAUDE.md guidelines? + + + +Review only - identify issues but do not fix them. Write findings to report. + + + +Write your review to .claude/REPORT.md: + +## Summary +[Overall assessment of the changes] + +## Issues Found +| Severity | File:Line | Issue | Suggestion | +|----------|-----------|-------|------------| +| Critical/High/Medium/Low | path:123 | description | fix | + +## Positive Observations +[What's well done] +" +``` + +**Common mistake**: Forgetting `--from HEAD` means the agent starts from `origin/main` and won't see any of the branch changes! + +## Scenario 2: Multi-feature implementation **User request**: "Implement user auth, payment processing, and email notifications" @@ -169,7 +230,7 @@ After verifying tests pass, write to .claude/REPORT.md with summary, files chang " ``` -## Scenario 2: Test-driven development +## Scenario 3: Test-driven development **User request**: "Add a caching layer with comprehensive tests" @@ -289,7 +350,7 @@ After ALL tests pass, write to .claude/REPORT.md: " ``` -## Scenario 3: Large refactoring by module +## Scenario 4: Large refactoring by module **User request**: "Refactor the API to use consistent error handling" @@ -357,7 +418,7 @@ After tests pass and linting is clean, write to .claude/REPORT.md: " ``` -## Scenario 4: Documentation and implementation in parallel +## Scenario 5: Documentation and implementation in parallel **User request**: "Add a plugin system with documentation" diff --git a/.claude/skills/agent-cli-dev/SKILL.md b/.claude/skills/agent-cli-dev/SKILL.md index dce8f31fd..9c4e52f55 100644 --- a/.claude/skills/agent-cli-dev/SKILL.md +++ b/.claude/skills/agent-cli-dev/SKILL.md @@ -34,14 +34,19 @@ Do NOT spawn when: ## Core command -For short prompts: +For new features (starts from origin/main): ```bash -agent-cli dev new --agent --prompt "Fix the login bug" +agent-cli dev new --agent --prompt "Implement the new feature..." +``` + +For work on current branch (review, test, fix) - use `--from HEAD`: +```bash +agent-cli dev new --from HEAD --agent --prompt "Review/test/fix..." ``` For longer prompts (recommended for multi-line or complex instructions): ```bash -agent-cli dev new --agent --prompt-file path/to/prompt.md +agent-cli dev new --from HEAD --agent --prompt-file path/to/prompt.md ``` This creates: @@ -129,7 +134,7 @@ Each agent works independently in its own branch. Results can be reviewed and me | `--agent` / `-a` | Start AI coding agent after creation | | `--prompt` / `-p` | Initial prompt for the agent (short prompts only) | | `--prompt-file` / `-P` | Read prompt from file (recommended for longer prompts) | -| `--from` / `-f` | Base branch (default: origin/main) | +| `--from` / `-f` | Base ref (default: origin/main). **Use `--from HEAD` when reviewing/testing current branch!** | | `--with-agent` | Specific agent: claude, aider, codex, gemini | | `--agent-args` | Extra arguments for the agent | diff --git a/.claude/skills/agent-cli-dev/examples.md b/.claude/skills/agent-cli-dev/examples.md index e833eb96d..5a06a8d2c 100644 --- a/.claude/skills/agent-cli-dev/examples.md +++ b/.claude/skills/agent-cli-dev/examples.md @@ -20,7 +20,68 @@ Each prompt for a spawned agent should follow this structure: 5. **Focused scope** - Keep solutions minimal, implement only what's requested 6. **Structured report** - Write conclusions to `.claude/REPORT.md` -## Scenario 1: Multi-feature implementation +## Scenario 1: Code review of current branch + +**User request**: "Review the code on this branch" or "Spawn an agent to review my changes" + +**CRITICAL**: Use `--from HEAD` (or the branch name) so the review agent has access to the changes! + +```bash +# Review the current branch - MUST use --from HEAD +agent-cli dev new review-changes --from HEAD --agent --prompt "Review the code changes on this branch. + + +- Run git diff origin/main...HEAD to identify all changes +- Read changed files in parallel to understand context +- Check CLAUDE.md for project-specific guidelines +- Test changes with real services if applicable + + + +- Use git diff origin/main...HEAD to see the full diff +- Read each changed file completely before judging +- Look at surrounding code to understand patterns +- Check existing tests to understand expected behavior + + + +Code review catches issues before merge. Focus on real problems - not style nitpicks. Apply these criteria: +- Code cleanliness: Is the implementation clean and well-structured? +- DRY principle: Does it avoid duplication? +- Code reuse: Are there parts that should be reused from other places? +- Organization: Is everything in the right place? +- Consistency: Is it in the same style as other parts of the codebase? +- Simplicity: Is it over-engineered? Remember KISS and YAGNI. No dead code paths, no defensive programming. +- No pointless wrappers: Functions that just call another function should be inlined. +- User experience: Does it provide a good user experience? +- Tests: Are tests meaningful or just trivial coverage? +- Live tests: Test changes with real services if applicable. +- Rules: Does the code follow CLAUDE.md guidelines? + + + +Review only - identify issues but do not fix them. Write findings to report. + + + +Write your review to .claude/REPORT.md: + +## Summary +[Overall assessment of the changes] + +## Issues Found +| Severity | File:Line | Issue | Suggestion | +|----------|-----------|-------|------------| +| Critical/High/Medium/Low | path:123 | description | fix | + +## Positive Observations +[What's well done] +" +``` + +**Common mistake**: Forgetting `--from HEAD` means the agent starts from `origin/main` and won't see any of the branch changes! + +## Scenario 2: Multi-feature implementation **User request**: "Implement user auth, payment processing, and email notifications" @@ -169,7 +230,7 @@ After verifying tests pass, write to .claude/REPORT.md with summary, files chang " ``` -## Scenario 2: Test-driven development +## Scenario 3: Test-driven development **User request**: "Add a caching layer with comprehensive tests" @@ -289,7 +350,7 @@ After ALL tests pass, write to .claude/REPORT.md: " ``` -## Scenario 3: Large refactoring by module +## Scenario 4: Large refactoring by module **User request**: "Refactor the API to use consistent error handling" @@ -357,7 +418,7 @@ After tests pass and linting is clean, write to .claude/REPORT.md: " ``` -## Scenario 4: Documentation and implementation in parallel +## Scenario 5: Documentation and implementation in parallel **User request**: "Add a plugin system with documentation" diff --git a/agent_cli/dev/skill/SKILL.md b/agent_cli/dev/skill/SKILL.md index dce8f31fd..9c4e52f55 100644 --- a/agent_cli/dev/skill/SKILL.md +++ b/agent_cli/dev/skill/SKILL.md @@ -34,14 +34,19 @@ Do NOT spawn when: ## Core command -For short prompts: +For new features (starts from origin/main): ```bash -agent-cli dev new --agent --prompt "Fix the login bug" +agent-cli dev new --agent --prompt "Implement the new feature..." +``` + +For work on current branch (review, test, fix) - use `--from HEAD`: +```bash +agent-cli dev new --from HEAD --agent --prompt "Review/test/fix..." ``` For longer prompts (recommended for multi-line or complex instructions): ```bash -agent-cli dev new --agent --prompt-file path/to/prompt.md +agent-cli dev new --from HEAD --agent --prompt-file path/to/prompt.md ``` This creates: @@ -129,7 +134,7 @@ Each agent works independently in its own branch. Results can be reviewed and me | `--agent` / `-a` | Start AI coding agent after creation | | `--prompt` / `-p` | Initial prompt for the agent (short prompts only) | | `--prompt-file` / `-P` | Read prompt from file (recommended for longer prompts) | -| `--from` / `-f` | Base branch (default: origin/main) | +| `--from` / `-f` | Base ref (default: origin/main). **Use `--from HEAD` when reviewing/testing current branch!** | | `--with-agent` | Specific agent: claude, aider, codex, gemini | | `--agent-args` | Extra arguments for the agent | diff --git a/agent_cli/dev/skill/examples.md b/agent_cli/dev/skill/examples.md index e833eb96d..5a06a8d2c 100644 --- a/agent_cli/dev/skill/examples.md +++ b/agent_cli/dev/skill/examples.md @@ -20,7 +20,68 @@ Each prompt for a spawned agent should follow this structure: 5. **Focused scope** - Keep solutions minimal, implement only what's requested 6. **Structured report** - Write conclusions to `.claude/REPORT.md` -## Scenario 1: Multi-feature implementation +## Scenario 1: Code review of current branch + +**User request**: "Review the code on this branch" or "Spawn an agent to review my changes" + +**CRITICAL**: Use `--from HEAD` (or the branch name) so the review agent has access to the changes! + +```bash +# Review the current branch - MUST use --from HEAD +agent-cli dev new review-changes --from HEAD --agent --prompt "Review the code changes on this branch. + + +- Run git diff origin/main...HEAD to identify all changes +- Read changed files in parallel to understand context +- Check CLAUDE.md for project-specific guidelines +- Test changes with real services if applicable + + + +- Use git diff origin/main...HEAD to see the full diff +- Read each changed file completely before judging +- Look at surrounding code to understand patterns +- Check existing tests to understand expected behavior + + + +Code review catches issues before merge. Focus on real problems - not style nitpicks. Apply these criteria: +- Code cleanliness: Is the implementation clean and well-structured? +- DRY principle: Does it avoid duplication? +- Code reuse: Are there parts that should be reused from other places? +- Organization: Is everything in the right place? +- Consistency: Is it in the same style as other parts of the codebase? +- Simplicity: Is it over-engineered? Remember KISS and YAGNI. No dead code paths, no defensive programming. +- No pointless wrappers: Functions that just call another function should be inlined. +- User experience: Does it provide a good user experience? +- Tests: Are tests meaningful or just trivial coverage? +- Live tests: Test changes with real services if applicable. +- Rules: Does the code follow CLAUDE.md guidelines? + + + +Review only - identify issues but do not fix them. Write findings to report. + + + +Write your review to .claude/REPORT.md: + +## Summary +[Overall assessment of the changes] + +## Issues Found +| Severity | File:Line | Issue | Suggestion | +|----------|-----------|-------|------------| +| Critical/High/Medium/Low | path:123 | description | fix | + +## Positive Observations +[What's well done] +" +``` + +**Common mistake**: Forgetting `--from HEAD` means the agent starts from `origin/main` and won't see any of the branch changes! + +## Scenario 2: Multi-feature implementation **User request**: "Implement user auth, payment processing, and email notifications" @@ -169,7 +230,7 @@ After verifying tests pass, write to .claude/REPORT.md with summary, files chang " ``` -## Scenario 2: Test-driven development +## Scenario 3: Test-driven development **User request**: "Add a caching layer with comprehensive tests" @@ -289,7 +350,7 @@ After ALL tests pass, write to .claude/REPORT.md: " ``` -## Scenario 3: Large refactoring by module +## Scenario 4: Large refactoring by module **User request**: "Refactor the API to use consistent error handling" @@ -357,7 +418,7 @@ After tests pass and linting is clean, write to .claude/REPORT.md: " ``` -## Scenario 4: Documentation and implementation in parallel +## Scenario 5: Documentation and implementation in parallel **User request**: "Add a plugin system with documentation" From e0925e3949a31511e7e25db1d76fc5640caa0baf Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 25 Jan 2026 06:17:21 -0800 Subject: [PATCH 08/16] fix(tts): address PR review feedback - use helpers and clean up deps - Use existing pcm_to_wav helper instead of duplicating WAV conversion - Remove redundant librosa/soundfile deps (already transitive from qwen-tts) - Restore compact JSON format for _extras.json - Remove unused io/wave imports from qwen.py --- agent_cli/_extras.json | 85 ++++----------------------- agent_cli/_requirements/qwen-tts.txt | 5 +- agent_cli/server/tts/backends/qwen.py | 25 +++----- pyproject.toml | 2 - scripts/sync_extras.py | 9 ++- uv.lock | 4 -- 6 files changed, 28 insertions(+), 102 deletions(-) diff --git a/agent_cli/_extras.json b/agent_cli/_extras.json index 84dad9e59..51c037685 100644 --- a/agent_cli/_extras.json +++ b/agent_cli/_extras.json @@ -1,75 +1,14 @@ { - "audio": [ - "Audio recording/playback", - [ - "sounddevice" - ] - ], - "faster-whisper": [ - "Faster Whisper ASR backend", - [ - "faster_whisper" - ] - ], - "kokoro": [ - "Kokoro TTS backend (GPU-accelerated)", - [ - "kokoro" - ] - ], - "llm": [ - "LLM framework (pydantic-ai)", - [ - "pydantic_ai" - ] - ], - "memory": [ - "Long-term memory proxy", - [ - "chromadb", - "yaml" - ] - ], - "mlx-whisper": [ - "MLX Whisper ASR for Apple Silicon", - [ - "mlx_whisper" - ] - ], - "piper": [ - "Piper TTS backend (CPU-friendly)", - [ - "piper" - ] - ], - "qwen-tts": [ - "Qwen3-TTS backend (multilingual)", - [ - "qwen_tts" - ] - ], - "rag": [ - "RAG proxy (ChromaDB, embeddings)", - [ - "chromadb" - ] - ], - "server": [ - "FastAPI server components", - [ - "fastapi" - ] - ], - "speed": [ - "Audio speed adjustment (audiostretchy)", - [ - "audiostretchy" - ] - ], - "vad": [ - "Voice Activity Detection (silero-vad)", - [ - "silero_vad" - ] - ] + "audio": ["Audio recording/playback", ["sounddevice"]], + "faster-whisper": ["Faster Whisper ASR backend", ["faster_whisper"]], + "kokoro": ["Kokoro TTS backend (GPU-accelerated)", ["kokoro"]], + "llm": ["LLM framework (pydantic-ai)", ["pydantic_ai"]], + "memory": ["Long-term memory proxy", ["chromadb", "yaml"]], + "mlx-whisper": ["MLX Whisper ASR for Apple Silicon", ["mlx_whisper"]], + "piper": ["Piper TTS backend (CPU-friendly)", ["piper"]], + "qwen-tts": ["Qwen3-TTS backend (multilingual)", ["qwen_tts"]], + "rag": ["RAG proxy (ChromaDB, embeddings)", ["chromadb"]], + "server": ["FastAPI server components", ["fastapi"]], + "speed": ["Audio speed adjustment (audiostretchy)", ["audiostretchy"]], + "vad": ["Voice Activity Detection (silero-vad)", ["silero_vad"]] } diff --git a/agent_cli/_requirements/qwen-tts.txt b/agent_cli/_requirements/qwen-tts.txt index b5cc8c76f..58ac499a6 100644 --- a/agent_cli/_requirements/qwen-tts.txt +++ b/agent_cli/_requirements/qwen-tts.txt @@ -133,9 +133,7 @@ joblib==1.5.3 lazy-loader==0.4 # via librosa librosa==0.11.0 - # via - # agent-cli - # qwen-tts + # via qwen-tts llvmlite==0.46.0 # via numba markdown-it-py==4.0.0 @@ -328,7 +326,6 @@ six==1.17.0 # via python-dateutil soundfile==0.13.1 # via - # agent-cli # librosa # qwen-tts sox==1.5.0 diff --git a/agent_cli/server/tts/backends/qwen.py b/agent_cli/server/tts/backends/qwen.py index 0f0960178..3c6cbc0e1 100644 --- a/agent_cli/server/tts/backends/qwen.py +++ b/agent_cli/server/tts/backends/qwen.py @@ -3,10 +3,8 @@ from __future__ import annotations import asyncio -import io import logging import time -import wave from concurrent.futures import ProcessPoolExecutor from dataclasses import dataclass from multiprocessing import get_context @@ -153,27 +151,20 @@ def _synthesize_in_subprocess( audio = wavs[0] - # Apply speed adjustment if needed (using librosa if available) + # Apply speed adjustment if needed if speed != 1.0: - try: - import librosa # noqa: PLC0415 + import librosa # noqa: PLC0415 - audio = librosa.effects.time_stretch(audio.astype(np.float32), rate=speed) - except ImportError: - logger.warning("Speed adjustment requested but librosa not available") + audio = librosa.effects.time_stretch(audio.astype(np.float32), rate=speed) - # Convert to int16 WAV - audio_int16 = (audio * 32767).astype(np.int16) + # Convert to int16 WAV using existing helper + from agent_cli.services import pcm_to_wav # noqa: PLC0415 - buffer = io.BytesIO() - with wave.open(buffer, "wb") as wav: - wav.setnchannels(1) - wav.setsampwidth(2) - wav.setframerate(sr) - wav.writeframes(audio_int16.tobytes()) + audio_int16 = (audio * 32767).astype(np.int16) + wav_bytes = pcm_to_wav(audio_int16.tobytes(), sample_rate=sr) return { - "audio": buffer.getvalue(), + "audio": wav_bytes, "sample_rate": sr, "duration": len(audio_int16) / sr, } diff --git a/pyproject.toml b/pyproject.toml index 2d2b273dc..f05707d20 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,8 +84,6 @@ kokoro = [ qwen-tts = [ "fastapi[standard]", "qwen-tts>=0.0.5", - "soundfile>=0.12.0", - "librosa>=0.10.0", # For speed adjustment ] speed = ["audiostretchy>=1.3.0"] diff --git a/scripts/sync_extras.py b/scripts/sync_extras.py index 67cb617ba..89ad402c6 100755 --- a/scripts/sync_extras.py +++ b/scripts/sync_extras.py @@ -132,9 +132,14 @@ def main() -> int: print(f"Warning: The following extras need metadata in EXTRA_METADATA: {missing}") print("Please update EXTRA_METADATA in scripts/sync_extras.py") - # Generate the file + # Generate the file (compact format with one entry per line) content = generate_extras_json(extras) - EXTRAS_FILE.write_text(json.dumps(content, indent=2) + "\n") + lines = ["{"] + for i, (key, value) in enumerate(sorted(content.items())): + comma = "," if i < len(content) - 1 else "" + lines.append(f' "{key}": {json.dumps(value)}{comma}') + lines.append("}") + EXTRAS_FILE.write_text("\n".join(lines) + "\n") print(f"Generated {EXTRAS_FILE}") return 0 diff --git a/uv.lock b/uv.lock index d6f89bd52..d4aede767 100644 --- a/uv.lock +++ b/uv.lock @@ -106,9 +106,7 @@ piper = [ ] qwen-tts = [ { name = "fastapi", extra = ["standard"] }, - { name = "librosa" }, { name = "qwen-tts" }, - { name = "soundfile" }, ] rag = [ { name = "chromadb" }, @@ -177,7 +175,6 @@ requires-dist = [ { name = "huggingface-hub", marker = "extra == 'memory'", specifier = ">=0.20.0" }, { name = "huggingface-hub", marker = "extra == 'rag'", specifier = ">=0.20.0" }, { name = "kokoro", marker = "extra == 'kokoro'", specifier = ">=0.9.0" }, - { name = "librosa", marker = "extra == 'qwen-tts'", specifier = ">=0.10.0" }, { name = "markdown-code-runner", marker = "extra == 'dev'", specifier = ">=2.7.0" }, { name = "markitdown", extras = ["docx", "pdf", "pptx"], marker = "extra == 'rag'", specifier = ">=0.1.3" }, { name = "mlx-whisper", marker = "platform_machine == 'arm64' and sys_platform == 'darwin' and extra == 'mlx-whisper'", specifier = ">=0.4.0" }, @@ -213,7 +210,6 @@ requires-dist = [ { name = "silero-vad", marker = "extra == 'vad'", specifier = ">=5.1" }, { name = "sounddevice", marker = "extra == 'audio'", specifier = ">=0.4.6" }, { name = "soundfile", marker = "extra == 'kokoro'", specifier = ">=0.12.0" }, - { name = "soundfile", marker = "extra == 'qwen-tts'", specifier = ">=0.12.0" }, { name = "transformers", marker = "extra == 'kokoro'", specifier = ">=4.40.0" }, { name = "transformers", marker = "extra == 'memory'", specifier = ">=4.30.0" }, { name = "transformers", marker = "extra == 'rag'", specifier = ">=4.30.0" }, From d44164e6324f637a32650f149e8abcba3edcebd0 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 25 Jan 2026 06:21:19 -0800 Subject: [PATCH 09/16] fix(tts): preserve original _extras.json format and ordering Restore original format for _extras.json - just add qwen-tts entry without changing existing entries or ordering. --- agent_cli/_extras.json | 14 +++++++------- scripts/sync_extras.py | 17 ++++++----------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/agent_cli/_extras.json b/agent_cli/_extras.json index 51c037685..cc36b38c5 100644 --- a/agent_cli/_extras.json +++ b/agent_cli/_extras.json @@ -1,14 +1,14 @@ { - "audio": ["Audio recording/playback", ["sounddevice"]], - "faster-whisper": ["Faster Whisper ASR backend", ["faster_whisper"]], - "kokoro": ["Kokoro TTS backend (GPU-accelerated)", ["kokoro"]], + "audio": ["Audio recording/playback with Wyoming protocol", ["numpy", "sounddevice", "wyoming"]], "llm": ["LLM framework (pydantic-ai)", ["pydantic_ai"]], "memory": ["Long-term memory proxy", ["chromadb", "yaml"]], - "mlx-whisper": ["MLX Whisper ASR for Apple Silicon", ["mlx_whisper"]], - "piper": ["Piper TTS backend (CPU-friendly)", ["piper"]], - "qwen-tts": ["Qwen3-TTS backend (multilingual)", ["qwen_tts"]], "rag": ["RAG proxy (ChromaDB, embeddings)", ["chromadb"]], "server": ["FastAPI server components", ["fastapi"]], "speed": ["Audio speed adjustment (audiostretchy)", ["audiostretchy"]], - "vad": ["Voice Activity Detection (silero-vad)", ["silero_vad"]] + "piper": ["Local Piper TTS", ["piper"]], + "kokoro": ["Kokoro neural TTS", ["kokoro"]], + "qwen-tts": ["Qwen3-TTS backend (multilingual)", ["qwen_tts"]], + "vad": ["Voice Activity Detection (silero-vad)", ["silero_vad"]], + "faster-whisper": ["Whisper ASR (CUDA/CPU)", ["faster_whisper"]], + "mlx-whisper": ["Whisper ASR (Apple Silicon)", ["mlx_whisper"]] } diff --git a/scripts/sync_extras.py b/scripts/sync_extras.py index 89ad402c6..cc72b2caa 100755 --- a/scripts/sync_extras.py +++ b/scripts/sync_extras.py @@ -44,11 +44,11 @@ "server": ("FastAPI server components", ["fastapi"]), "speed": ("Audio speed adjustment (audiostretchy)", ["audiostretchy"]), # Server backend extras - "piper": ("Piper TTS backend (CPU-friendly)", ["piper"]), - "kokoro": ("Kokoro TTS backend (GPU-accelerated)", ["kokoro"]), + "piper": ("Local Piper TTS", ["piper"]), + "kokoro": ("Kokoro neural TTS", ["kokoro"]), "qwen-tts": ("Qwen3-TTS backend (multilingual)", ["qwen_tts"]), - "faster-whisper": ("Faster Whisper ASR backend", ["faster_whisper"]), - "mlx-whisper": ("MLX Whisper ASR for Apple Silicon", ["mlx_whisper"]), + "faster-whisper": ("Whisper ASR (CUDA/CPU)", ["faster_whisper"]), + "mlx-whisper": ("Whisper ASR (Apple Silicon)", ["mlx_whisper"]), } @@ -132,14 +132,9 @@ def main() -> int: print(f"Warning: The following extras need metadata in EXTRA_METADATA: {missing}") print("Please update EXTRA_METADATA in scripts/sync_extras.py") - # Generate the file (compact format with one entry per line) + # Generate the file content = generate_extras_json(extras) - lines = ["{"] - for i, (key, value) in enumerate(sorted(content.items())): - comma = "," if i < len(content) - 1 else "" - lines.append(f' "{key}": {json.dumps(value)}{comma}') - lines.append("}") - EXTRAS_FILE.write_text("\n".join(lines) + "\n") + EXTRAS_FILE.write_text(json.dumps(content, indent=2) + "\n") print(f"Generated {EXTRAS_FILE}") return 0 From 782faaa76640e8a0113787febcff9cfc2b427da5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 25 Jan 2026 14:22:15 +0000 Subject: [PATCH 10/16] Update auto-generated docs --- docs/commands/install-extras.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/commands/install-extras.md b/docs/commands/install-extras.md index 62cd20c69..89607ad6d 100644 --- a/docs/commands/install-extras.md +++ b/docs/commands/install-extras.md @@ -31,18 +31,18 @@ Available extras: | Extra | Description | |-------|-------------| -| `audio` | Audio recording/playback | -| `faster-whisper` | Faster Whisper ASR backend | -| `kokoro` | Kokoro TTS backend (GPU-accelerated) | +| `audio` | Audio recording/playback with Wyoming protocol | | `llm` | LLM framework (pydantic-ai) | | `memory` | Long-term memory proxy | -| `mlx-whisper` | MLX Whisper ASR for Apple Silicon | -| `piper` | Piper TTS backend (CPU-friendly) | -| `qwen-tts` | Qwen3-TTS backend (multilingual) | | `rag` | RAG proxy (ChromaDB, embeddings) | | `server` | FastAPI server components | | `speed` | Audio speed adjustment (audiostretchy) | +| `piper` | Local Piper TTS | +| `kokoro` | Kokoro neural TTS | +| `qwen-tts` | Qwen3-TTS backend (multilingual) | | `vad` | Voice Activity Detection (silero-vad) | +| `faster-whisper` | Whisper ASR (CUDA/CPU) | +| `mlx-whisper` | Whisper ASR (Apple Silicon) | From c7a816ffa072673ba802410c6adf3b9939beb12f Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 25 Jan 2026 06:22:23 -0800 Subject: [PATCH 11/16] fix(tts): correct Qwen model size to ~4GB --- agent_cli/server/cli.py | 2 +- docs/commands/server/tts.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/agent_cli/server/cli.py b/agent_cli/server/cli.py index ec917b65d..9f31e340f 100644 --- a/agent_cli/server/cli.py +++ b/agent_cli/server/cli.py @@ -142,7 +142,7 @@ def _download_tts_models( download_dir = cache_dir or get_backend_cache_dir("qwen-tts") model_name = models[0] if models and models[0] != "qwen" else DEFAULT_QWEN_MODEL console.print(f"[bold]Downloading Qwen3-TTS model: {model_name}...[/bold]") - console.print(" This may take a while (model is ~3GB)") + console.print(" This may take a while (model is ~4GB)") snapshot_download(repo_id=model_name, cache_dir=download_dir) console.print("[bold green]Download complete![/bold green]") return diff --git a/docs/commands/server/tts.md b/docs/commands/server/tts.md index b13b2a468..32bb1d21e 100644 --- a/docs/commands/server/tts.md +++ b/docs/commands/server/tts.md @@ -235,7 +235,7 @@ Browse all voices at [rhasspy/piper](https://github.com/rhasspy/piper?tab=readme ### Qwen3-TTS Voices -Qwen voices are specified per-request via the API `voice` parameter. The model auto-downloads from HuggingFace on first use (~3GB). +Qwen voices are specified per-request via the API `voice` parameter. The model auto-downloads from HuggingFace on first use (~4GB). | Voice | Gender | Description | |-------|--------|-------------| From cf357c3b8e90d811755d6a89182d913e0672899e Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 25 Jan 2026 06:26:06 -0800 Subject: [PATCH 12/16] fix(tts): move first-party imports to module level in cli.py --- agent_cli/server/cli.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/agent_cli/server/cli.py b/agent_cli/server/cli.py index 9f31e340f..83b276074 100644 --- a/agent_cli/server/cli.py +++ b/agent_cli/server/cli.py @@ -15,6 +15,8 @@ from agent_cli.core.deps import requires_extras from agent_cli.core.process import set_process_title from agent_cli.server.common import setup_rich_logging +from agent_cli.server.tts.backends.base import get_backend_cache_dir +from agent_cli.server.tts.backends.qwen import DEFAULT_QWEN_MODEL console = Console() err_console = Console(stderr=True) @@ -108,9 +110,6 @@ def _download_tts_models( ) -> None: """Download TTS models/voices without starting the server.""" if backend == "kokoro": - from agent_cli.server.tts.backends.base import ( # noqa: PLC0415 - get_backend_cache_dir, - ) from agent_cli.server.tts.backends.kokoro import ( # noqa: PLC0415 DEFAULT_VOICE, _ensure_model, @@ -132,13 +131,6 @@ def _download_tts_models( if backend == "qwen": from huggingface_hub import snapshot_download # noqa: PLC0415 - from agent_cli.server.tts.backends.base import ( # noqa: PLC0415 - get_backend_cache_dir, - ) - from agent_cli.server.tts.backends.qwen import ( # noqa: PLC0415 - DEFAULT_QWEN_MODEL, - ) - download_dir = cache_dir or get_backend_cache_dir("qwen-tts") model_name = models[0] if models and models[0] != "qwen" else DEFAULT_QWEN_MODEL console.print(f"[bold]Downloading Qwen3-TTS model: {model_name}...[/bold]") @@ -150,8 +142,6 @@ def _download_tts_models( # Piper backend from piper.download_voices import download_voice # noqa: PLC0415 - from agent_cli.server.tts.backends.base import get_backend_cache_dir # noqa: PLC0415 - download_dir = cache_dir or get_backend_cache_dir("piper") console.print("[bold]Downloading Piper model(s)...[/bold]") for model_name in models: From 0f91e53083d74c60c6917b9a729fbf18ead5c5b4 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 25 Jan 2026 06:28:16 -0800 Subject: [PATCH 13/16] fix(tts): move pcm_to_wav import to module level --- agent_cli/server/tts/backends/qwen.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/agent_cli/server/tts/backends/qwen.py b/agent_cli/server/tts/backends/qwen.py index 3c6cbc0e1..f612284c3 100644 --- a/agent_cli/server/tts/backends/qwen.py +++ b/agent_cli/server/tts/backends/qwen.py @@ -18,6 +18,7 @@ get_backend_cache_dir, get_torch_device, ) +from agent_cli.services import pcm_to_wav logger = logging.getLogger(__name__) @@ -158,8 +159,6 @@ def _synthesize_in_subprocess( audio = librosa.effects.time_stretch(audio.astype(np.float32), rate=speed) # Convert to int16 WAV using existing helper - from agent_cli.services import pcm_to_wav # noqa: PLC0415 - audio_int16 = (audio * 32767).astype(np.int16) wav_bytes = pcm_to_wav(audio_int16.tobytes(), sample_rate=sr) From 2bc9b083e339729fd5d2983a122c69a15293333d Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 25 Jan 2026 06:32:26 -0800 Subject: [PATCH 14/16] fix(tts): remove flash_attention_2 requirement (not always available) --- agent_cli/server/tts/backends/qwen.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/agent_cli/server/tts/backends/qwen.py b/agent_cli/server/tts/backends/qwen.py index f612284c3..f2f19ff39 100644 --- a/agent_cli/server/tts/backends/qwen.py +++ b/agent_cli/server/tts/backends/qwen.py @@ -94,10 +94,9 @@ def _load_model_in_subprocess( "dtype": dtype, } - # Add device_map for CUDA + # Add device_map for CUDA/MPS/CPU if device == "cuda": load_kwargs["device_map"] = "cuda:0" - load_kwargs["attn_implementation"] = "flash_attention_2" elif device == "mps": load_kwargs["device_map"] = "mps" else: From 3c5eab9693b51e08bd0b54a55a3a70d8cc6fc215 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 25 Jan 2026 07:24:14 -0800 Subject: [PATCH 15/16] perf(tts): add torch.compile() optimization for Qwen backend Adds torch.compile() with reduce-overhead mode for 20-30% faster inference on CUDA. First few inferences will be slower due to JIT compilation warmup. Based on optimizations from Qwen3-TTS-Openai-Fastapi repo. --- agent_cli/server/tts/backends/qwen.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/agent_cli/server/tts/backends/qwen.py b/agent_cli/server/tts/backends/qwen.py index f2f19ff39..01feedb4d 100644 --- a/agent_cli/server/tts/backends/qwen.py +++ b/agent_cli/server/tts/backends/qwen.py @@ -105,6 +105,20 @@ def _load_model_in_subprocess( # Load model model = Qwen3TTSModel.from_pretrained(model_name, cache_dir=cache_dir, **load_kwargs) + # Apply torch.compile() for faster inference (20-30% speedup) + # Requires PyTorch 2.0+, warmup needed for first few inferences + if device == "cuda": + try: + logger.info("Applying torch.compile() optimization...") + model.model = torch.compile( + model.model, + mode="reduce-overhead", + fullgraph=False, + ) + logger.info("torch.compile() optimization applied") + except Exception as e: + logger.warning("Could not apply torch.compile(): %s", e) + # Store in subprocess state for reuse _state.model = model _state.device = device From 7fcfefa531ff56405b5262131e28706080c7bbd0 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Sun, 25 Jan 2026 07:25:53 -0800 Subject: [PATCH 16/16] perf(tts): add Flash Attention 2 support for Qwen backend Automatically enables Flash Attention 2 if flash-attn package is installed, providing ~10% additional speedup on top of torch.compile(). Install with: pip install flash-attn --- agent_cli/server/tts/backends/qwen.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/agent_cli/server/tts/backends/qwen.py b/agent_cli/server/tts/backends/qwen.py index 01feedb4d..18455bb08 100644 --- a/agent_cli/server/tts/backends/qwen.py +++ b/agent_cli/server/tts/backends/qwen.py @@ -102,6 +102,16 @@ def _load_model_in_subprocess( else: load_kwargs["device_map"] = "cpu" + # Use Flash Attention 2 if available (10% speedup, requires flash-attn package) + if device == "cuda": + try: + import flash_attn # noqa: F401, PLC0415 + + load_kwargs["attn_implementation"] = "flash_attention_2" + logger.info("Flash Attention 2 enabled") + except ImportError: + logger.info("flash-attn not installed, using default attention") + # Load model model = Qwen3TTSModel.from_pretrained(model_name, cache_dir=cache_dir, **load_kwargs)