"""Output filename derivation. Bugs fixed from the previous version: * twitch.tv/videos/123 produced "www.twitch.tv_123" (the channel guess matched the host, because the host is the path element before "videos"). * Kick VOD slugs were truncated to 12 chars, so every VOD of a channel with a long shared title prefix collided on the same filename. """ from __future__ import annotations import hashlib import re import uuid from urllib.parse import urlparse _DOMAINS = ( ("kick.com", "kick"), ("twitch.tv", "twitch"), ("youtube.com", "youtube"), ("youtu.be", "youtube"), ) _SAFE_RE = re.compile(r"[^A-Za-z0-9._-]+") _KEEP_TITLE = 40 _MAX_LEN = 110 def _platform(url: str) -> str: host = urlparse(url).netloc.lower() for domain, name in _DOMAINS: if host == domain or host.endswith("." + domain): return name return "stream" def _channel(url: str, platform: str) -> str: parts = [p for p in urlparse(url).path.split("/") if p] if platform == "kick" and len(parts) >= 3 and parts[1] == "videos": return parts[0] return "" def _shorten(slug: str) -> str: slug = slug.strip() if len(slug) <= _KEEP_TITLE: return slug head = slug[:_KEEP_TITLE] if "-" in head[9:]: head = head[: head.rfind("-")] digest = hashlib.sha1(slug.encode("utf-8")).hexdigest()[:6] return f"{head}-{digest}" def name_from_url(url: str, fallback: str | None = None) -> str: platform = _platform(url) vod = re.search(r"/videos/([^/?#]+)", url) or re.search(r"/shorts/([^/?#]+)", url) if vod: parts = [platform, _channel(url, platform), _shorten(vod.group(1))] return _sanitize("_".join(p for p in parts if p)) watch = re.search(r"[?&]v=([A-Za-z0-9_-]{6,})", url) if watch: return _sanitize(f"{platform}_{watch.group(1)}") live = re.search(r"youtu\.be/([A-Za-z0-9_-]{6,})", url) if live: return _sanitize(f"{platform}_{live.group(1)}") return fallback or uuid.uuid4().hex[:8] def _sanitize(name: str) -> str: name = _SAFE_RE.sub("_", name).strip("._-") if not name: name = uuid.uuid4().hex[:8] return name[:_MAX_LEN]