170 lines
5.5 KiB
Python
170 lines
5.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
yt_download.py — Download YouTube video v 1080p (16:9) za reels pipeline.
|
|
|
|
Primer:
|
|
python3 yt_download.py "https://youtu.be/dQw4w9WgXcQ" /data/uploads/video.mp4
|
|
"""
|
|
import argparse
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
import json
|
|
|
|
|
|
def download(url, output, max_height=1080, format_str=None, cookies_file=None):
|
|
"""
|
|
Download YT video. Privzeto: best mp4 ≤1080p z audiotrackom.
|
|
"""
|
|
if format_str is None:
|
|
format_str = (
|
|
f"bestvideo[height<={max_height}][ext=mp4]+bestaudio[ext=m4a]/"
|
|
f"best[height<={max_height}][ext=mp4]/best"
|
|
)
|
|
|
|
cmd = [
|
|
"yt-dlp",
|
|
"-f", format_str,
|
|
"--merge-output-format", "mp4",
|
|
"--no-playlist",
|
|
"--write-info-json",
|
|
"--restrict-filenames",
|
|
"-o", str(output),
|
|
]
|
|
|
|
# Auto-detect cookies file če ni eksplicitno podan
|
|
if cookies_file is None:
|
|
for candidate in [
|
|
"/data/cookies/youtube.txt",
|
|
os.environ.get("YT_COOKIES_FILE", ""),
|
|
]:
|
|
if candidate and Path(candidate).exists():
|
|
cookies_file = candidate
|
|
break
|
|
|
|
if cookies_file and Path(cookies_file).exists():
|
|
cmd += ["--cookies", str(cookies_file)]
|
|
print(f"🍪 Using cookies: {cookies_file}", file=sys.stderr)
|
|
|
|
cmd.append(url)
|
|
print(f"⬇ Downloading {url}...", file=sys.stderr)
|
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
if result.returncode != 0:
|
|
print(f"❌ yt-dlp napaka:\n{result.stderr[-1500:]}", file=sys.stderr)
|
|
sys.exit(1)
|
|
print(f"✅ {output}", file=sys.stderr)
|
|
return output
|
|
|
|
|
|
def get_info(url, cookies_file=None):
|
|
"""Vrni metadata brez prenosa."""
|
|
cmd = ["yt-dlp", "--dump-json", "--no-playlist"]
|
|
if cookies_file is None:
|
|
for candidate in ["/data/cookies/youtube.txt", os.environ.get("YT_COOKIES_FILE", "")]:
|
|
if candidate and Path(candidate).exists():
|
|
cookies_file = candidate
|
|
break
|
|
if cookies_file and Path(cookies_file).exists():
|
|
cmd += ["--cookies", str(cookies_file)]
|
|
cmd.append(url)
|
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
if result.returncode != 0:
|
|
return None
|
|
return json.loads(result.stdout.strip().split("\n")[0])
|
|
|
|
|
|
def get_playlist(url, cookies_file=None):
|
|
"""Vrni listo videov v playlistu (samo metadata, ne pobere).
|
|
|
|
Returns:
|
|
{
|
|
"is_playlist": bool,
|
|
"playlist_title": str,
|
|
"items": [
|
|
{"id": "VIDEO_ID", "title": "...", "url": "https://...", "duration": 234},
|
|
...
|
|
]
|
|
}
|
|
|
|
Če URL ni playlist, vrne is_playlist=False in samo en item.
|
|
"""
|
|
cmd = ["yt-dlp", "--flat-playlist", "--dump-json"]
|
|
if cookies_file is None:
|
|
for candidate in ["/data/cookies/youtube.txt", os.environ.get("YT_COOKIES_FILE", "")]:
|
|
if candidate and Path(candidate).exists():
|
|
cookies_file = candidate
|
|
break
|
|
if cookies_file and Path(cookies_file).exists():
|
|
cmd += ["--cookies", str(cookies_file)]
|
|
cmd.append(url)
|
|
print(f"📋 Resolving playlist: {url}", file=sys.stderr)
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
|
if result.returncode != 0:
|
|
print(f"❌ yt-dlp playlist napaka:\n{result.stderr[-1500:]}", file=sys.stderr)
|
|
return {"is_playlist": False, "playlist_title": "", "items": [], "error": result.stderr[-500:]}
|
|
|
|
items = []
|
|
playlist_title = ""
|
|
for line in result.stdout.strip().split("\n"):
|
|
if not line.strip():
|
|
continue
|
|
try:
|
|
entry = json.loads(line)
|
|
except json.JSONDecodeError:
|
|
continue
|
|
# Playlist header (typ=playlist)
|
|
if entry.get("_type") == "playlist":
|
|
playlist_title = entry.get("title", "")
|
|
continue
|
|
# Video entry
|
|
vid = entry.get("id")
|
|
if not vid:
|
|
continue
|
|
items.append({
|
|
"id": vid,
|
|
"title": entry.get("title", "") or "",
|
|
"url": entry.get("url") or f"https://www.youtube.com/watch?v={vid}",
|
|
"duration": entry.get("duration"),
|
|
"uploader": entry.get("uploader") or entry.get("channel") or "",
|
|
})
|
|
|
|
is_playlist = len(items) > 1 or ("list=" in url)
|
|
return {
|
|
"is_playlist": is_playlist,
|
|
"playlist_title": playlist_title or (items[0]["title"] if items else ""),
|
|
"items": items,
|
|
}
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("url")
|
|
ap.add_argument("output")
|
|
ap.add_argument("--max-height", type=int, default=1080)
|
|
ap.add_argument("--cookies", default=None,
|
|
help="Pot do cookies.txt (Netscape format)")
|
|
ap.add_argument("--info-only", action="store_true",
|
|
help="Samo metadata, brez prenosa")
|
|
args = ap.parse_args()
|
|
|
|
if args.info_only:
|
|
info = get_info(args.url, cookies_file=args.cookies)
|
|
if info:
|
|
print(json.dumps({
|
|
"title": info.get("title"),
|
|
"duration": info.get("duration"),
|
|
"uploader": info.get("uploader"),
|
|
"thumbnail": info.get("thumbnail"),
|
|
}, indent=2))
|
|
else:
|
|
print("❌ Ne morem dobiti info", file=sys.stderr)
|
|
sys.exit(1)
|
|
return
|
|
|
|
download(args.url, args.output, max_height=args.max_height, cookies_file=args.cookies)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|