261 lines
8.3 KiB
Python
261 lines
8.3 KiB
Python
#!/usr/bin/env python3
|
||||
|
|
"""
|
|||
|
|
Installer for YouTube Downloader dependencies.
|
|||
|
|
Installs: yt-dlp, ffmpeg
|
|||
|
|
Supports: Windows, macOS, Linux (Debian/Ubuntu, Fedora/RHEL, Arch)
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import os
|
|||
|
|
import sys
|
|||
|
|
import shutil
|
|||
|
|
import subprocess
|
|||
|
|
import platform
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ── Helpers ────────────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
def run(cmd: list[str], check=True) -> subprocess.CompletedProcess:
|
|||
|
|
print(f" → {' '.join(cmd)}")
|
|||
|
|
return subprocess.run(cmd, check=check)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def ok(msg: str):
|
|||
|
|
print(f" ✅ {msg}")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def info(msg: str):
|
|||
|
|
print(f" ℹ️ {msg}")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def warn(msg: str):
|
|||
|
|
print(f" ⚠️ {msg}")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def fail(msg: str):
|
|||
|
|
print(f" ❌ {msg}")
|
|||
|
|
sys.exit(1)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def section(title: str):
|
|||
|
|
print(f"\n{'─' * 50}")
|
|||
|
|
print(f" {title}")
|
|||
|
|
print(f"{'─' * 50}")
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ── Platform detection ─────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
OS = platform.system() # 'Windows', 'Darwin', 'Linux'
|
|||
|
|
IS_WIN = OS == "Windows"
|
|||
|
|
IS_MAC = OS == "Darwin"
|
|||
|
|
IS_LINUX = OS == "Linux"
|
|||
|
|
|
|||
|
|
|
|||
|
|
def linux_distro() -> str:
|
|||
|
|
"""Return 'debian', 'fedora', 'arch', or 'unknown'."""
|
|||
|
|
if shutil.which("apt-get"):
|
|||
|
|
return "debian"
|
|||
|
|
if shutil.which("dnf") or shutil.which("yum"):
|
|||
|
|
return "fedora"
|
|||
|
|
if shutil.which("pacman"):
|
|||
|
|
return "arch"
|
|||
|
|
return "unknown"
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ── Python / pip check ─────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
def check_python():
|
|||
|
|
section("Checking Python")
|
|||
|
|
major, minor = sys.version_info[:2]
|
|||
|
|
print(f" Python {major}.{minor} detected")
|
|||
|
|
if major < 3 or (major == 3 and minor < 8):
|
|||
|
|
fail("Python 3.8+ is required. Please upgrade and re-run this script.")
|
|||
|
|
ok("Python version is compatible")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def ensure_pip():
|
|||
|
|
section("Checking pip")
|
|||
|
|
try:
|
|||
|
|
import pip # noqa: F401
|
|||
|
|
ok("pip is available")
|
|||
|
|
except ImportError:
|
|||
|
|
info("pip not found — installing via ensurepip...")
|
|||
|
|
run([sys.executable, "-m", "ensurepip", "--upgrade"])
|
|||
|
|
ok("pip installed")
|
|||
|
|
|
|||
|
|
# Always upgrade pip to avoid resolver issues
|
|||
|
|
run([sys.executable, "-m", "pip", "install", "--upgrade", "pip"])
|
|||
|
|
ok("pip is up to date")
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ── yt-dlp ─────────────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
def install_ytdlp():
|
|||
|
|
section("Installing yt-dlp")
|
|||
|
|
run([sys.executable, "-m", "pip", "install", "--upgrade", "yt-dlp"])
|
|||
|
|
ok("yt-dlp installed/updated")
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ── ffmpeg ─────────────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
def ffmpeg_already_installed() -> bool:
|
|||
|
|
if shutil.which("ffmpeg"):
|
|||
|
|
result = subprocess.run(
|
|||
|
|
["ffmpeg", "-version"], capture_output=True, text=True, check=False
|
|||
|
|
)
|
|||
|
|
version_line = result.stdout.splitlines()[0] if result.stdout else "unknown"
|
|||
|
|
info(f"ffmpeg already installed: {version_line}")
|
|||
|
|
return True
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
|
|||
|
|
def install_ffmpeg_windows():
|
|||
|
|
"""
|
|||
|
|
On Windows, download the pre-built ffmpeg binary via winget or chocolatey.
|
|||
|
|
Falls back to manual instructions if neither is available.
|
|||
|
|
"""
|
|||
|
|
if shutil.which("winget"):
|
|||
|
|
info("Using winget to install ffmpeg...")
|
|||
|
|
run(["winget", "install", "--id", "Gyan.FFmpeg", "-e", "--silent"])
|
|||
|
|
elif shutil.which("choco"):
|
|||
|
|
info("Using Chocolatey to install ffmpeg...")
|
|||
|
|
run(["choco", "install", "ffmpeg", "-y"])
|
|||
|
|
elif shutil.which("scoop"):
|
|||
|
|
info("Using Scoop to install ffmpeg...")
|
|||
|
|
run(["scoop", "install", "ffmpeg"])
|
|||
|
|
else:
|
|||
|
|
warn("No package manager found (winget / choco / scoop).")
|
|||
|
|
print("""
|
|||
|
|
Please install ffmpeg manually:
|
|||
|
|
1. Go to https://www.gyan.dev/ffmpeg/builds/
|
|||
|
|
2. Download the latest 'release essentials' zip
|
|||
|
|
3. Extract it and add the 'bin' folder to your PATH
|
|||
|
|
""")
|
|||
|
|
return
|
|||
|
|
ok("ffmpeg installed")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def install_ffmpeg_mac():
|
|||
|
|
if shutil.which("brew"):
|
|||
|
|
info("Using Homebrew to install ffmpeg...")
|
|||
|
|
run(["brew", "install", "ffmpeg"])
|
|||
|
|
elif shutil.which("port"):
|
|||
|
|
info("Using MacPorts to install ffmpeg...")
|
|||
|
|
run(["sudo", "port", "install", "ffmpeg"])
|
|||
|
|
else:
|
|||
|
|
warn("Homebrew not found. Installing Homebrew first...")
|
|||
|
|
install_cmd = (
|
|||
|
|
'/bin/bash -c "$(curl -fsSL '
|
|||
|
|
'https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
|
|||
|
|
)
|
|||
|
|
subprocess.run(install_cmd, shell=True, check=True)
|
|||
|
|
run(["brew", "install", "ffmpeg"])
|
|||
|
|
ok("ffmpeg installed")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def install_ffmpeg_linux():
|
|||
|
|
distro = linux_distro()
|
|||
|
|
|
|||
|
|
if distro == "debian":
|
|||
|
|
info("Using apt-get (Debian/Ubuntu)...")
|
|||
|
|
run(["sudo", "apt-get", "update", "-qq"])
|
|||
|
|
run(["sudo", "apt-get", "install", "-y", "ffmpeg"])
|
|||
|
|
|
|||
|
|
elif distro == "fedora":
|
|||
|
|
info("Using dnf (Fedora/RHEL)...")
|
|||
|
|
# ffmpeg lives in RPM Fusion on Fedora
|
|||
|
|
run(["sudo", "dnf", "install", "-y",
|
|||
|
|
"https://download1.rpmfusion.org/free/fedora/"
|
|||
|
|
"rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm"], check=False)
|
|||
|
|
run(["sudo", "dnf", "install", "-y", "ffmpeg"])
|
|||
|
|
|
|||
|
|
elif distro == "arch":
|
|||
|
|
info("Using pacman (Arch Linux)...")
|
|||
|
|
run(["sudo", "pacman", "-Sy", "--noconfirm", "ffmpeg"])
|
|||
|
|
|
|||
|
|
else:
|
|||
|
|
warn("Unrecognised Linux distro — cannot auto-install ffmpeg.")
|
|||
|
|
print("""
|
|||
|
|
Please install ffmpeg with your distro's package manager, e.g.:
|
|||
|
|
sudo apt-get install ffmpeg # Debian / Ubuntu
|
|||
|
|
sudo dnf install ffmpeg # Fedora
|
|||
|
|
sudo pacman -S ffmpeg # Arch
|
|||
|
|
""")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
ok("ffmpeg installed")
|
|||
|
|
|
|||
|
|
|
|||
|
|
def install_ffmpeg():
|
|||
|
|
section("Installing ffmpeg")
|
|||
|
|
|
|||
|
|
if ffmpeg_already_installed():
|
|||
|
|
ok("Skipping — ffmpeg is already installed")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
if IS_WIN:
|
|||
|
|
install_ffmpeg_windows()
|
|||
|
|
elif IS_MAC:
|
|||
|
|
install_ffmpeg_mac()
|
|||
|
|
elif IS_LINUX:
|
|||
|
|
install_ffmpeg_linux()
|
|||
|
|
else:
|
|||
|
|
warn(f"Unsupported OS: {OS}")
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ── Final verification ─────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
def verify():
|
|||
|
|
section("Verification")
|
|||
|
|
all_good = True
|
|||
|
|
|
|||
|
|
# yt-dlp
|
|||
|
|
try:
|
|||
|
|
import yt_dlp
|
|||
|
|
ok(f"yt-dlp {yt_dlp.version.__version__}")
|
|||
|
|
except ImportError:
|
|||
|
|
fail("yt-dlp import failed after installation!")
|
|||
|
|
all_good = False
|
|||
|
|
|
|||
|
|
# ffmpeg
|
|||
|
|
if shutil.which("ffmpeg"):
|
|||
|
|
result = subprocess.run(
|
|||
|
|
["ffmpeg", "-version"], capture_output=True, text=True, check=False
|
|||
|
|
)
|
|||
|
|
version_line = result.stdout.splitlines()[0] if result.stdout else "unknown version"
|
|||
|
|
ok(f"ffmpeg — {version_line}")
|
|||
|
|
else:
|
|||
|
|
warn("ffmpeg not found in PATH. High-quality merging may not work.")
|
|||
|
|
warn("You can still download videos but 1080p/4K may be unavailable.")
|
|||
|
|
all_good = False
|
|||
|
|
|
|||
|
|
return all_good
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ── Main ───────────────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
print("\n" + "=" * 50)
|
|||
|
|
print(" YouTube Downloader — Dependency Installer")
|
|||
|
|
print("=" * 50)
|
|||
|
|
print(f" OS : {OS} ({platform.machine()})")
|
|||
|
|
print(f" Python : {sys.version.split()[0]}")
|
|||
|
|
|
|||
|
|
check_python()
|
|||
|
|
ensure_pip()
|
|||
|
|
install_ytdlp()
|
|||
|
|
install_ffmpeg()
|
|||
|
|
|
|||
|
|
all_good = verify()
|
|||
|
|
|
|||
|
|
print("\n" + "=" * 50)
|
|||
|
|
if all_good:
|
|||
|
|
print(" 🎉 All dependencies installed successfully!")
|
|||
|
|
print(" Run python yt_downloader.py to start downloading.")
|
|||
|
|
else:
|
|||
|
|
print(" ⚠️ Setup finished with warnings (see above).")
|
|||
|
|
print("=" * 50 + "\n")
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|