159 lines
6.3 KiB
Python
159 lines
6.3 KiB
Python
"""
|
|
Automatic Dependency Checker & Self-Healing Installer for YouTube Factory.
|
|
Ensures that all Python packages, system CLI utilities (ffmpeg, ssh, etc.),
|
|
and remote PC connectivity are fully configured when moving orchestration to a new PC.
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import logging
|
|
import importlib
|
|
|
|
logger = logging.getLogger("YTFactory.Deps")
|
|
|
|
PYTHON_DEPENDENCIES = {
|
|
"requests": "requests>=2.28.0",
|
|
"yaml": "pyyaml>=6.0",
|
|
"numpy": "numpy>=1.22.0",
|
|
"edge_tts": "edge-tts>=7.0.0",
|
|
"aiohttp": "aiohttp>=3.8.0",
|
|
"PIL": "Pillow>=9.0.0",
|
|
"googleapiclient": "google-api-python-client>=2.0.0",
|
|
"google_auth_oauthlib": "google-auth-oauthlib>=1.0.0",
|
|
"google.auth": "google-auth-httplib2>=0.1.0",
|
|
}
|
|
|
|
SYSTEM_BINARIES = [
|
|
("ffmpeg", "Required for video rendering, Ken Burns motion, and audio ducking"),
|
|
("ffprobe", "Required for audio and video stream analysis"),
|
|
("ssh", "Required for remote PC orchestration (AMDLLM, NvidiaLLM, IntelLLM)"),
|
|
("scp", "Required for cross-PC artifact transfers"),
|
|
("curl", "Required for REST API polling"),
|
|
]
|
|
|
|
def check_python_packages(auto_install: bool = True) -> bool:
|
|
"""Check for missing Python packages and automatically install them via pip."""
|
|
missing = []
|
|
for mod_name, pip_pkg in PYTHON_DEPENDENCIES.items():
|
|
try:
|
|
importlib.import_module(mod_name)
|
|
except ImportError:
|
|
missing.append(pip_pkg)
|
|
|
|
if not missing:
|
|
return True
|
|
|
|
logger.warning(f"[DEPS] Missing {len(missing)} Python packages: {', '.join(missing)}")
|
|
if not auto_install:
|
|
return False
|
|
|
|
logger.info("[DEPS] Automatically installing missing Python packages via pip...")
|
|
install_commands = [
|
|
[sys.executable, "-m", "pip", "install", "--upgrade"] + missing,
|
|
[sys.executable, "-m", "pip", "install", "--break-system-packages", "--upgrade"] + missing,
|
|
[sys.executable, "-m", "pip", "install", "--user", "--break-system-packages"] + missing,
|
|
]
|
|
|
|
for cmd in install_commands:
|
|
try:
|
|
r = subprocess.run(cmd, check=True, capture_output=True, text=True)
|
|
logger.info("[DEPS] All Python packages successfully installed!")
|
|
return True
|
|
except subprocess.CalledProcessError:
|
|
continue
|
|
|
|
logger.error(f"[DEPS] Automatic pip install failed.")
|
|
logger.error(f"Please run manually: {sys.executable} -m pip install --break-system-packages -r requirements.txt")
|
|
return False
|
|
|
|
def check_system_binaries(auto_install: bool = True) -> bool:
|
|
"""Check for required system binaries (ffmpeg, ssh, scp, etc.) and attempt self-install."""
|
|
missing_bins = []
|
|
for binary, desc in SYSTEM_BINARIES:
|
|
if not shutil.which(binary):
|
|
missing_bins.append((binary, desc))
|
|
|
|
if not missing_bins:
|
|
return True
|
|
|
|
logger.warning(f"[DEPS] Missing {len(missing_bins)} required system tools:")
|
|
for binary, desc in missing_bins:
|
|
logger.warning(f" - '{binary}': {desc}")
|
|
|
|
if not auto_install:
|
|
return False
|
|
|
|
# Attempt automatic package manager install on Linux
|
|
if sys.platform.startswith("linux"):
|
|
if shutil.which("apt-get"):
|
|
logger.info("[DEPS] Attempting to install system tools via apt-get...")
|
|
pkg_map = {
|
|
"ffmpeg": "ffmpeg",
|
|
"ffprobe": "ffmpeg",
|
|
"ssh": "openssh-client",
|
|
"scp": "openssh-client",
|
|
"curl": "curl",
|
|
}
|
|
pkgs = list({pkg_map.get(b, b) for b, _ in missing_bins})
|
|
try:
|
|
# Test if sudo is passwordless or we are root
|
|
cmd = ["sudo", "apt-get", "update", "-qq"]
|
|
subprocess.run(cmd, check=True, capture_output=True)
|
|
install_cmd = ["sudo", "apt-get", "install", "-y", "-qq"] + pkgs
|
|
subprocess.run(install_cmd, check=True)
|
|
logger.info("[DEPS] System tools successfully installed via apt-get!")
|
|
return True
|
|
except Exception as e:
|
|
logger.warning(f"[DEPS] Automatic apt install failed: {e}")
|
|
|
|
logger.error("=" * 70)
|
|
logger.error("🚨 REQUIRED SYSTEM BINARIES MISSING")
|
|
logger.error("Please install them using your system package manager:")
|
|
logger.error(" Ubuntu / Debian: sudo apt-get update && sudo apt-get install -y ffmpeg openssh-client curl")
|
|
logger.error(" Arch Linux: sudo pacman -S ffmpeg openssh curl")
|
|
logger.error(" Fedora / RHEL: sudo dnf install ffmpeg openssh-clients curl")
|
|
logger.error(" macOS: brew install ffmpeg curl")
|
|
logger.error("=" * 70)
|
|
return False
|
|
|
|
def check_remote_hosts(hosts: list = None) -> bool:
|
|
"""Verify SSH connectivity to the remote AI servers."""
|
|
if not hosts:
|
|
hosts = [("AMDLLM", "10.4.0.181"), ("NvidiaLLM", "10.4.0.180"), ("IntelLLM", "10.4.0.182")]
|
|
|
|
all_ok = True
|
|
for name, host in hosts:
|
|
r = subprocess.run(
|
|
["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=3", "-o", "StrictHostKeyChecking=no", f"mark@{host}", "echo ok"],
|
|
capture_output=True, text=True
|
|
)
|
|
if r.returncode != 0:
|
|
logger.warning(f"[DEPS] SSH key auth to {name} ({host}) failed or timed out.")
|
|
logger.warning(f" Run: ssh-copy-id mark@{host}")
|
|
all_ok = False
|
|
else:
|
|
logger.debug(f"[DEPS] SSH connection to {name} ({host}) verified.")
|
|
|
|
return all_ok
|
|
|
|
def ensure_dependencies(auto_install: bool = True, check_remote: bool = False) -> bool:
|
|
"""
|
|
Main entry point for verifying runtime dependencies.
|
|
Called automatically on factory startup to ensure smooth portability across PCs.
|
|
"""
|
|
py_ok = check_python_packages(auto_install=auto_install)
|
|
sys_ok = check_system_binaries(auto_install=auto_install)
|
|
if check_remote:
|
|
check_remote_hosts()
|
|
return py_ok and sys_ok
|
|
|
|
if __name__ == "__main__":
|
|
logging.basicConfig(level=logging.INFO, format="%(levelname)s | %(message)s")
|
|
print("\n🔍 Checking YouTube Factory Dependencies across PC...\n")
|
|
ok = ensure_dependencies(auto_install=True, check_remote=True)
|
|
if ok:
|
|
print("\n✅ All local dependencies are satisfied and verified!\n")
|
|
else:
|
|
print("\n⚠️ Some dependencies require attention (see logs above).\n")
|