""" 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()