fix: update innertube clients and fix HLS/DASH quality switching
All checks were successful
CI / test (push) Successful in 53s
All checks were successful
CI / test (push) Successful in 53s
- 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
This commit is contained in:
103
youtube/watch.py
103
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',
|
||||
|
||||
Reference in New Issue
Block a user