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)