#!/usr/bin/env python3 """ start.py — Start the AI Video Upscaler web application. Usage: python start.py # Start on default port 8000 (binds to 0.0.0.0 for LAN access) python start.py --port 9000 # Start on a custom port python start.py --host 127.0.0.1 # Bind to loopback only (no LAN access) """ import argparse import os import signal import socket import subprocess import sys import time import webbrowser # ── Paths ──────────────────────────────────────────────────────────────────── BASE_DIR = os.path.dirname(os.path.abspath(__file__)) PID_FILE = os.path.join(BASE_DIR, ".uvicorn.pid") LOG_FILE = os.path.join(BASE_DIR, "server.log") VENV_PYTHON = sys.executable # default fallback def ensure_venv(): """Detect or create the virtual environment and install dependencies.""" global VENV_PYTHON # 1. Already inside an activated venv — use it if sys.prefix != sys.base_prefix: VENV_PYTHON = sys.executable return venv_dir = os.path.join(BASE_DIR, "venv") dot_venv_dir = os.path.join(BASE_DIR, ".venv") # Check if either venv/ or .venv/ exists with a python interpreter selected_venv = None if os.path.isdir(os.path.join(venv_dir, "bin")): selected_venv = venv_dir elif os.path.isdir(os.path.join(dot_venv_dir, "bin")): selected_venv = dot_venv_dir if selected_venv: python_exe = os.path.join(selected_venv, "bin", "python") if os.path.isfile(python_exe): VENV_PYTHON = python_exe return # No valid venv found — let's build it! print("⚙️ Virtual environment not detected. Initializing setup...") print(f" Creating virtual environment at: {venv_dir}") try: import venv venv.create(venv_dir, with_pip=True) print("✅ Virtual environment created.") except Exception as e: print(f"❌ Failed to create virtual environment via 'venv' module: {e}") print(" Attempting subprocess fallback...") try: subprocess.run([sys.executable, "-m", "venv", venv_dir], check=True) print("✅ Virtual environment created (fallback).") except Exception as err: print(f"❌ Subprocess fallback failed: {err}") sys.exit(1) VENV_PYTHON = os.path.join(venv_dir, "bin", "python") pip_exe = os.path.join(venv_dir, "bin", "pip") # Install dependencies requirements_file = os.path.join(BASE_DIR, "requirements.txt") if os.path.isfile(requirements_file): print("📦 Installing Python dependencies from requirements.txt...") try: # Upgrade pip subprocess.run([pip_exe, "install", "--upgrade", "pip", "--quiet"], check=True) # Install requirements subprocess.run([pip_exe, "install", "-r", requirements_file], check=True) print("✅ All Python dependencies installed successfully.") except Exception as e: print(f"❌ Error installing dependencies: {e}") sys.exit(1) else: print("⚠ requirements.txt not found. Skipping dependency installation.") # Configure Real-ESRGAN binary permissions binary_path = os.path.join(BASE_DIR, "realesrgan-bin", "realesrgan-ncnn-vulkan") if os.path.isfile(binary_path): try: os.chmod(binary_path, 0o755) print("✅ Real-ESRGAN binary marked as executable.") except Exception as e: print(f"⚠ Failed to change binary permissions: {e}") # Create working directories for d in ["uploads", "outputs", "temp"]: os.makedirs(os.path.join(BASE_DIR, d), exist_ok=True) print("✅ Working directories verified.") def _is_running(pid: int) -> bool: """Check whether a process with the given PID is still alive and is our server.""" try: os.kill(pid, 0) except OSError: return False # Check /proc/{pid}/cmdline to verify it's our server process try: cmdline_path = f"/proc/{pid}/cmdline" if os.path.exists(cmdline_path): with open(cmdline_path, "r") as f: cmdline = f.read() # Check for indicators of our server return "uvicorn" in cmdline or "app.main" in cmdline except Exception: pass # If we can't read cmdline, default to True (safer fallback) return True def _read_pid() -> int | None: """Read and validate the PID from the pidfile.""" if not os.path.isfile(PID_FILE): return None try: pid = int(open(PID_FILE).read().strip()) return pid if _is_running(pid) else None except (ValueError, OSError): return None def _get_local_ip() -> str: """Get the primary local network IP address.""" s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: # Dummy connection to google DNS to get local route IP s.connect(("8.8.8.8", 80)) ip = s.getsockname()[0] except Exception: try: ip = socket.gethostbyname(socket.gethostname()) except Exception: ip = "127.0.0.1" finally: s.close() return ip def start(host: str, port: int, open_browser: bool = True) -> None: existing = _read_pid() if existing: print(f"⚠ Server is already running (PID {existing}).") if host == "0.0.0.0": local_ip = _get_local_ip() print(f" → Local: http://localhost:{port}") if local_ip and local_ip != "127.0.0.1": print(f" → Network: http://{local_ip}:{port}") else: print(f" → http://{host}:{port}") return print(f"🚀 Starting AI Video Upscaler …") print(f" Host : {host}") print(f" Port : {port}") print(f" Log : {LOG_FILE}") log_fh = open(LOG_FILE, "a") proc = subprocess.Popen( [ VENV_PYTHON, "-m", "uvicorn", "app.main:app", "--host", host, "--port", str(port), ], cwd=BASE_DIR, stdout=log_fh, stderr=log_fh, start_new_session=True, # detach from terminal ) # Write PID with open(PID_FILE, "w") as f: f.write(str(proc.pid)) # Wait briefly to make sure it didn't crash immediately time.sleep(1.5) if proc.poll() is not None: print("❌ Server failed to start. Check server.log for details.") os.remove(PID_FILE) sys.exit(1) url = f"http://{'localhost' if host == '0.0.0.0' else host}:{port}" print(f"✅ Server started (PID {proc.pid})") if host == "0.0.0.0": local_ip = _get_local_ip() print(f" → Local: http://localhost:{port}") if local_ip and local_ip != "127.0.0.1": print(f" → Network: http://{local_ip}:{port}") else: print(f" → {url}") if open_browser: webbrowser.open(url) # ── CLI ────────────────────────────────────────────────────────────────────── if __name__ == "__main__": parser = argparse.ArgumentParser(description="Start the AI Video Upscaler") parser.add_argument("--host", default="0.0.0.0", help="Bind address (default: 0.0.0.0 to bind all interfaces, use 127.0.0.1 for local loopback only)") parser.add_argument("--port", type=int, default=8000, help="Port to listen on (default: 8000)") parser.add_argument("--no-browser", action="store_true", help="Don't open the browser automatically") args = parser.parse_args() ensure_venv() start(args.host, args.port, open_browser=not args.no_browser)