from fastapi import FastAPI, HTTPException, Security, Depends from fastapi.security import APIKeyHeader from pydantic import HttpUrl import yt_dlp import uvicorn from typing import Dict, Optional from datetime import timedelta import os import logging # Set up logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) app = FastAPI( title="Video Downloader API", description="API to extract direct download links (or streaming links as fallback), thumbnail, and video details for all yt-dlp supported sites", docs_url="/docs", redoc_url="/redoc" ) # API Key Authentication (only X-API-Key header) API_KEYS = ["123456", "marufking"] api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False) async def get_api_key(api_key_header: Optional[str] = Security(api_key_header)): if api_key_header in API_KEYS: return api_key_header raise HTTPException(status_code=401, detail="Invalid or missing X-API-Key") # Path to cookies file (optional) COOKIES_FILE = "cookies.txt" @app.get("/video-info", response_model=Dict) async def get_video_info(video_url: HttpUrl, api_key: str = Depends(get_api_key)): """ Extract direct download links for video-only MP4 (720p and higher) or streaming links if no direct links are available, best thumbnail, and video details (title, size, duration) for any yt-dlp supported site. Works with or without cookies. """ # Convert HttpUrl to string to avoid type issues video_url_str = str(video_url) logger.info(f"Processing URL: {video_url_str}") ydl_opts = { 'quiet': True, 'no_warnings': True, 'format_sort': ['ext:mp4', 'vcodec', '+res:720'], # Prefer MP4, 720p or higher 'noplaylist': True, 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'force_generic_extractor': False, } # Add cookies file only if it exists and is non-empty if os.path.exists(COOKIES_FILE) and os.path.getsize(COOKIES_FILE) > 0: ydl_opts['cookiefile'] = COOKIES_FILE logger.info(f"Using cookies file: {COOKIES_FILE}") else: logger.info("No cookies file provided or file is empty; proceeding without cookies") try: with yt_dlp.YoutubeDL(ydl_opts) as ydl: # Extract info with retry for robustness try: info = ydl.extract_info(video_url_str, download=False) except yt_dlp.utils.DownloadError as de: logger.error(f"DownloadError for URL {video_url_str}: {str(de)}") raise HTTPException(status_code=500, detail=f"Failed to extract video info: {str(de)}. Try providing valid cookies if the video is private.") except Exception as e: logger.error(f"Unexpected error for URL {video_url_str}: {str(e)}") raise HTTPException(status_code=500, detail=f"Error processing URL: {str(e)}. Ensure the URL is valid and accessible.") # Get video details title = info.get('title', 'Unknown Title') duration = info.get('duration', None) duration_str = str(timedelta(seconds=int(duration))) if duration else "Unknown" # Estimate file size file_size = None for fmt in info.get('formats', []): if fmt.get('filesize'): file_size = fmt['filesize'] break file_size_str = f"{file_size / (1024 * 1024):.2f} MB" if file_size else "Unknown" # Filter direct video-only MP4 formats (720p or higher, no .m3u8) direct_formats = [ f for f in info.get('formats', []) if f.get('vcodec') != 'none' and f.get('acodec') == 'none' and f.get('ext') == 'mp4' and not f.get('url', '').endswith('.m3u8') and f.get('height', 0) >= 720 ] # If no direct formats, fall back to streaming formats (720p or higher) if not direct_formats: logger.warning(f"No direct MP4 video-only formats (720p+) found for {video_url_str}, falling back to streaming formats") streaming_formats = [ f for f in info.get('formats', []) if f.get('vcodec') != 'none' and f.get('acodec') == 'none' and f.get('height', 0) >= 720 ] if not streaming_formats: logger.warning(f"No streaming formats (720p+) found for {video_url_str}") raise HTTPException( status_code=404, detail="No video-only formats (720p or higher) found, neither direct nor streaming." ) formats = streaming_formats link_type = "streaming" else: formats = direct_formats link_type = "direct" # Sort by resolution formats.sort(key=lambda x: x.get('height', 0), reverse=True) # Get download links download_links = [ { "quality": f"{f.get('height')}p", "url": f.get('url'), "format_id": f.get('format_id'), "type": link_type } for f in formats ] # Get best thumbnail thumbnails = info.get('thumbnails', []) best_thumbnail = max( thumbnails, key=lambda x: x.get('width', 0) * x.get('height', 0), default={} ).get('url', 'No thumbnail available') logger.info(f"Successfully extracted info for {video_url_str}: {len(download_links)} {link_type} formats found") return { "title": title, "duration": duration_str, "file_size": file_size_str, "download_links": download_links, "thumbnail": best_thumbnail, "link_type": link_type } except HTTPException as he: raise he except Exception as e: logger.error(f"Unexpected error for URL {video_url_str}: {str(e)}") raise HTTPException(status_code=500, detail=f"Error: {str(e)}") if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=7860)