"""yt-dlp wrapper service for video downloading.""" import os import uuid import asyncio import logging from pathlib import Path from typing import Optional import yt_dlp logger = logging.getLogger(__name__) VIDEO_BASE_PATH = os.getenv("VIDEO_BASE_PATH", "/home/xdl/xdl_videos") X_VIDEOS_PATH = os.path.join(VIDEO_BASE_PATH, "x_videos") # Ensure directories exist os.makedirs(X_VIDEOS_PATH, exist_ok=True) def get_video_path(filename: str) -> str: return os.path.join(X_VIDEOS_PATH, filename) def parse_video_url(url: str) -> dict: """Extract video info without downloading.""" ydl_opts = { "quiet": True, "no_warnings": True, "extract_flat": False, "skip_download": True, } with yt_dlp.YoutubeDL(ydl_opts) as ydl: info = ydl.extract_info(url, download=False) formats = [] seen = set() for f in info.get("formats", []): # Only video formats with both video and audio, or video-only if f.get("vcodec", "none") == "none": continue height = f.get("height", 0) ext = f.get("ext", "mp4") fmt_id = f.get("format_id", "") quality = f"{height}p" if height else f.get("format_note", "unknown") key = f"{quality}-{ext}" if key in seen: continue seen.add(key) formats.append({ "format_id": fmt_id, "quality": quality, "ext": ext, "filesize": f.get("filesize") or f.get("filesize_approx") or 0, "note": f.get("format_note", ""), }) # Sort by resolution descending formats.sort(key=lambda x: int(x["quality"].replace("p", "")) if x["quality"].endswith("p") else 0, reverse=True) # Add a "best" option formats.insert(0, { "format_id": "best", "quality": "best", "ext": "mp4", "filesize": 0, "note": "Best available quality", }) return { "title": info.get("title", "Untitled"), "thumbnail": info.get("thumbnail", ""), "duration": info.get("duration", 0) or 0, "formats": formats, "url": url, } def download_video(url: str, format_id: str = "best", progress_callback=None) -> dict: """Download video and return file info.""" task_id = str(uuid.uuid4())[:8] output_template = os.path.join(X_VIDEOS_PATH, f"%(id)s_{task_id}.%(ext)s") format_spec = "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best" if format_id == "best" else f"{format_id}+bestaudio/best" def hook(d): if d["status"] == "downloading" and progress_callback: total = d.get("total_bytes") or d.get("total_bytes_estimate") or 0 downloaded = d.get("downloaded_bytes", 0) pct = int(downloaded * 100 / total) if total > 0 else 0 progress_callback(pct) elif d["status"] == "finished" and progress_callback: progress_callback(100) ydl_opts = { "format": format_spec, "outtmpl": output_template, "merge_output_format": "mp4", "quiet": True, "no_warnings": True, "progress_hooks": [hook], } with yt_dlp.YoutubeDL(ydl_opts) as ydl: info = ydl.extract_info(url, download=True) filename = ydl.prepare_filename(info) # yt-dlp may change extension after merge if not os.path.exists(filename): base = os.path.splitext(filename)[0] filename = base + ".mp4" file_size = os.path.getsize(filename) if os.path.exists(filename) else 0 return { "title": info.get("title", "Untitled"), "thumbnail": info.get("thumbnail", ""), "duration": info.get("duration", 0) or 0, "filename": os.path.basename(filename), "file_path": filename, "file_size": file_size, "platform": "twitter", }