From 02f24bb524bc970a8508dbf7ce733ebbfbe6c529 Mon Sep 17 00:00:00 2001 From: "RingOfStorms (Joshua Bell)" Date: Tue, 13 Jan 2026 14:10:10 -0600 Subject: [PATCH] stt test --- .gitignore | 1 + flakes/de_plasma/de_plasma.nix | 3 + flakes/stt_ime/README.md | 108 + flakes/stt_ime/fcitx5-stt/CMakeLists.txt | 42 + flakes/stt_ime/fcitx5-stt/data/stt-im.conf | 7 + flakes/stt_ime/fcitx5-stt/data/stt.conf | 7 + flakes/stt_ime/fcitx5-stt/src/config.h.in | 4 + flakes/stt_ime/fcitx5-stt/src/stt.cpp | 533 +++++ flakes/stt_ime/flake.lock | 77 + flakes/stt_ime/flake.nix | 166 ++ flakes/stt_ime/stt-stream/Cargo.lock | 2487 ++++++++++++++++++++ flakes/stt_ime/stt-stream/Cargo.toml | 50 + flakes/stt_ime/stt-stream/src/main.rs | 599 +++++ hosts/lio/flake.lock | 101 +- hosts/lio/flake.nix | 14 +- 15 files changed, 4184 insertions(+), 15 deletions(-) create mode 100644 flakes/stt_ime/README.md create mode 100644 flakes/stt_ime/fcitx5-stt/CMakeLists.txt create mode 100644 flakes/stt_ime/fcitx5-stt/data/stt-im.conf create mode 100644 flakes/stt_ime/fcitx5-stt/data/stt.conf create mode 100644 flakes/stt_ime/fcitx5-stt/src/config.h.in create mode 100644 flakes/stt_ime/fcitx5-stt/src/stt.cpp create mode 100644 flakes/stt_ime/flake.lock create mode 100644 flakes/stt_ime/flake.nix create mode 100644 flakes/stt_ime/stt-stream/Cargo.lock create mode 100644 flakes/stt_ime/stt-stream/Cargo.toml create mode 100644 flakes/stt_ime/stt-stream/src/main.rs diff --git a/.gitignore b/.gitignore index 59cf5ae0..97263acd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ modules/ **/result *.qcow +**/target diff --git a/flakes/de_plasma/de_plasma.nix b/flakes/de_plasma/de_plasma.nix index 01fd3b99..7c538cf4 100644 --- a/flakes/de_plasma/de_plasma.nix +++ b/flakes/de_plasma/de_plasma.nix @@ -149,6 +149,9 @@ in kdePackages.plasma-browser-integration # kdePackages.plasma-workspace-wallpapers + # On-screen keyboard (Plasma Wayland) + kdePackages.plasma-keyboard + # Panel applets required for widgets kdePackages.plasma-nm # org.kde.plasma.networkmanagement kdePackages.bluedevil # org.kde.plasma.bluetooth diff --git a/flakes/stt_ime/README.md b/flakes/stt_ime/README.md new file mode 100644 index 00000000..0dda446f --- /dev/null +++ b/flakes/stt_ime/README.md @@ -0,0 +1,108 @@ +# stt_ime - Speech-to-Text Input Method for Fcitx5 + +Local, privacy-preserving speech-to-text that integrates as a native Fcitx5 input method. + +## Components + +- **stt-stream**: Rust CLI that captures audio, runs VAD, and transcribes with Whisper +- **fcitx5-stt**: C++ Fcitx5 addon that spawns stt-stream and commits text to apps + +## Modes + +- **Manual**: Press `Ctrl+Space` or `Ctrl+R` to start/stop recording +- **Oneshot**: Automatically starts on speech, commits on silence, then resets +- **Continuous**: Always listening, commits each utterance automatically + +Press `Ctrl+M` while STT is active to cycle between modes. + +## Keys (when STT input method is active) + +| Key | Action | +|-----|--------| +| `Ctrl+Space` / `Ctrl+R` | Toggle recording (manual mode) | +| `Ctrl+M` | Cycle mode (manual → oneshot → continuous) | +| `Enter` | Accept current preedit text | +| `Escape` | Cancel recording / clear preedit | + +## Usage + +### NixOS Module + +```nix +# In your host's flake.nix inputs: +stt_ime.url = "git+https://git.ros.one/josh/nixos-config?dir=flakes/stt_ime"; + +# In your NixOS config: +{ + imports = [ inputs.stt_ime.nixosModules.default ]; + + ringofstorms.sttIme = { + enable = true; + model = "base.en"; # tiny, base, small, medium, large-v3 (add .en for English-only) + useGpu = false; # set true for CUDA acceleration + }; +} +``` + +### Standalone CLI + +```bash +# Run with default settings (manual mode) +stt-stream + +# Run in continuous mode +stt-stream --mode continuous + +# Use a specific model +stt-stream --model small-en + +# Commands via stdin (manual mode): +echo "start" | stt-stream # begin recording +echo "stop" | stt-stream # stop and transcribe +echo "cancel" | stt-stream # cancel without transcribing +echo "shutdown" | stt-stream # exit +``` + +### Output Format (NDJSON) + +```json +{"type":"ready"} +{"type":"recording_started"} +{"type":"partial","text":"hello worl"} +{"type":"partial","text":"hello world"} +{"type":"final","text":"Hello world."} +{"type":"recording_stopped"} +{"type":"shutdown"} +``` + +## Models + +Models are automatically downloaded from Hugging Face on first run and cached in `~/.cache/stt-stream/models/`. + +| Model | Size | Speed | Quality | +|-------|------|-------|---------| +| tiny.en | ~75MB | Fastest | Basic | +| base.en | ~150MB | Fast | Good (default) | +| small.en | ~500MB | Medium | Better | +| medium.en | ~1.5GB | Slow | Great | +| large-v3 | ~3GB | Slowest | Best (multilingual) | + +## Environment Variables + +- `STT_STREAM_MODEL_PATH`: Path to a specific model file +- `STT_STREAM_MODEL`: Model name (overridden by CLI) +- `STT_STREAM_USE_GPU`: Set to "1" for GPU acceleration + +## Building + +```bash +cd flakes/stt_ime +nix build .#stt-stream # Rust CLI only +nix build .#fcitx5-stt # Fcitx5 addon (includes stt-stream) +nix build # Default: fcitx5-stt +``` + +## Integration with de_plasma + +The addon is automatically added to Fcitx5 when `ringofstorms.sttIme.enable = true`. +It appears as "Speech to Text" (STT) in the input method switcher alongside US and Mozc. diff --git a/flakes/stt_ime/fcitx5-stt/CMakeLists.txt b/flakes/stt_ime/fcitx5-stt/CMakeLists.txt new file mode 100644 index 00000000..57917248 --- /dev/null +++ b/flakes/stt_ime/fcitx5-stt/CMakeLists.txt @@ -0,0 +1,42 @@ +cmake_minimum_required(VERSION 3.16) +project(fcitx5-stt VERSION 0.1.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Find Fcitx5 +find_package(Fcitx5Core REQUIRED) +find_package(Fcitx5Utils REQUIRED) + +# Path to stt-stream binary (set by Nix) +if(NOT DEFINED STT_STREAM_PATH) + set(STT_STREAM_PATH "stt-stream") +endif() + +# Configure header with path +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.in + ${CMAKE_CURRENT_BINARY_DIR}/config.h +) + +# Build the addon shared library +add_library(stt MODULE + src/stt.cpp +) + +target_include_directories(stt PRIVATE + ${CMAKE_CURRENT_BINARY_DIR} +) + +target_link_libraries(stt PRIVATE + Fcitx5::Core + Fcitx5::Utils +) + +# Set output name without "lib" prefix +set_target_properties(stt PROPERTIES PREFIX "") + +# Install targets - use standard paths, Nix postInstall will handle fcitx5 paths +install(TARGETS stt DESTINATION lib/fcitx5) +install(FILES data/stt.conf DESTINATION share/fcitx5/addon) +install(FILES data/stt-im.conf DESTINATION share/fcitx5/inputmethod) diff --git a/flakes/stt_ime/fcitx5-stt/data/stt-im.conf b/flakes/stt_ime/fcitx5-stt/data/stt-im.conf new file mode 100644 index 00000000..8b62855f --- /dev/null +++ b/flakes/stt_ime/fcitx5-stt/data/stt-im.conf @@ -0,0 +1,7 @@ +[InputMethod] +Name=Speech to Text +Icon=audio-input-microphone +Label=STT +LangCode= +Addon=stt +Configurable=False diff --git a/flakes/stt_ime/fcitx5-stt/data/stt.conf b/flakes/stt_ime/fcitx5-stt/data/stt.conf new file mode 100644 index 00000000..28a54774 --- /dev/null +++ b/flakes/stt_ime/fcitx5-stt/data/stt.conf @@ -0,0 +1,7 @@ +[Addon] +Name=stt +Category=InputMethod +Library=stt +Type=SharedLibrary +OnDemand=True +Configurable=False diff --git a/flakes/stt_ime/fcitx5-stt/src/config.h.in b/flakes/stt_ime/fcitx5-stt/src/config.h.in new file mode 100644 index 00000000..3626c88f --- /dev/null +++ b/flakes/stt_ime/fcitx5-stt/src/config.h.in @@ -0,0 +1,4 @@ +#pragma once + +// Path to stt-stream binary +#define STT_STREAM_PATH "@STT_STREAM_PATH@" diff --git a/flakes/stt_ime/fcitx5-stt/src/stt.cpp b/flakes/stt_ime/fcitx5-stt/src/stt.cpp new file mode 100644 index 00000000..eb738860 --- /dev/null +++ b/flakes/stt_ime/fcitx5-stt/src/stt.cpp @@ -0,0 +1,533 @@ +/* + * fcitx5-stt: Speech-to-Text Input Method Engine for Fcitx5 + * + * This is a thin shim that spawns the stt-stream Rust binary and + * bridges its JSON events to Fcitx5's input method API. + * + * Modes: + * - Oneshot: Record until silence, commit, reset + * - Continuous: Always listen, commit on silence + * - Manual: Start/stop via hotkey + * + * UX: + * - Partial text shown as preedit (underlined) + * - Final text committed on stop/silence + * - Escape cancels without committing + * - Enter accepts current preedit + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +namespace { + +FCITX_DEFINE_LOG_CATEGORY(stt_log, "stt"); +#define STT_DEBUG() FCITX_LOGC(stt_log, Debug) +#define STT_INFO() FCITX_LOGC(stt_log, Info) +#define STT_WARN() FCITX_LOGC(stt_log, Warn) +#define STT_ERROR() FCITX_LOGC(stt_log, Error) + +// Operating modes +enum class SttMode { + Oneshot, + Continuous, + Manual +}; + +// Simple JSON parsing (we only need a few fields) +struct JsonEvent { + std::string type; + std::string text; + std::string message; + + static JsonEvent parse(const std::string& line) { + JsonEvent ev; + // Very basic JSON parsing - find "type" and "text" fields + auto findValue = [&line](const std::string& key) -> std::string { + std::string search = "\"" + key + "\":\""; + auto pos = line.find(search); + if (pos == std::string::npos) return ""; + pos += search.length(); + auto end = line.find("\"", pos); + if (end == std::string::npos) return ""; + return line.substr(pos, end - pos); + }; + + ev.type = findValue("type"); + ev.text = findValue("text"); + ev.message = findValue("message"); + return ev; + } +}; + +} // anonymous namespace + +class SttEngine; + +class SttState : public fcitx::InputContextProperty { +public: + SttState(SttEngine* engine, fcitx::InputContext* ic) + : engine_(engine), ic_(ic) {} + + void setPreedit(const std::string& text); + void commit(const std::string& text); + void clear(); + + bool isRecording() const { return recording_; } + void setRecording(bool r) { recording_ = r; } + + const std::string& preeditText() const { return preedit_; } + +private: + SttEngine* engine_; + fcitx::InputContext* ic_; + std::string preedit_; + bool recording_ = false; +}; + +class SttEngine : public fcitx::InputMethodEngineV2 { +public: + SttEngine(fcitx::Instance* instance); + ~SttEngine() override; + + // InputMethodEngine interface + void activate(const fcitx::InputMethodEntry& entry, + fcitx::InputContextEvent& event) override; + void deactivate(const fcitx::InputMethodEntry& entry, + fcitx::InputContextEvent& event) override; + void keyEvent(const fcitx::InputMethodEntry& entry, + fcitx::KeyEvent& keyEvent) override; + void reset(const fcitx::InputMethodEntry& entry, + fcitx::InputContextEvent& event) override; + + // List input methods this engine provides + std::vector listInputMethods() override { + std::vector result; + result.emplace_back( + "stt", // unique name + _("Speech to Text"), // display name + "*", // language (any) + "stt" // addon name + ); + return result; + } + + fcitx::Instance* instance() { return instance_; } + + // Process management + void startProcess(); + void stopProcess(); + void sendCommand(const std::string& cmd); + + // Mode + SttMode mode() const { return mode_; } + void setMode(SttMode m); + void cycleMode(); + +private: + void onProcessOutput(); + void handleEvent(const JsonEvent& ev); + + fcitx::Instance* instance_; + fcitx::FactoryFor factory_; + + // Process state + pid_t childPid_ = -1; + int stdinFd_ = -1; + int stdoutFd_ = -1; + std::unique_ptr ioEvent_; + std::string readBuffer_; + + // Mode + SttMode mode_ = SttMode::Manual; + + // Current state + bool ready_ = false; + fcitx::InputContext* activeIc_ = nullptr; +}; + +// SttState implementation +void SttState::setPreedit(const std::string& text) { + preedit_ = text; + if (ic_->hasFocus()) { + fcitx::Text preeditText; + preeditText.append(text, fcitx::TextFormatFlag::Underline); + preeditText.setCursor(text.length()); + ic_->inputPanel().setClientPreedit(preeditText); + ic_->updatePreedit(); + } +} + +void SttState::commit(const std::string& text) { + if (!text.empty() && ic_->hasFocus()) { + ic_->commitString(text); + } + clear(); +} + +void SttState::clear() { + preedit_.clear(); + if (ic_->hasFocus()) { + ic_->inputPanel().reset(); + ic_->updatePreedit(); + ic_->updateUserInterface(fcitx::UserInterfaceComponent::InputPanel); + } +} + +// SttEngine implementation +SttEngine::SttEngine(fcitx::Instance* instance) + : instance_(instance), + factory_([this](fcitx::InputContext& ic) { + return new SttState(this, &ic); + }) { + instance_->inputContextManager().registerProperty("sttState", &factory_); + STT_INFO() << "SttEngine initialized"; +} + +SttEngine::~SttEngine() { + stopProcess(); +} + +void SttEngine::activate(const fcitx::InputMethodEntry& entry, + fcitx::InputContextEvent& event) { + FCITX_UNUSED(entry); + auto* ic = event.inputContext(); + activeIc_ = ic; + + STT_INFO() << "STT activated"; + + // Start the backend process if not running + if (childPid_ < 0) { + startProcess(); + } + + // In continuous mode, start recording automatically + if (mode_ == SttMode::Continuous && ready_) { + sendCommand("start"); + auto* state = ic->propertyFor(&factory_); + state->setRecording(true); + } +} + +void SttEngine::deactivate(const fcitx::InputMethodEntry& entry, + fcitx::InputContextEvent& event) { + FCITX_UNUSED(entry); + auto* ic = event.inputContext(); + auto* state = ic->propertyFor(&factory_); + + // Stop recording if active + if (state->isRecording()) { + sendCommand("cancel"); + state->setRecording(false); + } + state->clear(); + + activeIc_ = nullptr; + STT_INFO() << "STT deactivated"; +} + +void SttEngine::keyEvent(const fcitx::InputMethodEntry& entry, + fcitx::KeyEvent& keyEvent) { + FCITX_UNUSED(entry); + auto* ic = keyEvent.inputContext(); + auto* state = ic->propertyFor(&factory_); + + // Handle special keys + if (keyEvent.isRelease()) { + return; + } + + auto key = keyEvent.key(); + + // Escape: cancel recording/preedit + if (key.check(FcitxKey_Escape)) { + if (state->isRecording() || !state->preeditText().empty()) { + sendCommand("cancel"); + state->setRecording(false); + state->clear(); + keyEvent.filterAndAccept(); + return; + } + } + + // Enter/Return: accept preedit + if (key.check(FcitxKey_Return) || key.check(FcitxKey_KP_Enter)) { + if (!state->preeditText().empty()) { + state->commit(state->preeditText()); + sendCommand("cancel"); + state->setRecording(false); + keyEvent.filterAndAccept(); + return; + } + } + + // Space or Ctrl+R: toggle recording (in manual mode) + if (mode_ == SttMode::Manual) { + if (key.check(FcitxKey_space, fcitx::KeyState::Ctrl) || + key.check(FcitxKey_r, fcitx::KeyState::Ctrl)) { + if (state->isRecording()) { + sendCommand("stop"); + state->setRecording(false); + } else { + state->clear(); + sendCommand("start"); + state->setRecording(true); + } + keyEvent.filterAndAccept(); + return; + } + } + + // Ctrl+M: cycle mode + if (key.check(FcitxKey_m, fcitx::KeyState::Ctrl)) { + cycleMode(); + keyEvent.filterAndAccept(); + return; + } + + // In recording state, absorb most keys + if (state->isRecording()) { + keyEvent.filterAndAccept(); + return; + } +} + +void SttEngine::reset(const fcitx::InputMethodEntry& entry, + fcitx::InputContextEvent& event) { + FCITX_UNUSED(entry); + auto* ic = event.inputContext(); + auto* state = ic->propertyFor(&factory_); + state->clear(); +} + +void SttEngine::startProcess() { + if (childPid_ > 0) { + return; // Already running + } + + int stdinPipe[2]; + int stdoutPipe[2]; + + if (pipe(stdinPipe) < 0 || pipe(stdoutPipe) < 0) { + STT_ERROR() << "Failed to create pipes"; + return; + } + + pid_t pid = fork(); + if (pid < 0) { + STT_ERROR() << "Failed to fork"; + close(stdinPipe[0]); + close(stdinPipe[1]); + close(stdoutPipe[0]); + close(stdoutPipe[1]); + return; + } + + if (pid == 0) { + // Child process + close(stdinPipe[1]); + close(stdoutPipe[0]); + + dup2(stdinPipe[0], STDIN_FILENO); + dup2(stdoutPipe[1], STDOUT_FILENO); + + close(stdinPipe[0]); + close(stdoutPipe[1]); + + // Determine mode string + const char* modeStr = "manual"; + switch (mode_) { + case SttMode::Oneshot: modeStr = "oneshot"; break; + case SttMode::Continuous: modeStr = "continuous"; break; + case SttMode::Manual: modeStr = "manual"; break; + } + + execlp(STT_STREAM_PATH, "stt-stream", "--mode", modeStr, nullptr); + _exit(127); + } + + // Parent process + close(stdinPipe[0]); + close(stdoutPipe[1]); + + childPid_ = pid; + stdinFd_ = stdinPipe[1]; + stdoutFd_ = stdoutPipe[0]; + + // Set stdout non-blocking + int flags = fcntl(stdoutFd_, F_GETFL, 0); + fcntl(stdoutFd_, F_SETFL, flags | O_NONBLOCK); + + // Watch stdout for events + ioEvent_ = instance_->eventLoop().addIOEvent( + stdoutFd_, + fcitx::IOEventFlag::In, + [this](fcitx::EventSourceIO*, int, fcitx::IOEventFlags) { + onProcessOutput(); + return true; + } + ); + + STT_INFO() << "Started stt-stream process (pid=" << childPid_ << ")"; +} + +void SttEngine::stopProcess() { + if (childPid_ < 0) { + return; + } + + ioEvent_.reset(); + + sendCommand("shutdown"); + close(stdinFd_); + close(stdoutFd_); + + // Wait for child to exit + int status; + waitpid(childPid_, &status, 0); + + stdinFd_ = -1; + stdoutFd_ = -1; + childPid_ = -1; + ready_ = false; + + STT_INFO() << "Stopped stt-stream process"; +} + +void SttEngine::sendCommand(const std::string& cmd) { + if (stdinFd_ < 0) { + return; + } + + std::string line = cmd + "\n"; + write(stdinFd_, line.c_str(), line.length()); +} + +void SttEngine::onProcessOutput() { + char buf[4096]; + ssize_t n; + + while ((n = read(stdoutFd_, buf, sizeof(buf) - 1)) > 0) { + buf[n] = '\0'; + readBuffer_ += buf; + + // Process complete lines + size_t pos; + while ((pos = readBuffer_.find('\n')) != std::string::npos) { + std::string line = readBuffer_.substr(0, pos); + readBuffer_ = readBuffer_.substr(pos + 1); + + if (!line.empty()) { + auto ev = JsonEvent::parse(line); + handleEvent(ev); + } + } + } +} + +void SttEngine::handleEvent(const JsonEvent& ev) { + STT_DEBUG() << "Event: type=" << ev.type << " text=" << ev.text; + + if (ev.type == "ready") { + ready_ = true; + STT_INFO() << "stt-stream ready"; + } else if (ev.type == "recording_started") { + // Update UI to show recording state + if (activeIc_) { + auto* state = activeIc_->propertyFor(&factory_); + state->setRecording(true); + } + } else if (ev.type == "recording_stopped") { + if (activeIc_) { + auto* state = activeIc_->propertyFor(&factory_); + state->setRecording(false); + } + } else if (ev.type == "partial") { + if (activeIc_) { + auto* state = activeIc_->propertyFor(&factory_); + state->setPreedit(ev.text); + } + } else if (ev.type == "final") { + if (activeIc_) { + auto* state = activeIc_->propertyFor(&factory_); + state->commit(ev.text); + state->setRecording(false); + + // In oneshot mode, we're done + // In continuous mode, keep listening + if (mode_ == SttMode::Continuous && ready_) { + sendCommand("start"); + state->setRecording(true); + } + } + } else if (ev.type == "error") { + STT_ERROR() << "stt-stream error: " << ev.message; + } else if (ev.type == "shutdown") { + ready_ = false; + } +} + +void SttEngine::setMode(SttMode m) { + if (mode_ == m) return; + + mode_ = m; + + // Notify the backend + const char* modeStr = "manual"; + switch (m) { + case SttMode::Oneshot: modeStr = "oneshot"; break; + case SttMode::Continuous: modeStr = "continuous"; break; + case SttMode::Manual: modeStr = "manual"; break; + } + + std::string cmd = "{\"cmd\":\"set_mode\",\"mode\":\""; + cmd += modeStr; + cmd += "\"}"; + sendCommand(cmd); + + STT_INFO() << "Mode changed to: " << modeStr; +} + +void SttEngine::cycleMode() { + switch (mode_) { + case SttMode::Manual: + setMode(SttMode::Oneshot); + break; + case SttMode::Oneshot: + setMode(SttMode::Continuous); + break; + case SttMode::Continuous: + setMode(SttMode::Manual); + break; + } +} + +// Addon factory +class SttEngineFactory : public fcitx::AddonFactory { +public: + fcitx::AddonInstance* create(fcitx::AddonManager* manager) override { + return new SttEngine(manager->instance()); + } +}; + +FCITX_ADDON_FACTORY(SttEngineFactory); diff --git a/flakes/stt_ime/flake.lock b/flakes/stt_ime/flake.lock new file mode 100644 index 00000000..c22bd6f2 --- /dev/null +++ b/flakes/stt_ime/flake.lock @@ -0,0 +1,77 @@ +{ + "nodes": { + "crane": { + "locked": { + "lastModified": 1768319649, + "narHash": "sha256-VFkNyxHxkqGp8gf8kfFMW1j6XeBy609kv6TE9uF/0Js=", + "owner": "ipetkov", + "repo": "crane", + "rev": "4b6527687cfd20da3c2ef8287e01b74c2d6c705b", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1768127708, + "narHash": "sha256-1Sm77VfZh3mU0F5OqKABNLWxOuDeHIlcFjsXeeiPazs=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "ffbc9f8cbaacfb331b6017d5a5abb21a492c9a38", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "crane": "crane", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flakes/stt_ime/flake.nix b/flakes/stt_ime/flake.nix new file mode 100644 index 00000000..0e6ae14d --- /dev/null +++ b/flakes/stt_ime/flake.nix @@ -0,0 +1,166 @@ +{ + description = "Local speech-to-text input method for Fcitx5"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + crane.url = "github:ipetkov/crane"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = + { + self, + nixpkgs, + crane, + flake-utils, + ... + }: + let + # Systems we support + supportedSystems = [ + "x86_64-linux" + "aarch64-linux" + ]; + in + flake-utils.lib.eachSystem supportedSystems ( + system: + let + pkgs = nixpkgs.legacyPackages.${system}; + craneLib = crane.mkLib pkgs; + + # Rust STT streaming CLI + stt-stream = craneLib.buildPackage { + pname = "stt-stream"; + version = "0.1.0"; + src = craneLib.cleanCargoSource ./stt-stream; + + nativeBuildInputs = with pkgs; [ + pkg-config + cmake # for whisper-rs + clang + llvmPackages.libclang + ]; + + buildInputs = with pkgs; [ + alsa-lib + openssl + # whisper.cpp dependencies + openblas + ]; + + # For bindgen to find libclang + LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib"; + + # Enable CUDA if available (user can override) + WHISPER_CUBLAS = "OFF"; + }; + + # Fcitx5 C++ shim addon + fcitx5-stt = pkgs.stdenv.mkDerivation { + pname = "fcitx5-stt"; + version = "0.1.0"; + src = ./fcitx5-stt; + + nativeBuildInputs = with pkgs; [ + cmake + extra-cmake-modules + pkg-config + ]; + + buildInputs = with pkgs; [ + fcitx5 + ]; + + cmakeFlags = [ + "-DSTT_STREAM_PATH=${stt-stream}/bin/stt-stream" + ]; + + # Install to fcitx5 addon paths + postInstall = '' + mkdir -p $out/share/fcitx5/addon + mkdir -p $out/share/fcitx5/inputmethod + mkdir -p $out/lib/fcitx5 + ''; + }; + in + { + packages = { + inherit stt-stream fcitx5-stt; + default = fcitx5-stt; + }; + + # Expose as runnable apps + apps = { + stt-stream = { + type = "app"; + program = "${stt-stream}/bin/stt-stream"; + }; + default = { + type = "app"; + program = "${stt-stream}/bin/stt-stream"; + }; + }; + + devShells.default = pkgs.mkShell { + inputsFrom = [ stt-stream ]; + packages = with pkgs; [ + rust-analyzer + rustfmt + clippy + fcitx5 + ]; + }; + } + ) + // { + # NixOS module for integration + nixosModules.default = + { + config, + lib, + pkgs, + ... + }: + let + cfg = config.ringofstorms.sttIme; + sttPkgs = self.packages.${pkgs.stdenv.hostPlatform.system}; + in + { + options.ringofstorms.sttIme = { + enable = lib.mkEnableOption "Speech-to-text input method for Fcitx5"; + + model = lib.mkOption { + type = lib.types.str; + default = "base.en"; + description = "Whisper model to use (tiny, base, small, medium, large)"; + }; + + useGpu = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to use GPU acceleration (CUDA)"; + }; + }; + + config = lib.mkIf cfg.enable { + # Ensure fcitx5 addon is available + i18n.inputMethod.fcitx5.addons = [ sttPkgs.fcitx5-stt ]; + + # Add STT to the Fcitx5 input method group + # This assumes de_plasma sets up Groups/0 with keyboard-us (0) and mozc (1) + i18n.inputMethod.fcitx5.settings.inputMethod = { + "Groups/0/Items/2".Name = "stt"; + }; + + # Make stt-stream available system-wide + environment.systemPackages = [ sttPkgs.stt-stream ]; + + # Set default model via environment + environment.sessionVariables = { + STT_STREAM_MODEL = cfg.model; + STT_STREAM_USE_GPU = if cfg.useGpu then "1" else "0"; + }; + }; + }; + }; +} diff --git a/flakes/stt_ime/stt-stream/Cargo.lock b/flakes/stt_ime/stt-stream/Cargo.lock new file mode 100644 index 00000000..b25c4923 --- /dev/null +++ b/flakes/stt_ime/stt-stream/Cargo.lock @@ -0,0 +1,2487 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alsa" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" +dependencies = [ + "alsa-sys", + "bitflags 2.10.0", + "cfg-if", + "libc", +] + +[[package]] +name = "alsa-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags 2.10.0", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn", + "which", +] + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.10.0", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.1", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "cc" +version = "1.2.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" + +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys 0.59.0", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "coreaudio-rs" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace" +dependencies = [ + "bitflags 1.3.2", + "core-foundation-sys", + "coreaudio-sys", +] + +[[package]] +name = "coreaudio-sys" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ceec7a6067e62d6f931a2baf6f3a751f4a892595bcec1461a3c94ef9949864b6" +dependencies = [ + "bindgen 0.72.1", +] + +[[package]] +name = "cpal" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779" +dependencies = [ + "alsa", + "core-foundation-sys", + "coreaudio-rs", + "dasp_sample", + "jni", + "js-sys", + "libc", + "mach2", + "ndk", + "ndk-context", + "oboe", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "dasp_sample" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "find-msvc-tools" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" + +[[package]] +name = "flate2" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hf-hub" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b780635574b3d92f036890d8373433d6f9fc7abb320ee42a5c25897fc8ed732" +dependencies = [ + "dirs", + "indicatif", + "log", + "native-tls", + "rand", + "serde", + "serde_json", + "thiserror", + "ureq", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "indicatif" +version = "0.17.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" +dependencies = [ + "console", + "number_prefix", + "portable-atomic", + "unicode-width", + "web-time", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags 2.10.0", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "ndk" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" +dependencies = [ + "bitflags 2.10.0", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "oboe" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb" +dependencies = [ + "jni", + "ndk", + "ndk-context", + "num-derive", + "num-traits", + "oboe-sys", +] + +[[package]] +name = "oboe-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bb09a4a2b1d668170cfe0a7d5bc103f8999fb316c98099b6a9939c9f2e79d" +dependencies = [ + "cc", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "portable-atomic" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "primal-check" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0d895b311e3af9902528fbb8f928688abbd95872819320517cc24ca6b2bd08" +dependencies = [ + "num-integer", +] + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "realfft" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f821338fddb99d089116342c46e9f1fbf3828dba077674613e734e01d6ea8677" +dependencies = [ + "rustfft", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rubato" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5d18b486e7d29a408ef3f825bc1327d8f87af091c987ca2f5b734625940e234" +dependencies = [ + "num-complex", + "num-integer", + "num-traits", + "realfft", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustfft" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21db5f9893e91f41798c88680037dba611ca6674703c1a18601b01a72c8adb89" +dependencies = [ + "num-complex", + "num-integer", + "num-traits", + "primal-check", + "strength_reduce", + "transpose", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.10.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "stt-stream" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "cpal", + "hf-hub", + "rubato", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "tracing-subscriber", + "whisper-rs", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tempfile" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix 1.1.3", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.23.10+spec-1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "transpose" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e" +dependencies = [ + "num-integer", + "strength_reduce", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +dependencies = [ + "base64", + "flate2", + "log", + "native-tls", + "once_cell", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "url", + "webpki-roots 0.26.11", +] + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.5", +] + +[[package]] +name = "webpki-roots" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + +[[package]] +name = "whisper-rs" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c597ac8a9d5c4719fee232abc871da184ea50a4fea38d2d00348fd95072b2b0" +dependencies = [ + "whisper-rs-sys", +] + +[[package]] +name = "whisper-rs-sys" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22f00ed0995463eecc34ef89905845f6bf6fd37ea70789fed180520050da8f8" +dependencies = [ + "bindgen 0.69.5", + "cfg-if", + "cmake", + "fs_extra", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +dependencies = [ + "windows-core", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" diff --git a/flakes/stt_ime/stt-stream/Cargo.toml b/flakes/stt_ime/stt-stream/Cargo.toml new file mode 100644 index 00000000..cbd7c8de --- /dev/null +++ b/flakes/stt_ime/stt-stream/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "stt-stream" +version = "0.1.0" +edition = "2021" +description = "Local speech-to-text streaming CLI for Fcitx5 integration" +license = "MIT" + +[dependencies] +# Audio capture +cpal = "0.15" +# Resampling (48k -> 16k) +rubato = "0.15" +# Whisper inference +whisper-rs = "0.12" +# Voice activity detection +# Using silero via ONNX (reserved for future use) +# ort = { version = "2.0.0-rc.9", default-features = false, features = ["load-dynamic"] } +# ndarray = "0.16" + +# Async runtime +tokio = { version = "1", features = ["full"] } + +# CLI +clap = { version = "4", features = ["derive"] } + +# Serialization for IPC protocol +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +# Error handling +anyhow = "1" +thiserror = "1" + +# Logging +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } + +# Ring buffer for audio (reserved for future use) +# ringbuf = "0.4" + +# For downloading models +hf-hub = "0.3" + +[features] +default = [] +cuda = ["whisper-rs/cuda"] + +[profile.release] +lto = true +codegen-units = 1 diff --git a/flakes/stt_ime/stt-stream/src/main.rs b/flakes/stt_ime/stt-stream/src/main.rs new file mode 100644 index 00000000..e0f92082 --- /dev/null +++ b/flakes/stt_ime/stt-stream/src/main.rs @@ -0,0 +1,599 @@ +//! stt-stream: Local speech-to-text streaming CLI +//! +//! Captures audio from microphone, performs VAD, transcribes with Whisper, +//! and outputs JSON events to stdout for Fcitx5 integration. + +use anyhow::{Context, Result}; +use clap::{Parser, ValueEnum}; +use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; +use rubato::{FftFixedInOut, Resampler}; +use serde::{Deserialize, Serialize}; +use std::io::{BufRead, Write}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex}; +use tokio::sync::mpsc; +use tracing::{error, info, warn}; +use whisper_rs::{FullParams, SamplingStrategy, WhisperContext, WhisperContextParameters}; + +/// Operating mode for the STT engine +#[derive(Debug, Clone, Copy, ValueEnum, PartialEq, Eq)] +pub enum Mode { + /// Record until silence, transcribe, then reset (one-shot) + Oneshot, + /// Always listen, emit text when speech detected (continuous) + Continuous, + /// Manual start/stop via stdin commands + Manual, +} + +/// Whisper model size +#[derive(Debug, Clone, Copy, ValueEnum)] +pub enum ModelSize { + Tiny, + TinyEn, + Base, + BaseEn, + Small, + SmallEn, + Medium, + MediumEn, + LargeV3, +} + +impl ModelSize { + fn model_name(&self) -> &'static str { + match self { + ModelSize::Tiny => "tiny", + ModelSize::TinyEn => "tiny.en", + ModelSize::Base => "base", + ModelSize::BaseEn => "base.en", + ModelSize::Small => "small", + ModelSize::SmallEn => "small.en", + ModelSize::Medium => "medium", + ModelSize::MediumEn => "medium.en", + ModelSize::LargeV3 => "large-v3", + } + } + + fn hf_repo(&self) -> &'static str { + "ggerganov/whisper.cpp" + } + + fn hf_filename(&self) -> String { + format!("ggml-{}.bin", self.model_name()) + } +} + +#[derive(Parser, Debug)] +#[command(name = "stt-stream")] +#[command(about = "Local speech-to-text streaming for Fcitx5")] +struct Args { + /// Operating mode + #[arg(short, long, value_enum, default_value = "manual")] + mode: Mode, + + /// Whisper model size + #[arg(short = 'M', long, value_enum, default_value = "base-en")] + model: ModelSize, + + /// Path to whisper model file (overrides --model) + #[arg(long)] + model_path: Option, + + /// VAD threshold (0.0-1.0) + #[arg(long, default_value = "0.5")] + vad_threshold: f32, + + /// Silence duration (ms) to end utterance + #[arg(long, default_value = "800")] + silence_ms: u64, + + /// Emit partial transcripts while speaking + #[arg(long, default_value = "true")] + partials: bool, + + /// Partial transcript interval (ms) + #[arg(long, default_value = "500")] + partial_interval_ms: u64, + + /// Language code (e.g., "en", "ja", "auto") + #[arg(short, long, default_value = "en")] + language: String, + + /// Use GPU acceleration + #[arg(long)] + gpu: bool, +} + +/// Events emitted to stdout as NDJSON +#[derive(Debug, Serialize)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum SttEvent { + /// STT engine is ready + Ready, + /// Recording started + RecordingStarted, + /// Recording stopped + RecordingStopped, + /// Partial (unstable) transcript + Partial { text: String }, + /// Final transcript + Final { text: String }, + /// Error occurred + Error { message: String }, + /// Engine shutting down + Shutdown, +} + +/// Commands received from stdin as NDJSON +#[derive(Debug, Deserialize)] +#[serde(tag = "cmd", rename_all = "snake_case")] +pub enum SttCommand { + /// Start recording + Start, + /// Stop recording and transcribe + Stop, + /// Cancel current recording without transcribing + Cancel, + /// Shutdown the engine + Shutdown, + /// Switch mode + SetMode { mode: String }, +} + +fn emit_event(event: &SttEvent) { + if let Ok(json) = serde_json::to_string(event) { + let mut stdout = std::io::stdout().lock(); + let _ = writeln!(stdout, "{}", json); + let _ = stdout.flush(); + } +} + +/// Simple energy-based VAD (placeholder for Silero VAD) +/// Returns true if the audio chunk likely contains speech +fn simple_vad(samples: &[f32], threshold: f32) -> bool { + if samples.is_empty() { + return false; + } + let energy: f32 = samples.iter().map(|s| s * s).sum::() / samples.len() as f32; + let db = 10.0 * energy.max(1e-10).log10(); + // Typical speech is around -20 to -10 dB, silence is < -40 dB + // Map threshold 0-1 to dB range -50 to -20 + let threshold_db = -50.0 + (threshold * 30.0); + db > threshold_db +} + +/// Download or locate the Whisper model +fn get_model_path(args: &Args) -> Result { + if let Some(ref path) = args.model_path { + return Ok(path.clone()); + } + + // Check environment variable + if let Ok(path) = std::env::var("STT_STREAM_MODEL_PATH") { + if std::path::Path::new(&path).exists() { + return Ok(path); + } + } + + // Check XDG cache + let cache_dir = dirs::cache_dir() + .unwrap_or_else(|| std::path::PathBuf::from(".")) + .join("stt-stream") + .join("models"); + + let model_file = cache_dir.join(args.model.hf_filename()); + if model_file.exists() { + return Ok(model_file.to_string_lossy().to_string()); + } + + // Download from Hugging Face + info!("Downloading model {} from Hugging Face...", args.model.model_name()); + std::fs::create_dir_all(&cache_dir)?; + + let api = hf_hub::api::sync::Api::new()?; + let repo = api.model(args.model.hf_repo().to_string()); + let path = repo.get(&args.model.hf_filename())?; + + Ok(path.to_string_lossy().to_string()) +} + +/// Audio processing state +struct AudioState { + /// Audio samples buffer (16kHz mono) + buffer: Vec, + /// Whether we're currently recording + is_recording: bool, + /// Whether speech was detected in current segment + speech_detected: bool, + /// Samples since last speech + silence_samples: usize, + /// Last partial emission time + last_partial: std::time::Instant, +} + +impl AudioState { + fn new() -> Self { + Self { + buffer: Vec::with_capacity(16000 * 30), // 30 seconds max + is_recording: false, + speech_detected: false, + silence_samples: 0, + last_partial: std::time::Instant::now(), + } + } + + fn clear(&mut self) { + self.buffer.clear(); + self.speech_detected = false; + self.silence_samples = 0; + } +} + +#[tokio::main] +async fn main() -> Result<()> { + // Initialize logging + tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::from_default_env() + .add_directive("stt_stream=info".parse().unwrap()), + ) + .with_writer(std::io::stderr) + .init(); + + let args = Args::parse(); + info!("Starting stt-stream with mode: {:?}", args.mode); + + // Load Whisper model + let model_path = get_model_path(&args).context("Failed to get model path")?; + info!("Loading Whisper model from: {}", model_path); + + let ctx_params = WhisperContextParameters::default(); + let whisper_ctx = WhisperContext::new_with_params(&model_path, ctx_params) + .context("Failed to load Whisper model")?; + + let whisper_ctx = Arc::new(Mutex::new(whisper_ctx)); + + // Audio capture setup + let host = cpal::default_host(); + let device = host + .default_input_device() + .context("No input device available")?; + + info!("Using input device: {}", device.name().unwrap_or_default()); + + let config = device.default_input_config()?; + let sample_rate = config.sample_rate().0; + let channels = config.channels() as usize; + + info!("Input config: {}Hz, {} channels", sample_rate, channels); + + // Resampler: input rate -> 16kHz + let resampler = if sample_rate != 16000 { + Some(Arc::new(Mutex::new( + FftFixedInOut::::new(sample_rate as usize, 16000, 1024, 1) + .context("Failed to create resampler")?, + ))) + } else { + None + }; + + // Shared state + let audio_state = Arc::new(Mutex::new(AudioState::new())); + let running = Arc::new(AtomicBool::new(true)); + let mode = Arc::new(Mutex::new(args.mode)); + + // Channel for audio data + let (audio_tx, mut audio_rx) = mpsc::channel::>(100); + + // Audio callback + let resampler_clone = resampler.clone(); + let running_clone = running.clone(); + + let stream = device.build_input_stream( + &config.into(), + move |data: &[f32], _: &cpal::InputCallbackInfo| { + if !running_clone.load(Ordering::Relaxed) { + return; + } + + // Convert to mono if needed + let mono: Vec = if channels > 1 { + data.chunks(channels) + .map(|frame| frame.iter().sum::() / channels as f32) + .collect() + } else { + data.to_vec() + }; + + // Resample if needed + let resampled = if let Some(ref resampler) = resampler_clone { + if let Ok(mut r) = resampler.lock() { + // Pad input to required length + let input_frames = r.input_frames_next(); + if mono.len() >= input_frames { + let input = vec![mono[..input_frames].to_vec()]; + match r.process(&input, None) { + Ok(output) => output.into_iter().flatten().collect(), + Err(_) => return, + } + } else { + return; + } + } else { + return; + } + } else { + mono + }; + + let _ = audio_tx.blocking_send(resampled); + }, + |err| { + error!("Audio stream error: {}", err); + }, + None, + )?; + + stream.play()?; + emit_event(&SttEvent::Ready); + + // Stdin command reader + let running_stdin = running.clone(); + let mode_stdin = mode.clone(); + let audio_state_stdin = audio_state.clone(); + + let stdin_handle = std::thread::spawn(move || { + let stdin = std::io::stdin(); + for line in stdin.lock().lines() { + if !running_stdin.load(Ordering::Relaxed) { + break; + } + + let line = match line { + Ok(l) => l, + Err(_) => continue, + }; + + let cmd: SttCommand = match serde_json::from_str(&line) { + Ok(c) => c, + Err(_) => { + // Try simple text commands + match line.trim().to_lowercase().as_str() { + "start" => SttCommand::Start, + "stop" => SttCommand::Stop, + "cancel" => SttCommand::Cancel, + "shutdown" | "quit" | "exit" => SttCommand::Shutdown, + _ => continue, + } + } + }; + + match cmd { + SttCommand::Start => { + if let Ok(mut state) = audio_state_stdin.lock() { + state.is_recording = true; + state.clear(); + emit_event(&SttEvent::RecordingStarted); + } + } + SttCommand::Stop => { + if let Ok(mut state) = audio_state_stdin.lock() { + state.is_recording = false; + emit_event(&SttEvent::RecordingStopped); + } + } + SttCommand::Cancel => { + if let Ok(mut state) = audio_state_stdin.lock() { + state.is_recording = false; + state.clear(); + emit_event(&SttEvent::RecordingStopped); + } + } + SttCommand::Shutdown => { + running_stdin.store(false, Ordering::Relaxed); + break; + } + SttCommand::SetMode { mode: m } => { + if let Ok(mut current_mode) = mode_stdin.lock() { + *current_mode = match m.as_str() { + "oneshot" => Mode::Oneshot, + "continuous" => Mode::Continuous, + "manual" => Mode::Manual, + _ => continue, + }; + } + } + } + } + }); + + // Main processing loop + let vad_threshold = args.vad_threshold; + let silence_samples_threshold = (args.silence_ms as f32 * 16.0) as usize; // 16 samples per ms at 16kHz + let partial_interval = std::time::Duration::from_millis(args.partial_interval_ms); + let emit_partials = args.partials; + let language = args.language.clone(); + + while running.load(Ordering::Relaxed) { + // Receive audio data + let samples = match tokio::time::timeout( + std::time::Duration::from_millis(100), + audio_rx.recv(), + ) + .await + { + Ok(Some(s)) => s, + Ok(None) => break, + Err(_) => continue, // Timeout, check running flag + }; + + let current_mode = *mode.lock().unwrap(); + let mut state = audio_state.lock().unwrap(); + + // Mode-specific behavior + match current_mode { + Mode::Manual => { + if !state.is_recording { + continue; + } + } + Mode::Oneshot | Mode::Continuous => { + // Auto-start on speech detection + let has_speech = simple_vad(&samples, vad_threshold); + + if !state.is_recording && has_speech { + state.is_recording = true; + state.clear(); + emit_event(&SttEvent::RecordingStarted); + } + + if !state.is_recording { + continue; + } + } + } + + // Accumulate audio + state.buffer.extend_from_slice(&samples); + + // VAD check + let has_speech = simple_vad(&samples, vad_threshold); + if has_speech { + state.speech_detected = true; + state.silence_samples = 0; + } else { + state.silence_samples += samples.len(); + } + + // Emit partial transcript if enabled + if emit_partials + && state.speech_detected + && state.last_partial.elapsed() > partial_interval + && state.buffer.len() > 16000 // At least 1 second + { + state.last_partial = std::time::Instant::now(); + let buffer_copy = state.buffer.clone(); + let ctx = whisper_ctx.clone(); + let lang = language.clone(); + + // Transcribe in background + tokio::task::spawn_blocking(move || { + if let Ok(text) = transcribe(&ctx, &buffer_copy, &lang, false) { + if !text.is_empty() { + emit_event(&SttEvent::Partial { text }); + } + } + }); + } + + // Check for end of utterance + let should_finalize = match current_mode { + Mode::Manual => !state.is_recording && state.speech_detected, + Mode::Oneshot | Mode::Continuous => { + state.speech_detected && state.silence_samples > silence_samples_threshold + } + }; + + if should_finalize && !state.buffer.is_empty() { + let buffer_copy = state.buffer.clone(); + let ctx = whisper_ctx.clone(); + let lang = language.clone(); + + // Final transcription + match transcribe(&ctx, &buffer_copy, &lang, true) { + Ok(text) => { + if !text.is_empty() { + emit_event(&SttEvent::Final { text }); + } + } + Err(e) => { + emit_event(&SttEvent::Error { + message: e.to_string(), + }); + } + } + + state.clear(); + state.is_recording = current_mode == Mode::Continuous; + + if current_mode == Mode::Oneshot { + emit_event(&SttEvent::RecordingStopped); + } + } + + // Prevent buffer from growing too large + if state.buffer.len() > 16000 * 30 { + warn!("Buffer too large, truncating"); + let start = state.buffer.len() - 16000 * 20; + state.buffer = state.buffer[start..].to_vec(); + } + } + + // Cleanup + drop(stream); + emit_event(&SttEvent::Shutdown); + let _ = stdin_handle.join(); + + Ok(()) +} + +/// Transcribe audio buffer using Whisper +fn transcribe( + ctx: &Arc>, + samples: &[f32], + language: &str, + is_final: bool, +) -> Result { + let ctx = ctx.lock().map_err(|_| anyhow::anyhow!("Lock poisoned"))?; + let mut state = ctx.create_state()?; + + let mut params = FullParams::new(SamplingStrategy::Greedy { best_of: 1 }); + + // Configure for speed vs accuracy + if is_final { + params.set_n_threads(4); + } else { + params.set_n_threads(2); + params.set_no_context(true); + } + + params.set_language(Some(language)); + params.set_print_special(false); + params.set_print_progress(false); + params.set_print_realtime(false); + params.set_print_timestamps(false); + params.set_suppress_blank(true); + params.set_suppress_non_speech_tokens(true); + + // Run inference + state.full(params, samples)?; + + // Collect segments + let num_segments = state.full_n_segments()?; + let mut text = String::new(); + + for i in 0..num_segments { + if let Ok(segment) = state.full_get_segment_text(i) { + text.push_str(&segment); + } + } + + Ok(text.trim().to_string()) +} + +/// Stub for dirs crate functionality +mod dirs { + use std::path::PathBuf; + + pub fn cache_dir() -> Option { + std::env::var("XDG_CACHE_HOME") + .map(PathBuf::from) + .ok() + .or_else(|| { + std::env::var("HOME") + .map(|h| PathBuf::from(h).join(".cache")) + .ok() + }) + } +} diff --git a/hosts/lio/flake.lock b/hosts/lio/flake.lock index 8e8563ce..3ff8fa39 100644 --- a/hosts/lio/flake.lock +++ b/hosts/lio/flake.lock @@ -87,6 +87,21 @@ "type": "github" } }, + "crane_2": { + "locked": { + "lastModified": 1768319649, + "narHash": "sha256-VFkNyxHxkqGp8gf8kfFMW1j6XeBy609kv6TE9uF/0Js=", + "owner": "ipetkov", + "repo": "crane", + "rev": "4b6527687cfd20da3c2ef8287e01b74c2d6c705b", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, "darwin": { "inputs": { "nixpkgs": [ @@ -116,20 +131,14 @@ "plasma-manager": "plasma-manager" }, "locked": { - "dir": "flakes/de_plasma", - "lastModified": 1768233301, - "narHash": "sha256-m7Og7WuCT8VdQdLhsR6J7ZCR+aFM5ddJ7A1Kt2LBXQs=", - "ref": "refs/heads/master", - "rev": "128209e4aa8927b7514bcfd2acaf097ac0d59310", - "revCount": 1122, - "type": "git", - "url": "https://git.joshuabell.xyz/ringofstorms/dotfiles" + "path": "../../flakes/de_plasma", + "type": "path" }, "original": { - "dir": "flakes/de_plasma", - "type": "git", - "url": "https://git.joshuabell.xyz/ringofstorms/dotfiles" - } + "path": "../../flakes/de_plasma", + "type": "path" + }, + "parent": [] }, "flake-utils": { "inputs": { @@ -149,6 +158,24 @@ "type": "github" } }, + "flake-utils_2": { + "inputs": { + "systems": "systems_3" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, "flatpaks": { "inputs": { "nix-flatpak": "nix-flatpak" @@ -361,6 +388,22 @@ "type": "github" } }, + "nixpkgs_7": { + "locked": { + "lastModified": 1768127708, + "narHash": "sha256-1Sm77VfZh3mU0F5OqKABNLWxOuDeHIlcFjsXeeiPazs=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "ffbc9f8cbaacfb331b6017d5a5abb21a492c9a38", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, "nvim_plugin-Almo7aya/openingh.nvim": { "flake": false, "locked": { @@ -1297,7 +1340,8 @@ "opencode": "opencode", "ros_neovim": "ros_neovim", "secrets": "secrets", - "secrets-bao": "secrets-bao" + "secrets-bao": "secrets-bao", + "stt_ime": "stt_ime" } }, "ros_neovim": { @@ -1447,6 +1491,22 @@ }, "parent": [] }, + "stt_ime": { + "inputs": { + "crane": "crane_2", + "flake-utils": "flake-utils_2", + "nixpkgs": "nixpkgs_7" + }, + "locked": { + "path": "../../flakes/stt_ime", + "type": "path" + }, + "original": { + "path": "../../flakes/stt_ime", + "type": "path" + }, + "parent": [] + }, "systems": { "locked": { "lastModified": 1681028828, @@ -1476,6 +1536,21 @@ "repo": "default", "type": "github" } + }, + "systems_3": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/hosts/lio/flake.nix b/hosts/lio/flake.nix index 45954158..c7b16aeb 100644 --- a/hosts/lio/flake.nix +++ b/hosts/lio/flake.nix @@ -16,8 +16,10 @@ flatpaks.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=flakes/flatpaks"; # beszel.url = "path:../../flakes/beszel"; beszel.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=flakes/beszel"; - # de_plasma.url = "path:../../flakes/de_plasma"; - de_plasma.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=flakes/de_plasma"; + de_plasma.url = "path:../../flakes/de_plasma"; + # de_plasma.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=flakes/de_plasma"; + stt_ime.url = "path:../../flakes/stt_ime"; + # stt_ime.url = "git+https://git.joshuabell.xyz/ringofstorms/dotfiles?dir=flakes/stt_ime"; opencode.url = "github:sst/opencode?ref=latest"; ros_neovim.url = "git+https://git.joshuabell.xyz/ringofstorms/nvim"; @@ -69,6 +71,14 @@ # sddm.autologinUser = "josh"; }; }) + inputs.stt_ime.nixosModules.default + ({ + ringofstorms.sttIme = { + enable = true; + useGpu = true; + }; + }) + secrets.nixosModules.default ros_neovim.nixosModules.default ({