Skip to content

Commit f852228

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 4701a17 commit f852228

6 files changed

Lines changed: 1142 additions & 2 deletions

File tree

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,8 +425,27 @@ private void collectStreamsFrom(@Nonnull final StreamInfoItemsCollector collecto
425425
.map(JsonObject.class::cast)
426426
.forEach(video -> {
427427
if (video.has(PLAYLIST_VIDEO_RENDERER)) {
428+
final PlaylistExtractor playlistExtractor = this;
428429
collector.commit(new YoutubeStreamInfoItemExtractor(
429-
video.getObject(PLAYLIST_VIDEO_RENDERER), timeAgoParser));
430+
video.getObject(PLAYLIST_VIDEO_RENDERER), timeAgoParser) {
431+
@Override
432+
public String getUploaderName() throws ParsingException {
433+
try {
434+
return super.getUploaderName();
435+
} catch (final ParsingException e) {
436+
return playlistExtractor.getUploaderName();
437+
}
438+
}
439+
440+
@Override
441+
public String getUploaderUrl() throws ParsingException {
442+
try {
443+
return super.getUploaderUrl();
444+
} catch (final ParsingException e) {
445+
return playlistExtractor.getUploaderUrl();
446+
}
447+
}
448+
});
430449
} else if (video.has(RICH_ITEM_RENDERER)) {
431450
final JsonObject richItemRenderer = video.getObject(RICH_ITEM_RENDERER);
432451
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)