Skip to content

Commit 50db871

Browse files
committed
[YouTube] Extract mixes from streams related items
1 parent 638da17 commit 50db871

5 files changed

Lines changed: 122 additions & 19 deletions

File tree

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,17 @@ public static boolean isYoutubeMixId(@Nonnull final String playlistId) {
249249
return playlistId.startsWith("RD") && !isYoutubeMusicMixId(playlistId);
250250
}
251251

252+
/**
253+
* Checks if the given playlist id is a YouTube My Mix (auto-generated playlist)
254+
* Ids from a YouTube My Mix start with "RDMM"
255+
*
256+
* @param playlistId the playlist id
257+
* @return Whether given id belongs to a YouTube My Mix
258+
*/
259+
public static boolean isYoutubeMyMixId(@Nonnull final String playlistId) {
260+
return playlistId.startsWith("RDMM");
261+
}
262+
252263
/**
253264
* Checks if the given playlist id is a YouTube Music Mix (auto-generated playlist)
254265
* Ids from a YouTube Music Mix start with "RDAMVM" or "RDCLAK"
@@ -278,7 +289,7 @@ public static boolean isYoutubeChannelMixId(@Nonnull final String playlistId) {
278289
@Nonnull
279290
public static String extractVideoIdFromMixId(@Nonnull final String playlistId)
280291
throws ParsingException {
281-
if (playlistId.startsWith("RDMM")) { // My Mix
292+
if (isYoutubeMyMixId(playlistId)) { // My Mix
282293
return playlistId.substring(4);
283294

284295
} else if (isYoutubeMusicMixId(playlistId)) { // starts with "RDAMVM" or "RDCLAK"
@@ -705,6 +716,17 @@ public static String fixThumbnailUrl(@Nonnull String thumbnailUrl) {
705716
return thumbnailUrl;
706717
}
707718

719+
public static String getThumbnailUrlFromInfoItem(final JsonObject infoItem)
720+
throws ParsingException {
721+
// TODO: Don't simply get the first item, but look at all thumbnails and their resolution
722+
try {
723+
return fixThumbnailUrl(infoItem.getObject("thumbnail").getArray("thumbnails")
724+
.getObject(0).getString("url"));
725+
} catch (final Exception e) {
726+
throw new ParsingException("Could not get thumbnail url", e);
727+
}
728+
}
729+
708730
@Nonnull
709731
public static String getValidJsonResponseBody(@Nonnull final Response response)
710732
throws ParsingException, MalformedURLException {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,9 +234,9 @@ private void collectStreamsFrom(@Nonnull final StreamInfoItemsCollector collecto
234234
@Nonnull
235235
private String getThumbnailUrlFromPlaylistId(@Nonnull final String playlistId) throws ParsingException {
236236
final String videoId;
237-
if (playlistId.startsWith("RDMM")) {
237+
if (isYoutubeMyMixId(playlistId)) {
238238
videoId = playlistId.substring(4);
239-
} else if (playlistId.startsWith("RDCMUC")) {
239+
} else if (isYoutubeChannelMixId(playlistId)) {
240240
throw new ParsingException("This playlist is a channel mix");
241241
} else {
242242
videoId = playlistId.substring(2);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package org.schabi.newpipe.extractor.services.youtube.extractors;
2+
3+
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
4+
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getThumbnailUrlFromInfoItem;
5+
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.isYoutubeChannelMixId;
6+
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.isYoutubeMusicMixId;
7+
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
8+
9+
import com.grack.nanojson.JsonObject;
10+
11+
import org.schabi.newpipe.extractor.ListExtractor;
12+
import org.schabi.newpipe.extractor.exceptions.ParsingException;
13+
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
14+
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor;
15+
import org.schabi.newpipe.extractor.utils.Utils;
16+
17+
import java.net.MalformedURLException;
18+
19+
import javax.annotation.Nonnull;
20+
21+
public class YoutubeMixPlaylistInfoItemExtractor implements PlaylistInfoItemExtractor {
22+
private final JsonObject mixInfoItem;
23+
24+
public YoutubeMixPlaylistInfoItemExtractor(final JsonObject mixInfoItem) {
25+
this.mixInfoItem = mixInfoItem;
26+
}
27+
28+
@Override
29+
public String getName() throws ParsingException {
30+
final String name = getTextFromObject(mixInfoItem.getObject("title"));
31+
if (isNullOrEmpty(name)) {
32+
throw new ParsingException("Could not get name");
33+
}
34+
return name;
35+
}
36+
37+
@Override
38+
public String getUrl() throws ParsingException {
39+
final String url = mixInfoItem.getString("shareUrl");
40+
if (isNullOrEmpty(url)) {
41+
throw new ParsingException("Could not get url");
42+
}
43+
return url;
44+
}
45+
46+
@Override
47+
public String getThumbnailUrl() throws ParsingException {
48+
return getThumbnailUrlFromInfoItem(mixInfoItem);
49+
}
50+
51+
@Override
52+
public String getUploaderName() throws ParsingException {
53+
// YouTube mixes are auto-generated by YouTube
54+
return "YouTube";
55+
}
56+
57+
@Override
58+
public long getStreamCount() throws ParsingException {
59+
// Auto-generated playlists always start with 25 videos and are endless
60+
return ListExtractor.ITEM_COUNT_INFINITE;
61+
}
62+
63+
@Nonnull
64+
@Override
65+
public PlaylistInfo.PlaylistType getPlaylistType() throws ParsingException {
66+
try {
67+
final String url = getUrl();
68+
final String mixPlaylistId = Utils.getQueryValue(Utils.stringToURL(url), "list");
69+
if (isNullOrEmpty(mixPlaylistId)) {
70+
throw new ParsingException("Mix playlist id was null or empty for url " + url);
71+
}
72+
73+
if (isYoutubeMusicMixId(mixPlaylistId)) {
74+
return PlaylistInfo.PlaylistType.MIX_MUSIC;
75+
} else if (isYoutubeChannelMixId(mixPlaylistId)) {
76+
return PlaylistInfo.PlaylistType.MIX_CHANNEL;
77+
} else {
78+
// either a normal mix based on a stream, or a "my mix" (still based on a stream)
79+
return PlaylistInfo.PlaylistType.MIX_STREAM;
80+
}
81+
} catch (final MalformedURLException e) {
82+
throw new ParsingException("Could not obtain mix playlist id", e);
83+
}
84+
}
85+
}

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

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import org.schabi.newpipe.extractor.MediaFormat;
1212
import org.schabi.newpipe.extractor.MetaInfo;
13+
import org.schabi.newpipe.extractor.MultiInfoItemsCollector;
1314
import org.schabi.newpipe.extractor.StreamingService;
1415
import org.schabi.newpipe.extractor.downloader.Downloader;
1516
import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException;
@@ -618,27 +619,30 @@ public StreamType getStreamType() {
618619

619620
@Nullable
620621
@Override
621-
public StreamInfoItemsCollector getRelatedItems() throws ExtractionException {
622+
public MultiInfoItemsCollector getRelatedItems() throws ExtractionException {
622623
assertPageFetched();
623624

624625
if (getAgeLimit() != NO_AGE_LIMIT) {
625626
return null;
626627
}
627628

628629
try {
629-
final StreamInfoItemsCollector collector = new StreamInfoItemsCollector(
630-
getServiceId());
630+
final MultiInfoItemsCollector collector = new MultiInfoItemsCollector(getServiceId());
631631

632632
final JsonArray results = nextResponse.getObject("contents")
633633
.getObject("twoColumnWatchNextResults").getObject("secondaryResults")
634634
.getObject("secondaryResults").getArray("results");
635635

636636
final TimeAgoParser timeAgoParser = getTimeAgoParser();
637637

638-
for (final Object ul : results) {
639-
if (((JsonObject) ul).has("compactVideoRenderer")) {
640-
collector.commit(new YoutubeStreamInfoItemExtractor(((JsonObject) ul)
641-
.getObject("compactVideoRenderer"), timeAgoParser));
638+
for (final Object resultObject : results) {
639+
final JsonObject result = (JsonObject) resultObject;
640+
if (result.has("compactVideoRenderer")) {
641+
collector.commit(new YoutubeStreamInfoItemExtractor(
642+
result.getObject("compactVideoRenderer"), timeAgoParser));
643+
} else if (result.has("compactRadioRenderer")) {
644+
collector.commit(new YoutubeMixPlaylistInfoItemExtractor(
645+
result.getObject("compactRadioRenderer")));
642646
}
643647
}
644648
return collector;

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

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -252,15 +252,7 @@ public long getViewCount() throws ParsingException {
252252

253253
@Override
254254
public String getThumbnailUrl() throws ParsingException {
255-
try {
256-
// TODO: Don't simply get the first item, but look at all thumbnails and their resolution
257-
String url = videoInfo.getObject("thumbnail").getArray("thumbnails")
258-
.getObject(0).getString("url");
259-
260-
return fixThumbnailUrl(url);
261-
} catch (Exception e) {
262-
throw new ParsingException("Could not get thumbnail url", e);
263-
}
255+
return getThumbnailUrlFromInfoItem(videoInfo);
264256
}
265257

266258
private boolean isPremium() {

0 commit comments

Comments
 (0)