Merge pull request #484 from TeamNewPipe/yt_,music_search

Fix YouTube Music search
This commit is contained in:
Tobias Groza 2020-12-25 18:30:25 +01:00 committed by GitHub
commit b5e50cc9fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 61 additions and 34 deletions

View File

@ -74,7 +74,7 @@ public abstract class ListLinkHandlerFactory extends LinkHandlerFactory {
* however it should not be overridden by the actual implementation. * however it should not be overridden by the actual implementation.
* *
* @param id * @param id
* @return the url coresponding to id without any filters applied * @return the url corresponding to id without any filters applied
*/ */
public String getUrl(String id) throws ParsingException { public String getUrl(String id) throws ParsingException {
return getUrl(id, new ArrayList<String>(0), ""); return getUrl(id, new ArrayList<String>(0), "");

View File

@ -205,12 +205,12 @@ public class YoutubeParsingHelper {
/** /**
* Checks if the given playlist id is a YouTube Music Mix (auto-generated playlist) * Checks if the given playlist id is a YouTube Music Mix (auto-generated playlist)
* Ids from a YouTube Music Mix start with "RDAMVM" * Ids from a YouTube Music Mix start with "RDAMVM" or "RDCLAK"
* @param playlistId * @param playlistId
* @return Whether given id belongs to a YouTube Music Mix * @return Whether given id belongs to a YouTube Music Mix
*/ */
public static boolean isYoutubeMusicMixId(final String playlistId) { public static boolean isYoutubeMusicMixId(final String playlistId) {
return playlistId.startsWith("RDAMVM"); return playlistId.startsWith("RDAMVM") || playlistId.startsWith("RDCLAK");
} }
/** /**
* Checks if the given playlist id is a YouTube Channel Mix (auto-generated playlist) * Checks if the given playlist id is a YouTube Channel Mix (auto-generated playlist)
@ -229,14 +229,14 @@ public class YoutubeParsingHelper {
if (playlistId.startsWith("RDMM")) { // My Mix if (playlistId.startsWith("RDMM")) { // My Mix
return playlistId.substring(4); return playlistId.substring(4);
} else if (playlistId.startsWith("RDAMVM")) { //Music mix } else if (isYoutubeMusicMixId(playlistId)) { // starts with "RDAMVM" or "RDCLAK"
return playlistId.substring(6); return playlistId.substring(6);
} else if (playlistId.startsWith("RMCM")) { //Channel mix } else if (isYoutubeChannelMixId(playlistId)) { // starts with "RMCM"
// Channel mix are build with RMCM{channelId}, so videoId can't be determined // Channel mix are build with RMCM{channelId}, so videoId can't be determined
throw new ParsingException("Video id could not be determined from mix id: " + playlistId); throw new ParsingException("Video id could not be determined from mix id: " + playlistId);
} else if (playlistId.startsWith("RD")) { // Normal mix } else if (isYoutubeMixId(playlistId)) { // normal mix, starts with "RD"
return playlistId.substring(2); return playlistId.substring(2);
} else { // not a mix } else { // not a mix

View File

@ -111,7 +111,8 @@ public class YoutubeService extends StreamingService {
@Override @Override
public PlaylistExtractor getPlaylistExtractor(final ListLinkHandler linkHandler) { public PlaylistExtractor getPlaylistExtractor(final ListLinkHandler linkHandler) {
if (YoutubeParsingHelper.isYoutubeMixId(linkHandler.getId())) { if (YoutubeParsingHelper.isYoutubeMixId(linkHandler.getId())
&& !YoutubeParsingHelper.isYoutubeMusicMixId(linkHandler.getId())) {
return new YoutubeMixPlaylistExtractor(this, linkHandler); return new YoutubeMixPlaylistExtractor(this, linkHandler);
} else { } else {
return new YoutubePlaylistExtractor(this, linkHandler); return new YoutubePlaylistExtractor(this, linkHandler);

View File

@ -20,6 +20,8 @@ import org.schabi.newpipe.extractor.localization.TimeAgoParser;
import org.schabi.newpipe.extractor.search.InfoItemsSearchCollector; import org.schabi.newpipe.extractor.search.InfoItemsSearchCollector;
import org.schabi.newpipe.extractor.search.SearchExtractor; import org.schabi.newpipe.extractor.search.SearchExtractor;
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper; import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubePlaylistLinkHandlerFactory;
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeStreamLinkHandlerFactory;
import org.schabi.newpipe.extractor.utils.JsonUtils; import org.schabi.newpipe.extractor.utils.JsonUtils;
import org.schabi.newpipe.extractor.utils.Utils; import org.schabi.newpipe.extractor.utils.Utils;
@ -258,16 +260,29 @@ public class YoutubeMusicSearchExtractor extends SearchExtractor {
final TimeAgoParser timeAgoParser = getTimeAgoParser(); final TimeAgoParser timeAgoParser = getTimeAgoParser();
for (Object item : videos) { for (Object item : videos) {
final JsonObject info = ((JsonObject) item).getObject("musicResponsiveListItemRenderer", null); final JsonObject info = ((JsonObject) item)
.getObject("musicResponsiveListItemRenderer", null);
if (info != null) { if (info != null) {
final String displayPolicy = info.getString("musicItemRendererDisplayPolicy", EMPTY_STRING);
if (displayPolicy.equals("MUSIC_ITEM_RENDERER_DISPLAY_POLICY_GREY_OUT")) {
continue; // no info about video URL available
}
final JsonObject flexColumnRenderer = info
.getArray("flexColumns")
.getObject(1)
.getObject("musicResponsiveListItemFlexColumnRenderer");
final JsonArray descriptionElements = flexColumnRenderer
.getObject("text")
.getArray("runs");
final String searchType = getLinkHandler().getContentFilters().get(0); final String searchType = getLinkHandler().getContentFilters().get(0);
if (searchType.equals(MUSIC_SONGS) || searchType.equals(MUSIC_VIDEOS)) { if (searchType.equals(MUSIC_SONGS) || searchType.equals(MUSIC_VIDEOS)) {
collector.commit(new YoutubeStreamInfoItemExtractor(info, timeAgoParser) { collector.commit(new YoutubeStreamInfoItemExtractor(info, timeAgoParser) {
@Override @Override
public String getUrl() throws ParsingException { public String getUrl() throws ParsingException {
final String url = getUrlFromNavigationEndpoint(info.getObject("doubleTapCommand")); final String id = info.getObject("playlistItemData").getString("videoId");
if (!isNullOrEmpty(url)) { if (!isNullOrEmpty(id)) {
return url; return "https://music.youtube.com/watch?v=" + id;
} }
throw new ParsingException("Could not get url"); throw new ParsingException("Could not get url");
} }
@ -284,8 +299,9 @@ public class YoutubeMusicSearchExtractor extends SearchExtractor {
@Override @Override
public long getDuration() throws ParsingException { public long getDuration() throws ParsingException {
final String duration = getTextFromObject(info.getArray("flexColumns").getObject(3) final String duration = descriptionElements
.getObject("musicResponsiveListItemFlexColumnRenderer").getObject("text")); .getObject(descriptionElements.size() - 1)
.getString("text");
if (!isNullOrEmpty(duration)) { if (!isNullOrEmpty(duration)) {
return YoutubeParsingHelper.parseDurationString(duration); return YoutubeParsingHelper.parseDurationString(duration);
} }
@ -294,8 +310,7 @@ public class YoutubeMusicSearchExtractor extends SearchExtractor {
@Override @Override
public String getUploaderName() throws ParsingException { public String getUploaderName() throws ParsingException {
final String name = getTextFromObject(info.getArray("flexColumns").getObject(1) final String name = descriptionElements.getObject(0).getString("text");
.getObject("musicResponsiveListItemFlexColumnRenderer").getObject("text"));
if (!isNullOrEmpty(name)) { if (!isNullOrEmpty(name)) {
return name; return name;
} }
@ -346,8 +361,9 @@ public class YoutubeMusicSearchExtractor extends SearchExtractor {
if (searchType.equals(MUSIC_SONGS)) { if (searchType.equals(MUSIC_SONGS)) {
return -1; return -1;
} }
final String viewCount = getTextFromObject(info.getArray("flexColumns").getObject(2) final String viewCount = descriptionElements
.getObject("musicResponsiveListItemFlexColumnRenderer").getObject("text")); .getObject(descriptionElements.size() - 3)
.getString("text");
if (!isNullOrEmpty(viewCount)) { if (!isNullOrEmpty(viewCount)) {
return Utils.mixedNumberWordToLong(viewCount); return Utils.mixedNumberWordToLong(viewCount);
} }
@ -451,9 +467,27 @@ public class YoutubeMusicSearchExtractor extends SearchExtractor {
@Override @Override
public String getUrl() throws ParsingException { public String getUrl() throws ParsingException {
final String url = getUrlFromNavigationEndpoint(info.getObject("doubleTapCommand")); String playlistId = info.getObject("menu")
if (!isNullOrEmpty(url)) { .getObject("menuRenderer")
return url; .getArray("items")
.getObject(4)
.getObject("toggleMenuServiceItemRenderer")
.getObject("toggledServiceEndpoint")
.getObject("likeEndpoint")
.getObject("target")
.getString("playlistId");
if (isNullOrEmpty(playlistId)) {
playlistId = info.getObject("overlay")
.getObject("musicItemThumbnailOverlayRenderer")
.getObject("content")
.getObject("musicPlayButtonRenderer")
.getObject("playNavigationEndpoint")
.getObject("watchPlaylistEndpoint")
.getString("playlistId");
}
if (!isNullOrEmpty(playlistId)) {
return "https://music.youtube.com/playlist?list=" + playlistId;
} }
throw new ParsingException("Could not get url"); throw new ParsingException("Could not get url");
} }
@ -462,11 +496,9 @@ public class YoutubeMusicSearchExtractor extends SearchExtractor {
public String getUploaderName() throws ParsingException { public String getUploaderName() throws ParsingException {
final String name; final String name;
if (searchType.equals(MUSIC_ALBUMS)) { if (searchType.equals(MUSIC_ALBUMS)) {
name = getTextFromObject(info.getArray("flexColumns").getObject(2) name = descriptionElements.getObject(2).getString("text");
.getObject("musicResponsiveListItemFlexColumnRenderer").getObject("text"));
} else { } else {
name = getTextFromObject(info.getArray("flexColumns").getObject(1) name = descriptionElements.getObject(0).getString("text");
.getObject("musicResponsiveListItemFlexColumnRenderer").getObject("text"));
} }
if (!isNullOrEmpty(name)) { if (!isNullOrEmpty(name)) {
return name; return name;
@ -479,8 +511,7 @@ public class YoutubeMusicSearchExtractor extends SearchExtractor {
if (searchType.equals(MUSIC_ALBUMS)) { if (searchType.equals(MUSIC_ALBUMS)) {
return ITEM_COUNT_UNKNOWN; return ITEM_COUNT_UNKNOWN;
} }
final String count = getTextFromObject(info.getArray("flexColumns").getObject(2) final String count = descriptionElements.getObject(2).getString("text");
.getObject("musicResponsiveListItemFlexColumnRenderer").getObject("text"));
if (!isNullOrEmpty(count)) { if (!isNullOrEmpty(count)) {
if (count.contains("100+")) { if (count.contains("100+")) {
return ITEM_COUNT_MORE_THAN_100; return ITEM_COUNT_MORE_THAN_100;

View File

@ -52,11 +52,6 @@ public class YoutubePlaylistLinkHandlerFactory extends ListLinkHandlerFactory {
"the list-ID given in the URL does not match the list pattern"); "the list-ID given in the URL does not match the list pattern");
} }
if (YoutubeParsingHelper.isYoutubeMusicMixId(listID)) {
throw new ContentNotSupportedException(
"YouTube Music Mix playlists are not yet supported");
}
if (YoutubeParsingHelper.isYoutubeChannelMixId(listID) if (YoutubeParsingHelper.isYoutubeChannelMixId(listID)
&& Utils.getQueryValue(urlObj, "v") == null) { && Utils.getQueryValue(urlObj, "v") == null) {
//Video id can't be determined from the channel mix id. See YoutubeParsingHelper#extractVideoIdFromMixId //Video id can't be determined from the channel mix id. See YoutubeParsingHelper#extractVideoIdFromMixId