#!/bin/sh # OmniVoice Studio — universal installer. # # Works on macOS (ARM + Intel), Linux (Debian/Ubuntu, Fedora, Arch), and WSL. # Run once, then `./run.sh` each time you want to use the app. # # Usage: # curl -fsSL https://raw.githubusercontent.com/debpalash/OmniVoice-Studio/main/install.sh | sh # # or locally: # sh install.sh # sh install.sh --verbose # show all subcommand output # sh install.sh --python 3.12 # override Python version set -e # ── Output style ──────────────────────────────────────────────────────────── RULE="" _rule_i=0 while [ "$_rule_i" -lt 56 ]; do RULE="${RULE}─" _rule_i=$((_rule_i + 1)) done if [ -n "${NO_COLOR:-}" ]; then C_TITLE="" C_DIM="" C_OK="" C_WARN="" C_ERR="" C_RST="" elif [ -t 1 ] || [ -n "${FORCE_COLOR:-}" ]; then _ESC="$(printf '\033')" C_TITLE="${_ESC}[1;38;5;141m" # bold purple C_DIM="${_ESC}[38;5;245m" C_OK="${_ESC}[38;5;108m" # green C_WARN="${_ESC}[38;5;136m" # yellow C_ERR="${_ESC}[91m" # red C_RST="${_ESC}[0m" else C_TITLE="" C_DIM="" C_OK="" C_WARN="" C_ERR="" C_RST="" fi step() { printf " ${C_DIM}%-18.18s${C_RST}${3:-$C_OK}%s${C_RST}\n" "$1" "$2"; } note() { printf " ${C_DIM}%-18s${2:-$C_DIM}%s${C_RST}\n" "" "$1"; } warn() { printf " ${C_WARN}⚠ %s${C_RST}\n" "$1"; } die() { printf " ${C_ERR}✗ %s${C_RST}\n" "$1" >&2; exit 1; } have() { command -v "$1" >/dev/null 2>&1; } # ── Parse flags ───────────────────────────────────────────────────────────── _VERBOSE=false _USER_PYTHON="" _next_is_python=false for arg in "$@"; do if [ "$_next_is_python" = true ]; then _USER_PYTHON="$arg" _next_is_python=false continue fi case "$arg" in --verbose|-v) _VERBOSE=true ;; --python) _next_is_python=true ;; esac done if [ "$_next_is_python" = true ]; then die "--python requires a version argument (e.g. --python 3.12)" fi run_quiet() { if [ "$_VERBOSE" = true ]; then "$@" else "$@" > /dev/null 2>&1 fi } # ── Helper: download (curl or wget) ──────────────────────────────────────── download() { if have curl; then curl -LsSf "$1" -o "$2" elif have wget; then wget -qO "$2" "$1" else die "Neither curl nor wget found. Install one and re-run." fi } # ── Helper: open browser (cross-platform) ────────────────────────────────── open_browser() { _url="$1" if [ "$(uname)" = "Darwin" ] && have open; then open "$_url" elif grep -qi microsoft /proc/version 2>/dev/null; then # WSL: use Windows browser if have powershell.exe; then powershell.exe -NoProfile -Command "Start-Process '$_url'" >/dev/null 2>&1 & elif have cmd.exe; then cmd.exe /c start "" "$_url" >/dev/null 2>&1 & elif have xdg-open; then xdg-open "$_url" >/dev/null 2>&1 & else echo " Open in your browser: $_url" fi elif have xdg-open; then xdg-open "$_url" >/dev/null 2>&1 & else echo " Open in your browser: $_url" fi } # ── Detect platform ──────────────────────────────────────────────────────── OS="linux" if [ "$(uname)" = "Darwin" ]; then OS="macos" elif grep -qi microsoft /proc/version 2>/dev/null; then OS="wsl" fi ARCH=$(uname -m) echo "" printf " ${C_TITLE}%s${C_RST}\n" "🎙 OmniVoice Studio Installer" printf " ${C_DIM}%s${C_RST}\n" "$RULE" echo "" step "platform" "$OS ($ARCH)" # Detect Rosetta on macOS if [ "$OS" = "macos" ] && [ "$ARCH" = "x86_64" ]; then if [ "$(sysctl -in hw.optional.arm64 2>/dev/null || echo 0)" = "1" ]; then warn "Apple Silicon detected running under Rosetta (x86_64)." note "Re-run from a native arm64 terminal for full MLX support." fi fi # ── Resolve script directory (for local installs) ────────────────────────── SCRIPT_DIR="" if [ -n "${0:-}" ] && [ -f "$0" ]; then SCRIPT_DIR=$(cd "$(dirname "$0")" 2>/dev/null && pwd) || true fi if [ -z "$SCRIPT_DIR" ]; then SCRIPT_DIR=$(pwd) fi # If run via curl pipe, clone the repo first if [ ! -f "$SCRIPT_DIR/pyproject.toml" ]; then step "clone" "downloading OmniVoice Studio..." INSTALL_DIR="$HOME/OmniVoice" if [ -d "$INSTALL_DIR/.git" ]; then note "Updating existing clone at $INSTALL_DIR" (cd "$INSTALL_DIR" && git pull --ff-only 2>/dev/null || true) else if have git; then git clone --depth 1 https://github.com/debpalash/OmniVoice-Studio.git "$INSTALL_DIR" else die "git is required. Install git and re-run." fi fi SCRIPT_DIR="$INSTALL_DIR" fi cd "$SCRIPT_DIR" # ── Python version ────────────────────────────────────────────────────────── if [ -n "$_USER_PYTHON" ]; then PYTHON_VERSION="$_USER_PYTHON" note "Using user-specified Python $PYTHON_VERSION" else PYTHON_VERSION="3.11" fi # ── System dependencies ──────────────────────────────────────────────────── # Helper: install system packages with the right package manager _install_sys_pkgs() { case "$OS" in macos) if ! have brew; then note "Installing Homebrew..." /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" /dev/null || true ;; linux|wsl) if have apt-get; then # Try without sudo first, then escalate apt-get update -y /dev/null 2>&1 || true apt-get install -y "$@" /dev/null 2>&1 || { if have sudo; then echo "" echo " Need elevated permissions to install: $*" sudo apt-get update -y /dev/null || true elif have yum; then sudo yum install -y "$@" /dev/null || true elif have pacman; then sudo pacman -S --noconfirm "$@" 2>/dev/null || true else warn "No supported package manager found. Please install manually: $*" fi ;; esac } # Xcode CLT (macOS only) if [ "$OS" = "macos" ]; then step "xcode" "checking..." if ! xcode-select -p >/dev/null 2>&1; then note "Installing Xcode Command Line Tools..." xcode-select --install /dev/null || true until xcode-select -p >/dev/null 2>&1; do note "Waiting for Xcode CLT install..." sleep 10 done fi step "xcode" "$(xcode-select -p)" fi # ffmpeg (required for audio/video processing) step "ffmpeg" "checking..." if ! have ffmpeg; then note "Installing ffmpeg..." case "$OS" in macos) _install_sys_pkgs ffmpeg ;; linux|wsl) if have apt-get; then _install_sys_pkgs ffmpeg elif have dnf; then _install_sys_pkgs ffmpeg-free # Fedora elif have pacman; then _install_sys_pkgs ffmpeg else warn "Please install ffmpeg manually." fi ;; esac fi if have ffmpeg; then step "ffmpeg" "$(ffmpeg -version 2>/dev/null | head -n1 | cut -d' ' -f1-3)" else warn "ffmpeg not found — some features will be unavailable." fi # ── Install uv ────────────────────────────────────────────────────────────── step "uv" "checking..." UV_MIN_VERSION="0.7.0" _version_ge() { # Returns 0 if $1 >= $2 (dotted version comparison) _a=$1; _b=$2 while [ -n "$_a" ] || [ -n "$_b" ]; do _a_part=${_a%%.*}; _b_part=${_b%%.*} [ "$_a" = "$_a_part" ] && _a="" || _a=${_a#*.} [ "$_b" = "$_b_part" ] && _b="" || _b=${_b#*.} [ -z "$_a_part" ] && _a_part=0 [ -z "$_b_part" ] && _b_part=0 if [ "$_a_part" -gt "$_b_part" ] 2>/dev/null; then return 0; fi if [ "$_a_part" -lt "$_b_part" ] 2>/dev/null; then return 1; fi done return 0 } _uv_ok() { have uv || return 1 _raw=$(uv --version 2>/dev/null | awk '{print $2}') || return 1 [ -n "$_raw" ] || return 1 _ver=${_raw%%[-+]*} _version_ge "$_ver" "$UV_MIN_VERSION" } if ! _uv_ok; then note "Installing uv package manager..." _uv_tmp=$(mktemp) download "https://astral.sh/uv/install.sh" "$_uv_tmp" run_quiet sh "$_uv_tmp" /dev/null)" # ── Install bun (JS runtime for frontend) ────────────────────────────────── step "bun" "checking..." if ! have bun; then note "Installing bun..." if have curl; then curl -fsSL https://bun.sh/install | sh /dev/null)" # ── GPU detection (informational) ────────────────────────────────────────── step "gpu" "detecting..." GPU_INFO="CPU only" if [ "$OS" = "macos" ] && [ "$ARCH" = "arm64" ]; then GPU_INFO="Apple Silicon (Metal/MPS)" elif have nvidia-smi; then _gpu_name=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -1) _cuda_ver=$(nvidia-smi 2>/dev/null | sed -n 's/.*CUDA Version:[[:space:]]*\([0-9]*\.[0-9]*\).*/\1/p' | head -1) if [ -n "$_gpu_name" ]; then GPU_INFO="NVIDIA $_gpu_name (CUDA $_cuda_ver)" fi elif have rocminfo; then _amd_name=$(rocminfo 2>/dev/null | awk '/Marketing Name:/{$1=$2=""; print; exit}' | sed 's/^ *//') if [ -n "$_amd_name" ]; then GPU_INFO="AMD $_amd_name (ROCm)" fi fi step "gpu" "$GPU_INFO" # ── Python dependencies via uv ────────────────────────────────────────────── step "python" "syncing dependencies..." note "This can take 5–10 min the first time (torch + torchaudio + demucs...)" # Create venv with the target Python version if it doesn't exist if [ ! -d .venv ]; then uv venv --python "$PYTHON_VERSION" fi # Sync all deps from pyproject.toml + uv.lock uv sync step "python" "OK — virtualenv at .venv/" # ── Frontend deps + build ────────────────────────────────────────────────── step "frontend" "installing dependencies..." (cd frontend && bun install) step "frontend" "OK" step "frontend" "building bundle..." (cd frontend && bun run build) step "frontend" "OK — output at frontend/dist/" # ── Log directory ────────────────────────────────────────────────────────── case "$OS" in macos) LOG_DIR="$HOME/Library/Application Support/OmniVoice" ;; *) LOG_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/OmniVoice" ;; esac mkdir -p "$LOG_DIR" # ── Done ──────────────────────────────────────────────────────────────────── echo "" printf " ${C_TITLE}%s${C_RST}\n" "✓ Install complete!" printf " ${C_DIM}%s${C_RST}\n" "$RULE" echo "" step "next" "Run ./run.sh to start OmniVoice Studio" echo "" note "First launch downloads ~5 GB of ML model weights (OmniVoice TTS + Whisper)." note "After that, launches are instant." echo "" note "GPU: $GPU_INFO" note "Logs: $LOG_DIR/omnivoice.log" echo ""