diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeMusicAlbumOrPlaylistInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeMusicAlbumOrPlaylistInfoItemExtractor.java new file mode 100644 index 000000000..16619d0a1 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeMusicAlbumOrPlaylistInfoItemExtractor.java @@ -0,0 +1,157 @@ +package org.schabi.newpipe.extractor.services.youtube.extractors; + +import com.grack.nanojson.JsonArray; +import com.grack.nanojson.JsonObject; +import org.schabi.newpipe.extractor.Image; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor; +import org.schabi.newpipe.extractor.utils.Utils; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; + +import static org.schabi.newpipe.extractor.ListExtractor.ITEM_COUNT_MORE_THAN_100; +import static org.schabi.newpipe.extractor.ListExtractor.ITEM_COUNT_UNKNOWN; +import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject; +import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getImagesFromThumbnailsArray; +import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getUrlFromNavigationEndpoint; +import static org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory.MUSIC_ALBUMS; +import static org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory.MUSIC_PLAYLISTS; +import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; + +public class YoutubeMusicAlbumOrPlaylistInfoItemExtractor implements PlaylistInfoItemExtractor { + private final JsonObject albumOrPlaylistInfoItem; + private final JsonArray descriptionElements; + private final String searchType; + + public YoutubeMusicAlbumOrPlaylistInfoItemExtractor(final JsonObject albumOrPlaylistInfoItem, + final JsonArray descriptionElements, + final String searchType) { + this.albumOrPlaylistInfoItem = albumOrPlaylistInfoItem; + this.descriptionElements = descriptionElements; + this.searchType = searchType; + } + + @Nonnull + @Override + public List getThumbnails() throws ParsingException { + try { + return getImagesFromThumbnailsArray( + albumOrPlaylistInfoItem.getObject("thumbnail") + .getObject("musicThumbnailRenderer") + .getObject("thumbnail") + .getArray("thumbnails")); + } catch (final Exception e) { + throw new ParsingException("Could not get thumbnails", e); + } + } + + @Override + public String getName() throws ParsingException { + final String name = getTextFromObject(albumOrPlaylistInfoItem.getArray("flexColumns") + .getObject(0) + .getObject("musicResponsiveListItemFlexColumnRenderer") + .getObject("text")); + + if (!isNullOrEmpty(name)) { + return name; + } + + throw new ParsingException("Could not get name"); + } + + @Override + public String getUrl() throws ParsingException { + String playlistId = albumOrPlaylistInfoItem.getObject("menu") + .getObject("menuRenderer") + .getArray("items") + .getObject(4) + .getObject("toggleMenuServiceItemRenderer") + .getObject("toggledServiceEndpoint") + .getObject("likeEndpoint") + .getObject("target") + .getString("playlistId"); + + if (isNullOrEmpty(playlistId)) { + playlistId = albumOrPlaylistInfoItem.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"); + } + + @Override + public String getUploaderName() throws ParsingException { + final String name; + if (searchType.equals(MUSIC_ALBUMS)) { + name = descriptionElements.getObject(2).getString("text"); + } else { + name = descriptionElements.getObject(0).getString("text"); + } + + if (!isNullOrEmpty(name)) { + return name; + } + + throw new ParsingException("Could not get uploader name"); + } + + @Nullable + @Override + public String getUploaderUrl() throws ParsingException { + if (searchType.equals(MUSIC_PLAYLISTS)) { + return null; + } + + final JsonArray items = albumOrPlaylistInfoItem.getObject("menu") + .getObject("menuRenderer") + .getArray("items"); + for (final Object item : items) { + final JsonObject menuNavigationItemRenderer = + ((JsonObject) item).getObject("menuNavigationItemRenderer"); + if (menuNavigationItemRenderer.getObject("icon") + .getString("iconType", "") + .equals("ARTIST")) { + return getUrlFromNavigationEndpoint( + menuNavigationItemRenderer.getObject("navigationEndpoint")); + } + } + + throw new ParsingException("Could not get uploader URL"); + } + + @Override + public boolean isUploaderVerified() throws ParsingException { + return false; + } + + @Override + public long getStreamCount() throws ParsingException { + if (searchType.equals(MUSIC_ALBUMS)) { + return ITEM_COUNT_UNKNOWN; + } + + final String count = descriptionElements.getObject(2) + .getString("text"); + + if (!isNullOrEmpty(count)) { + if (count.contains("100+")) { + return ITEM_COUNT_MORE_THAN_100; + } else { + return Long.parseLong(Utils.removeNonDigitCharacters(count)); + } + } + + throw new ParsingException("Could not get stream count"); + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeMusicArtistInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeMusicArtistInfoItemExtractor.java new file mode 100644 index 000000000..477eaf709 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeMusicArtistInfoItemExtractor.java @@ -0,0 +1,95 @@ +package org.schabi.newpipe.extractor.services.youtube.extractors; + +import com.grack.nanojson.JsonObject; +import org.schabi.newpipe.extractor.Image; +import org.schabi.newpipe.extractor.channel.ChannelInfoItemExtractor; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.utils.Parser; +import org.schabi.newpipe.extractor.utils.Utils; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; + +import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject; +import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getImagesFromThumbnailsArray; +import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getUrlFromNavigationEndpoint; +import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; + +public class YoutubeMusicArtistInfoItemExtractor implements ChannelInfoItemExtractor { + private final JsonObject artistInfoItem; + + public YoutubeMusicArtistInfoItemExtractor(final JsonObject artistInfoItem) { + this.artistInfoItem = artistInfoItem; + } + + @Nonnull + @Override + public List getThumbnails() throws ParsingException { + try { + return getImagesFromThumbnailsArray( + artistInfoItem.getObject("thumbnail") + .getObject("musicThumbnailRenderer") + .getObject("thumbnail") + .getArray("thumbnails")); + } catch (final Exception e) { + throw new ParsingException("Could not get thumbnails", e); + } + } + + @Override + public String getName() throws ParsingException { + final String name = getTextFromObject(artistInfoItem.getArray("flexColumns") + .getObject(0) + .getObject("musicResponsiveListItemFlexColumnRenderer") + .getObject("text")); + if (!isNullOrEmpty(name)) { + return name; + } + throw new ParsingException("Could not get name"); + } + + @Override + public String getUrl() throws ParsingException { + final String url = getUrlFromNavigationEndpoint( + artistInfoItem.getObject("navigationEndpoint")); + if (!isNullOrEmpty(url)) { + return url; + } + throw new ParsingException("Could not get URL"); + } + + @Override + public long getSubscriberCount() throws ParsingException { + final String subscriberCount = getTextFromObject(artistInfoItem.getArray("flexColumns") + .getObject(2) + .getObject("musicResponsiveListItemFlexColumnRenderer") + .getObject("text")); + if (!isNullOrEmpty(subscriberCount)) { + try { + return Utils.mixedNumberWordToLong(subscriberCount); + } catch (final Parser.RegexException ignored) { + // probably subscriberCount == "No subscribers" or similar + return 0; + } + } + throw new ParsingException("Could not get subscriber count"); + } + + @Override + public long getStreamCount() { + return -1; + } + + @Override + public boolean isVerified() { + // An artist on YouTube Music is always verified + return true; + } + + @Nullable + @Override + public String getDescription() { + return null; + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeMusicSearchExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeMusicSearchExtractor.java index ae6b10a65..3efe6a74e 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeMusicSearchExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeMusicSearchExtractor.java @@ -1,9 +1,7 @@ package org.schabi.newpipe.extractor.services.youtube.extractors; import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.DISABLE_PRETTY_PRINT_PARAMETER; -import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.fixThumbnailUrl; import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject; -import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getUrlFromNavigationEndpoint; import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getValidJsonResponseBody; import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getYoutubeMusicHeaders; import static org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory.MUSIC_ALBUMS; @@ -29,19 +27,16 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandler; -import org.schabi.newpipe.extractor.localization.DateWrapper; -import org.schabi.newpipe.extractor.localization.TimeAgoParser; import org.schabi.newpipe.extractor.search.SearchExtractor; import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper; import org.schabi.newpipe.extractor.utils.JsonUtils; -import org.schabi.newpipe.extractor.utils.Parser; -import org.schabi.newpipe.extractor.utils.Utils; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +import java.util.Objects; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -222,7 +217,7 @@ public class YoutubeMusicSearchExtractor extends SearchExtractor { .object("client") .value("clientName", "WEB_REMIX") .value("clientVersion", youtubeMusicKeys[2]) - .value("hl", "en") + .value("hl", "en-GB") .value("gl", getExtractorContentCountry().getCountryCode()) .array("experimentIds").end() .value("experimentsToken", "") @@ -263,316 +258,44 @@ public class YoutubeMusicSearchExtractor extends SearchExtractor { return new InfoItemsPage<>(collector, getNextPageFrom(continuations)); } - @SuppressWarnings("MethodLength") private void collectMusicStreamsFrom(final MultiInfoItemsCollector collector, @Nonnull final JsonArray videos) { - final TimeAgoParser timeAgoParser = getTimeAgoParser(); + final String searchType = getLinkHandler().getContentFilters().get(0); + videos.stream() + .filter(JsonObject.class::isInstance) + .map(JsonObject.class::cast) + .map(item -> item.getObject("musicResponsiveListItemRenderer", null)) + .filter(Objects::nonNull) + .forEachOrdered(infoItem -> { + final String displayPolicy = infoItem.getString( + "musicItemRendererDisplayPolicy", ""); + if (displayPolicy.equals("MUSIC_ITEM_RENDERER_DISPLAY_POLICY_GREY_OUT")) { + // No info about URL available + return; + } - for (final Object item : videos) { - final JsonObject info = ((JsonObject) item) - .getObject("musicResponsiveListItemRenderer", null); - if (info != null) { - final String displayPolicy = info.getString("musicItemRendererDisplayPolicy", - ""); - if (displayPolicy.equals("MUSIC_ITEM_RENDERER_DISPLAY_POLICY_GREY_OUT")) { - continue; // No info about video URL available - } + final JsonArray descriptionElements = infoItem.getArray("flexColumns") + .getObject(1) + .getObject("musicResponsiveListItemFlexColumnRenderer") + .getObject("text") + .getArray("runs"); - 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); - if (searchType.equals(MUSIC_SONGS) || searchType.equals(MUSIC_VIDEOS)) { - collector.commit(new YoutubeStreamInfoItemExtractor(info, timeAgoParser) { - @Override - public String getUrl() throws ParsingException { - final String id = info.getObject("playlistItemData") - .getString("videoId"); - if (!isNullOrEmpty(id)) { - return "https://music.youtube.com/watch?v=" + id; - } - throw new ParsingException("Could not get url"); - } - - @Override - public String getName() throws ParsingException { - final String name = getTextFromObject(info.getArray("flexColumns") - .getObject(0) - .getObject("musicResponsiveListItemFlexColumnRenderer") - .getObject("text")); - if (!isNullOrEmpty(name)) { - return name; - } - throw new ParsingException("Could not get name"); - } - - @Override - public long getDuration() throws ParsingException { - final String duration = descriptionElements - .getObject(descriptionElements.size() - 1) - .getString("text"); - if (!isNullOrEmpty(duration)) { - return YoutubeParsingHelper.parseDurationString(duration); - } - throw new ParsingException("Could not get duration"); - } - - @Override - public String getUploaderName() throws ParsingException { - final String name = descriptionElements.getObject(0).getString("text"); - if (!isNullOrEmpty(name)) { - return name; - } - throw new ParsingException("Could not get uploader name"); - } - - @Override - public String getUploaderUrl() throws ParsingException { - if (searchType.equals(MUSIC_VIDEOS)) { - final JsonArray items = info.getObject("menu") - .getObject("menuRenderer") - .getArray("items"); - for (final Object item : items) { - final JsonObject menuNavigationItemRenderer = - ((JsonObject) item).getObject( - "menuNavigationItemRenderer"); - if (menuNavigationItemRenderer.getObject("icon") - .getString("iconType", "") - .equals("ARTIST")) { - return getUrlFromNavigationEndpoint( - menuNavigationItemRenderer - .getObject("navigationEndpoint")); - } - } - - return null; - } else { - final JsonObject navigationEndpointHolder = info - .getArray("flexColumns") - .getObject(1) - .getObject("musicResponsiveListItemFlexColumnRenderer") - .getObject("text").getArray("runs").getObject(0); - - if (!navigationEndpointHolder.has("navigationEndpoint")) { - return null; - } - - final String url = getUrlFromNavigationEndpoint( - navigationEndpointHolder.getObject("navigationEndpoint")); - - if (!isNullOrEmpty(url)) { - return url; - } - - throw new ParsingException("Could not get uploader URL"); - } - } - - @Override - public String getTextualUploadDate() { - return null; - } - - @Override - public DateWrapper getUploadDate() { - return null; - } - - @Override - public long getViewCount() throws ParsingException { - if (searchType.equals(MUSIC_SONGS)) { - return -1; - } - final String viewCount = descriptionElements - .getObject(descriptionElements.size() - 3) - .getString("text"); - if (!isNullOrEmpty(viewCount)) { - try { - return Utils.mixedNumberWordToLong(viewCount); - } catch (final Parser.RegexException e) { - // probably viewCount == "No views" or similar - return 0; - } - } - throw new ParsingException("Could not get view count"); - } - - @Override - public String getThumbnailUrl() throws ParsingException { - try { - final JsonArray thumbnails = info.getObject("thumbnail") - .getObject("musicThumbnailRenderer") - .getObject("thumbnail").getArray("thumbnails"); - // the last thumbnail is the one with the highest resolution - final String url = thumbnails.getObject(thumbnails.size() - 1) - .getString("url"); - - return fixThumbnailUrl(url); - } catch (final Exception e) { - throw new ParsingException("Could not get thumbnail url", e); - } - } - }); - } else if (searchType.equals(MUSIC_ARTISTS)) { - collector.commit(new YoutubeChannelInfoItemExtractor(info) { - @Override - public String getThumbnailUrl() throws ParsingException { - try { - final JsonArray thumbnails = info.getObject("thumbnail") - .getObject("musicThumbnailRenderer") - .getObject("thumbnail").getArray("thumbnails"); - // the last thumbnail is the one with the highest resolution - final String url = thumbnails.getObject(thumbnails.size() - 1) - .getString("url"); - - return fixThumbnailUrl(url); - } catch (final Exception e) { - throw new ParsingException("Could not get thumbnail url", e); - } - } - - @Override - public String getName() throws ParsingException { - final String name = getTextFromObject(info.getArray("flexColumns") - .getObject(0) - .getObject("musicResponsiveListItemFlexColumnRenderer") - .getObject("text")); - if (!isNullOrEmpty(name)) { - return name; - } - throw new ParsingException("Could not get name"); - } - - @Override - public String getUrl() throws ParsingException { - final String url = getUrlFromNavigationEndpoint(info - .getObject("navigationEndpoint")); - if (!isNullOrEmpty(url)) { - return url; - } - throw new ParsingException("Could not get url"); - } - - @Override - public long getSubscriberCount() throws ParsingException { - final String subscriberCount = getTextFromObject(info - .getArray("flexColumns").getObject(2) - .getObject("musicResponsiveListItemFlexColumnRenderer") - .getObject("text")); - if (!isNullOrEmpty(subscriberCount)) { - try { - return Utils.mixedNumberWordToLong(subscriberCount); - } catch (final Parser.RegexException ignored) { - // probably subscriberCount == "No subscribers" or similar - return 0; - } - } - throw new ParsingException("Could not get subscriber count"); - } - - @Override - public long getStreamCount() { - return -1; - } - - @Override - public String getDescription() { - return null; - } - }); - } else if (searchType.equals(MUSIC_ALBUMS) || searchType.equals(MUSIC_PLAYLISTS)) { - collector.commit(new YoutubePlaylistInfoItemExtractor(info) { - @Override - public String getThumbnailUrl() throws ParsingException { - try { - final JsonArray thumbnails = info.getObject("thumbnail") - .getObject("musicThumbnailRenderer") - .getObject("thumbnail").getArray("thumbnails"); - // the last thumbnail is the one with the highest resolution - final String url = thumbnails.getObject(thumbnails.size() - 1) - .getString("url"); - - return fixThumbnailUrl(url); - } catch (final Exception e) { - throw new ParsingException("Could not get thumbnail url", e); - } - } - - @Override - public String getName() throws ParsingException { - final String name = getTextFromObject(info.getArray("flexColumns") - .getObject(0) - .getObject("musicResponsiveListItemFlexColumnRenderer") - .getObject("text")); - if (!isNullOrEmpty(name)) { - return name; - } - throw new ParsingException("Could not get name"); - } - - @Override - public String getUrl() throws ParsingException { - String playlistId = info.getObject("menu") - .getObject("menuRenderer") - .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"); - } - - @Override - public String getUploaderName() throws ParsingException { - final String name; - if (searchType.equals(MUSIC_ALBUMS)) { - name = descriptionElements.getObject(2).getString("text"); - } else { - name = descriptionElements.getObject(0).getString("text"); - } - if (!isNullOrEmpty(name)) { - return name; - } - throw new ParsingException("Could not get uploader name"); - } - - @Override - public long getStreamCount() throws ParsingException { - if (searchType.equals(MUSIC_ALBUMS)) { - return ITEM_COUNT_UNKNOWN; - } - final String count = descriptionElements.getObject(2) - .getString("text"); - if (!isNullOrEmpty(count)) { - if (count.contains("100+")) { - return ITEM_COUNT_MORE_THAN_100; - } else { - return Long.parseLong(Utils.removeNonDigitCharacters(count)); - } - } - throw new ParsingException("Could not get count"); - } - }); - } - } - } + switch (searchType) { + case MUSIC_SONGS: + case MUSIC_VIDEOS: + collector.commit(new YoutubeMusicSongOrVideoInfoItemExtractor( + infoItem, descriptionElements, searchType)); + break; + case MUSIC_ARTISTS: + collector.commit(new YoutubeMusicArtistInfoItemExtractor(infoItem)); + break; + case MUSIC_ALBUMS: + case MUSIC_PLAYLISTS: + collector.commit(new YoutubeMusicAlbumOrPlaylistInfoItemExtractor( + infoItem, descriptionElements, searchType)); + break; + } + }); } @Nullable diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeMusicSongOrVideoInfoItemExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeMusicSongOrVideoInfoItemExtractor.java new file mode 100644 index 000000000..11b220288 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeMusicSongOrVideoInfoItemExtractor.java @@ -0,0 +1,177 @@ +package org.schabi.newpipe.extractor.services.youtube.extractors; + +import com.grack.nanojson.JsonArray; +import com.grack.nanojson.JsonObject; +import org.schabi.newpipe.extractor.Image; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.localization.DateWrapper; +import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper; +import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor; +import org.schabi.newpipe.extractor.stream.StreamType; +import org.schabi.newpipe.extractor.utils.Parser; +import org.schabi.newpipe.extractor.utils.Utils; + +import javax.annotation.Nonnull; +import java.util.List; + +import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject; +import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getImagesFromThumbnailsArray; +import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getUrlFromNavigationEndpoint; +import static org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory.MUSIC_SONGS; +import static org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory.MUSIC_VIDEOS; +import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; + +public class YoutubeMusicSongOrVideoInfoItemExtractor implements StreamInfoItemExtractor { + private final JsonObject songOrVideoInfoItem; + private final JsonArray descriptionElements; + private final String searchType; + + public YoutubeMusicSongOrVideoInfoItemExtractor(final JsonObject songOrVideoInfoItem, + final JsonArray descriptionElements, + final String searchType) { + this.songOrVideoInfoItem = songOrVideoInfoItem; + this.descriptionElements = descriptionElements; + this.searchType = searchType; + } + + + @Override + public String getUrl() throws ParsingException { + final String id = songOrVideoInfoItem.getObject("playlistItemData").getString("videoId"); + if (!isNullOrEmpty(id)) { + return "https://music.youtube.com/watch?v=" + id; + } + throw new ParsingException("Could not get URL"); + } + + @Override + public String getName() throws ParsingException { + final String name = getTextFromObject(songOrVideoInfoItem.getArray("flexColumns") + .getObject(0) + .getObject("musicResponsiveListItemFlexColumnRenderer") + .getObject("text")); + if (!isNullOrEmpty(name)) { + return name; + } + throw new ParsingException("Could not get name"); + } + + @Override + public StreamType getStreamType() { + return StreamType.VIDEO_STREAM; + } + + @Override + public boolean isAd() { + return false; + } + + @Override + public long getDuration() throws ParsingException { + final String duration = descriptionElements.getObject(descriptionElements.size() - 1) + .getString("text"); + if (!isNullOrEmpty(duration)) { + return YoutubeParsingHelper.parseDurationString(duration); + } + throw new ParsingException("Could not get duration"); + } + + @Override + public String getUploaderName() throws ParsingException { + final String name = descriptionElements.getObject(0).getString("text"); + if (!isNullOrEmpty(name)) { + return name; + } + throw new ParsingException("Could not get uploader name"); + } + + @Override + public String getUploaderUrl() throws ParsingException { + if (searchType.equals(MUSIC_VIDEOS)) { + final JsonArray items = songOrVideoInfoItem.getObject("menu") + .getObject("menuRenderer") + .getArray("items"); + for (final Object item : items) { + final JsonObject menuNavigationItemRenderer = + ((JsonObject) item).getObject("menuNavigationItemRenderer"); + if (menuNavigationItemRenderer.getObject("icon") + .getString("iconType", "") + .equals("ARTIST")) { + return getUrlFromNavigationEndpoint( + menuNavigationItemRenderer.getObject("navigationEndpoint")); + } + } + + return null; + } else { + final JsonObject navigationEndpointHolder = songOrVideoInfoItem.getArray("flexColumns") + .getObject(1) + .getObject("musicResponsiveListItemFlexColumnRenderer") + .getObject("text") + .getArray("runs") + .getObject(0); + + if (!navigationEndpointHolder.has("navigationEndpoint")) { + return null; + } + + final String url = getUrlFromNavigationEndpoint( + navigationEndpointHolder.getObject("navigationEndpoint")); + + if (!isNullOrEmpty(url)) { + return url; + } + + throw new ParsingException("Could not get uploader URL"); + } + } + + @Override + public boolean isUploaderVerified() { + // We don't have the ability to know this information on YouTube Music + return false; + } + + @Override + public String getTextualUploadDate() { + return null; + } + + @Override + public DateWrapper getUploadDate() { + return null; + } + + @Override + public long getViewCount() throws ParsingException { + if (searchType.equals(MUSIC_SONGS)) { + return -1; + } + final String viewCount = descriptionElements + .getObject(descriptionElements.size() - 3) + .getString("text"); + if (!isNullOrEmpty(viewCount)) { + try { + return Utils.mixedNumberWordToLong(viewCount); + } catch (final Parser.RegexException e) { + // probably viewCount == "No views" or similar + return 0; + } + } + throw new ParsingException("Could not get view count"); + } + + @Nonnull + @Override + public List getThumbnails() throws ParsingException { + try { + return getImagesFromThumbnailsArray( + songOrVideoInfoItem.getObject("thumbnail") + .getObject("musicThumbnailRenderer") + .getObject("thumbnail") + .getArray("thumbnails")); + } catch (final Exception e) { + throw new ParsingException("Could not get thumbnails", e); + } + } +}