feat: Package VOSK model with installation for faster first-run experience (#32)
* feat: Package VOSK model with installation for faster first-run experience 🚀 Installation Improvements: - Add VOSK small model download during installation process - Eliminate first-run model download delay for better user experience - Add --skip-models option for offline installations - Include wget, curl, unzip in system dependencies for model download 🔍 Model Management Enhancements: - Add multi-location model search (user, system-wide, pre-installed) - Prioritize pre-installed models over downloads - Add informative logging for model source detection - Create marker files to identify installer-provided models 📦 Installation Script Features: - Robust model download with progress indicators - Internet connectivity checking before download attempts - Graceful fallback when download fails (runtime download) - Comprehensive error handling and cleanup - Installation summary includes model status 🔧 Recognition Manager Updates: - Check multiple model locations in priority order: 1. User's local models directory 2. System-wide installation directories 3. Download to user directory if not found - Enhanced logging for model source identification - Backward compatibility with existing installations 💡 Benefits: - Faster application startup (no initial model download) - Better offline installation support - Reduced first-run friction for users - Maintains fallback to runtime download if needed - Clear feedback about model installation status This addresses the issue where users had to wait for model downloads on first application launch, making VocaLinux ready to use immediately after installation. * fix: Move install_vosk_models function definition before its call The function was being called before it was defined, causing a 'command not found' error during installation. Fixed by moving the function definition to before the installation steps where it's called. This resolves the installation error: ./install.sh: line 701: install_vosk_models: command not found
This commit is contained in:
+122
-4
@@ -26,6 +26,7 @@ INSTALL_MODE="user"
|
||||
RUN_TESTS="no"
|
||||
DEV_MODE="no"
|
||||
VENV_DIR="venv"
|
||||
SKIP_MODELS="no"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
@@ -41,6 +42,10 @@ while [[ $# -gt 0 ]]; do
|
||||
VENV_DIR="${1#*=}"
|
||||
shift
|
||||
;;
|
||||
--skip-models)
|
||||
SKIP_MODELS="yes"
|
||||
shift
|
||||
;;
|
||||
--help)
|
||||
echo "Vocalinux Installer"
|
||||
echo "Usage: $0 [options]"
|
||||
@@ -48,6 +53,7 @@ while [[ $# -gt 0 ]]; do
|
||||
echo " --dev Install in development mode with all dev dependencies"
|
||||
echo " --test Run tests after installation"
|
||||
echo " --venv-dir=PATH Specify custom virtual environment directory (default: venv)"
|
||||
echo " --skip-models Skip downloading VOSK models during installation"
|
||||
echo " --help Show this help message"
|
||||
exit 0
|
||||
;;
|
||||
@@ -170,10 +176,10 @@ install_system_dependencies() {
|
||||
print_info "Installing system dependencies..."
|
||||
|
||||
# Define package names for different distributions
|
||||
local APT_PACKAGES="python3-pip python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-appindicator3-0.1 libgirepository1.0-dev python3-dev portaudio19-dev python3-venv"
|
||||
local DNF_PACKAGES="python3-pip python3-gobject gtk3 libappindicator-gtk3 gobject-introspection-devel python3-devel portaudio-devel python3-virtualenv"
|
||||
local PACMAN_PACKAGES="python-pip python-gobject gtk3 libappindicator-gtk3 gobject-introspection python-cairo portaudio python-virtualenv"
|
||||
local ZYPPER_PACKAGES="python3-pip python3-gobject python3-gobject-cairo gtk3 libappindicator-gtk3 gobject-introspection-devel python3-devel portaudio-devel python3-virtualenv"
|
||||
local APT_PACKAGES="python3-pip python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-appindicator3-0.1 libgirepository1.0-dev python3-dev portaudio19-dev python3-venv wget curl unzip"
|
||||
local DNF_PACKAGES="python3-pip python3-gobject gtk3 libappindicator-gtk3 gobject-introspection-devel python3-devel portaudio-devel python3-virtualenv wget curl unzip"
|
||||
local PACMAN_PACKAGES="python-pip python-gobject gtk3 libappindicator-gtk3 gobject-introspection python-cairo portaudio python-virtualenv wget curl unzip"
|
||||
local ZYPPER_PACKAGES="python3-pip python3-gobject python3-gobject-cairo gtk3 libappindicator-gtk3 gobject-introspection-devel python3-devel portaudio-devel python3-virtualenv wget curl unzip"
|
||||
|
||||
local MISSING_PACKAGES=""
|
||||
local INSTALL_CMD=""
|
||||
@@ -578,6 +584,101 @@ if ! install_python_package; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Function to download and install VOSK models
|
||||
install_vosk_models() {
|
||||
print_info "Installing VOSK speech recognition models..."
|
||||
|
||||
# Create models directory
|
||||
local MODELS_DIR="$DATA_DIR/models"
|
||||
mkdir -p "$MODELS_DIR"
|
||||
|
||||
# Define model information
|
||||
local SMALL_MODEL_URL="https://alphacephei.com/vosk/models/vosk-model-small-en-us-0.15.zip"
|
||||
local SMALL_MODEL_NAME="vosk-model-small-en-us-0.15"
|
||||
local SMALL_MODEL_PATH="$MODELS_DIR/$SMALL_MODEL_NAME"
|
||||
|
||||
# Check if small model already exists
|
||||
if [ -d "$SMALL_MODEL_PATH" ]; then
|
||||
print_info "Small VOSK model already exists at $SMALL_MODEL_PATH"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check internet connectivity
|
||||
if ! command -v wget >/dev/null 2>&1 && ! command -v curl >/dev/null 2>&1; then
|
||||
print_warning "Neither wget nor curl found. Cannot download VOSK models."
|
||||
print_warning "Models will be downloaded on first application run."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Test internet connectivity
|
||||
if ! ping -c 1 google.com >/dev/null 2>&1; then
|
||||
print_warning "No internet connection detected."
|
||||
print_warning "VOSK models will be downloaded on first application run."
|
||||
return 1
|
||||
fi
|
||||
|
||||
print_info "Downloading small VOSK model (approximately 40MB)..."
|
||||
print_info "This may take a few minutes depending on your internet connection."
|
||||
|
||||
local TEMP_ZIP="$MODELS_DIR/$(basename $SMALL_MODEL_URL)"
|
||||
|
||||
# Download the model
|
||||
if command -v wget >/dev/null 2>&1; then
|
||||
if ! wget --progress=bar:force:noscroll -O "$TEMP_ZIP" "$SMALL_MODEL_URL" 2>&1; then
|
||||
print_error "Failed to download VOSK model with wget"
|
||||
rm -f "$TEMP_ZIP"
|
||||
return 1
|
||||
fi
|
||||
elif command -v curl >/dev/null 2>&1; then
|
||||
if ! curl -L --progress-bar -o "$TEMP_ZIP" "$SMALL_MODEL_URL"; then
|
||||
print_error "Failed to download VOSK model with curl"
|
||||
rm -f "$TEMP_ZIP"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Verify download
|
||||
if [ ! -f "$TEMP_ZIP" ] || [ ! -s "$TEMP_ZIP" ]; then
|
||||
print_error "Downloaded model file is empty or missing"
|
||||
rm -f "$TEMP_ZIP"
|
||||
return 1
|
||||
fi
|
||||
|
||||
print_info "Extracting VOSK model..."
|
||||
|
||||
# Extract the model
|
||||
if command -v unzip >/dev/null 2>&1; then
|
||||
if ! unzip -q "$TEMP_ZIP" -d "$MODELS_DIR"; then
|
||||
print_error "Failed to extract VOSK model"
|
||||
rm -f "$TEMP_ZIP"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
print_error "unzip command not found. Cannot extract VOSK model."
|
||||
rm -f "$TEMP_ZIP"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Clean up zip file
|
||||
rm -f "$TEMP_ZIP"
|
||||
|
||||
# Verify extraction
|
||||
if [ -d "$SMALL_MODEL_PATH" ]; then
|
||||
print_success "VOSK small model installed successfully at $SMALL_MODEL_PATH"
|
||||
|
||||
# Set proper permissions
|
||||
chmod -R 755 "$SMALL_MODEL_PATH"
|
||||
|
||||
# Create a marker file to indicate this model was pre-installed
|
||||
echo "$(date)" > "$SMALL_MODEL_PATH/.vocalinux_preinstalled"
|
||||
|
||||
return 0
|
||||
else
|
||||
print_error "VOSK model extraction failed - directory not found"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to install desktop entry with error handling
|
||||
install_desktop_entry() {
|
||||
print_info "Installing desktop entry..."
|
||||
@@ -690,6 +791,14 @@ install_desktop_entry || print_warning "Desktop entry installation failed"
|
||||
# Install icons
|
||||
install_icons || print_warning "Icon installation failed"
|
||||
|
||||
# Install VOSK models
|
||||
if [ "$SKIP_MODELS" = "no" ]; then
|
||||
install_vosk_models || print_warning "VOSK model installation failed - models will be downloaded on first run"
|
||||
else
|
||||
print_info "Skipping VOSK model installation (--skip-models specified)"
|
||||
print_info "Models will be downloaded automatically on first application run"
|
||||
fi
|
||||
|
||||
# Update icon cache
|
||||
update_icon_cache
|
||||
|
||||
@@ -812,6 +921,15 @@ print_installation_summary() {
|
||||
print_info "- Configuration: $CONFIG_DIR"
|
||||
print_info "- Data directory: $DATA_DIR"
|
||||
|
||||
# Check if VOSK model was installed
|
||||
if [ "$SKIP_MODELS" = "no" ] && [ -d "$DATA_DIR/models/vosk-model-small-en-us-0.15" ]; then
|
||||
print_info "- VOSK small model: $DATA_DIR/models/vosk-model-small-en-us-0.15"
|
||||
elif [ "$SKIP_MODELS" = "yes" ]; then
|
||||
print_info "- VOSK models: Will be downloaded on first run (--skip-models used)"
|
||||
else
|
||||
print_info "- VOSK models: Will be downloaded on first run (installation failed)"
|
||||
fi
|
||||
|
||||
echo
|
||||
print_info "To activate the virtual environment in the future, run:"
|
||||
print_info " source activate-vocalinux.sh"
|
||||
|
||||
@@ -8,6 +8,7 @@ currently supporting VOSK and Whisper.
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
@@ -21,6 +22,11 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# Define constants
|
||||
MODELS_DIR = os.path.expanduser("~/.local/share/vocalinux/models")
|
||||
# Alternative locations for pre-installed models
|
||||
SYSTEM_MODELS_DIRS = [
|
||||
"/usr/local/share/vocalinux/models",
|
||||
"/usr/share/vocalinux/models",
|
||||
]
|
||||
|
||||
|
||||
class SpeechRecognitionManager:
|
||||
@@ -86,6 +92,16 @@ class SpeechRecognitionManager:
|
||||
f"VOSK model not found at {self.vosk_model_path}. Downloading..."
|
||||
)
|
||||
self._download_vosk_model()
|
||||
# Update path after download
|
||||
self.vosk_model_path = self._get_vosk_model_path()
|
||||
else:
|
||||
# Check if this is a pre-installed model
|
||||
if any(self.vosk_model_path.startswith(sys_dir) for sys_dir in SYSTEM_MODELS_DIRS):
|
||||
logger.info(f"Using pre-installed VOSK model from {self.vosk_model_path}")
|
||||
elif os.path.exists(os.path.join(self.vosk_model_path, ".vocalinux_preinstalled")):
|
||||
logger.info(f"Using installer-provided VOSK model from {self.vosk_model_path}")
|
||||
else:
|
||||
logger.info(f"Using existing VOSK model from {self.vosk_model_path}")
|
||||
|
||||
logger.info(f"Loading VOSK model from {self.vosk_model_path}")
|
||||
# Ensure previous model/recognizer are released if re-initializing
|
||||
@@ -129,7 +145,23 @@ class SpeechRecognitionManager:
|
||||
}
|
||||
|
||||
model_name = model_map.get(self.model_size, model_map["small"])
|
||||
return os.path.join(MODELS_DIR, model_name)
|
||||
|
||||
# First, check user's local models directory
|
||||
user_model_path = os.path.join(MODELS_DIR, model_name)
|
||||
if os.path.exists(user_model_path):
|
||||
logger.debug(f"Found user model at: {user_model_path}")
|
||||
return user_model_path
|
||||
|
||||
# Then check system-wide installation directories
|
||||
for system_dir in SYSTEM_MODELS_DIRS:
|
||||
system_model_path = os.path.join(system_dir, model_name)
|
||||
if os.path.exists(system_model_path):
|
||||
logger.info(f"Found pre-installed model at: {system_model_path}")
|
||||
return system_model_path
|
||||
|
||||
# If not found anywhere, return the user path (will be created if needed)
|
||||
logger.debug(f"No existing model found, will use: {user_model_path}")
|
||||
return user_model_path
|
||||
|
||||
def _download_vosk_model(self):
|
||||
"""Download the VOSK model if it doesn't exist."""
|
||||
@@ -149,8 +181,15 @@ class SpeechRecognitionManager:
|
||||
raise ValueError(f"Unknown model size: {self.model_size}")
|
||||
|
||||
model_name = os.path.basename(url).replace(".zip", "")
|
||||
|
||||
# Always download to user's local directory
|
||||
model_path = os.path.join(MODELS_DIR, model_name)
|
||||
zip_path = os.path.join(MODELS_DIR, os.path.basename(url))
|
||||
|
||||
# Create models directory if it doesn't exist
|
||||
os.makedirs(MODELS_DIR, exist_ok=True)
|
||||
|
||||
logger.info(f"Downloading VOSK {self.model_size} model to user directory: {model_path}")
|
||||
|
||||
# Download the model
|
||||
logger.info(f"Downloading VOSK model from {url}")
|
||||
|
||||
Reference in New Issue
Block a user