Spaces:
Sleeping
fix: validate URLs upfront — reject invalid/non-YouTube URLs immediately
Browse filesZeroGPU blocks all external DNS except HuggingFace services, so only
YouTube URLs (via proxy) and uploaded files work. Previously:
- Invalid text ("hello") was treated as a file path → FileNotFoundError
- Non-YouTube URLs tried download.download_video → hung on DNS
- YouTube URLs without scheme ("youtube.com/...") → treated as file
- YouTube URLs with invalid video ID → confusing proxy error
Now validates source before the pipeline starts:
- Auto-adds https:// for youtube.com/youtu.be URLs missing scheme
- Rejects non-YouTube URLs with clear explanation
- Validates YouTube video ID format (11 chars from URL)
- Rejects garbage input with helpful guidance
- Simplifies Stage 1 download logic (only YouTube proxy or local file)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- pipeline_runner.py +52 -20
|
@@ -322,23 +322,56 @@ def run_pipeline(
|
|
| 322 |
"""
|
| 323 |
from pathlib import Path
|
| 324 |
import soundfile as sf
|
| 325 |
-
|
| 326 |
-
# Resolve slug — YouTube DNS is blocked on ZeroGPU so skip yt-dlp for
|
| 327 |
-
# YouTube URLs entirely (avoids 30-60s of pointless DNS retries).
|
| 328 |
import hashlib
|
| 329 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 330 |
match = _YT_RE.search(source)
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
|
|
|
| 338 |
try:
|
| 339 |
slug = download.slug_from_path(source)
|
| 340 |
except Exception:
|
| 341 |
slug = hashlib.md5(source.encode()).hexdigest()[:12]
|
|
|
|
|
|
|
|
|
|
| 342 |
proj = ProjectPaths(slug, base_dir=BASE_DIR, target_language=target_language)
|
| 343 |
proj.ensure_dirs()
|
| 344 |
tracker = LLMUsageTracker()
|
|
@@ -351,18 +384,17 @@ def run_pipeline(
|
|
| 351 |
yield stage, f"[{STAGE_NAMES[stage - 1]}] Downloading: {source}", {}
|
| 352 |
|
| 353 |
try:
|
| 354 |
-
if
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
yield stage, f"[{STAGE_NAMES[stage - 1]}] Downloading via YouTube proxy...", {}
|
| 358 |
-
_download_via_proxy(source, proj.video)
|
| 359 |
-
else:
|
| 360 |
-
download.download_video(source, proj.video)
|
| 361 |
download.extract_audio(proj.video, proj.audio)
|
| 362 |
-
elif download.is_audio_file(source):
|
| 363 |
download.ingest_local_audio(source, proj.audio)
|
| 364 |
-
|
| 365 |
download.ingest_local_video(source, proj.video, proj.audio)
|
|
|
|
|
|
|
|
|
|
| 366 |
except Exception as exc:
|
| 367 |
yield stage, f"[{STAGE_NAMES[stage - 1]}] FAILED: {exc}", {"error": str(exc)}
|
| 368 |
return
|
|
|
|
| 322 |
"""
|
| 323 |
from pathlib import Path
|
| 324 |
import soundfile as sf
|
|
|
|
|
|
|
|
|
|
| 325 |
import hashlib
|
| 326 |
+
|
| 327 |
+
# ── Upfront source validation ──────────────────────────────────────
|
| 328 |
+
# ZeroGPU blocks all external DNS except HuggingFace services.
|
| 329 |
+
# Only YouTube URLs (via proxy) and uploaded files work.
|
| 330 |
+
source = source.strip()
|
| 331 |
+
is_local = os.path.exists(source)
|
| 332 |
+
is_url = download.is_url(source)
|
| 333 |
+
is_yt = _is_youtube_url(source)
|
| 334 |
+
|
| 335 |
+
if not is_local and not is_url:
|
| 336 |
+
# Might be a URL without scheme — try adding https://
|
| 337 |
+
if "youtube.com" in source or "youtu.be" in source:
|
| 338 |
+
source = "https://" + source
|
| 339 |
+
is_url = True
|
| 340 |
+
is_yt = True
|
| 341 |
+
else:
|
| 342 |
+
yield 1, (
|
| 343 |
+
f"[ERROR] Invalid source: '{source}'\n"
|
| 344 |
+
"Please provide a YouTube URL (e.g. https://youtube.com/watch?v=...) "
|
| 345 |
+
"or upload a video/audio file."
|
| 346 |
+
), {"error": "invalid source"}
|
| 347 |
+
return
|
| 348 |
+
|
| 349 |
+
if is_url and not is_yt:
|
| 350 |
+
yield 1, (
|
| 351 |
+
f"[ERROR] Non-YouTube URLs are not supported on this Space.\n"
|
| 352 |
+
f"URL: {source}\n"
|
| 353 |
+
"ZeroGPU blocks external network access. Only YouTube URLs work "
|
| 354 |
+
"(downloaded via proxy). Please use a YouTube link or upload your file directly."
|
| 355 |
+
), {"error": "unsupported URL"}
|
| 356 |
+
return
|
| 357 |
+
|
| 358 |
+
if is_yt:
|
| 359 |
match = _YT_RE.search(source)
|
| 360 |
+
if not match:
|
| 361 |
+
yield 1, (
|
| 362 |
+
f"[ERROR] Could not find a valid YouTube video ID in: {source}\n"
|
| 363 |
+
"Expected format: https://youtube.com/watch?v=VIDEO_ID or https://youtu.be/VIDEO_ID"
|
| 364 |
+
), {"error": "invalid YouTube URL"}
|
| 365 |
+
return
|
| 366 |
+
slug = match.group(1)
|
| 367 |
+
elif is_local:
|
| 368 |
try:
|
| 369 |
slug = download.slug_from_path(source)
|
| 370 |
except Exception:
|
| 371 |
slug = hashlib.md5(source.encode()).hexdigest()[:12]
|
| 372 |
+
else:
|
| 373 |
+
slug = hashlib.md5(source.encode()).hexdigest()[:12]
|
| 374 |
+
|
| 375 |
proj = ProjectPaths(slug, base_dir=BASE_DIR, target_language=target_language)
|
| 376 |
proj.ensure_dirs()
|
| 377 |
tracker = LLMUsageTracker()
|
|
|
|
| 384 |
yield stage, f"[{STAGE_NAMES[stage - 1]}] Downloading: {source}", {}
|
| 385 |
|
| 386 |
try:
|
| 387 |
+
if is_yt:
|
| 388 |
+
yield stage, f"[{STAGE_NAMES[stage - 1]}] Downloading via YouTube proxy...", {}
|
| 389 |
+
_download_via_proxy(source, proj.video)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 390 |
download.extract_audio(proj.video, proj.audio)
|
| 391 |
+
elif is_local and download.is_audio_file(source):
|
| 392 |
download.ingest_local_audio(source, proj.audio)
|
| 393 |
+
elif is_local:
|
| 394 |
download.ingest_local_video(source, proj.video, proj.audio)
|
| 395 |
+
else:
|
| 396 |
+
yield stage, f"[{STAGE_NAMES[stage - 1]}] FAILED: unsupported source", {"error": "unsupported"}
|
| 397 |
+
return
|
| 398 |
except Exception as exc:
|
| 399 |
yield stage, f"[{STAGE_NAMES[stage - 1]}] FAILED: {exc}", {"error": str(exc)}
|
| 400 |
return
|