Spaces:
Paused
Paused
File size: 6,495 Bytes
1533e4d 84a08ce 1533e4d 84a08ce 1533e4d 84a08ce 1533e4d 122b88f 1533e4d 122b88f 1533e4d 84a08ce 1533e4d 84a08ce 1533e4d 84a08ce 1533e4d 122b88f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | 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) |