Chrunos commited on
Commit
05fb587
·
verified ·
1 Parent(s): 7c1dce6

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +278 -0
  2. requirements.txt +26 -0
app.py ADDED
@@ -0,0 +1,278 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import time
3
+ from fastapi import FastAPI, BackgroundTasks, Request, HTTPException
4
+ from fastapi.responses import FileResponse
5
+ from fastapi.concurrency import run_in_threadpool
6
+ import yt_dlp
7
+ import ffmpeg
8
+ import urllib.parse
9
+ import os
10
+ from datetime import datetime
11
+ import schedule
12
+ import requests
13
+ import uvicorn
14
+ import subprocess
15
+ import json
16
+ from dotenv import load_dotenv
17
+ import mimetypes
18
+ import tempfile
19
+ from mutagen.mp4 import MP4, MP4Cover, MP4FreeForm
20
+ from mutagen.mp3 import MP3
21
+ from mutagen.id3 import ID3, USLT, SYLT, Encoding, APIC, TIT2, TPE1, TALB, TPE2, TDRC, TCON, TRCK, COMM
22
+ from mutagen.oggvorbis import OggVorbis
23
+ from mutagen.oggopus import OggOpus
24
+ from mutagen.flac import FLAC, Picture
25
+ from PIL import Image
26
+ from io import BytesIO
27
+ from pathlib import Path
28
+ from fastapi.staticfiles import StaticFiles
29
+
30
+ tmp_dir = tempfile.gettempdir()
31
+ BASE_URL = "https://chrunos-yt-dlp.hf.space"
32
+
33
+ MP4_TAGS_MAP = {
34
+ "album": "\xa9alb",
35
+ "album_artist": "aART",
36
+ "artist": "\xa9ART",
37
+ "composer": "\xa9wrt",
38
+ "copyright": "cprt",
39
+ "lyrics": "\xa9lyr",
40
+ "comment": "desc",
41
+ "media_type": "stik",
42
+ "producer": "\xa9prd",
43
+ "rating": "rtng",
44
+ "release_date": "\xa9day",
45
+ "title": "\xa9nam",
46
+ "url": "\xa9url",
47
+ }
48
+ def env_to_cookies(env_content: str, output_file: str) -> None:
49
+ """Convert environment variable content back to cookie file"""
50
+ try:
51
+ # Extract content from env format
52
+ if '="' not in env_content:
53
+ raise ValueError("Invalid env content format")
54
+
55
+ content = env_content.split('="', 1)[1].strip('"')
56
+
57
+ # Replace escaped newlines with actual newlines
58
+ cookie_content = content.replace('\\n', '\n')
59
+
60
+ # Write to cookie file
61
+ with open(output_file, 'w') as f:
62
+ f.write(cookie_content)
63
+
64
+ except Exception as e:
65
+ raise ValueError(f"Error converting to cookie file: {str(e)}")
66
+
67
+ def save_to_env_file(env_content: str, env_file: str = '.env') -> None:
68
+ """Save environment variable content to .env file"""
69
+ try:
70
+ with open(env_file, 'w') as f:
71
+ f.write(env_content)
72
+ #print(f"Successfully saved to {env_file}")
73
+ except Exception as e:
74
+ raise ValueError(f"Error saving to env file: {str(e)}")
75
+
76
+ def env_to_cookies_from_env(output_file: str) -> None:
77
+ """Convert environment variable from .env file to cookie file"""
78
+ try:
79
+ load_dotenv() # Load from .env file
80
+ env_content = os.getenv('FIREFOX_COOKIES')
81
+ #print(f"Printing env content: \n{env_content}")
82
+ if not env_content:
83
+ raise ValueError("FIREFOX_COOKIES not found in .env file")
84
+
85
+ env_to_cookies(f'FIREFOX_COOKIES="{env_content}"', output_file)
86
+ except Exception as e:
87
+ raise ValueError(f"Error converting to cookie file: {str(e)}")
88
+
89
+ def get_cookies():
90
+ """Get cookies from environment variable"""
91
+ load_dotenv()
92
+ cookie_content = os.getenv('FIREFOX_COOKIES')
93
+ #print(cookie_content)
94
+ if not cookie_content:
95
+ raise ValueError("FIREFOX_COOKIES environment variable not set")
96
+ return cookie_content
97
+
98
+ def create_temp_cookie_file():
99
+ """Create temporary cookie file from environment variable"""
100
+ temp_cookie = tempfile.NamedTemporaryFile(mode='w+', delete=False, suffix='.txt')
101
+ try:
102
+ cookie_content = get_cookies()
103
+ # Replace escaped newlines with actual newlines
104
+ cookie_content = cookie_content.replace('\\n', '\n')
105
+ temp_cookie.write()
106
+ temp_cookie.flush()
107
+ return Path(temp_cookie.name)
108
+ finally:
109
+ temp_cookie.close()
110
+
111
+ load_dotenv()
112
+ app = FastAPI()
113
+
114
+ ydl_opts = {
115
+ 'format': 'best',
116
+ 'quiet': True,
117
+ #'outtmpl': f'{VIDEO_DIR}/%(id)s.%(ext)s',
118
+ 'max_filesize': 50 * 1024 * 1024
119
+ }
120
+
121
+
122
+ @app.get('/')
123
+ def main():
124
+ return "Chrunos Downloader API Is Running."
125
+
126
+ @app.get("/get_video_url")
127
+ async def get_video_url(youtube_url: str):
128
+ try:
129
+ cookiefile = "firefox-cookies.txt"
130
+ env_to_cookies_from_env("firefox-cookies.txt")
131
+ # Add cookies
132
+ ydl_opts["cookiefile"] = "firefox-cookies.txt" #create_temp_cookie_file()
133
+
134
+
135
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
136
+ info = ydl.extract_info(youtube_url, download=False)
137
+ return info
138
+ except Exception as e:
139
+ raise HTTPException(status_code=500, detail=str(e))
140
+
141
+
142
+
143
+ # Define a global temporary download directory
144
+ global_download_dir = tempfile.mkdtemp()
145
+
146
+ @app.post("/high")
147
+ async def download_high_quality_video(request: Request):
148
+ data = await request.json()
149
+ video_url = data.get('url')
150
+ cookiefile = "firefox-cookies.txt"
151
+ env_to_cookies_from_env("firefox-cookies.txt")
152
+ # Generate a unique output filename using timestamp
153
+ timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
154
+ output_template = str(Path(global_download_dir) / f'%(title)s_{timestamp}.%(ext)s')
155
+
156
+
157
+ ydl_opts = {
158
+ 'format': 'bestvideo[height<=720]+bestaudio/best',
159
+ 'outtmpl': output_template,
160
+ 'quiet': True,
161
+ 'no_warnings': True,
162
+ 'noprogress': True,
163
+ 'merge_output_format': 'mp4'
164
+ }
165
+ ydl_opts["cookiefile"] = "firefox-cookies.txt" #create_temp_cookie_file()
166
+
167
+ await run_in_threadpool(lambda: yt_dlp.YoutubeDL(ydl_opts).download([video_url]))
168
+
169
+ # Find the downloaded file (with the title as filename)
170
+ downloaded_files = list(Path(global_download_dir).glob(f"*_{timestamp}.mp4"))
171
+
172
+ if not downloaded_files:
173
+ return {"error": "Download failed"}
174
+
175
+ # Assume there is only one matching file
176
+ downloaded_file = downloaded_files[0]
177
+ encoded_filename = urllib.parse.quote(downloaded_file.name)
178
+ download_url = f"{BASE_URL}/file/{encoded_filename}"
179
+
180
+
181
+ return {"url": download_url}
182
+
183
+
184
+
185
+
186
+ @app.post("/max")
187
+ async def download_high_quality_video(request: Request):
188
+ data = await request.json()
189
+ video_url = data.get('url')
190
+ # Get quality from request, default to 1080p if not specified
191
+ quality = data.get('quality', '1080') # Can be '480', '720', '1080', '1440', '2160' (4K)
192
+ cookiefile = "firefox-cookies.txt"
193
+ env_to_cookies_from_env("firefox-cookies.txt")
194
+
195
+ timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
196
+ output_template = str(Path(global_download_dir) / f'%(title)s_{timestamp}.%(ext)s')
197
+
198
+ # Convert quality string to height
199
+ height_map = {
200
+ '480': 480,
201
+ '720': 720,
202
+ '1080': 1080,
203
+ '1440': 1440,
204
+ '2160': 2160
205
+ }
206
+ max_height = height_map.get(quality, 1080)
207
+ if max_height > 1080:
208
+ format_str = f'bestvideo[height<={max_height}]+bestaudio/best'
209
+ else:
210
+ format_str = f'bestvideo[height<={max_height}][vcodec^=avc]+bestaudio/best'
211
+
212
+ ydl_opts = {
213
+ 'format': format_str,
214
+ 'outtmpl': output_template,
215
+ 'quiet': True,
216
+ 'no_warnings': True,
217
+ 'noprogress': True,
218
+ 'merge_output_format': 'mp4',
219
+ 'cookiefile': cookiefile
220
+ }
221
+
222
+ await run_in_threadpool(lambda: yt_dlp.YoutubeDL(ydl_opts).download([video_url]))
223
+
224
+ downloaded_files = list(Path(global_download_dir).glob(f"*_{timestamp}.mp4"))
225
+ if not downloaded_files:
226
+ return {"error": "Download failed"}
227
+
228
+ downloaded_file = downloaded_files[0]
229
+ encoded_filename = urllib.parse.quote(downloaded_file.name)
230
+ download_url = f"{BASE_URL}/file/{encoded_filename}"
231
+ return {"url": download_url}
232
+
233
+ @app.post("/audio")
234
+ async def download_audio(request: Request):
235
+ data = await request.json()
236
+ video_url = data.get('url')
237
+ cookiefile = "firefox-cookies.txt"
238
+ env_to_cookies_from_env("firefox-cookies.txt")
239
+
240
+ timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
241
+ output_template = str(Path(global_download_dir) / f'%(title)s_{timestamp}.%(ext)s')
242
+
243
+ ydl_opts = {
244
+ 'format': 'bestaudio/best',
245
+ 'outtmpl': output_template,
246
+ 'quiet': True,
247
+ 'no_warnings': True,
248
+ 'noprogress': True,
249
+ 'cookiefile': cookiefile
250
+
251
+ }
252
+
253
+ await run_in_threadpool(lambda: yt_dlp.YoutubeDL(ydl_opts).download([video_url]))
254
+
255
+ downloaded_files = list(Path(global_download_dir).glob(f"*_{timestamp}.*"))
256
+ if not downloaded_files:
257
+ return {"error": "Download failed"}
258
+
259
+ downloaded_file = downloaded_files[0]
260
+ encoded_filename = urllib.parse.quote(downloaded_file.name)
261
+ download_url = f"{BASE_URL}/file/{encoded_filename}"
262
+ return {"url": download_url}
263
+ # Mount the static files directory
264
+ app.mount("/file", StaticFiles(directory=global_download_dir), name="downloads")
265
+
266
+
267
+
268
+
269
+
270
+
271
+
272
+ @app.middleware("http")
273
+ async def set_mime_type_middleware(request: Request, call_next):
274
+ response = await call_next(request)
275
+ if request.url.path.endswith(".mp4"):
276
+ response.headers["Content-Type"] = "video/mp4"
277
+ return response
278
+
requirements.txt ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ annotated-types==0.5.0
2
+ anyio==3.7.1
3
+ Brotli==1.1.0
4
+ certifi==2023.7.22
5
+ click==8.1.7
6
+ colorama==0.4.6
7
+ fastapi==0.103.1
8
+ future==0.18.3
9
+ h11==0.14.0
10
+ idna==3.4
11
+ mutagen
12
+ pycryptodomex==3.18.0
13
+ pydantic==2.3.0
14
+ pydantic_core==2.6.3
15
+ python-dotenv==1.0.0
16
+ pytz==2024.1
17
+ schedule==1.2.1
18
+ sniffio==1.3.0
19
+ starlette==0.27.0
20
+ typing_extensions==4.7.1
21
+ uvicorn==0.23.2
22
+ websockets==11.0.3
23
+ yt-dlp
24
+ requests
25
+ pillow
26
+ ffmpeg-python