aboutsummaryrefslogtreecommitdiffstats
path: root/youtube/playlist.py
diff options
context:
space:
mode:
authorAstounds <kirito@disroot.org>2026-03-31 21:38:51 -0500
committerAstounds <kirito@disroot.org>2026-03-31 21:38:51 -0500
commit06051dd127fd9805442ee10b569f36a611dbbc8e (patch)
treeaf6c8c40fb4189d7ef4748a1b47b14959cc7def1 /youtube/playlist.py
parent7c64630be1fd781f5964799da04d43cf191c61c3 (diff)
downloadyt-local-06051dd127fd9805442ee10b569f36a611dbbc8e.tar.lz
yt-local-06051dd127fd9805442ee10b569f36a611dbbc8e.tar.xz
yt-local-06051dd127fd9805442ee10b569f36a611dbbc8e.zip
fix: support YouTube 2024+ data formats for playlists, podcasts and channels
- Add PODCAST content type support in lockupViewModel extraction - Extract thumbnails and episode count from thumbnail overlay badges - Migrate playlist page fetching from pbj=1 to innertube API (youtubei/v1/browse) - Support new pageHeaderRenderer format in playlist metadata extraction - Fix subscriber count extraction when YouTube returns handle instead of count - Hide "None subscribers" in template when data is unavailable
Diffstat (limited to 'youtube/playlist.py')
-rw-r--r--youtube/playlist.py80
1 files changed, 48 insertions, 32 deletions
diff --git a/youtube/playlist.py b/youtube/playlist.py
index c7e0410..e5a03cd 100644
--- a/youtube/playlist.py
+++ b/youtube/playlist.py
@@ -30,42 +30,58 @@ def playlist_ctoken(playlist_id, offset, include_shorts=True):
def playlist_first_page(playlist_id, report_text="Retrieved playlist",
use_mobile=False):
- if use_mobile:
- url = 'https://m.youtube.com/playlist?list=' + playlist_id + '&pbj=1'
- content = util.fetch_url(
- url, util.mobile_xhr_headers,
- report_text=report_text, debug_name='playlist_first_page'
- )
- content = json.loads(content.decode('utf-8'))
- else:
- url = 'https://www.youtube.com/playlist?list=' + playlist_id + '&pbj=1'
- content = util.fetch_url(
- url, util.desktop_xhr_headers,
- report_text=report_text, debug_name='playlist_first_page'
- )
- content = json.loads(content.decode('utf-8'))
-
- return content
+ # Use innertube API (pbj=1 no longer works for many playlists)
+ key = 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8'
+ url = 'https://www.youtube.com/youtubei/v1/browse?key=' + key
+
+ data = {
+ 'context': {
+ 'client': {
+ 'hl': 'en',
+ 'gl': 'US',
+ 'clientName': 'WEB',
+ 'clientVersion': '2.20240327.00.00',
+ },
+ },
+ 'browseId': 'VL' + playlist_id,
+ }
+
+ content_type_header = (('Content-Type', 'application/json'),)
+ content = util.fetch_url(
+ url, util.desktop_xhr_headers + content_type_header,
+ data=json.dumps(data),
+ report_text=report_text, debug_name='playlist_first_page'
+ )
+ return json.loads(content.decode('utf-8'))
def get_videos(playlist_id, page, include_shorts=True, use_mobile=False,
report_text='Retrieved playlist'):
- # mobile requests return 20 videos per page
- if use_mobile:
- page_size = 20
- headers = util.mobile_xhr_headers
- # desktop requests return 100 videos per page
- else:
- page_size = 100
- headers = util.desktop_xhr_headers
-
- url = "https://m.youtube.com/playlist?ctoken="
- url += playlist_ctoken(playlist_id, (int(page)-1)*page_size,
- include_shorts=include_shorts)
- url += "&pbj=1"
+ page_size = 100
+
+ key = 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8'
+ url = 'https://www.youtube.com/youtubei/v1/browse?key=' + key
+
+ ctoken = playlist_ctoken(playlist_id, (int(page)-1)*page_size,
+ include_shorts=include_shorts)
+
+ data = {
+ 'context': {
+ 'client': {
+ 'hl': 'en',
+ 'gl': 'US',
+ 'clientName': 'WEB',
+ 'clientVersion': '2.20240327.00.00',
+ },
+ },
+ 'continuation': ctoken,
+ }
+
+ content_type_header = (('Content-Type', 'application/json'),)
content = util.fetch_url(
- url, headers, report_text=report_text,
- debug_name='playlist_videos'
+ url, util.desktop_xhr_headers + content_type_header,
+ data=json.dumps(data),
+ report_text=report_text, debug_name='playlist_videos'
)
info = json.loads(content.decode('utf-8'))
@@ -96,7 +112,7 @@ def get_playlist_page():
tasks = (
gevent.spawn(
playlist_first_page, playlist_id,
- report_text="Retrieved playlist info", use_mobile=True
+ report_text="Retrieved playlist info"
),
gevent.spawn(get_videos, playlist_id, page)
)