ytdllpapi / app.py
mojaalagevai's picture
Update app.py
84a08ce verified
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)