Files
YT_Download_Script/yt_downloader.py
T

172 lines
5.6 KiB
Python
Raw Normal View History

2026-09-13 19:56:37 +01:00
#!/usr/bin/env python3
"""
YouTube Video Downloader
Requires: yt-dlp → pip install yt-dlp
"""
import subprocess
import sys
import os
import shutil
def check_ytdlp():
try:
import yt_dlp # noqa: F401
except ImportError:
print("Installing yt-dlp...")
subprocess.check_call([sys.executable, "-m", "pip", "install", "yt-dlp"])
def has_ffmpeg() -> bool:
"""Return True if ffmpeg is available on PATH."""
return shutil.which("ffmpeg") is not None
def list_formats(url: str):
"""Print available formats for a video."""
import yt_dlp
with yt_dlp.YoutubeDL({"quiet": True}) as ydl:
info = ydl.extract_info(url, download=False)
print(f"\nTitle: {info.get('title')}")
print(f"Duration: {info.get('duration_string', 'N/A')}\n")
print(f"{'ID':<12} {'EXT':<6} {'RESOLUTION':<12} {'FPS':<6} NOTE")
print("-" * 60)
for f in info.get("formats", []):
fid = f.get("format_id", "")
ext = f.get("ext", "")
res = f.get("resolution") or f.get("format_note", "")
fps = str(f.get("fps") or "")
note = f.get("format_note", "")
print(f"{fid:<12} {ext:<6} {res:<12} {fps:<6} {note}")
def build_format_string(quality: str, ffmpeg: bool) -> str:
"""
Return a yt-dlp format string appropriate for whether ffmpeg is available.
With ffmpeg → prefer separate video+audio streams (best quality, then merge).
Without ffmpeg → request a single pre-muxed file so no merging is needed.
"""
if ffmpeg:
return {
"best": "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best",
"1080p": "bestvideo[height<=1080][ext=mp4]+bestaudio[ext=m4a]/best[height<=1080][ext=mp4]/best[height<=1080]",
"720p": "bestvideo[height<=720][ext=mp4]+bestaudio[ext=m4a]/best[height<=720][ext=mp4]/best[height<=720]",
"480p": "bestvideo[height<=480][ext=mp4]+bestaudio[ext=m4a]/best[height<=480][ext=mp4]/best[height<=480]",
"360p": "bestvideo[height<=360][ext=mp4]+bestaudio[ext=m4a]/best[height<=360][ext=mp4]/best[height<=360]",
}.get(quality, "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best")
else:
# Single-file (pre-muxed) formats — no merging required
return {
"best": "best[ext=mp4]/best",
"1080p": "best[height<=1080][ext=mp4]/best[height<=1080]",
"720p": "best[height<=720][ext=mp4]/best[height<=720]",
"480p": "best[height<=480][ext=mp4]/best[height<=480]",
"360p": "best[height<=360][ext=mp4]/best[height<=360]",
}.get(quality, "best[ext=mp4]/best")
def download(url: str, quality: str, output_dir: str, audio_only: bool):
"""Download a video or audio from YouTube."""
import yt_dlp
ffmpeg = has_ffmpeg()
os.makedirs(output_dir, exist_ok=True)
outtmpl = os.path.join(output_dir, "%(title)s.%(ext)s")
if audio_only:
if ffmpeg:
ydl_opts = {
"format": "bestaudio/best",
"outtmpl": outtmpl,
"postprocessors": [{
"key": "FFmpegExtractAudio",
"preferredcodec": "mp3",
"preferredquality": "192",
}],
}
else:
# Without ffmpeg, download best audio as-is (usually .m4a)
print(" ⚠️ ffmpeg not found — audio will be saved as .m4a instead of .mp3")
ydl_opts = {
"format": "bestaudio[ext=m4a]/bestaudio",
"outtmpl": outtmpl,
}
else:
fmt = build_format_string(quality, ffmpeg)
ydl_opts = {
"format": fmt,
"outtmpl": outtmpl,
}
if ffmpeg:
ydl_opts["merge_output_format"] = "mp4"
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
ydl.download([url])
def prompt_quality() -> str:
options = ["best", "1080p", "720p", "480p", "360p"]
print("\nSelect quality:")
for i, opt in enumerate(options, 1):
print(f" {i}. {opt}")
while True:
choice = input("Enter number (default 1 = best): ").strip() or "1"
if choice.isdigit() and 1 <= int(choice) <= len(options):
return options[int(choice) - 1]
print("Invalid choice, try again.")
def main():
check_ytdlp()
print("=" * 50)
print(" YouTube Downloader (powered by yt-dlp)")
print("=" * 50)
# Show ffmpeg status upfront
if has_ffmpeg():
print(" ✅ ffmpeg detected — full quality available")
else:
print(" ⚠️ ffmpeg NOT found — quality may be limited (max ~720p)")
print(" Run install_dependencies.py to fix this\n")
url = input("\nEnter YouTube URL: ").strip()
if not url:
print("No URL provided. Exiting.")
sys.exit(1)
print("\nWhat would you like to do?")
print(" 1. Download video")
print(" 2. Download audio only (MP3)")
print(" 3. List available formats")
mode = input("Enter choice (1/2/3): ").strip()
if mode == "3":
list_formats(url)
sys.exit(0)
audio_only = mode == "2"
quality = "best"
if not audio_only:
quality = prompt_quality()
output_dir = input("\nSave to folder (default: ./downloads): ").strip() or "./downloads"
print(f"\nDownloading {'audio' if audio_only else f'{quality} video'}...")
print(f"Saving to: {os.path.abspath(output_dir)}\n")
try:
download(url, quality, output_dir, audio_only)
print("\n✅ Download complete!")
except Exception as e:
print(f"\n❌ Error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()