aboutsummaryrefslogtreecommitdiffstats
path: root/youtube/watch.py
diff options
context:
space:
mode:
authorAstounds <kirito@disroot.org>2026-03-27 20:47:44 -0500
committerAstounds <kirito@disroot.org>2026-03-27 20:47:44 -0500
commit22c72aa842efa6d1dca3bb95eeb47122537ce12a (patch)
treea94cf15bd0d7748db0532f56ddefde1fda74a33d /youtube/watch.py
parent56ecd6cb1b461bd3622c669936050fa7e4d83542 (diff)
downloadyt-local-22c72aa842efa6d1dca3bb95eeb47122537ce12a.tar.lz
yt-local-22c72aa842efa6d1dca3bb95eeb47122537ce12a.tar.xz
yt-local-22c72aa842efa6d1dca3bb95eeb47122537ce12a.zip
remove yt-dlp, fix captions PO Token issue, fix 429 retry logic
- Remove yt-dlp entirely (modules, routes, settings, dependency) Was blocking page loads by running synchronously in gevent - Fix captions: use Android client caption URLs (no PO Token needed) instead of web timedtext URLs that YouTube now blocks - Fix 429 retry: fail immediately without Tor (same IP = pointless retry) Was causing ~27s delays with exponential backoff - Accept ytdlp_enabled as legacy setting to avoid warning on startup
Diffstat (limited to 'youtube/watch.py')
-rw-r--r--youtube/watch.py93
1 files changed, 44 insertions, 49 deletions
diff --git a/youtube/watch.py b/youtube/watch.py
index b76a462..2fbc1fc 100644
--- a/youtube/watch.py
+++ b/youtube/watch.py
@@ -180,8 +180,34 @@ def make_caption_src(info, lang, auto=False, trans_lang=None):
label += ' (Automatic)'
if trans_lang:
label += ' -> ' + trans_lang
+
+ # Try to use Android caption URL directly (no PO Token needed)
+ caption_url = None
+ for track in info.get('_android_caption_tracks', []):
+ track_lang = track.get('languageCode', '')
+ track_kind = track.get('kind', '')
+ if track_lang == lang and (
+ (auto and track_kind == 'asr') or
+ (not auto and track_kind != 'asr')
+ ):
+ caption_url = track.get('baseUrl')
+ break
+
+ if caption_url:
+ # Add format
+ if '&fmt=' in caption_url:
+ caption_url = re.sub(r'&fmt=[^&]*', '&fmt=vtt', caption_url)
+ else:
+ caption_url += '&fmt=vtt'
+ if trans_lang:
+ caption_url += '&tlang=' + trans_lang
+ url = util.prefix_url(caption_url)
+ else:
+ # Fallback to old method
+ url = util.prefix_url(yt_data_extract.get_caption_url(info, lang, 'vtt', auto, trans_lang))
+
return {
- 'url': util.prefix_url(yt_data_extract.get_caption_url(info, lang, 'vtt', auto, trans_lang)),
+ 'url': url,
'label': label,
'srclang': trans_lang[0:2] if trans_lang else lang[0:2],
'on': False,
@@ -387,6 +413,19 @@ def extract_info(video_id, use_invidious, playlist_id=None, index=None):
info = tasks[0].value or {}
player_response = tasks[1].value or {}
+ # Save android_vr caption tracks (no PO Token needed for these URLs)
+ if isinstance(player_response, str):
+ try:
+ pr_data = json.loads(player_response)
+ except Exception:
+ pr_data = {}
+ else:
+ pr_data = player_response or {}
+ android_caption_tracks = yt_data_extract.deep_get(
+ pr_data, 'captions', 'playerCaptionsTracklistRenderer',
+ 'captionTracks', default=[])
+ info['_android_caption_tracks'] = android_caption_tracks
+
yt_data_extract.update_with_new_urls(info, player_response)
# Fallback to 'ios' if no valid URLs are found
@@ -696,30 +735,6 @@ def get_watch_page(video_id=None):
pair_sources = source_info['pair_sources']
uni_idx, pair_idx = source_info['uni_idx'], source_info['pair_idx']
- # Extract audio tracks using yt-dlp for multi-language support
- audio_tracks = []
- try:
- from youtube import ytdlp_integration
- logger.info(f'Extracting audio tracks for video: {video_id}')
- ytdlp_info = ytdlp_integration.extract_video_info_ytdlp(video_id)
- audio_tracks = ytdlp_info.get('audio_tracks', [])
-
- if audio_tracks:
- logger.info(f'✓ Found {len(audio_tracks)} audio tracks:')
- for i, track in enumerate(audio_tracks[:10], 1): # Log first 10
- logger.info(f' [{i}] {track["language_name"]} ({track["language"]}) - '
- f'bitrate: {track.get("audio_bitrate", "N/A")}k, '
- f'codec: {track.get("acodec", "N/A")}, '
- f'format_id: {track.get("format_id", "N/A")}')
- if len(audio_tracks) > 10:
- logger.info(f' ... and {len(audio_tracks) - 10} more')
- else:
- logger.warning(f'No audio tracks found for video {video_id}')
-
- except Exception as e:
- logger.error(f'Failed to extract audio tracks: {e}', exc_info=True)
- audio_tracks = []
-
pair_quality = yt_data_extract.deep_get(pair_sources, pair_idx, 'quality')
uni_quality = yt_data_extract.deep_get(uni_sources, uni_idx, 'quality')
@@ -843,9 +858,7 @@ def get_watch_page(video_id=None):
'playlist': info['playlist'],
'related': info['related_videos'],
'playability_error': info['playability_error'],
- 'audio_tracks': audio_tracks,
},
- audio_tracks = audio_tracks,
font_family = youtube.font_choices[settings.font], # for embed page
**source_info,
using_pair_sources = using_pair_sources,
@@ -854,16 +867,13 @@ def get_watch_page(video_id=None):
@yt_app.route('/api/<path:dummy>')
def get_captions(dummy):
+ url = 'https://www.youtube.com' + request.full_path
try:
- result = util.fetch_url('https://www.youtube.com' + request.full_path)
+ result = util.fetch_url(url, headers=util.mobile_ua)
result = result.replace(b"align:start position:0%", b"")
- return result
- except util.FetchError as e:
- # Return empty captions gracefully instead of error page
- logger.warning(f'Failed to fetch captions: {e}')
- return flask.Response(b'WEBVTT\n\n', mimetype='text/vtt', status=200)
+ return flask.Response(result, mimetype='text/vtt')
except Exception as e:
- logger.error(f'Unexpected error fetching captions: {e}')
+ logger.debug(f'Caption fetch failed: {e}')
return flask.Response(b'WEBVTT\n\n', mimetype='text/vtt', status=200)
@@ -929,18 +939,3 @@ def get_transcript(caption_path):
return flask.Response(result.encode('utf-8'),
mimetype='text/plain;charset=UTF-8')
-
-
-# ============================================================================
-# yt-dlp Integration Routes
-# ============================================================================
-
-@yt_app.route('/ytl-api/video-with-audio/<video_id>')
-def proxy_video_with_audio(video_id):
- """
- Proxy para servir video con audio específico usando yt-dlp
- """
- from youtube import ytdlp_proxy
- audio_lang = request.args.get('lang', 'en')
- max_quality = int(request.args.get('quality', 720))
- return ytdlp_proxy.stream_video_with_audio(video_id, audio_lang, max_quality)