Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
@@ -109,407 +109,449 @@
|
|
109 |
# """)
|
110 |
|
111 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
import streamlit as st
|
113 |
import yt_dlp
|
114 |
import os
|
115 |
from pathlib import Path
|
116 |
import time
|
|
|
117 |
from datetime import datetime
|
118 |
import json
|
|
|
|
|
|
|
119 |
|
120 |
# Set page config
|
121 |
st.set_page_config(page_title="YouTube Video Downloader", page_icon="📺", layout="wide")
|
122 |
-
|
123 |
-
# Set the title of the app
|
124 |
st.title("YouTube Video Downloader 📺")
|
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 |
'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',
|
157 |
-
'outtmpl': str(
|
158 |
'merge_output_format': 'mp4',
|
159 |
'quiet': True,
|
160 |
'no_warnings': True,
|
161 |
'extract_flat': False,
|
162 |
-
'
|
163 |
-
'
|
164 |
-
'
|
165 |
-
'retries': 3,
|
166 |
'file_access_retries': 3,
|
167 |
'fragment_retries': 3,
|
|
|
168 |
'skip_unavailable_fragments': True,
|
169 |
'abort_on_unavailable_fragment': False,
|
170 |
}
|
171 |
|
172 |
-
|
173 |
-
|
174 |
-
opts['cookiefile'] = AUTH_CONFIG['COOKIES_FILE']
|
175 |
|
176 |
-
|
177 |
-
|
178 |
-
if token:
|
179 |
-
opts['headers'] = {
|
180 |
-
'Authorization': f'Bearer {token}'
|
181 |
-
}
|
182 |
|
183 |
-
return
|
184 |
|
185 |
-
def
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
while retry_count < max_retries:
|
190 |
try:
|
191 |
-
|
192 |
-
ydl_opts =
|
193 |
|
194 |
-
# Add progress hooks
|
195 |
def progress_hook(d):
|
196 |
if d['status'] == 'downloading':
|
197 |
try:
|
198 |
progress = d['downloaded_bytes'] / d['total_bytes']
|
199 |
progress_callback(progress, f"Downloading: {progress:.1%}")
|
200 |
except:
|
201 |
-
progress_callback(
|
202 |
elif d['status'] == 'finished':
|
203 |
progress_callback(1.0, "Processing...")
|
204 |
-
|
205 |
ydl_opts['progress_hooks'] = [progress_hook]
|
206 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
207 |
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
208 |
info = ydl.extract_info(url, download=True)
|
209 |
filename = ydl.prepare_filename(info)
|
210 |
return filename, None
|
211 |
|
212 |
except yt_dlp.utils.ExtractorError as e:
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
return None, f"Video extraction failed: {
|
220 |
|
221 |
except Exception as e:
|
222 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
223 |
|
224 |
return None, "Maximum retries reached. Please try again later."
|
225 |
|
226 |
-
#
|
227 |
-
|
228 |
-
|
229 |
-
with col1:
|
230 |
-
video_url = st.text_input("Enter YouTube Video URL:", placeholder="https://www.youtube.com/watch?v=...")
|
231 |
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
252 |
else:
|
253 |
-
st.
|
254 |
-
|
255 |
-
|
|
|
256 |
|
257 |
-
|
258 |
with st.expander("⚙️ Settings & Information"):
|
259 |
-
st.markdown(""
|
260 |
-
**Authentication Status:**
|
261 |
-
""")
|
262 |
|
263 |
-
if os.path.exists(
|
264 |
-
st.success("✅ Cookie authentication
|
|
|
|
|
|
|
|
|
|
|
|
|
265 |
else:
|
266 |
st.warning("""
|
267 |
⚠️ No cookie authentication configured
|
268 |
|
269 |
To enable cookie authentication:
|
270 |
-
1.
|
271 |
-
2.
|
|
|
272 |
""")
|
273 |
-
|
274 |
-
st.markdown("""
|
275 |
-
**Download Options:**
|
276 |
-
- Videos are downloaded in best available quality
|
277 |
-
- Format: MP4 (when available)
|
278 |
-
- Automatic quality selection
|
279 |
-
""")
|
280 |
-
|
281 |
-
with st.expander("❓ Help & Troubleshooting"):
|
282 |
-
st.markdown("""
|
283 |
-
**Common Issues & Solutions:**
|
284 |
-
|
285 |
-
1. **Bot Detection:**
|
286 |
-
- The app implements automatic retries
|
287 |
-
- Use cookie authentication for better success rate
|
288 |
-
- Wait a few minutes between attempts
|
289 |
-
|
290 |
-
2. **Download Fails:**
|
291 |
-
- Verify the video is public
|
292 |
-
- Check your internet connection
|
293 |
-
- Try a different video
|
294 |
-
- Clear browser cache and cookies
|
295 |
-
|
296 |
-
3. **Quality Issues:**
|
297 |
-
- The app automatically selects the best available quality
|
298 |
-
- Some videos may have quality restrictions
|
299 |
-
- Premium content may require authentication
|
300 |
-
|
301 |
-
**Need More Help?**
|
302 |
-
- Check if the video is available in your region
|
303 |
-
- Verify YouTube's terms of service
|
304 |
-
- Consider using cookie authentication
|
305 |
-
""")
|
306 |
-
|
307 |
-
|
308 |
-
# import streamlit as st
|
309 |
-
# import yt_dlp
|
310 |
-
# import os
|
311 |
-
# from pathlib import Path
|
312 |
-
# from http.cookiejar import MozillaCookieJar
|
313 |
-
# import random
|
314 |
-
# import time
|
315 |
-
|
316 |
-
# # Constants
|
317 |
-
# COOKIES_FILE = 'youtube.com_cookies.txt'
|
318 |
-
# OUTPUT_DIR = Path("downloads")
|
319 |
-
|
320 |
-
# # Set page config
|
321 |
-
# st.set_page_config(page_title="YouTube Video Downloader", page_icon="📺")
|
322 |
-
# st.title("YouTube Video Downloader 📺")
|
323 |
-
|
324 |
-
# # Create output directory if it doesn't exist
|
325 |
-
# OUTPUT_DIR.mkdir(exist_ok=True)
|
326 |
-
|
327 |
-
# def get_random_user_agent():
|
328 |
-
# """랜덤 User-Agent 생성"""
|
329 |
-
# user_agents = [
|
330 |
-
# 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
|
331 |
-
# 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0',
|
332 |
-
# 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2.1 Safari/605.1.15',
|
333 |
-
# 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0'
|
334 |
-
# ]
|
335 |
-
# return random.choice(user_agents)
|
336 |
-
|
337 |
-
# def validate_cookies():
|
338 |
-
# """쿠키 파일 유효성 검사"""
|
339 |
-
# if not os.path.exists(COOKIES_FILE):
|
340 |
-
# return False, "Cookie file not found"
|
341 |
-
|
342 |
-
# try:
|
343 |
-
# cookie_jar = MozillaCookieJar(COOKIES_FILE)
|
344 |
-
# cookie_jar.load()
|
345 |
-
# return True, "Cookies loaded successfully"
|
346 |
-
# except Exception as e:
|
347 |
-
# return False, f"Cookie error: {str(e)}"
|
348 |
-
|
349 |
-
# def download_with_retry(url, ydl_opts, max_retries=3, delay=5):
|
350 |
-
# """재시도 로직이 포함된 다운로드 함수"""
|
351 |
-
# last_error = None
|
352 |
-
|
353 |
-
# for attempt in range(max_retries):
|
354 |
-
# try:
|
355 |
-
# # 매 시도마다 새로운 User-Agent 사용
|
356 |
-
# ydl_opts['headers']['User-Agent'] = get_random_user_agent()
|
357 |
-
|
358 |
-
# with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
359 |
-
# info = ydl.extract_info(url, download=True)
|
360 |
-
# filename = ydl.prepare_filename(info)
|
361 |
-
# return filename, info
|
362 |
-
|
363 |
-
# except yt_dlp.utils.DownloadError as e:
|
364 |
-
# last_error = e
|
365 |
-
# if "Sign in to confirm you're not a bot" in str(e):
|
366 |
-
# if attempt < max_retries - 1:
|
367 |
-
# st.warning(f"Bot detection encountered. Retrying in {delay} seconds... (Attempt {attempt + 1}/{max_retries})")
|
368 |
-
# time.sleep(delay)
|
369 |
-
# delay *= 2 # 지수 백오프
|
370 |
-
# continue
|
371 |
-
# elif "HTTP Error 429" in str(e):
|
372 |
-
# if attempt < max_retries - 1:
|
373 |
-
# st.warning(f"Rate limit detected. Waiting {delay} seconds... (Attempt {attempt + 1}/{max_retries})")
|
374 |
-
# time.sleep(delay * 2) # 레이트 리밋의 경우 더 긴 대기 시간
|
375 |
-
# continue
|
376 |
-
# except Exception as e:
|
377 |
-
# last_error = e
|
378 |
-
# if attempt < max_retries - 1:
|
379 |
-
# st.warning(f"Error occurred. Retrying... (Attempt {attempt + 1}/{max_retries})")
|
380 |
-
# time.sleep(delay)
|
381 |
-
# continue
|
382 |
-
|
383 |
-
# # 모든 재시도 실패 후
|
384 |
-
# raise last_error or Exception("Download failed after all retries")
|
385 |
-
|
386 |
-
# def download_video(url):
|
387 |
-
# try:
|
388 |
-
# ydl_opts = {
|
389 |
-
# 'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',
|
390 |
-
# 'outtmpl': str(OUTPUT_DIR / '%(title)s.%(ext)s'),
|
391 |
-
# 'merge_output_format': 'mp4',
|
392 |
-
|
393 |
-
# # 쿠키 설정
|
394 |
-
# 'cookiefile': COOKIES_FILE,
|
395 |
-
|
396 |
-
# # 향상된 옵션
|
397 |
-
# 'quiet': True,
|
398 |
-
# 'no_warnings': True,
|
399 |
-
# 'extract_flat': False,
|
400 |
-
# 'socket_timeout': 30,
|
401 |
-
# 'retries': 10,
|
402 |
-
# 'fragment_retries': 10,
|
403 |
-
|
404 |
-
# # 향상된 헤더
|
405 |
-
# 'headers': {
|
406 |
-
# 'User-Agent': get_random_user_agent(),
|
407 |
-
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
|
408 |
-
# 'Accept-Language': 'en-US,en;q=0.5',
|
409 |
-
# 'Accept-Encoding': 'gzip, deflate, br',
|
410 |
-
# 'Connection': 'keep-alive',
|
411 |
-
# 'Upgrade-Insecure-Requests': '1',
|
412 |
-
# 'Sec-Fetch-Dest': 'document',
|
413 |
-
# 'Sec-Fetch-Mode': 'navigate',
|
414 |
-
# 'Sec-Fetch-Site': 'none',
|
415 |
-
# 'Sec-Fetch-User': '?1',
|
416 |
-
# 'DNT': '1',
|
417 |
-
# 'Sec-GPC': '1',
|
418 |
-
# 'Pragma': 'no-cache',
|
419 |
-
# 'Cache-Control': 'no-cache',
|
420 |
-
# },
|
421 |
|
422 |
-
#
|
423 |
-
|
424 |
-
|
425 |
-
|
426 |
-
|
427 |
-
|
428 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
429 |
|
430 |
-
|
431 |
-
|
432 |
-
|
|
|
|
|
433 |
|
434 |
-
|
435 |
-
|
436 |
-
|
437 |
-
|
438 |
-
|
439 |
-
# status_text.text(f"Downloading: {progress:.1%} | Speed: {d.get('speed_str', 'N/A')} | ETA: {d.get('eta_str', 'N/A')}")
|
440 |
-
# except:
|
441 |
-
# status_text.text(f"Downloading... Speed: {d.get('speed_str', 'N/A')}")
|
442 |
-
# elif d['status'] == 'finished':
|
443 |
-
# progress_bar.progress(1.0)
|
444 |
-
# status_text.text("Processing downloaded files...")
|
445 |
-
|
446 |
-
# ydl_opts['progress_hooks'] = [progress_hook]
|
447 |
|
448 |
-
|
449 |
-
|
450 |
-
|
451 |
-
|
452 |
-
|
453 |
-
|
454 |
-
|
455 |
-
|
456 |
-
|
457 |
-
|
458 |
-
|
459 |
|
460 |
-
#
|
461 |
-
|
|
|
|
|
462 |
|
463 |
-
|
464 |
-
|
465 |
-
# if not video_url:
|
466 |
-
# st.warning("⚠️ Please enter a valid YouTube URL.")
|
467 |
-
# elif not cookie_valid:
|
468 |
-
# st.error("❌ Cannot proceed without valid cookies.")
|
469 |
-
# else:
|
470 |
-
# try:
|
471 |
-
# with st.spinner("Preparing download..."):
|
472 |
-
# downloaded_file_path, video_info = download_video(video_url)
|
473 |
-
|
474 |
-
# if downloaded_file_path and os.path.exists(downloaded_file_path):
|
475 |
-
# file_size = os.path.getsize(downloaded_file_path)
|
476 |
-
# st.info(f"""
|
477 |
-
# **Download Complete!**
|
478 |
-
# - File: {os.path.basename(downloaded_file_path)}
|
479 |
-
# - Size: {file_size / (1024*1024):.1f} MB
|
480 |
-
# """)
|
481 |
-
|
482 |
-
# with open(downloaded_file_path, 'rb') as file:
|
483 |
-
# st.download_button(
|
484 |
-
# label="💾 Download Video",
|
485 |
-
# data=file,
|
486 |
-
# file_name=os.path.basename(downloaded_file_path),
|
487 |
-
# mime="video/mp4"
|
488 |
-
# )
|
489 |
-
# else:
|
490 |
-
# st.error("❌ Download failed. Please try again.")
|
491 |
-
# except Exception as e:
|
492 |
-
# st.error(f"❌ {str(e)}")
|
493 |
-
|
494 |
-
# # Help 섹션
|
495 |
-
# with st.expander("ℹ️ Troubleshooting Guide"):
|
496 |
-
# st.markdown(f"""
|
497 |
-
# **Current Status:**
|
498 |
-
# - Cookie File: {'✅ Found' if os.path.exists(COOKIES_FILE) else '❌ Not Found'}
|
499 |
-
# - Cookie Validity: {'✅ Valid' if cookie_valid else '❌ Invalid'}
|
500 |
-
|
501 |
-
# **Common Issues:**
|
502 |
-
# 1. Bot Detection:
|
503 |
-
# - The app will automatically retry with different settings
|
504 |
-
# - Each retry uses a random User-Agent and headers
|
505 |
-
|
506 |
-
# 2. Cookie Problems:
|
507 |
-
# - Current cookie file: {COOKIES_FILE}
|
508 |
-
# - Make sure the cookie file is up to date
|
509 |
-
# - Export fresh cookies when issues persist
|
510 |
-
|
511 |
-
# 3. Download Fails:
|
512 |
-
# - Try again after a few minutes
|
513 |
-
# - Check if the video is available in your region
|
514 |
-
# - Verify that the video is public
|
515 |
-
# """)
|
|
|
109 |
# """)
|
110 |
|
111 |
|
112 |
+
# import streamlit as st
|
113 |
+
# import yt_dlp
|
114 |
+
# import os
|
115 |
+
# from pathlib import Path
|
116 |
+
# import time
|
117 |
+
# from datetime import datetime
|
118 |
+
# import json
|
119 |
+
|
120 |
+
# # Set page config
|
121 |
+
# st.set_page_config(page_title="YouTube Video Downloader", page_icon="📺", layout="wide")
|
122 |
+
|
123 |
+
# # Set the title of the app
|
124 |
+
# st.title("YouTube Video Downloader 📺")
|
125 |
+
|
126 |
+
# # Create output directory if it doesn't exist
|
127 |
+
# output_dir = Path("downloads")
|
128 |
+
# output_dir.mkdir(exist_ok=True)
|
129 |
+
|
130 |
+
# # Authentication settings
|
131 |
+
# AUTH_CONFIG = {
|
132 |
+
# 'COOKIES_FILE': 'youtube.com_cookies.txt',
|
133 |
+
# 'TOKEN_FILE': 'auth_token.json'
|
134 |
+
# }
|
135 |
+
|
136 |
+
# def load_auth_token():
|
137 |
+
# try:
|
138 |
+
# if os.path.exists(AUTH_CONFIG['TOKEN_FILE']):
|
139 |
+
# with open(AUTH_CONFIG['TOKEN_FILE'], 'r') as f:
|
140 |
+
# data = json.load(f)
|
141 |
+
# if datetime.fromisoformat(data['expires']) > datetime.now():
|
142 |
+
# return data['token']
|
143 |
+
# except Exception:
|
144 |
+
# pass
|
145 |
+
# return None
|
146 |
+
|
147 |
+
# def save_auth_token(token):
|
148 |
+
# with open(AUTH_CONFIG['TOKEN_FILE'], 'w') as f:
|
149 |
+
# json.dump({
|
150 |
+
# 'token': token,
|
151 |
+
# 'expires': (datetime.now().isoformat())
|
152 |
+
# }, f)
|
153 |
+
|
154 |
+
# def setup_ydl_options(has_cookies):
|
155 |
+
# opts = {
|
156 |
+
# 'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',
|
157 |
+
# 'outtmpl': str(output_dir / '%(title)s.%(ext)s'),
|
158 |
+
# 'merge_output_format': 'mp4',
|
159 |
+
# 'quiet': True,
|
160 |
+
# 'no_warnings': True,
|
161 |
+
# 'extract_flat': False,
|
162 |
+
# 'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
163 |
+
# 'referer': 'https://www.youtube.com/',
|
164 |
+
# 'http_chunk_size': 10485760,
|
165 |
+
# 'retries': 3,
|
166 |
+
# 'file_access_retries': 3,
|
167 |
+
# 'fragment_retries': 3,
|
168 |
+
# 'skip_unavailable_fragments': True,
|
169 |
+
# 'abort_on_unavailable_fragment': False,
|
170 |
+
# }
|
171 |
+
|
172 |
+
# # Add cookies file if available
|
173 |
+
# if has_cookies:
|
174 |
+
# opts['cookiefile'] = AUTH_CONFIG['COOKIES_FILE']
|
175 |
+
|
176 |
+
# # Add authentication token if available
|
177 |
+
# token = load_auth_token()
|
178 |
+
# if token:
|
179 |
+
# opts['headers'] = {
|
180 |
+
# 'Authorization': f'Bearer {token}'
|
181 |
+
# }
|
182 |
+
|
183 |
+
# return opts
|
184 |
+
|
185 |
+
# def download_video(url, progress_callback):
|
186 |
+
# max_retries = 3
|
187 |
+
# retry_count = 0
|
188 |
+
|
189 |
+
# while retry_count < max_retries:
|
190 |
+
# try:
|
191 |
+
# has_cookies = os.path.exists(AUTH_CONFIG['COOKIES_FILE'])
|
192 |
+
# ydl_opts = setup_ydl_options(has_cookies)
|
193 |
+
|
194 |
+
# # Add progress hooks
|
195 |
+
# def progress_hook(d):
|
196 |
+
# if d['status'] == 'downloading':
|
197 |
+
# try:
|
198 |
+
# progress = d['downloaded_bytes'] / d['total_bytes']
|
199 |
+
# progress_callback(progress, f"Downloading: {progress:.1%}")
|
200 |
+
# except:
|
201 |
+
# progress_callback(0, "Downloading... (size unknown)")
|
202 |
+
# elif d['status'] == 'finished':
|
203 |
+
# progress_callback(1.0, "Processing...")
|
204 |
+
|
205 |
+
# ydl_opts['progress_hooks'] = [progress_hook]
|
206 |
+
|
207 |
+
# with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
208 |
+
# info = ydl.extract_info(url, download=True)
|
209 |
+
# filename = ydl.prepare_filename(info)
|
210 |
+
# return filename, None
|
211 |
+
|
212 |
+
# except yt_dlp.utils.ExtractorError as e:
|
213 |
+
# if "Sign in to confirm you're not a bot" in str(e):
|
214 |
+
# retry_count += 1
|
215 |
+
# if retry_count < max_retries:
|
216 |
+
# time.sleep(5) # Wait before retry
|
217 |
+
# continue
|
218 |
+
# return None, "Bot detection triggered. Please try again later or use authentication."
|
219 |
+
# return None, f"Video extraction failed: {str(e)}"
|
220 |
+
|
221 |
+
# except Exception as e:
|
222 |
+
# return None, f"Download failed: {str(e)}"
|
223 |
+
|
224 |
+
# return None, "Maximum retries reached. Please try again later."
|
225 |
+
|
226 |
+
# # Main UI
|
227 |
+
# col1, col2 = st.columns([2, 1])
|
228 |
+
|
229 |
+
# with col1:
|
230 |
+
# video_url = st.text_input("Enter YouTube Video URL:", placeholder="https://www.youtube.com/watch?v=...")
|
231 |
+
|
232 |
+
# if st.button("Download", type="primary"):
|
233 |
+
# if video_url:
|
234 |
+
# progress_bar = st.progress(0)
|
235 |
+
# status_text = st.empty()
|
236 |
+
|
237 |
+
# def update_progress(progress, status):
|
238 |
+
# progress_bar.progress(progress)
|
239 |
+
# status_text.text(status)
|
240 |
+
|
241 |
+
# downloaded_file, error = download_video(video_url, update_progress)
|
242 |
+
|
243 |
+
# if downloaded_file and os.path.exists(downloaded_file):
|
244 |
+
# with open(downloaded_file, 'rb') as file:
|
245 |
+
# st.download_button(
|
246 |
+
# label="⬇️ Download Video",
|
247 |
+
# data=file,
|
248 |
+
# file_name=os.path.basename(downloaded_file),
|
249 |
+
# mime="video/mp4"
|
250 |
+
# )
|
251 |
+
# st.success("✅ Download ready!")
|
252 |
+
# else:
|
253 |
+
# st.error(f"❌ {error}")
|
254 |
+
# else:
|
255 |
+
# st.warning("⚠️ Please enter a valid YouTube URL.")
|
256 |
+
|
257 |
+
# with col2:
|
258 |
+
# with st.expander("⚙️ Settings & Information"):
|
259 |
+
# st.markdown("""
|
260 |
+
# **Authentication Status:**
|
261 |
+
# """)
|
262 |
+
|
263 |
+
# if os.path.exists(AUTH_CONFIG['COOKIES_FILE']):
|
264 |
+
# st.success("✅ Cookie authentication available")
|
265 |
+
# else:
|
266 |
+
# st.warning("""
|
267 |
+
# ⚠️ No cookie authentication configured
|
268 |
+
|
269 |
+
# To enable cookie authentication:
|
270 |
+
# 1. Export cookies from your browser
|
271 |
+
# 2. Save as 'youtube.com_cookies.txt' in the app directory
|
272 |
+
# """)
|
273 |
+
|
274 |
+
# st.markdown("""
|
275 |
+
# **Download Options:**
|
276 |
+
# - Videos are downloaded in best available quality
|
277 |
+
# - Format: MP4 (when available)
|
278 |
+
# - Automatic quality selection
|
279 |
+
# """)
|
280 |
+
|
281 |
+
# with st.expander("❓ Help & Troubleshooting"):
|
282 |
+
# st.markdown("""
|
283 |
+
# **Common Issues & Solutions:**
|
284 |
+
|
285 |
+
# 1. **Bot Detection:**
|
286 |
+
# - The app implements automatic retries
|
287 |
+
# - Use cookie authentication for better success rate
|
288 |
+
# - Wait a few minutes between attempts
|
289 |
+
|
290 |
+
# 2. **Download Fails:**
|
291 |
+
# - Verify the video is public
|
292 |
+
# - Check your internet connection
|
293 |
+
# - Try a different video
|
294 |
+
# - Clear browser cache and cookies
|
295 |
+
|
296 |
+
# 3. **Quality Issues:**
|
297 |
+
# - The app automatically selects the best available quality
|
298 |
+
# - Some videos may have quality restrictions
|
299 |
+
# - Premium content may require authentication
|
300 |
+
|
301 |
+
# **Need More Help?**
|
302 |
+
# - Check if the video is available in your region
|
303 |
+
# - Verify YouTube's terms of service
|
304 |
+
# - Consider using cookie authentication
|
305 |
+
# """)
|
306 |
+
|
307 |
+
|
308 |
import streamlit as st
|
309 |
import yt_dlp
|
310 |
import os
|
311 |
from pathlib import Path
|
312 |
import time
|
313 |
+
import random
|
314 |
from datetime import datetime
|
315 |
import json
|
316 |
+
import requests
|
317 |
+
from fake_useragent import UserAgent
|
318 |
+
import http.cookiejar as cookielib
|
319 |
|
320 |
# Set page config
|
321 |
st.set_page_config(page_title="YouTube Video Downloader", page_icon="📺", layout="wide")
|
|
|
|
|
322 |
st.title("YouTube Video Downloader 📺")
|
323 |
|
324 |
+
# Constants and Configurations
|
325 |
+
class Config:
|
326 |
+
OUTPUT_DIR = Path("downloads")
|
327 |
+
COOKIES_FILE = 'youtube.com_cookies.txt'
|
328 |
+
TOKEN_FILE = 'auth_token.json'
|
329 |
+
MAX_RETRIES = 5
|
330 |
+
RETRY_DELAYS = [3, 5, 10, 15, 30] # Progressive delays in seconds
|
331 |
+
|
332 |
+
HEADERS_POOL = [
|
333 |
+
{
|
334 |
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
335 |
+
'Accept-Language': 'en-US,en;q=0.5',
|
336 |
+
'DNT': '1',
|
337 |
+
'Connection': 'keep-alive',
|
338 |
+
'Upgrade-Insecure-Requests': '1',
|
339 |
+
},
|
340 |
+
{
|
341 |
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
342 |
+
'Accept-Language': 'en-US,en;q=0.9',
|
343 |
+
'DNT': '1',
|
344 |
+
'Connection': 'keep-alive',
|
345 |
+
}
|
346 |
+
]
|
347 |
+
|
348 |
+
# Create output directory
|
349 |
+
Config.OUTPUT_DIR.mkdir(exist_ok=True)
|
350 |
+
|
351 |
+
# Session management
|
352 |
+
class SessionManager:
|
353 |
+
@staticmethod
|
354 |
+
def create_cookie_jar():
|
355 |
+
return cookielib.MozillaCookieJar(Config.COOKIES_FILE)
|
356 |
+
|
357 |
+
@staticmethod
|
358 |
+
def get_random_headers():
|
359 |
+
ua = UserAgent()
|
360 |
+
headers = random.choice(Config.HEADERS_POOL).copy()
|
361 |
+
headers['User-Agent'] = ua.random
|
362 |
+
return headers
|
363 |
+
|
364 |
+
@staticmethod
|
365 |
+
def get_session():
|
366 |
+
session = requests.Session()
|
367 |
+
if os.path.exists(Config.COOKIES_FILE):
|
368 |
+
session.cookies = SessionManager.create_cookie_jar()
|
369 |
+
session.cookies.load(ignore_discard=True, ignore_expires=True)
|
370 |
+
session.headers.update(SessionManager.get_random_headers())
|
371 |
+
return session
|
372 |
+
|
373 |
+
def get_download_options(session_headers=None):
|
374 |
+
"""Enhanced download options with anti-bot measures"""
|
375 |
+
options = {
|
376 |
'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',
|
377 |
+
'outtmpl': str(Config.OUTPUT_DIR / '%(title)s.%(ext)s'),
|
378 |
'merge_output_format': 'mp4',
|
379 |
'quiet': True,
|
380 |
'no_warnings': True,
|
381 |
'extract_flat': False,
|
382 |
+
'nocheckcertificate': True,
|
383 |
+
'http_chunk_size': random.randint(10000000, 15000000), # Random chunk size
|
384 |
+
'retries': Config.MAX_RETRIES,
|
|
|
385 |
'file_access_retries': 3,
|
386 |
'fragment_retries': 3,
|
387 |
+
'retry_sleep_functions': {'http': lambda n: random.uniform(1, 5) * n},
|
388 |
'skip_unavailable_fragments': True,
|
389 |
'abort_on_unavailable_fragment': False,
|
390 |
}
|
391 |
|
392 |
+
if os.path.exists(Config.COOKIES_FILE):
|
393 |
+
options['cookiefile'] = Config.COOKIES_FILE
|
|
|
394 |
|
395 |
+
if session_headers:
|
396 |
+
options['headers'] = session_headers
|
|
|
|
|
|
|
|
|
397 |
|
398 |
+
return options
|
399 |
|
400 |
+
def download_with_retry(url, progress_callback):
|
401 |
+
"""Enhanced download function with multiple retry strategies"""
|
402 |
+
for attempt in range(Config.MAX_RETRIES):
|
|
|
|
|
403 |
try:
|
404 |
+
session = SessionManager.get_session()
|
405 |
+
ydl_opts = get_download_options(session.headers)
|
406 |
|
|
|
407 |
def progress_hook(d):
|
408 |
if d['status'] == 'downloading':
|
409 |
try:
|
410 |
progress = d['downloaded_bytes'] / d['total_bytes']
|
411 |
progress_callback(progress, f"Downloading: {progress:.1%}")
|
412 |
except:
|
413 |
+
progress_callback(-1, f"Downloading... (Attempt {attempt + 1}/{Config.MAX_RETRIES})")
|
414 |
elif d['status'] == 'finished':
|
415 |
progress_callback(1.0, "Processing...")
|
416 |
+
|
417 |
ydl_opts['progress_hooks'] = [progress_hook]
|
418 |
|
419 |
+
# Pre-check video availability
|
420 |
+
with yt_dlp.YoutubeDL({'quiet': True}) as ydl:
|
421 |
+
try:
|
422 |
+
ydl.extract_info(url, download=False)
|
423 |
+
except Exception as e:
|
424 |
+
if "This video is not available" in str(e):
|
425 |
+
return None, "Video is not available. It might be private or deleted."
|
426 |
+
|
427 |
+
# Actual download
|
428 |
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
429 |
info = ydl.extract_info(url, download=True)
|
430 |
filename = ydl.prepare_filename(info)
|
431 |
return filename, None
|
432 |
|
433 |
except yt_dlp.utils.ExtractorError as e:
|
434 |
+
error_msg = str(e)
|
435 |
+
if "Sign in to confirm you're not a bot" in error_msg:
|
436 |
+
delay = Config.RETRY_DELAYS[min(attempt, len(Config.RETRY_DELAYS)-1)]
|
437 |
+
progress_callback(-1, f"Bot detection encountered. Waiting {delay}s before retry...")
|
438 |
+
time.sleep(delay)
|
439 |
+
continue
|
440 |
+
return None, f"Video extraction failed: {error_msg}"
|
441 |
|
442 |
except Exception as e:
|
443 |
+
error_msg = str(e)
|
444 |
+
if attempt < Config.MAX_RETRIES - 1:
|
445 |
+
delay = Config.RETRY_DELAYS[attempt]
|
446 |
+
progress_callback(-1, f"Download failed. Retrying in {delay}s... ({attempt + 1}/{Config.MAX_RETRIES})")
|
447 |
+
time.sleep(delay)
|
448 |
+
continue
|
449 |
+
return None, f"Download failed after {Config.MAX_RETRIES} attempts: {error_msg}"
|
450 |
|
451 |
return None, "Maximum retries reached. Please try again later."
|
452 |
|
453 |
+
# UI Components
|
454 |
+
def render_main_ui():
|
455 |
+
col1, col2 = st.columns([2, 1])
|
|
|
|
|
456 |
|
457 |
+
with col1:
|
458 |
+
video_url = st.text_input("Enter YouTube Video URL:", placeholder="https://www.youtube.com/watch?v=...")
|
459 |
+
|
460 |
+
if st.button("Download", type="primary"):
|
461 |
+
if video_url:
|
462 |
+
progress_bar = st.progress(0)
|
463 |
+
status_text = st.empty()
|
464 |
+
|
465 |
+
def update_progress(progress, status):
|
466 |
+
if progress >= 0:
|
467 |
+
progress_bar.progress(progress)
|
468 |
+
status_text.text(status)
|
469 |
+
|
470 |
+
downloaded_file, error = download_with_retry(video_url, update_progress)
|
471 |
+
|
472 |
+
if downloaded_file and os.path.exists(downloaded_file):
|
473 |
+
with open(downloaded_file, 'rb') as file:
|
474 |
+
st.download_button(
|
475 |
+
label="⬇️ Download Video",
|
476 |
+
data=file,
|
477 |
+
file_name=os.path.basename(downloaded_file),
|
478 |
+
mime="video/mp4"
|
479 |
+
)
|
480 |
+
st.success("✅ Download completed successfully!")
|
481 |
+
else:
|
482 |
+
st.error(f"❌ {error}")
|
483 |
else:
|
484 |
+
st.warning("⚠️ Please enter a valid YouTube URL.")
|
485 |
+
|
486 |
+
with col2:
|
487 |
+
render_settings_panel()
|
488 |
|
489 |
+
def render_settings_panel():
|
490 |
with st.expander("⚙️ Settings & Information"):
|
491 |
+
st.markdown("**Authentication Status:**")
|
|
|
|
|
492 |
|
493 |
+
if os.path.exists(Config.COOKIES_FILE):
|
494 |
+
st.success("✅ Cookie authentication active")
|
495 |
+
if st.button("Remove Cookie File"):
|
496 |
+
try:
|
497 |
+
os.remove(Config.COOKIES_FILE)
|
498 |
+
st.rerun()
|
499 |
+
except Exception as e:
|
500 |
+
st.error(f"Failed to remove cookie file: {str(e)}")
|
501 |
else:
|
502 |
st.warning("""
|
503 |
⚠️ No cookie authentication configured
|
504 |
|
505 |
To enable cookie authentication:
|
506 |
+
1. Install a browser extension to export cookies
|
507 |
+
2. Export cookies from YouTube (while logged in)
|
508 |
+
3. Save as 'youtube.com_cookies.txt' in the app directory
|
509 |
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
510 |
|
511 |
+
# Cookie file uploader
|
512 |
+
uploaded_file = st.file_uploader("Upload Cookie File", type=['txt'])
|
513 |
+
if uploaded_file is not None:
|
514 |
+
try:
|
515 |
+
with open(Config.COOKIES_FILE, 'wb') as f:
|
516 |
+
f.write(uploaded_file.getvalue())
|
517 |
+
st.success("✅ Cookie file uploaded successfully!")
|
518 |
+
st.rerun()
|
519 |
+
except Exception as e:
|
520 |
+
st.error(f"Failed to save cookie file: {str(e)}")
|
521 |
+
|
522 |
+
def render_help_section():
|
523 |
+
with st.expander("❓ Help & Troubleshooting"):
|
524 |
+
st.markdown("""
|
525 |
+
**Common Issues & Solutions:**
|
526 |
|
527 |
+
1. **Bot Detection Issues:**
|
528 |
+
- The app now implements multiple retry strategies
|
529 |
+
- Uses random delays between attempts
|
530 |
+
- Rotates User-Agents automatically
|
531 |
+
- Uses cookie authentication when available
|
532 |
|
533 |
+
2. **Download Problems:**
|
534 |
+
- Check if the video is public and available
|
535 |
+
- Verify your internet connection
|
536 |
+
- Try uploading a fresh cookie file
|
537 |
+
- Clear browser cache and try again
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
538 |
|
539 |
+
3. **Quality Settings:**
|
540 |
+
- Automatically selects best available quality
|
541 |
+
- Prioritizes MP4 format when available
|
542 |
+
- Handles premium content with proper authentication
|
543 |
+
|
544 |
+
**Need More Help?**
|
545 |
+
- Make sure you're using an up-to-date browser
|
546 |
+
- Check if the video is available in your region
|
547 |
+
- Consider using a VPN if region-blocked
|
548 |
+
- Ensure your cookie file is recent and valid
|
549 |
+
""")
|
550 |
|
551 |
+
# Main App
|
552 |
+
def main():
|
553 |
+
render_main_ui()
|
554 |
+
render_help_section()
|
555 |
|
556 |
+
if __name__ == "__main__":
|
557 |
+
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|