lordofc commited on
Commit
2bc540a
·
verified ·
1 Parent(s): 6f25647

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +277 -0
app.py ADDED
@@ -0,0 +1,277 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Request, Query, BackgroundTasks
2
+ from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ import yt_dlp
5
+ import os
6
+ import asyncio
7
+ from datetime import datetime, timedelta
8
+ from urllib.parse import quote
9
+ import io
10
+ import logging
11
+ import json
12
+ import time
13
+ from collections import defaultdict
14
+ import uvicorn
15
+ import gradio as gr
16
+
17
+ OUTPUT_DIR = "output"
18
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
19
+ PROXY_URL = os.environ.get("YTDLP_PROXY")
20
+ BANNED_IP_FILE = "bannedip.json"
21
+ MAX_REQUESTS_PER_MINUTE = 35
22
+
23
+ logging.basicConfig(
24
+ level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
25
+ )
26
+ logger = logging.getLogger(__name__)
27
+
28
+ server_start_time = time.time()
29
+ total_request_count = 0
30
+ request_counter = defaultdict(list)
31
+
32
+
33
+ def load_banned_ips():
34
+ try:
35
+ with open(BANNED_IP_FILE, "r") as f:
36
+ return set(json.load(f))
37
+ except (FileNotFoundError, json.JSONDecodeError):
38
+ return set()
39
+
40
+
41
+ def save_banned_ips(banned_ips):
42
+ with open(BANNED_IP_FILE, "w") as f:
43
+ json.dump(list(banned_ips), f)
44
+
45
+
46
+ async def delete_file_after_delay(file_path: str, delay: int = 600):
47
+ await asyncio.sleep(delay)
48
+ try:
49
+ os.remove(file_path)
50
+ logger.info(f"Deleted file {file_path} after {delay} seconds.")
51
+ except FileNotFoundError:
52
+ logger.warning(f"File {file_path} not found during deletion.")
53
+ except Exception as e:
54
+ logger.error(f"Error deleting file {file_path}: {e}", exc_info=True)
55
+
56
+
57
+ app = FastAPI(
58
+ title="YouTube Downloader API",
59
+ description="API to download video/audio from YouTube.",
60
+ version="1.0.0",
61
+ )
62
+
63
+ app.add_middleware(
64
+ CORSMiddleware,
65
+ allow_origins=["*"],
66
+ allow_credentials=True,
67
+ allow_methods=["*"],
68
+ allow_headers=["*"],
69
+ )
70
+
71
+ @app.middleware("http")
72
+ async def log_requests(request: Request, call_next):
73
+ global total_request_count
74
+ total_request_count += 1
75
+
76
+ client_ip = (
77
+ request.headers.get("X-Forwarded-For", request.client.host)
78
+ .split(",")[0]
79
+ .strip()
80
+ )
81
+ banned_ips = load_banned_ips()
82
+
83
+ if client_ip in banned_ips:
84
+ return JSONResponse(
85
+ status_code=403, content={"message": "Blocked due to excessive requests."}
86
+ )
87
+
88
+ now = datetime.now()
89
+ request_counter[client_ip].append(now)
90
+ request_counter[client_ip] = [
91
+ t for t in request_counter[client_ip] if t > now - timedelta(minutes=1)
92
+ ]
93
+
94
+ if len(request_counter[client_ip]) > MAX_REQUESTS_PER_MINUTE:
95
+ logger.warning(f"IP {client_ip} is blocked due to rate limiting.")
96
+ banned_ips.add(client_ip)
97
+ save_banned_ips(banned_ips)
98
+ request_counter[client_ip] = []
99
+
100
+ logger.info(f"{client_ip} requested {request.method} {request.url}")
101
+ response = await call_next(request)
102
+ return response
103
+
104
+
105
+ @app.get("/", summary="Root Endpoint")
106
+ async def root():
107
+ return JSONResponse(status_code=200, content={"status": "Server Running"})
108
+
109
+
110
+ @app.get("/ytv/", summary="Download YouTube Video")
111
+ async def download_video(
112
+ background_tasks: BackgroundTasks,
113
+ url: str = Query(...),
114
+ quality: int = Query(720),
115
+ mode: str = Query("url"),
116
+ ):
117
+ if mode not in ["url", "buffer"]:
118
+ return JSONResponse(
119
+ status_code=400, content={"error": "Invalid mode. Use 'url' or 'buffer'."}
120
+ )
121
+
122
+ try:
123
+ resx = f"_{quality}p"
124
+ ydl_opts = {
125
+ "format": f"bestvideo[height<={quality}]+bestaudio/best",
126
+ "outtmpl": os.path.join(OUTPUT_DIR, "%(title)s" + resx + ".%(ext)s"),
127
+ "merge_output_format": "mp4",
128
+ "cookiefile": "yt.txt",
129
+ "postprocessors": [
130
+ {"key": "FFmpegVideoConvertor", "preferedformat": "mp4"}
131
+ ],
132
+ }
133
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
134
+ info = ydl.extract_info(url, download=True)
135
+ file_path = ydl.prepare_filename(info)
136
+ file_ext = os.path.splitext(file_path)[-1].lstrip(".")
137
+ if not os.path.exists(file_path):
138
+ raise FileNotFoundError(f"File not found after download: {file_path}")
139
+ background_tasks.add_task(delete_file_after_delay, file_path)
140
+ if mode == "url":
141
+ return {
142
+ "status": "success",
143
+ "title": info["title"],
144
+ "thumb": info.get("thumbnail"),
145
+ "url": f"https://lordxdd-ytdlp.hf.space/cdn/video/{quote(os.path.basename(file_path))}",
146
+ }
147
+ media_type = f"video/{file_ext}" if file_ext else "video/mp4"
148
+ with open(file_path, "rb") as f:
149
+ return StreamingResponse(
150
+ io.BytesIO(f.read()),
151
+ media_type=media_type,
152
+ headers={
153
+ "Content-Disposition": f"attachment; filename={os.path.basename(file_path)}"
154
+ },
155
+ )
156
+ except Exception as e:
157
+ logger.error(f"Download error: {e}", exc_info=True)
158
+ return JSONResponse(status_code=500, content={"error": str(e)})
159
+
160
+
161
+ @app.get("/cdn/video/{filename}", summary="Serve Downloaded File")
162
+ async def download_file(filename: str):
163
+ file_path = os.path.join(OUTPUT_DIR, filename)
164
+ if os.path.exists(file_path):
165
+ file_ext = os.path.splitext(filename)[-1].lstrip(".")
166
+ media_type = f"video/{file_ext}" if file_ext else "video/mp4"
167
+ return FileResponse(file_path, filename=filename, media_type=media_type)
168
+ return JSONResponse(status_code=404, content={"error": "File not found"})
169
+
170
+
171
+ @app.get("/yta/", summary="Download YouTube Audio (.mp3)")
172
+ async def download_audio(
173
+ background_tasks: BackgroundTasks,
174
+ url: str = Query(..., description="YouTube video URL"),
175
+ mode: str = Query("url", description="Response mode: 'url' or 'buffer'"),
176
+ ):
177
+ if mode not in {"url", "buffer"}:
178
+ return JSONResponse(
179
+ status_code=400, content={"error": "Invalid mode. Use 'url' or 'buffer'."}
180
+ )
181
+ ydl_opts = {
182
+ "format": "bestaudio/best",
183
+ "outtmpl": os.path.join(OUTPUT_DIR, "%(title)s.%(ext)s"),
184
+ "cookiefile": "yt.txt",
185
+ "noplaylist": True,
186
+ "no_warnings": True,
187
+ "proxy": PROXY_URL,
188
+ "postprocessors": [
189
+ {
190
+ "key": "FFmpegExtractAudio",
191
+ "preferredcodec": "mp3",
192
+ "preferredquality": "128",
193
+ }
194
+ ],
195
+ "postprocessor_args": ["-vn", "-preset", "ultrafast", "-threads", "4"],
196
+ "prefer_ffmpeg": True,
197
+ "noplaylist": True,
198
+ "no_warnings": True,
199
+ }
200
+ try:
201
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
202
+ info = ydl.extract_info(url, download=True)
203
+ if info.get("requested_downloads"):
204
+ file_path = info["requested_downloads"][0]["filepath"]
205
+ else:
206
+ base, _ = os.path.splitext(ydl.prepare_filename(info))
207
+ file_path = base + ".m4a"
208
+ if not os.path.exists(file_path):
209
+ raise FileNotFoundError(f"File not found: {file_path}")
210
+ background_tasks.add_task(delete_file_after_delay, file_path)
211
+ if mode == "url":
212
+ return {
213
+ "status": "success",
214
+ "title": info.get("title"),
215
+ "thumb": info.get("thumbnail"),
216
+ "url": f"https://lordofc-ytdlp.hf.space/cdn/audio/{quote(os.path.basename(file_path))}",
217
+ }
218
+
219
+ with open(file_path, "rb") as f:
220
+ data = f.read()
221
+ headers = {
222
+ "Content-Disposition": f'attachment; filename="{os.path.basename(file_path)}"'
223
+ }
224
+ return StreamingResponse(
225
+ io.BytesIO(data), media_type="audio/m4a", headers=headers
226
+ )
227
+
228
+ except Exception as e:
229
+ logger.error(f"Download error: {e}", exc_info=True)
230
+ return JSONResponse(status_code=500, content={"error": str(e)})
231
+
232
+
233
+ @app.get("/cdn/audio/{filename}", summary="Serve Downloaded Audio File")
234
+ async def serve_audio_file(filename: str):
235
+ file_path = os.path.join(OUTPUT_DIR, filename)
236
+ if os.path.exists(file_path):
237
+ ext = os.path.splitext(filename)[-1].lstrip(".")
238
+ return FileResponse(file_path, filename=filename, media_type=f"audio/{ext}")
239
+ return JSONResponse(status_code=404, content={"error": "File not found"})
240
+
241
+
242
+ def run_gradio_ui():
243
+ def download_gradio(url, resolution):
244
+ try:
245
+ quality = int(resolution)
246
+ ydl_opts = {
247
+ "format": f"bestvideo[height<={quality}]+bestaudio/best",
248
+ "outtmpl": os.path.join(
249
+ OUTPUT_DIR, "%(title)s" + f"_{quality}p" + ".%(ext)s"
250
+ ),
251
+ "merge_output_format": "mp4",
252
+ "postprocessors": [
253
+ {"key": "FFmpegVideoConvertor", "preferedformat": "mp4"}
254
+ ],
255
+ }
256
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
257
+ info = ydl.extract_info(url, download=True)
258
+ file_path = ydl.prepare_filename(info)
259
+ return {
260
+ "status": "success",
261
+ "title": info["title"],
262
+ "url": f"/cdn/video/{quote(os.path.basename(file_path))}",
263
+ }
264
+ except Exception as e:
265
+ return {"error": str(e)}
266
+
267
+ interface = gr.Interface(
268
+ fn=download_gradio,
269
+ inputs=["text", gr.Slider(240, 1080)],
270
+ outputs="json",
271
+ title="YouTube Video Downloader",
272
+ )
273
+ interface.launch()
274
+
275
+
276
+ if __name__ == "__main__":
277
+ uvicorn.run("app:app", host="0.0.0.0", port=7860)