Skip to content

Commit 56e41f2

Browse files
committed
[YouTube] Extract uploader URL for videos with multiple uploaders
Fixes an issue, where for videos with multiple uploaders/collaborators, the uploader URL could not be extracted, leading to the entire video failing to be extracted. This is fixed by extracting the URL of the first collaborator, which appears to be the main channel where the video is actually displayed. Closes: #1333
1 parent 4368f2b commit 56e41f2

File tree

5 files changed

+647
-0
lines changed

5 files changed

+647
-0
lines changed

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeParsingHelper.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,22 @@ public static String getUrlFromNavigationEndpoint(
759759
.getString("playlistId");
760760
}
761761

762+
if (navigationEndpoint.has("showDialogCommand")) {
763+
try {
764+
final JsonArray listItems = JsonUtils.getArray(navigationEndpoint,
765+
"showDialogCommand.panelLoadingStrategy.inlineContent.dialogViewModel"
766+
+ ".customContent.listViewModel.listItems");
767+
768+
// the first item seems to always be the channel that actually uploaded the video,
769+
// i.e. it appears in their video feed
770+
final JsonObject command = JsonUtils.getObject(listItems.getObject(0),
771+
"listItemViewModel.rendererContext.commandContext.onTap.innertubeCommand");
772+
return getUrlFromNavigationEndpoint(command);
773+
} catch (final ParsingException p) {
774+
}
775+
}
776+
777+
762778
if (navigationEndpoint.has("commandMetadata")) {
763779
final JsonObject metadata = navigationEndpoint.getObject("commandMetadata")
764780
.getObject("webCommandMetadata");

extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/search/YoutubeSearchExtractorTest.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,56 @@ public void testSearchSuggestion() throws Exception {
335335
}
336336
}
337337

