From 21c3aad3202ae6c365571b7eb4393461a355cf08 Mon Sep 17 00:00:00 2001 From: AudricV <74829229+AudricV@users.noreply.github.com> Date: Sat, 26 Jul 2025 23:30:24 +0200 Subject: [PATCH] [YouTube] Add trending music extractor This kiosk is meant to return official music videos, but it also returns unofficial content and autogenerated tracks, hence the kiosk name. Making requests with an unsupported YouTube Charts country leads to a 400 HTTP error, so for these countries a ContentNotSupportedException is thrown by the extractor. --- .../services/youtube/YoutubeService.java | 12 +++++ .../kiosk/YoutubeTrendingMusicExtractor.java | 39 ++++++++++++++ ...outubeTrendingMusicLinkHandlerFactory.java | 52 +++++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/kiosk/YoutubeTrendingMusicExtractor.java create mode 100644 extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/linkHandler/YoutubeTrendingMusicLinkHandlerFactory.java diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeService.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeService.java index 0d8845fa4..fcdff3af6 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeService.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeService.java @@ -39,6 +39,7 @@ import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeTrendingE import org.schabi.newpipe.extractor.services.youtube.extractors.kiosk.YoutubeLiveExtractor; import org.schabi.newpipe.extractor.services.youtube.extractors.kiosk.YoutubeTrendingGamingVideosExtractor; import org.schabi.newpipe.extractor.services.youtube.extractors.kiosk.YoutubeTrendingMoviesAndShowsTrailersExtractor; +import org.schabi.newpipe.extractor.services.youtube.extractors.kiosk.YoutubeTrendingMusicExtractor; import org.schabi.newpipe.extractor.services.youtube.extractors.kiosk.YoutubeTrendingPodcastsEpisodesExtractor; import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeChannelLinkHandlerFactory; import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeChannelTabLinkHandlerFactory; @@ -50,6 +51,7 @@ import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeStreamLi import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeTrendingGamingVideosLinkHandlerFactory; import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeTrendingLinkHandlerFactory; import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeTrendingMoviesAndShowsTrailersLinkHandlerFactory; +import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeTrendingMusicLinkHandlerFactory; import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeTrendingPodcastsEpisodesLinkHandlerFactory; import org.schabi.newpipe.extractor.stream.StreamExtractor; import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor; @@ -171,6 +173,8 @@ public class YoutubeService extends StreamingService { YoutubeTrendingGamingVideosLinkHandlerFactory.INSTANCE; final ListLinkHandlerFactory trendingMoviesAndShowsLHF = YoutubeTrendingMoviesAndShowsTrailersLinkHandlerFactory.INSTANCE; + final ListLinkHandlerFactory trendingMusicLHF = + YoutubeTrendingMusicLinkHandlerFactory.INSTANCE; try { list.addKioskEntry( @@ -206,6 +210,14 @@ public class YoutubeService extends StreamingService { trendingMoviesAndShowsLHF, YoutubeTrendingMoviesAndShowsTrailersLinkHandlerFactory.KIOSK_ID ); + list.addKioskEntry( + (streamingService, url, id) -> new YoutubeTrendingMusicExtractor( + YoutubeService.this, + trendingMusicLHF.fromUrl(url), + id), + trendingMusicLHF, + YoutubeTrendingMusicLinkHandlerFactory.KIOSK_ID + ); list.addKioskEntry( (streamingService, url, id) -> new YoutubeTrendingExtractor( YoutubeService.this, diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/kiosk/YoutubeTrendingMusicExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/kiosk/YoutubeTrendingMusicExtractor.java new file mode 100644 index 000000000..b545ec1e6 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/kiosk/YoutubeTrendingMusicExtractor.java @@ -0,0 +1,39 @@ +package org.schabi.newpipe.extractor.services.youtube.extractors.kiosk; + +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.downloader.Downloader; +import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler; + +import javax.annotation.Nonnull; +import java.io.IOException; + +public class YoutubeTrendingMusicExtractor extends YoutubeChartsBaseKioskExtractor { + + public YoutubeTrendingMusicExtractor(final StreamingService streamingService, + final ListLinkHandler linkHandler, + final String kioskId) { + super(streamingService, linkHandler, kioskId, "TRENDING_VIDEOS"); + } + + @Override + public void onFetchPage(@Nonnull final Downloader downloader) + throws IOException, ExtractionException { + if (!YT_CHARTS_SUPPORTED_COUNTRY_CODES.contains( + getExtractorContentCountry().getCountryCode())) { + throw new ContentNotSupportedException( + "YouTube Charts doesn't support this country for trending music videos charts"); + } + super.onFetchPage(downloader); + } + + @Nonnull + @Override + public String getName() throws ParsingException { + // This is the official YouTube Charts name, even if autogenerated tracks and unofficial + // contents are returned + return "Trending Music Videos"; + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/linkHandler/YoutubeTrendingMusicLinkHandlerFactory.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/linkHandler/YoutubeTrendingMusicLinkHandlerFactory.java new file mode 100644 index 000000000..4fe6df761 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/linkHandler/YoutubeTrendingMusicLinkHandlerFactory.java @@ -0,0 +1,52 @@ +package org.schabi.newpipe.extractor.services.youtube.linkHandler; + +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory; +import org.schabi.newpipe.extractor.utils.Utils; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; +import java.util.Locale; + +public final class YoutubeTrendingMusicLinkHandlerFactory + extends ListLinkHandlerFactory { + + public static final String KIOSK_ID = "trending_music"; + + public static final YoutubeTrendingMusicLinkHandlerFactory INSTANCE = + new YoutubeTrendingMusicLinkHandlerFactory(); + + private static final String PATH = "/charts/TrendingVideos"; + + private YoutubeTrendingMusicLinkHandlerFactory() { + } + + @Override + public String getUrl(final String id, + final List contentFilter, + final String sortFilter) + throws ParsingException, UnsupportedOperationException { + return "https://charts.youtube.com" + PATH + "/RightNow"; + } + + @Override + public String getId(final String url) throws ParsingException, UnsupportedOperationException { + return KIOSK_ID; + } + + @Override + public boolean onAcceptUrl(final String url) throws ParsingException { + final URL urlObj; + try { + urlObj = Utils.stringToURL(url); + } catch (final MalformedURLException e) { + return false; + } + + return Utils.isHTTP(urlObj) + && "charts.youtube.com".equals(urlObj.getHost().toLowerCase(Locale.ROOT)) + // Accept URLs not containing the /RightNow part + && urlObj.getPath().startsWith(PATH); + } +}