Spaces:
Bagda
/
Build error

Bagda commited on
Commit
2ac06dd
·
verified ·
1 Parent(s): 0ab62f5

Create app.py

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