Spaces:
Running
Running
Nrighton233j
Add yt-dlp video extraction, page scraper, and speed optimizations (session reuse, parallel related-links search, caching)
080713a | """ | |
| video.py — yt-dlp integration for B24 Browser. | |
| Two modes, matching what the user picks in the app: | |
| - "play": return a direct stream URL for the video player to use | |
| - "download": stream the video bytes through this backend to the client | |
| Note: merging separate video+audio streams (needed for >720p on most | |
| sites) requires ffmpeg, which must be present in the container. | |
| Pre-muxed formats (usually up to 720p) work without ffmpeg. | |
| """ | |
| import subprocess | |
| import yt_dlp | |
| YDL_BASE_OPTS = { | |
| "quiet": True, | |
| "no_warnings": True, | |
| "skip_download": True, | |
| } | |
| def get_video_info(url: str): | |
| """ | |
| Returns { title, thumbnail, duration, formats: [...] } or {"error": ...} | |
| Each format: { format_id, resolution, ext, filesize, needs_merge, note } | |
| """ | |
| try: | |
| with yt_dlp.YoutubeDL(YDL_BASE_OPTS) as ydl: | |
| info = ydl.extract_info(url, download=False) | |
| except yt_dlp.utils.DownloadError as e: | |
| return {"error": f"could not extract video info: {e}"} | |
| raw_formats = info.get("formats", []) | |
| formats = [] | |
| seen_resolutions = set() | |
| for f in raw_formats: | |
| height = f.get("height") | |
| vcodec = f.get("vcodec", "none") | |
| acodec = f.get("acodec", "none") | |
| if vcodec == "none": | |
| continue | |
| resolution = f"{height}p" if height else f.get("format_note", "unknown") | |
| if resolution in seen_resolutions: | |
| continue | |
| seen_resolutions.add(resolution) | |
| formats.append({ | |
| "format_id": f.get("format_id"), | |
| "resolution": resolution, | |
| "ext": f.get("ext"), | |
| "filesize": f.get("filesize") or f.get("filesize_approx"), | |
| "needs_merge": acodec == "none", | |
| "url": f.get("url"), | |
| }) | |
| formats.sort(key=lambda x: (x["resolution"] != "unknown", x["resolution"]), reverse=True) | |
| return { | |
| "title": info.get("title"), | |
| "thumbnail": info.get("thumbnail"), | |
| "duration": info.get("duration"), | |
| "formats": formats[:8], | |
| } | |
| def stream_download(url: str, format_id: str = None): | |
| """ | |
| Yields raw video bytes for streaming to the client, via yt-dlp CLI | |
| piping to stdout. Handles merging (ffmpeg) automatically if the | |
| chosen format needs it. Falls back to best pre-muxed format. | |
| """ | |
| fmt = format_id if format_id else "best[ext=mp4]/best" | |
| cmd = [ | |
| "yt-dlp", | |
| "-f", fmt, | |
| "--merge-output-format", "mkv", | |
| "-o", "-", | |
| "--quiet", | |
| "--no-warnings", | |
| url, | |
| ] | |
| process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) | |
| try: | |
| while True: | |
| chunk = process.stdout.read(65536) | |
| if not chunk: | |
| break | |
| yield chunk | |
| finally: | |
| process.stdout.close() | |
| process.wait() | |