From 266cd1f76bf2bf8d6996cac8cecffd5474d7a471 Mon Sep 17 00:00:00 2001 From: AudricV <74829229+AudricV@users.noreply.github.com> Date: Sun, 24 Jul 2022 19:52:07 +0200 Subject: [PATCH] [YouTube] Apply changes in YoutubeMusicSearchExtractor and split its InfoItemExtractors into separate classes Splitting YoutubeMusicSearchExtractor's InfoItemExtractors into separate classes (YoutubeMusicSongOrVideoInfoItemExtractor, YoutubeMusicAlbumOrPlaylistInfoItemExtractor and YoutubeMusicArtistInfoItemExtractor) allows to simplify YoutubeMusicSearchExtractor,improves reading and applying changes to InfoItems (no more losing at least quarter of a line due to indentations). These InfoItems, in which the image changes have been applied, don't extend the YouTube ones anymore, as most methods were overridden and the few ones that are not don't apply in YouTube Music items responses, so it was useless to extend them. The code of YoutubeMusicSearchExtractor have been also improved a bit. --- ...MusicAlbumOrPlaylistInfoItemExtractor.java | 157 ++++++++ .../YoutubeMusicArtistInfoItemExtractor.java | 95 +++++ .../YoutubeMusicSearchExtractor.java | 349 ++---------------- ...tubeMusicSongOrVideoInfoItemExtractor.java | 177 +++++++++ 4 files changed, 465 insertions(+), 313 deletions(-) create mode 100644 extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeMusicAlbumOrPlaylistInfoItemExtractor.java create mode 100644 extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeMusicArtistInfoItemExtractor.java create mode 100644 extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeMusicSongOrVideoInfoItemExtractor.java 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); + } + } +}