aboutsummaryrefslogtreecommitdiffstats
path: root/youtube/watch.py
diff options
context:
space:
mode:
authorAstounds <kirito@disroot.org>2026-05-03 12:32:55 -0500
committerAstounds <kirito@disroot.org>2026-05-03 12:32:55 -0500
commit8d66143c90c4b86e8ec8dfed67753bef2abf2114 (patch)
treef7d73591c228cf52c7a4abd15c855d7d06e80ff8 /youtube/watch.py
parent50ad959a8051fec95f26b573f9fe067bdf3fdf6a (diff)
downloadyt-local-8d66143c90c4b86e8ec8dfed67753bef2abf2114.tar.lz
yt-local-8d66143c90c4b86e8ec8dfed67753bef2abf2114.tar.xz
yt-local-8d66143c90c4b86e8ec8dfed67753bef2abf2114.zip
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
Diffstat (limited to 'youtube/watch.py')
-rw-r--r--youtube/watch.py103
1 files changed, 36 insertions, 67 deletions
diff --git a/youtube/watch.py b/youtube/watch.py
index 9d1e442..ec446f4 100644
--- a/youtube/watch.py
+++ b/youtube/watch.py
@@ -17,8 +17,16 @@ from flask import request
import youtube
from youtube import yt_app
from youtube import util, comments, local_playlist, yt_data_extract
+from youtube import watch_formats
import settings
+# Backward compatibility aliases
+codec_name = watch_formats.codec_name
+video_quality_string = watch_formats.video_quality_string
+short_video_quality_string = watch_formats.short_video_quality_string
+audio_quality_string = watch_formats.audio_quality_string
+format_bytes = watch_formats.format_bytes
+
logger = logging.getLogger(__name__)
@@ -29,15 +37,7 @@ except FileNotFoundError:
decrypt_cache = {}
-def codec_name(vcodec):
- if vcodec.startswith('avc'):
- return 'h264'
- elif vcodec.startswith('av01'):
- return 'av1'
- elif vcodec.startswith('vp'):
- return 'vp'
- else:
- return 'unknown'
+# codec_name imported from watch_formats
def get_video_sources(info, target_resolution):
@@ -446,7 +446,7 @@ def extract_info(video_id, use_invidious, playlist_id=None, index=None):
info['hls_audio_tracks'] = {}
hls_data = None
hls_client_used = None
- for hls_client in ('ios', 'ios_vr', 'android'):
+ for hls_client in ('ios', 'android'):
try:
resp = fetch_player_response(hls_client, video_id) or {}
hls_data = json.loads(resp) if isinstance(resp, str) else resp
@@ -621,55 +621,10 @@ def extract_info(video_id, use_invidious, playlist_id=None, index=None):
return info
-def video_quality_string(format):
- if format['vcodec']:
- result = f"{format['width'] or '?'}x{format['height'] or '?'}"
- if format['fps']:
- result += f" {format['fps']}fps"
- return result
- elif format['acodec']:
- return 'audio only'
-
- return '?'
-
-
-def short_video_quality_string(fmt):
- result = f"{fmt['quality'] or '?'}p"
- if fmt['fps']:
- result += str(fmt['fps'])
- if fmt['vcodec'].startswith('av01'):
- result += ' AV1'
- elif fmt['vcodec'].startswith('avc'):
- result += ' h264'
- else:
- result += f" {fmt['vcodec']}"
- return result
-
-
-def audio_quality_string(fmt):
- if fmt['acodec']:
- if fmt['audio_bitrate']:
- result = f"{fmt['audio_bitrate']}k"
- else:
- result = '?k'
- if fmt['audio_sample_rate']:
- result += f" {'%.3G' % (fmt['audio_sample_rate']/1000)}kHz"
- return result
- elif fmt['vcodec']:
- return 'video only'
- return '?'
-
-
-# from https://github.com/ytdl-org/youtube-dl/blob/master/youtube_dl/utils.py
-def format_bytes(bytes):
- if bytes is None:
- return 'N/A'
- if type(bytes) is str:
- bytes = float(bytes)
- if bytes == 0.0:
- exponent = 0
- else:
- exponent = int(math.log(bytes, 1024.0))
+# video_quality_string imported from watch_formats
+# short_video_quality_string imported from watch_formats
+# audio_quality_string imported from watch_formats
+# format_bytes imported from watch_formats
suffix = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'][exponent]
converted = float(bytes) / float(1024 ** exponent)
return '%.2f%s' % (converted, suffix)
@@ -832,14 +787,12 @@ def get_audio_track():
# This is an actual segment - fetch and serve it
try:
- headers = (
- ('User-Agent', 'Mozilla/5.0'),
- ('Accept', '*/*'),
- )
- content = util.fetch_url(seg_url, headers=headers,
- debug_name='hls_seg', report_text=None)
+ headers_dict = {
+ 'User-Agent': 'Mozilla/5.0',
+ 'Accept': '*/*',
+ }
- # Determine content type based on URL or content
+ # Determine content type based on URL
# HLS segments are usually MPEG-TS (.ts) but can be MP4 (.mp4, .m4s)
if '.mp4' in seg_url or '.m4s' in seg_url or seg_url.lower().endswith('.mp4'):
content_type = 'video/mp4'
@@ -849,7 +802,23 @@ def get_audio_track():
# Default to MPEG-TS for HLS
content_type = 'video/mp2t'
- return flask.Response(content, mimetype=content_type,
+ response, cleanup_func = util.fetch_url_response(
+ seg_url, headers=tuple(headers_dict.items()),
+ timeout=30, use_tor=settings.route_tor)
+
+ def generate():
+ try:
+ while True:
+ chunk = response.read(64 * 1024) # 64 KB chunks
+ if not chunk:
+ break
+ yield chunk
+ finally:
+ cleanup_func(response)
+
+ return flask.Response(
+ flask.stream_with_context(generate()),
+ mimetype=content_type,
headers={
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, OPTIONS',