Spaces:
Running
Running
Commit ·
075f520
1
Parent(s): 5de1239
Persist error alerts until close button is clicked and disable Tor download method
Browse files- app.py +1 -39
- static/css/style.css +31 -3
- static/js/app.js +24 -3
app.py
CHANGED
|
@@ -666,48 +666,10 @@ def clip_youtube_video():
|
|
| 666 |
stderr_text = str(e)
|
| 667 |
continue
|
| 668 |
|
| 669 |
-
# ── Method E: yt-dlp via Tor SOCKS5 proxy (exits from non-datacenter IPs) ──
|
| 670 |
-
if not dl_success:
|
| 671 |
-
print('[download] All direct methods failed, trying yt-dlp via Tor ...')
|
| 672 |
-
ytdlp_bin_tor = get_pip_binary('yt-dlp')
|
| 673 |
-
has_cookies_tor = os.path.exists(COOKIES_FILE_PATH) and os.path.getsize(COOKIES_FILE_PATH) > 0
|
| 674 |
-
for path in [raw_download_path, raw_download_path + '.part']:
|
| 675 |
-
if os.path.exists(path):
|
| 676 |
-
try: os.remove(path)
|
| 677 |
-
except Exception: pass
|
| 678 |
-
tor_cmd = [
|
| 679 |
-
ytdlp_bin_tor,
|
| 680 |
-
'-f', 'bestvideo[height<=480][ext=mp4]+bestaudio/best[height<=480]/best',
|
| 681 |
-
'--merge-output-format', 'mp4',
|
| 682 |
-
'--no-check-certificates',
|
| 683 |
-
'--proxy', 'socks5h://127.0.0.1:9050', # Tor SOCKS5 (with DNS resolved on proxy)
|
| 684 |
-
'--socket-timeout', '20',
|
| 685 |
-
'--retries', '1',
|
| 686 |
-
'-o', raw_download_path
|
| 687 |
-
]
|
| 688 |
-
if has_cookies_tor:
|
| 689 |
-
tor_cmd.extend(['--cookies', COOKIES_FILE_PATH])
|
| 690 |
-
tor_cmd.append(url)
|
| 691 |
-
try:
|
| 692 |
-
tor_res = subprocess.run(tor_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=120)
|
| 693 |
-
tor_stderr = tor_res.stderr.decode('utf-8', errors='ignore')
|
| 694 |
-
if tor_res.returncode == 0 and os.path.exists(raw_download_path) and os.path.getsize(raw_download_path) > 0:
|
| 695 |
-
dl_success = True
|
| 696 |
-
print('[yt-dlp/tor] Succeeded via Tor proxy!')
|
| 697 |
-
else:
|
| 698 |
-
stderr_text = tor_stderr
|
| 699 |
-
print(f'[yt-dlp/tor] Failed: {tor_stderr[-200:]}')
|
| 700 |
-
except subprocess.TimeoutExpired:
|
| 701 |
-
stderr_text = 'Tor download timed out.'
|
| 702 |
-
print('[yt-dlp/tor] Timed out.')
|
| 703 |
-
except Exception as e:
|
| 704 |
-
stderr_text = str(e)
|
| 705 |
-
print(f'[yt-dlp/tor] Exception: {e}')
|
| 706 |
-
|
| 707 |
if not dl_success:
|
| 708 |
raw_err = stderr_text[-600:].strip() if stderr_text else 'All download methods failed'
|
| 709 |
return jsonify({'success': False,
|
| 710 |
-
'error': f'Download failed — tried cobalt, pytubefix, invidious, yt-dlp+cookies
|
| 711 |
|
| 712 |
|
| 713 |
|
|
|
|
| 666 |
stderr_text = str(e)
|
| 667 |
continue
|
| 668 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 669 |
if not dl_success:
|
| 670 |
raw_err = stderr_text[-600:].strip() if stderr_text else 'All download methods failed'
|
| 671 |
return jsonify({'success': False,
|
| 672 |
+
'error': f'Download failed — tried cobalt, pytubefix, invidious, yt-dlp+cookies. Detail: {raw_err}'}), 500
|
| 673 |
|
| 674 |
|
| 675 |
|
static/css/style.css
CHANGED
|
@@ -1336,15 +1336,20 @@ body.light-theme .close-btn {
|
|
| 1336 |
border: 1px solid var(--border-color);
|
| 1337 |
box-shadow: var(--shadow-xl);
|
| 1338 |
color: var(--text-primary);
|
| 1339 |
-
padding: 12px
|
| 1340 |
-
border-radius: var(--r-
|
| 1341 |
z-index: 2000;
|
| 1342 |
font-size: 0.85rem;
|
| 1343 |
font-weight: 600;
|
| 1344 |
opacity: 0;
|
| 1345 |
pointer-events: none;
|
| 1346 |
transition: all var(--t-normal) cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
| 1347 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1348 |
}
|
| 1349 |
body.light-theme .toast {
|
| 1350 |
background: #ffffff;
|
|
@@ -1353,6 +1358,29 @@ body.light-theme .toast {
|
|
| 1353 |
.toast.show {
|
| 1354 |
transform: translateX(-50%) translateY(0);
|
| 1355 |
opacity: 1;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1356 |
}
|
| 1357 |
|
| 1358 |
.toast.success { border-left: 4px solid var(--success); }
|
|
|
|
| 1336 |
border: 1px solid var(--border-color);
|
| 1337 |
box-shadow: var(--shadow-xl);
|
| 1338 |
color: var(--text-primary);
|
| 1339 |
+
padding: 12px 20px;
|
| 1340 |
+
border-radius: var(--r-md);
|
| 1341 |
z-index: 2000;
|
| 1342 |
font-size: 0.85rem;
|
| 1343 |
font-weight: 600;
|
| 1344 |
opacity: 0;
|
| 1345 |
pointer-events: none;
|
| 1346 |
transition: all var(--t-normal) cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
| 1347 |
+
max-width: 90%;
|
| 1348 |
+
width: 480px;
|
| 1349 |
+
display: flex;
|
| 1350 |
+
align-items: center;
|
| 1351 |
+
justify-content: space-between;
|
| 1352 |
+
gap: 16px;
|
| 1353 |
}
|
| 1354 |
body.light-theme .toast {
|
| 1355 |
background: #ffffff;
|
|
|
|
| 1358 |
.toast.show {
|
| 1359 |
transform: translateX(-50%) translateY(0);
|
| 1360 |
opacity: 1;
|
| 1361 |
+
pointer-events: auto;
|
| 1362 |
+
}
|
| 1363 |
+
|
| 1364 |
+
.toast-content {
|
| 1365 |
+
flex: 1;
|
| 1366 |
+
white-space: normal;
|
| 1367 |
+
word-break: break-word;
|
| 1368 |
+
line-height: 1.4;
|
| 1369 |
+
}
|
| 1370 |
+
|
| 1371 |
+
.toast-close {
|
| 1372 |
+
background: transparent;
|
| 1373 |
+
border: none;
|
| 1374 |
+
color: var(--text-muted);
|
| 1375 |
+
cursor: pointer;
|
| 1376 |
+
font-size: 1.25rem;
|
| 1377 |
+
font-weight: 700;
|
| 1378 |
+
line-height: 1;
|
| 1379 |
+
padding: 0 4px;
|
| 1380 |
+
transition: color var(--t-fast);
|
| 1381 |
+
}
|
| 1382 |
+
.toast-close:hover {
|
| 1383 |
+
color: var(--text-primary);
|
| 1384 |
}
|
| 1385 |
|
| 1386 |
.toast.success { border-left: 4px solid var(--success); }
|
static/js/app.js
CHANGED
|
@@ -11,12 +11,33 @@ const state = {
|
|
| 11 |
function $(id) { return document.getElementById(id); }
|
| 12 |
function $q(sel) { return document.querySelector(sel); }
|
| 13 |
|
| 14 |
-
|
|
|
|
|
|
|
| 15 |
const t = $('toast');
|
| 16 |
if (!t) return;
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
t.className = `toast ${type} show`;
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
}
|
| 21 |
|
| 22 |
function animateCount(el, target) {
|
|
|
|
| 11 |
function $(id) { return document.getElementById(id); }
|
| 12 |
function $q(sel) { return document.querySelector(sel); }
|
| 13 |
|
| 14 |
+
let toastTimeout = null;
|
| 15 |
+
|
| 16 |
+
function showToast(msg, type = 'success', duration = 4000) {
|
| 17 |
const t = $('toast');
|
| 18 |
if (!t) return;
|
| 19 |
+
|
| 20 |
+
if (toastTimeout) {
|
| 21 |
+
clearTimeout(toastTimeout);
|
| 22 |
+
toastTimeout = null;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
t.innerHTML = `
|
| 26 |
+
<span class="toast-content">${msg}</span>
|
| 27 |
+
<button type="button" class="toast-close" id="toast-close-btn" title="Dismiss Alert">×</button>
|
| 28 |
+
`;
|
| 29 |
t.className = `toast ${type} show`;
|
| 30 |
+
|
| 31 |
+
t.querySelector('#toast-close-btn')?.addEventListener('click', () => {
|
| 32 |
+
t.classList.remove('show');
|
| 33 |
+
});
|
| 34 |
+
|
| 35 |
+
// Keep error toasts visible until explicitly dismissed
|
| 36 |
+
if (type !== 'error') {
|
| 37 |
+
toastTimeout = setTimeout(() => {
|
| 38 |
+
t.classList.remove('show');
|
| 39 |
+
}, duration);
|
| 40 |
+
}
|
| 41 |
}
|
| 42 |
|
| 43 |
function animateCount(el, target) {
|