338+
public static class MultipleUploader extends DefaultSearchExtractorTest implements InitYoutubeTest {
339+
private static final String QUERY = "Nxk6aRHi664";
340+
341+
@Override
342+
protected SearchExtractor createExtractor() throws Exception {
343+
return YouTube.getSearchExtractor(QUERY, singletonList(VIDEOS), "");
344+
}
345+
346+
@Override public StreamingService expectedService() { return YouTube; }
347+
@Override public String expectedName() { return QUERY; }
348+
@Override public String expectedId() { return QUERY; }
349+
@Override public String expectedUrlContains() { return "youtube.com/results?search_query=" + QUERY; }
350+
@Override public String expectedOriginalUrlContains() { return "youtube.com/results?search_query=" + QUERY; }
351+
@Override public String expectedSearchString() { return QUERY; }
352+
@Nullable @Override public String expectedSearchSuggestion() { return null; }
353+
@Override public InfoItem.InfoType expectedInfoItemType() { return InfoItem.InfoType.STREAM; }
354+
355+
@Test
356+
void testUploaderName() throws IOException, ExtractionException {
357+
final List<InfoItem> items = extractor().getInitialPage().getItems();
358+
assertEquals("Le Vortex - ARTE and Thomas Gauthier",
359+
((StreamInfoItem) items.get(0)).getUploaderName());
360+
}
361+
362+
@Test
363+
void testUploaderUrl() throws IOException, ExtractionException {
364+
final List<InfoItem> items = extractor().getInitialPage().getItems();
365+
assertEquals("https://www.youtube.com/channel/UCZxLew-WXWm5dhRZBgEFl-Q",
366+
((StreamInfoItem) items.get(0)).getUploaderUrl());
367+
}
368+
@Test
369+
void testUploaderAvatars() throws IOException, ExtractionException {
370+
final List<InfoItem> items = extractor().getInitialPage().getItems();
371+
assertNotNull(((StreamInfoItem) items.get(0)).getUploaderAvatars());
372+
}
373+
374+
@Disabled("Irrelevant - sometimes suggestions show up, sometimes not")
375+
@Override
376+
public void testSearchSuggestion() throws Exception {
377+
super.testSearchSuggestion();
378+
}
379+
380+
@Disabled("Irrelevant - sometimes suggestions show up, sometimes not")
381+
@Override
382+
public void testMoreRelatedItems() throws Exception {
383+
super.testMoreRelatedItems();
384+
}
385+
}
386+
387+
338388
public static class ShortFormContent extends DefaultSearchExtractorTest implements InitYoutubeTest {
339389
private static final String QUERY = "#shorts";
340390

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
{
2+
"request": {
3+
"httpMethod": "GET",
4+
"url": "https://www.youtube.com/sw.js",
5+
"headers": {
6+
"Referer": [
7+
"https://www.youtube.com"
8+
],
9+
"Origin": [
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+
"access-control-allow-credentials": [
26+
"true"
27+
],
28+
"access-control-allow-origin": [
29+
"https://www.youtube.com"
30+
],
31+
"alt-svc": [
32+
"h3\u003d\":443\"; ma\u003d2592000,h3-29\u003d\":443\"; ma\u003d2592000"
33+
],
34+
"cache-control": [
35+
"private, max-age\u003d0"
36+
],
37+
"content-security-policy": [
38+
"require-trusted-types-for \u0027script\u0027"
39+
],
40+
"content-security-policy-report-only": [
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 /cspreport/allowlist"
42+
],
43+
"content-type": [
44+
"text/javascript; charset\u003dutf-8"
45+
],
46+
"cross-origin-opener-policy": [
47+
"same-origin; report-to\u003d\"youtube_main\""
48+
],
49+
"date": [
50+
"Fri, 31 Oct 2025 15:26:13 GMT"
51+
],
52+
"document-policy": [
53+
"include-js-call-stacks-in-crash-reports"
54+
],
55+
"expires": [
56+
"Fri, 31 Oct 2025 15:26:13 GMT"
57+
],
58+
"origin-trial": [
59+
"AmhMBR6zCLzDDxpW+HfpP67BqwIknWnyMOXOQGfzYswFmJe+fgaI6XZgAzcxOrzNtP7hEDsOo1jdjFnVr2IdxQ4AAAB4eyJvcmlnaW4iOiJodHRwczovL3lvdXR1YmUuY29tOjQ0MyIsImZlYXR1cmUiOiJXZWJWaWV3WFJlcXVlc3RlZFdpdGhEZXByZWNhdGlvbiIsImV4cGlyeSI6MTc1ODA2NzE5OSwiaXNTdWJkb21haW4iOnRydWV9",
60+
"AiDEBptUfVeO93q48VdVMe/ubupazdAl8AaHP+NBzdnW8quUcHdzJUyGSfrmtpKJu7EOvwRp9ug2rEo3XU+WMAMAAAB2eyJvcmlnaW4iOiJodHRwczovL3lvdXR1YmUuY29tOjQ0MyIsImZlYXR1cmUiOiJEZXZpY2VCb3VuZFNlc3Npb25DcmVkZW50aWFsczIiLCJleHBpcnkiOjE3NzQzMTA0MDAsImlzU3ViZG9tYWluIjp0cnVlfQ\u003d\u003d"
61+
],
62+
"p3p": [
63+
"CP\u003d\"This is not a P3P policy! See http://support.google.com/accounts/answer/151657?hl\u003den-GB for more info.\""
64+
],
65+
"permissions-policy": [
66+
"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*"
67+
],
68+
"report-to": [
69+
"{\"group\":\"youtube_main\",\"max_age\":2592000,\"endpoints\":[{\"url\":\"https://csp.withgoogle.com/csp/report-to/youtube_main\"}]}"
70+
],
71+
"reporting-endpoints": [
72+
"default\u003d\"/web-reports?context\u003deJwNzmtsjGkYxnFv_3Z0Z6bzzvvcjxQlbLU2sabaJiWOH0irS4Zu4jw2bTEtQquzM0MtiU1JEMdYp9o6bHZV1CllJXRtgw_OuqF0lwqpD9IQrETVsft8-H24k_u6crmbvyjuWmKl5ZdZrm-XWidXVVhzvo5YuU0_WBvzolbHoah1rCFqPR8Qt0KpcWv33bi1-NFya59_ZsLyPjMTGma7KSlx82aPm_9a3VS-dBP6xsOdiR6eT_ZwptZDQ72Hh089xEJeXhV5yYl5Ke_w0l6QxPk9SQyZ6uPwWR8Fl31sM1588pGWZbNqhM2wMTbHJ9jEwzaeuE31epusXTaJB22Sr9msb7M5n-YnsciP27i910-PE35a3_oZ9d7P9-JQlOPwyzSHh4UOwVKHlSsd1hiT1jqM2-wwdqdD4xGHAXUOKccdpNmhb7tDW6eD70tF_lDFipGK-3mKromKYEjRK66wVivUTsWCWsUiY4VRZVxsVIy5pUhpUoy-rfjqriK9RVH_RJHcYbKdig2fFS0uodkjDO0pBPsYKUJdf0FShfx0Yb5RmyEEcoR_RwkV44XioBCbInQae2cL40PCwLDJlQrucmG78ahCWBsxd5VQsk6YtUPI2CX8US2sqRF8B4Sag8KWX4V-v4vZLVyoExKOCleNP08Jj88KReeEk5eE6BVh_1Xzc134-4bw3U3T3SxYLULeP0KlkfzAbDbuGaWtQrfHJtMmbHoqpD8Tfn4tHO0QkjqFd0buO-Evo91oei98_GA2fhQiXcL07hq8mp98mqijuag1h3trXAM1wUGaQ4M104do6odrto3VVOVqTk_WVM_QvC3U_JjSzaW8iTuObL_i8m9t3PSblRqoLI9FY3PDGcvCcwMlkfKyaCBcNj8wL7IwunBe8eLC7MzsnKzM7BEZWZmFSzP_B5923l0\""
73+
],
74+
"server": [
75+
"ESF"
76+
],
77+
"set-cookie": [
78+
"YSC\u003dEPX_t6VvDa8; Domain\u003d.youtube.com; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
79+
"VISITOR_INFO1_LIVE\u003d; Domain\u003d.youtube.com; Expires\u003dSat, 04-Feb-2023 15:26:13 GMT; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone"
80+
],
81+
"strict-transport-security": [
82+
"max-age\u003d31536000"
83+
],
84+
"x-content-type-options": [
85+
"nosniff"
86+
],
87+
"x-frame-options": [
88+
"SAMEORIGIN"
89+
],
90+
"x-xss-protection": [
91+
"0"
92+
]
93+
},
94+
"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 ",
95+
"latestUrl": "https://www.youtube.com/sw.js"
96+
}
97+
}

extractor/src/test/resources/mocks/v1/org/schabi/newpipe/extractor/services/youtube/search/youtubesearchextractor/multipleuploader/generated_mock_1.json

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

0 commit comments

Comments
 (0)