commit d31536d9fc2b017fd2cb0138606403382792608e Author: ANLGBOY Date: Wed Nov 19 01:18:16 2025 +0900 init diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..855e325 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +assets/onnx/*.onnx filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..32804eb --- /dev/null +++ b/.gitignore @@ -0,0 +1,61 @@ +assets/* +assets/.git +assets/.gitignore +assets/.gitattributes + +*.onnx +onnx + +# Output files +results + +# Python +__pycache__ +*.py[cod] +*$py.class +*.so +.Python + +# Virtual environments +.venv +venv/ +ENV/ +env/ + +# Node.js +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +package-lock.json + +# Swift +.build/ +.swiftpm/ +*.xcodeproj +*.xcworkspace +xcuserdata/ +DerivedData/ + +# Distribution / packaging +build/ +dist/ +*.egg-info/ +.eggs/ + +# Testing +.pytest_cache/ +.coverage +htmlcov/ +.tox/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..943171b --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Supertone Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f23af76 --- /dev/null +++ b/README.md @@ -0,0 +1,393 @@ +# Supertonic β€” Lightning Fast, On-Device TTS + +[![Demo](https://img.shields.io/badge/πŸ€—%20Hugging%20Face-Demo-yellow)](https://huggingface.co/spaces/Supertone/supertonic#interactive-demo) +[![Models](https://img.shields.io/badge/πŸ€—%20Hugging%20Face-Models-blue)](https://huggingface.co/Supertone/supertonic) + +

+ Supertonic Banner +

+ +**Supertonic** is a lightning-fast, on-device text-to-speech system designed for **extreme performance** with minimal computational overhead. Powered by ONNX Runtime, it runs entirely on your deviceβ€”no cloud, no API calls, no privacy concerns. + +> 🎧 **Try it now**: Experience Supertonic in your browser with our [**Interactive Demo**](https://huggingface.co/spaces/Supertone/supertonic#interactive-demo), or get started with pre-trained models from [**Hugging Face Hub**](https://huggingface.co/Supertone/supertonic) + +### Table of Contents + +- [Why Supertonic?](#why-supertonic) +- [Language Support](#language-support) +- [Getting Started](#getting-started) +- [Performance](#performance) +- [Citation](#citation) +- [License](#license) + +## Why Supertonic? + +- **⚑ Blazingly Fast**: Generates speech up to **167Γ— faster than real-time** on consumer hardware (M4 Pro)β€”unmatched by any other TTS system +- **πŸͺΆ Ultra Lightweight**: Only **66M parameters**, optimized for efficient on-device performance with minimal footprint +- **πŸ“± On-Device Capable**: **Complete privacy** and **zero latency**β€”all processing happens locally on your device +- **🎨 Natural Text Handling**: Seamlessly processes numbers, dates, currency, abbreviations, and complex expressions without pre-processing +- **βš™οΈ Highly Configurable**: Adjust inference steps, batch processing, and other parameters to match your specific needs +- **🧩 Flexible Deployment**: Deploy seamlessly across servers, browsers, and edge devices with multiple runtime backends. + + +## Language Support + +We provide ready-to-use TTS inference examples across multiple ecosystems: + +| Language/Platform | Path | Description | +|-------------------|------|-------------| +| [**Python**](py/) | `py/` | ONNX Runtime inference | +| [**Node.js**](nodejs/) | `nodejs/` | Server-side JavaScript | +| [**Browser**](web/) | `web/` | WebGPU/WASM inference | +| [**Java**](java/) | `java/` | Cross-platform JVM | +| [**C++**](cpp/) | `cpp/` | High-performance C++ | +| [**C#**](csharp/) | `csharp/` | .NET ecosystem | +| [**Go**](go/) | `go/` | Go implementation | +| [**Swift**](swift/) | `swift/` | macOS applications | +| [**iOS**](ios/) | `ios/` | Native iOS apps | +| [**Rust**](rust/) | `rust/` | Memory-safe systems | + +> For detailed usage instructions, please refer to the README.md in each language directory. + +## Getting Started + +First, clone the repository: + +```bash +git clone https://github.com/supertone-inc/supertonic.git +cd supertonic +``` + +### Prerequisites + +Before running the examples, download the ONNX models and preset voices, and place them in the `assets` directory: + +> **Note:** The Hugging Face repository uses Git LFS. Please ensure Git LFS is installed and initialized before cloning or pulling large model files. +> - macOS: `brew install git-lfs && git lfs install` +> - Generic: see `https://git-lfs.com` for installers + +```bash +git clone https://huggingface.co/Supertone/supertonic assets +``` + +### Quick Start + +**Python Example** ([Details](py/)) +```bash +cd py +uv sync +uv run example_onnx.py +``` + +**Node.js Example** ([Details](nodejs/)) +```bash +cd nodejs +npm install +npm start +``` + +**Browser Example** ([Details](web/)) +```bash +cd web +npm install +npm run dev +``` + +**Java Example** ([Details](java/)) +```bash +cd java +mvn clean install +mvn exec:java +``` + +**C++ Example** ([Details](cpp/)) +```bash +cd cpp +mkdir build && cd build +cmake .. && cmake --build . --config Release +./example_onnx +``` + +**C# Example** ([Details](csharp/)) +```bash +cd csharp +dotnet restore +dotnet run +``` + +**Go Example** ([Details](go/)) +```bash +cd go +go mod download +go run example_onnx.go helper.go +``` + +**Swift Example** ([Details](swift/)) +```bash +cd swift +swift build -c release +.build/release/example_onnx +``` + +**Rust Example** ([Details](rust/)) +```bash +cd rust +cargo build --release +./target/release/example_onnx +``` + +**iOS Example** ([Details](ios/)) +```bash +cd ios/ExampleiOSApp +xcodegen generate +open ExampleiOSApp.xcodeproj +``` +- In Xcode: Targets β†’ ExampleiOSApp β†’ Signing: select your Team +- Choose your iPhone as run destination β†’ Build & Run + + +### Technical Details + +- **Runtime**: ONNX Runtime for cross-platform inference (CPU-optimized; GPU mode is not tested) +- **Browser Support**: onnxruntime-web for client-side inference +- **Batch Processing**: Supports batch inference for improved throughput +- **Audio Output**: Outputs 16-bit WAV files + +## Performance + +We evaluated Supertonic's performance (with 2 inference steps) using two key metrics across input texts of varying lengths: Short (59 chars), Mid (152 chars), and Long (266 chars). + +**Metrics:** +- **Characters per Second**: Measures throughput by dividing the number of input characters by the time required to generate audio. Higher is better. +- **Real-time Factor (RTF)**: Measures the time taken to synthesize audio relative to its duration. Lower is better (e.g., RTF of 0.1 means it takes 0.1 seconds to generate one second of audio). + +### Characters per Second +| System | Short (59 chars) | Mid (152 chars) | Long (266 chars) | +|--------|-----------------|----------------|-----------------| +| **Supertonic** (M4 pro - CPU) | 912 | 1048 | 1263 | +| **Supertonic** (M4 pro - WebGPU) | 996 | 1801 | 2509 | +| **Supertonic** (RTX4090) | 2615 | 6548 | 12164 | +| `API` [ElevenLabs Flash v2.5](https://elevenlabs.io/docs/api-reference/text-to-speech/convert) | 144 | 209 | 287 | +| `API` [OpenAI TTS-1](https://platform.openai.com/docs/guides/text-to-speech) | 37 | 55 | 82 | +| `API` [Gemini 2.5 Flash TTS](https://ai.google.dev/gemini-api/docs/speech-generation) | 12 | 18 | 24 | +| `API` [Supertone Sona speech 1](https://docs.supertoneapi.com/en/api-reference/endpoints/text-to-speech) | 38 | 64 | 92 | +| `Open` [Kokoro](https://github.com/hexgrad/kokoro/) | 104 | 107 | 117 | +| `Open` [NeuTTS Air](https://github.com/neuphonic/neutts-air) | 37 | 42 | 47 | + +> **Notes:** +> `API` = Cloud-based API services (measured from Seoul) +> `Open` = Open-source models +> Supertonic (M4 pro - CPU) and (M4 pro - WebGPU): Tested with ONNX +> Supertonic (RTX4090): Tested with PyTorch model +> Kokoro: Tested on M4 Pro CPU with ONNX +> NeuTTS Air: Tested on M4 Pro CPU with Q8-GGUF + +### Real-time Factor + +| System | Short (59 chars) | Mid (152 chars) | Long (266 chars) | +|--------|-----------------|----------------|-----------------| +| **Supertonic** (M4 pro - CPU) | 0.015 | 0.013 | 0.012 | +| **Supertonic** (M4 pro - WebGPU) | 0.014 | 0.007 | 0.006 | +| **Supertonic** (RTX4090) | 0.005 | 0.002 | 0.001 | +| `API` [ElevenLabs Flash v2.5](https://elevenlabs.io/docs/api-reference/text-to-speech/convert) | 0.133 | 0.077 | 0.057 | +| `API` [OpenAI TTS-1](https://platform.openai.com/docs/guides/text-to-speech) | 0.471 | 0.302 | 0.201 | +| `API` [Gemini 2.5 Flash TTS](https://ai.google.dev/gemini-api/docs/speech-generation) | 1.060 | 0.673 | 0.541 | +| `API` [Supertone Sona speech 1](https://docs.supertoneapi.com/en/api-reference/endpoints/text-to-speech) | 0.372 | 0.206 | 0.163 | +| `Open` [Kokoro](https://github.com/hexgrad/kokoro/) | 0.144 | 0.124 | 0.126 | +| `Open` [NeuTTS Air](https://github.com/neuphonic/neutts-air) | 0.390 | 0.338 | 0.343 | + +
+Additional Performance Data (5-step inference) + +
+ +**Characters per Second (5-step)** + +| System | Short (59 chars) | Mid (152 chars) | Long (266 chars) | +|--------|-----------------|----------------|-----------------| +| **Supertonic** (M4 pro - CPU) | 596 | 691 | 850 | +| **Supertonic** (M4 pro - WebGPU) | 570 | 1118 | 1546 | +| **Supertonic** (RTX4090) | 1286 | 3757 | 6242 | + +**Real-time Factor (5-step)** + +| System | Short (59 chars) | Mid (152 chars) | Long (266 chars) | +|--------|-----------------|----------------|-----------------| +| **Supertonic** (M4 pro - CPU) | 0.023 | 0.019 | 0.018 | +| **Supertonic** (M4 pro - WebGPU) | 0.024 | 0.012 | 0.010 | +| **Supertonic** (RTX4090) | 0.011 | 0.004 | 0.002 | + +
+ +### Natural Text Handling + +Supertonic is designed to handle complex, real-world text inputs that contain numbers, currency symbols, abbreviations, dates, and proper nouns. + +> 🎧 **View audio samples more easily**: Check out our [**Interactive Demo**](https://huggingface.co/spaces/Supertone/supertonic#text-handling) for a better viewing experience of all audio examples + +**Overview of Test Cases:** + +| Category | Key Challenges | Supertonic | ElevenLabs | OpenAI | Gemini | +|:--------:|:--------------:|:----------:|:----------:|:------:|:------:| +| Financial Expression | Decimal currency, abbreviated magnitudes (M, K), currency symbols, currency codes | βœ… | ❌ | ❌ | ❌ | +| Time and Date | Time notation, abbreviated weekdays/months, date formats | βœ… | ❌ | ❌ | ❌ | +| Phone Number | Area codes, hyphens, extensions (ext.) | βœ… | ❌ | ❌ | ❌ | +| Technical Unit | Decimal numbers with units, abbreviated technical notations | βœ… | ❌ | ❌ | ❌ | + +
+Example 1: Financial Expression + +
+ +**Text:** +> "The startup secured **$5.2M** in venture capital, a huge leap from their initial **$450K** seed round." + +**Challenges:** +- Decimal point in currency ($5.2M should be read as "five point two million") +- Abbreviated magnitude units (M for million, K for thousand) +- Currency symbol ($) that needs to be properly pronounced as "dollars" + +**Audio Samples:** + +| System | Result | Audio Sample | +|--------|--------|--------------| +| **Supertonic** | βœ… | [🎧 Play Audio](https://drive.google.com/file/d/1eancUOhiSXCVoTu9ddh4S-OcVQaWrPV-/view?usp=sharing) | +| ElevenLabs Flash v2.5 | ❌ | [🎧 Play Audio](https://drive.google.com/file/d/1-r2scv7XQ1crIDu6QOh3eqVl445W6ap_/view?usp=sharing) | +| OpenAI TTS-1 | ❌ | [🎧 Play Audio](https://drive.google.com/file/d/1MFDXMjfmsAVOqwPx7iveS0KUJtZvcwxB/view?usp=sharing) | +| Gemini 2.5 Flash TTS | ❌ | [🎧 Play Audio](https://drive.google.com/file/d/1dEHpNzfMUucFTJPQK0k4RcFZvPwQTt09/view?usp=sharing) | + +
+ +
+Example 2: Time and Date + +
+ +**Text:** +> "The train delay was announced at **4:45 PM** on **Wed, Apr 3, 2024** due to track maintenance." + +**Challenges:** +- Time expression with PM notation (4:45 PM) +- Abbreviated weekday (Wed) +- Abbreviated month (Apr) +- Full date format (Apr 3, 2024) + +**Audio Samples:** + +| System | Result | Audio Sample | +|--------|--------|--------------| +| **Supertonic** | βœ… | [🎧 Play Audio](https://drive.google.com/file/d/1ehkZU8eiizBenG2DgR5tzBGQBvHS0Uaj/view?usp=sharing) | +| ElevenLabs Flash v2.5 | ❌ | [🎧 Play Audio](https://drive.google.com/file/d/1ta3r6jFyebmA-sT44l8EaEQcMLVmuOEr/view?usp=sharing) | +| OpenAI TTS-1 | ❌ | [🎧 Play Audio](https://drive.google.com/file/d/1sskmem9AzHAQ3Hv8DRSZoqX_pye-CXuU/view?usp=sharing) | +| Gemini 2.5 Flash TTS | ❌ | [🎧 Play Audio](https://drive.google.com/file/d/1zx9X8oMsLMXW0Zx_SURoqjju-By2yh_n/view?usp=sharing) | + +
+ +
+Example 3: Phone Number + +
+ +**Text:** +> "You can reach the hotel front desk at **(212) 555-0142 ext. 402** anytime." + +**Challenges:** +- Area code in parentheses that should be read as separate digits +- Phone number with hyphen separator (555-0142) +- Abbreviated extension notation (ext.) +- Extension number (402) + +**Audio Samples:** + +| System | Result | Audio Sample | +|--------|--------|--------------| +| **Supertonic** | βœ… | [🎧 Play Audio](https://drive.google.com/file/d/1z-e5iTsihryMR8ll1-N1YXkB2CIJYJ6F/view?usp=sharing) | +| ElevenLabs Flash v2.5 | ❌ | [🎧 Play Audio](https://drive.google.com/file/d/1HAzVXFTZfZm0VEK2laSpsMTxzufcuaxA/view?usp=sharing) | +| OpenAI TTS-1 | ❌ | [🎧 Play Audio](https://drive.google.com/file/d/15tjfAmb3GbjP_kmvD7zSdIWkhtAaCPOg/view?usp=sharing) | +| Gemini 2.5 Flash TTS | ❌ | [🎧 Play Audio](https://drive.google.com/file/d/1BCL8n7yligUZyso970ud7Gf5NWb1OhKD/view?usp=sharing) | + +
+ +
+Example 4: Technical Unit + +
+ +**Text:** +> "Our drone battery lasts **2.3h** when flying at **30kph** with full camera payload." + +**Challenges:** +- Decimal time duration with abbreviation (2.3h = two point three hours) +- Speed unit with abbreviation (30kph = thirty kilometers per hour) +- Technical abbreviations (h for hours, kph for kilometers per hour) +- Technical/engineering context requiring proper pronunciation + +**Audio Samples:** + +| System | Result | Audio Sample | +|--------|--------|--------------| +| **Supertonic** | βœ… | [🎧 Play Audio](https://drive.google.com/file/d/1kvOBvswFkLfmr8hGplH0V2XiMxy1shYf/view?usp=sharing) | +| ElevenLabs Flash v2.5 | ❌ | [🎧 Play Audio](https://drive.google.com/file/d/1_SzfjWJe5YEd0t3R7DztkYhHcI_av48p/view?usp=sharing) | +| OpenAI TTS-1 | ❌ | [🎧 Play Audio](https://drive.google.com/file/d/1P5BSilj5xFPTV2Xz6yW5jitKZohO9o-6/view?usp=sharing) | +| Gemini 2.5 Flash TTS | ❌ | [🎧 Play Audio](https://drive.google.com/file/d/1GU82SnWC50OvC8CZNjhxvNZFKQb7I9_Y/view?usp=sharing) | + +
+ +> **Note:** These samples demonstrate how each system handles text normalization and pronunciation of complex expressions **without requiring pre-processing or phonetic annotations**. + +## Citation + +The following papers describe the core technologies used in Supertonic. If you use this system in your research or find these techniques useful, please consider citing the relevant papers: + +### SupertonicTTS: Main Architecture + +This paper introduces the overall architecture of SupertonicTTS, including the speech autoencoder, flow-matching based text-to-latent module, and efficient design choices. + +```bibtex +@article{kim2025supertonic, + title={SupertonicTTS: Towards Highly Efficient and Streamlined Text-to-Speech System}, + author={Kim, Hyeongju and Yang, Jinhyeok and Yu, Yechan and Ji, Seunghun and Morton, Jacob and Bous, Frederik and Byun, Joon and Lee, Juheon}, + journal={arXiv preprint arXiv:2503.23108}, + year={2025}, + url={https://arxiv.org/abs/2503.23108} +} +``` + +### Length-Aware RoPE: Text-Speech Alignment + +This paper presents Length-Aware Rotary Position Embedding (LARoPE), which improves text-speech alignment in cross-attention mechanisms. + +```bibtex +@article{kim2025larope, + title={Length-Aware Rotary Position Embedding for Text-Speech Alignment}, + author={Kim, Hyeongju and Lee, Juheon and Yang, Jinhyeok and Morton, Jacob}, + journal={arXiv preprint arXiv:2509.11084}, + year={2025}, + url={https://arxiv.org/abs/2509.11084} +} +``` + +### Self-Purifying Flow Matching: Training with Noisy Labels + +This paper describes the self-purification technique for training flow matching models robustly with noisy or unreliable labels. + +```bibtex +@article{kim2025spfm, + title={Training Flow Matching Models with Reliable Labels via Self-Purification}, + author={Kim, Hyeongju and Yu, Yechan and Yi, June Young and Lee, Juheon}, + journal={arXiv preprint arXiv:2509.19091}, + year={2025}, + url={https://arxiv.org/abs/2509.19091} +} +``` + +## License + +This project’s sample code is released under the MIT License. - see the [LICENSE](https://github.com/supertone-inc/supertonic?tab=MIT-1-ov-file) for details. + +The accompanying model is released under the OpenRAIL-M License. - see the [LICENSE](https://huggingface.co/Supertone/supertonic/blob/main/LICENSE) file for details. + +This model was trained using PyTorch, which is licensed under the BSD 3-Clause License but is not redistributed with this project. - see the [LICENSE](https://docs.pytorch.org/FBGEMM/general/License.html) for details. + +Copyright (c) 2025 Supertone Inc. + diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt new file mode 100644 index 0000000..f17563c --- /dev/null +++ b/cpp/CMakeLists.txt @@ -0,0 +1,122 @@ +cmake_minimum_required(VERSION 3.15) +project(Supertonic_CPP) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Enable aggressive optimization +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +# Add optimization flags +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -DNDEBUG -ffast-math") +set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3 -DNDEBUG -ffast-math") + +# Find required packages +find_package(PkgConfig REQUIRED) +find_package(OpenMP) + +# ONNX Runtime - Try multiple methods +# Method 1: Try to find via CMake config +find_package(onnxruntime QUIET CONFIG) + +if(NOT onnxruntime_FOUND) + # Method 2: Try pkg-config + pkg_check_modules(ONNXRUNTIME QUIET libonnxruntime) + + if(ONNXRUNTIME_FOUND) + set(ONNXRUNTIME_INCLUDE_DIR ${ONNXRUNTIME_INCLUDE_DIRS}) + set(ONNXRUNTIME_LIB ${ONNXRUNTIME_LIBRARIES}) + else() + # Method 3: Manual search in common locations + find_path(ONNXRUNTIME_INCLUDE_DIR + NAMES onnxruntime_cxx_api.h + PATHS + /usr/local/include + /opt/homebrew/include + /usr/include + ${CMAKE_PREFIX_PATH}/include + PATH_SUFFIXES onnxruntime + ) + + find_library(ONNXRUNTIME_LIB + NAMES onnxruntime libonnxruntime + PATHS + /usr/local/lib + /opt/homebrew/lib + /usr/lib + ${CMAKE_PREFIX_PATH}/lib + ) + endif() + + if(NOT ONNXRUNTIME_INCLUDE_DIR OR NOT ONNXRUNTIME_LIB) + message(FATAL_ERROR "ONNX Runtime not found. Please install it:\n" + " macOS: brew install onnxruntime\n" + " Ubuntu: See README.md for installation instructions") + endif() + + message(STATUS "Found ONNX Runtime:") + message(STATUS " Include: ${ONNXRUNTIME_INCLUDE_DIR}") + message(STATUS " Library: ${ONNXRUNTIME_LIB}") +endif() + +# nlohmann/json +find_package(nlohmann_json REQUIRED) + +# Include directories +if(NOT onnxruntime_FOUND) + include_directories(${ONNXRUNTIME_INCLUDE_DIR}) +endif() + +# Helper library +add_library(tts_helper STATIC + helper.cpp + helper.h +) + +if(onnxruntime_FOUND) + target_link_libraries(tts_helper + onnxruntime::onnxruntime + nlohmann_json::nlohmann_json + ) +else() + target_include_directories(tts_helper PUBLIC ${ONNXRUNTIME_INCLUDE_DIR}) + target_link_libraries(tts_helper + ${ONNXRUNTIME_LIB} + nlohmann_json::nlohmann_json + ) +endif() + +# Enable OpenMP if available +if(OpenMP_CXX_FOUND) + target_link_libraries(tts_helper OpenMP::OpenMP_CXX) + message(STATUS "OpenMP enabled for parallel processing") +else() + message(WARNING "OpenMP not found - parallel processing will be disabled") +endif() + +# Example executable +add_executable(example_onnx + example_onnx.cpp +) + +if(onnxruntime_FOUND) + target_link_libraries(example_onnx + tts_helper + onnxruntime::onnxruntime + nlohmann_json::nlohmann_json + ) +else() + target_link_libraries(example_onnx + tts_helper + ${ONNXRUNTIME_LIB} + nlohmann_json::nlohmann_json + ) +endif() + +# Installation +install(TARGETS example_onnx DESTINATION bin) +install(TARGETS tts_helper DESTINATION lib) +install(FILES helper.h DESTINATION include) + diff --git a/cpp/README.md b/cpp/README.md new file mode 100644 index 0000000..a11aef4 --- /dev/null +++ b/cpp/README.md @@ -0,0 +1,101 @@ +# Supertonic C++ Implementation + +High-performance text-to-speech inference using ONNX Runtime. + +## Requirements + +- C++17 compiler, CMake 3.15+ +- Libraries: ONNX Runtime, nlohmann/json + +## Installation + +**Ubuntu/Debian:** +> ⚠️ **Note:** Installation instructions not yet verified. + +```bash +sudo apt-get install -y cmake g++ nlohmann-json3-dev +wget https://github.com/microsoft/onnxruntime/releases/download/v1.16.3/onnxruntime-linux-x64-1.16.3.tgz +tar -xzf onnxruntime-linux-x64-1.16.3.tgz +sudo cp -r onnxruntime-linux-x64-1.16.3/include/* /usr/local/include/ +sudo cp -r onnxruntime-linux-x64-1.16.3/lib/* /usr/local/lib/ +sudo ldconfig +``` + +**macOS:** +```bash +brew install cmake nlohmann-json onnxruntime +``` + +**Windows (vcpkg):** +> ⚠️ **Note:** Installation instructions not yet verified. + +```powershell +vcpkg install nlohmann-json:x64-windows onnxruntime:x64-windows +vcpkg integrate install +``` + +## Building + +```bash +cd cpp && mkdir build && cd build +cmake .. && cmake --build . --config Release +./example_onnx +``` + +## Basic Usage + +### Example 1: Default Inference +Run inference with default settings: +```bash +./example_onnx +``` + +This will use: +- Voice style: `../assets/voice_styles/M1.json` +- Text: "This morning, I took a walk in the park, and the sound of the birds and the breeze was so pleasant that I stopped for a long time just to listen." +- Output directory: `results/` +- Total steps: 5 +- Number of generations: 4 + +### Example 2: Batch Inference +Process multiple voice styles and texts at once: +```bash +./example_onnx \ + --voice-style ../assets/voice_styles/M1.json,../assets/voice_styles/F1.json \ + --text "The sun sets behind the mountains, painting the sky in shades of pink and orange.|The weather is beautiful and sunny outside. A gentle breeze makes the air feel fresh and pleasant." +``` + +This will: +- Generate speech for 2 different voice-text pairs +- Use male voice style (M1.json) for the first text +- Use female voice style (F1.json) for the second text +- Process both samples in a single batch + +### Example 3: High Quality Inference +Increase denoising steps for better quality: +```bash +./example_onnx \ + --total-step 10 \ + --voice-style ../assets/voice_styles/M1.json \ + --text "Increasing the number of denoising steps improves the output's fidelity and overall quality." +``` + +This will: +- Use 10 denoising steps instead of the default 5 +- Produce higher quality output at the cost of slower inference + +## Available Arguments + +| Argument | Type | Default | Description | +|----------|------|---------|-------------| +| `--onnx-dir` | str | `../assets/onnx` | Path to ONNX model directory | +| `--total-step` | int | 5 | Number of denoising steps (higher = better quality, slower) | +| `--n-test` | int | 4 | Number of times to generate each sample | +| `--voice-style` | str | `../assets/voice_styles/M1.json` | Voice style file path(s) (comma-separated for batch) | +| `--text` | str | (long default text) | Text(s) to synthesize (pipe-separated for batch) | +| `--save-dir` | str | `results` | Output directory | + +## Notes + +- **Batch Processing**: The number of `--voice-style` files must match the number of `--text` entries +- **Quality vs Speed**: Higher `--total-step` values produce better quality but take longer diff --git a/cpp/assets b/cpp/assets new file mode 120000 index 0000000..ec2e4be --- /dev/null +++ b/cpp/assets @@ -0,0 +1 @@ +../assets \ No newline at end of file diff --git a/cpp/example_onnx.cpp b/cpp/example_onnx.cpp new file mode 100644 index 0000000..09d51c1 --- /dev/null +++ b/cpp/example_onnx.cpp @@ -0,0 +1,109 @@ +#include "helper.h" +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +struct Args { + std::string onnx_dir = "../assets/onnx"; + int total_step = 5; + int n_test = 4; + std::vector voice_style = {"../assets/voice_styles/M1.json"}; + std::vector text = { + "This morning, I took a walk in the park, and the sound of the birds and the breeze was so pleasant that I stopped for a long time just to listen." + }; + std::string save_dir = "results"; +}; + +auto splitString = [](const std::string& str, char delim) { + std::vector result; + size_t start = 0, pos; + while ((pos = str.find(delim, start)) != std::string::npos) { + result.push_back(str.substr(start, pos - start)); + start = pos + 1; + } + result.push_back(str.substr(start)); + return result; +}; + +Args parseArgs(int argc, char* argv[]) { + Args args; + for (int i = 1; i < argc; i++) { + std::string arg = argv[i]; + if (arg == "--onnx-dir" && i + 1 < argc) args.onnx_dir = argv[++i]; + else if (arg == "--total-step" && i + 1 < argc) args.total_step = std::stoi(argv[++i]); + else if (arg == "--n-test" && i + 1 < argc) args.n_test = std::stoi(argv[++i]); + else if (arg == "--voice-style" && i + 1 < argc) args.voice_style = splitString(argv[++i], ','); + else if (arg == "--text" && i + 1 < argc) args.text = splitString(argv[++i], '|'); + else if (arg == "--save-dir" && i + 1 < argc) args.save_dir = argv[++i]; + } + return args; +} + +int main(int argc, char* argv[]) { + std::cout << "=== TTS Inference with ONNX Runtime (C++) ===\n\n"; + + // --- 1. Parse arguments --- // + Args args = parseArgs(argc, argv); + int total_step = args.total_step; + int n_test = args.n_test; + std::string save_dir = args.save_dir; + std::vector voice_style_paths = args.voice_style; + std::vector text_list = args.text; + + if (voice_style_paths.size() != text_list.size()) { + std::cerr << "Error: Number of voice styles (" << voice_style_paths.size() + << ") must match number of texts (" << text_list.size() << ")\n"; + return 1; + } + + int bsz = voice_style_paths.size(); + + // --- 2. Load Text to Speech --- // + Ort::Env env(ORT_LOGGING_LEVEL_WARNING, "TTS"); + Ort::MemoryInfo memory_info = Ort::MemoryInfo::CreateCpu( + OrtAllocatorType::OrtArenaAllocator, OrtMemType::OrtMemTypeDefault + ); + + auto text_to_speech = loadTextToSpeech(env, args.onnx_dir, false); + std::cout << std::endl; + + // --- 3. Load Voice Style --- // + auto style = loadVoiceStyle(voice_style_paths, true); + + // --- 4. Synthesize speech --- // + fs::create_directories(save_dir); + + for (int n = 0; n < n_test; n++) { + std::cout << "\n[" << (n + 1) << "/" << n_test << "] Starting synthesis...\n"; + + auto result = timer("Generating speech from text", [&]() { + return text_to_speech->call(memory_info, text_list, style, total_step); + }); + + int sample_rate = text_to_speech->getSampleRate(); + int wav_shape_1 = result.wav.size() / bsz; + + for (int b = 0; b < bsz; b++) { + std::string fname = sanitizeFilename(text_list[b], 20) + "_" + std::to_string(n + 1) + ".wav"; + int wav_len = static_cast(sample_rate * result.duration[b]); + + std::vector wav_out( + result.wav.begin() + b * wav_shape_1, + result.wav.begin() + b * wav_shape_1 + wav_len + ); + + std::string output_path = save_dir + "/" + fname; + writeWavFile(output_path, wav_out, sample_rate); + std::cout << "Saved: " << output_path << "\n"; + } + + clearTensorBuffers(); + } + + std::cout << "\n=== Synthesis completed successfully! ===\n"; + return 0; +} diff --git a/cpp/helper.cpp b/cpp/helper.cpp new file mode 100644 index 0000000..1d91a6a --- /dev/null +++ b/cpp/helper.cpp @@ -0,0 +1,714 @@ +#include "helper.h" +#include +#include +#include +#include +#include +#include +#include + +using json = nlohmann::json; + +// Global tensor buffers for memory management +static std::vector> g_tensor_buffers_float; +static std::vector> g_tensor_buffers_int64; + +void clearTensorBuffers() { + g_tensor_buffers_float.clear(); + g_tensor_buffers_int64.clear(); +} + +// ============================================================================ +// UnicodeProcessor implementation +// ============================================================================ + +UnicodeProcessor::UnicodeProcessor(const std::string& unicode_indexer_json_path) { + indexer_ = loadJsonInt64(unicode_indexer_json_path); +} + +std::string UnicodeProcessor::preprocessText(const std::string& text) { + // Simple NFKD normalization (C++ doesn't have built-in Unicode normalization) + // For now, just return the text as-is + // TODO: add proper Unicode normalization + return text; +} + +std::vector UnicodeProcessor::textToUnicodeValues(const std::string& text) { + std::vector unicode_values; + for (char c : text) { + unicode_values.push_back(static_cast(static_cast(c))); + } + return unicode_values; +} + +std::vector>> UnicodeProcessor::getTextMask( + const std::vector& text_ids_lengths +) { + return lengthToMask(text_ids_lengths); +} + +void UnicodeProcessor::call( + const std::vector& text_list, + std::vector>& text_ids, + std::vector>>& text_mask +) { + std::vector processed_texts; + for (const auto& text : text_list) { + processed_texts.push_back(preprocessText(text)); + } + + std::vector text_ids_lengths; + for (const auto& text : processed_texts) { + text_ids_lengths.push_back(static_cast(text.length())); + } + + int64_t max_len = *std::max_element(text_ids_lengths.begin(), text_ids_lengths.end()); + + text_ids.resize(text_list.size()); + for (size_t i = 0; i < processed_texts.size(); i++) { + text_ids[i].resize(max_len, 0); + auto unicode_vals = textToUnicodeValues(processed_texts[i]); + for (size_t j = 0; j < unicode_vals.size(); j++) { + if (unicode_vals[j] < indexer_.size()) { + text_ids[i][j] = indexer_[unicode_vals[j]]; + } + } + } + + text_mask = getTextMask(text_ids_lengths); +} + +// ============================================================================ +// Style implementation +// ============================================================================ + +Style::Style(const std::vector& ttl_data, const std::vector& ttl_shape, + const std::vector& dp_data, const std::vector& dp_shape) + : ttl_data_(ttl_data), ttl_shape_(ttl_shape), dp_data_(dp_data), dp_shape_(dp_shape) {} + +// ============================================================================ +// TextToSpeech implementation +// ============================================================================ + +TextToSpeech::TextToSpeech( + const Config& cfgs, + UnicodeProcessor* text_processor, + Ort::Session* dp_ort, + Ort::Session* text_enc_ort, + Ort::Session* vector_est_ort, + Ort::Session* vocoder_ort +) : cfgs_(cfgs), + text_processor_(text_processor), + dp_ort_(dp_ort), + text_enc_ort_(text_enc_ort), + vector_est_ort_(vector_est_ort), + vocoder_ort_(vocoder_ort) { + + sample_rate_ = cfgs.ae.sample_rate; + base_chunk_size_ = cfgs.ae.base_chunk_size; + chunk_compress_factor_ = cfgs.ttl.chunk_compress_factor; + ldim_ = cfgs.ttl.latent_dim; +} + +void TextToSpeech::sampleNoisyLatent( + const std::vector& duration, + std::vector>>& noisy_latent, + std::vector>>& latent_mask +) { + int bsz = duration.size(); + float wav_len_max = *std::max_element(duration.begin(), duration.end()) * sample_rate_; + + std::vector wav_lengths; + for (float d : duration) { + wav_lengths.push_back(static_cast(d * sample_rate_)); + } + + int chunk_size = base_chunk_size_ * chunk_compress_factor_; + int latent_len = static_cast((wav_len_max + chunk_size - 1) / chunk_size); + int latent_dim = ldim_ * chunk_compress_factor_; + + // Generate random noise with normal distribution + std::random_device rd; + std::mt19937 gen(rd()); + std::normal_distribution dist(0.0f, 1.0f); + + noisy_latent.resize(bsz); + for (int b = 0; b < bsz; b++) { + noisy_latent[b].resize(latent_dim); + for (int d = 0; d < latent_dim; d++) { + noisy_latent[b][d].resize(latent_len); + for (int t = 0; t < latent_len; t++) { + noisy_latent[b][d][t] = dist(gen); + } + } + } + + latent_mask = getLatentMask(wav_lengths, base_chunk_size_, chunk_compress_factor_); + + // Apply mask + for (int b = 0; b < bsz; b++) { + for (int d = 0; d < latent_dim; d++) { + for (size_t t = 0; t < noisy_latent[b][d].size(); t++) { + noisy_latent[b][d][t] *= latent_mask[b][0][t]; + } + } + } +} + +TextToSpeech::SynthesisResult TextToSpeech::call( + Ort::MemoryInfo& memory_info, + const std::vector& text_list, + const Style& style, + int total_step +) { + int bsz = text_list.size(); + + if (bsz != style.getTtlShape()[0]) { + throw std::runtime_error("Number of texts must match number of style vectors"); + } + + // Process text + std::vector> text_ids; + std::vector>> text_mask; + text_processor_->call(text_list, text_ids, text_mask); + + std::vector text_ids_shape = {bsz, static_cast(text_ids[0].size())}; + std::vector text_mask_shape = {bsz, 1, static_cast(text_mask[0][0].size())}; + + auto text_ids_tensor = intArrayToTensor(memory_info, text_ids, text_ids_shape); + auto text_mask_tensor = arrayToTensor(memory_info, text_mask, text_mask_shape); + + // Create style tensors + auto style_ttl_tensor = Ort::Value::CreateTensor( + memory_info, + const_cast(style.getTtlData().data()), + style.getTtlData().size(), + style.getTtlShape().data(), + style.getTtlShape().size() + ); + + auto style_dp_tensor = Ort::Value::CreateTensor( + memory_info, + const_cast(style.getDpData().data()), + style.getDpData().size(), + style.getDpShape().data(), + style.getDpShape().size() + ); + + // Run duration predictor + const char* dp_input_names[] = {"text_ids", "style_dp", "text_mask"}; + const char* dp_output_names[] = {"duration"}; + std::vector dp_inputs; + dp_inputs.push_back(std::move(text_ids_tensor)); + dp_inputs.push_back(std::move(style_dp_tensor)); + dp_inputs.push_back(std::move(text_mask_tensor)); + + auto dp_outputs = dp_ort_->Run( + Ort::RunOptions{nullptr}, + dp_input_names, dp_inputs.data(), dp_inputs.size(), + dp_output_names, 1 + ); + + auto* dur_data = dp_outputs[0].GetTensorMutableData(); + std::vector duration(dur_data, dur_data + bsz); + + // Create new tensors for text encoder (previous ones were moved) + text_ids_tensor = intArrayToTensor(memory_info, text_ids, text_ids_shape); + text_mask_tensor = arrayToTensor(memory_info, text_mask, text_mask_shape); + style_ttl_tensor = Ort::Value::CreateTensor( + memory_info, + const_cast(style.getTtlData().data()), + style.getTtlData().size(), + style.getTtlShape().data(), + style.getTtlShape().size() + ); + + // Run text encoder + const char* text_enc_input_names[] = {"text_ids", "style_ttl", "text_mask"}; + const char* text_enc_output_names[] = {"text_emb"}; + std::vector text_enc_inputs; + text_enc_inputs.push_back(std::move(text_ids_tensor)); + text_enc_inputs.push_back(std::move(style_ttl_tensor)); + text_enc_inputs.push_back(std::move(text_mask_tensor)); + + auto text_enc_outputs = text_enc_ort_->Run( + Ort::RunOptions{nullptr}, + text_enc_input_names, text_enc_inputs.data(), text_enc_inputs.size(), + text_enc_output_names, 1 + ); + + // Sample noisy latent + std::vector>> xt, latent_mask; + sampleNoisyLatent(duration, xt, latent_mask); + + std::vector latent_shape = { + bsz, + static_cast(xt[0].size()), + static_cast(xt[0][0].size()) + }; + std::vector latent_mask_shape = { + bsz, 1, + static_cast(latent_mask[0][0].size()) + }; + + // Prepare scalar tensors + std::vector total_step_vec(bsz, static_cast(total_step)); + auto total_step_tensor = Ort::Value::CreateTensor( + memory_info, + total_step_vec.data(), + total_step_vec.size(), + std::vector{bsz}.data(), + 1 + ); + + // Store text_emb data to reuse across iterations + auto text_emb_info = text_enc_outputs[0].GetTensorTypeAndShapeInfo(); + size_t text_emb_size = text_emb_info.GetElementCount(); + auto* text_emb_data = text_enc_outputs[0].GetTensorMutableData(); + std::vector text_emb_vec(text_emb_data, text_emb_data + text_emb_size); + auto text_emb_shape = text_emb_info.GetShape(); + + // Iterative denoising + for (int step = 0; step < total_step; step++) { + std::vector current_step_vec(bsz, static_cast(step)); + + text_mask_tensor = arrayToTensor(memory_info, text_mask, text_mask_shape); + auto latent_mask_tensor = arrayToTensor(memory_info, latent_mask, latent_mask_shape); + auto noisy_latent_tensor = arrayToTensor(memory_info, xt, latent_shape); + style_ttl_tensor = Ort::Value::CreateTensor( + memory_info, + const_cast(style.getTtlData().data()), + style.getTtlData().size(), + style.getTtlShape().data(), + style.getTtlShape().size() + ); + + auto text_emb_tensor = Ort::Value::CreateTensor( + memory_info, + text_emb_vec.data(), + text_emb_vec.size(), + text_emb_shape.data(), + text_emb_shape.size() + ); + + auto current_step_tensor = Ort::Value::CreateTensor( + memory_info, + current_step_vec.data(), + current_step_vec.size(), + std::vector{bsz}.data(), + 1 + ); + + const char* vector_est_input_names[] = { + "noisy_latent", "text_emb", "style_ttl", "text_mask", "latent_mask", "total_step", "current_step" + }; + const char* vector_est_output_names[] = {"denoised_latent"}; + + std::vector vector_est_inputs; + vector_est_inputs.push_back(std::move(noisy_latent_tensor)); + vector_est_inputs.push_back(std::move(text_emb_tensor)); + vector_est_inputs.push_back(std::move(style_ttl_tensor)); + vector_est_inputs.push_back(std::move(text_mask_tensor)); + vector_est_inputs.push_back(std::move(latent_mask_tensor)); + + // Create a new total_step tensor for each iteration + auto total_step_tensor_iter = Ort::Value::CreateTensor( + memory_info, + total_step_vec.data(), + total_step_vec.size(), + std::vector{bsz}.data(), + 1 + ); + vector_est_inputs.push_back(std::move(total_step_tensor_iter)); + vector_est_inputs.push_back(std::move(current_step_tensor)); + + auto vector_est_outputs = vector_est_ort_->Run( + Ort::RunOptions{nullptr}, + vector_est_input_names, vector_est_inputs.data(), vector_est_inputs.size(), + vector_est_output_names, 1 + ); + + // Update xt with denoised output + auto* denoised_data = vector_est_outputs[0].GetTensorMutableData(); + size_t idx = 0; + for (int b = 0; b < bsz; b++) { + for (size_t d = 0; d < xt[b].size(); d++) { + for (size_t t = 0; t < xt[b][d].size(); t++) { + xt[b][d][t] = denoised_data[idx++]; + } + } + } + } + + // Run vocoder + auto latent_tensor = arrayToTensor(memory_info, xt, latent_shape); + const char* vocoder_input_names[] = {"latent"}; + const char* vocoder_output_names[] = {"wav_tts"}; + std::vector vocoder_inputs; + vocoder_inputs.push_back(std::move(latent_tensor)); + + auto vocoder_outputs = vocoder_ort_->Run( + Ort::RunOptions{nullptr}, + vocoder_input_names, vocoder_inputs.data(), vocoder_inputs.size(), + vocoder_output_names, 1 + ); + + auto wav_info = vocoder_outputs[0].GetTensorTypeAndShapeInfo(); + size_t wav_size = wav_info.GetElementCount(); + auto* wav_data = vocoder_outputs[0].GetTensorMutableData(); + + SynthesisResult result; + result.wav.assign(wav_data, wav_data + wav_size); + result.duration = duration; + + return result; +} + +// ============================================================================ +// Utility functions +// ============================================================================ + +std::vector>> lengthToMask( + const std::vector& lengths, int max_len +) { + if (max_len == -1) { + max_len = *std::max_element(lengths.begin(), lengths.end()); + } + + std::vector>> mask; + for (auto len : lengths) { + std::vector> batch_mask(1); + batch_mask[0].resize(max_len); + for (int i = 0; i < max_len; i++) { + batch_mask[0][i] = (i < len) ? 1.0f : 0.0f; + } + mask.push_back(batch_mask); + } + return mask; +} + +std::vector>> getLatentMask( + const std::vector& wav_lengths, + int base_chunk_size, + int chunk_compress_factor +) { + int latent_size = base_chunk_size * chunk_compress_factor; + std::vector latent_lengths; + for (auto len : wav_lengths) { + latent_lengths.push_back((len + latent_size - 1) / latent_size); + } + return lengthToMask(latent_lengths); +} + +// ============================================================================ +// ONNX model loading +// ============================================================================ + +std::unique_ptr loadOnnx( + Ort::Env& env, + const std::string& onnx_path, + const Ort::SessionOptions& opts +) { + return std::make_unique(env, onnx_path.c_str(), opts); +} + +OnnxModels loadOnnxAll( + Ort::Env& env, + const std::string& onnx_dir, + const Ort::SessionOptions& opts +) { + OnnxModels models; + models.dp = loadOnnx(env, onnx_dir + "/duration_predictor.onnx", opts); + models.text_enc = loadOnnx(env, onnx_dir + "/text_encoder.onnx", opts); + models.vector_est = loadOnnx(env, onnx_dir + "/vector_estimator.onnx", opts); + models.vocoder = loadOnnx(env, onnx_dir + "/vocoder.onnx", opts); + return models; +} + +// ============================================================================ +// Configuration and processor loading +// ============================================================================ + +Config loadCfgs(const std::string& onnx_dir) { + std::string cfg_path = onnx_dir + "/tts.json"; + std::ifstream file(cfg_path); + if (!file.is_open()) { + throw std::runtime_error("Failed to open config file: " + cfg_path); + } + + json j; + file >> j; + + Config cfg; + cfg.ae.sample_rate = j["ae"]["sample_rate"]; + cfg.ae.base_chunk_size = j["ae"]["base_chunk_size"]; + cfg.ttl.chunk_compress_factor = j["ttl"]["chunk_compress_factor"]; + cfg.ttl.latent_dim = j["ttl"]["latent_dim"]; + + return cfg; +} + +std::unique_ptr loadTextProcessor(const std::string& onnx_dir) { + std::string unicode_indexer_path = onnx_dir + "/unicode_indexer.json"; + return std::make_unique(unicode_indexer_path); +} + +// ============================================================================ +// Voice style loading +// ============================================================================ + +Style loadVoiceStyle(const std::vector& voice_style_paths, bool verbose) { + int bsz = voice_style_paths.size(); + + // Read first file to get dimensions + std::ifstream first_file(voice_style_paths[0]); + if (!first_file.is_open()) { + throw std::runtime_error("Failed to open voice style file: " + voice_style_paths[0]); + } + json first_json; + first_file >> first_json; + + auto ttl_dims = first_json["style_ttl"]["dims"].get>(); + auto dp_dims = first_json["style_dp"]["dims"].get>(); + + int64_t ttl_dim1 = ttl_dims[1]; + int64_t ttl_dim2 = ttl_dims[2]; + int64_t dp_dim1 = dp_dims[1]; + int64_t dp_dim2 = dp_dims[2]; + + // Pre-allocate arrays with full batch size + size_t ttl_size = bsz * ttl_dim1 * ttl_dim2; + size_t dp_size = bsz * dp_dim1 * dp_dim2; + std::vector ttl_flat(ttl_size); + std::vector dp_flat(dp_size); + + // Fill in the data + for (int i = 0; i < bsz; i++) { + std::ifstream file(voice_style_paths[i]); + if (!file.is_open()) { + throw std::runtime_error("Failed to open voice style file: " + voice_style_paths[i]); + } + + json j; + file >> j; + + // Flatten data + auto ttl_data_nested = j["style_ttl"]["data"].get>>>(); + std::vector ttl_data; + for (const auto& batch : ttl_data_nested) { + for (const auto& row : batch) { + ttl_data.insert(ttl_data.end(), row.begin(), row.end()); + } + } + + auto dp_data_nested = j["style_dp"]["data"].get>>>(); + std::vector dp_data; + for (const auto& batch : dp_data_nested) { + for (const auto& row : batch) { + dp_data.insert(dp_data.end(), row.begin(), row.end()); + } + } + + // Copy to pre-allocated array + size_t ttl_offset = i * ttl_dim1 * ttl_dim2; + std::copy(ttl_data.begin(), ttl_data.end(), ttl_flat.begin() + ttl_offset); + + size_t dp_offset = i * dp_dim1 * dp_dim2; + std::copy(dp_data.begin(), dp_data.end(), dp_flat.begin() + dp_offset); + } + + std::vector ttl_shape = {bsz, ttl_dim1, ttl_dim2}; + std::vector dp_shape = {bsz, dp_dim1, dp_dim2}; + + if (verbose) { + std::cout << "Loaded " << bsz << " voice styles" << std::endl; + } + + return Style(ttl_flat, ttl_shape, dp_flat, dp_shape); +} + +// ============================================================================ +// TextToSpeech loading +// ============================================================================ + +std::unique_ptr loadTextToSpeech( + Ort::Env& env, + const std::string& onnx_dir, + bool use_gpu +) { + Ort::SessionOptions opts; + if (use_gpu) { + throw std::runtime_error("GPU mode is not supported yet"); + } else { + std::cout << "Using CPU for inference" << std::endl; + } + + auto cfgs = loadCfgs(onnx_dir); + auto models = loadOnnxAll(env, onnx_dir, opts); + auto text_processor = loadTextProcessor(onnx_dir); + + // Transfer ownership to TextToSpeech (use raw pointers internally) + auto tts = std::make_unique( + cfgs, + text_processor.get(), + models.dp.get(), + models.text_enc.get(), + models.vector_est.get(), + models.vocoder.get() + ); + + // Keep the models and processor alive by storing them + // (In production, you'd want better lifetime management) + static OnnxModels static_models; + static std::unique_ptr static_text_processor; + static_models = std::move(models); + static_text_processor = std::move(text_processor); + + return tts; +} + +// ============================================================================ +// WAV file writing +// ============================================================================ + +void writeWavFile( + const std::string& filename, + const std::vector& audio_data, + int sample_rate +) { + std::ofstream file(filename, std::ios::binary); + if (!file.is_open()) { + throw std::runtime_error("Failed to open file for writing: " + filename); + } + + int num_channels = 1; + int bits_per_sample = 16; + int byte_rate = sample_rate * num_channels * bits_per_sample / 8; + int block_align = num_channels * bits_per_sample / 8; + int data_size = audio_data.size() * bits_per_sample / 8; + + // RIFF header + file.write("RIFF", 4); + int32_t chunk_size = 36 + data_size; + file.write(reinterpret_cast(&chunk_size), 4); + file.write("WAVE", 4); + + // fmt chunk + file.write("fmt ", 4); + int32_t fmt_chunk_size = 16; + file.write(reinterpret_cast(&fmt_chunk_size), 4); + int16_t audio_format = 1; // PCM + file.write(reinterpret_cast(&audio_format), 2); + int16_t num_channels_16 = num_channels; + file.write(reinterpret_cast(&num_channels_16), 2); + file.write(reinterpret_cast(&sample_rate), 4); + file.write(reinterpret_cast(&byte_rate), 4); + int16_t block_align_16 = block_align; + file.write(reinterpret_cast(&block_align_16), 2); + int16_t bits_per_sample_16 = bits_per_sample; + file.write(reinterpret_cast(&bits_per_sample_16), 2); + + // data chunk + file.write("data", 4); + file.write(reinterpret_cast(&data_size), 4); + + // Write audio data + for (float sample : audio_data) { + float clamped = std::max(-1.0f, std::min(1.0f, sample)); + int16_t int_sample = static_cast(clamped * 32767); + file.write(reinterpret_cast(&int_sample), 2); + } +} + +// ============================================================================ +// Tensor conversion utilities +// ============================================================================ + +Ort::Value arrayToTensor( + Ort::MemoryInfo& memory_info, + const std::vector>>& array, + const std::vector& dims +) { + // Flatten the array + std::vector flat; + for (const auto& batch : array) { + for (const auto& row : batch) { + for (float val : row) { + flat.push_back(val); + } + } + } + + // Store in global buffer to keep data alive + g_tensor_buffers_float.push_back(std::move(flat)); + auto& buffer = g_tensor_buffers_float.back(); + + return Ort::Value::CreateTensor( + memory_info, + buffer.data(), + buffer.size(), + dims.data(), + dims.size() + ); +} + +Ort::Value intArrayToTensor( + Ort::MemoryInfo& memory_info, + const std::vector>& array, + const std::vector& dims +) { + // Flatten the array + std::vector flat; + for (const auto& row : array) { + for (int64_t val : row) { + flat.push_back(val); + } + } + + // Store in global buffer to keep data alive + g_tensor_buffers_int64.push_back(std::move(flat)); + auto& buffer = g_tensor_buffers_int64.back(); + + return Ort::Value::CreateTensor( + memory_info, + buffer.data(), + buffer.size(), + dims.data(), + dims.size() + ); +} + +// ============================================================================ +// JSON loading helpers +// ============================================================================ + +std::vector loadJsonInt64(const std::string& file_path) { + std::ifstream file(file_path); + if (!file.is_open()) { + throw std::runtime_error("Failed to open file: " + file_path); + } + + json j; + file >> j; + + return j.get>(); +} + +// ============================================================================ +// Sanitize filename +// ============================================================================ + +std::string sanitizeFilename(const std::string& text, int max_len) { + std::string result; + int count = 0; + for (char c : text) { + if (count >= max_len) break; + if (std::isalnum(static_cast(c))) { + result += c; + } else { + result += '_'; + } + count++; + } + return result; +} diff --git a/cpp/helper.h b/cpp/helper.h new file mode 100644 index 0000000..4e3742b --- /dev/null +++ b/cpp/helper.h @@ -0,0 +1,202 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +/** + * Configuration structure + */ +struct Config { + struct AEConfig { + int sample_rate; + int base_chunk_size; + } ae; + + struct TTLConfig { + int chunk_compress_factor; + int latent_dim; + } ttl; +}; + +/** + * Unicode text processor + */ +class UnicodeProcessor { +public: + explicit UnicodeProcessor(const std::string& unicode_indexer_json_path); + + // Process text list to text IDs and mask + void call( + const std::vector& text_list, + std::vector>& text_ids, + std::vector>>& text_mask + ); + +private: + std::vector indexer_; + + std::string preprocessText(const std::string& text); + std::vector textToUnicodeValues(const std::string& text); + std::vector>> getTextMask( + const std::vector& text_ids_lengths + ); +}; + +/** + * Style class + */ +class Style { +public: + Style(const std::vector& ttl_data, const std::vector& ttl_shape, + const std::vector& dp_data, const std::vector& dp_shape); + + const std::vector& getTtlData() const { return ttl_data_; } + const std::vector& getDpData() const { return dp_data_; } + const std::vector& getTtlShape() const { return ttl_shape_; } + const std::vector& getDpShape() const { return dp_shape_; } + +private: + std::vector ttl_data_; + std::vector dp_data_; + std::vector ttl_shape_; + std::vector dp_shape_; +}; + +/** + * TextToSpeech class + */ +class TextToSpeech { +public: + TextToSpeech( + const Config& cfgs, + UnicodeProcessor* text_processor, + Ort::Session* dp_ort, + Ort::Session* text_enc_ort, + Ort::Session* vector_est_ort, + Ort::Session* vocoder_ort + ); + + struct SynthesisResult { + std::vector wav; + std::vector duration; + }; + + SynthesisResult call( + Ort::MemoryInfo& memory_info, + const std::vector& text_list, + const Style& style, + int total_step + ); + + int getSampleRate() const { return sample_rate_; } + +private: + Config cfgs_; + UnicodeProcessor* text_processor_; + Ort::Session* dp_ort_; + Ort::Session* text_enc_ort_; + Ort::Session* vector_est_ort_; + Ort::Session* vocoder_ort_; + int sample_rate_; + int base_chunk_size_; + int chunk_compress_factor_; + int ldim_; + + void sampleNoisyLatent( + const std::vector& duration, + std::vector>>& noisy_latent, + std::vector>>& latent_mask + ); +}; + +// Utility functions +std::vector>> lengthToMask( + const std::vector& lengths, int max_len = -1 +); + +std::vector>> getLatentMask( + const std::vector& wav_lengths, + int base_chunk_size, + int chunk_compress_factor +); + +// ONNX model loading +struct OnnxModels { + std::unique_ptr dp; + std::unique_ptr text_enc; + std::unique_ptr vector_est; + std::unique_ptr vocoder; +}; + +std::unique_ptr loadOnnx( + Ort::Env& env, + const std::string& onnx_path, + const Ort::SessionOptions& opts +); + +OnnxModels loadOnnxAll( + Ort::Env& env, + const std::string& onnx_dir, + const Ort::SessionOptions& opts +); + +// Configuration and processor loading +Config loadCfgs(const std::string& onnx_dir); + +std::unique_ptr loadTextProcessor(const std::string& onnx_dir); + +// Voice style loading +Style loadVoiceStyle(const std::vector& voice_style_paths, bool verbose = false); + +// TextToSpeech loading +std::unique_ptr loadTextToSpeech( + Ort::Env& env, + const std::string& onnx_dir, + bool use_gpu = false +); + +// WAV file writing +void writeWavFile( + const std::string& filename, + const std::vector& audio_data, + int sample_rate +); + +// Tensor conversion utilities +void clearTensorBuffers(); + +Ort::Value arrayToTensor( + Ort::MemoryInfo& memory_info, + const std::vector>>& array, + const std::vector& dims +); + +Ort::Value intArrayToTensor( + Ort::MemoryInfo& memory_info, + const std::vector>& array, + const std::vector& dims +); + +// JSON loading helpers +std::vector loadJsonInt64(const std::string& file_path); + +// Timer utility +template +auto timer(const std::string& name, Func&& func) -> decltype(func()) { + auto start = std::chrono::high_resolution_clock::now(); + std::cout << name << "..." << std::endl; + auto result = func(); + auto end = std::chrono::high_resolution_clock::now(); + std::chrono::duration elapsed = end - start; + std::cout << " -> " << name << " completed in " + << std::fixed << std::setprecision(2) << elapsed.count() << " sec" << std::endl; + return result; +} + +// Sanitize filename +std::string sanitizeFilename(const std::string& text, int max_len); diff --git a/csharp/.gitignore b/csharp/.gitignore new file mode 100644 index 0000000..2c7f8fe --- /dev/null +++ b/csharp/.gitignore @@ -0,0 +1,41 @@ +# Build results +bin/ +obj/ +[Dd]ebug/ +[Rr]elease/ +x64/ +x86/ +[Aa]rm/ +[Aa]rm64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio files +.vs/ +*.suo +*.user +*.userosscache +*.sln.docstates +*.userprefs + +# Rider +.idea/ +*.sln.iml + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# Output directory +results/*.wav + +# OS files +.DS_Store +Thumbs.db + + diff --git a/csharp/ExampleONNX.cs b/csharp/ExampleONNX.cs new file mode 100644 index 0000000..d4e2078 --- /dev/null +++ b/csharp/ExampleONNX.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Supertonic +{ + class Program + { + class Args + { + public bool UseGpu { get; set; } = false; + public string OnnxDir { get; set; } = "assets/onnx"; + public int TotalStep { get; set; } = 5; + public int NTest { get; set; } = 4; + public List VoiceStyle { get; set; } = new List { "assets/voice_styles/M1.json" }; + public List Text { get; set; } = new List + { + "This morning, I took a walk in the park, and the sound of the birds and the breeze was so pleasant that I stopped for a long time just to listen." + }; + public string SaveDir { get; set; } = "results"; + } + + static Args ParseArgs(string[] args) + { + var result = new Args(); + + for (int i = 0; i < args.Length; i++) + { + switch (args[i]) + { + case "--use-gpu": + result.UseGpu = true; + break; + case "--onnx-dir" when i + 1 < args.Length: + result.OnnxDir = args[++i]; + break; + case "--total-step" when i + 1 < args.Length: + result.TotalStep = int.Parse(args[++i]); + break; + case "--n-test" when i + 1 < args.Length: + result.NTest = int.Parse(args[++i]); + break; + case "--voice-style" when i + 1 < args.Length: + result.VoiceStyle = args[++i].Split(',').ToList(); + break; + case "--text" when i + 1 < args.Length: + result.Text = args[++i].Split('|').ToList(); + break; + case "--save-dir" when i + 1 < args.Length: + result.SaveDir = args[++i]; + break; + } + } + + return result; + } + + static void Main(string[] args) + { + Console.WriteLine("=== TTS Inference with ONNX Runtime (C#) ===\n"); + + // --- 1. Parse arguments --- // + var parsedArgs = ParseArgs(args); + int totalStep = parsedArgs.TotalStep; + int nTest = parsedArgs.NTest; + string saveDir = parsedArgs.SaveDir; + var voiceStylePaths = parsedArgs.VoiceStyle; + var textList = parsedArgs.Text; + + if (voiceStylePaths.Count != textList.Count) + { + throw new ArgumentException( + $"Number of voice styles ({voiceStylePaths.Count}) must match number of texts ({textList.Count})"); + } + + int bsz = voiceStylePaths.Count; + + // --- 2. Load Text to Speech --- // + var textToSpeech = Helper.LoadTextToSpeech(parsedArgs.OnnxDir, parsedArgs.UseGpu); + Console.WriteLine(); + + // --- 3. Load Voice Style --- // + var style = Helper.LoadVoiceStyle(voiceStylePaths, verbose: true); + + // --- 4. Synthesize speech --- // + for (int n = 0; n < nTest; n++) + { + Console.WriteLine($"\n[{n + 1}/{nTest}] Starting synthesis..."); + + var (wav, duration) = Helper.Timer("Generating speech from text", () => + textToSpeech.Call(textList, style, totalStep) + ); + + if (!Directory.Exists(saveDir)) + { + Directory.CreateDirectory(saveDir); + } + + for (int b = 0; b < bsz; b++) + { + string fname = $"{Helper.SanitizeFilename(textList[b], 20)}_{n + 1}.wav"; + + int wavLen = (int)(textToSpeech.SampleRate * duration[b]); + var wavOut = new float[wavLen]; + Array.Copy(wav, b * wav.Length / bsz, wavOut, 0, Math.Min(wavLen, wav.Length / bsz)); + + string outputPath = Path.Combine(saveDir, fname); + Helper.WriteWavFile(outputPath, wavOut, textToSpeech.SampleRate); + Console.WriteLine($"Saved: {outputPath}"); + } + } + + Console.WriteLine("\n=== Synthesis completed successfully! ==="); + } + } +} + diff --git a/csharp/Helper.cs b/csharp/Helper.cs new file mode 100644 index 0000000..be5901b --- /dev/null +++ b/csharp/Helper.cs @@ -0,0 +1,612 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.Json; +using Microsoft.ML.OnnxRuntime; +using Microsoft.ML.OnnxRuntime.Tensors; + +namespace Supertonic +{ + // ============================================================================ + // Configuration classes + // ============================================================================ + + public class Config + { + public AEConfig AE { get; set; } = null!; + public TTLConfig TTL { get; set; } = null!; + + public class AEConfig + { + public int SampleRate { get; set; } + public int BaseChunkSize { get; set; } + } + + public class TTLConfig + { + public int ChunkCompressFactor { get; set; } + public int LatentDim { get; set; } + } + } + + // ============================================================================ + // Style class + // ============================================================================ + + public class Style + { + public float[] Ttl { get; set; } + public long[] TtlShape { get; set; } + public float[] Dp { get; set; } + public long[] DpShape { get; set; } + + public Style(float[] ttl, long[] ttlShape, float[] dp, long[] dpShape) + { + Ttl = ttl; + TtlShape = ttlShape; + Dp = dp; + DpShape = dpShape; + } + } + + // ============================================================================ + // Unicode text processor + // ============================================================================ + + public class UnicodeProcessor + { + private readonly Dictionary _indexer; + + public UnicodeProcessor(string unicodeIndexerPath) + { + var json = File.ReadAllText(unicodeIndexerPath); + var indexerArray = JsonSerializer.Deserialize(json) ?? throw new Exception("Failed to load indexer"); + _indexer = new Dictionary(); + for (int i = 0; i < indexerArray.Length; i++) + { + _indexer[i] = indexerArray[i]; + } + } + + private string PreprocessText(string text) + { + // Simple normalization (C# has Normalize built-in) + return text.Normalize(NormalizationForm.FormKD); + } + + private int[] TextToUnicodeValues(string text) + { + return text.Select(c => (int)c).ToArray(); + } + + private float[][][] GetTextMask(long[] textIdsLengths) + { + return Helper.LengthToMask(textIdsLengths); + } + + public (long[][] textIds, float[][][] textMask) Call(List textList) + { + var processedTexts = textList.Select(t => PreprocessText(t)).ToList(); + var textIdsLengths = processedTexts.Select(t => (long)t.Length).ToArray(); + long maxLen = textIdsLengths.Max(); + + var textIds = new long[textList.Count][]; + for (int i = 0; i < processedTexts.Count; i++) + { + textIds[i] = new long[maxLen]; + var unicodeVals = TextToUnicodeValues(processedTexts[i]); + for (int j = 0; j < unicodeVals.Length; j++) + { + if (_indexer.TryGetValue(unicodeVals[j], out long val)) + { + textIds[i][j] = val; + } + } + } + + var textMask = GetTextMask(textIdsLengths); + return (textIds, textMask); + } + } + + // ============================================================================ + // TextToSpeech class + // ============================================================================ + + public class TextToSpeech + { + private readonly Config _cfgs; + private readonly UnicodeProcessor _textProcessor; + private readonly InferenceSession _dpOrt; + private readonly InferenceSession _textEncOrt; + private readonly InferenceSession _vectorEstOrt; + private readonly InferenceSession _vocoderOrt; + public readonly int SampleRate; + private readonly int _baseChunkSize; + private readonly int _chunkCompressFactor; + private readonly int _ldim; + + public TextToSpeech( + Config cfgs, + UnicodeProcessor textProcessor, + InferenceSession dpOrt, + InferenceSession textEncOrt, + InferenceSession vectorEstOrt, + InferenceSession vocoderOrt) + { + _cfgs = cfgs; + _textProcessor = textProcessor; + _dpOrt = dpOrt; + _textEncOrt = textEncOrt; + _vectorEstOrt = vectorEstOrt; + _vocoderOrt = vocoderOrt; + SampleRate = cfgs.AE.SampleRate; + _baseChunkSize = cfgs.AE.BaseChunkSize; + _chunkCompressFactor = cfgs.TTL.ChunkCompressFactor; + _ldim = cfgs.TTL.LatentDim; + } + + private (float[][][] noisyLatent, float[][][] latentMask) SampleNoisyLatent(float[] duration) + { + int bsz = duration.Length; + float wavLenMax = duration.Max() * SampleRate; + var wavLengths = duration.Select(d => (long)(d * SampleRate)).ToArray(); + int chunkSize = _baseChunkSize * _chunkCompressFactor; + int latentLen = (int)((wavLenMax + chunkSize - 1) / chunkSize); + int latentDim = _ldim * _chunkCompressFactor; + + // Generate random noise + var random = new Random(); + var noisyLatent = new float[bsz][][]; + for (int b = 0; b < bsz; b++) + { + noisyLatent[b] = new float[latentDim][]; + for (int d = 0; d < latentDim; d++) + { + noisyLatent[b][d] = new float[latentLen]; + for (int t = 0; t < latentLen; t++) + { + // Box-Muller transform for normal distribution + double u1 = 1.0 - random.NextDouble(); + double u2 = 1.0 - random.NextDouble(); + noisyLatent[b][d][t] = (float)(Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Cos(2.0 * Math.PI * u2)); + } + } + } + + var latentMask = Helper.GetLatentMask(wavLengths, _baseChunkSize, _chunkCompressFactor); + + // Apply mask + for (int b = 0; b < bsz; b++) + { + for (int d = 0; d < latentDim; d++) + { + for (int t = 0; t < latentLen; t++) + { + noisyLatent[b][d][t] *= latentMask[b][0][t]; + } + } + } + + return (noisyLatent, latentMask); + } + + public (float[] wav, float[] duration) Call(List textList, Style style, int totalStep) + { + int bsz = textList.Count; + if (bsz != style.TtlShape[0]) + { + throw new ArgumentException("Number of texts must match number of style vectors"); + } + + // Process text + var (textIds, textMask) = _textProcessor.Call(textList); + var textIdsShape = new long[] { bsz, textIds[0].Length }; + var textMaskShape = new long[] { bsz, 1, textMask[0][0].Length }; + + var textIdsTensor = Helper.IntArrayToTensor(textIds, textIdsShape); + var textMaskTensor = Helper.ArrayToTensor(textMask, textMaskShape); + + var styleTtlTensor = new DenseTensor(style.Ttl, style.TtlShape.Select(x => (int)x).ToArray()); + var styleDpTensor = new DenseTensor(style.Dp, style.DpShape.Select(x => (int)x).ToArray()); + + // Run duration predictor + var dpInputs = new List + { + NamedOnnxValue.CreateFromTensor("text_ids", textIdsTensor), + NamedOnnxValue.CreateFromTensor("style_dp", styleDpTensor), + NamedOnnxValue.CreateFromTensor("text_mask", textMaskTensor) + }; + using var dpOutputs = _dpOrt.Run(dpInputs); + var durOnnx = dpOutputs.First(o => o.Name == "duration").AsTensor().ToArray(); + + // Run text encoder + var textEncInputs = new List + { + NamedOnnxValue.CreateFromTensor("text_ids", textIdsTensor), + NamedOnnxValue.CreateFromTensor("style_ttl", styleTtlTensor), + NamedOnnxValue.CreateFromTensor("text_mask", textMaskTensor) + }; + using var textEncOutputs = _textEncOrt.Run(textEncInputs); + var textEmbTensor = textEncOutputs.First(o => o.Name == "text_emb").AsTensor(); + + // Sample noisy latent + var (xt, latentMask) = SampleNoisyLatent(durOnnx); + var latentShape = new long[] { bsz, xt[0].Length, xt[0][0].Length }; + var latentMaskShape = new long[] { bsz, 1, latentMask[0][0].Length }; + + var totalStepArray = Enumerable.Repeat((float)totalStep, bsz).ToArray(); + + // Iterative denoising + for (int step = 0; step < totalStep; step++) + { + var currentStepArray = Enumerable.Repeat((float)step, bsz).ToArray(); + + var vectorEstInputs = new List + { + NamedOnnxValue.CreateFromTensor("noisy_latent", Helper.ArrayToTensor(xt, latentShape)), + NamedOnnxValue.CreateFromTensor("text_emb", textEmbTensor), + NamedOnnxValue.CreateFromTensor("style_ttl", styleTtlTensor), + NamedOnnxValue.CreateFromTensor("text_mask", textMaskTensor), + NamedOnnxValue.CreateFromTensor("latent_mask", Helper.ArrayToTensor(latentMask, latentMaskShape)), + NamedOnnxValue.CreateFromTensor("total_step", new DenseTensor(totalStepArray, new int[] { bsz })), + NamedOnnxValue.CreateFromTensor("current_step", new DenseTensor(currentStepArray, new int[] { bsz })) + }; + + using var vectorEstOutputs = _vectorEstOrt.Run(vectorEstInputs); + var denoisedLatent = vectorEstOutputs.First(o => o.Name == "denoised_latent").AsTensor(); + + // Update xt + int idx = 0; + for (int b = 0; b < bsz; b++) + { + for (int d = 0; d < xt[b].Length; d++) + { + for (int t = 0; t < xt[b][d].Length; t++) + { + xt[b][d][t] = denoisedLatent.GetValue(idx++); + } + } + } + } + + // Run vocoder + var vocoderInputs = new List + { + NamedOnnxValue.CreateFromTensor("latent", Helper.ArrayToTensor(xt, latentShape)) + }; + using var vocoderOutputs = _vocoderOrt.Run(vocoderInputs); + var wavTensor = vocoderOutputs.First(o => o.Name == "wav_tts").AsTensor(); + + return (wavTensor.ToArray(), durOnnx); + } + } + + // ============================================================================ + // Helper class with utility functions + // ============================================================================ + + public static class Helper + { + // ============================================================================ + // Utility functions + // ============================================================================ + + public static float[][][] LengthToMask(long[] lengths, long maxLen = -1) + { + if (maxLen == -1) + { + maxLen = lengths.Max(); + } + + var mask = new float[lengths.Length][][]; + for (int i = 0; i < lengths.Length; i++) + { + mask[i] = new float[1][]; + mask[i][0] = new float[maxLen]; + for (int j = 0; j < maxLen; j++) + { + mask[i][0][j] = j < lengths[i] ? 1.0f : 0.0f; + } + } + return mask; + } + + public static float[][][] GetLatentMask(long[] wavLengths, int baseChunkSize, int chunkCompressFactor) + { + int latentSize = baseChunkSize * chunkCompressFactor; + var latentLengths = wavLengths.Select(len => (len + latentSize - 1) / latentSize).ToArray(); + return LengthToMask(latentLengths); + } + + // ============================================================================ + // ONNX model loading + // ============================================================================ + + public static InferenceSession LoadOnnx(string onnxPath, SessionOptions opts) + { + return new InferenceSession(onnxPath, opts); + } + + public static (InferenceSession dp, InferenceSession textEnc, InferenceSession vectorEst, InferenceSession vocoder) + LoadOnnxAll(string onnxDir, SessionOptions opts) + { + var dpPath = Path.Combine(onnxDir, "duration_predictor.onnx"); + var textEncPath = Path.Combine(onnxDir, "text_encoder.onnx"); + var vectorEstPath = Path.Combine(onnxDir, "vector_estimator.onnx"); + var vocoderPath = Path.Combine(onnxDir, "vocoder.onnx"); + + return ( + LoadOnnx(dpPath, opts), + LoadOnnx(textEncPath, opts), + LoadOnnx(vectorEstPath, opts), + LoadOnnx(vocoderPath, opts) + ); + } + + // ============================================================================ + // Configuration loading + // ============================================================================ + + public static Config LoadCfgs(string onnxDir) + { + var cfgPath = Path.Combine(onnxDir, "tts.json"); + var json = File.ReadAllText(cfgPath); + + using var doc = JsonDocument.Parse(json); + var root = doc.RootElement; + + return new Config + { + AE = new Config.AEConfig + { + SampleRate = root.GetProperty("ae").GetProperty("sample_rate").GetInt32(), + BaseChunkSize = root.GetProperty("ae").GetProperty("base_chunk_size").GetInt32() + }, + TTL = new Config.TTLConfig + { + ChunkCompressFactor = root.GetProperty("ttl").GetProperty("chunk_compress_factor").GetInt32(), + LatentDim = root.GetProperty("ttl").GetProperty("latent_dim").GetInt32() + } + }; + } + + public static UnicodeProcessor LoadTextProcessor(string onnxDir) + { + var unicodeIndexerPath = Path.Combine(onnxDir, "unicode_indexer.json"); + return new UnicodeProcessor(unicodeIndexerPath); + } + + // ============================================================================ + // Voice style loading + // ============================================================================ + + public static Style LoadVoiceStyle(List voiceStylePaths, bool verbose = false) + { + int bsz = voiceStylePaths.Count; + + // Read first file to get dimensions + var firstJson = File.ReadAllText(voiceStylePaths[0]); + using var firstDoc = JsonDocument.Parse(firstJson); + var firstRoot = firstDoc.RootElement; + + var ttlDims = ParseInt64Array(firstRoot.GetProperty("style_ttl").GetProperty("dims")); + var dpDims = ParseInt64Array(firstRoot.GetProperty("style_dp").GetProperty("dims")); + + long ttlDim1 = ttlDims[1]; + long ttlDim2 = ttlDims[2]; + long dpDim1 = dpDims[1]; + long dpDim2 = dpDims[2]; + + // Pre-allocate arrays with full batch size + int ttlSize = (int)(bsz * ttlDim1 * ttlDim2); + int dpSize = (int)(bsz * dpDim1 * dpDim2); + var ttlFlat = new float[ttlSize]; + var dpFlat = new float[dpSize]; + + // Fill in the data + for (int i = 0; i < bsz; i++) + { + var json = File.ReadAllText(voiceStylePaths[i]); + using var doc = JsonDocument.Parse(json); + var root = doc.RootElement; + + // Flatten data + var ttlData3D = ParseFloat3DArray(root.GetProperty("style_ttl").GetProperty("data")); + var ttlDataFlat = new List(); + foreach (var batch in ttlData3D) + { + foreach (var row in batch) + { + ttlDataFlat.AddRange(row); + } + } + + var dpData3D = ParseFloat3DArray(root.GetProperty("style_dp").GetProperty("data")); + var dpDataFlat = new List(); + foreach (var batch in dpData3D) + { + foreach (var row in batch) + { + dpDataFlat.AddRange(row); + } + } + + // Copy to pre-allocated array + int ttlOffset = (int)(i * ttlDim1 * ttlDim2); + ttlDataFlat.CopyTo(ttlFlat, ttlOffset); + + int dpOffset = (int)(i * dpDim1 * dpDim2); + dpDataFlat.CopyTo(dpFlat, dpOffset); + } + + var ttlShape = new long[] { bsz, ttlDim1, ttlDim2 }; + var dpShape = new long[] { bsz, dpDim1, dpDim2 }; + + if (verbose) + { + Console.WriteLine($"Loaded {bsz} voice styles"); + } + + return new Style(ttlFlat, ttlShape, dpFlat, dpShape); + } + + private static float[][][] ParseFloat3DArray(JsonElement element) + { + var result = new List(); + foreach (var batch in element.EnumerateArray()) + { + var batch2D = new List(); + foreach (var row in batch.EnumerateArray()) + { + var rowData = new List(); + foreach (var val in row.EnumerateArray()) + { + rowData.Add(val.GetSingle()); + } + batch2D.Add(rowData.ToArray()); + } + result.Add(batch2D.ToArray()); + } + return result.ToArray(); + } + + private static long[] ParseInt64Array(JsonElement element) + { + var result = new List(); + foreach (var val in element.EnumerateArray()) + { + result.Add(val.GetInt64()); + } + return result.ToArray(); + } + + // ============================================================================ + // TextToSpeech loading + // ============================================================================ + + public static TextToSpeech LoadTextToSpeech(string onnxDir, bool useGpu = false) + { + var opts = new SessionOptions(); + if (useGpu) + { + throw new NotImplementedException("GPU mode is not supported yet"); + } + else + { + Console.WriteLine("Using CPU for inference"); + } + + var cfgs = LoadCfgs(onnxDir); + var (dpOrt, textEncOrt, vectorEstOrt, vocoderOrt) = LoadOnnxAll(onnxDir, opts); + var textProcessor = LoadTextProcessor(onnxDir); + + return new TextToSpeech(cfgs, textProcessor, dpOrt, textEncOrt, vectorEstOrt, vocoderOrt); + } + + // ============================================================================ + // WAV file writing + // ============================================================================ + + public static void WriteWavFile(string filename, float[] audioData, int sampleRate) + { + using var writer = new BinaryWriter(File.Open(filename, FileMode.Create)); + + int numChannels = 1; + int bitsPerSample = 16; + int byteRate = sampleRate * numChannels * bitsPerSample / 8; + short blockAlign = (short)(numChannels * bitsPerSample / 8); + int dataSize = audioData.Length * bitsPerSample / 8; + + // RIFF header + writer.Write(Encoding.ASCII.GetBytes("RIFF")); + writer.Write(36 + dataSize); + writer.Write(Encoding.ASCII.GetBytes("WAVE")); + + // fmt chunk + writer.Write(Encoding.ASCII.GetBytes("fmt ")); + writer.Write(16); // fmt chunk size + writer.Write((short)1); // audio format (PCM) + writer.Write((short)numChannels); + writer.Write(sampleRate); + writer.Write(byteRate); + writer.Write(blockAlign); + writer.Write((short)bitsPerSample); + + // data chunk + writer.Write(Encoding.ASCII.GetBytes("data")); + writer.Write(dataSize); + + // Write audio data + foreach (var sample in audioData) + { + float clamped = Math.Max(-1.0f, Math.Min(1.0f, sample)); + short intSample = (short)(clamped * 32767); + writer.Write(intSample); + } + } + + // ============================================================================ + // Tensor conversion utilities + // ============================================================================ + + public static DenseTensor ArrayToTensor(float[][][] array, long[] dims) + { + var flat = new List(); + foreach (var batch in array) + { + foreach (var row in batch) + { + flat.AddRange(row); + } + } + return new DenseTensor(flat.ToArray(), dims.Select(x => (int)x).ToArray()); + } + + public static DenseTensor IntArrayToTensor(long[][] array, long[] dims) + { + var flat = new List(); + foreach (var row in array) + { + flat.AddRange(row); + } + return new DenseTensor(flat.ToArray(), dims.Select(x => (int)x).ToArray()); + } + + // ============================================================================ + // Timer utility + // ============================================================================ + + public static T Timer(string name, Func func) + { + var start = DateTime.Now; + Console.WriteLine($"{name}..."); + var result = func(); + var elapsed = (DateTime.Now - start).TotalSeconds; + Console.WriteLine($" -> {name} completed in {elapsed:F2} sec"); + return result; + } + + public static string SanitizeFilename(string text, int maxLen) + { + var result = new StringBuilder(); + int count = 0; + foreach (char c in text) + { + if (count >= maxLen) break; + if (char.IsLetterOrDigit(c)) + { + result.Append(c); + } + else + { + result.Append('_'); + } + count++; + } + return result.ToString(); + } + } +} diff --git a/csharp/README.md b/csharp/README.md new file mode 100644 index 0000000..806af1d --- /dev/null +++ b/csharp/README.md @@ -0,0 +1,99 @@ +# TTS ONNX Inference Examples + +This guide provides examples for running TTS inference using `ExampleONNX.cs`. + +## Installation + +### Prerequisites +- .NET 9.0 SDK or later +- [Download .NET SDK](https://dotnet.microsoft.com/download) + +### Install dependencies +```bash +dotnet restore +``` + +## Basic Usage + +### Example 1: Default Inference +Run inference with default settings: +```bash +dotnet run +``` + +This will use: +- Voice style: `assets/voice_styles/M1.json` +- Text: "This morning, I took a walk in the park, and the sound of the birds and the breeze was so pleasant that I stopped for a long time just to listen." +- Output directory: `results/` +- Total steps: 5 +- Number of generations: 4 + +### Example 2: Batch Inference +Process multiple voice styles and texts at once: +```bash +dotnet run -- \ + --voice-style assets/voice_styles/M1.json,assets/voice_styles/F1.json \ + --text "The sun sets behind the mountains, painting the sky in shades of pink and orange.|The weather is beautiful and sunny outside. A gentle breeze makes the air feel fresh and pleasant." +``` + +This will: +- Generate speech for 2 different voice-text pairs +- Use male voice style (M1.json) for the first text +- Use female voice style (F1.json) for the second text +- Process both samples in a single batch + +### Example 3: High Quality Inference +Increase denoising steps for better quality: +```bash +dotnet run -- \ + --total-step 10 \ + --voice-style assets/voice_styles/M1.json \ + --text "Increasing the number of denoising steps improves the output's fidelity and overall quality." +``` + +This will: +- Use 10 denoising steps instead of the default 5 +- Produce higher quality output at the cost of slower inference + +## Available Arguments + +| Argument | Type | Default | Description | +|----------|------|---------|-------------| +| `--use-gpu` | flag | False | Use GPU for inference (not supported yet) | +| `--onnx-dir` | str | `assets/onnx` | Path to ONNX model directory | +| `--total-step` | int | 5 | Number of denoising steps (higher = better quality, slower) | +| `--n-test` | int | 4 | Number of times to generate each sample | +| `--voice-style` | str+ | `assets/voice_styles/M1.json` | Voice style file path(s) (comma-separated) | +| `--text` | str+ | (long default text) | Text(s) to synthesize (pipe-separated: `|`) | +| `--save-dir` | str | `results` | Output directory | + +## Notes + +- **Batch Processing**: The number of `--voice-style` files must match the number of `--text` entries +- **Quality vs Speed**: Higher `--total-step` values produce better quality but take longer +- **GPU Support**: GPU mode is not supported yet + +## Building the Project + +### Build for Release +```bash +dotnet build -c Release +``` + +### Run the compiled executable +```bash +./bin/Release/net9.0/Supertonic +``` + +## Project Structure + +``` +csharp/ +β”œβ”€β”€ ExampleONNX.cs # Main inference script +β”œβ”€β”€ Helper.cs # Helper functions and classes +β”œβ”€β”€ Supertonic.csproj # Project configuration +β”œβ”€β”€ README.md # This file +└── results/ # Output directory (created automatically) +``` + + diff --git a/csharp/Supertonic.csproj b/csharp/Supertonic.csproj new file mode 100644 index 0000000..25514f8 --- /dev/null +++ b/csharp/Supertonic.csproj @@ -0,0 +1,17 @@ + + + + Exe + net9.0 + 13.0 + enable + + + + + + + + + + diff --git a/csharp/assets b/csharp/assets new file mode 120000 index 0000000..ec2e4be --- /dev/null +++ b/csharp/assets @@ -0,0 +1 @@ +../assets \ No newline at end of file diff --git a/go/.gitignore b/go/.gitignore new file mode 100644 index 0000000..8d3598d --- /dev/null +++ b/go/.gitignore @@ -0,0 +1,17 @@ +# Binaries +tts_example +example_onnx +*.exe + +# Go build artifacts +*.o +*.a +*.so + +# Results +results/ + +# Go workspace +go.work +go.work.sum + diff --git a/go/README.md b/go/README.md new file mode 100644 index 0000000..bc37fb6 --- /dev/null +++ b/go/README.md @@ -0,0 +1,128 @@ +# TTS ONNX Inference Examples + +This guide provides examples for running TTS inference using `example_onnx.go`. + +## Installation + +This project uses Go modules for dependency management. + +### Prerequisites + +1. Install Go 1.21 or later from [https://golang.org/dl/](https://golang.org/dl/) +2. Install ONNX Runtime C library: + +**macOS (via Homebrew):** +```bash +brew install onnxruntime +``` + +**Linux:** +```bash +# Download ONNX Runtime from GitHub releases +wget https://github.com/microsoft/onnxruntime/releases/download/v1.16.0/onnxruntime-linux-x64-1.16.0.tgz +tar -xzf onnxruntime-linux-x64-1.16.0.tgz +sudo cp onnxruntime-linux-x64-1.16.0/lib/* /usr/local/lib/ +sudo cp -r onnxruntime-linux-x64-1.16.0/include/* /usr/local/include/ +sudo ldconfig +``` + +### Install Go dependencies + +```bash +go mod download +``` + +### Configure ONNX Runtime Library Path (Optional) + +If the ONNX Runtime library is not in a standard location, set the environment variable: + +**Automatic Detection (Recommended):** + +```bash +# macOS +export ONNXRUNTIME_LIB_PATH=$(brew --prefix onnxruntime 2>/dev/null)/lib/libonnxruntime.dylib + +# Linux +export ONNXRUNTIME_LIB_PATH=$(find /usr/local/lib /usr/lib -name "libonnxruntime.so*" 2>/dev/null | head -n 1) +``` + +**Manual Configuration:** + +```bash +export ONNXRUNTIME_LIB_PATH=/path/to/libonnxruntime.so # Linux +# or +export ONNXRUNTIME_LIB_PATH=/path/to/libonnxruntime.dylib # macOS +``` + +## Basic Usage + +### Example 1: Default Inference +Run inference with default settings: +```bash +go run example_onnx.go helper.go +``` + +This will use: +- Voice style: `assets/voice_styles/M1.json` +- Text: "This morning, I took a walk in the park, and the sound of the birds and the breeze was so pleasant that I stopped for a long time just to listen." +- Output directory: `results/` +- Total steps: 5 +- Number of generations: 4 + +### Example 2: Batch Inference +Process multiple voice styles and texts at once: +```bash +go run example_onnx.go helper.go \ + -voice-style "assets/voice_styles/M1.json,assets/voice_styles/F1.json" \ + -text "The sun sets behind the mountains, painting the sky in shades of pink and orange.|The weather is beautiful and sunny outside. A gentle breeze makes the air feel fresh and pleasant." +``` + +This will: +- Generate speech for 2 different voice-text pairs +- Use male voice (M1.json) for the first text +- Use female voice (F1.json) for the second text +- Process both samples in a single batch + +### Example 3: High Quality Inference +Increase denoising steps for better quality: +```bash +go run example_onnx.go helper.go \ + -total-step 10 \ + -voice-style "assets/voice_styles/M1.json" \ + -text "Increasing the number of denoising steps improves the output's fidelity and overall quality." +``` + +This will: +- Use 10 denoising steps instead of the default 5 +- Produce higher quality output at the cost of slower inference + +## Available Arguments + +| Argument | Type | Default | Description | +|----------|------|---------|-------------| +| `-use-gpu` | flag | false | Use GPU for inference (default: CPU) | +| `-onnx-dir` | str | `assets/onnx` | Path to ONNX model directory | +| `-total-step` | int | 5 | Number of denoising steps (higher = better quality, slower) | +| `-n-test` | int | 4 | Number of times to generate each sample | +| `-voice-style` | str | `assets/voice_styles/M1.json` | Voice style file path(s), comma-separated | +| `-text` | str | (long default text) | Text(s) to synthesize, pipe-separated | +| `-save-dir` | str | `results` | Output directory | + +## Notes + +- **Batch Processing**: The number of `-voice-style` files must match the number of `-text` entries +- **Quality vs Speed**: Higher `-total-step` values produce better quality but take longer +- **GPU Support**: GPU mode is not supported yet + +## Building a Binary + +To build a standalone executable: +```bash +go build -o tts_example example_onnx.go helper.go +``` + +Then run it: +```bash +./tts_example -voice-style "../assets/voice_styles/M1.json" -text "Hello world" +``` + diff --git a/go/assets b/go/assets new file mode 120000 index 0000000..ec2e4be --- /dev/null +++ b/go/assets @@ -0,0 +1 @@ +../assets \ No newline at end of file diff --git a/go/example_onnx.go b/go/example_onnx.go new file mode 100644 index 0000000..567285f --- /dev/null +++ b/go/example_onnx.go @@ -0,0 +1,144 @@ +package main + +import ( + "flag" + "fmt" + "os" + "path/filepath" + "strings" + + ort "github.com/yalue/onnxruntime_go" +) + +// Args holds command line arguments +type Args struct { + useGPU bool + onnxDir string + totalStep int + nTest int + voiceStyle []string + text []string + saveDir string +} + +func parseArgs() *Args { + args := &Args{} + + flag.BoolVar(&args.useGPU, "use-gpu", false, "Use GPU for inference (default: CPU)") + flag.StringVar(&args.onnxDir, "onnx-dir", "assets/onnx", "Path to ONNX model directory") + flag.IntVar(&args.totalStep, "total-step", 5, "Number of denoising steps") + flag.IntVar(&args.nTest, "n-test", 4, "Number of times to generate") + flag.StringVar(&args.saveDir, "save-dir", "results", "Output directory") + + var voiceStyleStr, textStr string + flag.StringVar(&voiceStyleStr, "voice-style", "assets/voice_styles/M1.json", "Voice style file path(s), comma-separated") + flag.StringVar(&textStr, "text", "This morning, I took a walk in the park, and the sound of the birds and the breeze was so pleasant that I stopped for a long time just to listen.", "Text(s) to synthesize, pipe-separated") + + flag.Parse() + + // Parse comma-separated voice-style + if voiceStyleStr != "" { + args.voiceStyle = strings.Split(voiceStyleStr, ",") + for i := range args.voiceStyle { + args.voiceStyle[i] = strings.TrimSpace(args.voiceStyle[i]) + } + } + + // Parse pipe-separated text + if textStr != "" { + args.text = strings.Split(textStr, "|") + for i := range args.text { + args.text[i] = strings.TrimSpace(args.text[i]) + } + } + + return args +} + +func main() { + fmt.Println("=== TTS Inference with ONNX Runtime (Go) ===\n") + + // --- 1. Parse arguments --- // + args := parseArgs() + totalStep := args.totalStep + nTest := args.nTest + saveDir := args.saveDir + voiceStylePaths := args.voiceStyle + textList := args.text + + if len(voiceStylePaths) != len(textList) { + fmt.Printf("Error: Number of voice styles (%d) must match number of texts (%d)\n", + len(voiceStylePaths), len(textList)) + os.Exit(1) + } + + bsz := len(voiceStylePaths) + + // Initialize ONNX Runtime + if err := InitializeONNXRuntime(); err != nil { + fmt.Printf("Error initializing ONNX Runtime: %v\n", err) + os.Exit(1) + } + defer ort.DestroyEnvironment() + + // --- 2. Load config --- // + cfg, err := LoadCfgs(args.onnxDir) + if err != nil { + fmt.Printf("Error loading config: %v\n", err) + os.Exit(1) + } + + // --- 3. Load TTS components --- // + textToSpeech, err := LoadTextToSpeech(args.onnxDir, args.useGPU, cfg) + if err != nil { + fmt.Printf("Error loading TTS components: %v\n", err) + os.Exit(1) + } + defer textToSpeech.Destroy() + + // --- 4. Load voice styles --- // + style, err := LoadVoiceStyle(voiceStylePaths, true) + if err != nil { + fmt.Printf("Error loading voice styles: %v\n", err) + os.Exit(1) + } + defer style.Destroy() + + // --- 5. Synthesize speech --- // + if err := os.MkdirAll(saveDir, 0755); err != nil { + fmt.Printf("Error creating save directory: %v\n", err) + os.Exit(1) + } + + for n := 0; n < nTest; n++ { + fmt.Printf("\n[%d/%d] Starting synthesis...\n", n+1, nTest) + + var wav []float32 + var duration []float32 + Timer("Generating speech from text", func() interface{} { + w, d, err := textToSpeech.Call(textList, style, totalStep) + if err != nil { + fmt.Printf("Error generating speech: %v\n", err) + os.Exit(1) + } + wav = w + duration = d + return nil + }) + + // Save outputs + for i := 0; i < bsz; i++ { + fname := fmt.Sprintf("%s_%d.wav", sanitizeFilename(textList[i], 20), n+1) + wavOut := extractWavSegment(wav, duration[i], textToSpeech.SampleRate, i, bsz) + + outputPath := filepath.Join(saveDir, fname) + if err := writeWavFile(outputPath, wavOut, textToSpeech.SampleRate); err != nil { + fmt.Printf("Error writing wav file: %v\n", err) + continue + } + fmt.Printf("Saved: %s\n", outputPath) + } + } + + fmt.Println("\n=== Synthesis completed successfully! ===") +} diff --git a/go/go.mod b/go/go.mod new file mode 100644 index 0000000..d8685ca --- /dev/null +++ b/go/go.mod @@ -0,0 +1,12 @@ +module supertonic-tts + +go 1.21 + +require ( + github.com/go-audio/audio v1.0.0 + github.com/go-audio/wav v1.1.0 + github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12 + github.com/yalue/onnxruntime_go v1.11.0 +) + +require github.com/go-audio/riff v1.0.0 // indirect diff --git a/go/go.sum b/go/go.sum new file mode 100644 index 0000000..c456c20 --- /dev/null +++ b/go/go.sum @@ -0,0 +1,10 @@ +github.com/go-audio/audio v1.0.0 h1:zS9vebldgbQqktK4H0lUqWrG8P0NxCJVqcj7ZpNnwd4= +github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs= +github.com/go-audio/riff v1.0.0 h1:d8iCGbDvox9BfLagY94fBynxSPHO80LmZCaOsmKxokA= +github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498= +github.com/go-audio/wav v1.1.0 h1:jQgLtbqBzY7G+BM8fXF7AHUk1uHUviWS4X39d5rsL2g= +github.com/go-audio/wav v1.1.0/go.mod h1:mpe9qfwbScEbkd8uybLuIpTgHyrISw/OTuvjUW2iGtE= +github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12 h1:dd7vnTDfjtwCETZDrRe+GPYNLA1jBtbZeyfyE8eZCyk= +github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12/go.mod h1:i/KKcxEWEO8Yyl11DYafRPKOPVYTrhxiTRigjtEEXZU= +github.com/yalue/onnxruntime_go v1.11.0 h1:aKH4yPIbqfcB3SfnQWq/WxzLelkyolntHnffL3eMBHY= +github.com/yalue/onnxruntime_go v1.11.0/go.mod h1:b4X26A8pekNb1ACJ58wAXgNKeUCGEAQ9dmACut9Sm/4= diff --git a/go/helper.go b/go/helper.go new file mode 100644 index 0000000..80da321 --- /dev/null +++ b/go/helper.go @@ -0,0 +1,734 @@ +package main + +import ( + "encoding/json" + "fmt" + "math" + "math/rand" + "os" + "path/filepath" + "time" + + "github.com/go-audio/audio" + "github.com/go-audio/wav" + ort "github.com/yalue/onnxruntime_go" +) + +// Config structures +type SpecProcessorConfig struct { + NFFT int `json:"n_fft"` + WinLength int `json:"win_length"` + HopLength int `json:"hop_length"` + NMels int `json:"n_mels"` + Eps float64 `json:"eps"` + NormMean float64 `json:"norm_mean"` + NormStd float64 `json:"norm_std"` +} + +type EncoderConfig struct { + SpecProcessor SpecProcessorConfig `json:"spec_processor"` +} + +type AEConfig struct { + SampleRate int `json:"sample_rate"` + BaseChunkSize int `json:"base_chunk_size"` + Encoder EncoderConfig `json:"encoder"` +} + +type StyleTokenLayerConfig struct { + NStyle int `json:"n_style"` + StyleValueDim int `json:"style_value_dim"` +} + +type StyleEncoderConfig struct { + StyleTokenLayer StyleTokenLayerConfig `json:"style_token_layer"` +} + +type ProjOutConfig struct { + Idim int `json:"idim"` + Odim int `json:"odim"` +} + +type TextEncoderConfig struct { + ProjOut ProjOutConfig `json:"proj_out"` +} + +type TTLConfig struct { + ChunkCompressFactor int `json:"chunk_compress_factor"` + LatentDim int `json:"latent_dim"` + StyleEncoder StyleEncoderConfig `json:"style_encoder"` + TextEncoder TextEncoderConfig `json:"text_encoder"` +} + +type DPStyleEncoderConfig struct { + StyleTokenLayer StyleTokenLayerConfig `json:"style_token_layer"` +} + +type DPConfig struct { + LatentDim int `json:"latent_dim"` + ChunkCompressFactor int `json:"chunk_compress_factor"` + StyleEncoder DPStyleEncoderConfig `json:"style_encoder"` +} + +type Config struct { + AE AEConfig `json:"ae"` + TTL TTLConfig `json:"ttl"` + DP DPConfig `json:"dp"` +} + +// VoiceStyleData holds voice style JSON structure +type VoiceStyleData struct { + StyleTTL struct { + Data [][][]float64 `json:"data"` + Dims []int64 `json:"dims"` + Type string `json:"type"` + } `json:"style_ttl"` + StyleDP struct { + Data [][][]float64 `json:"data"` + Dims []int64 `json:"dims"` + Type string `json:"type"` + } `json:"style_dp"` +} + +// UnicodeProcessor for text processing +type UnicodeProcessor struct { + indexer []int64 +} + +// NewUnicodeProcessor creates a new UnicodeProcessor +func NewUnicodeProcessor(unicodeIndexerPath string) (*UnicodeProcessor, error) { + indexer, err := loadJSONInt64(unicodeIndexerPath) + if err != nil { + return nil, fmt.Errorf("failed to load unicode indexer: %w", err) + } + + return &UnicodeProcessor{indexer: indexer}, nil +} + +// Call processes text list to text IDs and mask +func (up *UnicodeProcessor) Call(textList []string) ([][]int64, [][][]float64) { + // Preprocess texts + processedTexts := make([]string, len(textList)) + for i, text := range textList { + processedTexts[i] = preprocessText(text) + } + + // Get text lengths + textLengths := make([]int64, len(processedTexts)) + maxLen := 0 + for i, text := range processedTexts { + textLengths[i] = int64(len([]rune(text))) + if int(textLengths[i]) > maxLen { + maxLen = int(textLengths[i]) + } + } + + // Create text IDs + textIDs := make([][]int64, len(processedTexts)) + for i, text := range processedTexts { + row := make([]int64, maxLen) + runes := []rune(text) + for j, r := range runes { + unicodeVal := int(r) + if unicodeVal < len(up.indexer) { + row[j] = up.indexer[unicodeVal] + } else { + row[j] = -1 + } + } + textIDs[i] = row + } + + // Create text mask + textMask := lengthToMask(textLengths, maxLen) + + return textIDs, textMask +} + +// Utility functions +func preprocessText(text string) string { + // Simple normalization (Go doesn't have built-in NFKD normalization) + // For full Unicode normalization, use golang.org/x/text/unicode/norm + return text +} + +func lengthToMask(lengths []int64, maxLen int) [][][]float64 { + bsz := len(lengths) + mask := make([][][]float64, bsz) + + for i := 0; i < bsz; i++ { + row := make([]float64, maxLen) + for j := 0; j < maxLen; j++ { + if int64(j) < lengths[i] { + row[j] = 1.0 + } else { + row[j] = 0.0 + } + } + mask[i] = [][]float64{row} + } + + return mask +} + +func getTextMask(textLengths []int64, maxLen int) [][][]float64 { + return lengthToMask(textLengths, maxLen) +} + +func getLatentMask(wavLengths []int64, cfg Config) [][][]float64 { + baseChunkSize := int64(cfg.AE.BaseChunkSize) + chunkCompressFactor := int64(cfg.TTL.ChunkCompressFactor) + latentSize := baseChunkSize * chunkCompressFactor + + latentLengths := make([]int64, len(wavLengths)) + maxLen := int64(0) + for i, wavLen := range wavLengths { + latentLengths[i] = (wavLen + latentSize - 1) / latentSize + if latentLengths[i] > maxLen { + maxLen = latentLengths[i] + } + } + + return lengthToMask(latentLengths, int(maxLen)) +} + +func writeWavFile(filename string, audioData []float64, sampleRate int) error { + file, err := os.Create(filename) + if err != nil { + return err + } + defer file.Close() + + // Convert float64 to int + intData := make([]int, len(audioData)) + for i, sample := range audioData { + // Clamp to [-1, 1] and convert to 16-bit int + clamped := math.Max(-1.0, math.Min(1.0, sample)) + intData[i] = int(clamped * 32767) + } + + encoder := wav.NewEncoder(file, sampleRate, 16, 1, 1) + buf := &audio.IntBuffer{ + Data: intData, + Format: &audio.Format{SampleRate: sampleRate, NumChannels: 1}, + SourceBitDepth: 16, + } + + if err := encoder.Write(buf); err != nil { + return err + } + + return encoder.Close() +} + +// Style holds style tensors +type Style struct { + TtlTensor *ort.Tensor[float32] + DpTensor *ort.Tensor[float32] +} + +func (s *Style) Destroy() { + if s.TtlTensor != nil { + s.TtlTensor.Destroy() + } + if s.DpTensor != nil { + s.DpTensor.Destroy() + } +} + +// LoadVoiceStyle loads voice style from JSON files +func LoadVoiceStyle(voiceStylePaths []string, verbose bool) (*Style, error) { + bsz := len(voiceStylePaths) + + // Read first file to get dimensions + firstData, err := os.ReadFile(voiceStylePaths[0]) + if err != nil { + return nil, fmt.Errorf("failed to read voice style file: %w", err) + } + + var firstStyle VoiceStyleData + if err := json.Unmarshal(firstData, &firstStyle); err != nil { + return nil, fmt.Errorf("failed to parse voice style JSON: %w", err) + } + + ttlDims := firstStyle.StyleTTL.Dims + dpDims := firstStyle.StyleDP.Dims + + ttlDim1 := ttlDims[1] + ttlDim2 := ttlDims[2] + dpDim1 := dpDims[1] + dpDim2 := dpDims[2] + + // Pre-allocate arrays with full batch size + ttlSize := int(int64(bsz) * ttlDim1 * ttlDim2) + dpSize := int(int64(bsz) * dpDim1 * dpDim2) + ttlFlat := make([]float32, ttlSize) + dpFlat := make([]float32, dpSize) + + // Fill in the data + for i := 0; i < bsz; i++ { + data, err := os.ReadFile(voiceStylePaths[i]) + if err != nil { + return nil, fmt.Errorf("failed to read voice style file: %w", err) + } + + var voiceStyle VoiceStyleData + if err := json.Unmarshal(data, &voiceStyle); err != nil { + return nil, fmt.Errorf("failed to parse voice style JSON: %w", err) + } + + // Flatten TTL data + ttlOffset := int(int64(i) * ttlDim1 * ttlDim2) + idx := 0 + for _, batch := range voiceStyle.StyleTTL.Data { + for _, row := range batch { + for _, val := range row { + ttlFlat[ttlOffset+idx] = float32(val) + idx++ + } + } + } + + // Flatten DP data + dpOffset := int(int64(i) * dpDim1 * dpDim2) + idx = 0 + for _, batch := range voiceStyle.StyleDP.Data { + for _, row := range batch { + for _, val := range row { + dpFlat[dpOffset+idx] = float32(val) + idx++ + } + } + } + } + + ttlShape := []int64{int64(bsz), ttlDim1, ttlDim2} + dpShape := []int64{int64(bsz), dpDim1, dpDim2} + + ttlTensor, err := ort.NewTensor(ttlShape, ttlFlat) + if err != nil { + return nil, fmt.Errorf("failed to create TTL tensor: %w", err) + } + + dpTensor, err := ort.NewTensor(dpShape, dpFlat) + if err != nil { + ttlTensor.Destroy() + return nil, fmt.Errorf("failed to create DP tensor: %w", err) + } + + if verbose { + fmt.Printf("Loaded %d voice styles\n\n", bsz) + } + + return &Style{ + TtlTensor: ttlTensor, + DpTensor: dpTensor, + }, nil +} + +// TextToSpeech generates speech from text +type TextToSpeech struct { + cfg Config + textProcessor *UnicodeProcessor + dpOrt *ort.DynamicAdvancedSession + textEncOrt *ort.DynamicAdvancedSession + vectorEstOrt *ort.DynamicAdvancedSession + vocoderOrt *ort.DynamicAdvancedSession + SampleRate int + baseChunkSize int + chunkCompress int + ldim int +} + +func (tts *TextToSpeech) sampleNoisyLatent(durOnnx []float32) ([][][]float64, [][][]float64) { + bsz := len(durOnnx) + maxDur := float64(0) + for _, d := range durOnnx { + if float64(d) > maxDur { + maxDur = float64(d) + } + } + + wavLenMax := maxDur * float64(tts.SampleRate) + wavLengths := make([]int64, bsz) + for i, d := range durOnnx { + wavLengths[i] = int64(float64(d) * float64(tts.SampleRate)) + } + + chunkSize := tts.baseChunkSize * tts.chunkCompress + latentLen := int((wavLenMax + float64(chunkSize) - 1) / float64(chunkSize)) + latentDim := tts.ldim * tts.chunkCompress + + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + noisyLatent := make([][][]float64, bsz) + for b := 0; b < bsz; b++ { + batch := make([][]float64, latentDim) + for d := 0; d < latentDim; d++ { + row := make([]float64, latentLen) + for t := 0; t < latentLen; t++ { + // Box-Muller transform for normal distribution + // Add epsilon to avoid log(0) + const eps = 1e-10 + u1 := math.Max(eps, rng.Float64()) + u2 := rng.Float64() + row[t] = math.Sqrt(-2.0*math.Log(u1)) * math.Cos(2.0*math.Pi*u2) + } + batch[d] = row + } + noisyLatent[b] = batch + } + + latentMask := getLatentMask(wavLengths, tts.cfg) + + // Apply mask + for b := 0; b < bsz; b++ { + for d := 0; d < latentDim; d++ { + for t := 0; t < latentLen; t++ { + noisyLatent[b][d][t] *= latentMask[b][0][t] + } + } + } + + return noisyLatent, latentMask +} + +func (tts *TextToSpeech) Call(textList []string, style *Style, totalStep int) ([]float32, []float32, error) { + bsz := len(textList) + + // Process text + textIDs, textMask := tts.textProcessor.Call(textList) + textIDsShape := []int64{int64(bsz), int64(len(textIDs[0]))} + textMaskShape := []int64{int64(bsz), 1, int64(len(textMask[0][0]))} + + textIDsTensor := IntArrayToTensor(textIDs, textIDsShape) + defer textIDsTensor.Destroy() + textMaskTensor := ArrayToTensor(textMask, textMaskShape) + defer textMaskTensor.Destroy() + + // Predict duration + dpOutputs := []ort.Value{nil} + err := tts.dpOrt.Run( + []ort.Value{textIDsTensor, style.DpTensor, textMaskTensor}, + dpOutputs, + ) + if err != nil { + return nil, nil, fmt.Errorf("failed to run duration predictor: %w", err) + } + durTensor := dpOutputs[0].(*ort.Tensor[float32]) + defer durTensor.Destroy() + durOnnx := durTensor.GetData() + + // Encode text + textIDsTensor2 := IntArrayToTensor(textIDs, textIDsShape) + defer textIDsTensor2.Destroy() + textEncOutputs := []ort.Value{nil} + err = tts.textEncOrt.Run( + []ort.Value{textIDsTensor2, style.TtlTensor, textMaskTensor}, + textEncOutputs, + ) + if err != nil { + return nil, nil, fmt.Errorf("failed to run text encoder: %w", err) + } + textEmbTensor := textEncOutputs[0].(*ort.Tensor[float32]) + defer textEmbTensor.Destroy() + + // Sample noisy latent + xt, latentMask := tts.sampleNoisyLatent(durOnnx) + latentShape := []int64{int64(bsz), int64(len(xt[0])), int64(len(xt[0][0]))} + latentMaskShape := []int64{int64(bsz), 1, int64(len(latentMask[0][0]))} + + // Prepare constant arrays + totalStepArray := make([]float32, bsz) + for b := 0; b < bsz; b++ { + totalStepArray[b] = float32(totalStep) + } + scalarShape := []int64{int64(bsz)} + + totalStepTensor, _ := ort.NewTensor(scalarShape, totalStepArray) + defer totalStepTensor.Destroy() + + // Denoising loop + for step := 0; step < totalStep; step++ { + currentStepArray := make([]float32, bsz) + for b := 0; b < bsz; b++ { + currentStepArray[b] = float32(step) + } + + currentStepTensor, _ := ort.NewTensor(scalarShape, currentStepArray) + noisyLatentTensor := ArrayToTensor(xt, latentShape) + latentMaskTensor := ArrayToTensor(latentMask, latentMaskShape) + textMaskTensor2 := ArrayToTensor(textMask, textMaskShape) + + vectorEstOutputs := []ort.Value{nil} + err = tts.vectorEstOrt.Run( + []ort.Value{noisyLatentTensor, textEmbTensor, style.TtlTensor, latentMaskTensor, textMaskTensor2, + currentStepTensor, totalStepTensor}, + vectorEstOutputs, + ) + if err != nil { + return nil, nil, fmt.Errorf("failed to run vector estimator: %w", err) + } + + denoisedTensor := vectorEstOutputs[0].(*ort.Tensor[float32]) + denoisedData := denoisedTensor.GetData() + + // Update latent + idx := 0 + for b := 0; b < bsz; b++ { + for d := 0; d < len(xt[b]); d++ { + for t := 0; t < len(xt[b][d]); t++ { + xt[b][d][t] = float64(denoisedData[idx]) + idx++ + } + } + } + + noisyLatentTensor.Destroy() + latentMaskTensor.Destroy() + textMaskTensor2.Destroy() + currentStepTensor.Destroy() + denoisedTensor.Destroy() + } + + // Generate waveform + finalLatentTensor := ArrayToTensor(xt, latentShape) + defer finalLatentTensor.Destroy() + + vocoderOutputs := []ort.Value{nil} + err = tts.vocoderOrt.Run( + []ort.Value{finalLatentTensor}, + vocoderOutputs, + ) + if err != nil { + return nil, nil, fmt.Errorf("failed to run vocoder: %w", err) + } + + wavBatchTensor := vocoderOutputs[0].(*ort.Tensor[float32]) + defer wavBatchTensor.Destroy() + wav := wavBatchTensor.GetData() + + return wav, durOnnx, nil +} + +func (tts *TextToSpeech) Destroy() { + if tts.dpOrt != nil { + tts.dpOrt.Destroy() + } + if tts.textEncOrt != nil { + tts.textEncOrt.Destroy() + } + if tts.vectorEstOrt != nil { + tts.vectorEstOrt.Destroy() + } + if tts.vocoderOrt != nil { + tts.vocoderOrt.Destroy() + } +} + +// LoadTextToSpeech loads TTS components +func LoadTextToSpeech(onnxDir string, useGPU bool, cfg Config) (*TextToSpeech, error) { + if useGPU { + return nil, fmt.Errorf("GPU mode is not supported yet") + } + fmt.Println("Using CPU for inference\n") + + // Load models + dpPath := filepath.Join(onnxDir, "duration_predictor.onnx") + textEncPath := filepath.Join(onnxDir, "text_encoder.onnx") + vectorEstPath := filepath.Join(onnxDir, "vector_estimator.onnx") + vocoderPath := filepath.Join(onnxDir, "vocoder.onnx") + + dpOrt, err := ort.NewDynamicAdvancedSession(dpPath, []string{"text_ids", "style_dp", "text_mask"}, + []string{"duration"}, nil) + if err != nil { + return nil, fmt.Errorf("failed to load duration predictor: %w", err) + } + + textEncOrt, err := ort.NewDynamicAdvancedSession(textEncPath, []string{"text_ids", "style_ttl", "text_mask"}, + []string{"text_emb"}, nil) + if err != nil { + return nil, fmt.Errorf("failed to load text encoder: %w", err) + } + + vectorEstOrt, err := ort.NewDynamicAdvancedSession(vectorEstPath, + []string{"noisy_latent", "text_emb", "style_ttl", "latent_mask", "text_mask", "current_step", "total_step"}, + []string{"denoised_latent"}, nil) + if err != nil { + return nil, fmt.Errorf("failed to load vector estimator: %w", err) + } + + vocoderOrt, err := ort.NewDynamicAdvancedSession(vocoderPath, []string{"latent"}, + []string{"wav_tts"}, nil) + if err != nil { + return nil, fmt.Errorf("failed to load vocoder: %w", err) + } + + // Load text processor + unicodeIndexerPath := filepath.Join(onnxDir, "unicode_indexer.json") + textProcessor, err := NewUnicodeProcessor(unicodeIndexerPath) + if err != nil { + return nil, err + } + + textToSpeech := &TextToSpeech{ + cfg: cfg, + textProcessor: textProcessor, + dpOrt: dpOrt, + textEncOrt: textEncOrt, + vectorEstOrt: vectorEstOrt, + vocoderOrt: vocoderOrt, + SampleRate: cfg.AE.SampleRate, + baseChunkSize: cfg.AE.BaseChunkSize, + chunkCompress: cfg.TTL.ChunkCompressFactor, + ldim: cfg.TTL.LatentDim, + } + + return textToSpeech, nil +} + +// InitializeONNXRuntime initializes ONNX Runtime environment +func InitializeONNXRuntime() error { + libPath := os.Getenv("ONNXRUNTIME_LIB_PATH") + if libPath == "" { + libPath = "/usr/local/lib/libonnxruntime.so" + if _, err := os.Stat("/usr/local/lib/libonnxruntime.dylib"); err == nil { + libPath = "/usr/local/lib/libonnxruntime.dylib" + } else if _, err := os.Stat("/usr/lib/libonnxruntime.so"); err == nil { + libPath = "/usr/lib/libonnxruntime.so" + } + } + ort.SetSharedLibraryPath(libPath) + + if err := ort.InitializeEnvironment(); err != nil { + return fmt.Errorf("failed to initialize ONNX Runtime: %w\nHint: Set ONNXRUNTIME_LIB_PATH environment variable", err) + } + return nil +} + +// sanitizeFilename creates a safe filename from text +func sanitizeFilename(text string, maxLen int) string { + if len(text) > maxLen { + text = text[:maxLen] + } + + result := make([]rune, 0, len(text)) + for _, r := range text { + if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') { + result = append(result, r) + } else { + result = append(result, '_') + } + } + return string(result) +} + +// extractWavSegment extracts a single audio segment from batch output +func extractWavSegment(wav []float32, duration float32, sampleRate int, index int, batchSize int) []float64 { + wavLen := int(float64(sampleRate) * float64(duration)) + wavPerBatch := len(wav) / batchSize + + wavStart := index * wavPerBatch + wavEnd := wavStart + wavLen + if wavEnd > len(wav) { + wavEnd = len(wav) + } + + wavOut := make([]float64, wavLen) + for j := 0; j < wavLen && wavStart+j < len(wav); j++ { + wavOut[j] = float64(wav[wavStart+j]) + } + + return wavOut +} + +// Timer measures execution time +func Timer(name string, fn func() interface{}) interface{} { + start := time.Now() + fmt.Printf("%s...\n", name) + result := fn() + elapsed := time.Since(start).Seconds() + fmt.Printf(" -> %s completed in %.2f sec\n", name, elapsed) + return result +} + +// LoadCfgs loads configuration from JSON file +func LoadCfgs(onnxDir string) (Config, error) { + cfgPath := filepath.Join(onnxDir, "tts.json") + data, err := os.ReadFile(cfgPath) + if err != nil { + return Config{}, err + } + + var cfg Config + if err := json.Unmarshal(data, &cfg); err != nil { + return Config{}, err + } + + return cfg, nil +} + +// JSON loading helpers +func loadJSONInt64(filePath string) ([]int64, error) { + data, err := os.ReadFile(filePath) + if err != nil { + return nil, err + } + + var result []int64 + if err := json.Unmarshal(data, &result); err != nil { + return nil, err + } + + return result, nil +} + +// Tensor conversion utilities +func ArrayToTensor(array [][][]float64, shape []int64) *ort.Tensor[float32] { + // Flatten array + totalSize := int64(1) + for _, dim := range shape { + totalSize *= dim + } + + flat := make([]float32, totalSize) + idx := 0 + for b := 0; b < len(array); b++ { + for d := 0; d < len(array[b]); d++ { + for t := 0; t < len(array[b][d]); t++ { + flat[idx] = float32(array[b][d][t]) + idx++ + } + } + } + + tensor, err := ort.NewTensor(shape, flat) + if err != nil { + panic(err) + } + + return tensor +} + +func IntArrayToTensor(array [][]int64, shape []int64) *ort.Tensor[int64] { + // Flatten array + totalSize := int64(1) + for _, dim := range shape { + totalSize *= dim + } + + flat := make([]int64, totalSize) + idx := 0 + for b := 0; b < len(array); b++ { + for t := 0; t < len(array[b]); t++ { + flat[idx] = array[b][t] + idx++ + } + } + + tensor, err := ort.NewTensor(shape, flat) + if err != nil { + panic(err) + } + + return tensor +} diff --git a/img/Supertonic_IMG_v02.svg b/img/Supertonic_IMG_v02.svg new file mode 100644 index 0000000..9237279 --- /dev/null +++ b/img/Supertonic_IMG_v02.svg @@ -0,0 +1,250 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/img/Supertonic_IMG_v02_4x.webp b/img/Supertonic_IMG_v02_4x.webp new file mode 100644 index 0000000..78b0e03 Binary files /dev/null and b/img/Supertonic_IMG_v02_4x.webp differ diff --git a/ios/ExampleiOSApp/App.swift b/ios/ExampleiOSApp/App.swift new file mode 100644 index 0000000..1be5703 --- /dev/null +++ b/ios/ExampleiOSApp/App.swift @@ -0,0 +1,10 @@ +import SwiftUI + +@main +struct ExampleiOSApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/ios/ExampleiOSApp/AudioPlayer.swift b/ios/ExampleiOSApp/AudioPlayer.swift new file mode 100644 index 0000000..e6f178d --- /dev/null +++ b/ios/ExampleiOSApp/AudioPlayer.swift @@ -0,0 +1,30 @@ +import Foundation +import AVFoundation + +final class AudioPlayer: NSObject, AVAudioPlayerDelegate { + private var player: AVAudioPlayer? + private var onFinish: (() -> Void)? + + func play(url: URL, onFinish: (() -> Void)? = nil) { + self.onFinish = onFinish + do { + let data = try Data(contentsOf: url) + let player = try AVAudioPlayer(data: data) + player.delegate = self + player.prepareToPlay() + player.play() + self.player = player + } catch { + print("Audio play error: \(error)") + } + } + + func stop() { + player?.stop() + player = nil + } + + func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { + onFinish?() + } +} diff --git a/ios/ExampleiOSApp/ContentView.swift b/ios/ExampleiOSApp/ContentView.swift new file mode 100644 index 0000000..0d380af --- /dev/null +++ b/ios/ExampleiOSApp/ContentView.swift @@ -0,0 +1,98 @@ +import SwiftUI + +struct ContentView: View { + @StateObject private var vm = TTSViewModel() + + var body: some View { + ZStack { + LinearGradient(gradient: Gradient(colors: [Color(.systemBackground), Color(.secondarySystemBackground)]), startPoint: .topLeading, endPoint: .bottomTrailing) + .ignoresSafeArea() + + VStack(spacing: 20) { + Spacer() + + VStack(spacing: 12) { + Text("SupertonicTTS iOS Demo") + .font(.title2.weight(.semibold)) + .foregroundColor(.primary) + + ZStack(alignment: .topLeading) { + if vm.text.isEmpty { + Text("Type text to synthesize") + .foregroundColor(.secondary) + .padding(.horizontal, 14) + .padding(.vertical, 12) + } + TextEditor(text: $vm.text) + .frame(minHeight: 120, maxHeight: 180) + .padding(8) + .background(Color(.secondarySystemBackground)) + .cornerRadius(12) + .overlay( + RoundedRectangle(cornerRadius: 12) + .stroke(Color.secondary.opacity(0.3), lineWidth: 1) + ) + } + .padding(.horizontal) + + HStack(spacing: 12) { + Text("NFE") + .font(.subheadline) + .foregroundColor(.secondary) + Slider(value: $vm.nfe, in: 2...15, step: 1) + Text("\(Int(vm.nfe))") + .font(.subheadline.monospacedDigit()) + .frame(width: 36) + } + .padding(.horizontal) + + Picker("Voice", selection: $vm.voice) { + Text("M").tag(TTSService.Voice.male) + Text("F").tag(TTSService.Voice.female) + } + .pickerStyle(SegmentedPickerStyle()) + .padding(.horizontal) + } + + HStack(spacing: 16) { + Button(action: { vm.generate() }) { + Label(vm.isGenerating ? "Generating..." : "Generate", systemImage: vm.isGenerating ? "hourglass" : "wand.and.stars" + ) + .labelStyle(.titleAndIcon) + } + .buttonStyle(.borderedProminent) + .tint(.accentColor) + .disabled(vm.isGenerating) + + Button(action: { vm.togglePlay() }) { + Label(vm.isPlaying ? "Stop" : "Play", systemImage: vm.isPlaying ? "stop.fill" : "play.fill") + } + .buttonStyle(.bordered) + .disabled(vm.audioURL == nil) + } + + if let rtf = vm.rtfText { + Text(rtf) + .font(.footnote.monospacedDigit()) + .foregroundColor(.secondary) + .padding(.top, 2) + } + + if let error = vm.errorMessage { + Text(error) + .foregroundColor(.red) + .font(.footnote) + .multilineTextAlignment(.center) + .padding(.horizontal) + } + + Spacer() + } + } + .onAppear { vm.startup() } + } +} + +#Preview { + ContentView() +} diff --git a/ios/ExampleiOSApp/Info.plist b/ios/ExampleiOSApp/Info.plist new file mode 100644 index 0000000..aa1ea23 --- /dev/null +++ b/ios/ExampleiOSApp/Info.plist @@ -0,0 +1,29 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ExampleiOSApp + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + UILaunchScreen + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + + + diff --git a/ios/ExampleiOSApp/TTSService.swift b/ios/ExampleiOSApp/TTSService.swift new file mode 100644 index 0000000..5d3189e --- /dev/null +++ b/ios/ExampleiOSApp/TTSService.swift @@ -0,0 +1,140 @@ +import Foundation +import OnnxRuntimeBindings + +final class TTSService { + enum Voice { case male, female } + + struct Settings { + var nTest: Int = 1 + } + + struct SynthesisResult { + let url: URL + let elapsedSeconds: Double + let audioSeconds: Double + var rtf: Double { elapsedSeconds / max(audioSeconds, 1e-6) } + } + + private let env: ORTEnv + private let textToSpeech: TextToSpeech + private let bundleOnnxDir: String + private let sampleRate: Int + + // Cached style per voice (precomputed at startup or on first use) + private var cachedStyle: [Voice: Style] = [:] + + init() throws { + bundleOnnxDir = try Self.locateOnnxDirInBundle() + env = try ORTEnv(loggingLevel: .warning) + textToSpeech = try loadTextToSpeech(bundleOnnxDir, false, env) + sampleRate = textToSpeech.sampleRate + } + + // Public warmup: precompute styles and run a quick generation to warm models + func warmup(nfe: Int = 1) async { + do { try precomputeStyle(for: .male) } catch { print("Warmup style (M) error: \(error)") } + do { try precomputeStyle(for: .female) } catch { print("Warmup style (F) error: \(error)") } + // Run a tiny synthesis to JIT/warm up kernels; discard file + do { + let res = try await synthesize(text: "Warm up", nfe: max(1, nfe), voice: .male) + try? FileManager.default.removeItem(at: res.url) + } catch { + print("Warmup synth error: \(error)") + } + } + + func synthesize(text: String, nfe: Int, voice: Voice, settings: Settings = Settings()) async throws -> SynthesisResult { + let tic = Date() + + // 1) Get or compute style for the selected voice + let style = try getStyle(voice: voice) + + // 2) Synthesize via packed TextToSpeech component + let (wav, duration) = try textToSpeech.call([text], style, nfe) + let audioSeconds = Double(duration[0]) + let wavLenSample = min(Int(Double(sampleRate) * audioSeconds), wav.count) + let wavOut = Array(wav[0.. Style { + if let style = cachedStyle[voice] { return style } + try precomputeStyle(for: voice) + return cachedStyle[voice]! + } + + // MARK: - Resource location helpers + private static func locateOnnxDirInBundle() throws -> String { + let bundle = Bundle.main + let fm = FileManager.default + + func dirHasRequiredFiles(_ dir: URL) -> Bool { + let required = [ + "tts.json", + "duration_predictor.onnx", + "text_encoder.onnx", + "vector_estimator.onnx", + "vocoder.onnx" + ] + return required.allSatisfy { fm.fileExists(atPath: dir.appendingPathComponent($0).path) } + } + + var candidates: [URL] = [] + if let dir = bundle.resourceURL?.appendingPathComponent("onnx", isDirectory: true) { candidates.append(dir) } + if let dir = bundle.resourceURL?.appendingPathComponent("assets/onnx", isDirectory: true) { candidates.append(dir) } + if let url = bundle.url(forResource: "tts", withExtension: "json", subdirectory: "onnx") { candidates.append(url.deletingLastPathComponent()) } + if let url = bundle.url(forResource: "tts", withExtension: "json", subdirectory: "assets/onnx") { candidates.append(url.deletingLastPathComponent()) } + if let url = bundle.url(forResource: "tts", withExtension: "json", subdirectory: nil) { candidates.append(url.deletingLastPathComponent()) } + if let root = bundle.resourceURL { candidates.append(root) } + + for dir in candidates { + if dirHasRequiredFiles(dir) { return dir.path } + } + throw NSError( + domain: "TTS", + code: -100, + userInfo: [NSLocalizedDescriptionKey: "Could not find the onnx directory in the bundle. Please make sure the onnx folder (as a folder reference) is included in Copy Bundle Resources in Xcode."] + ) + } + + private static func locateVoiceStyleURL(voice: Voice) throws -> URL { + // Prefer M1/F1 defaults; search common subdirectories + let fileName = (voice == .male) ? "M1" : "F1" + let bundle = Bundle.main + let candidates: [URL?] = [ + bundle.url(forResource: fileName, withExtension: "json", subdirectory: "voice_styles"), + bundle.url(forResource: fileName, withExtension: "json", subdirectory: "assets/voice_styles"), + bundle.url(forResource: fileName, withExtension: "json", subdirectory: nil) + ] + for url in candidates { + if let url = url { return url } + } + // Fallback: scan folders if needed + if let folder1 = bundle.resourceURL?.appendingPathComponent("voice_styles", isDirectory: true) { + let file = folder1.appendingPathComponent("\(fileName).json") + if FileManager.default.fileExists(atPath: file.path) { return file } + } + if let folder2 = bundle.resourceURL?.appendingPathComponent("assets/voice_styles", isDirectory: true) { + let file = folder2.appendingPathComponent("\(fileName).json") + if FileManager.default.fileExists(atPath: file.path) { return file } + } + throw NSError( + domain: "TTS", + code: -102, + userInfo: [NSLocalizedDescriptionKey: "Could not find the voice style JSON (\(fileName).json) in the bundle. Ensure voice_styles folder is included in Copy Bundle Resources."] + ) + } +} diff --git a/ios/ExampleiOSApp/TTSViewModel.swift b/ios/ExampleiOSApp/TTSViewModel.swift new file mode 100644 index 0000000..318c862 --- /dev/null +++ b/ios/ExampleiOSApp/TTSViewModel.swift @@ -0,0 +1,76 @@ +import Foundation +import AVFoundation + +@MainActor +final class TTSViewModel: ObservableObject { + @Published var text: String = "This morning, I took a walk in the park, and the sound of the birds and the breeze was so pleasant that I stopped for a long time just to listen." + @Published var nfe: Double = 5 + @Published var voice: TTSService.Voice = .male + @Published var isGenerating: Bool = false + @Published var isPlaying: Bool = false + @Published var errorMessage: String? + @Published var audioURL: URL? + + @Published var elapsedSeconds: Double? + @Published var audioSeconds: Double? + + private var service: TTSService? + private var player = AudioPlayer() + + var rtfText: String? { + guard let e = elapsedSeconds, let a = audioSeconds, a > 0 else { return nil } + let rtf = e / a + return String(format: "RTF %.2fx Β· %.2fs / %.2fs", rtf, e, a) + } + + func startup() { + do { + service = try TTSService() + Task { await self.service?.warmup(nfe: 5) } + } catch { + errorMessage = "Failed to init TTS: \(error.localizedDescription)" + } + } + + func generate() { + guard let service = service else { return } + isGenerating = true + errorMessage = nil + audioURL = nil + elapsedSeconds = nil + audioSeconds = nil + Task { + do { + let result = try await service.synthesize(text: text, nfe: Int(nfe), voice: voice) + await MainActor.run { + self.audioURL = result.url + self.elapsedSeconds = result.elapsedSeconds + self.audioSeconds = result.audioSeconds + self.isGenerating = false + } + self.play(url: result.url) + } catch { + await MainActor.run { + self.errorMessage = error.localizedDescription + self.isGenerating = false + } + } + } + } + + func togglePlay() { + if isPlaying { + player.stop() + isPlaying = false + } else if let url = audioURL { + play(url: url) + } + } + + private func play(url: URL) { + player.play(url: url) { [weak self] in + DispatchQueue.main.async { self?.isPlaying = false } + } + isPlaying = true + } +} diff --git a/ios/ExampleiOSApp/project.yml b/ios/ExampleiOSApp/project.yml new file mode 100644 index 0000000..9bef665 --- /dev/null +++ b/ios/ExampleiOSApp/project.yml @@ -0,0 +1,29 @@ +name: ExampleiOSApp +options: + minimumXcodeGenVersion: 2.37.0 +packages: + onnxruntime: + url: https://github.com/microsoft/onnxruntime-swift-package-manager.git + from: 1.16.0 +targets: + ExampleiOSApp: + type: application + platform: iOS + deploymentTarget: "15.0" + sources: + - path: . + - path: ../../swift/Sources/Helper.swift <<- μ—¬κΈ° + type: file + resources: + - path: onnx + type: folder + - path: audio + type: folder + settings: + base: + PRODUCT_BUNDLE_IDENTIFIER: com.supertonic.ExampleiOSApp + SWIFT_VERSION: 5.9 + INFOPLIST_FILE: Info.plist + dependencies: + - package: onnxruntime + product: onnxruntime diff --git a/ios/README.md b/ios/README.md new file mode 100644 index 0000000..9871218 --- /dev/null +++ b/ios/README.md @@ -0,0 +1,59 @@ +# Supertonic iOS Example App + +A minimal iOS demo that runs Supertonic (ONNX Runtime) on-device. The app shows: +- Multiline text input +- NFE (denoising steps) slider +- Voice toggle (M/F) +- Generate & Play buttons +- RTF display (Elapsed / Audio seconds) + +All ONNX models/configs are reused from `Supertonic/assets/onnx`, and voice style JSON files from `Supertonic/assets/voice_styles`. + +## Prerequisites +- macOS 13+, Xcode 15+ +- Swift 5.9+ +- iOS 15+ device (recommended) +- Homebrew, XcodeGen + +Install tools (if needed): +```bash +brew install xcodegen +``` + +## Quick Start (zero-click in Xcode) +0) Prepare assets next to the iOS target (one-time) +```bash +cd ios/ExampleiOSApp +mkdir -p onnx voice_styles +rsync -a ../../assets/onnx/ onnx/ +rsync -a ../../assets/voice_styles/ voice_styles/ +``` + +1) Generate the Xcode project with XcodeGen +```bash +xcodegen generate +open ExampleiOSApp.xcodeproj +``` + +2) Open in Xcode and select your iPhone as the run destination +- Targets β†’ ExampleiOSApp β†’ Signing & Capabilities: Select your Team +- iOS Deployment Target: 15.0+ + +3) Build & Run on device +- Type text β†’ adjust NFE/Voice β†’ Tap Generate β†’ Audio plays automatically +- An RTF line shows like: `RTF 0.30x Β· 3.04s / 10.11s` + +## What's included (generated project) +- SwiftUI app files: `App.swift`, `ContentView.swift`, `TTSViewModel.swift`, `AudioPlayer.swift` +- Runtime wrapper: `TTSService.swift` (includes TTS inference logic) +- Resources (local, vendored in `ios/ExampleiOSApp/onnx` and `ios/ExampleiOSApp/voice_styles` after step 0) + +These references are defined in `project.yml` and added to the app bundle by XcodeGen. + +## App Controls +- **Text**: Multiline `TextEditor` +- **NFE**: Denoising steps (default 5) +- **Voice**: M1/M2/F1/F2 voice style selector (4 pre-extracted styles) +- **Generate**: Runs end-to-end synthesis +- **Play/Stop**: Controls playback of the last output +- **RTF**: Shows Elapsed / Audio seconds for quick performance intuition \ No newline at end of file diff --git a/java/.gitignore b/java/.gitignore new file mode 100644 index 0000000..ba9bd99 --- /dev/null +++ b/java/.gitignore @@ -0,0 +1,35 @@ +# Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar + +# Compiled class files +*.class + +# IntelliJ IDEA +.idea/ +*.iml +*.iws +*.ipr + +# Eclipse +.classpath +.project +.settings/ + +# VS Code +.vscode/ + +# Results +results/*.wav + +# Mac +.DS_Store + diff --git a/java/ExampleONNX.java b/java/ExampleONNX.java new file mode 100644 index 0000000..ab11666 --- /dev/null +++ b/java/ExampleONNX.java @@ -0,0 +1,141 @@ +import ai.onnxruntime.*; + +import java.io.File; +import java.util.*; + +/** + * TTS Inference Example with ONNX Runtime (Java) + */ +public class ExampleONNX { + + /** + * Command line arguments + */ + static class Args { + boolean useGpu = false; + String onnxDir = "assets/onnx"; + int totalStep = 5; + int nTest = 4; + List voiceStyle = Arrays.asList("assets/voice_styles/M1.json"); + List text = Arrays.asList( + "This morning, I took a walk in the park, and the sound of the birds and the breeze was so pleasant that I stopped for a long time just to listen." + ); + String saveDir = "results"; + } + + /** + * Parse command line arguments + */ + private static Args parseArgs(String[] args) { + Args result = new Args(); + + for (int i = 0; i < args.length; i++) { + switch (args[i]) { + case "--use-gpu": + result.useGpu = true; + break; + case "--onnx-dir": + if (i + 1 < args.length) result.onnxDir = args[++i]; + break; + case "--total-step": + if (i + 1 < args.length) result.totalStep = Integer.parseInt(args[++i]); + break; + case "--n-test": + if (i + 1 < args.length) result.nTest = Integer.parseInt(args[++i]); + break; + case "--voice-style": + if (i + 1 < args.length) { + result.voiceStyle = Arrays.asList(args[++i].split(",")); + } + break; + case "--text": + if (i + 1 < args.length) { + result.text = Arrays.asList(args[++i].split("\\|")); + } + break; + case "--save-dir": + if (i + 1 < args.length) result.saveDir = args[++i]; + break; + } + } + + return result; + } + + /** + * Main inference function + */ + public static void main(String[] args) { + try { + System.out.println("=== TTS Inference with ONNX Runtime (Java) ===\n"); + + // --- 1. Parse arguments --- // + Args parsedArgs = parseArgs(args); + int totalStep = parsedArgs.totalStep; + int nTest = parsedArgs.nTest; + String saveDir = parsedArgs.saveDir; + List voiceStylePaths = parsedArgs.voiceStyle; + List textList = parsedArgs.text; + + if (voiceStylePaths.size() != textList.size()) { + throw new RuntimeException("Number of voice styles (" + voiceStylePaths.size() + + ") must match number of texts (" + textList.size() + ")"); + } + + int bsz = voiceStylePaths.size(); + OrtEnvironment env = OrtEnvironment.getEnvironment(); + + // --- 2. Load TTS components --- // + TextToSpeech textToSpeech = Helper.loadTextToSpeech(parsedArgs.onnxDir, parsedArgs.useGpu, env); + + // --- 3. Load voice styles --- // + Style style = Helper.loadVoiceStyle(voiceStylePaths, true, env); + + // --- 4. Synthesize speech --- // + File saveDirFile = new File(saveDir); + if (!saveDirFile.exists()) { + saveDirFile.mkdirs(); + } + + for (int n = 0; n < nTest; n++) { + System.out.println("\n[" + (n + 1) + "/" + nTest + "] Starting synthesis..."); + + TTSResult ttsResult = Helper.timer("Generating speech from text", () -> { + try { + return textToSpeech.call(textList, style, totalStep, env); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + + float[] wav = ttsResult.wav; + float[] duration = ttsResult.duration; + + // Save outputs + int wavLen = wav.length / bsz; + for (int i = 0; i < bsz; i++) { + String fname = Helper.sanitizeFilename(textList.get(i), 20) + "_" + (n + 1) + ".wav"; + int actualLen = (int) (textToSpeech.sampleRate * duration[i]); + + float[] wavOut = new float[actualLen]; + System.arraycopy(wav, i * wavLen, wavOut, 0, Math.min(actualLen, wavLen)); + + String outputPath = saveDir + "/" + fname; + Helper.writeWavFile(outputPath, wavOut, textToSpeech.sampleRate); + System.out.println("Saved: " + outputPath); + } + } + + // Clean up + style.close(); + textToSpeech.close(); + + System.out.println("\n=== Synthesis completed successfully! ==="); + + } catch (Exception e) { + System.err.println("Error during inference: " + e.getMessage()); + e.printStackTrace(); + System.exit(1); + } + } +} diff --git a/java/Helper.java b/java/Helper.java new file mode 100644 index 0000000..a297ab4 --- /dev/null +++ b/java/Helper.java @@ -0,0 +1,597 @@ +import ai.onnxruntime.*; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.nio.LongBuffer; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.text.Normalizer; +import java.util.*; + +/** + * Configuration classes + */ +class Config { + static class AEConfig { + int sampleRate; + int baseChunkSize; + } + + static class TTLConfig { + int chunkCompressFactor; + int latentDim; + } + + AEConfig ae; + TTLConfig ttl; +} + +/** + * Voice Style Data from JSON + */ +class VoiceStyleData { + static class StyleData { + float[][][] data; + long[] dims; + String type; + } + + StyleData styleTtl; + StyleData styleDp; +} + +/** + * Unicode text processor + */ +class UnicodeProcessor { + private long[] indexer; + + public UnicodeProcessor(String unicodeIndexerJsonPath) throws IOException { + this.indexer = Helper.loadJsonLongArray(unicodeIndexerJsonPath); + } + + public TextProcessResult call(List textList) { + List processedTexts = new ArrayList<>(); + for (String text : textList) { + processedTexts.add(preprocessText(text)); + } + + int[] textIdsLengths = new int[processedTexts.size()]; + int maxLen = 0; + for (int i = 0; i < processedTexts.size(); i++) { + textIdsLengths[i] = processedTexts.get(i).length(); + maxLen = Math.max(maxLen, textIdsLengths[i]); + } + + long[][] textIds = new long[processedTexts.size()][maxLen]; + for (int i = 0; i < processedTexts.size(); i++) { + int[] unicodeVals = textToUnicodeValues(processedTexts.get(i)); + for (int j = 0; j < unicodeVals.length; j++) { + textIds[i][j] = indexer[unicodeVals[j]]; + } + } + + float[][][] textMask = getTextMask(textIdsLengths); + return new TextProcessResult(textIds, textMask); + } + + private String preprocessText(String text) { + return Normalizer.normalize(text, Normalizer.Form.NFKD); + } + + private int[] textToUnicodeValues(String text) { + int[] values = new int[text.length()]; + for (int i = 0; i < text.length(); i++) { + values[i] = text.codePointAt(i); + } + return values; + } + + private float[][][] getTextMask(int[] lengths) { + int bsz = lengths.length; + int maxLen = 0; + for (int len : lengths) { + maxLen = Math.max(maxLen, len); + } + + float[][][] mask = new float[bsz][1][maxLen]; + for (int i = 0; i < bsz; i++) { + for (int j = 0; j < maxLen; j++) { + mask[i][0][j] = j < lengths[i] ? 1.0f : 0.0f; + } + } + return mask; + } + + static class TextProcessResult { + long[][] textIds; + float[][][] textMask; + + TextProcessResult(long[][] textIds, float[][][] textMask) { + this.textIds = textIds; + this.textMask = textMask; + } + } +} + +/** + * Text-to-Speech inference class + */ +class TextToSpeech { + private Config config; + private UnicodeProcessor textProcessor; + private OrtSession dpSession; + private OrtSession textEncSession; + private OrtSession vectorEstSession; + private OrtSession vocoderSession; + public int sampleRate; + private int baseChunkSize; + private int chunkCompress; + private int ldim; + + public TextToSpeech(Config config, UnicodeProcessor textProcessor, + OrtSession dpSession, OrtSession textEncSession, + OrtSession vectorEstSession, OrtSession vocoderSession) { + this.config = config; + this.textProcessor = textProcessor; + this.dpSession = dpSession; + this.textEncSession = textEncSession; + this.vectorEstSession = vectorEstSession; + this.vocoderSession = vocoderSession; + this.sampleRate = config.ae.sampleRate; + this.baseChunkSize = config.ae.baseChunkSize; + this.chunkCompress = config.ttl.chunkCompressFactor; + this.ldim = config.ttl.latentDim; + } + + public TTSResult call(List textList, Style style, int totalStep, OrtEnvironment env) + throws OrtException { + int bsz = textList.size(); + + // Process text + UnicodeProcessor.TextProcessResult textResult = textProcessor.call(textList); + long[][] textIds = textResult.textIds; + float[][][] textMask = textResult.textMask; + + // Create tensors + OnnxTensor textIdsTensor = Helper.createLongTensor(textIds, env); + OnnxTensor textMaskTensor = Helper.createFloatTensor(textMask, env); + + // Predict duration + Map dpInputs = new HashMap<>(); + dpInputs.put("text_ids", textIdsTensor); + dpInputs.put("style_dp", style.dpTensor); + dpInputs.put("text_mask", textMaskTensor); + + OrtSession.Result dpResult = dpSession.run(dpInputs); + Object dpValue = dpResult.get(0).getValue(); + float[] duration; + if (dpValue instanceof float[][]) { + duration = ((float[][]) dpValue)[0]; + } else { + duration = (float[]) dpValue; + } + + // Encode text + Map textEncInputs = new HashMap<>(); + textEncInputs.put("text_ids", textIdsTensor); + textEncInputs.put("style_ttl", style.ttlTensor); + textEncInputs.put("text_mask", textMaskTensor); + + OrtSession.Result textEncResult = textEncSession.run(textEncInputs); + OnnxTensor textEmbTensor = (OnnxTensor) textEncResult.get(0); + + // Sample noisy latent + NoisyLatentResult noisyLatentResult = sampleNoisyLatent(duration); + float[][][] xt = noisyLatentResult.noisyLatent; + float[][][] latentMask = noisyLatentResult.latentMask; + + // Prepare constant tensors + float[] totalStepArray = new float[bsz]; + Arrays.fill(totalStepArray, (float) totalStep); + OnnxTensor totalStepTensor = OnnxTensor.createTensor(env, totalStepArray); + + // Denoising loop + for (int step = 0; step < totalStep; step++) { + float[] currentStepArray = new float[bsz]; + Arrays.fill(currentStepArray, (float) step); + OnnxTensor currentStepTensor = OnnxTensor.createTensor(env, currentStepArray); + OnnxTensor noisyLatentTensor = Helper.createFloatTensor(xt, env); + OnnxTensor latentMaskTensor = Helper.createFloatTensor(latentMask, env); + OnnxTensor textMaskTensor2 = Helper.createFloatTensor(textMask, env); + + Map vectorEstInputs = new HashMap<>(); + vectorEstInputs.put("noisy_latent", noisyLatentTensor); + vectorEstInputs.put("text_emb", textEmbTensor); + vectorEstInputs.put("style_ttl", style.ttlTensor); + vectorEstInputs.put("latent_mask", latentMaskTensor); + vectorEstInputs.put("text_mask", textMaskTensor2); + vectorEstInputs.put("current_step", currentStepTensor); + vectorEstInputs.put("total_step", totalStepTensor); + + OrtSession.Result vectorEstResult = vectorEstSession.run(vectorEstInputs); + float[][][] denoised = (float[][][]) vectorEstResult.get(0).getValue(); + + // Update latent + xt = denoised; + + // Clean up + currentStepTensor.close(); + noisyLatentTensor.close(); + latentMaskTensor.close(); + textMaskTensor2.close(); + vectorEstResult.close(); + } + + // Generate waveform + OnnxTensor finalLatentTensor = Helper.createFloatTensor(xt, env); + Map vocoderInputs = new HashMap<>(); + vocoderInputs.put("latent", finalLatentTensor); + + OrtSession.Result vocoderResult = vocoderSession.run(vocoderInputs); + float[][] wavBatch = (float[][]) vocoderResult.get(0).getValue(); + float[] wav = wavBatch[0]; + + // Clean up + textIdsTensor.close(); + textMaskTensor.close(); + dpResult.close(); + textEncResult.close(); + totalStepTensor.close(); + finalLatentTensor.close(); + vocoderResult.close(); + + return new TTSResult(wav, duration); + } + + private NoisyLatentResult sampleNoisyLatent(float[] duration) { + int bsz = duration.length; + float maxDur = 0; + for (float d : duration) { + maxDur = Math.max(maxDur, d); + } + + long wavLenMax = (long) (maxDur * sampleRate); + long[] wavLengths = new long[bsz]; + for (int i = 0; i < bsz; i++) { + wavLengths[i] = (long) (duration[i] * sampleRate); + } + + int chunkSize = baseChunkSize * chunkCompress; + int latentLen = (int) ((wavLenMax + chunkSize - 1) / chunkSize); + int latentDim = ldim * chunkCompress; + + Random rng = new Random(); + float[][][] noisyLatent = new float[bsz][latentDim][latentLen]; + for (int b = 0; b < bsz; b++) { + for (int d = 0; d < latentDim; d++) { + for (int t = 0; t < latentLen; t++) { + // Box-Muller transform + double u1 = Math.max(1e-10, rng.nextDouble()); + double u2 = rng.nextDouble(); + noisyLatent[b][d][t] = (float) (Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2)); + } + } + } + + float[][][] latentMask = Helper.getLatentMask(wavLengths, config); + + // Apply mask + for (int b = 0; b < bsz; b++) { + for (int d = 0; d < latentDim; d++) { + for (int t = 0; t < latentLen; t++) { + noisyLatent[b][d][t] *= latentMask[b][0][t]; + } + } + } + + return new NoisyLatentResult(noisyLatent, latentMask); + } + + public void close() throws OrtException { + if (dpSession != null) dpSession.close(); + if (textEncSession != null) textEncSession.close(); + if (vectorEstSession != null) vectorEstSession.close(); + if (vocoderSession != null) vocoderSession.close(); + } +} + +/** + * Style holder class + */ +class Style { + OnnxTensor ttlTensor; + OnnxTensor dpTensor; + + Style(OnnxTensor ttlTensor, OnnxTensor dpTensor) { + this.ttlTensor = ttlTensor; + this.dpTensor = dpTensor; + } + + public void close() throws OrtException { + if (ttlTensor != null) ttlTensor.close(); + if (dpTensor != null) dpTensor.close(); + } +} + +/** + * TTS result holder + */ +class TTSResult { + float[] wav; + float[] duration; + + TTSResult(float[] wav, float[] duration) { + this.wav = wav; + this.duration = duration; + } +} + +/** + * Noisy latent result holder + */ +class NoisyLatentResult { + float[][][] noisyLatent; + float[][][] latentMask; + + NoisyLatentResult(float[][][] noisyLatent, float[][][] latentMask) { + this.noisyLatent = noisyLatent; + this.latentMask = latentMask; + } +} + +/** + * Helper utility class + */ +public class Helper { + + /** + * Load voice style from JSON files + */ + public static Style loadVoiceStyle(List voiceStylePaths, boolean verbose, OrtEnvironment env) + throws IOException, OrtException { + int bsz = voiceStylePaths.size(); + + // Read first file to get dimensions + ObjectMapper mapper = new ObjectMapper(); + JsonNode firstRoot = mapper.readTree(new File(voiceStylePaths.get(0))); + + long[] ttlDims = new long[3]; + for (int i = 0; i < 3; i++) { + ttlDims[i] = firstRoot.get("style_ttl").get("dims").get(i).asLong(); + } + long[] dpDims = new long[3]; + for (int i = 0; i < 3; i++) { + dpDims[i] = firstRoot.get("style_dp").get("dims").get(i).asLong(); + } + + long ttlDim1 = ttlDims[1]; + long ttlDim2 = ttlDims[2]; + long dpDim1 = dpDims[1]; + long dpDim2 = dpDims[2]; + + // Pre-allocate arrays with full batch size + int ttlSize = (int) (bsz * ttlDim1 * ttlDim2); + int dpSize = (int) (bsz * dpDim1 * dpDim2); + float[] ttlFlat = new float[ttlSize]; + float[] dpFlat = new float[dpSize]; + + // Fill in the data + for (int i = 0; i < bsz; i++) { + JsonNode root = mapper.readTree(new File(voiceStylePaths.get(i))); + + // Flatten TTL data + int ttlOffset = (int) (i * ttlDim1 * ttlDim2); + int idx = 0; + JsonNode ttlData = root.get("style_ttl").get("data"); + for (JsonNode batch : ttlData) { + for (JsonNode row : batch) { + for (JsonNode val : row) { + ttlFlat[ttlOffset + idx++] = (float) val.asDouble(); + } + } + } + + // Flatten DP data + int dpOffset = (int) (i * dpDim1 * dpDim2); + idx = 0; + JsonNode dpData = root.get("style_dp").get("data"); + for (JsonNode batch : dpData) { + for (JsonNode row : batch) { + for (JsonNode val : row) { + dpFlat[dpOffset + idx++] = (float) val.asDouble(); + } + } + } + } + + long[] ttlShape = {bsz, ttlDim1, ttlDim2}; + long[] dpShape = {bsz, dpDim1, dpDim2}; + + OnnxTensor ttlTensor = OnnxTensor.createTensor(env, FloatBuffer.wrap(ttlFlat), ttlShape); + OnnxTensor dpTensor = OnnxTensor.createTensor(env, FloatBuffer.wrap(dpFlat), dpShape); + + if (verbose) { + System.out.println("Loaded " + bsz + " voice styles\n"); + } + + return new Style(ttlTensor, dpTensor); + } + + /** + * Load TTS components + */ + public static TextToSpeech loadTextToSpeech(String onnxDir, boolean useGpu, OrtEnvironment env) + throws IOException, OrtException { + if (useGpu) { + throw new RuntimeException("GPU mode is not supported yet"); + } + System.out.println("Using CPU for inference\n"); + + // Load config + Config config = loadCfgs(onnxDir); + + // Create session options + OrtSession.SessionOptions opts = new OrtSession.SessionOptions(); + + // Load models + OrtSession dpSession = env.createSession(onnxDir + "/duration_predictor.onnx", opts); + OrtSession textEncSession = env.createSession(onnxDir + "/text_encoder.onnx", opts); + OrtSession vectorEstSession = env.createSession(onnxDir + "/vector_estimator.onnx", opts); + OrtSession vocoderSession = env.createSession(onnxDir + "/vocoder.onnx", opts); + + // Load text processor + UnicodeProcessor textProcessor = new UnicodeProcessor(onnxDir + "/unicode_indexer.json"); + + return new TextToSpeech(config, textProcessor, dpSession, textEncSession, vectorEstSession, vocoderSession); + } + + /** + * Load configuration from JSON + */ + public static Config loadCfgs(String onnxDir) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + JsonNode root = mapper.readTree(new File(onnxDir + "/tts.json")); + + Config config = new Config(); + config.ae = new Config.AEConfig(); + config.ae.sampleRate = root.get("ae").get("sample_rate").asInt(); + config.ae.baseChunkSize = root.get("ae").get("base_chunk_size").asInt(); + + config.ttl = new Config.TTLConfig(); + config.ttl.chunkCompressFactor = root.get("ttl").get("chunk_compress_factor").asInt(); + config.ttl.latentDim = root.get("ttl").get("latent_dim").asInt(); + + return config; + } + + /** + * Get latent mask from wav lengths + */ + public static float[][][] getLatentMask(long[] wavLengths, Config config) { + long baseChunkSize = config.ae.baseChunkSize; + long chunkCompressFactor = config.ttl.chunkCompressFactor; + long latentSize = baseChunkSize * chunkCompressFactor; + + long[] latentLengths = new long[wavLengths.length]; + long maxLen = 0; + for (int i = 0; i < wavLengths.length; i++) { + latentLengths[i] = (wavLengths[i] + latentSize - 1) / latentSize; + maxLen = Math.max(maxLen, latentLengths[i]); + } + + float[][][] mask = new float[wavLengths.length][1][(int) maxLen]; + for (int i = 0; i < wavLengths.length; i++) { + for (int j = 0; j < maxLen; j++) { + mask[i][0][j] = j < latentLengths[i] ? 1.0f : 0.0f; + } + } + return mask; + } + + /** + * Write WAV file + */ + public static void writeWavFile(String filename, float[] audioData, int sampleRate) throws IOException { + // Convert float to byte array + byte[] bytes = new byte[audioData.length * 2]; + ByteBuffer buffer = ByteBuffer.wrap(bytes); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + for (float sample : audioData) { + short val = (short) Math.max(-32768, Math.min(32767, sample * 32767)); + buffer.putShort(val); + } + + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + AudioFormat format = new AudioFormat(sampleRate, 16, 1, true, false); + AudioInputStream ais = new AudioInputStream(bais, format, audioData.length); + AudioSystem.write(ais, AudioFileFormat.Type.WAVE, new File(filename)); + } + + /** + * Sanitize filename + */ + public static String sanitizeFilename(String text, int maxLen) { + if (text.length() > maxLen) { + text = text.substring(0, maxLen); + } + return text.replaceAll("[^a-zA-Z0-9]", "_"); + } + + /** + * Timer utility + */ + public static T timer(String name, java.util.function.Supplier fn) { + long start = System.currentTimeMillis(); + System.out.println(name + "..."); + T result = fn.get(); + long elapsed = System.currentTimeMillis() - start; + System.out.printf(" -> %s completed in %.2f sec\n", name, elapsed / 1000.0); + return result; + } + + /** + * Create float tensor from 3D array + */ + public static OnnxTensor createFloatTensor(float[][][] array, OrtEnvironment env) throws OrtException { + int dim0 = array.length; + int dim1 = array[0].length; + int dim2 = array[0][0].length; + + float[] flat = new float[dim0 * dim1 * dim2]; + int idx = 0; + for (int i = 0; i < dim0; i++) { + for (int j = 0; j < dim1; j++) { + for (int k = 0; k < dim2; k++) { + flat[idx++] = array[i][j][k]; + } + } + } + + long[] shape = {dim0, dim1, dim2}; + return OnnxTensor.createTensor(env, FloatBuffer.wrap(flat), shape); + } + + /** + * Create long tensor from 2D array + */ + public static OnnxTensor createLongTensor(long[][] array, OrtEnvironment env) throws OrtException { + int dim0 = array.length; + int dim1 = array[0].length; + + long[] flat = new long[dim0 * dim1]; + int idx = 0; + for (int i = 0; i < dim0; i++) { + for (int j = 0; j < dim1; j++) { + flat[idx++] = array[i][j]; + } + } + + long[] shape = {dim0, dim1}; + return OnnxTensor.createTensor(env, LongBuffer.wrap(flat), shape); + } + + /** + * Load JSON long array + */ + public static long[] loadJsonLongArray(String filePath) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + JsonNode root = mapper.readTree(new File(filePath)); + + long[] result = new long[root.size()]; + for (int i = 0; i < root.size(); i++) { + result[i] = root.get(i).asLong(); + } + return result; + } +} + diff --git a/java/README.md b/java/README.md new file mode 100644 index 0000000..cf09896 --- /dev/null +++ b/java/README.md @@ -0,0 +1,97 @@ +# TTS ONNX Inference Examples + +This guide provides examples for running TTS inference using `ExampleONNX.java`. + +## Installation + +This project uses [Maven](https://maven.apache.org/) for dependency management. + +### Prerequisites + +- Java 11 or higher +- Maven 3.6 or higher + +### Install dependencies + +```bash +mvn clean install +``` + +## Basic Usage + +### Example 1: Default Inference +Run inference with default settings: +```bash +mvn exec:java +``` + +This will use: +- Voice style: `assets/voice_styles/M1.json` +- Text: "This morning, I took a walk in the park, and the sound of the birds and the breeze was so pleasant that I stopped for a long time just to listen." +- Output directory: `results/` +- Total steps: 5 +- Number of generations: 4 + +### Example 2: Batch Inference +Process multiple voice styles and texts at once: +```bash +mvn exec:java -Dexec.args="--voice-style assets/voice_styles/M1.json,assets/voice_styles/F1.json --text 'The sun sets behind the mountains, painting the sky in shades of pink and orange.|The weather is beautiful and sunny outside. A gentle breeze makes the air feel fresh and pleasant.'" +``` + +This will: +- Generate speech for 2 different voice-text pairs +- Use male voice (M1.json) for the first text +- Use female voice (F1.json) for the second text +- Process both samples in a single batch + +### Example 3: High Quality Inference +Increase denoising steps for better quality: +```bash +mvn exec:java -Dexec.args="--total-step 10 --voice-style assets/voice_styles/M1.json --text 'Increasing the number of denoising steps improves the output fidelity and overall quality.'" +``` + +This will: +- Use 10 denoising steps instead of the default 5 +- Produce higher quality output at the cost of slower inference + +**Note**: If your text contains apostrophes, use escaping or run the JAR directly: +```bash +java -jar target/tts-example.jar --total-step 10 --text "Text with apostrophe's here" +``` + +## Building a Fat JAR + +To create a standalone JAR with all dependencies: +```bash +mvn clean package +``` + +Then run it directly: +```bash +java -jar target/tts-example.jar +``` + +Or with arguments: +```bash +java -jar target/tts-example.jar --total-step 10 --text "Your custom text here" +``` + +## Available Arguments + +| Argument | Type | Default | Description | +|----------|------|---------|-------------| +| `--use-gpu` | flag | False | Use GPU for inference (default: CPU) | +| `--onnx-dir` | str | `assets/onnx` | Path to ONNX model directory | +| `--total-step` | int | 5 | Number of denoising steps (higher = better quality, slower) | +| `--n-test` | int | 4 | Number of times to generate each sample | +| `--voice-style` | str+ | `assets/voice_styles/M1.json` | Voice style file path(s) | +| `--text` | str+ | (long default text) | Text(s) to synthesize | +| `--save-dir` | str | `results` | Output directory | + +## Notes + +- **Batch Processing**: The number of `--voice-style` files must match the number of `--text` entries +- **Quality vs Speed**: Higher `--total-step` values produce better quality but take longer +- **GPU Support**: GPU mode is not supported yet +- **Voice Styles**: Uses pre-extracted voice style JSON files for fast inference + diff --git a/java/assets b/java/assets new file mode 120000 index 0000000..ec2e4be --- /dev/null +++ b/java/assets @@ -0,0 +1 @@ +../assets \ No newline at end of file diff --git a/java/pom.xml b/java/pom.xml new file mode 100644 index 0000000..f99d57e --- /dev/null +++ b/java/pom.xml @@ -0,0 +1,110 @@ + + + 4.0.0 + + ai.supertonic + tts-onnx-java + 1.0.0 + jar + + TTS ONNX Java Example + Text-to-Speech inference using ONNX Runtime in Java + + + UTF-8 + 11 + 11 + 1.23.1 + 2.15.2 + + + + + + com.microsoft.onnxruntime + onnxruntime + ${onnxruntime.version} + + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + + + com.github.wendykierp + JTransforms + 3.1 + + + + + . + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 11 + 11 + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + ExampleONNX + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + + ExampleONNX + + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.0 + + + package + + shade + + + + + ExampleONNX + + + tts-example + + + + + + + + diff --git a/nodejs/README.md b/nodejs/README.md new file mode 100644 index 0000000..f8fb9a9 --- /dev/null +++ b/nodejs/README.md @@ -0,0 +1,102 @@ +# TTS ONNX Node.js Implementation + +Node.js implementation for TTS inference. Uses ONNX Runtime to generate speech from text. + +## Requirements + +- Node.js v16 or higher +- npm or yarn + +## Installation + +```bash +cd nodejs +npm install +``` + +## Basic Usage + +### Example 1: Default Inference +Run inference with default settings: +```bash +npm start +``` + +Or: +```bash +node example_onnx.js +``` + +This will use: +- Voice style: `assets/voice_styles/M1.json` +- Text: "This morning, I took a walk in the park, and the sound of the birds and the breeze was so pleasant that I stopped for a long time just to listen." +- Output directory: `results/` +- Total steps: 5 +- Number of generations: 4 + +### Example 2: Batch Inference +Process multiple voice styles and texts at once: +```bash +node example_onnx.js \ + --voice-style "assets/voice_styles/M1.json,assets/voice_styles/F1.json" \ + --text "The sun sets behind the mountains, painting the sky in shades of pink and orange.|The weather is beautiful and sunny outside. A gentle breeze makes the air feel fresh and pleasant." +``` + +This will: +- Generate speech for 2 different voice-text pairs +- Use male voice style (M1.json) for the first text +- Use female voice style (F1.json) for the second text +- Process both samples in a single batch + +### Example 3: High Quality Inference +Increase denoising steps for better quality: +```bash +node example_onnx.js \ + --total-step 10 \ + --voice-style "assets/voice_styles/M1.json" \ + --text "Increasing the number of denoising steps improves the output's fidelity and overall quality." +``` + +This will: +- Use 10 denoising steps instead of the default 5 +- Produce higher quality output at the cost of slower inference + +## Available Arguments + +| Argument | Type | Default | Description | +|----------|------|---------|-------------| +| `--use-gpu` | flag | False | Use GPU for inference (not supported yet) | +| `--onnx-dir` | str | `assets/onnx` | Path to ONNX model directory | +| `--total-step` | int | 5 | Number of denoising steps (higher = better quality, slower) | +| `--n-test` | int | 4 | Number of times to generate each sample | +| `--voice-style` | str+ | `assets/voice_styles/M1.json` | Voice style file path(s). Separate multiple files with commas | +| `--text` | str+ | (long default text) | Text(s) to synthesize. Separate multiple texts with pipes | +| `--save-dir` | str | `results` | Output directory | + +## Notes + +- **Batch Processing**: The number of voice style files must match the number of texts. Use commas to separate files and pipes to separate texts +- **Quality vs Speed**: Higher `--total-step` values produce better quality but take longer +- **GPU Support**: GPU mode is not supported yet + +## Architecture + +- `helper.js`: Node.js port of Python's `helper.py` + - `Preprocessor`: Audio preprocessing (STFT, Mel Spectrogram) + - `UnicodeProcessor`: Text preprocessing + - Utility functions (mask generation, tensor conversion, etc.) + +- `example_onnx.js`: Main inference script + - ONNX model loading + - TTS inference pipeline execution + - WAV file saving + +- `package.json`: Node.js project configuration and dependencies + +## Implementation Notes + +1. **Pure Node.js WAV Processing**: Writes WAV files without external native libraries. Outputs 16-bit PCM format. + +2. **Memory Efficiency**: Note that Node.js may consume significant memory when processing large arrays. + +3. **Performance**: The mel spectrogram extraction (Step 1-1) is currently slower than Python's Librosa, which uses highly optimized C extensions. This bottleneck could be further improved with additional optimizations such as WASM-based FFT libraries or native addons. diff --git a/nodejs/assets b/nodejs/assets new file mode 120000 index 0000000..ec2e4be --- /dev/null +++ b/nodejs/assets @@ -0,0 +1 @@ +../assets \ No newline at end of file diff --git a/nodejs/example_onnx.js b/nodejs/example_onnx.js new file mode 100644 index 0000000..47087b5 --- /dev/null +++ b/nodejs/example_onnx.js @@ -0,0 +1,104 @@ +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +import { loadTextToSpeech, loadVoiceStyle, timer, writeWavFile } from './helper.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +/** + * Parse command line arguments + */ +function parseArgs() { + const args = { + useGpu: false, + onnxDir: 'assets/onnx', + totalStep: 5, + nTest: 4, + voiceStyle: ['assets/voice_styles/M1.json'], + text: ['This morning, I took a walk in the park, and the sound of the birds and the breeze was so pleasant that I stopped for a long time just to listen.'], + saveDir: 'results' + }; + + for (let i = 2; i < process.argv.length; i++) { + const arg = process.argv[i]; + if (arg === '--use-gpu') { + args.useGpu = true; + } else if (arg === '--onnx-dir' && i + 1 < process.argv.length) { + args.onnxDir = process.argv[++i]; + } else if (arg === '--total-step' && i + 1 < process.argv.length) { + args.totalStep = parseInt(process.argv[++i]); + } else if (arg === '--n-test' && i + 1 < process.argv.length) { + args.nTest = parseInt(process.argv[++i]); + } else if (arg === '--voice-style' && i + 1 < process.argv.length) { + args.voiceStyle = process.argv[++i].split(','); + } else if (arg === '--text' && i + 1 < process.argv.length) { + args.text = process.argv[++i].split('|'); + } else if (arg === '--save-dir' && i + 1 < process.argv.length) { + args.saveDir = process.argv[++i]; + } + } + + return args; +} + +/** + * Main inference function + */ +async function main() { + console.log('=== TTS Inference with ONNX Runtime (Node.js) ===\n'); + + // --- 1. Parse arguments --- // + const args = parseArgs(); + const totalStep = args.totalStep; + const nTest = args.nTest; + const saveDir = args.saveDir; + const voiceStylePaths = args.voiceStyle.map(p => path.resolve(__dirname, p)); + const textList = args.text; + + if (voiceStylePaths.length !== textList.length) { + throw new Error(`Number of voice styles (${voiceStylePaths.length}) must match number of texts (${textList.length})`); + } + + const bsz = voiceStylePaths.length; + + // --- 2. Load Text to Speech --- // + const onnxDir = path.resolve(__dirname, args.onnxDir); + const textToSpeech = await loadTextToSpeech(onnxDir, args.useGpu); + + // --- 3. Load Voice Style --- // + const style = loadVoiceStyle(voiceStylePaths, true); + + // --- 4. Synthesize speech --- // + for (let n = 0; n < nTest; n++) { + console.log(`\n[${n + 1}/${nTest}] Starting synthesis...`); + + const { wav, duration } = await timer('Generating speech from text', async () => { + return await textToSpeech.call(textList, style, totalStep); + }); + + if (!fs.existsSync(saveDir)) { + fs.mkdirSync(saveDir, { recursive: true }); + } + + const wavShape = [bsz, wav.length / bsz]; + for (let b = 0; b < bsz; b++) { + const fname = `${textList[b].substring(0, 20).replace(/[^a-zA-Z0-9]/g, '_')}_${n + 1}.wav`; + const wavLen = Math.floor(textToSpeech.sampleRate * duration[b]); + const wavOut = wav.slice(b * wavShape[1], b * wavShape[1] + wavLen); + + const outputPath = path.join(saveDir, fname); + writeWavFile(outputPath, wavOut, textToSpeech.sampleRate); + console.log(`Saved: ${outputPath}`); + } + } + + console.log('\n=== Synthesis completed successfully! ==='); +} + +// Run main function +main().catch(err => { + console.error('Error during inference:', err); + process.exit(1); +}); diff --git a/nodejs/helper.js b/nodejs/helper.js new file mode 100644 index 0000000..1028604 --- /dev/null +++ b/nodejs/helper.js @@ -0,0 +1,392 @@ +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import * as ort from 'onnxruntime-node'; + +const __filename = fileURLToPath(import.meta.url); + +/** + * Unicode text processor + */ +class UnicodeProcessor { + constructor(unicodeIndexerJsonPath) { + this.indexer = JSON.parse(fs.readFileSync(unicodeIndexerJsonPath, 'utf8')); + } + + _preprocessText(text) { + // Simple NFKD normalization (JavaScript has normalize built-in) + return text.normalize('NFKD'); + } + + _textToUnicodeValues(text) { + return Array.from(text).map(char => char.charCodeAt(0)); + } + + _getTextMask(textIdsLengths) { + return lengthToMask(textIdsLengths); + } + + call(textList) { + const processedTexts = textList.map(t => this._preprocessText(t)); + const textIdsLengths = processedTexts.map(t => t.length); + const maxLen = Math.max(...textIdsLengths); + + const textIds = []; + for (let i = 0; i < processedTexts.length; i++) { + const row = new Array(maxLen).fill(0); + const unicodeVals = this._textToUnicodeValues(processedTexts[i]); + for (let j = 0; j < unicodeVals.length; j++) { + row[j] = this.indexer[unicodeVals[j]]; + } + textIds.push(row); + } + + const textMask = this._getTextMask(textIdsLengths); + return { textIds, textMask }; + } +} + +/** + * Style class + */ +class Style { + constructor(styleTtlOnnx, styleDpOnnx) { + this.ttl = styleTtlOnnx; + this.dp = styleDpOnnx; + } +} + +/** + * TextToSpeech class + */ +class TextToSpeech { + constructor(cfgs, textProcessor, dpOrt, textEncOrt, vectorEstOrt, vocoderOrt) { + this.cfgs = cfgs; + this.textProcessor = textProcessor; + this.dpOrt = dpOrt; + this.textEncOrt = textEncOrt; + this.vectorEstOrt = vectorEstOrt; + this.vocoderOrt = vocoderOrt; + this.sampleRate = cfgs.ae.sample_rate; + this.baseChunkSize = cfgs.ae.base_chunk_size; + this.chunkCompressFactor = cfgs.ttl.chunk_compress_factor; + this.ldim = cfgs.ttl.latent_dim; + } + + sampleNoisyLatent(duration) { + const wavLenMax = Math.max(...duration) * this.sampleRate; + const wavLengths = duration.map(d => Math.floor(d * this.sampleRate)); + const chunkSize = this.baseChunkSize * this.chunkCompressFactor; + const latentLen = Math.floor((wavLenMax + chunkSize - 1) / chunkSize); + const latentDim = this.ldim * this.chunkCompressFactor; + + // Generate random noise + const noisyLatent = []; + for (let b = 0; b < duration.length; b++) { + const batch = []; + for (let d = 0; d < latentDim; d++) { + const row = []; + for (let t = 0; t < latentLen; t++) { + // Box-Muller transform for normal distribution + // Add epsilon to avoid log(0) + const eps = 1e-10; + const u1 = Math.max(eps, Math.random()); + const u2 = Math.random(); + const randNormal = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2); + row.push(randNormal); + } + batch.push(row); + } + noisyLatent.push(batch); + } + + const latentMask = getLatentMask(wavLengths, this.baseChunkSize, this.chunkCompressFactor); + + // Apply mask + for (let b = 0; b < noisyLatent.length; b++) { + for (let d = 0; d < noisyLatent[b].length; d++) { + for (let t = 0; t < noisyLatent[b][d].length; t++) { + noisyLatent[b][d][t] *= latentMask[b][0][t]; + } + } + } + + return { noisyLatent, latentMask }; + } + + async call(textList, style, totalStep) { + if (textList.length !== style.ttl.dims[0]) { + throw new Error('Number of texts must match number of style vectors'); + } + const bsz = textList.length; + const { textIds, textMask } = this.textProcessor.call(textList); + const textIdsShape = [bsz, textIds[0].length]; + const textMaskShape = [bsz, 1, textMask[0][0].length]; + + const textMaskTensor = arrayToTensor(textMask, textMaskShape); + + const dpResult = await this.dpOrt.run({ + text_ids: intArrayToTensor(textIds, textIdsShape), + style_dp: style.dp, + text_mask: textMaskTensor + }); + + const durOnnx = Array.from(dpResult.duration.data); + + const textEncResult = await this.textEncOrt.run({ + text_ids: intArrayToTensor(textIds, textIdsShape), + style_ttl: style.ttl, + text_mask: textMaskTensor + }); + + const textEmbTensor = textEncResult.text_emb; + + let { noisyLatent, latentMask } = this.sampleNoisyLatent(durOnnx); + const latentShape = [bsz, noisyLatent[0].length, noisyLatent[0][0].length]; + const latentMaskShape = [bsz, 1, latentMask[0][0].length]; + + const latentMaskTensor = arrayToTensor(latentMask, latentMaskShape); + + const totalStepArray = new Array(bsz).fill(totalStep); + const scalarShape = [bsz]; + const totalStepTensor = arrayToTensor(totalStepArray, scalarShape); + + for (let step = 0; step < totalStep; step++) { + const currentStepArray = new Array(bsz).fill(step); + + const vectorEstResult = await this.vectorEstOrt.run({ + noisy_latent: arrayToTensor(noisyLatent, latentShape), + text_emb: textEmbTensor, + style_ttl: style.ttl, + text_mask: textMaskTensor, + latent_mask: latentMaskTensor, + total_step: totalStepTensor, + current_step: arrayToTensor(currentStepArray, scalarShape) + }); + + const denoisedLatent = Array.from(vectorEstResult.denoised_latent.data); + + // Update latent with the denoised output + let idx = 0; + for (let b = 0; b < noisyLatent.length; b++) { + for (let d = 0; d < noisyLatent[b].length; d++) { + for (let t = 0; t < noisyLatent[b][d].length; t++) { + noisyLatent[b][d][t] = denoisedLatent[idx++]; + } + } + } + } + + const vocoderResult = await this.vocoderOrt.run({ + latent: arrayToTensor(noisyLatent, latentShape) + }); + + const wav = Array.from(vocoderResult.wav_tts.data); + return { wav, duration: durOnnx }; + } +} + +/** + * Convert lengths to binary mask + */ +function lengthToMask(lengths, maxLen = null) { + maxLen = maxLen || Math.max(...lengths); + const mask = []; + for (let i = 0; i < lengths.length; i++) { + const row = []; + for (let j = 0; j < maxLen; j++) { + row.push(j < lengths[i] ? 1.0 : 0.0); + } + mask.push([row]); // [B, 1, maxLen] + } + return mask; +} + +/** + * Get latent mask from wav lengths + */ +function getLatentMask(wavLengths, baseChunkSize, chunkCompressFactor) { + const latentSize = baseChunkSize * chunkCompressFactor; + const latentLengths = wavLengths.map(len => + Math.floor((len + latentSize - 1) / latentSize) + ); + return lengthToMask(latentLengths); +} + +/** + * Load ONNX model + */ +async function loadOnnx(onnxPath, opts) { + return await ort.InferenceSession.create(onnxPath, opts); +} + +/** + * Load all ONNX models for TTS + */ +async function loadOnnxAll(onnxDir, opts) { + const dpPath = path.join(onnxDir, 'duration_predictor.onnx'); + const textEncPath = path.join(onnxDir, 'text_encoder.onnx'); + const vectorEstPath = path.join(onnxDir, 'vector_estimator.onnx'); + const vocoderPath = path.join(onnxDir, 'vocoder.onnx'); + + const [dpOrt, textEncOrt, vectorEstOrt, vocoderOrt] = await Promise.all([ + loadOnnx(dpPath, opts), + loadOnnx(textEncPath, opts), + loadOnnx(vectorEstPath, opts), + loadOnnx(vocoderPath, opts) + ]); + + return { dpOrt, textEncOrt, vectorEstOrt, vocoderOrt }; +} + +/** + * Load configuration + */ +function loadCfgs(onnxDir) { + const cfgPath = path.join(onnxDir, 'tts.json'); + const cfgs = JSON.parse(fs.readFileSync(cfgPath, 'utf8')); + return cfgs; +} + +/** + * Load text processor + */ +function loadTextProcessor(onnxDir) { + const unicodeIndexerPath = path.join(onnxDir, 'unicode_indexer.json'); + const textProcessor = new UnicodeProcessor(unicodeIndexerPath); + return textProcessor; +} + +/** + * Load voice style from JSON file + */ +export function loadVoiceStyle(voiceStylePaths, verbose = false) { + const bsz = voiceStylePaths.length; + + // Read first file to get dimensions + const firstStyle = JSON.parse(fs.readFileSync(voiceStylePaths[0], 'utf8')); + const ttlDims = firstStyle.style_ttl.dims; + const dpDims = firstStyle.style_dp.dims; + + const ttlDim1 = ttlDims[1]; + const ttlDim2 = ttlDims[2]; + const dpDim1 = dpDims[1]; + const dpDim2 = dpDims[2]; + + // Pre-allocate arrays with full batch size + const ttlSize = bsz * ttlDim1 * ttlDim2; + const dpSize = bsz * dpDim1 * dpDim2; + const ttlFlat = new Float32Array(ttlSize); + const dpFlat = new Float32Array(dpSize); + + // Fill in the data + for (let i = 0; i < bsz; i++) { + const voiceStyle = JSON.parse(fs.readFileSync(voiceStylePaths[i], 'utf8')); + + const ttlData = voiceStyle.style_ttl.data.flat(Infinity); + const ttlOffset = i * ttlDim1 * ttlDim2; + ttlFlat.set(ttlData, ttlOffset); + + const dpData = voiceStyle.style_dp.data.flat(Infinity); + const dpOffset = i * dpDim1 * dpDim2; + dpFlat.set(dpData, dpOffset); + } + + const ttlStyle = new ort.Tensor('float32', ttlFlat, [bsz, ttlDim1, ttlDim2]); + const dpStyle = new ort.Tensor('float32', dpFlat, [bsz, dpDim1, dpDim2]); + + if (verbose) { + console.log(`Loaded ${bsz} voice styles`); + } + + return new Style(ttlStyle, dpStyle); +} + +/** + * Load text to speech components + */ +export async function loadTextToSpeech(onnxDir, useGpu = false) { + const opts = {}; + if (useGpu) { + throw new Error('GPU mode is not supported yet'); + } else { + console.log('Using CPU for inference'); + } + + const cfgs = loadCfgs(onnxDir); + const { dpOrt, textEncOrt, vectorEstOrt, vocoderOrt } = await loadOnnxAll(onnxDir, opts); + const textProcessor = loadTextProcessor(onnxDir); + const textToSpeech = new TextToSpeech(cfgs, textProcessor, dpOrt, textEncOrt, vectorEstOrt, vocoderOrt); + + return textToSpeech; +} + +/** + * Convert 3D array to ONNX tensor + */ +function arrayToTensor(array, dims) { + // Flatten the array + const flat = array.flat(Infinity); + return new ort.Tensor('float32', Float32Array.from(flat), dims); +} + +/** + * Convert 2D int array to ONNX tensor + */ +function intArrayToTensor(array, dims) { + const flat = array.flat(Infinity); + return new ort.Tensor('int64', BigInt64Array.from(flat.map(x => BigInt(x))), dims); +} + +/** + * Write WAV file + */ +export function writeWavFile(filename, audioData, sampleRate) { + const numChannels = 1; + const bitsPerSample = 16; + const byteRate = sampleRate * numChannels * bitsPerSample / 8; + const blockAlign = numChannels * bitsPerSample / 8; + const dataSize = audioData.length * bitsPerSample / 8; + + const buffer = Buffer.alloc(44 + dataSize); + + // RIFF header + buffer.write('RIFF', 0); + buffer.writeUInt32LE(36 + dataSize, 4); + buffer.write('WAVE', 8); + + // fmt chunk + buffer.write('fmt ', 12); + buffer.writeUInt32LE(16, 16); // fmt chunk size + buffer.writeUInt16LE(1, 20); // audio format (PCM) + buffer.writeUInt16LE(numChannels, 22); + buffer.writeUInt32LE(sampleRate, 24); + buffer.writeUInt32LE(byteRate, 28); + buffer.writeUInt16LE(blockAlign, 32); + buffer.writeUInt16LE(bitsPerSample, 34); + + // data chunk + buffer.write('data', 36); + buffer.writeUInt32LE(dataSize, 40); + + // Write audio data + for (let i = 0; i < audioData.length; i++) { + const sample = Math.max(-1, Math.min(1, audioData[i])); + const intSample = Math.floor(sample * 32767); + buffer.writeInt16LE(intSample, 44 + i * 2); + } + + fs.writeFileSync(filename, buffer); +} + +/** + * Timer utility for measuring execution time + */ +export async function timer(name, fn) { + const start = Date.now(); + console.log(`${name}...`); + const result = await fn(); + const elapsed = ((Date.now() - start) / 1000).toFixed(2); + console.log(` -> ${name} completed in ${elapsed} sec`); + return result; +} diff --git a/nodejs/package.json b/nodejs/package.json new file mode 100644 index 0000000..a117c04 --- /dev/null +++ b/nodejs/package.json @@ -0,0 +1,26 @@ +{ + "name": "tts-onnx-nodejs", + "version": "1.0.0", + "description": "TTS inference using ONNX Runtime for Node.js", + "main": "example_onnx.js", + "type": "module", + "scripts": { + "start": "node example_onnx.js" + }, + "keywords": [ + "tts", + "onnx", + "speech-synthesis", + "nodejs" + ], + "author": "", + "license": "MIT", + "dependencies": { + "fft.js": "^4.0.3", + "js-yaml": "^4.1.0", + "onnxruntime-node": "^1.19.2" + }, + "engines": { + "node": ">=16.0.0" + } +} diff --git a/py/README.md b/py/README.md new file mode 100644 index 0000000..ce116fd --- /dev/null +++ b/py/README.md @@ -0,0 +1,83 @@ +# TTS ONNX Inference Examples + +This guide provides examples for running TTS inference using `example_onnx.py`. + +## Installation + +This project uses [uv](https://docs.astral.sh/uv/) for fast package management. + +### Install uv (if not already installed) +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +``` + +### Install dependencies +```bash +uv sync +``` + +Or if you prefer using traditional pip with requirements.txt: +```bash +pip install -r requirements.txt +``` + +## Basic Usage + +### Example 1: Default Inference +Run inference with default settings: +```bash +uv run example_onnx.py +``` + +This will use: +- Voice style: `assets/voice_styles/M1.json` +- Text: "This morning, I took a walk in the park, and the sound of the birds and the breeze was so pleasant that I stopped for a long time just to listen." +- Output directory: `results/` +- Total steps: 5 +- Number of generations: 4 + +### Example 2: Batch Inference +Process multiple voice styles and texts at once: +```bash +uv run example_onnx.py \ + --voice-style assets/voice_styles/M1.json assets/voice_styles/F1.json \ + --text "The sun sets behind the mountains, painting the sky in shades of pink and orange." "The weather is beautiful and sunny outside. A gentle breeze makes the air feel fresh and pleasant." +``` + +This will: +- Generate speech for 2 different voice-text pairs +- Use male voice style (M1.json) for the first text +- Use female voice style (F1.json) for the second text +- Process both samples in a single batch + +### Example 3: High Quality Inference +Increase denoising steps for better quality: +```bash +uv run example_onnx.py \ + --total-step 10 \ + --voice-style assets/voice_styles/M1.json \ + --text "Increasing the number of denoising steps improves the output's fidelity and overall quality." +``` + +This will: +- Use 10 denoising steps instead of the default 5 +- Produce higher quality output at the cost of slower inference + +## Available Arguments + +| Argument | Type | Default | Description | +|----------|------|---------|-------------| +| `--use-gpu` | flag | False | Use GPU for inference (with CPU fallback) | +| `--onnx-dir` | str | `assets/onnx` | Path to ONNX model directory | +| `--total-step` | int | 5 | Number of denoising steps (higher = better quality, slower) | +| `--n-test` | int | 4 | Number of times to generate each sample | +| `--voice-style` | str+ | `assets/voice_styles/M1.json` | Voice style file path(s) | +| `--text` | str+ | (long default text) | Text(s) to synthesize | +| `--save-dir` | str | `results` | Output directory | + +## Notes + +- **Batch Processing**: The number of `--voice-style` files must match the number of `--text` entries +- **Quality vs Speed**: Higher `--total-step` values produce better quality but take longer +- **GPU Support**: GPU mode is not supported yet + diff --git a/py/assets b/py/assets new file mode 120000 index 0000000..ec2e4be --- /dev/null +++ b/py/assets @@ -0,0 +1 @@ +../assets \ No newline at end of file diff --git a/py/example_onnx.py b/py/example_onnx.py new file mode 100644 index 0000000..37d3a2d --- /dev/null +++ b/py/example_onnx.py @@ -0,0 +1,91 @@ +import argparse +import os + +import soundfile as sf + +from helper import load_text_to_speech, timer, sanitize_filename, load_voice_style + + +def parse_args(): + parser = argparse.ArgumentParser(description="TTS Inference with ONNX") + + # Device settings + parser.add_argument( + "--use-gpu", action="store_true", help="Use GPU for inference (default: CPU)" + ) + + # Model settings + parser.add_argument( + "--onnx-dir", + type=str, + default="assets/onnx", + help="Path to ONNX model directory", + ) + + # Synthesis parameters + parser.add_argument( + "--total-step", type=int, default=5, help="Number of denoising steps" + ) + parser.add_argument( + "--n-test", type=int, default=4, help="Number of times to generate" + ) + + # Input/Output + parser.add_argument( + "--voice-style", + type=str, + nargs="+", + default=["assets/voice_styles/M1.json"], + help="Voice style file path(s). Can specify multiple files for batch processing", + ) + parser.add_argument( + "--text", + type=str, + nargs="+", + default=[ + "This morning, I took a walk in the park, and the sound of the birds and the breeze was so pleasant that I stopped for a long time just to listen." + ], + help="Text(s) to synthesize. Can specify multiple texts for batch processing", + ) + parser.add_argument( + "--save-dir", type=str, default="results", help="Output directory" + ) + + return parser.parse_args() + + +print("=== TTS Inference with ONNX Runtime (Python) ===\n") + +# --- 1. Parse arguments --- # +args = parse_args() +total_step = args.total_step +n_test = args.n_test +save_dir = args.save_dir +voice_style_paths = args.voice_style +text_list = args.text + +assert len(voice_style_paths) == len( + text_list +), f"Number of voice styles ({len(voice_style_paths)}) must match number of texts ({len(text_list)})" + +bsz = len(voice_style_paths) + +# --- 2. Load Text to Speech --- # +text_to_speech = load_text_to_speech(args.onnx_dir, args.use_gpu) + +# --- 3. Load Voice Style --- # +style = load_voice_style(voice_style_paths, verbose=True) + +# --- 4. Synthesize Speech --- # +for n in range(n_test): + print(f"\n[{n+1}/{n_test}] Starting synthesis...") + with timer("Generating speech from text"): + wav, duration = text_to_speech(text_list, style, total_step) + if not os.path.exists(save_dir): + os.makedirs(save_dir) + for b in range(bsz): + fname = f"{sanitize_filename(text_list[b], 20)}_{n+1}.wav" + w = wav[b, : int(text_to_speech.sample_rate * duration[b].item())] # [T_trim] + sf.write(os.path.join(save_dir, fname), w, text_to_speech.sample_rate) + print(f"Saved: {save_dir}/{fname}") +print("\n=== Synthesis completed successfully! ===") diff --git a/py/helper.py b/py/helper.py new file mode 100644 index 0000000..b390f81 --- /dev/null +++ b/py/helper.py @@ -0,0 +1,249 @@ +import json +import os +import time +from contextlib import contextmanager +from typing import Optional +from unicodedata import normalize + +import numpy as np +import onnxruntime as ort + + +class UnicodeProcessor: + def __init__(self, unicode_indexer_path: str): + with open(unicode_indexer_path, "r") as f: + self.indexer = json.load(f) + + def _preprocess_text(self, text: str) -> str: + # TODO: add more preprocessing + text = normalize("NFKD", text) + return text + + def _get_text_mask(self, text_ids_lengths: np.ndarray) -> np.ndarray: + text_mask = length_to_mask(text_ids_lengths) + return text_mask + + def _text_to_unicode_values(self, text: str) -> np.ndarray: + unicode_values = np.array( + [ord(char) for char in text], dtype=np.uint16 + ) # 2 bytes + return unicode_values + + def __call__(self, text_list: list[str]) -> tuple[np.ndarray, np.ndarray]: + text_list = [self._preprocess_text(t) for t in text_list] + text_ids_lengths = np.array([len(text) for text in text_list], dtype=np.int64) + text_ids = np.zeros((len(text_list), text_ids_lengths.max()), dtype=np.int64) + for i, text in enumerate(text_list): + unicode_vals = self._text_to_unicode_values(text) + text_ids[i, : len(unicode_vals)] = np.array( + [self.indexer[val] for val in unicode_vals], dtype=np.int64 + ) + text_mask = self._get_text_mask(text_ids_lengths) + return text_ids, text_mask + + +class Style: + def __init__(self, style_ttl_onnx: np.ndarray, style_dp_onnx: np.ndarray): + self.ttl = style_ttl_onnx + self.dp = style_dp_onnx + + +class TextToSpeech: + def __init__( + self, + cfgs: dict, + text_processor: UnicodeProcessor, + dp_ort: ort.InferenceSession, + text_enc_ort: ort.InferenceSession, + vector_est_ort: ort.InferenceSession, + vocoder_ort: ort.InferenceSession, + ): + self.cfgs = cfgs + self.text_processor = text_processor + self.dp_ort = dp_ort + self.text_enc_ort = text_enc_ort + self.vector_est_ort = vector_est_ort + self.vocoder_ort = vocoder_ort + self.sample_rate = cfgs["ae"]["sample_rate"] + self.base_chunk_size = cfgs["ae"]["base_chunk_size"] + self.chunk_compress_factor = cfgs["ttl"]["chunk_compress_factor"] + self.ldim = cfgs["ttl"]["latent_dim"] + + def sample_noisy_latent( + self, duration: np.ndarray + ) -> tuple[np.ndarray, np.ndarray]: + bsz = len(duration) + wav_len_max = duration.max() * self.sample_rate + wav_lengths = (duration * self.sample_rate).astype(np.int64) + chunk_size = self.base_chunk_size * self.chunk_compress_factor + latent_len = ((wav_len_max + chunk_size - 1) / chunk_size).astype(np.int32) + latent_dim = self.ldim * self.chunk_compress_factor + noisy_latent = np.random.randn(bsz, latent_dim, latent_len).astype(np.float32) + latent_mask = get_latent_mask( + wav_lengths, self.base_chunk_size, self.chunk_compress_factor + ) + noisy_latent = noisy_latent * latent_mask + return noisy_latent, latent_mask + + def __call__( + self, text_list: list[str], style: Style, total_step: int + ) -> tuple[np.ndarray, np.ndarray]: + assert ( + len(text_list) == style.ttl.shape[0] + ), "Number of texts must match number of style vectors" + bsz = len(text_list) + text_ids, text_mask = self.text_processor(text_list) + dur_onnx, *_ = self.dp_ort.run( + None, {"text_ids": text_ids, "style_dp": style.dp, "text_mask": text_mask} + ) + text_emb_onnx, *_ = self.text_enc_ort.run( + None, + {"text_ids": text_ids, "style_ttl": style.ttl, "text_mask": text_mask}, + ) # dur_onnx: [bsz] + xt, latent_mask = self.sample_noisy_latent(dur_onnx) + total_step_np = np.array([total_step] * bsz, dtype=np.float32) + for step in range(total_step): + current_step = np.array([step] * bsz, dtype=np.float32) + xt, *_ = self.vector_est_ort.run( + None, + { + "noisy_latent": xt, + "text_emb": text_emb_onnx, + "style_ttl": style.ttl, + "text_mask": text_mask, + "latent_mask": latent_mask, + "current_step": current_step, + "total_step": total_step_np, + }, + ) + wav, *_ = self.vocoder_ort.run(None, {"latent": xt}) + return wav, dur_onnx + + +def length_to_mask(lengths: np.ndarray, max_len: Optional[int] = None) -> np.ndarray: + """ + Convert lengths to binary mask. + + Args: + lengths: (B,) + max_len: int + + Returns: + mask: (B, 1, max_len) + """ + max_len = max_len or lengths.max() + ids = np.arange(0, max_len) + mask = (ids < np.expand_dims(lengths, axis=1)).astype(np.float32) + return mask.reshape(-1, 1, max_len) + + +def get_latent_mask( + wav_lengths: np.ndarray, base_chunk_size: int, chunk_compress_factor: int +) -> np.ndarray: + latent_size = base_chunk_size * chunk_compress_factor + latent_lengths = (wav_lengths + latent_size - 1) // latent_size + latent_mask = length_to_mask(latent_lengths) + return latent_mask + + +def load_onnx( + onnx_path: str, opts: ort.SessionOptions, providers: list[str] +) -> ort.InferenceSession: + return ort.InferenceSession(onnx_path, sess_options=opts, providers=providers) + + +def load_onnx_all( + onnx_dir: str, opts: ort.SessionOptions, providers: list[str] +) -> tuple[ + ort.InferenceSession, + ort.InferenceSession, + ort.InferenceSession, + ort.InferenceSession, +]: + dp_onnx_path = os.path.join(onnx_dir, "duration_predictor.onnx") + text_enc_onnx_path = os.path.join(onnx_dir, "text_encoder.onnx") + vector_est_onnx_path = os.path.join(onnx_dir, "vector_estimator.onnx") + vocoder_onnx_path = os.path.join(onnx_dir, "vocoder.onnx") + + dp_ort = load_onnx(dp_onnx_path, opts, providers) + text_enc_ort = load_onnx(text_enc_onnx_path, opts, providers) + vector_est_ort = load_onnx(vector_est_onnx_path, opts, providers) + vocoder_ort = load_onnx(vocoder_onnx_path, opts, providers) + return dp_ort, text_enc_ort, vector_est_ort, vocoder_ort + + +def load_cfgs(onnx_dir: str) -> dict: + cfg_path = os.path.join(onnx_dir, "tts.json") + with open(cfg_path, "r") as f: + cfgs = json.load(f) + return cfgs + + +def load_text_processor(onnx_dir: str) -> UnicodeProcessor: + unicode_indexer_path = os.path.join(onnx_dir, "unicode_indexer.json") + text_processor = UnicodeProcessor(unicode_indexer_path) + return text_processor + + +def load_text_to_speech(onnx_dir: str, use_gpu: bool = False) -> TextToSpeech: + opts = ort.SessionOptions() + if use_gpu: + raise NotImplementedError("GPU mode is not fully tested") + else: + providers = ["CPUExecutionProvider"] + print("Using CPU for inference") + cfgs = load_cfgs(onnx_dir) + dp_ort, text_enc_ort, vector_est_ort, vocoder_ort = load_onnx_all( + onnx_dir, opts, providers + ) + text_processor = load_text_processor(onnx_dir) + return TextToSpeech( + cfgs, text_processor, dp_ort, text_enc_ort, vector_est_ort, vocoder_ort + ) + + +def load_voice_style(voice_style_paths: list[str], verbose: bool = False) -> Style: + bsz = len(voice_style_paths) + + # Read first file to get dimensions + with open(voice_style_paths[0], "r") as f: + first_style = json.load(f) + ttl_dims = first_style["style_ttl"]["dims"] + dp_dims = first_style["style_dp"]["dims"] + + # Pre-allocate arrays with full batch size + ttl_style = np.zeros([bsz, ttl_dims[1], ttl_dims[2]], dtype=np.float32) + dp_style = np.zeros([bsz, dp_dims[1], dp_dims[2]], dtype=np.float32) + + # Fill in the data + for i, voice_style_path in enumerate(voice_style_paths): + with open(voice_style_path, "r") as f: + voice_style = json.load(f) + + ttl_data = np.array( + voice_style["style_ttl"]["data"], dtype=np.float32 + ).flatten() + ttl_style[i] = ttl_data.reshape(ttl_dims[1], ttl_dims[2]) + + dp_data = np.array(voice_style["style_dp"]["data"], dtype=np.float32).flatten() + dp_style[i] = dp_data.reshape(dp_dims[1], dp_dims[2]) + + if verbose: + print(f"Loaded {bsz} voice styles") + return Style(ttl_style, dp_style) + + +@contextmanager +def timer(name: str): + start = time.time() + print(f"{name}...") + yield + print(f" -> {name} completed in {time.time() - start:.2f} sec") + + +def sanitize_filename(text: str, max_len: int) -> str: + """Sanitize filename by replacing non-alphanumeric characters with underscores""" + import re + + prefix = text[:max_len] + return re.sub(r"[^a-zA-Z0-9]", "_", prefix) diff --git a/py/pyproject.toml b/py/pyproject.toml new file mode 100644 index 0000000..09aa437 --- /dev/null +++ b/py/pyproject.toml @@ -0,0 +1,20 @@ +[project] +name = "tts-onnx" +version = "1.0.0" +description = "TTS ONNX Inference" +requires-python = ">=3.10" +dependencies = [ + "onnxruntime==1.23.1", + "numpy>=1.26.0", + "soundfile>=0.12.1", + "librosa>=0.10.0", + "PyYAML>=6.0", +] + +[tool.setuptools] +py-modules = [] + +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + diff --git a/py/requirements.txt b/py/requirements.txt new file mode 100644 index 0000000..5f7d78b --- /dev/null +++ b/py/requirements.txt @@ -0,0 +1,5 @@ +onnxruntime==1.23.1 +numpy>=1.26.0 +soundfile>=0.12.1 +librosa>=0.10.0 +PyYAML>=6.0 diff --git a/py/uv.lock b/py/uv.lock new file mode 100644 index 0000000..9130dd2 --- /dev/null +++ b/py/uv.lock @@ -0,0 +1,1142 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version >= '3.11' and python_full_version < '3.13'", + "python_full_version < '3.11'", +] + +[[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" }, + { url = "https://files.pythonhosted.org/packages/5c/73/413b5a2804091e2c7d5def1d618e4837f1cb82464e230f827226278556b7/audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f9ee9b52f5f857fbaf9d605a360884f034c92c1c23021fb90b2e39b8e64bede6", size = 47104, upload-time = "2025-08-05T16:42:58.518Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/daa3308dc6593944410c2c68306a5e217f5c05b70a12e70228e7dd42dc5c/audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:49ee1a41738a23e98d98b937a0638357a2477bc99e61b0f768a8f654f45d9b7a", size = 27754, upload-time = "2025-08-05T16:43:00.132Z" }, + { url = "https://files.pythonhosted.org/packages/4e/86/c2e0f627168fcf61781a8f72cab06b228fe1da4b9fa4ab39cfb791b5836b/audioop_lts-0.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5b00be98ccd0fc123dcfad31d50030d25fcf31488cde9e61692029cd7394733b", size = 27332, upload-time = "2025-08-05T16:43:01.666Z" }, + { url = "https://files.pythonhosted.org/packages/c7/bd/35dce665255434f54e5307de39e31912a6f902d4572da7c37582809de14f/audioop_lts-0.2.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6d2e0f9f7a69403e388894d4ca5ada5c47230716a03f2847cfc7bd1ecb589d6", size = 92396, upload-time = "2025-08-05T16:43:02.991Z" }, + { url = "https://files.pythonhosted.org/packages/2d/d2/deeb9f51def1437b3afa35aeb729d577c04bcd89394cb56f9239a9f50b6f/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9b0b8a03ef474f56d1a842af1a2e01398b8f7654009823c6d9e0ecff4d5cfbf", size = 91811, upload-time = "2025-08-05T16:43:04.096Z" }, + { url = "https://files.pythonhosted.org/packages/76/3b/09f8b35b227cee28cc8231e296a82759ed80c1a08e349811d69773c48426/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2b267b70747d82125f1a021506565bdc5609a2b24bcb4773c16d79d2bb260bbd", size = 100483, upload-time = "2025-08-05T16:43:05.085Z" }, + { url = "https://files.pythonhosted.org/packages/0b/15/05b48a935cf3b130c248bfdbdea71ce6437f5394ee8533e0edd7cfd93d5e/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0337d658f9b81f4cd0fdb1f47635070cc084871a3d4646d9de74fdf4e7c3d24a", size = 103885, upload-time = "2025-08-05T16:43:06.197Z" }, + { url = "https://files.pythonhosted.org/packages/83/80/186b7fce6d35b68d3d739f228dc31d60b3412105854edb975aa155a58339/audioop_lts-0.2.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:167d3b62586faef8b6b2275c3218796b12621a60e43f7e9d5845d627b9c9b80e", size = 84899, upload-time = "2025-08-05T16:43:07.291Z" }, + { url = "https://files.pythonhosted.org/packages/49/89/c78cc5ac6cb5828f17514fb12966e299c850bc885e80f8ad94e38d450886/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0d9385e96f9f6da847f4d571ce3cb15b5091140edf3db97276872647ce37efd7", size = 89998, upload-time = "2025-08-05T16:43:08.335Z" }, + { url = "https://files.pythonhosted.org/packages/4c/4b/6401888d0c010e586c2ca50fce4c903d70a6bb55928b16cfbdfd957a13da/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:48159d96962674eccdca9a3df280e864e8ac75e40a577cc97c5c42667ffabfc5", size = 99046, upload-time = "2025-08-05T16:43:09.367Z" }, + { url = "https://files.pythonhosted.org/packages/de/f8/c874ca9bb447dae0e2ef2e231f6c4c2b0c39e31ae684d2420b0f9e97ee68/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8fefe5868cd082db1186f2837d64cfbfa78b548ea0d0543e9b28935ccce81ce9", size = 84843, upload-time = "2025-08-05T16:43:10.749Z" }, + { url = "https://files.pythonhosted.org/packages/3e/c0/0323e66f3daebc13fd46b36b30c3be47e3fc4257eae44f1e77eb828c703f/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:58cf54380c3884fb49fdd37dfb7a772632b6701d28edd3e2904743c5e1773602", size = 94490, upload-time = "2025-08-05T16:43:12.131Z" }, + { url = "https://files.pythonhosted.org/packages/98/6b/acc7734ac02d95ab791c10c3f17ffa3584ccb9ac5c18fd771c638ed6d1f5/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:088327f00488cdeed296edd9215ca159f3a5a5034741465789cad403fcf4bec0", size = 92297, upload-time = "2025-08-05T16:43:13.139Z" }, + { url = "https://files.pythonhosted.org/packages/13/c3/c3dc3f564ce6877ecd2a05f8d751b9b27a8c320c2533a98b0c86349778d0/audioop_lts-0.2.2-cp314-cp314t-win32.whl", hash = "sha256:068aa17a38b4e0e7de771c62c60bbca2455924b67a8814f3b0dee92b5820c0b3", size = 27331, upload-time = "2025-08-05T16:43:14.19Z" }, + { url = "https://files.pythonhosted.org/packages/72/bb/b4608537e9ffcb86449091939d52d24a055216a36a8bf66b936af8c3e7ac/audioop_lts-0.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a5bf613e96f49712073de86f20dbdd4014ca18efd4d34ed18c75bd808337851b", size = 31697, upload-time = "2025-08-05T16:43:15.193Z" }, + { url = "https://files.pythonhosted.org/packages/f6/22/91616fe707a5c5510de2cac9b046a30defe7007ba8a0c04f9c08f27df312/audioop_lts-0.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:b492c3b040153e68b9fdaff5913305aaaba5bb433d8a7f73d5cf6a64ed3cc1dd", size = 25206, upload-time = "2025-08-05T16:43:16.444Z" }, +] + +[[package]] +name = "audioread" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/d2/87016ca9f083acadffb2d8da59bfa3253e4da7eeb9f71fb8e7708dc97ecd/audioread-3.0.1.tar.gz", hash = "sha256:ac5460a5498c48bdf2e8e767402583a4dcd13f4414d286f42ce4379e8b35066d", size = 116513, upload-time = "2023-09-27T19:27:53.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/8d/30aa32745af16af0a9a650115fbe81bde7c610ed5c21b381fca0196f3a7f/audioread-3.0.1-py3-none-any.whl", hash = "sha256:4cdce70b8adc0da0a3c9e0d85fb10b3ace30fbdf8d1670fd443929b61d117c33", size = 23492, upload-time = "2023-09-27T19:27:51.334Z" }, +] + +[[package]] +name = "certifi" +version = "2025.10.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, + { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, + { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, + { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, + { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, + { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, + { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, + { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, + { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, + { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, + { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, + { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, + { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, + { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, + { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, + { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, + { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, + { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "coloredlogs" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "humanfriendly" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520, upload-time = "2021-06-11T10:22:45.202Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018, upload-time = "2021-06-11T10:22:42.561Z" }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, +] + +[[package]] +name = "flatbuffers" +version = "25.9.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/1f/3ee70b0a55137442038f2a33469cc5fddd7e0ad2abf83d7497c18a2b6923/flatbuffers-25.9.23.tar.gz", hash = "sha256:676f9fa62750bb50cf531b42a0a2a118ad8f7f797a511eda12881c016f093b12", size = 22067, upload-time = "2025-09-24T05:25:30.106Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/1b/00a78aa2e8fbd63f9af08c9c19e6deb3d5d66b4dda677a0f61654680ee89/flatbuffers-25.9.23-py2.py3-none-any.whl", hash = "sha256:255538574d6cb6d0a79a17ec8bc0d30985913b87513a01cce8bcdb6b4c44d0e2", size = 30869, upload-time = "2025-09-24T05:25:28.912Z" }, +] + +[[package]] +name = "humanfriendly" +version = "10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyreadline3", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702, upload-time = "2021-09-17T21:40:43.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794, upload-time = "2021-09-17T21:40:39.897Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "joblib" +version = "1.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/5d/447af5ea094b9e4c4054f82e223ada074c552335b9b4b2d14bd9b35a67c4/joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55", size = 331077, upload-time = "2025-08-27T12:15:46.575Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/e8/685f47e0d754320684db4425a0967f7d3fa70126bffd76110b7009a0090f/joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241", size = 308396, upload-time = "2025-08-27T12:15:45.188Z" }, +] + +[[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", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pooch" }, + { name = "scikit-learn" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.16.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { 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.45.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/99/8d/5baf1cef7f9c084fb35a8afbde88074f0d6a727bc63ef764fe0e7543ba40/llvmlite-0.45.1.tar.gz", hash = "sha256:09430bb9d0bb58fc45a45a57c7eae912850bedc095cd0810a57de109c69e1c32", size = 185600, upload-time = "2025-10-01T17:59:52.046Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/6d/585c84ddd9d2a539a3c3487792b3cf3f988e28ec4fa281bf8b0e055e1166/llvmlite-0.45.1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:1b1af0c910af0978aa55fa4f60bbb3e9f39b41e97c2a6d94d199897be62ba07a", size = 43043523, upload-time = "2025-10-01T18:02:58.621Z" }, + { url = "https://files.pythonhosted.org/packages/ae/34/992bd12d3ff245e0801bcf6013961daa8c19c9b9c2e61cb4b8bce94566f9/llvmlite-0.45.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02a164db2d79088bbd6e0d9633b4fe4021d6379d7e4ac7cc85ed5f44b06a30c5", size = 37253122, upload-time = "2025-10-01T18:03:55.159Z" }, + { url = "https://files.pythonhosted.org/packages/a6/7b/6d7585998a5991fa74dc925aae57913ba8c7c2efff909de9d34cc1cd3c27/llvmlite-0.45.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f2d47f34e4029e6df3395de34cc1c66440a8d72712993a6e6168db228686711b", size = 56288210, upload-time = "2025-10-01T18:00:41.978Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e2/a4abea058633bfc82eb08fd69ce242c118fdb9b0abad1fdcbe0bc6aedab5/llvmlite-0.45.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7319e5f9f90720578a7f56fbc805bdfb4bc071b507c7611f170d631c3c0f1e0", size = 55140958, upload-time = "2025-10-01T18:01:55.694Z" }, + { url = "https://files.pythonhosted.org/packages/74/c0/233468e96ed287b953239c3b24b1d69df47c6ba9262bfdca98eda7e83a04/llvmlite-0.45.1-cp310-cp310-win_amd64.whl", hash = "sha256:4edb62e685867799e336723cb9787ec6598d51d0b1ed9af0f38e692aa757e898", size = 38132232, upload-time = "2025-10-01T18:04:41.538Z" }, + { url = "https://files.pythonhosted.org/packages/04/ad/9bdc87b2eb34642c1cfe6bcb4f5db64c21f91f26b010f263e7467e7536a3/llvmlite-0.45.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:60f92868d5d3af30b4239b50e1717cb4e4e54f6ac1c361a27903b318d0f07f42", size = 43043526, upload-time = "2025-10-01T18:03:15.051Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ea/c25c6382f452a943b4082da5e8c1665ce29a62884e2ec80608533e8e82d5/llvmlite-0.45.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98baab513e19beb210f1ef39066288784839a44cd504e24fff5d17f1b3cf0860", size = 37253118, upload-time = "2025-10-01T18:04:06.783Z" }, + { url = "https://files.pythonhosted.org/packages/fe/af/85fc237de98b181dbbe8647324331238d6c52a3554327ccdc83ced28efba/llvmlite-0.45.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3adc2355694d6a6fbcc024d59bb756677e7de506037c878022d7b877e7613a36", size = 56288209, upload-time = "2025-10-01T18:01:00.168Z" }, + { url = "https://files.pythonhosted.org/packages/0a/df/3daf95302ff49beff4230065e3178cd40e71294968e8d55baf4a9e560814/llvmlite-0.45.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f3377a6db40f563058c9515dedcc8a3e562d8693a106a28f2ddccf2c8fcf6ca", size = 55140958, upload-time = "2025-10-01T18:02:11.199Z" }, + { url = "https://files.pythonhosted.org/packages/a4/56/4c0d503fe03bac820ecdeb14590cf9a248e120f483bcd5c009f2534f23f0/llvmlite-0.45.1-cp311-cp311-win_amd64.whl", hash = "sha256:f9c272682d91e0d57f2a76c6d9ebdfccc603a01828cdbe3d15273bdca0c3363a", size = 38132232, upload-time = "2025-10-01T18:04:52.181Z" }, + { url = "https://files.pythonhosted.org/packages/e2/7c/82cbd5c656e8991bcc110c69d05913be2229302a92acb96109e166ae31fb/llvmlite-0.45.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:28e763aba92fe9c72296911e040231d486447c01d4f90027c8e893d89d49b20e", size = 43043524, upload-time = "2025-10-01T18:03:30.666Z" }, + { url = "https://files.pythonhosted.org/packages/9d/bc/5314005bb2c7ee9f33102c6456c18cc81745d7055155d1218f1624463774/llvmlite-0.45.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1a53f4b74ee9fd30cb3d27d904dadece67a7575198bd80e687ee76474620735f", size = 37253123, upload-time = "2025-10-01T18:04:18.177Z" }, + { url = "https://files.pythonhosted.org/packages/96/76/0f7154952f037cb320b83e1c952ec4a19d5d689cf7d27cb8a26887d7bbc1/llvmlite-0.45.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b3796b1b1e1c14dcae34285d2f4ea488402fbd2c400ccf7137603ca3800864f", size = 56288211, upload-time = "2025-10-01T18:01:24.079Z" }, + { url = "https://files.pythonhosted.org/packages/00/b1/0b581942be2683ceb6862d558979e87387e14ad65a1e4db0e7dd671fa315/llvmlite-0.45.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:779e2f2ceefef0f4368548685f0b4adde34e5f4b457e90391f570a10b348d433", size = 55140958, upload-time = "2025-10-01T18:02:30.482Z" }, + { url = "https://files.pythonhosted.org/packages/33/94/9ba4ebcf4d541a325fd8098ddc073b663af75cc8b065b6059848f7d4dce7/llvmlite-0.45.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e6c9949baf25d9aa9cd7cf0f6d011b9ca660dd17f5ba2b23bdbdb77cc86b116", size = 38132231, upload-time = "2025-10-01T18:05:03.664Z" }, + { url = "https://files.pythonhosted.org/packages/1d/e2/c185bb7e88514d5025f93c6c4092f6120c6cea8fe938974ec9860fb03bbb/llvmlite-0.45.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:d9ea9e6f17569a4253515cc01dade70aba536476e3d750b2e18d81d7e670eb15", size = 43043524, upload-time = "2025-10-01T18:03:43.249Z" }, + { url = "https://files.pythonhosted.org/packages/09/b8/b5437b9ecb2064e89ccf67dccae0d02cd38911705112dd0dcbfa9cd9a9de/llvmlite-0.45.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:c9f3cadee1630ce4ac18ea38adebf2a4f57a89bd2740ce83746876797f6e0bfb", size = 37253121, upload-time = "2025-10-01T18:04:30.557Z" }, + { url = "https://files.pythonhosted.org/packages/f7/97/ad1a907c0173a90dd4df7228f24a3ec61058bc1a9ff8a0caec20a0cc622e/llvmlite-0.45.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:57c48bf2e1083eedbc9406fb83c4e6483017879714916fe8be8a72a9672c995a", size = 56288210, upload-time = "2025-10-01T18:01:40.26Z" }, + { url = "https://files.pythonhosted.org/packages/32/d8/c99c8ac7a326e9735401ead3116f7685a7ec652691aeb2615aa732b1fc4a/llvmlite-0.45.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3aa3dfceda4219ae39cf18806c60eeb518c1680ff834b8b311bd784160b9ce40", size = 55140957, upload-time = "2025-10-01T18:02:46.244Z" }, + { url = "https://files.pythonhosted.org/packages/09/56/ed35668130e32dbfad2eb37356793b0a95f23494ab5be7d9bf5cb75850ee/llvmlite-0.45.1-cp313-cp313-win_amd64.whl", hash = "sha256:080e6f8d0778a8239cd47686d402cb66eb165e421efa9391366a9b7e5810a38b", size = 38132232, upload-time = "2025-10-01T18:05:14.477Z" }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, +] + +[[package]] +name = "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/f5/a2/3b68a9e769db68668b25c6108444a35f9bd163bb848c0650d516761a59c0/msgpack-1.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0051fffef5a37ca2cd16978ae4f0aef92f164df86823871b5162812bebecd8e2", size = 81318, upload-time = "2025-10-08T09:14:38.722Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e1/2b720cc341325c00be44e1ed59e7cfeae2678329fbf5aa68f5bda57fe728/msgpack-1.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a605409040f2da88676e9c9e5853b3449ba8011973616189ea5ee55ddbc5bc87", size = 83786, upload-time = "2025-10-08T09:14:40.082Z" }, + { url = "https://files.pythonhosted.org/packages/71/e5/c2241de64bfceac456b140737812a2ab310b10538a7b34a1d393b748e095/msgpack-1.1.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b696e83c9f1532b4af884045ba7f3aa741a63b2bc22617293a2c6a7c645f251", size = 398240, upload-time = "2025-10-08T09:14:41.151Z" }, + { url = "https://files.pythonhosted.org/packages/b7/09/2a06956383c0fdebaef5aa9246e2356776f12ea6f2a44bd1368abf0e46c4/msgpack-1.1.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:365c0bbe981a27d8932da71af63ef86acc59ed5c01ad929e09a0b88c6294e28a", size = 406070, upload-time = "2025-10-08T09:14:42.821Z" }, + { url = "https://files.pythonhosted.org/packages/0e/74/2957703f0e1ef20637d6aead4fbb314330c26f39aa046b348c7edcf6ca6b/msgpack-1.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:41d1a5d875680166d3ac5c38573896453bbbea7092936d2e107214daf43b1d4f", size = 393403, upload-time = "2025-10-08T09:14:44.38Z" }, + { url = "https://files.pythonhosted.org/packages/a5/09/3bfc12aa90f77b37322fc33e7a8a7c29ba7c8edeadfa27664451801b9860/msgpack-1.1.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:354e81bcdebaab427c3df4281187edc765d5d76bfb3a7c125af9da7a27e8458f", size = 398947, upload-time = "2025-10-08T09:14:45.56Z" }, + { url = "https://files.pythonhosted.org/packages/4b/4f/05fcebd3b4977cb3d840f7ef6b77c51f8582086de5e642f3fefee35c86fc/msgpack-1.1.2-cp310-cp310-win32.whl", hash = "sha256:e64c8d2f5e5d5fda7b842f55dec6133260ea8f53c4257d64494c534f306bf7a9", size = 64769, upload-time = "2025-10-08T09:14:47.334Z" }, + { url = "https://files.pythonhosted.org/packages/d0/3e/b4547e3a34210956382eed1c85935fff7e0f9b98be3106b3745d7dec9c5e/msgpack-1.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:db6192777d943bdaaafb6ba66d44bf65aa0e9c5616fa1d2da9bb08828c6b39aa", size = 71293, upload-time = "2025-10-08T09:14:48.665Z" }, + { 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" }, + { url = "https://files.pythonhosted.org/packages/22/71/201105712d0a2ff07b7873ed3c220292fb2ea5120603c00c4b634bcdafb3/msgpack-1.1.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e23ce8d5f7aa6ea6d2a2b326b4ba46c985dbb204523759984430db7114f8aa00", size = 81127, upload-time = "2025-10-08T09:15:24.408Z" }, + { url = "https://files.pythonhosted.org/packages/1b/9f/38ff9e57a2eade7bf9dfee5eae17f39fc0e998658050279cbb14d97d36d9/msgpack-1.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6c15b7d74c939ebe620dd8e559384be806204d73b4f9356320632d783d1f7939", size = 84981, upload-time = "2025-10-08T09:15:25.812Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a9/3536e385167b88c2cc8f4424c49e28d49a6fc35206d4a8060f136e71f94c/msgpack-1.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99e2cb7b9031568a2a5c73aa077180f93dd2e95b4f8d3b8e14a73ae94a9e667e", size = 411885, upload-time = "2025-10-08T09:15:27.22Z" }, + { url = "https://files.pythonhosted.org/packages/2f/40/dc34d1a8d5f1e51fc64640b62b191684da52ca469da9cd74e84936ffa4a6/msgpack-1.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:180759d89a057eab503cf62eeec0aa61c4ea1200dee709f3a8e9397dbb3b6931", size = 419658, upload-time = "2025-10-08T09:15:28.4Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ef/2b92e286366500a09a67e03496ee8b8ba00562797a52f3c117aa2b29514b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:04fb995247a6e83830b62f0b07bf36540c213f6eac8e851166d8d86d83cbd014", size = 403290, upload-time = "2025-10-08T09:15:29.764Z" }, + { url = "https://files.pythonhosted.org/packages/78/90/e0ea7990abea5764e4655b8177aa7c63cdfa89945b6e7641055800f6c16b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8e22ab046fa7ede9e36eeb4cfad44d46450f37bb05d5ec482b02868f451c95e2", size = 415234, upload-time = "2025-10-08T09:15:31.022Z" }, + { url = "https://files.pythonhosted.org/packages/72/4e/9390aed5db983a2310818cd7d3ec0aecad45e1f7007e0cda79c79507bb0d/msgpack-1.1.2-cp314-cp314-win32.whl", hash = "sha256:80a0ff7d4abf5fecb995fcf235d4064b9a9a8a40a3ab80999e6ac1e30b702717", size = 66391, upload-time = "2025-10-08T09:15:32.265Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f1/abd09c2ae91228c5f3998dbd7f41353def9eac64253de3c8105efa2082f7/msgpack-1.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:9ade919fac6a3e7260b7f64cea89df6bec59104987cbea34d34a2fa15d74310b", size = 73787, upload-time = "2025-10-08T09:15:33.219Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b0/9d9f667ab48b16ad4115c1935d94023b82b3198064cb84a123e97f7466c1/msgpack-1.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:59415c6076b1e30e563eb732e23b994a61c159cec44deaf584e5cc1dd662f2af", size = 66453, upload-time = "2025-10-08T09:15:34.225Z" }, + { url = "https://files.pythonhosted.org/packages/16/67/93f80545eb1792b61a217fa7f06d5e5cb9e0055bed867f43e2b8e012e137/msgpack-1.1.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:897c478140877e5307760b0ea66e0932738879e7aa68144d9b78ea4c8302a84a", size = 85264, upload-time = "2025-10-08T09:15:35.61Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/33c8a24959cf193966ef11a6f6a2995a65eb066bd681fd085afd519a57ce/msgpack-1.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a668204fa43e6d02f89dbe79a30b0d67238d9ec4c5bd8a940fc3a004a47b721b", size = 89076, upload-time = "2025-10-08T09:15:36.619Z" }, + { url = "https://files.pythonhosted.org/packages/fc/6b/62e85ff7193663fbea5c0254ef32f0c77134b4059f8da89b958beb7696f3/msgpack-1.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5559d03930d3aa0f3aacb4c42c776af1a2ace2611871c84a75afe436695e6245", size = 435242, upload-time = "2025-10-08T09:15:37.647Z" }, + { url = "https://files.pythonhosted.org/packages/c1/47/5c74ecb4cc277cf09f64e913947871682ffa82b3b93c8dad68083112f412/msgpack-1.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:70c5a7a9fea7f036b716191c29047374c10721c389c21e9ffafad04df8c52c90", size = 432509, upload-time = "2025-10-08T09:15:38.794Z" }, + { url = "https://files.pythonhosted.org/packages/24/a4/e98ccdb56dc4e98c929a3f150de1799831c0a800583cde9fa022fa90602d/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f2cb069d8b981abc72b41aea1c580ce92d57c673ec61af4c500153a626cb9e20", size = 415957, upload-time = "2025-10-08T09:15:40.238Z" }, + { url = "https://files.pythonhosted.org/packages/da/28/6951f7fb67bc0a4e184a6b38ab71a92d9ba58080b27a77d3e2fb0be5998f/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d62ce1f483f355f61adb5433ebfd8868c5f078d1a52d042b0a998682b4fa8c27", size = 422910, upload-time = "2025-10-08T09:15:41.505Z" }, + { url = "https://files.pythonhosted.org/packages/f0/03/42106dcded51f0a0b5284d3ce30a671e7bd3f7318d122b2ead66ad289fed/msgpack-1.1.2-cp314-cp314t-win32.whl", hash = "sha256:1d1418482b1ee984625d88aa9585db570180c286d942da463533b238b98b812b", size = 75197, upload-time = "2025-10-08T09:15:42.954Z" }, + { url = "https://files.pythonhosted.org/packages/15/86/d0071e94987f8db59d4eeb386ddc64d0bb9b10820a8d82bcd3e53eeb2da6/msgpack-1.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:5a46bf7e831d09470ad92dff02b8b1ac92175ca36b087f904a0519857c6be3ff", size = 85772, upload-time = "2025-10-08T09:15:43.954Z" }, + { url = "https://files.pythonhosted.org/packages/81/f2/08ace4142eb281c12701fc3b93a10795e4d4dc7f753911d836675050f886/msgpack-1.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d99ef64f349d5ec3293688e91486c5fdb925ed03807f64d98d205d2713c60b46", size = 70868, upload-time = "2025-10-08T09:15:44.959Z" }, +] + +[[package]] +name = "numba" +version = "0.62.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llvmlite" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/20/33dbdbfe60e5fd8e3dbfde299d106279a33d9f8308346022316781368591/numba-0.62.1.tar.gz", hash = "sha256:7b774242aa890e34c21200a1fc62e5b5757d5286267e71103257f4e2af0d5161", size = 2749817, upload-time = "2025-09-29T10:46:31.551Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/27/a5a9a58f267ec3b72f609789b2a8eefd6156bd7117e41cc9b7cf5de30490/numba-0.62.1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:a323df9d36a0da1ca9c592a6baaddd0176d9f417ef49a65bb81951dce69d941a", size = 2684281, upload-time = "2025-09-29T10:43:31.863Z" }, + { url = "https://files.pythonhosted.org/packages/3a/9d/ffc091c0bfd7b80f66df3887a7061b6af80c8c2649902444026ee1454391/numba-0.62.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1e1f4781d3f9f7c23f16eb04e76ca10b5a3516e959634bd226fc48d5d8e7a0a", size = 2687311, upload-time = "2025-09-29T10:43:54.441Z" }, + { url = "https://files.pythonhosted.org/packages/a1/13/9a27bcd0baeea236116070c7df458414336f25e9dd5a872b066cf36b74bf/numba-0.62.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:14432af305ea68627a084cd702124fd5d0c1f5b8a413b05f4e14757202d1cf6c", size = 3734548, upload-time = "2025-09-29T10:42:38.232Z" }, + { url = "https://files.pythonhosted.org/packages/a7/00/17a1ac4a60253c784ce59549375e047da98330b82de7df6ac7f4ecc90902/numba-0.62.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f180922adf159ae36c2fe79fb94ffaa74cf5cb3688cb72dba0a904b91e978507", size = 3441277, upload-time = "2025-09-29T10:43:06.124Z" }, + { url = "https://files.pythonhosted.org/packages/86/94/20ae0ff78612c4697eaf942a639db01dd4e2d90f634ac41fa3e015c961fc/numba-0.62.1-cp310-cp310-win_amd64.whl", hash = "sha256:f41834909d411b4b8d1c68f745144136f21416547009c1e860cc2098754b4ca7", size = 2745647, upload-time = "2025-09-29T10:44:15.282Z" }, + { url = "https://files.pythonhosted.org/packages/dd/5f/8b3491dd849474f55e33c16ef55678ace1455c490555337899c35826836c/numba-0.62.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:f43e24b057714e480fe44bc6031de499e7cf8150c63eb461192caa6cc8530bc8", size = 2684279, upload-time = "2025-09-29T10:43:37.213Z" }, + { url = "https://files.pythonhosted.org/packages/bf/18/71969149bfeb65a629e652b752b80167fe8a6a6f6e084f1f2060801f7f31/numba-0.62.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:57cbddc53b9ee02830b828a8428757f5c218831ccc96490a314ef569d8342b7b", size = 2687330, upload-time = "2025-09-29T10:43:59.601Z" }, + { url = "https://files.pythonhosted.org/packages/0e/7d/403be3fecae33088027bc8a95dc80a2fda1e3beff3e0e5fc4374ada3afbe/numba-0.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:604059730c637c7885386521bb1b0ddcbc91fd56131a6dcc54163d6f1804c872", size = 3739727, upload-time = "2025-09-29T10:42:45.922Z" }, + { url = "https://files.pythonhosted.org/packages/e0/c3/3d910d08b659a6d4c62ab3cd8cd93c4d8b7709f55afa0d79a87413027ff6/numba-0.62.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6c540880170bee817011757dc9049dba5a29db0c09b4d2349295991fe3ee55f", size = 3445490, upload-time = "2025-09-29T10:43:12.692Z" }, + { url = "https://files.pythonhosted.org/packages/5b/82/9d425c2f20d9f0a37f7cb955945a553a00fa06a2b025856c3550227c5543/numba-0.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:03de6d691d6b6e2b76660ba0f38f37b81ece8b2cc524a62f2a0cfae2bfb6f9da", size = 2745550, upload-time = "2025-09-29T10:44:20.571Z" }, + { url = "https://files.pythonhosted.org/packages/5e/fa/30fa6873e9f821c0ae755915a3ca444e6ff8d6a7b6860b669a3d33377ac7/numba-0.62.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:1b743b32f8fa5fff22e19c2e906db2f0a340782caf024477b97801b918cf0494", size = 2685346, upload-time = "2025-09-29T10:43:43.677Z" }, + { url = "https://files.pythonhosted.org/packages/a9/d5/504ce8dc46e0dba2790c77e6b878ee65b60fe3e7d6d0006483ef6fde5a97/numba-0.62.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:90fa21b0142bcf08ad8e32a97d25d0b84b1e921bc9423f8dda07d3652860eef6", size = 2688139, upload-time = "2025-09-29T10:44:04.894Z" }, + { url = "https://files.pythonhosted.org/packages/50/5f/6a802741176c93f2ebe97ad90751894c7b0c922b52ba99a4395e79492205/numba-0.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6ef84d0ac19f1bf80431347b6f4ce3c39b7ec13f48f233a48c01e2ec06ecbc59", size = 3796453, upload-time = "2025-09-29T10:42:52.771Z" }, + { url = "https://files.pythonhosted.org/packages/7e/df/efd21527d25150c4544eccc9d0b7260a5dec4b7e98b5a581990e05a133c0/numba-0.62.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9315cc5e441300e0ca07c828a627d92a6802bcbf27c5487f31ae73783c58da53", size = 3496451, upload-time = "2025-09-29T10:43:19.279Z" }, + { url = "https://files.pythonhosted.org/packages/80/44/79bfdab12a02796bf4f1841630355c82b5a69933b1d50eb15c7fa37dabe8/numba-0.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:44e3aa6228039992f058f5ebfcfd372c83798e9464297bdad8cc79febcf7891e", size = 2745552, upload-time = "2025-09-29T10:44:26.399Z" }, + { url = "https://files.pythonhosted.org/packages/22/76/501ea2c07c089ef1386868f33dff2978f43f51b854e34397b20fc55e0a58/numba-0.62.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:b72489ba8411cc9fdcaa2458d8f7677751e94f0109eeb53e5becfdc818c64afb", size = 2685766, upload-time = "2025-09-29T10:43:49.161Z" }, + { url = "https://files.pythonhosted.org/packages/80/68/444986ed95350c0611d5c7b46828411c222ce41a0c76707c36425d27ce29/numba-0.62.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:44a1412095534a26fb5da2717bc755b57da5f3053965128fe3dc286652cc6a92", size = 2688741, upload-time = "2025-09-29T10:44:10.07Z" }, + { url = "https://files.pythonhosted.org/packages/78/7e/bf2e3634993d57f95305c7cee4c9c6cb3c9c78404ee7b49569a0dfecfe33/numba-0.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8c9460b9e936c5bd2f0570e20a0a5909ee6e8b694fd958b210e3bde3a6dba2d7", size = 3804576, upload-time = "2025-09-29T10:42:59.53Z" }, + { url = "https://files.pythonhosted.org/packages/e8/b6/8a1723fff71f63bbb1354bdc60a1513a068acc0f5322f58da6f022d20247/numba-0.62.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:728f91a874192df22d74e3fd42c12900b7ce7190b1aad3574c6c61b08313e4c5", size = 3503367, upload-time = "2025-09-29T10:43:26.326Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ec/9d414e7a80d6d1dc4af0e07c6bfe293ce0b04ea4d0ed6c45dad9bd6e72eb/numba-0.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:bbf3f88b461514287df66bc8d0307e949b09f2b6f67da92265094e8fa1282dd8", size = 2745529, upload-time = "2025-09-29T10:44:31.738Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version >= '3.11' and python_full_version < '3.13'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/19/95b3d357407220ed24c139018d2518fab0a61a948e68286a25f1a4d049ff/numpy-2.3.3.tar.gz", hash = "sha256:ddc7c39727ba62b80dfdbedf400d1c10ddfa8eefbd7ec8dcb118be8b56d31029", size = 20576648, upload-time = "2025-09-09T16:54:12.543Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/45/e80d203ef6b267aa29b22714fb558930b27960a0c5ce3c19c999232bb3eb/numpy-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ffc4f5caba7dfcbe944ed674b7eef683c7e94874046454bb79ed7ee0236f59d", size = 21259253, upload-time = "2025-09-09T15:56:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/52/18/cf2c648fccf339e59302e00e5f2bc87725a3ce1992f30f3f78c9044d7c43/numpy-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7e946c7170858a0295f79a60214424caac2ffdb0063d4d79cb681f9aa0aa569", size = 14450980, upload-time = "2025-09-09T15:56:05.926Z" }, + { url = "https://files.pythonhosted.org/packages/93/fb/9af1082bec870188c42a1c239839915b74a5099c392389ff04215dcee812/numpy-2.3.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:cd4260f64bc794c3390a63bf0728220dd1a68170c169088a1e0dfa2fde1be12f", size = 5379709, upload-time = "2025-09-09T15:56:07.95Z" }, + { url = "https://files.pythonhosted.org/packages/75/0f/bfd7abca52bcbf9a4a65abc83fe18ef01ccdeb37bfb28bbd6ad613447c79/numpy-2.3.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f0ddb4b96a87b6728df9362135e764eac3cfa674499943ebc44ce96c478ab125", size = 6913923, upload-time = "2025-09-09T15:56:09.443Z" }, + { url = "https://files.pythonhosted.org/packages/79/55/d69adad255e87ab7afda1caf93ca997859092afeb697703e2f010f7c2e55/numpy-2.3.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:afd07d377f478344ec6ca2b8d4ca08ae8bd44706763d1efb56397de606393f48", size = 14589591, upload-time = "2025-09-09T15:56:11.234Z" }, + { url = "https://files.pythonhosted.org/packages/10/a2/010b0e27ddeacab7839957d7a8f00e91206e0c2c47abbb5f35a2630e5387/numpy-2.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc92a5dedcc53857249ca51ef29f5e5f2f8c513e22cfb90faeb20343b8c6f7a6", size = 16938714, upload-time = "2025-09-09T15:56:14.637Z" }, + { url = "https://files.pythonhosted.org/packages/1c/6b/12ce8ede632c7126eb2762b9e15e18e204b81725b81f35176eac14dc5b82/numpy-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7af05ed4dc19f308e1d9fc759f36f21921eb7bbfc82843eeec6b2a2863a0aefa", size = 16370592, upload-time = "2025-09-09T15:56:17.285Z" }, + { url = "https://files.pythonhosted.org/packages/b4/35/aba8568b2593067bb6a8fe4c52babb23b4c3b9c80e1b49dff03a09925e4a/numpy-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:433bf137e338677cebdd5beac0199ac84712ad9d630b74eceeb759eaa45ddf30", size = 18884474, upload-time = "2025-09-09T15:56:20.943Z" }, + { url = "https://files.pythonhosted.org/packages/45/fa/7f43ba10c77575e8be7b0138d107e4f44ca4a1ef322cd16980ea3e8b8222/numpy-2.3.3-cp311-cp311-win32.whl", hash = "sha256:eb63d443d7b4ffd1e873f8155260d7f58e7e4b095961b01c91062935c2491e57", size = 6599794, upload-time = "2025-09-09T15:56:23.258Z" }, + { url = "https://files.pythonhosted.org/packages/0a/a2/a4f78cb2241fe5664a22a10332f2be886dcdea8784c9f6a01c272da9b426/numpy-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:ec9d249840f6a565f58d8f913bccac2444235025bbb13e9a4681783572ee3caa", size = 13088104, upload-time = "2025-09-09T15:56:25.476Z" }, + { url = "https://files.pythonhosted.org/packages/79/64/e424e975adbd38282ebcd4891661965b78783de893b381cbc4832fb9beb2/numpy-2.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:74c2a948d02f88c11a3c075d9733f1ae67d97c6bdb97f2bb542f980458b257e7", size = 10460772, upload-time = "2025-09-09T15:56:27.679Z" }, + { url = "https://files.pythonhosted.org/packages/51/5d/bb7fc075b762c96329147799e1bcc9176ab07ca6375ea976c475482ad5b3/numpy-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cfdd09f9c84a1a934cde1eec2267f0a43a7cd44b2cca4ff95b7c0d14d144b0bf", size = 20957014, upload-time = "2025-09-09T15:56:29.966Z" }, + { url = "https://files.pythonhosted.org/packages/6b/0e/c6211bb92af26517acd52125a237a92afe9c3124c6a68d3b9f81b62a0568/numpy-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb32e3cf0f762aee47ad1ddc6672988f7f27045b0783c887190545baba73aa25", size = 14185220, upload-time = "2025-09-09T15:56:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/22/f2/07bb754eb2ede9073f4054f7c0286b0d9d2e23982e090a80d478b26d35ca/numpy-2.3.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:396b254daeb0a57b1fe0ecb5e3cff6fa79a380fa97c8f7781a6d08cd429418fe", size = 5113918, upload-time = "2025-09-09T15:56:34.175Z" }, + { url = "https://files.pythonhosted.org/packages/81/0a/afa51697e9fb74642f231ea36aca80fa17c8fb89f7a82abd5174023c3960/numpy-2.3.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:067e3d7159a5d8f8a0b46ee11148fc35ca9b21f61e3c49fbd0a027450e65a33b", size = 6647922, upload-time = "2025-09-09T15:56:36.149Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f5/122d9cdb3f51c520d150fef6e87df9279e33d19a9611a87c0d2cf78a89f4/numpy-2.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c02d0629d25d426585fb2e45a66154081b9fa677bc92a881ff1d216bc9919a8", size = 14281991, upload-time = "2025-09-09T15:56:40.548Z" }, + { url = "https://files.pythonhosted.org/packages/51/64/7de3c91e821a2debf77c92962ea3fe6ac2bc45d0778c1cbe15d4fce2fd94/numpy-2.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9192da52b9745f7f0766531dcfa978b7763916f158bb63bdb8a1eca0068ab20", size = 16641643, upload-time = "2025-09-09T15:56:43.343Z" }, + { url = "https://files.pythonhosted.org/packages/30/e4/961a5fa681502cd0d68907818b69f67542695b74e3ceaa513918103b7e80/numpy-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cd7de500a5b66319db419dc3c345244404a164beae0d0937283b907d8152e6ea", size = 16056787, upload-time = "2025-09-09T15:56:46.141Z" }, + { url = "https://files.pythonhosted.org/packages/99/26/92c912b966e47fbbdf2ad556cb17e3a3088e2e1292b9833be1dfa5361a1a/numpy-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:93d4962d8f82af58f0b2eb85daaf1b3ca23fe0a85d0be8f1f2b7bb46034e56d7", size = 18579598, upload-time = "2025-09-09T15:56:49.844Z" }, + { url = "https://files.pythonhosted.org/packages/17/b6/fc8f82cb3520768718834f310c37d96380d9dc61bfdaf05fe5c0b7653e01/numpy-2.3.3-cp312-cp312-win32.whl", hash = "sha256:5534ed6b92f9b7dca6c0a19d6df12d41c68b991cef051d108f6dbff3babc4ebf", size = 6320800, upload-time = "2025-09-09T15:56:52.499Z" }, + { url = "https://files.pythonhosted.org/packages/32/ee/de999f2625b80d043d6d2d628c07d0d5555a677a3cf78fdf868d409b8766/numpy-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:497d7cad08e7092dba36e3d296fe4c97708c93daf26643a1ae4b03f6294d30eb", size = 12786615, upload-time = "2025-09-09T15:56:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/49/6e/b479032f8a43559c383acb20816644f5f91c88f633d9271ee84f3b3a996c/numpy-2.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:ca0309a18d4dfea6fc6262a66d06c26cfe4640c3926ceec90e57791a82b6eee5", size = 10195936, upload-time = "2025-09-09T15:56:56.541Z" }, + { url = "https://files.pythonhosted.org/packages/7d/b9/984c2b1ee61a8b803bf63582b4ac4242cf76e2dbd663efeafcb620cc0ccb/numpy-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f5415fb78995644253370985342cd03572ef8620b934da27d77377a2285955bf", size = 20949588, upload-time = "2025-09-09T15:56:59.087Z" }, + { url = "https://files.pythonhosted.org/packages/a6/e4/07970e3bed0b1384d22af1e9912527ecbeb47d3b26e9b6a3bced068b3bea/numpy-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d00de139a3324e26ed5b95870ce63be7ec7352171bc69a4cf1f157a48e3eb6b7", size = 14177802, upload-time = "2025-09-09T15:57:01.73Z" }, + { url = "https://files.pythonhosted.org/packages/35/c7/477a83887f9de61f1203bad89cf208b7c19cc9fef0cebef65d5a1a0619f2/numpy-2.3.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:9dc13c6a5829610cc07422bc74d3ac083bd8323f14e2827d992f9e52e22cd6a6", size = 5106537, upload-time = "2025-09-09T15:57:03.765Z" }, + { url = "https://files.pythonhosted.org/packages/52/47/93b953bd5866a6f6986344d045a207d3f1cfbad99db29f534ea9cee5108c/numpy-2.3.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d79715d95f1894771eb4e60fb23f065663b2298f7d22945d66877aadf33d00c7", size = 6640743, upload-time = "2025-09-09T15:57:07.921Z" }, + { url = "https://files.pythonhosted.org/packages/23/83/377f84aaeb800b64c0ef4de58b08769e782edcefa4fea712910b6f0afd3c/numpy-2.3.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:952cfd0748514ea7c3afc729a0fc639e61655ce4c55ab9acfab14bda4f402b4c", size = 14278881, upload-time = "2025-09-09T15:57:11.349Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a5/bf3db6e66c4b160d6ea10b534c381a1955dfab34cb1017ea93aa33c70ed3/numpy-2.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b83648633d46f77039c29078751f80da65aa64d5622a3cd62aaef9d835b6c93", size = 16636301, upload-time = "2025-09-09T15:57:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/a2/59/1287924242eb4fa3f9b3a2c30400f2e17eb2707020d1c5e3086fe7330717/numpy-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b001bae8cea1c7dfdb2ae2b017ed0a6f2102d7a70059df1e338e307a4c78a8ae", size = 16053645, upload-time = "2025-09-09T15:57:16.534Z" }, + { url = "https://files.pythonhosted.org/packages/e6/93/b3d47ed882027c35e94ac2320c37e452a549f582a5e801f2d34b56973c97/numpy-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e9aced64054739037d42fb84c54dd38b81ee238816c948c8f3ed134665dcd86", size = 18578179, upload-time = "2025-09-09T15:57:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/20/d9/487a2bccbf7cc9d4bfc5f0f197761a5ef27ba870f1e3bbb9afc4bbe3fcc2/numpy-2.3.3-cp313-cp313-win32.whl", hash = "sha256:9591e1221db3f37751e6442850429b3aabf7026d3b05542d102944ca7f00c8a8", size = 6312250, upload-time = "2025-09-09T15:57:21.296Z" }, + { url = "https://files.pythonhosted.org/packages/1b/b5/263ebbbbcede85028f30047eab3d58028d7ebe389d6493fc95ae66c636ab/numpy-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f0dadeb302887f07431910f67a14d57209ed91130be0adea2f9793f1a4f817cf", size = 12783269, upload-time = "2025-09-09T15:57:23.034Z" }, + { url = "https://files.pythonhosted.org/packages/fa/75/67b8ca554bbeaaeb3fac2e8bce46967a5a06544c9108ec0cf5cece559b6c/numpy-2.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:3c7cf302ac6e0b76a64c4aecf1a09e51abd9b01fc7feee80f6c43e3ab1b1dbc5", size = 10195314, upload-time = "2025-09-09T15:57:25.045Z" }, + { url = "https://files.pythonhosted.org/packages/11/d0/0d1ddec56b162042ddfafeeb293bac672de9b0cfd688383590090963720a/numpy-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:eda59e44957d272846bb407aad19f89dc6f58fecf3504bd144f4c5cf81a7eacc", size = 21048025, upload-time = "2025-09-09T15:57:27.257Z" }, + { url = "https://files.pythonhosted.org/packages/36/9e/1996ca6b6d00415b6acbdd3c42f7f03ea256e2c3f158f80bd7436a8a19f3/numpy-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:823d04112bc85ef5c4fda73ba24e6096c8f869931405a80aa8b0e604510a26bc", size = 14301053, upload-time = "2025-09-09T15:57:30.077Z" }, + { url = "https://files.pythonhosted.org/packages/05/24/43da09aa764c68694b76e84b3d3f0c44cb7c18cdc1ba80e48b0ac1d2cd39/numpy-2.3.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:40051003e03db4041aa325da2a0971ba41cf65714e65d296397cc0e32de6018b", size = 5229444, upload-time = "2025-09-09T15:57:32.733Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/50ffb0f22f7218ef8af28dd089f79f68289a7a05a208db9a2c5dcbe123c1/numpy-2.3.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6ee9086235dd6ab7ae75aba5662f582a81ced49f0f1c6de4260a78d8f2d91a19", size = 6738039, upload-time = "2025-09-09T15:57:34.328Z" }, + { url = "https://files.pythonhosted.org/packages/55/52/af46ac0795e09657d45a7f4db961917314377edecf66db0e39fa7ab5c3d3/numpy-2.3.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94fcaa68757c3e2e668ddadeaa86ab05499a70725811e582b6a9858dd472fb30", size = 14352314, upload-time = "2025-09-09T15:57:36.255Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b1/dc226b4c90eb9f07a3fff95c2f0db3268e2e54e5cce97c4ac91518aee71b/numpy-2.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da1a74b90e7483d6ce5244053399a614b1d6b7bc30a60d2f570e5071f8959d3e", size = 16701722, upload-time = "2025-09-09T15:57:38.622Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9d/9d8d358f2eb5eced14dba99f110d83b5cd9a4460895230f3b396ad19a323/numpy-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2990adf06d1ecee3b3dcbb4977dfab6e9f09807598d647f04d385d29e7a3c3d3", size = 16132755, upload-time = "2025-09-09T15:57:41.16Z" }, + { url = "https://files.pythonhosted.org/packages/b6/27/b3922660c45513f9377b3fb42240bec63f203c71416093476ec9aa0719dc/numpy-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ed635ff692483b8e3f0fcaa8e7eb8a75ee71aa6d975388224f70821421800cea", size = 18651560, upload-time = "2025-09-09T15:57:43.459Z" }, + { url = "https://files.pythonhosted.org/packages/5b/8e/3ab61a730bdbbc201bb245a71102aa609f0008b9ed15255500a99cd7f780/numpy-2.3.3-cp313-cp313t-win32.whl", hash = "sha256:a333b4ed33d8dc2b373cc955ca57babc00cd6f9009991d9edc5ddbc1bac36bcd", size = 6442776, upload-time = "2025-09-09T15:57:45.793Z" }, + { url = "https://files.pythonhosted.org/packages/1c/3a/e22b766b11f6030dc2decdeff5c2fb1610768055603f9f3be88b6d192fb2/numpy-2.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4384a169c4d8f97195980815d6fcad04933a7e1ab3b530921c3fef7a1c63426d", size = 12927281, upload-time = "2025-09-09T15:57:47.492Z" }, + { url = "https://files.pythonhosted.org/packages/7b/42/c2e2bc48c5e9b2a83423f99733950fbefd86f165b468a3d85d52b30bf782/numpy-2.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:75370986cc0bc66f4ce5110ad35aae6d182cc4ce6433c40ad151f53690130bf1", size = 10265275, upload-time = "2025-09-09T15:57:49.647Z" }, + { url = "https://files.pythonhosted.org/packages/6b/01/342ad585ad82419b99bcf7cebe99e61da6bedb89e213c5fd71acc467faee/numpy-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cd052f1fa6a78dee696b58a914b7229ecfa41f0a6d96dc663c1220a55e137593", size = 20951527, upload-time = "2025-09-09T15:57:52.006Z" }, + { url = "https://files.pythonhosted.org/packages/ef/d8/204e0d73fc1b7a9ee80ab1fe1983dd33a4d64a4e30a05364b0208e9a241a/numpy-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:414a97499480067d305fcac9716c29cf4d0d76db6ebf0bf3cbce666677f12652", size = 14186159, upload-time = "2025-09-09T15:57:54.407Z" }, + { url = "https://files.pythonhosted.org/packages/22/af/f11c916d08f3a18fb8ba81ab72b5b74a6e42ead4c2846d270eb19845bf74/numpy-2.3.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:50a5fe69f135f88a2be9b6ca0481a68a136f6febe1916e4920e12f1a34e708a7", size = 5114624, upload-time = "2025-09-09T15:57:56.5Z" }, + { url = "https://files.pythonhosted.org/packages/fb/11/0ed919c8381ac9d2ffacd63fd1f0c34d27e99cab650f0eb6f110e6ae4858/numpy-2.3.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:b912f2ed2b67a129e6a601e9d93d4fa37bef67e54cac442a2f588a54afe5c67a", size = 6642627, upload-time = "2025-09-09T15:57:58.206Z" }, + { url = "https://files.pythonhosted.org/packages/ee/83/deb5f77cb0f7ba6cb52b91ed388b47f8f3c2e9930d4665c600408d9b90b9/numpy-2.3.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e318ee0596d76d4cb3d78535dc005fa60e5ea348cd131a51e99d0bdbe0b54fe", size = 14296926, upload-time = "2025-09-09T15:58:00.035Z" }, + { url = "https://files.pythonhosted.org/packages/77/cc/70e59dcb84f2b005d4f306310ff0a892518cc0c8000a33d0e6faf7ca8d80/numpy-2.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce020080e4a52426202bdb6f7691c65bb55e49f261f31a8f506c9f6bc7450421", size = 16638958, upload-time = "2025-09-09T15:58:02.738Z" }, + { url = "https://files.pythonhosted.org/packages/b6/5a/b2ab6c18b4257e099587d5b7f903317bd7115333ad8d4ec4874278eafa61/numpy-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e6687dc183aa55dae4a705b35f9c0f8cb178bcaa2f029b241ac5356221d5c021", size = 16071920, upload-time = "2025-09-09T15:58:05.029Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f1/8b3fdc44324a259298520dd82147ff648979bed085feeacc1250ef1656c0/numpy-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d8f3b1080782469fdc1718c4ed1d22549b5fb12af0d57d35e992158a772a37cf", size = 18577076, upload-time = "2025-09-09T15:58:07.745Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a1/b87a284fb15a42e9274e7fcea0dad259d12ddbf07c1595b26883151ca3b4/numpy-2.3.3-cp314-cp314-win32.whl", hash = "sha256:cb248499b0bc3be66ebd6578b83e5acacf1d6cb2a77f2248ce0e40fbec5a76d0", size = 6366952, upload-time = "2025-09-09T15:58:10.096Z" }, + { url = "https://files.pythonhosted.org/packages/70/5f/1816f4d08f3b8f66576d8433a66f8fa35a5acfb3bbd0bf6c31183b003f3d/numpy-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:691808c2b26b0f002a032c73255d0bd89751425f379f7bcd22d140db593a96e8", size = 12919322, upload-time = "2025-09-09T15:58:12.138Z" }, + { url = "https://files.pythonhosted.org/packages/8c/de/072420342e46a8ea41c324a555fa90fcc11637583fb8df722936aed1736d/numpy-2.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:9ad12e976ca7b10f1774b03615a2a4bab8addce37ecc77394d8e986927dc0dfe", size = 10478630, upload-time = "2025-09-09T15:58:14.64Z" }, + { url = "https://files.pythonhosted.org/packages/d5/df/ee2f1c0a9de7347f14da5dd3cd3c3b034d1b8607ccb6883d7dd5c035d631/numpy-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9cc48e09feb11e1db00b320e9d30a4151f7369afb96bd0e48d942d09da3a0d00", size = 21047987, upload-time = "2025-09-09T15:58:16.889Z" }, + { url = "https://files.pythonhosted.org/packages/d6/92/9453bdc5a4e9e69cf4358463f25e8260e2ffc126d52e10038b9077815989/numpy-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:901bf6123879b7f251d3631967fd574690734236075082078e0571977c6a8e6a", size = 14301076, upload-time = "2025-09-09T15:58:20.343Z" }, + { url = "https://files.pythonhosted.org/packages/13/77/1447b9eb500f028bb44253105bd67534af60499588a5149a94f18f2ca917/numpy-2.3.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:7f025652034199c301049296b59fa7d52c7e625017cae4c75d8662e377bf487d", size = 5229491, upload-time = "2025-09-09T15:58:22.481Z" }, + { url = "https://files.pythonhosted.org/packages/3d/f9/d72221b6ca205f9736cb4b2ce3b002f6e45cd67cd6a6d1c8af11a2f0b649/numpy-2.3.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:533ca5f6d325c80b6007d4d7fb1984c303553534191024ec6a524a4c92a5935a", size = 6737913, upload-time = "2025-09-09T15:58:24.569Z" }, + { url = "https://files.pythonhosted.org/packages/3c/5f/d12834711962ad9c46af72f79bb31e73e416ee49d17f4c797f72c96b6ca5/numpy-2.3.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0edd58682a399824633b66885d699d7de982800053acf20be1eaa46d92009c54", size = 14352811, upload-time = "2025-09-09T15:58:26.416Z" }, + { url = "https://files.pythonhosted.org/packages/a1/0d/fdbec6629d97fd1bebed56cd742884e4eead593611bbe1abc3eb40d304b2/numpy-2.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:367ad5d8fbec5d9296d18478804a530f1191e24ab4d75ab408346ae88045d25e", size = 16702689, upload-time = "2025-09-09T15:58:28.831Z" }, + { url = "https://files.pythonhosted.org/packages/9b/09/0a35196dc5575adde1eb97ddfbc3e1687a814f905377621d18ca9bc2b7dd/numpy-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8f6ac61a217437946a1fa48d24c47c91a0c4f725237871117dea264982128097", size = 16133855, upload-time = "2025-09-09T15:58:31.349Z" }, + { url = "https://files.pythonhosted.org/packages/7a/ca/c9de3ea397d576f1b6753eaa906d4cdef1bf97589a6d9825a349b4729cc2/numpy-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:179a42101b845a816d464b6fe9a845dfaf308fdfc7925387195570789bb2c970", size = 18652520, upload-time = "2025-09-09T15:58:33.762Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c2/e5ed830e08cd0196351db55db82f65bc0ab05da6ef2b72a836dcf1936d2f/numpy-2.3.3-cp314-cp314t-win32.whl", hash = "sha256:1250c5d3d2562ec4174bce2e3a1523041595f9b651065e4a4473f5f48a6bc8a5", size = 6515371, upload-time = "2025-09-09T15:58:36.04Z" }, + { url = "https://files.pythonhosted.org/packages/47/c7/b0f6b5b67f6788a0725f744496badbb604d226bf233ba716683ebb47b570/numpy-2.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b37a0b2e5935409daebe82c1e42274d30d9dd355852529eab91dab8dcca7419f", size = 13112576, upload-time = "2025-09-09T15:58:37.927Z" }, + { url = "https://files.pythonhosted.org/packages/06/b9/33bba5ff6fb679aa0b1f8a07e853f002a6b04b9394db3069a1270a7784ca/numpy-2.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:78c9f6560dc7e6b3990e32df7ea1a50bbd0e2a111e05209963f5ddcab7073b0b", size = 10545953, upload-time = "2025-09-09T15:58:40.576Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f2/7e0a37cfced2644c9563c529f29fa28acbd0960dde32ece683aafa6f4949/numpy-2.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1e02c7159791cd481e1e6d5ddd766b62a4d5acf8df4d4d1afe35ee9c5c33a41e", size = 21131019, upload-time = "2025-09-09T15:58:42.838Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/3291f505297ed63831135a6cc0f474da0c868a1f31b0dd9a9f03a7a0d2ed/numpy-2.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:dca2d0fc80b3893ae72197b39f69d55a3cd8b17ea1b50aa4c62de82419936150", size = 14376288, upload-time = "2025-09-09T15:58:45.425Z" }, + { url = "https://files.pythonhosted.org/packages/bf/4b/ae02e985bdeee73d7b5abdefeb98aef1207e96d4c0621ee0cf228ddfac3c/numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:99683cbe0658f8271b333a1b1b4bb3173750ad59c0c61f5bbdc5b318918fffe3", size = 5305425, upload-time = "2025-09-09T15:58:48.6Z" }, + { url = "https://files.pythonhosted.org/packages/8b/eb/9df215d6d7250db32007941500dc51c48190be25f2401d5b2b564e467247/numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d9d537a39cc9de668e5cd0e25affb17aec17b577c6b3ae8a3d866b479fbe88d0", size = 6819053, upload-time = "2025-09-09T15:58:50.401Z" }, + { url = "https://files.pythonhosted.org/packages/57/62/208293d7d6b2a8998a4a1f23ac758648c3c32182d4ce4346062018362e29/numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8596ba2f8af5f93b01d97563832686d20206d303024777f6dfc2e7c7c3f1850e", size = 14420354, upload-time = "2025-09-09T15:58:52.704Z" }, + { url = "https://files.pythonhosted.org/packages/ed/0c/8e86e0ff7072e14a71b4c6af63175e40d1e7e933ce9b9e9f765a95b4e0c3/numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1ec5615b05369925bd1125f27df33f3b6c8bc10d788d5999ecd8769a1fa04db", size = 16760413, upload-time = "2025-09-09T15:58:55.027Z" }, + { url = "https://files.pythonhosted.org/packages/af/11/0cc63f9f321ccf63886ac203336777140011fb669e739da36d8db3c53b98/numpy-2.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2e267c7da5bf7309670523896df97f93f6e469fb931161f483cd6882b3b1a5dc", size = 12971844, upload-time = "2025-09-09T15:58:57.359Z" }, +] + +[[package]] +name = "onnxruntime" +version = "1.23.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coloredlogs" }, + { name = "flatbuffers" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "sympy" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/84/42b8a11c9ebfb042071aaab73d17829fc094126e30caf65b18a94c3a5116/onnxruntime-1.23.1-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:6b5257157d319abc87aa17294a9acf17119c6ecfdf9531017239b9022334f9b7", size = 17192895, upload-time = "2025-10-08T04:25:21.961Z" }, + { url = "https://files.pythonhosted.org/packages/c8/be/71568624483453083a8da5cecf6cebd78b0c06a65f41636a60db0b63c8a2/onnxruntime-1.23.1-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:0b99b96743322ed43c7825d339ad7b0fcb840b85b2e3047536ec1112afefdc41", size = 19148658, upload-time = "2025-10-08T04:24:19.031Z" }, + { url = "https://files.pythonhosted.org/packages/af/56/a5448bb8b33c29e78832cf193ead74ca7ee8c848aae171c6caa32c3c68c5/onnxruntime-1.23.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:04e54ed9f972aadfe41abbf539cab714fe719aba011db6403e2f0098a282bf38", size = 15215524, upload-time = "2025-10-08T04:24:01.686Z" }, + { url = "https://files.pythonhosted.org/packages/d8/b6/f42e0ca852226fccb34fa9949ea1b31d0170561e6731b9417bd94e19fd4e/onnxruntime-1.23.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:740f8b32903a28d96eb70ad5d2ec586443768018b3e1211db986d6fa9b4d0ca1", size = 17367900, upload-time = "2025-10-08T04:24:46.051Z" }, + { url = "https://files.pythonhosted.org/packages/82/18/b3c95ef9e2f19c8c1744218912f66867a985254684704fa17630e826c551/onnxruntime-1.23.1-cp310-cp310-win_amd64.whl", hash = "sha256:cbb28e658dcb60643b56b6ba0b60b03b92004eb9a5e4460471009a5dc16c7d8e", size = 13465277, upload-time = "2025-10-08T04:25:12.778Z" }, + { url = "https://files.pythonhosted.org/packages/8a/61/ee52bb2c9402cd1a0d550fc65b826c174f8eed49677dd3833ac1bfc0e35a/onnxruntime-1.23.1-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:9ba6e52fb7bc2758a61d1e421d060cf71d5e4259f95ea8a6f72320ae4415f229", size = 17194265, upload-time = "2025-10-08T04:25:24.479Z" }, + { url = "https://files.pythonhosted.org/packages/d3/67/67122b7b4138815090e0d304c8893fefb77370066a847d08e185f04f75fe/onnxruntime-1.23.1-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:7f130f4b0d31ba17c8789053a641958d0d341d96a1bff578d613fb52ded218c2", size = 19150493, upload-time = "2025-10-08T04:24:21.839Z" }, + { url = "https://files.pythonhosted.org/packages/73/e6/66cebc4dcdb217ccb1027cfcbcc01d6399e999c294d986806991c144cbe7/onnxruntime-1.23.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b89fd116f20b70e1140a77286954a7715eb9347260ff2008ee7ec94994df039", size = 15216531, upload-time = "2025-10-08T04:24:04.973Z" }, + { url = "https://files.pythonhosted.org/packages/38/47/083847220c4a429e272ce9407bc8c47fa77b62e0c787ef2cc94fe9776c1b/onnxruntime-1.23.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:61139a29d536b71db6045c75462e593a53feecc19756dc222531971cd08e5efe", size = 17368047, upload-time = "2025-10-08T04:24:48.426Z" }, + { url = "https://files.pythonhosted.org/packages/ac/8e/b3d861a7d199fd9c6a0b4af9b5d813bcc853d2e4dd4dac2c70b6c23097ed/onnxruntime-1.23.1-cp311-cp311-win_amd64.whl", hash = "sha256:7973186e8eb66e32ea20cb238ae92b604091e4d1df632653ec830abf7584d0b3", size = 13466816, upload-time = "2025-10-08T04:25:15.037Z" }, + { url = "https://files.pythonhosted.org/packages/00/3c/4b4f56b5df4596d1d95aafe13cbc987d050a89364ff5b2f90308376901fb/onnxruntime-1.23.1-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:564d6add1688efdb0720cf2158b50314fc35b744ad2623155ee3b805c381d9ce", size = 17194708, upload-time = "2025-10-08T04:25:27.188Z" }, + { url = "https://files.pythonhosted.org/packages/b4/97/05529b97142c1a09bde2caefea4fd29f71329b9275b52bacdbc2c4f9e964/onnxruntime-1.23.1-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:3864c39307714eff1753149215ad86324a9372e3172a0275d5b16ffd296574bf", size = 19152841, upload-time = "2025-10-08T04:24:24.157Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b9/1232fd295fa9c818aa2a7883d87a2f864fb5edee56ec757c6e857fdd1863/onnxruntime-1.23.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e6b6b5ea80a96924f67fe1e5519f6c6f9cd716fdb5a4fd1ecb4f2b0971e8d00", size = 15223749, upload-time = "2025-10-08T04:24:08.088Z" }, + { url = "https://files.pythonhosted.org/packages/c4/b0/4663a333a82c77f159e48fe8639b1f03e4a05036625be9129c20c4d71d12/onnxruntime-1.23.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:576502dad714ffe5f3b4e1918c5b3368766b222063c585e5fd88415c063e4c80", size = 17378483, upload-time = "2025-10-08T04:24:50.712Z" }, + { url = "https://files.pythonhosted.org/packages/7c/60/8100d98690cbf1de03e08d1f3eff33ff00c652806c7130658a48a8f60584/onnxruntime-1.23.1-cp312-cp312-win_amd64.whl", hash = "sha256:1b89b7c4d4c00a67debc2b0a1484d7f51b23fef85fbd80ac83ed2d17b2161bd6", size = 13467773, upload-time = "2025-10-08T04:25:17.097Z" }, + { url = "https://files.pythonhosted.org/packages/99/cc/0316dfd705407a78e4bf096aaa09b2de6b97676e3e028e1183b450c2ebd1/onnxruntime-1.23.1-cp313-cp313-macosx_13_0_arm64.whl", hash = "sha256:a5402841ff0a400739d2c0423f4f3e3a0ed62673af4323237bb5f5052fccf6cf", size = 17194641, upload-time = "2025-10-08T04:24:16.389Z" }, + { url = "https://files.pythonhosted.org/packages/48/32/7f0a3b21ea9282120fcc274f5227a3390661bdf9019e5ca2da5608f0112d/onnxruntime-1.23.1-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:7059296745fceafcac57badf0386e394185e20c27aa536ec705288c4cde19c8d", size = 19152562, upload-time = "2025-10-08T04:24:26.876Z" }, + { url = "https://files.pythonhosted.org/packages/c4/4a/f9ce32f39fac4465bae693591c6ff9f999635b6ed53171b50b6c4812d613/onnxruntime-1.23.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc8f92157234c3cfba23016576f73deb99aba165a6fc1f2fe4a37d0c524ad3ad", size = 15221548, upload-time = "2025-10-08T04:24:10.878Z" }, + { url = "https://files.pythonhosted.org/packages/e4/30/8a85c09c42a99d97e9445441a4607eacc9db9d40cf9484de6818cab8d154/onnxruntime-1.23.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce3ea70499aabc7c8b9407b3680b12473dba9322e3dfde0fe11ff8061c44a226", size = 17378269, upload-time = "2025-10-08T04:24:53.098Z" }, + { url = "https://files.pythonhosted.org/packages/af/2e/1b95ca7b33f0c345fb454f3187a301791e2a2aa2455ef0cf9e7cb0ab6036/onnxruntime-1.23.1-cp313-cp313-win_amd64.whl", hash = "sha256:371202e1468d5159e78518236cb22f7bbd170e29b31ee77722070a20f8a733ce", size = 13468418, upload-time = "2025-10-08T04:25:19.724Z" }, + { url = "https://files.pythonhosted.org/packages/60/1f/439d9ed8527734a60bf4efba05fbb228dfd9eba7a9ff6c39a29ad92a914d/onnxruntime-1.23.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16217416cb88aadcd6a86f8e7c6c22ff951b65f9f695faef9c1ff94052ba1c36", size = 15225857, upload-time = "2025-10-08T04:24:13.676Z" }, + { url = "https://files.pythonhosted.org/packages/42/03/127876e85542a1ce27cc2d50206d5aba0ccb034b00ab28407839aee272c8/onnxruntime-1.23.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:38eae2d803de3c08265a5b38211bcec315b19a7ca5867468029cca06fd217a6b", size = 17389605, upload-time = "2025-10-08T04:24:55.865Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, +] + +[[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 = "protobuf" +version = "6.32.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/a4/cc17347aa2897568beece2e674674359f911d6fe21b0b8d6268cd42727ac/protobuf-6.32.1.tar.gz", hash = "sha256:ee2469e4a021474ab9baafea6cd070e5bf27c7d29433504ddea1a4ee5850f68d", size = 440635, upload-time = "2025-09-11T21:38:42.935Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/98/645183ea03ab3995d29086b8bf4f7562ebd3d10c9a4b14ee3f20d47cfe50/protobuf-6.32.1-cp310-abi3-win32.whl", hash = "sha256:a8a32a84bc9f2aad712041b8b366190f71dde248926da517bde9e832e4412085", size = 424411, upload-time = "2025-09-11T21:38:27.427Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f3/6f58f841f6ebafe076cebeae33fc336e900619d34b1c93e4b5c97a81fdfa/protobuf-6.32.1-cp310-abi3-win_amd64.whl", hash = "sha256:b00a7d8c25fa471f16bc8153d0e53d6c9e827f0953f3c09aaa4331c718cae5e1", size = 435738, upload-time = "2025-09-11T21:38:30.959Z" }, + { url = "https://files.pythonhosted.org/packages/10/56/a8a3f4e7190837139e68c7002ec749190a163af3e330f65d90309145a210/protobuf-6.32.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d8c7e6eb619ffdf105ee4ab76af5a68b60a9d0f66da3ea12d1640e6d8dab7281", size = 426454, upload-time = "2025-09-11T21:38:34.076Z" }, + { url = "https://files.pythonhosted.org/packages/3f/be/8dd0a927c559b37d7a6c8ab79034fd167dcc1f851595f2e641ad62be8643/protobuf-6.32.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:2f5b80a49e1eb7b86d85fcd23fe92df154b9730a725c3b38c4e43b9d77018bf4", size = 322874, upload-time = "2025-09-11T21:38:35.509Z" }, + { url = "https://files.pythonhosted.org/packages/5c/f6/88d77011b605ef979aace37b7703e4eefad066f7e84d935e5a696515c2dd/protobuf-6.32.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:b1864818300c297265c83a4982fd3169f97122c299f56a56e2445c3698d34710", size = 322013, upload-time = "2025-09-11T21:38:37.017Z" }, + { url = "https://files.pythonhosted.org/packages/97/b7/15cc7d93443d6c6a84626ae3258a91f4c6ac8c0edd5df35ea7658f71b79c/protobuf-6.32.1-py3-none-any.whl", hash = "sha256:2601b779fc7d32a866c6b4404f9d42a3f67c5b9f3f15b4db3cccabe06b95c346", size = 169289, upload-time = "2025-09-11T21:38:41.234Z" }, +] + +[[package]] +name = "pycparser" +version = "2.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, +] + +[[package]] +name = "pyreadline3" +version = "3.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/49/4cea918a08f02817aabae639e3d0ac046fef9f9180518a3ad394e22da148/pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7", size = 99839, upload-time = "2024-09-19T02:40:10.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178, upload-time = "2024-09-19T02:40:08.598Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "scikit-learn" +version = "1.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.16.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/3e/daed796fd69cce768b8788401cc464ea90b306fb196ae1ffed0b98182859/scikit_learn-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b33579c10a3081d076ab403df4a4190da4f4432d443521674637677dc91e61f", size = 9336221, upload-time = "2025-09-09T08:20:19.328Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ce/af9d99533b24c55ff4e18d9b7b4d9919bbc6cd8f22fe7a7be01519a347d5/scikit_learn-1.7.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:36749fb62b3d961b1ce4fedf08fa57a1986cd409eff2d783bca5d4b9b5fce51c", size = 8653834, upload-time = "2025-09-09T08:20:22.073Z" }, + { url = "https://files.pythonhosted.org/packages/58/0e/8c2a03d518fb6bd0b6b0d4b114c63d5f1db01ff0f9925d8eb10960d01c01/scikit_learn-1.7.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7a58814265dfc52b3295b1900cfb5701589d30a8bb026c7540f1e9d3499d5ec8", size = 9660938, upload-time = "2025-09-09T08:20:24.327Z" }, + { url = "https://files.pythonhosted.org/packages/2b/75/4311605069b5d220e7cf5adabb38535bd96f0079313cdbb04b291479b22a/scikit_learn-1.7.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a847fea807e278f821a0406ca01e387f97653e284ecbd9750e3ee7c90347f18", size = 9477818, upload-time = "2025-09-09T08:20:26.845Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9b/87961813c34adbca21a6b3f6b2bea344c43b30217a6d24cc437c6147f3e8/scikit_learn-1.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:ca250e6836d10e6f402436d6463d6c0e4d8e0234cfb6a9a47835bd392b852ce5", size = 8886969, upload-time = "2025-09-09T08:20:29.329Z" }, + { url = "https://files.pythonhosted.org/packages/43/83/564e141eef908a5863a54da8ca342a137f45a0bfb71d1d79704c9894c9d1/scikit_learn-1.7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7509693451651cd7361d30ce4e86a1347493554f172b1c72a39300fa2aea79e", size = 9331967, upload-time = "2025-09-09T08:20:32.421Z" }, + { url = "https://files.pythonhosted.org/packages/18/d6/ba863a4171ac9d7314c4d3fc251f015704a2caeee41ced89f321c049ed83/scikit_learn-1.7.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:0486c8f827c2e7b64837c731c8feff72c0bd2b998067a8a9cbc10643c31f0fe1", size = 8648645, upload-time = "2025-09-09T08:20:34.436Z" }, + { url = "https://files.pythonhosted.org/packages/ef/0e/97dbca66347b8cf0ea8b529e6bb9367e337ba2e8be0ef5c1a545232abfde/scikit_learn-1.7.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89877e19a80c7b11a2891a27c21c4894fb18e2c2e077815bcade10d34287b20d", size = 9715424, upload-time = "2025-09-09T08:20:36.776Z" }, + { url = "https://files.pythonhosted.org/packages/f7/32/1f3b22e3207e1d2c883a7e09abb956362e7d1bd2f14458c7de258a26ac15/scikit_learn-1.7.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8da8bf89d4d79aaec192d2bda62f9b56ae4e5b4ef93b6a56b5de4977e375c1f1", size = 9509234, upload-time = "2025-09-09T08:20:38.957Z" }, + { url = "https://files.pythonhosted.org/packages/9f/71/34ddbd21f1da67c7a768146968b4d0220ee6831e4bcbad3e03dd3eae88b6/scikit_learn-1.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:9b7ed8d58725030568523e937c43e56bc01cadb478fc43c042a9aca1dacb3ba1", size = 8894244, upload-time = "2025-09-09T08:20:41.166Z" }, + { url = "https://files.pythonhosted.org/packages/a7/aa/3996e2196075689afb9fce0410ebdb4a09099d7964d061d7213700204409/scikit_learn-1.7.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8d91a97fa2b706943822398ab943cde71858a50245e31bc71dba62aab1d60a96", size = 9259818, upload-time = "2025-09-09T08:20:43.19Z" }, + { url = "https://files.pythonhosted.org/packages/43/5d/779320063e88af9c4a7c2cf463ff11c21ac9c8bd730c4a294b0000b666c9/scikit_learn-1.7.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:acbc0f5fd2edd3432a22c69bed78e837c70cf896cd7993d71d51ba6708507476", size = 8636997, upload-time = "2025-09-09T08:20:45.468Z" }, + { url = "https://files.pythonhosted.org/packages/5c/d0/0c577d9325b05594fdd33aa970bf53fb673f051a45496842caee13cfd7fe/scikit_learn-1.7.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e5bf3d930aee75a65478df91ac1225ff89cd28e9ac7bd1196853a9229b6adb0b", size = 9478381, upload-time = "2025-09-09T08:20:47.982Z" }, + { url = "https://files.pythonhosted.org/packages/82/70/8bf44b933837ba8494ca0fc9a9ab60f1c13b062ad0197f60a56e2fc4c43e/scikit_learn-1.7.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d6e9deed1a47aca9fe2f267ab8e8fe82ee20b4526b2c0cd9e135cea10feb44", size = 9300296, upload-time = "2025-09-09T08:20:50.366Z" }, + { url = "https://files.pythonhosted.org/packages/c6/99/ed35197a158f1fdc2fe7c3680e9c70d0128f662e1fee4ed495f4b5e13db0/scikit_learn-1.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:6088aa475f0785e01bcf8529f55280a3d7d298679f50c0bb70a2364a82d0b290", size = 8731256, upload-time = "2025-09-09T08:20:52.627Z" }, + { url = "https://files.pythonhosted.org/packages/ae/93/a3038cb0293037fd335f77f31fe053b89c72f17b1c8908c576c29d953e84/scikit_learn-1.7.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b7dacaa05e5d76759fb071558a8b5130f4845166d88654a0f9bdf3eb57851b7", size = 9212382, upload-time = "2025-09-09T08:20:54.731Z" }, + { url = "https://files.pythonhosted.org/packages/40/dd/9a88879b0c1104259136146e4742026b52df8540c39fec21a6383f8292c7/scikit_learn-1.7.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:abebbd61ad9e1deed54cca45caea8ad5f79e1b93173dece40bb8e0c658dbe6fe", size = 8592042, upload-time = "2025-09-09T08:20:57.313Z" }, + { url = "https://files.pythonhosted.org/packages/46/af/c5e286471b7d10871b811b72ae794ac5fe2989c0a2df07f0ec723030f5f5/scikit_learn-1.7.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:502c18e39849c0ea1a5d681af1dbcf15f6cce601aebb657aabbfe84133c1907f", size = 9434180, upload-time = "2025-09-09T08:20:59.671Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fd/df59faa53312d585023b2da27e866524ffb8faf87a68516c23896c718320/scikit_learn-1.7.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a4c328a71785382fe3fe676a9ecf2c86189249beff90bf85e22bdb7efaf9ae0", size = 9283660, upload-time = "2025-09-09T08:21:01.71Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c7/03000262759d7b6f38c836ff9d512f438a70d8a8ddae68ee80de72dcfb63/scikit_learn-1.7.2-cp313-cp313-win_amd64.whl", hash = "sha256:63a9afd6f7b229aad94618c01c252ce9e6fa97918c5ca19c9a17a087d819440c", size = 8702057, upload-time = "2025-09-09T08:21:04.234Z" }, + { url = "https://files.pythonhosted.org/packages/55/87/ef5eb1f267084532c8e4aef98a28b6ffe7425acbfd64b5e2f2e066bc29b3/scikit_learn-1.7.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9acb6c5e867447b4e1390930e3944a005e2cb115922e693c08a323421a6966e8", size = 9558731, upload-time = "2025-09-09T08:21:06.381Z" }, + { url = "https://files.pythonhosted.org/packages/93/f8/6c1e3fc14b10118068d7938878a9f3f4e6d7b74a8ddb1e5bed65159ccda8/scikit_learn-1.7.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:2a41e2a0ef45063e654152ec9d8bcfc39f7afce35b08902bfe290c2498a67a6a", size = 9038852, upload-time = "2025-09-09T08:21:08.628Z" }, + { url = "https://files.pythonhosted.org/packages/83/87/066cafc896ee540c34becf95d30375fe5cbe93c3b75a0ee9aa852cd60021/scikit_learn-1.7.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98335fb98509b73385b3ab2bd0639b1f610541d3988ee675c670371d6a87aa7c", size = 9527094, upload-time = "2025-09-09T08:21:11.486Z" }, + { url = "https://files.pythonhosted.org/packages/9c/2b/4903e1ccafa1f6453b1ab78413938c8800633988c838aa0be386cbb33072/scikit_learn-1.7.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191e5550980d45449126e23ed1d5e9e24b2c68329ee1f691a3987476e115e09c", size = 9367436, upload-time = "2025-09-09T08:21:13.602Z" }, + { url = "https://files.pythonhosted.org/packages/b5/aa/8444be3cfb10451617ff9d177b3c190288f4563e6c50ff02728be67ad094/scikit_learn-1.7.2-cp313-cp313t-win_amd64.whl", hash = "sha256:57dc4deb1d3762c75d685507fbd0bc17160144b2f2ba4ccea5dc285ab0d0e973", size = 9275749, upload-time = "2025-09-09T08:21:15.96Z" }, + { url = "https://files.pythonhosted.org/packages/d9/82/dee5acf66837852e8e68df6d8d3a6cb22d3df997b733b032f513d95205b7/scikit_learn-1.7.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fa8f63940e29c82d1e67a45d5297bdebbcb585f5a5a50c4914cc2e852ab77f33", size = 9208906, upload-time = "2025-09-09T08:21:18.557Z" }, + { url = "https://files.pythonhosted.org/packages/3c/30/9029e54e17b87cb7d50d51a5926429c683d5b4c1732f0507a6c3bed9bf65/scikit_learn-1.7.2-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:f95dc55b7902b91331fa4e5845dd5bde0580c9cd9612b1b2791b7e80c3d32615", size = 8627836, upload-time = "2025-09-09T08:21:20.695Z" }, + { url = "https://files.pythonhosted.org/packages/60/18/4a52c635c71b536879f4b971c2cedf32c35ee78f48367885ed8025d1f7ee/scikit_learn-1.7.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9656e4a53e54578ad10a434dc1f993330568cfee176dff07112b8785fb413106", size = 9426236, upload-time = "2025-09-09T08:21:22.645Z" }, + { url = "https://files.pythonhosted.org/packages/99/7e/290362f6ab582128c53445458a5befd471ed1ea37953d5bcf80604619250/scikit_learn-1.7.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96dc05a854add0e50d3f47a1ef21a10a595016da5b007c7d9cd9d0bffd1fcc61", size = 9312593, upload-time = "2025-09-09T08:21:24.65Z" }, + { url = "https://files.pythonhosted.org/packages/8e/87/24f541b6d62b1794939ae6422f8023703bbf6900378b2b34e0b4384dfefd/scikit_learn-1.7.2-cp314-cp314-win_amd64.whl", hash = "sha256:bb24510ed3f9f61476181e4db51ce801e2ba37541def12dc9333b946fc7a9cf8", size = 8820007, upload-time = "2025-09-09T08:21:26.713Z" }, +] + +[[package]] +name = "scipy" +version = "1.15.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/2f/4966032c5f8cc7e6a60f1b2e0ad686293b9474b65246b0c642e3ef3badd0/scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c", size = 38702770, upload-time = "2025-05-08T16:04:20.849Z" }, + { url = "https://files.pythonhosted.org/packages/a0/6e/0c3bf90fae0e910c274db43304ebe25a6b391327f3f10b5dcc638c090795/scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253", size = 30094511, upload-time = "2025-05-08T16:04:27.103Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b1/4deb37252311c1acff7f101f6453f0440794f51b6eacb1aad4459a134081/scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f", size = 22368151, upload-time = "2025-05-08T16:04:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/38/7d/f457626e3cd3c29b3a49ca115a304cebb8cc6f31b04678f03b216899d3c6/scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92", size = 25121732, upload-time = "2025-05-08T16:04:36.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/0a/92b1de4a7adc7a15dcf5bddc6e191f6f29ee663b30511ce20467ef9b82e4/scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82", size = 35547617, upload-time = "2025-05-08T16:04:43.546Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40", size = 37662964, upload-time = "2025-05-08T16:04:49.431Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/3df8f83cb15f3500478c889be8fb18700813b95e9e087328230b98d547ff/scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e", size = 37238749, upload-time = "2025-05-08T16:04:55.215Z" }, + { url = "https://files.pythonhosted.org/packages/93/3e/b3257cf446f2a3533ed7809757039016b74cd6f38271de91682aa844cfc5/scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c", size = 40022383, upload-time = "2025-05-08T16:05:01.914Z" }, + { url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13", size = 41259201, upload-time = "2025-05-08T16:05:08.166Z" }, + { url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b", size = 38714255, upload-time = "2025-05-08T16:05:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba", size = 30111035, upload-time = "2025-05-08T16:05:20.152Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65", size = 22384499, upload-time = "2025-05-08T16:05:24.494Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1", size = 25152602, upload-time = "2025-05-08T16:05:29.313Z" }, + { url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889", size = 35503415, upload-time = "2025-05-08T16:05:34.699Z" }, + { url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982", size = 37652622, upload-time = "2025-05-08T16:05:40.762Z" }, + { url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9", size = 37244796, upload-time = "2025-05-08T16:05:48.119Z" }, + { url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594", size = 40047684, upload-time = "2025-05-08T16:05:54.22Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb", size = 41246504, upload-time = "2025-05-08T16:06:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735, upload-time = "2025-05-08T16:06:06.471Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284, upload-time = "2025-05-08T16:06:11.686Z" }, + { url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958, upload-time = "2025-05-08T16:06:15.97Z" }, + { url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454, upload-time = "2025-05-08T16:06:20.394Z" }, + { url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199, upload-time = "2025-05-08T16:06:26.159Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455, upload-time = "2025-05-08T16:06:32.778Z" }, + { url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140, upload-time = "2025-05-08T16:06:39.249Z" }, + { url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549, upload-time = "2025-05-08T16:06:45.729Z" }, + { url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184, upload-time = "2025-05-08T16:06:52.623Z" }, + { url = "https://files.pythonhosted.org/packages/73/18/ec27848c9baae6e0d6573eda6e01a602e5649ee72c27c3a8aad673ebecfd/scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759", size = 38728256, upload-time = "2025-05-08T16:06:58.696Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/1aef2184948728b4b6e21267d53b3339762c285a46a274ebb7863c9e4742/scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62", size = 30109540, upload-time = "2025-05-08T16:07:04.209Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d8/59e452c0a255ec352bd0a833537a3bc1bfb679944c4938ab375b0a6b3a3e/scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb", size = 22383115, upload-time = "2025-05-08T16:07:08.998Z" }, + { url = "https://files.pythonhosted.org/packages/08/f5/456f56bbbfccf696263b47095291040655e3cbaf05d063bdc7c7517f32ac/scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730", size = 25163884, upload-time = "2025-05-08T16:07:14.091Z" }, + { url = "https://files.pythonhosted.org/packages/a2/66/a9618b6a435a0f0c0b8a6d0a2efb32d4ec5a85f023c2b79d39512040355b/scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825", size = 35174018, upload-time = "2025-05-08T16:07:19.427Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7", size = 37269716, upload-time = "2025-05-08T16:07:25.712Z" }, + { url = "https://files.pythonhosted.org/packages/77/0a/eac00ff741f23bcabd352731ed9b8995a0a60ef57f5fd788d611d43d69a1/scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11", size = 36872342, upload-time = "2025-05-08T16:07:31.468Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/4379be86dd74b6ad81551689107360d9a3e18f24d20767a2d5b9253a3f0a/scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126", size = 39670869, upload-time = "2025-05-08T16:07:38.002Z" }, + { url = "https://files.pythonhosted.org/packages/87/2e/892ad2862ba54f084ffe8cc4a22667eaf9c2bcec6d2bff1d15713c6c0703/scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163", size = 40988851, upload-time = "2025-05-08T16:08:33.671Z" }, + { url = "https://files.pythonhosted.org/packages/1b/e9/7a879c137f7e55b30d75d90ce3eb468197646bc7b443ac036ae3fe109055/scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8", size = 38863011, upload-time = "2025-05-08T16:07:44.039Z" }, + { url = "https://files.pythonhosted.org/packages/51/d1/226a806bbd69f62ce5ef5f3ffadc35286e9fbc802f606a07eb83bf2359de/scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5", size = 30266407, upload-time = "2025-05-08T16:07:49.891Z" }, + { url = "https://files.pythonhosted.org/packages/e5/9b/f32d1d6093ab9eeabbd839b0f7619c62e46cc4b7b6dbf05b6e615bbd4400/scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e", size = 22540030, upload-time = "2025-05-08T16:07:54.121Z" }, + { url = "https://files.pythonhosted.org/packages/e7/29/c278f699b095c1a884f29fda126340fcc201461ee8bfea5c8bdb1c7c958b/scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb", size = 25218709, upload-time = "2025-05-08T16:07:58.506Z" }, + { url = "https://files.pythonhosted.org/packages/24/18/9e5374b617aba742a990581373cd6b68a2945d65cc588482749ef2e64467/scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723", size = 34809045, upload-time = "2025-05-08T16:08:03.929Z" }, + { url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb", size = 36703062, upload-time = "2025-05-08T16:08:09.558Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8e/038ccfe29d272b30086b25a4960f757f97122cb2ec42e62b460d02fe98e9/scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4", size = 36393132, upload-time = "2025-05-08T16:08:15.34Z" }, + { url = "https://files.pythonhosted.org/packages/10/7e/5c12285452970be5bdbe8352c619250b97ebf7917d7a9a9e96b8a8140f17/scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5", size = 38979503, upload-time = "2025-05-08T16:08:21.513Z" }, + { url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097, upload-time = "2025-05-08T16:08:27.627Z" }, +] + +[[package]] +name = "scipy" +version = "1.16.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version >= '3.11' and python_full_version < '3.13'", +] +dependencies = [ + { name = "numpy", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/3b/546a6f0bfe791bbb7f8d591613454d15097e53f906308ec6f7c1ce588e8e/scipy-1.16.2.tar.gz", hash = "sha256:af029b153d243a80afb6eabe40b0a07f8e35c9adc269c019f364ad747f826a6b", size = 30580599, upload-time = "2025-09-11T17:48:08.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/ef/37ed4b213d64b48422df92560af7300e10fe30b5d665dd79932baebee0c6/scipy-1.16.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:6ab88ea43a57da1af33292ebd04b417e8e2eaf9d5aa05700be8d6e1b6501cd92", size = 36619956, upload-time = "2025-09-11T17:39:20.5Z" }, + { url = "https://files.pythonhosted.org/packages/85/ab/5c2eba89b9416961a982346a4d6a647d78c91ec96ab94ed522b3b6baf444/scipy-1.16.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c95e96c7305c96ede73a7389f46ccd6c659c4da5ef1b2789466baeaed3622b6e", size = 28931117, upload-time = "2025-09-11T17:39:29.06Z" }, + { url = "https://files.pythonhosted.org/packages/80/d1/eed51ab64d227fe60229a2d57fb60ca5898cfa50ba27d4f573e9e5f0b430/scipy-1.16.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:87eb178db04ece7c698220d523c170125dbffebb7af0345e66c3554f6f60c173", size = 20921997, upload-time = "2025-09-11T17:39:34.892Z" }, + { url = "https://files.pythonhosted.org/packages/be/7c/33ea3e23bbadde96726edba6bf9111fb1969d14d9d477ffa202c67bec9da/scipy-1.16.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:4e409eac067dcee96a57fbcf424c13f428037827ec7ee3cb671ff525ca4fc34d", size = 23523374, upload-time = "2025-09-11T17:39:40.846Z" }, + { url = "https://files.pythonhosted.org/packages/96/0b/7399dc96e1e3f9a05e258c98d716196a34f528eef2ec55aad651ed136d03/scipy-1.16.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e574be127bb760f0dad24ff6e217c80213d153058372362ccb9555a10fc5e8d2", size = 33583702, upload-time = "2025-09-11T17:39:49.011Z" }, + { url = "https://files.pythonhosted.org/packages/1a/bc/a5c75095089b96ea72c1bd37a4497c24b581ec73db4ef58ebee142ad2d14/scipy-1.16.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5db5ba6188d698ba7abab982ad6973265b74bb40a1efe1821b58c87f73892b9", size = 35883427, upload-time = "2025-09-11T17:39:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/ab/66/e25705ca3d2b87b97fe0a278a24b7f477b4023a926847935a1a71488a6a6/scipy-1.16.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec6e74c4e884104ae006d34110677bfe0098203a3fec2f3faf349f4cb05165e3", size = 36212940, upload-time = "2025-09-11T17:40:06.013Z" }, + { url = "https://files.pythonhosted.org/packages/d6/fd/0bb911585e12f3abdd603d721d83fc1c7492835e1401a0e6d498d7822b4b/scipy-1.16.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:912f46667d2d3834bc3d57361f854226475f695eb08c08a904aadb1c936b6a88", size = 38865092, upload-time = "2025-09-11T17:40:15.143Z" }, + { url = "https://files.pythonhosted.org/packages/d6/73/c449a7d56ba6e6f874183759f8483cde21f900a8be117d67ffbb670c2958/scipy-1.16.2-cp311-cp311-win_amd64.whl", hash = "sha256:91e9e8a37befa5a69e9cacbe0bcb79ae5afb4a0b130fd6db6ee6cc0d491695fa", size = 38687626, upload-time = "2025-09-11T17:40:24.041Z" }, + { url = "https://files.pythonhosted.org/packages/68/72/02f37316adf95307f5d9e579023c6899f89ff3a051fa079dbd6faafc48e5/scipy-1.16.2-cp311-cp311-win_arm64.whl", hash = "sha256:f3bf75a6dcecab62afde4d1f973f1692be013110cad5338007927db8da73249c", size = 25503506, upload-time = "2025-09-11T17:40:30.703Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8d/6396e00db1282279a4ddd507c5f5e11f606812b608ee58517ce8abbf883f/scipy-1.16.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:89d6c100fa5c48472047632e06f0876b3c4931aac1f4291afc81a3644316bb0d", size = 36646259, upload-time = "2025-09-11T17:40:39.329Z" }, + { url = "https://files.pythonhosted.org/packages/3b/93/ea9edd7e193fceb8eef149804491890bde73fb169c896b61aa3e2d1e4e77/scipy-1.16.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ca748936cd579d3f01928b30a17dc474550b01272d8046e3e1ee593f23620371", size = 28888976, upload-time = "2025-09-11T17:40:46.82Z" }, + { url = "https://files.pythonhosted.org/packages/91/4d/281fddc3d80fd738ba86fd3aed9202331180b01e2c78eaae0642f22f7e83/scipy-1.16.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:fac4f8ce2ddb40e2e3d0f7ec36d2a1e7f92559a2471e59aec37bd8d9de01fec0", size = 20879905, upload-time = "2025-09-11T17:40:52.545Z" }, + { url = "https://files.pythonhosted.org/packages/69/40/b33b74c84606fd301b2915f0062e45733c6ff5708d121dd0deaa8871e2d0/scipy-1.16.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:033570f1dcefd79547a88e18bccacff025c8c647a330381064f561d43b821232", size = 23553066, upload-time = "2025-09-11T17:40:59.014Z" }, + { url = "https://files.pythonhosted.org/packages/55/a7/22c739e2f21a42cc8f16bc76b47cff4ed54fbe0962832c589591c2abec34/scipy-1.16.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ea3421209bf00c8a5ef2227de496601087d8f638a2363ee09af059bd70976dc1", size = 33336407, upload-time = "2025-09-11T17:41:06.796Z" }, + { url = "https://files.pythonhosted.org/packages/53/11/a0160990b82999b45874dc60c0c183d3a3a969a563fffc476d5a9995c407/scipy-1.16.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f66bd07ba6f84cd4a380b41d1bf3c59ea488b590a2ff96744845163309ee8e2f", size = 35673281, upload-time = "2025-09-11T17:41:15.055Z" }, + { url = "https://files.pythonhosted.org/packages/96/53/7ef48a4cfcf243c3d0f1643f5887c81f29fdf76911c4e49331828e19fc0a/scipy-1.16.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e9feab931bd2aea4a23388c962df6468af3d808ddf2d40f94a81c5dc38f32ef", size = 36004222, upload-time = "2025-09-11T17:41:23.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7f/71a69e0afd460049d41c65c630c919c537815277dfea214031005f474d78/scipy-1.16.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03dfc75e52f72cf23ec2ced468645321407faad8f0fe7b1f5b49264adbc29cb1", size = 38664586, upload-time = "2025-09-11T17:41:31.021Z" }, + { url = "https://files.pythonhosted.org/packages/34/95/20e02ca66fb495a95fba0642fd48e0c390d0ece9b9b14c6e931a60a12dea/scipy-1.16.2-cp312-cp312-win_amd64.whl", hash = "sha256:0ce54e07bbb394b417457409a64fd015be623f36e330ac49306433ffe04bc97e", size = 38550641, upload-time = "2025-09-11T17:41:36.61Z" }, + { url = "https://files.pythonhosted.org/packages/92/ad/13646b9beb0a95528ca46d52b7babafbe115017814a611f2065ee4e61d20/scipy-1.16.2-cp312-cp312-win_arm64.whl", hash = "sha256:2a8ffaa4ac0df81a0b94577b18ee079f13fecdb924df3328fc44a7dc5ac46851", size = 25456070, upload-time = "2025-09-11T17:41:41.3Z" }, + { url = "https://files.pythonhosted.org/packages/c1/27/c5b52f1ee81727a9fc457f5ac1e9bf3d6eab311805ea615c83c27ba06400/scipy-1.16.2-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:84f7bf944b43e20b8a894f5fe593976926744f6c185bacfcbdfbb62736b5cc70", size = 36604856, upload-time = "2025-09-11T17:41:47.695Z" }, + { url = "https://files.pythonhosted.org/packages/32/a9/15c20d08e950b540184caa8ced675ba1128accb0e09c653780ba023a4110/scipy-1.16.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5c39026d12edc826a1ef2ad35ad1e6d7f087f934bb868fc43fa3049c8b8508f9", size = 28864626, upload-time = "2025-09-11T17:41:52.642Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fc/ea36098df653cca26062a627c1a94b0de659e97127c8491e18713ca0e3b9/scipy-1.16.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e52729ffd45b68777c5319560014d6fd251294200625d9d70fd8626516fc49f5", size = 20855689, upload-time = "2025-09-11T17:41:57.886Z" }, + { url = "https://files.pythonhosted.org/packages/dc/6f/d0b53be55727f3e6d7c72687ec18ea6d0047cf95f1f77488b99a2bafaee1/scipy-1.16.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:024dd4a118cccec09ca3209b7e8e614931a6ffb804b2a601839499cb88bdf925", size = 23512151, upload-time = "2025-09-11T17:42:02.303Z" }, + { url = "https://files.pythonhosted.org/packages/11/85/bf7dab56e5c4b1d3d8eef92ca8ede788418ad38a7dc3ff50262f00808760/scipy-1.16.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7a5dc7ee9c33019973a470556081b0fd3c9f4c44019191039f9769183141a4d9", size = 33329824, upload-time = "2025-09-11T17:42:07.549Z" }, + { url = "https://files.pythonhosted.org/packages/da/6a/1a927b14ddc7714111ea51f4e568203b2bb6ed59bdd036d62127c1a360c8/scipy-1.16.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c2275ff105e508942f99d4e3bc56b6ef5e4b3c0af970386ca56b777608ce95b7", size = 35681881, upload-time = "2025-09-11T17:42:13.255Z" }, + { url = "https://files.pythonhosted.org/packages/c1/5f/331148ea5780b4fcc7007a4a6a6ee0a0c1507a796365cc642d4d226e1c3a/scipy-1.16.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:af80196eaa84f033e48444d2e0786ec47d328ba00c71e4299b602235ffef9acb", size = 36006219, upload-time = "2025-09-11T17:42:18.765Z" }, + { url = "https://files.pythonhosted.org/packages/46/3a/e991aa9d2aec723b4a8dcfbfc8365edec5d5e5f9f133888067f1cbb7dfc1/scipy-1.16.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9fb1eb735fe3d6ed1f89918224e3385fbf6f9e23757cacc35f9c78d3b712dd6e", size = 38682147, upload-time = "2025-09-11T17:42:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/a1/57/0f38e396ad19e41b4c5db66130167eef8ee620a49bc7d0512e3bb67e0cab/scipy-1.16.2-cp313-cp313-win_amd64.whl", hash = "sha256:fda714cf45ba43c9d3bae8f2585c777f64e3f89a2e073b668b32ede412d8f52c", size = 38520766, upload-time = "2025-09-11T17:43:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a5/85d3e867b6822d331e26c862a91375bb7746a0b458db5effa093d34cdb89/scipy-1.16.2-cp313-cp313-win_arm64.whl", hash = "sha256:2f5350da923ccfd0b00e07c3e5cfb316c1c0d6c1d864c07a72d092e9f20db104", size = 25451169, upload-time = "2025-09-11T17:43:30.198Z" }, + { url = "https://files.pythonhosted.org/packages/09/d9/60679189bcebda55992d1a45498de6d080dcaf21ce0c8f24f888117e0c2d/scipy-1.16.2-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:53d8d2ee29b925344c13bda64ab51785f016b1b9617849dac10897f0701b20c1", size = 37012682, upload-time = "2025-09-11T17:42:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/83/be/a99d13ee4d3b7887a96f8c71361b9659ba4ef34da0338f14891e102a127f/scipy-1.16.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:9e05e33657efb4c6a9d23bd8300101536abd99c85cca82da0bffff8d8764d08a", size = 29389926, upload-time = "2025-09-11T17:42:35.845Z" }, + { url = "https://files.pythonhosted.org/packages/bf/0a/130164a4881cec6ca8c00faf3b57926f28ed429cd6001a673f83c7c2a579/scipy-1.16.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:7fe65b36036357003b3ef9d37547abeefaa353b237e989c21027b8ed62b12d4f", size = 21381152, upload-time = "2025-09-11T17:42:40.07Z" }, + { url = "https://files.pythonhosted.org/packages/47/a6/503ffb0310ae77fba874e10cddfc4a1280bdcca1d13c3751b8c3c2996cf8/scipy-1.16.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6406d2ac6d40b861cccf57f49592f9779071655e9f75cd4f977fa0bdd09cb2e4", size = 23914410, upload-time = "2025-09-11T17:42:44.313Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c7/1147774bcea50d00c02600aadaa919facbd8537997a62496270133536ed6/scipy-1.16.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff4dc42bd321991fbf611c23fc35912d690f731c9914bf3af8f417e64aca0f21", size = 33481880, upload-time = "2025-09-11T17:42:49.325Z" }, + { url = "https://files.pythonhosted.org/packages/6a/74/99d5415e4c3e46b2586f30cdbecb95e101c7192628a484a40dd0d163811a/scipy-1.16.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:654324826654d4d9133e10675325708fb954bc84dae6e9ad0a52e75c6b1a01d7", size = 35791425, upload-time = "2025-09-11T17:42:54.711Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ee/a6559de7c1cc710e938c0355d9d4fbcd732dac4d0d131959d1f3b63eb29c/scipy-1.16.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:63870a84cd15c44e65220eaed2dac0e8f8b26bbb991456a033c1d9abfe8a94f8", size = 36178622, upload-time = "2025-09-11T17:43:00.375Z" }, + { url = "https://files.pythonhosted.org/packages/4e/7b/f127a5795d5ba8ece4e0dce7d4a9fb7cb9e4f4757137757d7a69ab7d4f1a/scipy-1.16.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fa01f0f6a3050fa6a9771a95d5faccc8e2f5a92b4a2e5440a0fa7264a2398472", size = 38783985, upload-time = "2025-09-11T17:43:06.661Z" }, + { url = "https://files.pythonhosted.org/packages/3e/9f/bc81c1d1e033951eb5912cd3750cc005943afa3e65a725d2443a3b3c4347/scipy-1.16.2-cp313-cp313t-win_amd64.whl", hash = "sha256:116296e89fba96f76353a8579820c2512f6e55835d3fad7780fece04367de351", size = 38631367, upload-time = "2025-09-11T17:43:14.44Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5e/2cc7555fd81d01814271412a1d59a289d25f8b63208a0a16c21069d55d3e/scipy-1.16.2-cp313-cp313t-win_arm64.whl", hash = "sha256:98e22834650be81d42982360382b43b17f7ba95e0e6993e2a4f5b9ad9283a94d", size = 25787992, upload-time = "2025-09-11T17:43:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ac/ad8951250516db71619f0bd3b2eb2448db04b720a003dd98619b78b692c0/scipy-1.16.2-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:567e77755019bb7461513c87f02bb73fb65b11f049aaaa8ca17cfaa5a5c45d77", size = 36595109, upload-time = "2025-09-11T17:43:35.713Z" }, + { url = "https://files.pythonhosted.org/packages/ff/f6/5779049ed119c5b503b0f3dc6d6f3f68eefc3a9190d4ad4c276f854f051b/scipy-1.16.2-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:17d9bb346194e8967296621208fcdfd39b55498ef7d2f376884d5ac47cec1a70", size = 28859110, upload-time = "2025-09-11T17:43:40.814Z" }, + { url = "https://files.pythonhosted.org/packages/82/09/9986e410ae38bf0a0c737ff8189ac81a93b8e42349aac009891c054403d7/scipy-1.16.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:0a17541827a9b78b777d33b623a6dcfe2ef4a25806204d08ead0768f4e529a88", size = 20850110, upload-time = "2025-09-11T17:43:44.981Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ad/485cdef2d9215e2a7df6d61b81d2ac073dfacf6ae24b9ae87274c4e936ae/scipy-1.16.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:d7d4c6ba016ffc0f9568d012f5f1eb77ddd99412aea121e6fa8b4c3b7cbad91f", size = 23497014, upload-time = "2025-09-11T17:43:49.074Z" }, + { url = "https://files.pythonhosted.org/packages/a7/74/f6a852e5d581122b8f0f831f1d1e32fb8987776ed3658e95c377d308ed86/scipy-1.16.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9702c4c023227785c779cba2e1d6f7635dbb5b2e0936cdd3a4ecb98d78fd41eb", size = 33401155, upload-time = "2025-09-11T17:43:54.661Z" }, + { url = "https://files.pythonhosted.org/packages/d9/f5/61d243bbc7c6e5e4e13dde9887e84a5cbe9e0f75fd09843044af1590844e/scipy-1.16.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d1cdf0ac28948d225decdefcc45ad7dd91716c29ab56ef32f8e0d50657dffcc7", size = 35691174, upload-time = "2025-09-11T17:44:00.101Z" }, + { url = "https://files.pythonhosted.org/packages/03/99/59933956331f8cc57e406cdb7a483906c74706b156998f322913e789c7e1/scipy-1.16.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:70327d6aa572a17c2941cdfb20673f82e536e91850a2e4cb0c5b858b690e1548", size = 36070752, upload-time = "2025-09-11T17:44:05.619Z" }, + { url = "https://files.pythonhosted.org/packages/c6/7d/00f825cfb47ee19ef74ecf01244b43e95eae74e7e0ff796026ea7cd98456/scipy-1.16.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5221c0b2a4b58aa7c4ed0387d360fd90ee9086d383bb34d9f2789fafddc8a936", size = 38701010, upload-time = "2025-09-11T17:44:11.322Z" }, + { url = "https://files.pythonhosted.org/packages/e4/9f/b62587029980378304ba5a8563d376c96f40b1e133daacee76efdcae32de/scipy-1.16.2-cp314-cp314-win_amd64.whl", hash = "sha256:f5a85d7b2b708025af08f060a496dd261055b617d776fc05a1a1cc69e09fe9ff", size = 39360061, upload-time = "2025-09-11T17:45:09.814Z" }, + { url = "https://files.pythonhosted.org/packages/82/04/7a2f1609921352c7fbee0815811b5050582f67f19983096c4769867ca45f/scipy-1.16.2-cp314-cp314-win_arm64.whl", hash = "sha256:2cc73a33305b4b24556957d5857d6253ce1e2dcd67fa0ff46d87d1670b3e1e1d", size = 26126914, upload-time = "2025-09-11T17:45:14.73Z" }, + { url = "https://files.pythonhosted.org/packages/51/b9/60929ce350c16b221928725d2d1d7f86cf96b8bc07415547057d1196dc92/scipy-1.16.2-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:9ea2a3fed83065d77367775d689401a703d0f697420719ee10c0780bcab594d8", size = 37013193, upload-time = "2025-09-11T17:44:16.757Z" }, + { url = "https://files.pythonhosted.org/packages/2a/41/ed80e67782d4bc5fc85a966bc356c601afddd175856ba7c7bb6d9490607e/scipy-1.16.2-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7280d926f11ca945c3ef92ba960fa924e1465f8d07ce3a9923080363390624c4", size = 29390172, upload-time = "2025-09-11T17:44:21.783Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a3/2f673ace4090452696ccded5f5f8efffb353b8f3628f823a110e0170b605/scipy-1.16.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:8afae1756f6a1fe04636407ef7dbece33d826a5d462b74f3d0eb82deabefd831", size = 21381326, upload-time = "2025-09-11T17:44:25.982Z" }, + { url = "https://files.pythonhosted.org/packages/42/bf/59df61c5d51395066c35836b78136accf506197617c8662e60ea209881e1/scipy-1.16.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:5c66511f29aa8d233388e7416a3f20d5cae7a2744d5cee2ecd38c081f4e861b3", size = 23915036, upload-time = "2025-09-11T17:44:30.527Z" }, + { url = "https://files.pythonhosted.org/packages/91/c3/edc7b300dc16847ad3672f1a6f3f7c5d13522b21b84b81c265f4f2760d4a/scipy-1.16.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efe6305aeaa0e96b0ccca5ff647a43737d9a092064a3894e46c414db84bc54ac", size = 33484341, upload-time = "2025-09-11T17:44:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/26/c7/24d1524e72f06ff141e8d04b833c20db3021020563272ccb1b83860082a9/scipy-1.16.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f3a337d9ae06a1e8d655ee9d8ecb835ea5ddcdcbd8d23012afa055ab014f374", size = 35790840, upload-time = "2025-09-11T17:44:41.76Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b7/5aaad984eeedd56858dc33d75efa59e8ce798d918e1033ef62d2708f2c3d/scipy-1.16.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bab3605795d269067d8ce78a910220262711b753de8913d3deeaedb5dded3bb6", size = 36174716, upload-time = "2025-09-11T17:44:47.316Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c2/e276a237acb09824822b0ada11b028ed4067fdc367a946730979feacb870/scipy-1.16.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b0348d8ddb55be2a844c518cd8cc8deeeb8aeba707cf834db5758fc89b476a2c", size = 38790088, upload-time = "2025-09-11T17:44:53.011Z" }, + { url = "https://files.pythonhosted.org/packages/c6/b4/5c18a766e8353015439f3780f5fc473f36f9762edc1a2e45da3ff5a31b21/scipy-1.16.2-cp314-cp314t-win_amd64.whl", hash = "sha256:26284797e38b8a75e14ea6631d29bda11e76ceaa6ddb6fdebbfe4c4d90faf2f9", size = 39457455, upload-time = "2025-09-11T17:44:58.899Z" }, + { url = "https://files.pythonhosted.org/packages/97/30/2f9a5243008f76dfc5dee9a53dfb939d9b31e16ce4bd4f2e628bfc5d89d2/scipy-1.16.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d2a4472c231328d4de38d5f1f68fdd6d28a615138f842580a8a321b5845cf779", size = 26448374, upload-time = "2025-09-11T17:45:03.45Z" }, +] + +[[package]] +name = "soundfile" +version = "0.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/41/9b873a8c055582859b239be17902a85339bec6a30ad162f98c9b0288a2cc/soundfile-0.13.1.tar.gz", hash = "sha256:b2c68dab1e30297317080a5b43df57e302584c49e2942defdde0acccc53f0e5b", size = 46156, upload-time = "2025-01-25T09:17:04.831Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/28/e2a36573ccbcf3d57c00626a21fe51989380636e821b341d36ccca0c1c3a/soundfile-0.13.1-py2.py3-none-any.whl", hash = "sha256:a23c717560da2cf4c7b5ae1142514e0fd82d6bbd9dfc93a50423447142f2c445", size = 25751, upload-time = "2025-01-25T09:16:44.235Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ab/73e97a5b3cc46bba7ff8650a1504348fa1863a6f9d57d7001c6b67c5f20e/soundfile-0.13.1-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:82dc664d19831933fe59adad199bf3945ad06d84bc111a5b4c0d3089a5b9ec33", size = 1142250, upload-time = "2025-01-25T09:16:47.583Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e5/58fd1a8d7b26fc113af244f966ee3aecf03cb9293cb935daaddc1e455e18/soundfile-0.13.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:743f12c12c4054921e15736c6be09ac26b3b3d603aef6fd69f9dde68748f2593", size = 1101406, upload-time = "2025-01-25T09:16:49.662Z" }, + { url = "https://files.pythonhosted.org/packages/58/ae/c0e4a53d77cf6e9a04179535766b3321b0b9ced5f70522e4caf9329f0046/soundfile-0.13.1-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:9c9e855f5a4d06ce4213f31918653ab7de0c5a8d8107cd2427e44b42df547deb", size = 1235729, upload-time = "2025-01-25T09:16:53.018Z" }, + { url = "https://files.pythonhosted.org/packages/57/5e/70bdd9579b35003a489fc850b5047beeda26328053ebadc1fb60f320f7db/soundfile-0.13.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:03267c4e493315294834a0870f31dbb3b28a95561b80b134f0bd3cf2d5f0e618", size = 1313646, upload-time = "2025-01-25T09:16:54.872Z" }, + { url = "https://files.pythonhosted.org/packages/fe/df/8c11dc4dfceda14e3003bb81a0d0edcaaf0796dd7b4f826ea3e532146bba/soundfile-0.13.1-py2.py3-none-win32.whl", hash = "sha256:c734564fab7c5ddf8e9be5bf70bab68042cd17e9c214c06e365e20d64f9a69d5", size = 899881, upload-time = "2025-01-25T09:16:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/14/e9/6b761de83277f2f02ded7e7ea6f07828ec78e4b229b80e4ca55dd205b9dc/soundfile-0.13.1-py2.py3-none-win_amd64.whl", hash = "sha256:1e70a05a0626524a69e9f0f4dd2ec174b4e9567f4d8b6c11d38b5c289be36ee9", size = 1019162, upload-time = "2025-01-25T09:16:59.573Z" }, +] + +[[package]] +name = "soxr" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +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/1e/a7/11c36d71595b52fe84a220040ace679035953acf06b83bf2c7117c565d2c/soxr-1.0.0-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:b876a3156f67c76aef0cff1084eaf4088d9ca584bb569cb993f89a52ec5f399f", size = 206459, upload-time = "2025-09-07T13:21:46.904Z" }, + { url = "https://files.pythonhosted.org/packages/43/5e/8962f2aeea7777d2a6e65a24a2b83c6aea1a28badeda027fd328f7f03bb7/soxr-1.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d3b957a7b0cc19ae6aa45d40b2181474e53a8dd00efd7bce6bcf4e60e020892", size = 164808, upload-time = "2025-09-07T13:21:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/fc/91/00384166f110a3888ea8efd44523ba7168dd2dc39e3e43c931cc2d069fa9/soxr-1.0.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89685faedebc45af71f08f9957b61cc6143bc94ba43fe38e97067f81e272969", size = 208586, upload-time = "2025-09-07T13:21:50.341Z" }, + { url = "https://files.pythonhosted.org/packages/75/34/e18f1003e242aabed44ed8902534814d3e64209e4d1d874f5b9b67d73cde/soxr-1.0.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d255741b2f0084fd02d4a2ddd77cd495be9e7e7b6f9dba1c9494f86afefac65b", size = 242310, upload-time = "2025-09-07T13:21:51.56Z" }, + { url = "https://files.pythonhosted.org/packages/61/9c/a1c5ed106b40cc1e2e12cd58831b7f1b61c5fbdb8eceeca4b3a0b0dbef6c/soxr-1.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:158a4a9055958c4b95ef91dbbe280cabb00946b5423b25a9b0ce31bd9e0a271e", size = 173561, upload-time = "2025-09-07T13:21:53.03Z" }, + { 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" }, + { url = "https://files.pythonhosted.org/packages/99/77/d3b3c25b4f1b1aa4a73f669355edcaee7a52179d0c50407697200a0e55b9/soxr-1.0.0-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:392a5c70c04eb939c9c176bd6f654dec9a0eaa9ba33d8f1024ed63cf68cdba0a", size = 209509, upload-time = "2025-09-07T13:22:08.773Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ee/3ca73e18781bb2aff92b809f1c17c356dfb9a1870652004bd432e79afbfa/soxr-1.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fdc41a1027ba46777186f26a8fba7893be913383414135577522da2fcc684490", size = 167690, upload-time = "2025-09-07T13:22:10.259Z" }, + { url = "https://files.pythonhosted.org/packages/bd/f0/eea8b5f587a2531657dc5081d2543a5a845f271a3bea1c0fdee5cebde021/soxr-1.0.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:449acd1dfaf10f0ce6dfd75c7e2ef984890df94008765a6742dafb42061c1a24", size = 209541, upload-time = "2025-09-07T13:22:11.739Z" }, + { url = "https://files.pythonhosted.org/packages/64/59/2430a48c705565eb09e78346950b586f253a11bd5313426ced3ecd9b0feb/soxr-1.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:38b35c99e408b8f440c9376a5e1dd48014857cd977c117bdaa4304865ae0edd0", size = 243025, upload-time = "2025-09-07T13:22:12.877Z" }, + { url = "https://files.pythonhosted.org/packages/3c/1b/f84a2570a74094e921bbad5450b2a22a85d58585916e131d9b98029c3e69/soxr-1.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:a39b519acca2364aa726b24a6fd55acf29e4c8909102e0b858c23013c38328e5", size = 184850, upload-time = "2025-09-07T13:22:14.068Z" }, +] + +[[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 = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, +] + +[[package]] +name = "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 = "tts-onnx" +version = "1.0.0" +source = { editable = "." } +dependencies = [ + { name = "librosa" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "onnxruntime" }, + { name = "pyyaml" }, + { name = "soundfile" }, +] + +[package.metadata] +requires-dist = [ + { name = "librosa", specifier = ">=0.10.0" }, + { name = "numpy", specifier = ">=1.26.0" }, + { name = "onnxruntime", specifier = "==1.23.1" }, + { name = "pyyaml", specifier = ">=6.0" }, + { name = "soundfile", specifier = ">=0.12.1" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] diff --git a/rust/.gitignore b/rust/.gitignore new file mode 100644 index 0000000..e2175fb --- /dev/null +++ b/rust/.gitignore @@ -0,0 +1,21 @@ +# Rust build artifacts +/target/ +Cargo.lock + +# Output directory +/results/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Debug +*.pdb + diff --git a/rust/Cargo.toml b/rust/Cargo.toml new file mode 100644 index 0000000..e912720 --- /dev/null +++ b/rust/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "supertonic-tts" +version = "0.1.0" +edition = "2021" + +[dependencies] +# ONNX Runtime +ort = "2.0.0-rc.7" + +# Array processing (like NumPy) +ndarray = { version = "0.16", features = ["rayon"] } +rand = "0.8" +rand_distr = "0.4" + +# Parallel processing +rayon = "1.10" + +# Audio processing +hound = "3.5" +rustfft = "6.2" + +# JSON serialization +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" + +# CLI argument parsing +clap = { version = "4.5", features = ["derive"] } + +# Error handling +anyhow = "1.0" + +# Unicode normalization +unicode-normalization = "0.1" + +# System calls +libc = "0.2" + +[[bin]] +name = "example_onnx" +path = "src/example_onnx.rs" + diff --git a/rust/README.md b/rust/README.md new file mode 100644 index 0000000..7899d90 --- /dev/null +++ b/rust/README.md @@ -0,0 +1,101 @@ +# TTS ONNX Inference Examples + +This guide provides examples for running TTS inference using Rust. + +## Installation + +This project uses [Cargo](https://doc.rust-lang.org/cargo/) for package management. + +### Install Rust (if not already installed) +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +### Build the project +```bash +cargo build --release +``` + +## Basic Usage + +You can run the inference in two ways: +1. **Using cargo run** (builds if needed, then runs) +2. **Direct binary execution** (faster if already built) + +### Example 1: Default Inference +Run inference with default settings: +```bash +# Using cargo run +cargo run --release --bin example_onnx + +# Or directly execute the built binary (faster) +./target/release/example_onnx +``` + +This will use: +- Voice style: `assets/voice_styles/M1.json` +- Text: "This morning, I took a walk in the park, and the sound of the birds and the breeze was so pleasant that I stopped for a long time just to listen." +- Output directory: `results/` +- Total steps: 5 +- Number of generations: 4 + +### Example 2: Batch Inference +Process multiple voice styles and texts at once: +```bash +# Using cargo run +cargo run --release --bin example_onnx -- \ + --voice-style assets/voice_styles/M1.json,assets/voice_styles/F1.json \ + --text "The sun sets behind the mountains, painting the sky in shades of pink and orange.|The weather is beautiful and sunny outside. A gentle breeze makes the air feel fresh and pleasant." + +# Or using the binary directly +./target/release/example_onnx \ + --voice-style assets/voice_styles/M1.json,assets/voice_styles/F1.json \ + --text "The sun sets behind the mountains, painting the sky in shades of pink and orange.|The weather is beautiful and sunny outside. A gentle breeze makes the air feel fresh and pleasant." +``` + +This will: +- Generate speech for 2 different voice-text pairs +- Use male voice (M1.json) for the first text +- Use female voice (F1.json) for the second text +- Process both samples in a single batch + +### Example 3: High Quality Inference +Increase denoising steps for better quality: +```bash +# Using cargo run +cargo run --release --bin example_onnx -- \ + --total-step 10 \ + --voice-style assets/voice_styles/M1.json \ + --text "Increasing the number of denoising steps improves the output's fidelity and overall quality." + +# Or using the binary directly +./target/release/example_onnx \ + --total-step 10 \ + --voice-style assets/voice_styles/M1.json \ + --text "Increasing the number of denoising steps improves the output's fidelity and overall quality." +``` + +This will: +- Use 10 denoising steps instead of the default 5 +- Produce higher quality output at the cost of slower inference + +## Available Arguments + +| Argument | Type | Default | Description | +|----------|------|---------|-------------| +| `--use-gpu` | flag | False | Use GPU for inference (default: CPU) | +| `--onnx-dir` | str | `assets/onnx` | Path to ONNX model directory | +| `--total-step` | int | 5 | Number of denoising steps (higher = better quality, slower) | +| `--n-test` | int | 4 | Number of times to generate each sample | +| `--voice-style` | str+ | `assets/voice_styles/M1.json` | Voice style file path(s) | +| `--text` | str+ | (long default text) | Text(s) to synthesize | +| `--save-dir` | str | `results` | Output directory | + +## Notes + +- **Batch Processing**: The number of `--voice-style` files must match the number of `--text` entries +- **Quality vs Speed**: Higher `--total-step` values produce better quality but take longer +- **GPU Support**: GPU mode is not supported yet +- **Known Issues**: On some platforms (especially macOS), there might be a mutex cleanup warning during exit. This is a known ONNX Runtime issue and doesn't affect functionality. The implementation uses `libc::_exit()` and `mem::forget()` to bypass this issue. + + diff --git a/rust/assets b/rust/assets new file mode 120000 index 0000000..ec2e4be --- /dev/null +++ b/rust/assets @@ -0,0 +1 @@ +../assets \ No newline at end of file diff --git a/rust/src/example_onnx.rs b/rust/src/example_onnx.rs new file mode 100644 index 0000000..0392682 --- /dev/null +++ b/rust/src/example_onnx.rs @@ -0,0 +1,108 @@ +use anyhow::Result; +use clap::Parser; +use std::path::PathBuf; +use std::fs; +use std::mem; + +mod helper; + +use helper::{ + load_text_to_speech, load_voice_style, timer, write_wav_file, sanitize_filename, +}; + +#[derive(Parser, Debug)] +#[command(name = "TTS ONNX Inference")] +#[command(about = "TTS Inference with ONNX Runtime (Rust)", long_about = None)] +struct Args { + /// Use GPU for inference (default: CPU) + #[arg(long, default_value = "false")] + use_gpu: bool, + + /// Path to ONNX model directory + #[arg(long, default_value = "assets/onnx")] + onnx_dir: String, + + /// Number of denoising steps + #[arg(long, default_value = "5")] + total_step: usize, + + /// Number of times to generate + #[arg(long, default_value = "4")] + n_test: usize, + + /// Voice style file path(s) + #[arg(long, value_delimiter = ',', default_values_t = vec!["assets/voice_styles/M1.json".to_string()])] + voice_style: Vec, + + /// Text(s) to synthesize + #[arg(long, value_delimiter = '|', default_values_t = vec!["This morning, I took a walk in the park, and the sound of the birds and the breeze was so pleasant that I stopped for a long time just to listen.".to_string()])] + text: Vec, + + /// Output directory + #[arg(long, default_value = "results")] + save_dir: String, +} + +fn main() -> Result<()> { + println!("=== TTS Inference with ONNX Runtime (Rust) ===\n"); + + // --- 1. Parse arguments --- // + let args = Args::parse(); + let total_step = args.total_step; + let n_test = args.n_test; + let voice_style_paths = &args.voice_style; + let text_list = &args.text; + let save_dir = &args.save_dir; + + if voice_style_paths.len() != text_list.len() { + anyhow::bail!( + "Number of voice styles ({}) must match number of texts ({})", + voice_style_paths.len(), + text_list.len() + ); + } + + let bsz = voice_style_paths.len(); + + // --- 2. Load TTS components --- // + let mut text_to_speech = load_text_to_speech(&args.onnx_dir, args.use_gpu)?; + + // --- 3. Load voice styles --- // + let style = load_voice_style(voice_style_paths, true)?; + + // --- 4. Synthesize speech --- // + fs::create_dir_all(save_dir)?; + + for n in 0..n_test { + println!("\n[{}/{}] Starting synthesis...", n + 1, n_test); + + let (wav, duration) = timer("Generating speech from text", || { + text_to_speech.call(text_list, &style, total_step) + })?; + + // Save outputs + let wav_len = wav.len() / bsz; + for i in 0..bsz { + let fname = format!("{}_{}.wav", sanitize_filename(&text_list[i], 20), n + 1); + let actual_len = (text_to_speech.sample_rate as f32 * duration[i]) as usize; + + let wav_start = i * wav_len; + let wav_end = wav_start + actual_len.min(wav_len); + let wav_slice = &wav[wav_start..wav_end]; + + let output_path = PathBuf::from(save_dir).join(&fname); + write_wav_file(&output_path, wav_slice, text_to_speech.sample_rate)?; + println!("Saved: {}", output_path.display()); + } + } + + println!("\n=== Synthesis completed successfully! ==="); + + // Prevent ONNX Runtime sessions from being dropped, which causes mutex cleanup issues + mem::forget(text_to_speech); + + // Use _exit to bypass all cleanup handlers and avoid ONNX Runtime mutex issues on macOS + unsafe { + libc::_exit(0); + } +} diff --git a/rust/src/helper.rs b/rust/src/helper.rs new file mode 100644 index 0000000..eeb8fcc --- /dev/null +++ b/rust/src/helper.rs @@ -0,0 +1,507 @@ +// ============================================================================ +// TTS Helper Module - All utility functions and structures +// ============================================================================ + +use ndarray::{Array, Array3}; +use serde::{Deserialize, Serialize}; +use serde_json; +use std::fs::File; +use std::io::BufReader; +use std::path::Path; +use anyhow::{Result, Context}; +use unicode_normalization::UnicodeNormalization; +use hound::{WavWriter, WavSpec, SampleFormat}; +use rand_distr::{Distribution, Normal}; + +// ============================================================================ +// Configuration Structures +// ============================================================================ + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Config { + pub ae: AEConfig, + pub ttl: TTLConfig, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AEConfig { + pub sample_rate: i32, + pub base_chunk_size: i32, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TTLConfig { + pub chunk_compress_factor: i32, + pub latent_dim: i32, +} + +/// Load configuration from JSON file +pub fn load_cfgs>(onnx_dir: P) -> Result { + let cfg_path = onnx_dir.as_ref().join("tts.json"); + let file = File::open(cfg_path)?; + let reader = BufReader::new(file); + let cfgs: Config = serde_json::from_reader(reader)?; + Ok(cfgs) +} + +// ============================================================================ +// Voice Style Data Structure +// ============================================================================ + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct VoiceStyleData { + pub style_ttl: StyleComponent, + pub style_dp: StyleComponent, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct StyleComponent { + pub data: Vec>>, + pub dims: Vec, + #[serde(rename = "type")] + pub dtype: String, +} + +// ============================================================================ +// Unicode Text Processor +// ============================================================================ + +pub struct UnicodeProcessor { + indexer: Vec, +} + +impl UnicodeProcessor { + pub fn new>(unicode_indexer_json_path: P) -> Result { + let file = File::open(unicode_indexer_json_path)?; + let reader = BufReader::new(file); + let indexer: Vec = serde_json::from_reader(reader)?; + Ok(UnicodeProcessor { indexer }) + } + + pub fn call(&self, text_list: &[String]) -> (Vec>, Array3) { + let processed_texts: Vec = text_list + .iter() + .map(|t| preprocess_text(t)) + .collect(); + + let text_ids_lengths: Vec = processed_texts + .iter() + .map(|t| t.chars().count()) + .collect(); + + let max_len = *text_ids_lengths.iter().max().unwrap_or(&0); + + let mut text_ids = Vec::new(); + for text in &processed_texts { + let mut row = vec![0i64; max_len]; + let unicode_vals = text_to_unicode_values(text); + for (j, &val) in unicode_vals.iter().enumerate() { + if val < self.indexer.len() { + row[j] = self.indexer[val]; + } else { + row[j] = -1; + } + } + text_ids.push(row); + } + + let text_mask = get_text_mask(&text_ids_lengths); + + (text_ids, text_mask) + } +} + +pub fn preprocess_text(text: &str) -> String { + text.nfkd().collect() +} + +pub fn text_to_unicode_values(text: &str) -> Vec { + text.chars().map(|c| c as usize).collect() +} + +pub fn length_to_mask(lengths: &[usize], max_len: Option) -> Array3 { + let bsz = lengths.len(); + let max_len = max_len.unwrap_or_else(|| *lengths.iter().max().unwrap_or(&0)); + + let mut mask = Array3::::zeros((bsz, 1, max_len)); + for (i, &len) in lengths.iter().enumerate() { + for j in 0..len.min(max_len) { + mask[[i, 0, j]] = 1.0; + } + } + mask +} + +pub fn get_text_mask(text_ids_lengths: &[usize]) -> Array3 { + let max_len = *text_ids_lengths.iter().max().unwrap_or(&0); + length_to_mask(text_ids_lengths, Some(max_len)) +} + +/// Sample noisy latent from normal distribution and apply mask +pub fn sample_noisy_latent( + duration: &[f32], + sample_rate: i32, + base_chunk_size: i32, + chunk_compress: i32, + latent_dim: i32, +) -> (Array3, Array3) { + let bsz = duration.len(); + let max_dur = duration.iter().fold(0.0f32, |a, &b| a.max(b)); + + let wav_len_max = (max_dur * sample_rate as f32) as usize; + let wav_lengths: Vec = duration + .iter() + .map(|&d| (d * sample_rate as f32) as usize) + .collect(); + + let chunk_size = (base_chunk_size * chunk_compress) as usize; + let latent_len = (wav_len_max + chunk_size - 1) / chunk_size; + let latent_dim_val = (latent_dim * chunk_compress) as usize; + + let mut noisy_latent = Array3::::zeros((bsz, latent_dim_val, latent_len)); + + let normal = Normal::new(0.0, 1.0).unwrap(); + let mut rng = rand::thread_rng(); + + for b in 0..bsz { + for d in 0..latent_dim_val { + for t in 0..latent_len { + noisy_latent[[b, d, t]] = normal.sample(&mut rng); + } + } + } + + let latent_lengths: Vec = wav_lengths + .iter() + .map(|&len| (len + chunk_size - 1) / chunk_size) + .collect(); + + let latent_mask = length_to_mask(&latent_lengths, Some(latent_len)); + + // Apply mask + for b in 0..bsz { + for d in 0..latent_dim_val { + for t in 0..latent_len { + noisy_latent[[b, d, t]] *= latent_mask[[b, 0, t]]; + } + } + } + + (noisy_latent, latent_mask) +} + +// ============================================================================ +// WAV File I/O +// ============================================================================ + +pub fn write_wav_file>( + filename: P, + audio_data: &[f32], + sample_rate: i32, +) -> Result<()> { + let spec = WavSpec { + channels: 1, + sample_rate: sample_rate as u32, + bits_per_sample: 16, + sample_format: SampleFormat::Int, + }; + + let mut writer = WavWriter::create(filename, spec)?; + + for &sample in audio_data { + let clamped = sample.max(-1.0).min(1.0); + let val = (clamped * 32767.0) as i16; + writer.write_sample(val)?; + } + + writer.finalize()?; + Ok(()) +} + +// ============================================================================ +// Utility Functions +// ============================================================================ + +pub fn timer(name: &str, f: F) -> Result +where + F: FnOnce() -> Result, +{ + let start = std::time::Instant::now(); + println!("{}...", name); + let result = f()?; + let elapsed = start.elapsed().as_secs_f64(); + println!(" -> {} completed in {:.2} sec", name, elapsed); + Ok(result) +} + +pub fn sanitize_filename(text: &str, max_len: usize) -> String { + let text = if text.len() > max_len { + &text[..max_len] + } else { + text + }; + + text.chars() + .map(|c| { + if c.is_ascii_alphanumeric() { + c + } else { + '_' + } + }) + .collect() +} + +// ============================================================================ +// ONNX Runtime Integration +// ============================================================================ + +use ort::{ + session::Session, + value::Value, +}; + +pub struct Style { + pub ttl: Array3, + pub dp: Array3, +} + +pub struct TextToSpeech { + cfgs: Config, + text_processor: UnicodeProcessor, + dp_ort: Session, + text_enc_ort: Session, + vector_est_ort: Session, + vocoder_ort: Session, + pub sample_rate: i32, +} + +impl TextToSpeech { + pub fn new( + cfgs: Config, + text_processor: UnicodeProcessor, + dp_ort: Session, + text_enc_ort: Session, + vector_est_ort: Session, + vocoder_ort: Session, + ) -> Self { + let sample_rate = cfgs.ae.sample_rate; + TextToSpeech { + cfgs, + text_processor, + dp_ort, + text_enc_ort, + vector_est_ort, + vocoder_ort, + sample_rate, + } + } + + pub fn call( + &mut self, + text_list: &[String], + style: &Style, + total_step: usize, + ) -> Result<(Vec, Vec)> { + let bsz = text_list.len(); + + // Process text + let (text_ids, text_mask) = self.text_processor.call(text_list); + + let text_ids_array = { + let text_ids_shape = (bsz, text_ids[0].len()); + let mut flat = Vec::new(); + for row in &text_ids { + flat.extend_from_slice(row); + } + Array::from_shape_vec(text_ids_shape, flat)? + }; + + let text_ids_value = Value::from_array(text_ids_array)?; + let text_mask_value = Value::from_array(text_mask.clone())?; + let style_dp_value = Value::from_array(style.dp.clone())?; + + // Predict duration + let dp_outputs = self.dp_ort.run(ort::inputs!{ + "text_ids" => &text_ids_value, + "style_dp" => &style_dp_value, + "text_mask" => &text_mask_value + })?; + + let (_, duration_data) = dp_outputs["duration"].try_extract_tensor::()?; + let duration: Vec = duration_data.to_vec(); + + // Encode text + let style_ttl_value = Value::from_array(style.ttl.clone())?; + let text_enc_outputs = self.text_enc_ort.run(ort::inputs!{ + "text_ids" => &text_ids_value, + "style_ttl" => &style_ttl_value, + "text_mask" => &text_mask_value + })?; + + let (text_emb_shape, text_emb_data) = text_enc_outputs["text_emb"].try_extract_tensor::()?; + let text_emb = Array3::from_shape_vec( + (text_emb_shape[0] as usize, text_emb_shape[1] as usize, text_emb_shape[2] as usize), + text_emb_data.to_vec() + )?; + + // Sample noisy latent + let (mut xt, latent_mask) = sample_noisy_latent( + &duration, + self.sample_rate, + self.cfgs.ae.base_chunk_size, + self.cfgs.ttl.chunk_compress_factor, + self.cfgs.ttl.latent_dim, + ); + + // Prepare constant arrays + let total_step_array = Array::from_elem(bsz, total_step as f32); + + // Denoising loop + for step in 0..total_step { + let current_step_array = Array::from_elem(bsz, step as f32); + + let xt_value = Value::from_array(xt.clone())?; + let text_emb_value = Value::from_array(text_emb.clone())?; + let latent_mask_value = Value::from_array(latent_mask.clone())?; + let text_mask_value2 = Value::from_array(text_mask.clone())?; + let current_step_value = Value::from_array(current_step_array)?; + let total_step_value = Value::from_array(total_step_array.clone())?; + + let vector_est_outputs = self.vector_est_ort.run(ort::inputs!{ + "noisy_latent" => &xt_value, + "text_emb" => &text_emb_value, + "style_ttl" => &style_ttl_value, + "latent_mask" => &latent_mask_value, + "text_mask" => &text_mask_value2, + "current_step" => ¤t_step_value, + "total_step" => &total_step_value + })?; + + let (denoised_shape, denoised_data) = vector_est_outputs["denoised_latent"].try_extract_tensor::()?; + xt = Array3::from_shape_vec( + (denoised_shape[0] as usize, denoised_shape[1] as usize, denoised_shape[2] as usize), + denoised_data.to_vec() + )?; + } + + // Generate waveform + let final_latent_value = Value::from_array(xt)?; + let vocoder_outputs = self.vocoder_ort.run(ort::inputs!{ + "latent" => &final_latent_value + })?; + + let (_, wav_data) = vocoder_outputs["wav_tts"].try_extract_tensor::()?; + let wav: Vec = wav_data.to_vec(); + + Ok((wav, duration)) + } +} + +// ============================================================================ +// Component Loading Functions +// ============================================================================ + +/// Load voice style from JSON files +pub fn load_voice_style(voice_style_paths: &[String], verbose: bool) -> Result