Compare commits
13 Commits
af840601c0
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b1abff0cb | |||
| ede3d963f5 | |||
| bdbd6df9cd | |||
| 08e3f4d272 | |||
| 30cdcd8fba | |||
| 6e2bda5cfb | |||
| a572ca003f | |||
| 1b48e79e2e | |||
| 8922879b7d | |||
| a1f1a1eb79 | |||
| ae7b7d0869 | |||
| 15657df63f | |||
| 0ad663c835 |
@@ -27,3 +27,5 @@ wheels/
|
||||
/Users/wongdingfeng/.config/tooling/.env
|
||||
/Users/wongdingfeng/.config/tooling/.DS_Store
|
||||
/Users/wongdingfeng/.config/tooling/Thumbs.db
|
||||
uv.lock
|
||||
realtimesst.log
|
||||
|
||||
@@ -288,6 +288,22 @@ The following wake words are supported:
|
||||
- porcupine
|
||||
- terminator
|
||||
|
||||
### Wake Word Engines
|
||||
|
||||
Two wake word engines are supported:
|
||||
|
||||
- **openwakeword** (default) - Open source, free to use, good accuracy
|
||||
- **pvporcupine** - Picovoice's Porcupine engine, highly optimized
|
||||
|
||||
Choose the engine based on your requirements:
|
||||
```bash
|
||||
# Use OpenWakeWord (default)
|
||||
tooling stt listen --wakeword-engine openwakeword
|
||||
|
||||
# Use Porcupine for better performance
|
||||
tooling stt listen --wakeword-engine pvporcupine
|
||||
```
|
||||
|
||||
### Available Models
|
||||
|
||||
| Model | Speed | Accuracy | Memory | Use Case |
|
||||
@@ -311,6 +327,7 @@ Options:
|
||||
--save-to-file PATH Save transcriptions to a file
|
||||
--sensitivity FLOAT Wake word sensitivity (0.0 to 1.0) [default: 0.6]
|
||||
--device TEXT Device to use (auto, cuda, cpu) [default: auto]
|
||||
--wakeword-engine TEXT Wake word engine (openwakeword, pvporcupine) [default: openwakeword]
|
||||
--verbose Show verbose output and configuration
|
||||
--help Show this message and exit
|
||||
```
|
||||
@@ -342,6 +359,11 @@ tooling stt test --duration 5 --model tiny
|
||||
tooling stt listen --language es --sensitivity 0.8 --wake-word "hey google"
|
||||
```
|
||||
|
||||
**Use different wake word engine:**
|
||||
```bash
|
||||
tooling stt listen --wakeword-engine pvporcupine --wake-word alexa
|
||||
```
|
||||
|
||||
### How it Works
|
||||
|
||||
1. **Initialization**: Loads the selected Whisper model and sets up audio processing
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
# STT Status Bar App Usage
|
||||
|
||||
## Overview
|
||||
|
||||
The STT (Speech-to-Text) status bar app provides a convenient macOS menu bar interface for controlling speech-to-text functionality using wake word activation.
|
||||
|
||||
## Features
|
||||
|
||||
- **Start/Stop/Pause/Resume**: Full control over STT recording
|
||||
- **Status Indicators**: Visual status in menu bar (🎙️🔴 recording, 🎙️⏸️ paused, 🎙️⚫ stopped)
|
||||
- **Configurable Settings**: Change wake words and models on the fly
|
||||
- **File Output**: Save transcriptions to a file
|
||||
- **Notifications**: Real-time notifications for transcriptions and status changes
|
||||
|
||||
## Usage
|
||||
|
||||
### Starting the Status Bar App
|
||||
|
||||
```bash
|
||||
# Launch the status bar app
|
||||
tooling stt statusbar
|
||||
|
||||
# Or using the full CLI path
|
||||
python -m tooling.cli stt statusbar
|
||||
```
|
||||
|
||||
### Menu Options
|
||||
|
||||
#### Main Controls
|
||||
- **Start STT**: Begin speech-to-text with current settings
|
||||
- **Stop STT**: Stop speech-to-text completely
|
||||
- **Pause**: Temporarily pause recognition (keeps recorder alive)
|
||||
- **Resume**: Resume recognition from pause
|
||||
|
||||
#### Settings
|
||||
- **Wake Word**: Choose from predefined options:
|
||||
- jarvis (default)
|
||||
- alexa
|
||||
- hey google
|
||||
- hey siri
|
||||
- computer
|
||||
|
||||
- **Model**: Select Whisper model:
|
||||
- tiny (fastest, least accurate)
|
||||
- base (default, good balance)
|
||||
- small (more accurate)
|
||||
- medium (most accurate, slower)
|
||||
|
||||
#### File Management
|
||||
- **Show Recent Transcriptions**: Display last 10 transcription entries
|
||||
- **Save to File...**: Set output file for saving transcriptions
|
||||
|
||||
### Status Indicators
|
||||
|
||||
| Icon | Status | Description |
|
||||
|------|--------|-------------|
|
||||
| 🎙️⚫ | Stopped | STT is not running |
|
||||
| 🎙️🔴 | Recording | STT is active and listening |
|
||||
| 🎙️⏸️ | Paused | STT is paused but can be resumed |
|
||||
|
||||
### Workflow Example
|
||||
|
||||
1. **Launch the app**: `tooling stt statusbar`
|
||||
2. **Look for the 🎙️ icon** in your macOS menu bar
|
||||
3. **Click the icon** to open the menu
|
||||
4. **Set your preferences**:
|
||||
- Choose wake word from Settings > Wake Word
|
||||
- Select model from Settings > Model
|
||||
- Optionally set output file with "Save to File..."
|
||||
5. **Click "Start STT"** to begin
|
||||
6. **Say your wake word** (e.g., "jarvis") to trigger recording
|
||||
7. **Speak clearly** after the wake word is detected
|
||||
8. **Get notifications** with your transcribed text
|
||||
9. **Use Pause/Resume** as needed
|
||||
10. **Click "Stop STT"** when done
|
||||
|
||||
### Notifications
|
||||
|
||||
The app provides notifications for:
|
||||
- STT started/stopped/paused/resumed
|
||||
- Transcribed speech (shows first 100 characters)
|
||||
- Settings changes (when STT is running)
|
||||
- File operations
|
||||
|
||||
### File Output
|
||||
|
||||
When you set an output file:
|
||||
- Transcriptions are saved with timestamps
|
||||
- Sessions are marked with start/end timestamps
|
||||
- File is automatically created in ~/Documents/ by default
|
||||
- Each transcription includes: `[HH:MM:SS] transcribed text`
|
||||
|
||||
### Requirements
|
||||
|
||||
- macOS (rumps requires macOS and PyObjC)
|
||||
- RealtimeSTT library
|
||||
- Working microphone
|
||||
- Python 3.11+
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
- **No menu bar icon**: Make sure you're running on macOS and rumps is installed
|
||||
- **No transcriptions**: Check microphone permissions and try speaking louder
|
||||
- **Wake word not detected**: Try adjusting sensitivity or use a different wake word
|
||||
- **High CPU usage**: Consider using the "tiny" model for better performance
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
The status bar app uses sensible defaults, but you can modify the underlying configuration by editing the STTStatusBarApp class in `src/tooling/stt_cli.py`.
|
||||
|
||||
Default settings:
|
||||
- Wake word: "jarvis"
|
||||
- Model: "base"
|
||||
- Sensitivity: 0.6
|
||||
- Device: auto-detect (CUDA if available, otherwise CPU)
|
||||
- Realtime display: enabled
|
||||
+8
-1
@@ -13,8 +13,9 @@ dependencies = [
|
||||
"python-doctr>=0.8.0",
|
||||
"typer>=0.12.0",
|
||||
"rich>=13.0.0",
|
||||
"realtimestt>=0.3.104",
|
||||
"rumps>=0.4.0",
|
||||
"pynput>=1.7.6",
|
||||
"realtimestt",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
@@ -28,6 +29,9 @@ screenshot-all = [
|
||||
"pyscreenshot>=3.1"
|
||||
] # All screenshot libraries for maximum compatibility
|
||||
|
||||
# Keyboard automation
|
||||
keyboard = ["pynput>=1.7.6"] # Cross-platform keyboard automation
|
||||
|
||||
[project.scripts]
|
||||
ocr-screenshot = "tooling.cli:cli_main"
|
||||
tooling = "tooling.cli:cli_main"
|
||||
@@ -40,3 +44,6 @@ build-backend = "hatchling.build"
|
||||
dev = [
|
||||
"open-webui>=0.6.5",
|
||||
]
|
||||
|
||||
[tool.uv.sources]
|
||||
realtimestt = { path = "../../projects/RealtimeSTT" }
|
||||
|
||||
+1974
File diff suppressed because it is too large
Load Diff
+325
-285
@@ -28,6 +28,12 @@ try:
|
||||
except ImportError:
|
||||
RUMPS_AVAILABLE = False
|
||||
|
||||
try:
|
||||
from pynput.keyboard import Controller as KeyboardController
|
||||
PYNPUT_AVAILABLE = True
|
||||
except ImportError:
|
||||
PYNPUT_AVAILABLE = False
|
||||
|
||||
# Create STT app that can be imported as a subcommand
|
||||
stt_app = typer.Typer(
|
||||
name="stt",
|
||||
@@ -96,300 +102,315 @@ if RUMPS_AVAILABLE:
|
||||
|
||||
def __init__(self, name="STT", **kwargs):
|
||||
super().__init__(name, **kwargs)
|
||||
|
||||
# Initialize state
|
||||
self.recorder = None
|
||||
self.is_running = False
|
||||
self.is_paused = False
|
||||
self.output_file = None
|
||||
self.transcription_thread = None
|
||||
self.stop_event = threading.Event()
|
||||
|
||||
# Configuration
|
||||
self.wake_word = "jarvis"
|
||||
self.model = "base"
|
||||
self.language = ""
|
||||
self.realtime = True
|
||||
self.sensitivity = 0.6
|
||||
self.device = "auto"
|
||||
self.save_to_file = None
|
||||
|
||||
# Menu setup
|
||||
self.menu = [
|
||||
rumps.MenuItem("Start STT", callback=self.start_stt),
|
||||
rumps.MenuItem("Stop STT", callback=self.stop_stt),
|
||||
rumps.separator,
|
||||
rumps.MenuItem("Pause", callback=self.pause_stt),
|
||||
rumps.MenuItem("Resume", callback=self.resume_stt),
|
||||
rumps.separator,
|
||||
{
|
||||
"Settings": {
|
||||
"Wake Word": {
|
||||
"jarvis": rumps.MenuItem("jarvis", callback=self.set_wake_word),
|
||||
"alexa": rumps.MenuItem("alexa", callback=self.set_wake_word),
|
||||
"hey google": rumps.MenuItem("hey google", callback=self.set_wake_word),
|
||||
"hey siri": rumps.MenuItem("hey siri", callback=self.set_wake_word),
|
||||
"computer": rumps.MenuItem("computer", callback=self.set_wake_word),
|
||||
},
|
||||
"Model": {
|
||||
"tiny": rumps.MenuItem("tiny", callback=self.set_model),
|
||||
"base": rumps.MenuItem("base", callback=self.set_model),
|
||||
"small": rumps.MenuItem("small", callback=self.set_model),
|
||||
"medium": rumps.MenuItem("medium", callback=self.set_model),
|
||||
}
|
||||
}
|
||||
},
|
||||
rumps.separator,
|
||||
rumps.MenuItem("Show Recent Transcriptions", callback=self.show_transcriptions),
|
||||
rumps.MenuItem("Save to File...", callback=self.select_output_file),
|
||||
rumps.separator,
|
||||
rumps.MenuItem("Quit", callback=rumps.quit_application),
|
||||
]
|
||||
|
||||
# Set initial menu states
|
||||
self.update_menu_states()
|
||||
self.set_current_wake_word()
|
||||
self.set_current_model()
|
||||
|
||||
def update_menu_states(self):
|
||||
"""Update menu item states based on current status."""
|
||||
if self.is_running:
|
||||
if self.is_paused:
|
||||
self.title = "🎙️⏸️"
|
||||
self.menu["Start STT"].set_callback(None)
|
||||
self.menu["Stop STT"].set_callback(self.stop_stt)
|
||||
self.menu["Pause"].set_callback(None)
|
||||
self.menu["Resume"].set_callback(self.resume_stt)
|
||||
else:
|
||||
self.title = "🎙️🔴"
|
||||
self.menu["Start STT"].set_callback(None)
|
||||
self.menu["Stop STT"].set_callback(self.stop_stt)
|
||||
self.menu["Pause"].set_callback(self.pause_stt)
|
||||
self.menu["Resume"].set_callback(None)
|
||||
else:
|
||||
self.title = "🎙️⚫"
|
||||
self.menu["Start STT"].set_callback(self.start_stt)
|
||||
self.menu["Stop STT"].set_callback(None)
|
||||
self.menu["Pause"].set_callback(None)
|
||||
self.menu["Resume"].set_callback(None)
|
||||
|
||||
def set_current_wake_word(self):
|
||||
"""Set checkmark for current wake word."""
|
||||
for word in ["jarvis", "alexa", "hey google", "hey siri", "computer"]:
|
||||
self.menu["Settings"]["Wake Word"][word].state = (word == self.wake_word)
|
||||
|
||||
def set_current_model(self):
|
||||
"""Set checkmark for current model."""
|
||||
for model in ["tiny", "base", "small", "medium"]:
|
||||
self.menu["Settings"]["Model"][model].state = (model == self.model)
|
||||
|
||||
def set_wake_word(self, sender):
|
||||
"""Set the wake word from menu selection."""
|
||||
self.wake_word = sender.title
|
||||
self.set_current_wake_word()
|
||||
if self.is_running:
|
||||
rumps.notification("STT Settings", "Wake Word Changed", f"Restart STT to use '{self.wake_word}'")
|
||||
|
||||
def set_model(self, sender):
|
||||
"""Set the model from menu selection."""
|
||||
self.model = sender.title
|
||||
self.set_current_model()
|
||||
if self.is_running:
|
||||
rumps.notification("STT Settings", "Model Changed", f"Restart STT to use '{self.model}' model")
|
||||
|
||||
def start_stt(self, _):
|
||||
"""Start STT functionality."""
|
||||
if self.is_running:
|
||||
return
|
||||
|
||||
try:
|
||||
from RealtimeSTT import AudioToTextRecorder
|
||||
except ImportError:
|
||||
rumps.alert("RealtimeSTT not installed", "Please install with: pip install RealtimeSTT")
|
||||
return
|
||||
|
||||
try:
|
||||
# Determine device
|
||||
if self.device == "auto":
|
||||
try:
|
||||
import torch
|
||||
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
||||
except ImportError:
|
||||
self.device = "cpu"
|
||||
|
||||
# Setup output file if specified
|
||||
if self.save_to_file:
|
||||
self.save_to_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
self.output_file = open(self.save_to_file, 'a', encoding='utf-8')
|
||||
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
self.output_file.write(f"\n=== STT Session Started: {timestamp} ===\n")
|
||||
self.output_file.flush()
|
||||
|
||||
# Configure recorder
|
||||
recorder_config = {
|
||||
"model": self.model,
|
||||
"wake_words": self.wake_word,
|
||||
"wake_words_sensitivity": self.sensitivity,
|
||||
"device": self.device,
|
||||
"on_recording_start": self.on_recording_start,
|
||||
"on_recording_stop": self.on_recording_stop,
|
||||
"on_wakeword_detected": self.on_wakeword_detected,
|
||||
"on_wakeword_timeout": self.on_wakeword_timeout,
|
||||
"on_wakeword_detection_start": self.on_wakeword_detection_start,
|
||||
}
|
||||
|
||||
if self.language:
|
||||
recorder_config["language"] = self.language
|
||||
|
||||
if self.realtime:
|
||||
recorder_config.update({
|
||||
"enable_realtime_transcription": True,
|
||||
"on_realtime_transcription_update": self.on_realtime_transcription,
|
||||
})
|
||||
|
||||
# Initialize recorder
|
||||
self.recorder = AudioToTextRecorder(**recorder_config)
|
||||
|
||||
# Start transcription thread
|
||||
self.is_running = True
|
||||
# Initialize state
|
||||
self.recorder = None
|
||||
self.is_running = False
|
||||
self.is_paused = False
|
||||
self.stop_event.clear()
|
||||
self.transcription_thread = threading.Thread(target=self.transcription_loop, daemon=True)
|
||||
self.transcription_thread.start()
|
||||
self.output_file = None
|
||||
self.transcription_thread = None
|
||||
self.stop_event = threading.Event()
|
||||
|
||||
# Configuration
|
||||
self.wake_word = "jarvis"
|
||||
self.model = "base"
|
||||
self.language = ""
|
||||
self.realtime = True
|
||||
self.sensitivity = 0.6
|
||||
self.device = "auto"
|
||||
self.wakeword_backend = "openwakeword"
|
||||
self.save_to_file = None
|
||||
|
||||
# Menu setup
|
||||
self.menu = [
|
||||
rumps.MenuItem("Start STT", callback=self.start_stt),
|
||||
rumps.MenuItem("Stop STT", callback=self.stop_stt),
|
||||
rumps.separator,
|
||||
rumps.MenuItem("Pause", callback=self.pause_stt),
|
||||
rumps.MenuItem("Resume", callback=self.resume_stt),
|
||||
rumps.separator,
|
||||
{
|
||||
"Settings": {
|
||||
"Wake Word": {
|
||||
"jarvis": rumps.MenuItem("jarvis", callback=self.set_wake_word),
|
||||
"alexa": rumps.MenuItem("alexa", callback=self.set_wake_word),
|
||||
"hey google": rumps.MenuItem("hey google", callback=self.set_wake_word),
|
||||
"hey siri": rumps.MenuItem("hey siri", callback=self.set_wake_word),
|
||||
"computer": rumps.MenuItem("computer", callback=self.set_wake_word),
|
||||
},
|
||||
"Model": {
|
||||
"tiny": rumps.MenuItem("tiny", callback=self.set_model),
|
||||
"base": rumps.MenuItem("base", callback=self.set_model),
|
||||
"small": rumps.MenuItem("small", callback=self.set_model),
|
||||
"medium": rumps.MenuItem("medium", callback=self.set_model),
|
||||
}
|
||||
}
|
||||
},
|
||||
rumps.separator,
|
||||
rumps.MenuItem("Show Recent Transcriptions", callback=self.show_transcriptions),
|
||||
rumps.MenuItem("Save to File...", callback=self.select_output_file),
|
||||
rumps.separator,
|
||||
rumps.MenuItem("Quit", callback=rumps.quit_application),
|
||||
]
|
||||
|
||||
# Set initial menu states
|
||||
self.update_menu_states()
|
||||
self.set_current_wake_word()
|
||||
self.set_current_model()
|
||||
|
||||
def update_menu_states(self):
|
||||
"""Update menu item states based on current status."""
|
||||
if self.is_running:
|
||||
if self.is_paused:
|
||||
self.title = "🎙️⏸️"
|
||||
self.menu["Start STT"].set_callback(None)
|
||||
self.menu["Stop STT"].set_callback(self.stop_stt)
|
||||
self.menu["Pause"].set_callback(None)
|
||||
self.menu["Resume"].set_callback(self.resume_stt)
|
||||
else:
|
||||
self.title = "🎙️🔴"
|
||||
self.menu["Start STT"].set_callback(None)
|
||||
self.menu["Stop STT"].set_callback(self.stop_stt)
|
||||
self.menu["Pause"].set_callback(self.pause_stt)
|
||||
self.menu["Resume"].set_callback(None)
|
||||
else:
|
||||
self.title = "🎙️⚫"
|
||||
self.menu["Start STT"].set_callback(self.start_stt)
|
||||
self.menu["Stop STT"].set_callback(None)
|
||||
self.menu["Pause"].set_callback(None)
|
||||
self.menu["Resume"].set_callback(None)
|
||||
|
||||
def set_current_wake_word(self):
|
||||
"""Set checkmark for current wake word."""
|
||||
for word in ["jarvis", "alexa", "hey google", "hey siri", "computer"]:
|
||||
self.menu["Settings"]["Wake Word"][word].state = (word == self.wake_word)
|
||||
|
||||
def set_current_model(self):
|
||||
"""Set checkmark for current model."""
|
||||
for model in ["tiny", "base", "small", "medium"]:
|
||||
self.menu["Settings"]["Model"][model].state = (model == self.model)
|
||||
|
||||
def set_wake_word(self, sender):
|
||||
"""Set the wake word from menu selection."""
|
||||
self.wake_word = sender.title
|
||||
self.set_current_wake_word()
|
||||
if self.is_running:
|
||||
rumps.notification("STT Settings", "Wake Word Changed", f"Restart STT to use '{self.wake_word}'", sound=False)
|
||||
|
||||
def set_model(self, sender):
|
||||
"""Set the model from menu selection."""
|
||||
self.model = sender.title
|
||||
self.set_current_model()
|
||||
if self.is_running:
|
||||
rumps.notification("STT Settings", "Model Changed", f"Restart STT to use '{self.model}' model", sound=False)
|
||||
|
||||
def start_stt(self, _):
|
||||
"""Start STT functionality."""
|
||||
if self.is_running:
|
||||
return
|
||||
|
||||
try:
|
||||
from RealtimeSTT import AudioToTextRecorder
|
||||
except ImportError:
|
||||
rumps.alert("RealtimeSTT not installed", "Please install with: pip install RealtimeSTT")
|
||||
return
|
||||
|
||||
try:
|
||||
# Determine device
|
||||
if self.device == "auto":
|
||||
try:
|
||||
import torch
|
||||
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
||||
except ImportError:
|
||||
self.device = "cpu"
|
||||
|
||||
# Setup output file if specified
|
||||
if self.save_to_file:
|
||||
self.save_to_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
self.output_file = open(self.save_to_file, 'a', encoding='utf-8')
|
||||
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
self.output_file.write(f"\n=== STT Session Started: {timestamp} ===\n")
|
||||
self.output_file.flush()
|
||||
|
||||
# Configure recorder
|
||||
recorder_config = {
|
||||
"model": self.model,
|
||||
# "wake_words": self.wake_word,
|
||||
# "wakeword_backend": self.wakeword_backend,
|
||||
# "wake_words_sensitivity": self.sensitivity,
|
||||
"device": self.device,
|
||||
"on_recording_start": self.on_recording_start,
|
||||
"on_recording_stop": self.on_recording_stop,
|
||||
"on_wakeword_detected": self.on_wakeword_detected,
|
||||
"on_wakeword_timeout": self.on_wakeword_timeout,
|
||||
"on_wakeword_detection_start": self.on_wakeword_detection_start,
|
||||
}
|
||||
|
||||
if self.language:
|
||||
recorder_config["language"] = self.language
|
||||
|
||||
if self.realtime:
|
||||
recorder_config.update({
|
||||
"enable_realtime_transcription": True,
|
||||
"on_realtime_transcription_update": self.on_realtime_transcription,
|
||||
})
|
||||
|
||||
# Initialize recorder
|
||||
self.recorder = AudioToTextRecorder(**recorder_config)
|
||||
|
||||
# Start transcription thread
|
||||
self.is_running = True
|
||||
self.is_paused = False
|
||||
self.stop_event.clear()
|
||||
self.transcription_thread = threading.Thread(target=self.transcription_loop, daemon=True)
|
||||
self.transcription_thread.start()
|
||||
|
||||
self.update_menu_states()
|
||||
rumps.notification("STT Started", f"Wake word: {self.wake_word}", f"Model: {self.model} | Device: {self.device}", sound=False)
|
||||
|
||||
except Exception as e:
|
||||
rumps.alert("STT Error", f"Failed to start STT: {str(e)}")
|
||||
|
||||
def stop_stt(self, _):
|
||||
"""Stop STT functionality."""
|
||||
if not self.is_running:
|
||||
return
|
||||
|
||||
self.is_running = False
|
||||
self.is_paused = False
|
||||
self.stop_event.set()
|
||||
|
||||
if self.recorder:
|
||||
try:
|
||||
self.recorder.shutdown()
|
||||
except:
|
||||
pass
|
||||
self.recorder = None
|
||||
|
||||
if self.output_file:
|
||||
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
self.output_file.write(f"=== STT Session Ended: {timestamp} ===\n\n")
|
||||
self.output_file.close()
|
||||
self.output_file = None
|
||||
|
||||
self.update_menu_states()
|
||||
rumps.notification("STT Started", f"Wake word: {self.wake_word}", f"Model: {self.model} | Device: {self.device}")
|
||||
rumps.notification("STT Stopped", "Speech-to-text has been stopped", "", sound=False)
|
||||
|
||||
def pause_stt(self, _):
|
||||
"""Pause STT functionality."""
|
||||
if not self.is_running or self.is_paused:
|
||||
return
|
||||
|
||||
except Exception as e:
|
||||
rumps.alert("STT Error", f"Failed to start STT: {str(e)}")
|
||||
|
||||
def stop_stt(self, _):
|
||||
"""Stop STT functionality."""
|
||||
if not self.is_running:
|
||||
return
|
||||
self.is_paused = True
|
||||
self.update_menu_states()
|
||||
rumps.notification("STT Paused", "Speech recognition paused", "Resume from menu", sound=False)
|
||||
|
||||
self.is_running = False
|
||||
self.is_paused = False
|
||||
self.stop_event.set()
|
||||
|
||||
if self.recorder:
|
||||
try:
|
||||
self.recorder.shutdown()
|
||||
except:
|
||||
pass
|
||||
self.recorder = None
|
||||
|
||||
if self.output_file:
|
||||
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
self.output_file.write(f"=== STT Session Ended: {timestamp} ===\n\n")
|
||||
self.output_file.close()
|
||||
self.output_file = None
|
||||
|
||||
self.update_menu_states()
|
||||
rumps.notification("STT Stopped", "Speech-to-text has been stopped", "")
|
||||
|
||||
def pause_stt(self, _):
|
||||
"""Pause STT functionality."""
|
||||
if not self.is_running or self.is_paused:
|
||||
return
|
||||
|
||||
self.is_paused = True
|
||||
self.update_menu_states()
|
||||
rumps.notification("STT Paused", "Speech recognition paused", "Resume from menu")
|
||||
|
||||
def resume_stt(self, _):
|
||||
"""Resume STT functionality."""
|
||||
if not self.is_running or not self.is_paused:
|
||||
return
|
||||
|
||||
self.is_paused = False
|
||||
self.update_menu_states()
|
||||
rumps.notification("STT Resumed", "Speech recognition resumed", f"Listening for '{self.wake_word}'")
|
||||
|
||||
def transcription_loop(self):
|
||||
"""Main transcription loop running in background thread."""
|
||||
while self.is_running and not self.stop_event.is_set():
|
||||
try:
|
||||
if not self.is_paused and self.recorder:
|
||||
text = self.recorder.text()
|
||||
if text and text.strip():
|
||||
self.on_transcription_complete(text)
|
||||
else:
|
||||
time.sleep(0.1) # Small delay when paused
|
||||
except Exception as e:
|
||||
print(f"Transcription error: {e}")
|
||||
time.sleep(1) # Longer delay on error
|
||||
|
||||
def on_realtime_transcription(self, text: str):
|
||||
"""Handle real-time transcription updates."""
|
||||
# Could update a notification or log
|
||||
pass
|
||||
|
||||
def on_transcription_complete(self, text: str):
|
||||
"""Handle completed transcriptions."""
|
||||
if text.strip():
|
||||
# Show notification with transcription
|
||||
rumps.notification("Transcription", "Speech detected:", text[:100] + ("..." if len(text) > 100 else ""))
|
||||
def resume_stt(self, _):
|
||||
"""Resume STT functionality."""
|
||||
if not self.is_running or not self.is_paused:
|
||||
return
|
||||
|
||||
# Save to file if specified
|
||||
if self.output_file:
|
||||
timestamp = datetime.datetime.now().strftime("%H:%M:%S")
|
||||
self.output_file.write(f"[{timestamp}] {text}\n")
|
||||
self.output_file.flush()
|
||||
|
||||
def on_recording_start(self):
|
||||
"""Called when recording starts."""
|
||||
pass
|
||||
|
||||
def on_recording_stop(self):
|
||||
"""Called when recording stops."""
|
||||
pass
|
||||
|
||||
def on_wakeword_detected(self):
|
||||
"""Called when wake word is detected."""
|
||||
pass
|
||||
|
||||
def on_wakeword_timeout(self):
|
||||
"""Called when wake word times out."""
|
||||
pass
|
||||
|
||||
def on_wakeword_detection_start(self):
|
||||
"""Called when starting to listen for wake words."""
|
||||
pass
|
||||
|
||||
def show_transcriptions(self, _):
|
||||
"""Show recent transcriptions in an alert."""
|
||||
if self.output_file and self.save_to_file and self.save_to_file.exists():
|
||||
try:
|
||||
with open(self.save_to_file, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
recent = ''.join(lines[-10:]) # Last 10 lines
|
||||
if recent.strip():
|
||||
rumps.alert("Recent Transcriptions", recent)
|
||||
self.is_paused = False
|
||||
self.update_menu_states()
|
||||
rumps.notification("STT Resumed", "Speech recognition resumed", f"Listening for '{self.wake_word}'", sound=False)
|
||||
|
||||
def transcription_loop(self):
|
||||
"""Main transcription loop running in background thread."""
|
||||
print("test1")
|
||||
while self.is_running and not self.stop_event.is_set():
|
||||
print("test2")
|
||||
try:
|
||||
if not self.is_paused and self.recorder:
|
||||
print("test3")
|
||||
text = self.recorder.text()
|
||||
print("test4")
|
||||
if text and text.strip():
|
||||
print("test5")
|
||||
self.on_transcription_complete(text)
|
||||
else:
|
||||
rumps.alert("No Transcriptions", "No transcriptions found yet.")
|
||||
time.sleep(0.1) # Small delay when paused
|
||||
except Exception as e:
|
||||
print(f"Transcription error: {e}")
|
||||
time.sleep(1) # Longer delay on error
|
||||
print("test")
|
||||
|
||||
def on_realtime_transcription(self, text: str):
|
||||
"""Handle real-time transcription updates."""
|
||||
# Could update a notification or log
|
||||
pass
|
||||
|
||||
def on_transcription_complete(self, text: str):
|
||||
"""Handle completed transcriptions."""
|
||||
if text.strip():
|
||||
# Show notification with transcription
|
||||
rumps.notification("Transcription", "Speech detected:", text[:100] + ("..." if len(text) > 100 else ""), sound=False)
|
||||
|
||||
# Type the transcribed text using pynput
|
||||
if PYNPUT_AVAILABLE:
|
||||
keyboard = KeyboardController()
|
||||
keyboard.type(text + " ")
|
||||
else:
|
||||
print(f"pynput not available. Transcription: {text}")
|
||||
|
||||
# Save to file if specified
|
||||
if self.output_file:
|
||||
timestamp = datetime.datetime.now().strftime("%H:%M:%S")
|
||||
self.output_file.write(f"[{timestamp}] {text}\n")
|
||||
self.output_file.flush()
|
||||
|
||||
def on_recording_start(self):
|
||||
"""Called when recording starts."""
|
||||
pass
|
||||
|
||||
def on_recording_stop(self):
|
||||
"""Called when recording stops."""
|
||||
pass
|
||||
|
||||
def on_wakeword_detected(self):
|
||||
"""Called when wake word is detected."""
|
||||
pass
|
||||
|
||||
def on_wakeword_timeout(self):
|
||||
"""Called when wake word times out."""
|
||||
pass
|
||||
|
||||
def on_wakeword_detection_start(self):
|
||||
"""Called when starting to listen for wake words."""
|
||||
pass
|
||||
|
||||
def show_transcriptions(self, _):
|
||||
"""Show recent transcriptions in an alert."""
|
||||
if self.output_file and self.save_to_file and self.save_to_file.exists():
|
||||
try:
|
||||
with open(self.save_to_file, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
recent = ''.join(lines[-10:]) # Last 10 lines
|
||||
if recent.strip():
|
||||
rumps.alert("Recent Transcriptions", recent)
|
||||
else:
|
||||
rumps.alert("No Transcriptions", "No transcriptions found yet.")
|
||||
except Exception as e:
|
||||
rumps.alert("Error", f"Could not read transcriptions: {e}")
|
||||
else:
|
||||
rumps.alert("No File", "No output file configured. Use 'Save to File...' to set one.")
|
||||
|
||||
def select_output_file(self, _):
|
||||
"""Select output file for saving transcriptions."""
|
||||
try:
|
||||
# Simple file selection using input dialog
|
||||
response = rumps.Window(
|
||||
message="Enter filename for transcriptions:",
|
||||
title="Save Transcriptions",
|
||||
default_text="stt_transcriptions.txt",
|
||||
ok="Save",
|
||||
cancel="Cancel"
|
||||
).run()
|
||||
|
||||
if response.clicked and response.text:
|
||||
self.save_to_file = Path.home() / "Documents" / response.text
|
||||
rumps.notification("File Set", "Transcriptions will be saved to:", str(self.save_to_file), sound=False)
|
||||
except Exception as e:
|
||||
rumps.alert("Error", f"Could not read transcriptions: {e}")
|
||||
else:
|
||||
rumps.alert("No File", "No output file configured. Use 'Save to File...' to set one.")
|
||||
|
||||
def select_output_file(self, _):
|
||||
"""Select output file for saving transcriptions."""
|
||||
try:
|
||||
# Simple file selection using input dialog
|
||||
response = rumps.Window(
|
||||
message="Enter filename for transcriptions:",
|
||||
title="Save Transcriptions",
|
||||
default_text="stt_transcriptions.txt",
|
||||
ok="Save",
|
||||
cancel="Cancel"
|
||||
).run()
|
||||
|
||||
if response.clicked and response.text:
|
||||
self.save_to_file = Path.home() / "Documents" / response.text
|
||||
rumps.notification("File Set", "Transcriptions will be saved to:", str(self.save_to_file))
|
||||
except Exception as e:
|
||||
rumps.alert("Error", f"Could not set output file: {e}")
|
||||
rumps.alert("Error", f"Could not set output file: {e}")
|
||||
|
||||
|
||||
@stt_app.command("listen")
|
||||
@@ -422,6 +443,10 @@ def listen_cmd(
|
||||
default="auto",
|
||||
help="Device to use (auto, cuda, cpu)"
|
||||
),
|
||||
wakeword_backend: str = typer.Option(
|
||||
default="openwakeword",
|
||||
help="Wake word backend to use (pvporcupine, openwakeword)"
|
||||
),
|
||||
verbose: bool = typer.Option(
|
||||
default=False,
|
||||
help="Show verbose output and configuration"
|
||||
@@ -448,6 +473,13 @@ def listen_cmd(
|
||||
console.print(f"Valid options: {', '.join(valid_wake_words)}")
|
||||
raise typer.Exit(1)
|
||||
|
||||
# Validate wakeword backend
|
||||
valid_backends = ["pvporcupine", "openwakeword"]
|
||||
if wakeword_backend.lower() not in valid_backends:
|
||||
console.print(f"[bold red]❌ Invalid wakeword backend: {wakeword_backend}[/bold red]")
|
||||
console.print(f"Valid options: {', '.join(valid_backends)}")
|
||||
raise typer.Exit(1)
|
||||
|
||||
# Determine device
|
||||
if device == "auto":
|
||||
try:
|
||||
@@ -475,6 +507,7 @@ def listen_cmd(
|
||||
config_table.add_column("Value", style="green")
|
||||
|
||||
config_table.add_row("Wake Word", wake_word)
|
||||
config_table.add_row("Wakeword Backend", wakeword_backend)
|
||||
config_table.add_row("Model", model)
|
||||
config_table.add_row("Language", language if language else "Auto-detect")
|
||||
config_table.add_row("Device", device)
|
||||
@@ -530,6 +563,7 @@ def listen_cmd(
|
||||
recorder_config = {
|
||||
"model": model,
|
||||
"wake_words": wake_word,
|
||||
"wakeword_backend": wakeword_backend,
|
||||
"wake_words_sensitivity": sensitivity,
|
||||
"device": device,
|
||||
"on_recording_start": on_recording_start,
|
||||
@@ -734,12 +768,18 @@ def info_cmd():
|
||||
console.print(f"\n[bold cyan]Available Models:[/bold cyan]")
|
||||
console.print(", ".join(models))
|
||||
|
||||
# Available wakeword backends
|
||||
backends = ["pvporcupine", "openwakeword"]
|
||||
console.print(f"\n[bold cyan]Available Wakeword Backends:[/bold cyan]")
|
||||
console.print(", ".join(backends))
|
||||
|
||||
# Usage examples
|
||||
console.print(f"\n[bold cyan]Usage Examples:[/bold cyan]")
|
||||
examples = [
|
||||
"tooling stt listen # Use jarvis wake word with base model",
|
||||
"tooling stt listen --wake-word alexa # Use alexa wake word",
|
||||
"tooling stt listen --model tiny # Use faster tiny model",
|
||||
"tooling stt listen --wakeword-engine pvporcupine # Use pvporcupine engine",
|
||||
"tooling stt test --duration 5 # Test for 5 seconds",
|
||||
"tooling stt listen --save-to-file transcripts.txt # Save to file",
|
||||
"tooling stt statusbar # Launch status bar app"
|
||||
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple RealtimeSTT Test Script
|
||||
|
||||
Test wake word detection and transcription functionality
|
||||
without the complexity of threading and status bar apps.
|
||||
"""
|
||||
|
||||
from RealtimeSTT import AudioToTextRecorder
|
||||
|
||||
def process_text(text):
|
||||
print(text)
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Wait until it says 'speak now'")
|
||||
recorder = AudioToTextRecorder()
|
||||
|
||||
while True:
|
||||
recorder.text(process_text)
|
||||
@@ -1320,6 +1320,12 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/31/55cd413eaccd39125368be33c46de24a1f639f2e12349b0361b4678f3915/eval_type_backport-0.2.2-py3-none-any.whl", hash = "sha256:cb6ad7c393517f476f96d456d0412ea80f0a8cf96f6892834cd9340149111b0a", size = 5830, upload-time = "2024-12-21T20:09:44.175Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "evdev"
|
||||
version = "1.9.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/63/fe/a17c106a1f4061ce83f04d14bcedcfb2c38c7793ea56bfb906a6fadae8cb/evdev-1.9.2.tar.gz", hash = "sha256:5d3278892ce1f92a74d6bf888cc8525d9f68af85dbe336c95d1c87fb8f423069", size = 33301, upload-time = "2025-05-01T19:53:47.69Z" }
|
||||
|
||||
[[package]]
|
||||
name = "events"
|
||||
version = "0.5"
|
||||
@@ -4896,6 +4902,22 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/94/e4181a1f6286f545507528c78016e00065ea913276888db2262507693ce5/PyMySQL-1.1.1-py3-none-any.whl", hash = "sha256:4de15da4c61dc132f4fb9ab763063e693d521a80fd0e87943b9a453dd4c19d6c", size = 44972, upload-time = "2024-05-21T11:03:41.216Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pynput"
|
||||
version = "1.8.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "evdev", marker = "'linux' in sys_platform" },
|
||||
{ name = "pyobjc-framework-applicationservices", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "python-xlib", marker = "'linux' in sys_platform" },
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f0/c3/dccf44c68225046df5324db0cc7d563a560635355b3e5f1d249468268a6f/pynput-1.8.1.tar.gz", hash = "sha256:70d7c8373ee98911004a7c938742242840a5628c004573d84ba849d4601df81e", size = 82289, upload-time = "2025-03-17T17:12:01.481Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/59/4f/ac3fa906ae8a375a536b12794128c5efacade9eaa917a35dfd27ce0c7400/pynput-1.8.1-py2.py3-none-any.whl", hash = "sha256:42dfcf27404459ca16ca889c8fb8ffe42a9fe54f722fd1a3e130728e59e768d2", size = 91693, upload-time = "2025-03-17T17:12:00.094Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyobjc-core"
|
||||
version = "11.1"
|
||||
@@ -4910,6 +4932,26 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/49/0f/f5ab2b0e57430a3bec9a62b6153c0e79c05a30d77b564efdb9f9446eeac5/pyobjc_core-11.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:f2455683e807f8541f0d83fbba0f5d9a46128ab0d5cc83ea208f0bec759b7f96", size = 708807, upload-time = "2025-06-14T20:44:54.851Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyobjc-framework-applicationservices"
|
||||
version = "11.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "pyobjc-framework-coretext", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/be/3f/b33ce0cecc3a42f6c289dcbf9ff698b0d9e85f5796db2e9cb5dadccffbb9/pyobjc_framework_applicationservices-11.1.tar.gz", hash = "sha256:03fcd8c0c600db98fa8b85eb7b3bc31491701720c795e3f762b54e865138bbaf", size = 224842, upload-time = "2025-06-14T20:56:40.648Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/39/2d/9fde6de0b2a95fbb3d77ba11b3cc4f289dd208f38cb3a28389add87c0f44/pyobjc_framework_applicationservices-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cf45d15eddae36dec2330a9992fc852476b61c8f529874b9ec2805c768a75482", size = 30991, upload-time = "2025-06-14T20:45:18.169Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/ec/46a5c710e2d7edf55105223c34fed5a7b7cc7aba7d00a3a7b0405d6a2d1a/pyobjc_framework_applicationservices-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f4a85ccd78bab84f7f05ac65ff9be117839dfc09d48c39edd65c617ed73eb01c", size = 31056, upload-time = "2025-06-14T20:45:18.925Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/06/c2a309e6f37bfa73a2a581d3301321b2033e25b249e2a01e417a3c34e799/pyobjc_framework_applicationservices-11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:385a89f4d0838c97a331e247519d9e9745aa3f7427169d18570e3c664076a63c", size = 31072, upload-time = "2025-06-14T20:45:19.707Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/5f/357bf498c27f1b4d48385860d8374b2569adc1522aabe32befd77089c070/pyobjc_framework_applicationservices-11.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f480fab20f3005e559c9d06c9a3874a1f1c60dde52c6d28a53ab59b45e79d55f", size = 31335, upload-time = "2025-06-14T20:45:20.462Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/b6/797fdd81399fe8251196f29a621ba3f3f04d5c579d95fd304489f5558202/pyobjc_framework_applicationservices-11.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:e8dee91c6a14fd042f98819dc0ac4a182e0e816282565534032f0e544bfab143", size = 31196, upload-time = "2025-06-14T20:45:21.555Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/45/47eba8d7cdf16d778240ed13fb405e8d712464170ed29d0463363a695194/pyobjc_framework_applicationservices-11.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:a0ce40a57a9b993793b6f72c4fd93f80618ef54a69d76a1da97b8360a2f3ffc5", size = 31446, upload-time = "2025-06-14T20:45:22.313Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyobjc-framework-cocoa"
|
||||
version = "11.1"
|
||||
@@ -4927,6 +4969,25 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/7c/54afe9ffee547c41e1161691e72067a37ed27466ac71c089bfdcd07ca70d/pyobjc_framework_cocoa-11.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:1b5de4e1757bb65689d6dc1f8d8717de9ec8587eb0c4831c134f13aba29f9b71", size = 396742, upload-time = "2025-06-14T20:46:57.64Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyobjc-framework-coretext"
|
||||
version = "11.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/65/e9/d3231c4f87d07b8525401fd6ad3c56607c9e512c5490f0a7a6abb13acab6/pyobjc_framework_coretext-11.1.tar.gz", hash = "sha256:a29bbd5d85c77f46a8ee81d381b847244c88a3a5a96ac22f509027ceceaffaf6", size = 274702, upload-time = "2025-06-14T20:57:16.059Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/59/d6cc5470157cfd328b2d1ee2c1b6f846a5205307fce17291b57236d9f46e/pyobjc_framework_coretext-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b4f4d2d2a6331fa64465247358d7aafce98e4fb654b99301a490627a073d021e", size = 30072, upload-time = "2025-06-14T20:48:34.248Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/67/9cc5189c366e67dc3e5b5976fac73cc6405841095f795d3fa0d5fc43d76a/pyobjc_framework_coretext-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1597bf7234270ee1b9963bf112e9061050d5fb8e1384b3f50c11bde2fe2b1570", size = 30175, upload-time = "2025-06-14T20:48:35.023Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/d1/6ec2ef4f8133177203a742d5db4db90bbb3ae100aec8d17f667208da84c9/pyobjc_framework_coretext-11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:37e051e8f12a0f47a81b8efc8c902156eb5bc3d8123c43e5bd4cebd24c222228", size = 30180, upload-time = "2025-06-14T20:48:35.766Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/84/d4a95e49f6af59503ba257fbed0471b6932f0afe8b3725c018dd3ba40150/pyobjc_framework_coretext-11.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:56a3a02202e0d50be3c43e781c00f9f1859ab9b73a8342ff56260b908e911e37", size = 30768, upload-time = "2025-06-14T20:48:36.869Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/4c/16e1504e06a5cb23eec6276835ddddb087637beba66cf84b5c587eba99be/pyobjc_framework_coretext-11.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:15650ba99692d00953e91e53118c11636056a22c90d472020f7ba31500577bf5", size = 30155, upload-time = "2025-06-14T20:48:37.948Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/a4/cbfa9c874b2770fb1ba5c38c42b0e12a8b5aa177a5a86d0ad49b935aa626/pyobjc_framework_coretext-11.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:fb27f66a56660c31bb956191d64b85b95bac99cfb833f6e99622ca0ac4b3ba12", size = 30768, upload-time = "2025-06-14T20:48:38.734Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyobjc-framework-quartz"
|
||||
version = "11.1"
|
||||
@@ -5258,6 +5319,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/32/b4fb8585d1be0f68bde7e110dffbcf354915f77ad8c778563f0ad9655c02/python_socketio-5.13.0-py3-none-any.whl", hash = "sha256:51f68d6499f2df8524668c24bcec13ba1414117cfb3a90115c559b601ab10caf", size = 77800, upload-time = "2025-04-12T15:46:58.412Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-xlib"
|
||||
version = "0.33"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/86/f5/8c0653e5bb54e0cbdfe27bf32d41f27bc4e12faa8742778c17f2a71be2c0/python-xlib-0.33.tar.gz", hash = "sha256:55af7906a2c75ce6cb280a584776080602444f75815a7aff4d287bb2d7018b32", size = 269068, upload-time = "2022-12-25T18:53:00.824Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/b8/ff33610932e0ee81ae7f1269c890f697d56ff74b9f5b2ee5d9b7fa2c5355/python_xlib-0.33-py2.py3-none-any.whl", hash = "sha256:c3534038d42e0df2f1392a1b30a15a4ff5fdc2b86cfa94f072bf11b10a164398", size = 182185, upload-time = "2022-12-25T18:52:58.662Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python3-xlib"
|
||||
version = "0.15"
|
||||
@@ -5493,7 +5566,7 @@ wheels = [
|
||||
[[package]]
|
||||
name = "realtimestt"
|
||||
version = "0.3.104"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
source = { directory = "../../projects/RealtimeSTT" }
|
||||
dependencies = [
|
||||
{ name = "faster-whisper" },
|
||||
{ name = "halo" },
|
||||
@@ -5508,9 +5581,21 @@ dependencies = [
|
||||
{ name = "websocket-client" },
|
||||
{ name = "websockets" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/29/15/70c63317462a153ae46a1857e35b21c4de94729f56d8f1b899fbc5ff3b52/realtimestt-0.3.104.tar.gz", hash = "sha256:96bafae0e839c3b60729462cf760c5fc104569c9839fc1071b5afecc1a2b351f", size = 118257, upload-time = "2025-05-03T21:47:33.78Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/24/df28da0f3c5f0d4760e1492e7cee937e0aa72897d4cc41a36ee8c46af5c1/realtimestt-0.3.104-py3-none-any.whl", hash = "sha256:d0a4030551afa3d54b729d94f8a23cca800ef0b21e7778e84ab7ece014c9135f", size = 110633, upload-time = "2025-05-03T21:47:32.59Z" },
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "faster-whisper", specifier = "==1.1.1" },
|
||||
{ name = "halo", specifier = "==0.0.31" },
|
||||
{ name = "openwakeword", specifier = ">=0.4.0" },
|
||||
{ name = "pvporcupine", specifier = "==1.9.5" },
|
||||
{ name = "pyaudio", specifier = "==0.2.14" },
|
||||
{ name = "scipy", specifier = "==1.15.2" },
|
||||
{ name = "soundfile", specifier = "==0.13.1" },
|
||||
{ name = "torch", specifier = ">=2.7.1" },
|
||||
{ name = "torchaudio", specifier = ">=2.7.1" },
|
||||
{ name = "webrtcvad-wheels", specifier = "==2.0.14" },
|
||||
{ name = "websocket-client", specifier = "==1.8.0" },
|
||||
{ name = "websockets", specifier = "==15.0.1" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6257,6 +6342,7 @@ source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "pillow", version = "11.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" },
|
||||
{ name = "pillow", version = "11.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" },
|
||||
{ name = "pynput" },
|
||||
{ name = "pyperclip" },
|
||||
{ name = "python-doctr" },
|
||||
{ name = "realtimestt" },
|
||||
@@ -6266,6 +6352,9 @@ dependencies = [
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
keyboard = [
|
||||
{ name = "pynput" },
|
||||
]
|
||||
screenshot-all = [
|
||||
{ name = "mss" },
|
||||
{ name = "pyautogui" },
|
||||
@@ -6294,16 +6383,18 @@ requires-dist = [
|
||||
{ name = "pillow", specifier = ">=11.1.0" },
|
||||
{ name = "pyautogui", marker = "extra == 'screenshot-all'", specifier = ">=0.9.54" },
|
||||
{ name = "pyautogui", marker = "extra == 'screenshot-full'", specifier = ">=0.9.54" },
|
||||
{ name = "pynput", specifier = ">=1.7.6" },
|
||||
{ name = "pynput", marker = "extra == 'keyboard'", specifier = ">=1.7.6" },
|
||||
{ name = "pyperclip", specifier = ">=1.9.0" },
|
||||
{ name = "pyscreenshot", marker = "extra == 'screenshot-all'", specifier = ">=3.1" },
|
||||
{ name = "pyscreenshot", marker = "extra == 'screenshot-multi'", specifier = ">=3.1" },
|
||||
{ name = "python-doctr", specifier = ">=0.8.0" },
|
||||
{ name = "realtimestt", specifier = ">=0.3.104" },
|
||||
{ name = "realtimestt", directory = "../../projects/RealtimeSTT" },
|
||||
{ name = "rich", specifier = ">=13.0.0" },
|
||||
{ name = "rumps", specifier = ">=0.4.0" },
|
||||
{ name = "typer", specifier = ">=0.12.0" },
|
||||
]
|
||||
provides-extras = ["screenshot-fast", "screenshot-full", "screenshot-multi", "screenshot-all"]
|
||||
provides-extras = ["screenshot-fast", "screenshot-full", "screenshot-multi", "screenshot-all", "keyboard"]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [{ name = "open-webui", specifier = ">=0.6.5" }]
|
||||
|
||||
Reference in New Issue
Block a user