Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
@@ -216,9 +216,9 @@ import streamlit as st
|
|
216 |
import yt_dlp
|
217 |
import os
|
218 |
from pathlib import Path
|
219 |
-
import json
|
220 |
from http.cookiejar import MozillaCookieJar
|
221 |
-
|
|
|
222 |
|
223 |
# Constants
|
224 |
COOKIES_FILE = 'youtube.com_cookies.txt'
|
@@ -231,79 +231,99 @@ st.title("YouTube Video Downloader πΊ")
|
|
231 |
# Create output directory if it doesn't exist
|
232 |
OUTPUT_DIR.mkdir(exist_ok=True)
|
233 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
234 |
def validate_cookies():
|
235 |
-
"""μΏ ν€ νμΌ μ ν¨μ± κ²μ¬"""
|
236 |
if not os.path.exists(COOKIES_FILE):
|
237 |
return False, "Cookie file not found"
|
238 |
|
239 |
try:
|
240 |
cookie_jar = MozillaCookieJar(COOKIES_FILE)
|
241 |
cookie_jar.load()
|
242 |
-
|
243 |
-
# Check for essential YouTube cookies
|
244 |
-
essential_cookies = ['CONSENT', 'LOGIN_INFO', 'PREF']
|
245 |
-
found_cookies = [cookie.name for cookie in cookie_jar]
|
246 |
-
|
247 |
-
missing_cookies = [cookie for cookie in essential_cookies if cookie not in found_cookies]
|
248 |
-
if missing_cookies:
|
249 |
-
return False, f"Missing essential cookies: {', '.join(missing_cookies)}"
|
250 |
-
|
251 |
-
return True, "Cookies valid"
|
252 |
-
except Exception as e:
|
253 |
-
return False, f"Cookie validation error: {str(e)}"
|
254 |
-
|
255 |
-
def get_video_info(url):
|
256 |
-
"""λμμ μ 보 미리보기 μΆμΆ"""
|
257 |
-
ydl_opts = {
|
258 |
-
'quiet': True,
|
259 |
-
'no_warnings': True,
|
260 |
-
'extract_flat': True,
|
261 |
-
'cookiefile': COOKIES_FILE,
|
262 |
-
}
|
263 |
-
|
264 |
-
try:
|
265 |
-
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
266 |
-
return ydl.extract_info(url, download=False)
|
267 |
except Exception as e:
|
268 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
269 |
|
270 |
def download_video(url):
|
271 |
try:
|
272 |
ydl_opts = {
|
273 |
-
'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',
|
274 |
'outtmpl': str(OUTPUT_DIR / '%(title)s.%(ext)s'),
|
275 |
'merge_output_format': 'mp4',
|
276 |
|
277 |
-
#
|
278 |
'cookiefile': COOKIES_FILE,
|
279 |
-
'cookiesfrombrowser': ('chrome',),
|
280 |
|
281 |
-
#
|
282 |
'quiet': True,
|
283 |
'no_warnings': True,
|
284 |
'extract_flat': False,
|
285 |
'socket_timeout': 30,
|
286 |
'retries': 10,
|
287 |
'fragment_retries': 10,
|
288 |
-
'file_access_retries': 10,
|
289 |
|
290 |
-
#
|
291 |
'headers': {
|
292 |
-
'User-Agent':
|
293 |
-
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
294 |
-
'Accept-Language': 'en-
|
295 |
-
'Accept-Encoding': 'gzip,deflate',
|
296 |
-
'
|
297 |
'Upgrade-Insecure-Requests': '1',
|
|
|
|
|
|
|
|
|
|
|
298 |
},
|
299 |
|
300 |
-
#
|
301 |
-
'age_limit': None,
|
302 |
'geo_bypass': True,
|
303 |
'geo_bypass_country': 'US',
|
|
|
|
|
304 |
}
|
305 |
|
306 |
-
#
|
307 |
progress_bar = st.progress(0)
|
308 |
status_text = st.empty()
|
309 |
|
@@ -314,69 +334,45 @@ def download_video(url):
|
|
314 |
progress_bar.progress(progress)
|
315 |
status_text.text(f"Downloading: {progress:.1%} | Speed: {d.get('speed_str', 'N/A')} | ETA: {d.get('eta_str', 'N/A')}")
|
316 |
except:
|
317 |
-
status_text.text(f"Downloading...
|
318 |
elif d['status'] == 'finished':
|
319 |
progress_bar.progress(1.0)
|
320 |
-
status_text.text("Processing
|
321 |
|
322 |
ydl_opts['progress_hooks'] = [progress_hook]
|
323 |
|
324 |
-
|
325 |
-
info = ydl.extract_info(url, download=True)
|
326 |
-
filename = ydl.prepare_filename(info)
|
327 |
-
return filename, info
|
328 |
|
329 |
-
except yt_dlp.utils.DownloadError as e:
|
330 |
-
if "Sign in to confirm your age" in str(e):
|
331 |
-
raise Exception("This video requires age verification. Please ensure you're logged in through the cookies file.")
|
332 |
-
elif "The uploader has not made this video available in your country" in str(e):
|
333 |
-
raise Exception("This video is not available in your country. Try using a VPN.")
|
334 |
-
else:
|
335 |
-
raise Exception(f"Download error: {str(e)}")
|
336 |
except Exception as e:
|
337 |
-
raise Exception(f"
|
338 |
|
339 |
-
#
|
340 |
cookie_valid, cookie_message = validate_cookies()
|
341 |
if not cookie_valid:
|
342 |
st.warning(f"β οΈ Cookie Issue: {cookie_message}")
|
343 |
else:
|
344 |
st.success("β
Cookies loaded successfully")
|
345 |
|
346 |
-
#
|
347 |
video_url = st.text_input("Enter YouTube Video URL:", placeholder="https://www.youtube.com/watch?v=...")
|
348 |
|
349 |
-
#
|
350 |
-
if video_url and st.button("Preview Video Info"):
|
351 |
-
with st.spinner("Fetching video information..."):
|
352 |
-
info = get_video_info(video_url)
|
353 |
-
if info:
|
354 |
-
st.write("**Video Information:**")
|
355 |
-
st.write(f"Title: {info.get('title', 'N/A')}")
|
356 |
-
st.write(f"Duration: {info.get('duration_string', 'N/A')}")
|
357 |
-
st.write(f"View Count: {info.get('view_count', 'N/A'):,}")
|
358 |
-
st.write(f"Upload Date: {info.get('upload_date', 'N/A')}")
|
359 |
-
|
360 |
-
# Download button
|
361 |
if st.button("Download Video"):
|
362 |
if not video_url:
|
363 |
st.warning("β οΈ Please enter a valid YouTube URL.")
|
364 |
elif not cookie_valid:
|
365 |
-
st.error("β Cannot proceed without valid cookies.
|
366 |
else:
|
367 |
try:
|
368 |
with st.spinner("Preparing download..."):
|
369 |
downloaded_file_path, video_info = download_video(video_url)
|
370 |
|
371 |
if downloaded_file_path and os.path.exists(downloaded_file_path):
|
372 |
-
# Add file info
|
373 |
file_size = os.path.getsize(downloaded_file_path)
|
374 |
st.info(f"""
|
375 |
**Download Complete!**
|
376 |
- File: {os.path.basename(downloaded_file_path)}
|
377 |
- Size: {file_size / (1024*1024):.1f} MB
|
378 |
-
- Format: {video_info.get('format', 'N/A')}
|
379 |
-
- Resolution: {video_info.get('resolution', 'N/A')}
|
380 |
""")
|
381 |
|
382 |
with open(downloaded_file_path, 'rb') as file:
|
@@ -391,25 +387,23 @@ if st.button("Download Video"):
|
|
391 |
except Exception as e:
|
392 |
st.error(f"β {str(e)}")
|
393 |
|
394 |
-
#
|
395 |
-
with st.expander("βΉοΈ
|
396 |
st.markdown("""
|
397 |
-
**
|
398 |
-
- Location: `youtube.com_cookies.txt`
|
399 |
-
- Valid: {'β
' if cookie_valid else 'β'}
|
400 |
-
- Message: {cookie_message}
|
401 |
-
|
402 |
-
**Common Issues:**
|
403 |
-
1. Age-Restricted Videos:
|
404 |
-
- Ensure you're logged into YouTube when exporting cookies
|
405 |
-
- The cookie file must contain valid authentication tokens
|
406 |
-
|
407 |
-
2. Region-Restricted Content:
|
408 |
-
- The app attempts to bypass region restrictions automatically
|
409 |
-
- Consider using a VPN if download fails
|
410 |
|
411 |
-
|
412 |
-
-
|
413 |
-
-
|
414 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
415 |
""")
|
|
|
216 |
import yt_dlp
|
217 |
import os
|
218 |
from pathlib import Path
|
|
|
219 |
from http.cookiejar import MozillaCookieJar
|
220 |
+
import random
|
221 |
+
import time
|
222 |
|
223 |
# Constants
|
224 |
COOKIES_FILE = 'youtube.com_cookies.txt'
|
|
|
231 |
# Create output directory if it doesn't exist
|
232 |
OUTPUT_DIR.mkdir(exist_ok=True)
|
233 |
|
234 |
+
def get_random_user_agent():
|
235 |
+
"""λλ€ User-Agent μμ±"""
|
236 |
+
user_agents = [
|
237 |
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
|
238 |
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
239 |
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
|
240 |
+
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
|
241 |
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0'
|
242 |
+
]
|
243 |
+
return random.choice(user_agents)
|
244 |
+
|
245 |
def validate_cookies():
|
246 |
+
"""μΏ ν€ νμΌ μ ν¨μ± κ²μ¬ λ° κ°±μ """
|
247 |
if not os.path.exists(COOKIES_FILE):
|
248 |
return False, "Cookie file not found"
|
249 |
|
250 |
try:
|
251 |
cookie_jar = MozillaCookieJar(COOKIES_FILE)
|
252 |
cookie_jar.load()
|
253 |
+
return True, "Cookies loaded successfully"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
254 |
except Exception as e:
|
255 |
+
return False, f"Cookie error: {str(e)}"
|
256 |
+
|
257 |
+
def download_with_retry(url, ydl_opts, max_retries=3, delay=5):
|
258 |
+
"""μ¬μλ λ‘μ§μ΄ ν¬ν¨λ λ€μ΄λ‘λ ν¨μ"""
|
259 |
+
for attempt in range(max_retries):
|
260 |
+
try:
|
261 |
+
# 맀 μλλ§λ€ μλ‘μ΄ User-Agent μ¬μ©
|
262 |
+
ydl_opts['headers']['User-Agent'] = get_random_user_agent()
|
263 |
+
|
264 |
+
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
265 |
+
info = ydl.extract_info(url, download=True)
|
266 |
+
filename = ydl.prepare_filename(info)
|
267 |
+
return filename, info
|
268 |
+
|
269 |
+
except yt_dlp.utils.DownloadError as e:
|
270 |
+
if "Sign in to confirm you're not a bot" in str(e):
|
271 |
+
if attempt < max_retries - 1:
|
272 |
+
st.warning(f"Bot detection encountered. Retrying in {delay} seconds... (Attempt {attempt + 1}/{max_retries})")
|
273 |
+
time.sleep(delay)
|
274 |
+
delay *= 2 # μ§μ λ°±μ€ν
|
275 |
+
continue
|
276 |
+
raise e
|
277 |
+
except Exception as e:
|
278 |
+
if attempt < max_retries - 1:
|
279 |
+
st.warning(f"Error occurred. Retrying... (Attempt {attempt + 1}/{max_retries})")
|
280 |
+
time.sleep(delay)
|
281 |
+
continue
|
282 |
+
raise e
|
283 |
|
284 |
def download_video(url):
|
285 |
try:
|
286 |
ydl_opts = {
|
287 |
+
'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',
|
288 |
'outtmpl': str(OUTPUT_DIR / '%(title)s.%(ext)s'),
|
289 |
'merge_output_format': 'mp4',
|
290 |
|
291 |
+
# μΏ ν€ λ° μΈμ¦ μ€μ
|
292 |
'cookiefile': COOKIES_FILE,
|
293 |
+
'cookiesfrombrowser': ('chrome',),
|
294 |
|
295 |
+
# ν₯μλ μ΅μ
|
296 |
'quiet': True,
|
297 |
'no_warnings': True,
|
298 |
'extract_flat': False,
|
299 |
'socket_timeout': 30,
|
300 |
'retries': 10,
|
301 |
'fragment_retries': 10,
|
|
|
302 |
|
303 |
+
# ν₯μλ ν€λ
|
304 |
'headers': {
|
305 |
+
'User-Agent': get_random_user_agent(),
|
306 |
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
307 |
+
'Accept-Language': 'en-US,en;q=0.5',
|
308 |
+
'Accept-Encoding': 'gzip, deflate, br',
|
309 |
+
'Connection': 'keep-alive',
|
310 |
'Upgrade-Insecure-Requests': '1',
|
311 |
+
'Sec-Fetch-Dest': 'document',
|
312 |
+
'Sec-Fetch-Mode': 'navigate',
|
313 |
+
'Sec-Fetch-Site': 'none',
|
314 |
+
'Sec-Fetch-User': '?1',
|
315 |
+
'Cache-Control': 'max-age=0',
|
316 |
},
|
317 |
|
318 |
+
# μΆκ° μ΅μ
|
319 |
+
'age_limit': None,
|
320 |
'geo_bypass': True,
|
321 |
'geo_bypass_country': 'US',
|
322 |
+
'sleep_interval': 2, # μμ² μ¬μ΄ λκΈ° μκ°
|
323 |
+
'max_sleep_interval': 5,
|
324 |
}
|
325 |
|
326 |
+
# μ§ν μν© νμ
|
327 |
progress_bar = st.progress(0)
|
328 |
status_text = st.empty()
|
329 |
|
|
|
334 |
progress_bar.progress(progress)
|
335 |
status_text.text(f"Downloading: {progress:.1%} | Speed: {d.get('speed_str', 'N/A')} | ETA: {d.get('eta_str', 'N/A')}")
|
336 |
except:
|
337 |
+
status_text.text(f"Downloading... Speed: {d.get('speed_str', 'N/A')}")
|
338 |
elif d['status'] == 'finished':
|
339 |
progress_bar.progress(1.0)
|
340 |
+
status_text.text("Processing...")
|
341 |
|
342 |
ydl_opts['progress_hooks'] = [progress_hook]
|
343 |
|
344 |
+
return download_with_retry(url, ydl_opts)
|
|
|
|
|
|
|
345 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
346 |
except Exception as e:
|
347 |
+
raise Exception(f"Download error: {str(e)}")
|
348 |
|
349 |
+
# μΏ ν€ μν νμΈ
|
350 |
cookie_valid, cookie_message = validate_cookies()
|
351 |
if not cookie_valid:
|
352 |
st.warning(f"β οΈ Cookie Issue: {cookie_message}")
|
353 |
else:
|
354 |
st.success("β
Cookies loaded successfully")
|
355 |
|
356 |
+
# λΉλμ€ URL μ
λ ₯
|
357 |
video_url = st.text_input("Enter YouTube Video URL:", placeholder="https://www.youtube.com/watch?v=...")
|
358 |
|
359 |
+
# λ€μ΄λ‘λ λ²νΌ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
360 |
if st.button("Download Video"):
|
361 |
if not video_url:
|
362 |
st.warning("β οΈ Please enter a valid YouTube URL.")
|
363 |
elif not cookie_valid:
|
364 |
+
st.error("β Cannot proceed without valid cookies.")
|
365 |
else:
|
366 |
try:
|
367 |
with st.spinner("Preparing download..."):
|
368 |
downloaded_file_path, video_info = download_video(video_url)
|
369 |
|
370 |
if downloaded_file_path and os.path.exists(downloaded_file_path):
|
|
|
371 |
file_size = os.path.getsize(downloaded_file_path)
|
372 |
st.info(f"""
|
373 |
**Download Complete!**
|
374 |
- File: {os.path.basename(downloaded_file_path)}
|
375 |
- Size: {file_size / (1024*1024):.1f} MB
|
|
|
|
|
376 |
""")
|
377 |
|
378 |
with open(downloaded_file_path, 'rb') as file:
|
|
|
387 |
except Exception as e:
|
388 |
st.error(f"β {str(e)}")
|
389 |
|
390 |
+
# Help μΉμ
|
391 |
+
with st.expander("βΉοΈ Troubleshooting Guide"):
|
392 |
st.markdown("""
|
393 |
+
**If you encounter 'Sign in to confirm you're not a bot' error:**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
394 |
|
395 |
+
1. Cookie Solution:
|
396 |
+
- Make sure you're logged into YouTube in your browser
|
397 |
+
- Install 'Get cookies.txt' or similar extension
|
398 |
+
- Export fresh cookies and replace the existing cookie file
|
399 |
+
|
400 |
+
2. General Tips:
|
401 |
+
- Wait a few minutes between download attempts
|
402 |
+
- Try using a different video URL
|
403 |
+
- Clear your browser cache before exporting cookies
|
404 |
+
|
405 |
+
3. Advanced Tips:
|
406 |
+
- The app automatically retries downloads with different settings
|
407 |
+
- Each retry uses a different User-Agent to avoid detection
|
408 |
+
- Built-in delays help avoid triggering YouTube's protection
|
409 |
""")
|