Skip to content

Commit 9aca710

Browse files
authored
Merge pull request #1013 from Stypox/fix-music-mixes
[YouTube] Now music mixes can be treated as normal mixes
2 parents 76eeaba + 5945057 commit 9aca710

10 files changed

Lines changed: 2279 additions & 16 deletions

File tree

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -388,8 +388,7 @@ public static OffsetDateTime parseDateFrom(final String textualUploadDate)
388388
* @return Whether given id belongs to a YouTube Mix
389389
*/
390390
public static boolean isYoutubeMixId(@Nonnull final String playlistId) {
391-
return playlistId.startsWith("RD")
392-
&& !isYoutubeMusicMixId(playlistId);
391+
return playlistId.startsWith("RD");
393392
}
394393

395394
/**

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,7 @@ public ChannelExtractor getChannelExtractor(final ListLinkHandler linkHandler) {
110110

111111
@Override
112112
public PlaylistExtractor getPlaylistExtractor(final ListLinkHandler linkHandler) {
113-
if (YoutubeParsingHelper.isYoutubeMixId(linkHandler.getId())
114-
&& !YoutubeParsingHelper.isYoutubeMusicMixId(linkHandler.getId())) {
113+
if (YoutubeParsingHelper.isYoutubeMixId(linkHandler.getId())) {
115114
return new YoutubeMixPlaylistExtractor(this, linkHandler);
116115
} else {
117116
return new YoutubePlaylistExtractor(this, linkHandler);

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

Lines changed: 92 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,22 @@
2323
import org.schabi.newpipe.extractor.Page;
2424
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
2525
import org.schabi.newpipe.extractor.exceptions.ParsingException;
26+
import org.schabi.newpipe.extractor.playlist.PlaylistExtractor;
2627
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
2728
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeMixPlaylistExtractor;
2829
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
2930

3031
import java.io.IOException;
3132
import java.nio.charset.StandardCharsets;
32-
import java.util.HashMap;
3333
import java.util.HashSet;
3434
import java.util.Map;
3535
import java.util.Set;
3636

37+
@SuppressWarnings({"MismatchedQueryAndUpdateOfCollection", "NewClassNamingConvention"})
3738
public class YoutubeMixPlaylistExtractorTest {
3839

3940
private static final String RESOURCE_PATH = DownloaderFactory.RESOURCE_PATH + "services/youtube/extractor/mix/";
40-
private static final Map<String, String> dummyCookie = new HashMap<>();
41-
41+
private static final Map<String, String> dummyCookie = Map.of(YoutubeMixPlaylistExtractor.COOKIE_NAME, "whatever");
4242
private static YoutubeMixPlaylistExtractor extractor;
4343

4444
public static class Mix {
@@ -50,7 +50,6 @@ public static void setUp() throws Exception {
5050
YoutubeTestsUtils.ensureStateless();
5151
YoutubeParsingHelper.setConsentAccepted(true);
5252
NewPipe.init(DownloaderFactory.getDownloader(RESOURCE_PATH + "mix"));
53-
dummyCookie.put(YoutubeMixPlaylistExtractor.COOKIE_NAME, "whatever");
5453
extractor = (YoutubeMixPlaylistExtractor) YouTube
5554
.getPlaylistExtractor("https://www.youtube.com/watch?v=" + VIDEO_ID
5655
+ "&list=RD" + VIDEO_ID);
@@ -135,7 +134,6 @@ void getPlaylistType() throws ParsingException {
135134
}
136135

137136
public static class MixWithIndex {
138-
139137
private static final String VIDEO_ID = "FAqYW76GLPA";
140138
private static final String VIDEO_TITLE = "Mix – ";
141139
private static final int INDEX = 7; // YT starts the index with 1...
@@ -146,7 +144,6 @@ public static void setUp() throws Exception {
146144
YoutubeTestsUtils.ensureStateless();
147145
YoutubeParsingHelper.setConsentAccepted(true);
148146
NewPipe.init(DownloaderFactory.getDownloader(RESOURCE_PATH + "mixWithIndex"));
149-
dummyCookie.put(YoutubeMixPlaylistExtractor.COOKIE_NAME, "whatever");
150147
extractor = (YoutubeMixPlaylistExtractor) YouTube
151148
.getPlaylistExtractor("https://www.youtube.com/watch?v=" + VIDEO_ID_AT_INDEX
152149
+ "&list=RD" + VIDEO_ID + "&index=" + INDEX);
@@ -233,7 +230,6 @@ public static void setUp() throws Exception {
233230
YoutubeTestsUtils.ensureStateless();
234231
YoutubeParsingHelper.setConsentAccepted(true);
235232
NewPipe.init(DownloaderFactory.getDownloader(RESOURCE_PATH + "myMix"));
236-
dummyCookie.put(YoutubeMixPlaylistExtractor.COOKIE_NAME, "whatever");
237233
extractor = (YoutubeMixPlaylistExtractor) YouTube
238234
.getPlaylistExtractor("https://www.youtube.com/watch?v=" + VIDEO_ID
239235
+ "&list=RDMM" + VIDEO_ID);
@@ -316,15 +312,13 @@ void getPlaylistType() throws ParsingException {
316312
}
317313

318314
public static class Invalid {
319-
320315
private static final String VIDEO_ID = "QMVCAPd5cwBcg";
321316

322317
@BeforeAll
323318
public static void setUp() throws IOException {
324319
YoutubeTestsUtils.ensureStateless();
325320
YoutubeParsingHelper.setConsentAccepted(true);
326321
NewPipe.init(DownloaderFactory.getDownloader(RESOURCE_PATH + "invalid"));
327-
dummyCookie.put(YoutubeMixPlaylistExtractor.COOKIE_NAME, "whatever");
328322
}
329323

330324
@Test
@@ -348,7 +342,6 @@ void invalidVideoId() throws Exception {
348342
}
349343

350344
public static class ChannelMix {
351-
352345
private static final String CHANNEL_ID = "UCXuqSBlHAE6Xw-yeJA0Tunw";
353346
private static final String VIDEO_ID_OF_CHANNEL = "mnk6gnOBYIo";
354347
private static final String CHANNEL_TITLE = "Linus Tech Tips";
@@ -359,7 +352,6 @@ public static void setUp() throws Exception {
359352
YoutubeTestsUtils.ensureStateless();
360353
YoutubeParsingHelper.setConsentAccepted(true);
361354
NewPipe.init(DownloaderFactory.getDownloader(RESOURCE_PATH + "channelMix"));
362-
dummyCookie.put(YoutubeMixPlaylistExtractor.COOKIE_NAME, "whatever");
363355
extractor = (YoutubeMixPlaylistExtractor) YouTube
364356
.getPlaylistExtractor("https://www.youtube.com/watch?v=" + VIDEO_ID_OF_CHANNEL
365357
+ "&list=RDCM" + CHANNEL_ID);
@@ -424,7 +416,6 @@ public static void setUp() throws Exception {
424416
YoutubeTestsUtils.ensureStateless();
425417
YoutubeParsingHelper.setConsentAccepted(true);
426418
NewPipe.init(DownloaderFactory.getDownloader(RESOURCE_PATH + "genreMix"));
427-
dummyCookie.put(YoutubeMixPlaylistExtractor.COOKIE_NAME, "whatever");
428419
extractor = (YoutubeMixPlaylistExtractor) YouTube
429420
.getPlaylistExtractor("https://www.youtube.com/watch?v=" + VIDEO_ID
430421
+ "&list=RDGMEMYH9CUrFO7CfLJpaD7UR85w");
@@ -504,4 +495,93 @@ void getPlaylistType() throws ParsingException {
504495
assertEquals(PlaylistInfo.PlaylistType.MIX_GENRE, extractor.getPlaylistType());
505496
}
506497
}
498+
499+
public static class Music {
500+
private static final String VIDEO_ID = "dQw4w9WgXcQ";
501+
private static final String MIX_TITLE = "Mix – Rick Astley - Never Gonna Give You Up (Official Music Video)";
502+
503+
@BeforeAll
504+
public static void setUp() throws Exception {
505+
YoutubeTestsUtils.ensureStateless();
506+
YoutubeParsingHelper.setConsentAccepted(true);
507+
NewPipe.init(DownloaderFactory.getDownloader(RESOURCE_PATH + "musicMix"));
508+
extractor = (YoutubeMixPlaylistExtractor)
509+
YouTube.getPlaylistExtractor("https://m.youtube.com/watch?v=" + VIDEO_ID
510+
+ "&list=RDAMVM" + VIDEO_ID);
511+
extractor.fetchPage();
512+
}
513+
514+
@Test
515+
void getServiceId() {
516+
assertEquals(YouTube.getServiceId(), extractor.getServiceId());
517+
}
518+
519+
@Test
520+
void getName() throws Exception {
521+
assertEquals(MIX_TITLE, extractor.getName());
522+
}
523+
524+
@Test
525+
void getThumbnailUrl() throws Exception {
526+
final String thumbnailUrl = extractor.getThumbnailUrl();
527+
assertIsSecureUrl(thumbnailUrl);
528+
ExtractorAsserts.assertContains("yt", thumbnailUrl);
529+
ExtractorAsserts.assertContains(VIDEO_ID, thumbnailUrl);
530+
}
531+
532+
@Test
533+
void getInitialPage() throws Exception {
534+
final InfoItemsPage<StreamInfoItem> streams = extractor.getInitialPage();
535+
assertFalse(streams.getItems().isEmpty());
536+
assertTrue(streams.hasNextPage());
537+
}
538+
539+
@Test
540+
void getPage() throws Exception {
541+
final byte[] body = JsonWriter.string(prepareDesktopJsonBuilder(
542+
NewPipe.getPreferredLocalization(), NewPipe.getPreferredContentCountry())
543+
.value("videoId", VIDEO_ID)
544+
.value("playlistId", "RD" + VIDEO_ID)
545+
.value("params", "OAE%3D")
546+
.done())
547+
.getBytes(StandardCharsets.UTF_8);
548+
549+
final InfoItemsPage<StreamInfoItem> streams = extractor.getPage(new Page(
550+
YOUTUBEI_V1_URL + "next?key=" + getKey(), null, null, dummyCookie, body));
551+
assertFalse(streams.getItems().isEmpty());
552+
assertTrue(streams.hasNextPage());
553+
}
554+
555+
@Test
556+
void getContinuations() throws Exception {
557+
InfoItemsPage<StreamInfoItem> streams = extractor.getInitialPage();
558+
final Set<String> urls = new HashSet<>();
559+
560+
// Should work infinitely, but for testing purposes only 3 times
561+
for (int i = 0; i < 3; i++) {
562+
assertTrue(streams.hasNextPage());
563+
assertFalse(streams.getItems().isEmpty());
564+
565+
for (final StreamInfoItem item : streams.getItems()) {
566+
// TODO Duplicates are appearing
567+
// assertFalse(urls.contains(item.getUrl()));
568+
urls.add(item.getUrl());
569+
}
570+
571+
streams = extractor.getPage(streams.getNextPage());
572+
}
573+
assertTrue(streams.hasNextPage());
574+
assertFalse(streams.getItems().isEmpty());
575+
}
576+
577+
@Test
578+
void getStreamCount() throws ParsingException {
579+
assertEquals(ListExtractor.ITEM_COUNT_INFINITE, extractor.getStreamCount());
580+
}
581+
582+
@Test
583+
void getPlaylistType() throws ParsingException {
584+
assertEquals(PlaylistInfo.PlaylistType.MIX_MUSIC, extractor.getPlaylistType());
585+
}
586+
}
507587
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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+
"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,h3-Q050\u003d\":443\"; ma\u003d2592000,h3-Q046\u003d\":443\"; ma\u003d2592000,h3-Q043\u003d\":443\"; ma\u003d2592000,quic\u003d\":443\"; ma\u003d2592000; v\u003d\"46,43\""
33+
],
34+
"cache-control": [
35+
"private, max-age\u003d0"
36+
],
37+
"content-type": [
38+
"text/javascript; charset\u003dutf-8"
39+
],
40+
"cross-origin-opener-policy-report-only": [
41+
"same-origin; report-to\u003d\"youtube_main\""
42+
],
43+
"date": [
44+
"Sun, 15 Jan 2023 22:30:06 GMT"
45+
],
46+
"expires": [
47+
"Sun, 15 Jan 2023 22:30:06 GMT"
48+
],
49+
"p3p": [
50+
"CP\u003d\"This is not a P3P policy! See http://support.google.com/accounts/answer/151657?hl\u003den-GB for more info.\""
51+
],
52+
"permissions-policy": [
53+
"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-platform\u003d*, ch-ua-platform-version\u003d*"
54+
],
55+
"report-to": [
56+
"{\"group\":\"youtube_main\",\"max_age\":2592000,\"endpoints\":[{\"url\":\"https://csp.withgoogle.com/csp/report-to/youtube_main\"}]}"
57+
],
58+
"server": [
59+
"ESF"
60+
],
61+
"set-cookie": [
62+
"YSC\u003d8TYEubKDjFA; Domain\u003d.youtube.com; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
63+
"VISITOR_INFO1_LIVE\u003d; Domain\u003d.youtube.com; Expires\u003dMon, 20-Apr-2020 22:30:06 GMT; Path\u003d/; Secure; HttpOnly; SameSite\u003dnone",
64+
"CONSENT\u003dPENDING+835; expires\u003dTue, 14-Jan-2025 22:30:06 GMT; path\u003d/; domain\u003d.youtube.com; Secure"
65+
],
66+
"strict-transport-security": [
67+
"max-age\u003d31536000"
68+
],
69+
"x-content-type-options": [
70+
"nosniff"
71+
],
72+
"x-frame-options": [
73+
"SAMEORIGIN"
74+
],
75+
"x-xss-protection": [
76+
"0"
77+
]
78+
},
79+
"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 ",
80+
"latestUrl": "https://www.youtube.com/sw.js"
81+
}
82+
}

extractor/src/test/resources/org/schabi/newpipe/extractor/services/youtube/extractor/mix/musicMix/generated_mock_1.json

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

0 commit comments

Comments
 (0)