Skip to content

Commit b58045e

Browse files
committed
[YouTube] Fallback to playlist uploader on course playlists
Course playlists videos do not contain the uploader. This leads to an exception being thrown when trying to extract the uploader values. We can fallback to the playlist uploader values, as every video is uploaded by the playlist uploader. Closes: #1425
1 parent 20fc8f1 commit b58045e

6 files changed

Lines changed: 1150 additions & 2 deletions

File tree

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubePlaylistExtractor.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,8 +425,35 @@ private void collectStreamsFrom(@Nonnull final StreamInfoItemsCollector collecto
425425
.map(JsonObject.class::cast)
426426
.forEach(video -> {
427427
if (video.has(PLAYLIST_VIDEO_RENDERER)) {
428+
try {
429+
final String uploaderName = getUploaderName();
430+
final String uploaderUrl = getUploaderUrl();
431+
428432
collector.commit(new YoutubeStreamInfoItemExtractor(
429-
video.getObject(PLAYLIST_VIDEO_RENDERER), timeAgoParser));
433+
video.getObject(PLAYLIST_VIDEO_RENDERER), timeAgoParser) {
434+
@Override
435+
public String getUploaderName() throws ParsingException {
436+
try {
437+
return super.getUploaderName();
438+
} catch (final ParsingException e) {
439+
return uploaderName;
440+
}
441+
}
442+
443+
@Override
444+
public String getUploaderUrl() throws ParsingException {
445+
try {
446+
return super.getUploaderUrl();
447+
} catch (final ParsingException e) {
448+
return uploaderUrl;
449+
}
450+
}
451+
});
452+
} catch (final ParsingException e) {
453+
return;
454+
}
455+
456+
430457
} else if (video.has(RICH_ITEM_RENDERER)) {
431458
final JsonObject richItemRenderer = video.getObject(RICH_ITEM_RENDERER);
432459
if (richItemRenderer.has("content")) {

extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubePlaylistExtractorTest.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ public static class MembersOnlyTests implements InitYoutubeTest {
513513
void testOnlyMembersOnlyVideos() throws Exception {
514514
final YoutubePlaylistExtractor extractor = (YoutubePlaylistExtractor) YouTube
515515
.getPlaylistExtractor(
516-
// auto-generated playlist with only membersOnly videos
516+
// autogenerated playlist with only membersOnly videos
517517
"https://www.youtube.com/playlist?list=UUMOQuLXlFNAeDJMSmuzHU5axw");
518518
extractor.fetchPage();
519519

@@ -530,4 +530,23 @@ void testOnlyMembersOnlyVideos() throws Exception {
530530
assertTrue(membershipVideos.isEmpty());
531531
}
532532
}
533+
534+
public static class CoursePlaylistTest implements InitYoutubeTest {
535+
536+
@Test
537+
void uploaderName() throws Exception {
538+
final YoutubePlaylistExtractor extractor = (YoutubePlaylistExtractor) YouTube
539+
.getPlaylistExtractor(
540+
"https://www.youtube.com/playlist?list=PLWxziGKTUvQFIsbbFcTZz7jOT4TMGnZBh");
541+
extractor.fetchPage();
542+
543+
final List<StreamInfoItem> allItems = extractor.getInitialPage().getItems()
544+
.stream()
545+
.filter(StreamInfoItem.class::isInstance)
546+
.map(StreamInfoItem.class::cast)
547+
.collect(Collectors.toUnmodifiableList());
548+
assertEquals(14, allItems.size());
549+
assertEquals(extractor.getUploaderName(), allItems.get(0).getUploaderName());
550+
}
551+
}
533552
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
{
2+
"request": {
3+
"httpMethod": "GET",
4+
"url": "https://www.youtube.com/sw.js",
5+
"headers": {
6+
"Origin": [
7+
"https://www.youtube.com"
8+
],
9+
"Referer": [
10+
"https://www.youtube.com"
11+
],
12+
"Accept-Language": [
13+
"en-GB, en;q\u003d0.9"
14+
]
15+
},
16+
"localization": {
17+
"languageCode": "en",
18+
"countryCode": "GB"
19+
}
20+
},
21+
"response": {
22+
"responseCode": 200,
23+
"responseMessage": "",
24+
"responseHeaders": {
25+
"accept-ch": [
26+
"Sec-CH-Viewport-Width, Sec-CH-DPR, Device-Memory"
27+
],
28+
"access-control-allow-credentials": [
29+
"true"
30+
],
31+
"access-control-allow-origin": [
32+
"https://www.youtube.com"
33+
],
34+
"alt-svc": [
35+
"h3\u003d\":443\"; ma\u003d2592000,h3-29\u003d\":443\"; ma\u003d2592000"
36+
],
37+
"cache-control": [
38+
"private, max-age\u003d0"
39+
],
40+
"content-security-policy": [
41+
"script-src \u0027unsafe-eval\u0027 \u0027self\u0027 \u0027unsafe-inline\u0027 https://www.google.com https://apis.google.com https://ssl.gstatic.com https://www.gstatic.com https://www.googletagmanager.com https://www.google-analytics.com https://*.youtube.com https://*.google.com https://*.gstatic.com https://youtube.com https://www.youtube.com https://google.com https://*.doubleclick.net https://*.googleapis.com https://www.googleadservices.com https://tpc.googlesyndication.com https://www.youtubekids.com https://www.youtube-nocookie.com https://www.youtubeeducation.com https://www-onepick-opensocial.googleusercontent.com;report-uri https://csp.withgoogle.com/csp/youtube_main/allowlist",
42+
"require-trusted-types-for \u0027script\u0027"
43+
],
44+
"content-type": [
45+
"text/javascript; charset\u003dutf-8"
46+
],
47+
"cross-origin-opener-policy": [
48+
"same-origin; report-to\u003d\"youtube_main\""
49+
],
50+
"date": [
51+
"Mon, 05 Jan 2026 19:58:36 GMT"
52+
],
53+
"document-policy": [
54+
"include-js-call-stacks-in-crash-reports"
55+
],
56+
"expires": [
57+
"Mon, 05 Jan 2026 19:58:36 GMT"
58+
],
59+
"origin-trial": [
60+
"AmhMBR6zCLzDDxpW+HfpP67BqwIknWnyMOXOQGfzYswFmJe+fgaI6XZgAzcxOrzNtP7hEDsOo1jdjFnVr2IdxQ4AAAB4eyJvcmlnaW4iOiJodHRwczovL3lvdXR1YmUuY29tOjQ0MyIsImZlYXR1cmUiOiJXZWJWaWV3WFJlcXVlc3RlZFdpdGhEZXByZWNhdGlvbiIsImV4cGlyeSI6MTc1ODA2NzE5OSwiaXNTdWJkb21haW4iOnRydWV9",
61+
"AiDEBptUfVeO93q48VdVMe/ubupazdAl8AaHP+NBzdnW8quUcHdzJUyGSfrmtpKJu7EOvwRp9ug2rEo3XU+WMAMAAAB2eyJvcmlnaW4iOiJodHRwczovL3lvdXR1YmUuY29tOjQ0MyIsImZlYXR1cmUiOiJEZXZpY2VCb3VuZFNlc3Npb25DcmVkZW50aWFsczIiLCJleHBpcnkiOjE3NzQzMTA0MDAsImlzU3ViZG9tYWluIjp0cnVlfQ\u003d\u003d"
62+
],
63+
"p3p": [
64+
"CP\u003d\"This is not a P3P policy! See http://support.google.com/accounts/answer/151657?hl\u003den-GB for more info.\""
65+
],
66+
"permissions-policy": [
67+
"ch-ua-arch\u003d*, ch-ua-bitness\u003d*, ch-ua-full-version\u003d*, ch-ua-full-version-list\u003d*, ch-ua-model\u003d*, ch-ua-wow64\u003d*, ch-ua-form-factors\u003d*, ch-ua-platform\u003d*, ch-ua-platform-version\u003d*"
68+
],
69+
"report-to": [
70+
"{\"group\":\"youtube_main\",\"max_age\":2592000,\"endpoints\":[{\"url\":\"https://csp.withgoogle.com/csp/report-to/youtube_main\"}]}"
71+
],
72+
"reporting-endpoints": [
73+
"default\u003d\"/web-reports?context\u003deJwNzHtozXEcxnE_77DOOTvn_H7fz9dtCoclcdbZMlLmbiSH5ZLZaLPZ3GazOecYoYRYSYkwc1thMkz-ENaMTMvkOsslQ3JJlmtYIb5_vP54np4e17VuK_6tsgZPK7bObyy1Fg4ps6bcW2PtTI9YP09GrLP1ESvcFrU6BsSs7EDMOtAWs4pelltH_Jldy_tmdq3PclFY6OJHpYuvz12Uf3aRPcxN63Q3HTPdrFzo5mKNm_oLbtrfu_mS62FE1MOHjHgaKuMZPtfLqcteMpq97DY-_fWycbSPkWk-YgU-3DEfByt8JO_3EVfto1eLj4bBfuJy_biMh1V-nv_ys0jZ7Eq1OTTPpj3HJrzUZsMGm63GxF02Y_fZNJ62SThnox7Z9Ptg87rT5lm6w7_pDjWzHcLZDu9KHG40OqTddRjz0CHxsYP_qUPorUPvToe4HgpHFOEERW1_hQooapIUwVRF4XhFdJai06jKUkzKVrhKFHuMF6WKbWUmb1FM2K5YX6O4XqvgjOKWkX9JkXtFcafZ9LcV6U8U64y6p4rEj4q93xQjvitKfyv-GHiEzV4ho5cwb7jQkS58Mr4a8VMFKyzMMAJLhNplQnWRkBcR_kTNvlLYZFw1tlUJ544J948Lrca-M8LAeiGrQVjUKPy6LuTfENqbhKabwsdmIbNFmHxXqDAu3zfdA6NV2NEmrHomnDQqXgpD35gPw34rpH0Xcn4Ii43DXTTjuml-ezQT4zUJfs0CR1NnbFKaNtFM7qkZ1Fvj9NEcNe4laKYENHmJmuqQ5uoozYk5mjnzzcYT11H3paW7_9WbplorEFxXEo1E8wqS1hbkBQvLSoojwYLiJcH8suWR5fmLi3JSQimpySkpyUnJoZzVof8Zrtt2\""
74+
],
75+
"server": [
76+
"ESF"
77+
],
78+
"set-cookie": [
79+
"YSC\u003d53hT5wtfQ8Q; Domain\u003d.youtube.com; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
80+
"VISITOR_INFO1_LIVE\u003d; Domain\u003d.youtube.com; Expires\u003dTue, 11-Apr-2023 19:58:36 GMT; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone"
81+
],
82+
"strict-transport-security": [
83+
"max-age\u003d31536000"
84+
],
85+
"vary": [
86+
"Sec-CH-Viewport-Width, Sec-CH-DPR, Device-Memory"
87+
],
88+
"x-content-type-options": [
89+
"nosniff"
90+
],
91+
"x-frame-options": [
92+
"SAMEORIGIN"
93+
],
94+
"x-xss-protection": [
95+
"0"
96+
]
97+
},
98+
"responseBody": "\n self.addEventListener(\u0027install\u0027, event \u003d\u003e {\n event.waitUntil(self.skipWaiting());\n });\n self.addEventListener(\u0027activate\u0027, event \u003d\u003e {\n event.waitUntil(\n self.clients.claim().then(() \u003d\u003e self.registration.unregister()));\n });\n ",
99+
"latestUrl": "https://www.youtube.com/sw.js"
100+
}
101+
}

extractor/src/test/resources/mocks/v1/org/schabi/newpipe/extractor/services/youtube/youtubeplaylistextractor/courseplaylist/generated_mock_1.json

Lines changed: 97 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)