Spaces:
Sleeping
Sleeping
| """ | |
| SaverAPI video fetcher. | |
| Dispatches to the right SaverAPI endpoint based on URL: | |
| - YouTube / youtu.be / youtube Shorts -> /api/youtube-api | |
| - Everything else (Instagram, TikTok, X, ...) -> /api/all-in-one-downloader-api | |
| Stdlib only (urllib). Runs anywhere Python 3.7+ is installed, no pip needed. | |
| Response shapes handled: | |
| all-in-one / IG+TT : {"error": false, "download_url": "https://...", ...} | |
| all-in-one / Vimeo : {"medias": [{"url": "https://...", "quality": "..."}, ...], ...} | |
| all-in-one / others : {"url": "https://...", "source": "...", ...} (less common) | |
| all-in-one URL-err : {"error": true, "message": "..."} (URL-level, do NOT mark key dead) | |
| all-in-one / key : HTTP 402 + {"message": "Insufficient credits"} (KEY-level, mark dead) | |
| youtube success : {"status": "tunnel", "url": "https://...", ...} | |
| youtube error : (unconfirmed β passed through as best-effort) | |
| Cloudflare block : HTTP 403, body contains "error code: 1010" (key/network, mark dead) | |
| """ | |
| import json | |
| import os | |
| import sys | |
| import tempfile | |
| import urllib.error | |
| import urllib.parse | |
| import urllib.request | |
| # ββ Endpoints ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| ALL_IN_ONE_URL = "https://saverapi.net/api/all-in-one-downloader-api" | |
| YOUTUBE_URL = "https://saverapi.net/api/youtube-api" | |
| # ββ Cloudflare 1010 bypass: send a real-browser User-Agent ββββββββββββββββ | |
| BROWSER_HEADERS = { | |
| "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" | |
| ), | |
| "Accept": "application/json, text/plain, */*", | |
| "Accept-Language": "en-US,en;q=0.9", | |
| "Origin": "https://saverapi.net", | |
| "Referer": "https://saverapi.net/", | |
| } | |
| # ββ Tuning βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| LOOKUP_TIMEOUT_SEC = 15 | |
| DOWNLOAD_TIMEOUT_SEC = 60 | |
| DEFAULT_MAX_FILESIZE_MB = 100 | |
| YOUTUBE_DEFAULT_QUALITY = "720" # SaverAPI's param is misspelled "farmat" | |
| # ββ Platform detection ββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _is_youtube(url: str) -> bool: | |
| u = url.lower() | |
| return ("youtube.com" in u) or ("youtu.be" in u) or ("youtube-nocookie.com" in u) | |
| # ββ Step 1: ask SaverAPI for a direct video URL ββββββββββββββββββββββββββ | |
| def _saverapi_lookup(url: str, api_key: str) -> tuple[bool, str, str]: | |
| """Returns (ok, direct_video_url, error_message).""" | |
| if _is_youtube(url): | |
| endpoint = YOUTUBE_URL | |
| params = {"url": url, "farmat": YOUTUBE_DEFAULT_QUALITY} | |
| else: | |
| endpoint = ALL_IN_ONE_URL | |
| params = {"url": url} | |
| full_url = f"{endpoint}?{urllib.parse.urlencode(params)}" | |
| req = urllib.request.Request( | |
| full_url, | |
| headers={**BROWSER_HEADERS, "x-api-key": api_key, "Content-Type": "application/json"}, | |
| method="GET", | |
| ) | |
| try: | |
| with urllib.request.urlopen(req, timeout=LOOKUP_TIMEOUT_SEC) as r: | |
| status = r.status | |
| body = r.read().decode("utf-8", errors="replace") | |
| except urllib.error.HTTPError as e: | |
| body = e.read().decode("utf-8", errors="replace") | |
| if e.code == 403 and "error code" in body: | |
| return False, "", "key-level:cloudflare" | |
| if e.code == 402 and "insufficient" in body.lower(): | |
| return False, "", "key-level:insufficient-credits" | |
| return False, "", f"key-level:http-{e.code}: {body[:200]}" | |
| except Exception as e: | |
| return False, "", f"key-level:network: {type(e).__name__}: {e}" | |
| if status != 200: | |
| return False, "", f"key-level:http-{status}: {body[:200]}" | |
| try: | |
| result = json.loads(body) | |
| except json.JSONDecodeError: | |
| return False, "", f"Non-JSON response: {body[:200]}" | |
| if not isinstance(result, dict): | |
| return False, "", f"Unexpected response (not an object): {str(result)[:200]}" | |
| # YouTube success: {"status": "tunnel", "url": "https://..."} | |
| if result.get("status") == "tunnel" and result.get("url"): | |
| return True, result["url"], "" | |
| # Vimeo-style success: {"medias": [{"url": "...", "quality": "..."}, ...], ...} | |
| medias = result.get("medias") | |
| if isinstance(medias, list) and medias: | |
| # SaverAPI lists medias best-first; pick the first with a URL. | |
| for m in medias: | |
| if isinstance(m, dict) and m.get("url"): | |
| return True, m["url"], "" | |
| return False, "", "medias[] present but no url in any item" | |
| # all-in-one success: {"error": false, "download_url": "https://..."} | |
| if result.get("error") is False: | |
| direct = result.get("download_url") | |
| if direct: | |
| return True, direct, "" | |
| return False, "", "all-in-one: error=false but no download_url" | |
| # all-in-one URL-level error: {"error": true, "message": "..."} | |
| if result.get("error") is True: | |
| return False, "", f"SaverAPI: {result.get('message', 'unspecified error')}" | |
| # YouTube non-tunnel status (unconfirmed error shape β pass through) | |
| if "status" in result: | |
| return False, "", f"YouTube API status={result.get('status')!r}" | |
| # Generic fallback: some platforms return a top-level "url" + "source" | |
| if result.get("url") and result.get("source"): | |
| return True, result["url"], "" | |
| return False, "", f"Unexpected shape: {str(result)[:200]}" | |
| # ββ Step 2: stream the direct URL to a tempfile with a size cap ββββββββββ | |
| def _stream_to_file(direct_url: str, max_bytes: int) -> tuple[bool, str]: | |
| # Strip saverapi.net-specific headers (Referer/Origin) β those are for the | |
| # SaverAPI lookup, not for the underlying CDN (twimg.com, fbcdn.net, etc.). | |
| # Keep User-Agent + Accept so the CDN sees a real browser. | |
| headers = { | |
| "User-Agent": BROWSER_HEADERS["User-Agent"], | |
| "Accept": "*/*", | |
| } | |
| req = urllib.request.Request(direct_url, headers=headers, method="GET") | |
| tmp = tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) | |
| tmp.close() | |
| try: | |
| with urllib.request.urlopen(req, timeout=DOWNLOAD_TIMEOUT_SEC) as r: | |
| # Peek first 1 KB to catch HTML error pages | |
| first_chunk = r.read(1024) | |
| if first_chunk.lstrip().lower().startswith((b"<!doctype", b"<html")): | |
| return False, "download_url returned HTML, not a video" | |
| bytes_written = 0 | |
| with open(tmp.name, "wb") as f: | |
| if first_chunk: | |
| f.write(first_chunk) | |
| bytes_written = len(first_chunk) | |
| if bytes_written > max_bytes: | |
| return False, f"Exceeds {max_bytes // (1024 * 1024)} MB cap" | |
| while True: | |
| chunk = r.read(64 * 1024) | |
| if not chunk: | |
| break | |
| bytes_written += len(chunk) | |
| if bytes_written > max_bytes: | |
| return False, f"Exceeds {max_bytes // (1024 * 1024)} MB cap" | |
| f.write(chunk) | |
| if bytes_written == 0: | |
| return False, "Empty response" | |
| return True, tmp.name | |
| except urllib.error.HTTPError as e: | |
| try: | |
| os.remove(tmp.name) | |
| except OSError: | |
| pass | |
| return False, f"Download HTTP {e.code}" | |
| except Exception as e: | |
| try: | |
| os.remove(tmp.name) | |
| except OSError: | |
| pass | |
| return False, f"Download error: {type(e).__name__}: {e}" | |
| # ββ Public entry point ββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def saverapi_fetch( | |
| url: str, | |
| api_key: str, | |
| max_filesize_mb: int = DEFAULT_MAX_FILESIZE_MB, | |
| ) -> tuple[bool, str]: | |
| """ | |
| Fetch a video from a supported platform via SaverAPI. | |
| Args: | |
| url: Public video URL (Instagram, TikTok, YouTube, ...) | |
| api_key: SaverAPI key | |
| max_filesize_mb: Hard cap on downloaded video size (default 100) | |
| Returns: | |
| (True, file_path) on success β caller must `os.remove(path)` when done | |
| (False, err_message) on any failure | |
| """ | |
| if not url.lower().startswith(("http://", "https://")): | |
| return False, "Unsupported URL scheme (use http:// or https://)" | |
| ok, direct, err = _saverapi_lookup(url, api_key) | |
| if not ok: | |
| return False, err | |
| return _stream_to_file(direct, max_filesize_mb * 1024 * 1024) | |
| # ββ Quick self-test βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if __name__ == "__main__": | |
| import time | |
| api_key = os.environ.get("SAVER_API_KEY", "").strip() | |
| if not api_key: | |
| print("Set SAVER_API_KEY env var first.", file=sys.stderr) | |
| sys.exit(1) | |
| tests = [ | |
| "https://www.instagram.com/reel/DZTCpI2SQsq", | |
| "https://www.tiktok.com/@scout2015/video/6718335390845095173", | |
| "https://www.facebook.com/reel/763412386794044/", | |
| "https://x.com/traderpodcaste/status/2063797483629629691", | |
| "https://vimeo.com/76979871", | |
| "https://www.youtube.com/shorts/LXb3EKWsInQ", | |
| ] | |
| for u in tests: | |
| print(f"\nβ {u}") | |
| t0 = time.time() | |
| ok, result = saverapi_fetch(u, api_key) | |
| dt = time.time() - t0 | |
| if ok: | |
| size = os.path.getsize(result) // 1024 | |
| print(f" β {size} KB in {dt:.1f}s β {result}") | |
| os.remove(result) | |
| else: | |
| print(f" β {result}") | |