"""FlowDeck — Media embeds (v5.5.0): provider detection + iframe rewriting. Maps a raw http(s) URL to a provider-specific embed URL so that one generic ``embed`` block can render YouTube, Vimeo, Figma, Google Maps, Loom, CodePen, Miro, Spotify, SoundCloud, Twitch, X/Twitter, Pinterest, Office docs… exactly like Notion's universal embed. Unknown/showable URLs (PDF, images, direct video/audio files, plain http) fall back to a plain iframe so the link is still visible inline. """ from __future__ import annotations import re from urllib.parse import parse_qs, urlparse _PROVIDERS = ( # (host pattern, extra path check, to_embed(url, path, query) -> str | None) ) def _q(params, key): vals = params.get(key) return vals[0] if vals else "" def _embed_youtube(url: str, path: str, params) -> str | None: m = re.search(r"((?:v|shorts|embed)/|be/)([A-Za-z0-9_-]{6,20})", path) vid = m.group(2) if m else _q(params, "v") if not vid: return None start = _q(params, "t") or _q(params, "start") frag = f"?start={start}" if start else "" return f"https://www.youtube.com/embed/{vid}{frag}" def _embed_vimeo(url: str, path: str, params) -> str | None: m = re.search(r"/(\d{6,12})", path) if not m: return None return f"https://player.vimeo.com/video/{m.group(1)}" def _embed_loom(url: str, path: str, params) -> str | None: m = re.search(r"/(embed/)?([0-9a-f]{32})", path) if not m: return None return f"https://www.loom.com/embed/{m.group(2)}" def _embed_figma(url: str, path: str, params) -> str | None: if "figma.com/file/" not in url and "figma.com/proto/" not in url: return None m = re.search(r"(/[^/]+/[^?]+)", url.split("?", 1)[0]) if not m: return None return "https://www.figma.com/embed?embed_host=flowdeck&url=" + url.split("?", 1)[0] def _embed_map(url: str, path: str, params) -> str | None: if "google.com/maps" not in url and "maps.app.goo.gl" not in url: return None return f"https://www.google.com/maps?output=embed&q={url}" def _embed_codepen(url: str, path: str, params) -> str | None: m = re.search(r"codepen\.io/([^/]+)/pen/([^/?#]+)", url) if not m: return None return f"https://codepen.io/{m.group(1)}/embed/{m.group(2)}?default-tab=result" def _embed_miro(url: str, path: str, params) -> str | None: m = re.search(r"miro\.com/app/board/([^/?#]+)", url) if not m: return None return f"https://miro.com/app/live-embed/{m.group(1)}" def _embed_spotify(url: str, path: str, params) -> str | None: m = re.search(r"/(track|playlist|album|episode|show|artist)/([A-Za-z0-9]+)", url) if not m: return None return f"https://open.spotify.com/embed/{m.group(1)}/{m.group(2)}" def _embed_soundcloud(url: str, path: str, params) -> str | None: m = re.search(r"(?:soundcloud\.com/[^/]+/[^/?#]+)", url) if not m: return None return f"https://w.soundcloud.com/player/?url={url}&color=%2300aaff" def _embed_twitch(url: str, path: str, params) -> str | None: if "twitch.tv" not in url: return None m = re.search(r"twitch\.tv/([^/?#]+)", url) if not m: return None return f"https://player.twitch.tv/?channel={m.group(1)}&parent=" def _embed_twitter(url: str, path: str, params) -> str | None: if "twitter.com" not in url and "x.com" not in url: return None m = re.search(r"/([^/?#]+)/status/(\d+)", url) if not m: return f"https://platform.twitter.com/embed/Tweet.html?url={url}" return f"https://platform.twitter.com/embed/Tweet.html?id={m.group(2)}" def _embed_pinterest(url: str, path: str, params) -> str | None: if "pinterest" not in url: return None return f"https://pinterest.com/pin/embed?url={url}" def _embed_files(url: str, path: str, params) -> str | None: """Direct media: PDF/images/videos/audio can live in a plain iframe.""" return url _HANDLERS = ( ("youtube.com", _embed_youtube), ("youtu.be", _embed_youtube), ("vimeo.com", _embed_vimeo), ("loom.com", _embed_loom), ("figma.com", _embed_figma), ("google.com/maps", _embed_map), ("maps.app.goo.gl", _embed_map), ("codepen.io", _embed_codepen), ("miro.com", _embed_miro), ("open.spotify.com", _embed_spotify), ("spotify.com", _embed_spotify), ("soundcloud.com", _embed_soundcloud), ("twitch.tv", _embed_twitch), ("twitter.com", _embed_twitter), ("x.com", _embed_twitter), ("pinterest", _embed_pinterest), ) def embed_src(url: str) -> str | None: """Return the embeddable iframe src for a URL, or None if it can't embed.""" if not url: return None url = url.strip() u = urlparse(url if url.startswith("http") else "https://" + url) if u.scheme not in ("http", "https"): return None (u.hostname or "").lower().replace("www.", "") netloc = (u.netloc or "").lower() params = parse_qs(u.query) # Google Maps share links encode the query in the path (…&q=/maps/…) encoded = url for needle, handler in _HANDLERS: if needle in netloc: return handler(encoded, u.path, params) return _embed_files(encoded, u.path, params) _IMAGE_EXT = re.compile(r"\.(png|jpe?g|gif|webp|svg|bmp|ico|avif)$", re.I) _PDF_EXT = re.compile(r"\.pdf$", re.I) _VIDEO_EXT = re.compile(r"\.(mp4|webm|ogg|ogv|mov|m4v)$", re.I) _AUDIO_EXT = re.compile(r"\.(mp3|wav|ogg|oga|m4a|flac|aac)$", re.I) def inline_kind(url: str) -> str | None: """Best inline renderer for a URL: 'iframe' | 'image' | 'pdf' | 'video' | | 'audio'. Returns None when the URL should open in a new tab.""" if not url: return None url = url.strip() path = urlparse(url).path if _IMAGE_EXT.search(path): return "image" if _PDF_EXT.search(path): return "pdf" if _VIDEO_EXT.search(path): return "video" if _AUDIO_EXT.search(path): return "audio" if url.startswith(("http://", "https://")): return "iframe" return None def embed_html(src: str, *, height: int = 520) -> str: """A responsive, borderless iframe for a provider embed URL.""" return ( f'' )