From 8d66143c90c4b86e8ec8dfed67753bef2abf2114 Mon Sep 17 00:00:00 2001 From: Astounds Date: Sun, 3 May 2026 12:32:55 -0500 Subject: fix: update innertube clients and fix HLS/DASH quality switching - Update innertube client versions to match yt-dlp (android 21.02.35, ios 21.02.3, web 2.20260114.08.00, android_vr 1.65.10) - Remove obsolete clients (android-test-suite, ios_vr) - Replace tv_embedded with TVHTML5_SIMPLY (cn 75) - Add new clients: web_embedded, mweb, tv - Fix HLS freeze on quality switch: use nextLevel instead of currentLevel, handle bufferStalledError, stream proxy segments instead of buffering in memory - Populate DASH quality selector with actual sources (no Auto) - Render quality-select empty in template, let JS populate per mode --- youtube/watch_formats.py | 82 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 youtube/watch_formats.py (limited to 'youtube/watch_formats.py') diff --git a/youtube/watch_formats.py b/youtube/watch_formats.py new file mode 100644 index 0000000..7dad325 --- /dev/null +++ b/youtube/watch_formats.py @@ -0,0 +1,82 @@ +"""Video format helpers for yt-local.""" + +import math +from typing import Any, Dict, Optional + + +def codec_name(vcodec: str) -> str: + """Extract codec short name from codec string.""" + if vcodec.startswith('avc'): + return 'h264' + elif vcodec.startswith('av01'): + return 'av1' + elif vcodec.startswith('vp'): + return 'vp' + else: + return 'unknown' + + +def video_quality_string(fmt: Dict[str, Any]) -> str: + """Return video quality string (e.g., '1920x1080 30fps').""" + if fmt.get('vcodec'): + result = f"{fmt.get('width') or '?'}x{fmt.get('height') or '?'}" + if fmt.get('fps'): + result += f" {fmt['fps']}fps" + return result + elif fmt.get('acodec'): + return 'audio only' + return '?' + + +def short_video_quality_string(fmt: Dict[str, Any]) -> str: + """Return short video quality string (e.g., '1080p60 AV1').""" + result = f"{fmt.get('quality') or '?'}p" + if fmt.get('fps'): + result += str(fmt['fps']) + vcodec = fmt.get('vcodec', '') + if vcodec.startswith('av01'): + result += ' AV1' + elif vcodec.startswith('avc'): + result += ' h264' + else: + result += f" {vcodec}" + return result + + +def audio_quality_string(fmt: Dict[str, Any]) -> str: + """Return audio quality string (e.g., '128k 44.1kHz').""" + if fmt.get('acodec'): + if fmt.get('audio_bitrate'): + result = f"{fmt['audio_bitrate']}k" + else: + result = '?k' + if fmt.get('audio_sample_rate'): + result += f" {'%.3G' % (fmt['audio_sample_rate']/1000)}kHz" + return result + elif fmt.get('vcodec'): + return 'video only' + return '?' + + +def format_bytes(bytes_val: Optional[float]) -> str: + """Convert bytes to human-readable string (e.g., '1.5 MiB').""" + if bytes_val is None: + return 'N/A' + if type(bytes_val) is str: + bytes_val = float(bytes_val) + if bytes_val == 0.0: + exponent = 0 + else: + exponent = int(math.log(bytes_val, 1024.0)) + suffix = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'][exponent] + converted = float(bytes_val) / float(1024 ** exponent) + return '%.2f%s' % (converted, suffix) + + +__all__ = [ + 'codec_name', + 'video_quality_string', + 'short_video_quality_string', + 'audio_quality_string', + 'format_bytes', +] \ No newline at end of file -- cgit v1.2.3