searchfilters: convert youtube to new framework
Available content filters: Youtube - all - videos - channels - playlists Youtube Music - Songs - Videos - Albums - Playlists - Artists Available sort filters: - 'Sort by' - Upload Date - Duration - Features
This commit is contained in:
parent
8568f196ec
commit
0a4b88955a
|
@ -1,5 +1,6 @@
|
|||
plugins {
|
||||
id 'checkstyle'
|
||||
id 'com.squareup.wire' version '4.4.1'
|
||||
}
|
||||
|
||||
test {
|
||||
|
@ -18,6 +19,15 @@ checkstyle {
|
|||
toolVersion checkstyleVersion
|
||||
}
|
||||
|
||||
checkstyleMain
|
||||
// exclude the wire generated youtube protobuf files
|
||||
.exclude ('org/schabi/newpipe/extractor/services/youtube/search/filter/protobuf/')
|
||||
|
||||
wire {
|
||||
java {
|
||||
}
|
||||
}
|
||||
|
||||
checkstyleTest {
|
||||
enabled false // do not checkstyle test files
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package org.schabi.newpipe.extractor.services.youtube;
|
||||
|
||||
import org.schabi.newpipe.extractor.search.filter.FilterItem;
|
||||
import org.schabi.newpipe.extractor.services.youtube.search.filter.YoutubeFilters;
|
||||
|
||||
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.AUDIO;
|
||||
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS;
|
||||
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.LIVE;
|
||||
|
@ -46,6 +49,7 @@ import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeTrending
|
|||
import org.schabi.newpipe.extractor.stream.StreamExtractor;
|
||||
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
|
||||
import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor;
|
||||
import org.schabi.newpipe.extractor.utils.Utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -137,9 +141,10 @@ public class YoutubeService extends StreamingService {
|
|||
|
||||
@Override
|
||||
public SearchExtractor getSearchExtractor(final SearchQueryHandler query) {
|
||||
final List<String> contentFilters = query.getContentFilters();
|
||||
final FilterItem filterItem =
|
||||
Utils.getFirstContentFilterItem(query);
|
||||
|
||||
if (!contentFilters.isEmpty() && contentFilters.get(0).startsWith("music_")) {
|
||||
if (filterItem instanceof YoutubeFilters.MusicYoutubeContentFilterItem) {
|
||||
return new YoutubeMusicSearchExtractor(this, query);
|
||||
} else {
|
||||
return new YoutubeSearchExtractor(this, query);
|
||||
|
|
|
@ -29,14 +29,16 @@ import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
|||
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandler;
|
||||
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
||||
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
|
||||
import org.schabi.newpipe.extractor.services.youtube.search.filter.YoutubeFilters;
|
||||
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
||||
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 java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
@ -49,6 +51,15 @@ public class YoutubeMusicSearchExtractor extends SearchExtractor {
|
|||
super(service, linkHandler);
|
||||
}
|
||||
|
||||
private String getSearchType() {
|
||||
final YoutubeFilters.MusicYoutubeContentFilterItem contentFilterItem =
|
||||
Utils.getFirstContentFilterItem(getLinkHandler());
|
||||
if (contentFilterItem != null && contentFilterItem.getName() != null) {
|
||||
return contentFilterItem.getName();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchPage(@Nonnull final Downloader downloader)
|
||||
throws IOException, ExtractionException {
|
||||
|
@ -57,28 +68,12 @@ public class YoutubeMusicSearchExtractor extends SearchExtractor {
|
|||
final String url = "https://music.youtube.com/youtubei/v1/search?key="
|
||||
+ youtubeMusicKeys[0] + DISABLE_PRETTY_PRINT_PARAMETER;
|
||||
|
||||
final String params;
|
||||
|
||||
switch (getLinkHandler().getContentFilters().get(0)) {
|
||||
case MUSIC_SONGS:
|
||||
params = "Eg-KAQwIARAAGAAgACgAMABqChAEEAUQAxAKEAk%3D";
|
||||
break;
|
||||
case MUSIC_VIDEOS:
|
||||
params = "Eg-KAQwIABABGAAgACgAMABqChAEEAUQAxAKEAk%3D";
|
||||
break;
|
||||
case MUSIC_ALBUMS:
|
||||
params = "Eg-KAQwIABAAGAEgACgAMABqChAEEAUQAxAKEAk%3D";
|
||||
break;
|
||||
case MUSIC_PLAYLISTS:
|
||||
params = "Eg-KAQwIABAAGAAgACgBMABqChAEEAUQAxAKEAk%3D";
|
||||
break;
|
||||
case MUSIC_ARTISTS:
|
||||
params = "Eg-KAQwIABAAGAAgASgAMABqChAEEAUQAxAKEAk%3D";
|
||||
break;
|
||||
default:
|
||||
params = null;
|
||||
break;
|
||||
}
|
||||
final YoutubeFilters.MusicYoutubeContentFilterItem contentFilterItem =
|
||||
Utils.getFirstContentFilterItem(getLinkHandler());
|
||||
// Get the search parameter for the request. If getParams() be null
|
||||
// (which should never happen - only in test cases), JsonWriter.string() can handle it
|
||||
final String params = (contentFilterItem != null) ? contentFilterItem.getParams() : null;
|
||||
|
||||
// @formatter:off
|
||||
final byte[] json = JsonWriter.string()
|
||||
|
@ -256,7 +251,7 @@ public class YoutubeMusicSearchExtractor extends SearchExtractor {
|
|||
|
||||
private void collectMusicStreamsFrom(final MultiInfoItemsCollector collector,
|
||||
@Nonnull final JsonArray videos) {
|
||||
final String searchType = getLinkHandler().getContentFilters().get(0);
|
||||
final String searchType = getSearchType();
|
||||
videos.stream()
|
||||
.filter(JsonObject.class::isInstance)
|
||||
.map(JsonObject.class::cast)
|
||||
|
|
|
@ -6,11 +6,6 @@ import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper
|
|||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getKey;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.getTextFromObject;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper.prepareDesktopJsonBuilder;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory.ALL;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory.CHANNELS;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory.PLAYLISTS;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory.VIDEOS;
|
||||
import static org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeSearchQueryHandlerFactory.getSearchParameter;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||
|
||||
import com.grack.nanojson.JsonArray;
|
||||
|
@ -30,8 +25,11 @@ import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandler;
|
|||
import org.schabi.newpipe.extractor.localization.Localization;
|
||||
import org.schabi.newpipe.extractor.localization.TimeAgoParser;
|
||||
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
||||
import org.schabi.newpipe.extractor.search.filter.FilterItem;
|
||||
import org.schabi.newpipe.extractor.services.youtube.YoutubeMetaInfoHelper;
|
||||
import org.schabi.newpipe.extractor.services.youtube.search.filter.YoutubeFilters;
|
||||
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
||||
import org.schabi.newpipe.extractor.utils.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
@ -63,8 +61,6 @@ import javax.annotation.Nullable;
|
|||
|
||||
public class YoutubeSearchExtractor extends SearchExtractor {
|
||||
|
||||
@Nullable
|
||||
private final String searchType;
|
||||
private final boolean extractVideoResults;
|
||||
private final boolean extractChannelResults;
|
||||
private final boolean extractPlaylistResults;
|
||||
|
@ -74,17 +70,20 @@ public class YoutubeSearchExtractor extends SearchExtractor {
|
|||
public YoutubeSearchExtractor(final StreamingService service,
|
||||
final SearchQueryHandler linkHandler) {
|
||||
super(service, linkHandler);
|
||||
final List<String> contentFilters = linkHandler.getContentFilters();
|
||||
searchType = isNullOrEmpty(contentFilters) ? null : contentFilters.get(0);
|
||||
final List<FilterItem> contentFilters = linkHandler.getContentFilters();
|
||||
|
||||
// Save whether we should extract video, channel and playlist results depending on the
|
||||
// requested search type, as YouTube returns sometimes videos inside channel search results
|
||||
// If no search type is provided or ALL filter is requested, extract everything
|
||||
extractVideoResults = searchType == null || ALL.equals(searchType)
|
||||
|| VIDEOS.equals(searchType);
|
||||
extractChannelResults = searchType == null || ALL.equals(searchType)
|
||||
|| CHANNELS.equals(searchType);
|
||||
extractPlaylistResults = searchType == null || ALL.equals(searchType)
|
||||
|| PLAYLISTS.equals(searchType);
|
||||
final boolean nullOrAll = contentFilters == null || contentFilters.isEmpty()
|
||||
|| contentFilters.stream()
|
||||
.anyMatch(item -> item.getIdentifier() == YoutubeFilters.ID_CF_MAIN_ALL);
|
||||
extractVideoResults = nullOrAll || contentFilters.stream()
|
||||
.anyMatch(item -> item.getIdentifier() == YoutubeFilters.ID_CF_MAIN_VIDEOS);
|
||||
extractChannelResults = nullOrAll || contentFilters.stream()
|
||||
.anyMatch(item -> item.getIdentifier() == YoutubeFilters.ID_CF_MAIN_CHANNELS);
|
||||
extractPlaylistResults = nullOrAll || contentFilters.stream()
|
||||
.anyMatch(item -> item.getIdentifier() == YoutubeFilters.ID_CF_MAIN_PLAYLISTS);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -92,7 +91,12 @@ public class YoutubeSearchExtractor extends SearchExtractor {
|
|||
ExtractionException {
|
||||
final String query = super.getSearchString();
|
||||
final Localization localization = getExtractorLocalization();
|
||||
final String params = getSearchParameter(searchType);
|
||||
|
||||
final YoutubeFilters.YoutubeContentFilterItem contentFilterItem =
|
||||
Utils.getFirstContentFilterItem(getLinkHandler());
|
||||
// Get the search parameter for the request. If getParams() be null
|
||||
// (which should never happen - only in test cases), JsonWriter.string() can handle it
|
||||
final String params = (contentFilterItem != null) ? contentFilterItem.getParams() : null;
|
||||
|
||||
final JsonBuilder<JsonObject> jsonBody = prepareDesktopJsonBuilder(localization,
|
||||
getExtractorContentCountry())
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
|
||||
package org.schabi.newpipe.extractor.services.youtube.linkHandler;
|
||||
|
||||
import org.schabi.newpipe.extractor.search.filter.FilterItem;
|
||||
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
|
||||
|
@ -53,12 +55,14 @@ public final class YoutubeChannelLinkHandlerFactory extends ListLinkHandlerFacto
|
|||
* Returns the URL to a channel from an ID.
|
||||
*
|
||||
* @param id the channel ID including e.g. 'channel/'
|
||||
* @param contentFilters
|
||||
* @param searchFilter
|
||||
* @return the URL to the channel
|
||||
*/
|
||||
@Override
|
||||
public String getUrl(final String id,
|
||||
final List<String> contentFilters,
|
||||
final String searchFilter)
|
||||
final List<FilterItem> contentFilters,
|
||||
final List<FilterItem> searchFilter)
|
||||
throws ParsingException, UnsupportedOperationException {
|
||||
return "https://www.youtube.com/" + id;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package org.schabi.newpipe.extractor.services.youtube.linkHandler;
|
||||
|
||||
import org.schabi.newpipe.extractor.search.filter.FilterItem;
|
||||
|
||||
import org.schabi.newpipe.extractor.exceptions.FoundAdException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
|
||||
|
@ -44,8 +46,8 @@ public final class YoutubeCommentsLinkHandlerFactory extends ListLinkHandlerFact
|
|||
|
||||
@Override
|
||||
public String getUrl(final String id,
|
||||
final List<String> contentFilter,
|
||||
final String sortFilter)
|
||||
final List<FilterItem> contentFilter,
|
||||
final List<FilterItem> sortFilter)
|
||||
throws ParsingException, UnsupportedOperationException {
|
||||
return getUrl(id);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
|
|||
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper;
|
||||
import org.schabi.newpipe.extractor.utils.Utils;
|
||||
|
||||
import org.schabi.newpipe.extractor.search.filter.FilterItem;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
@ -25,8 +27,9 @@ public final class YoutubePlaylistLinkHandlerFactory extends ListLinkHandlerFact
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getUrl(final String id, final List<String> contentFilters,
|
||||
final String sortFilter)
|
||||
public String getUrl(final String id,
|
||||
final List<FilterItem> contentFilters,
|
||||
final List<FilterItem> sortFilter)
|
||||
throws ParsingException, UnsupportedOperationException {
|
||||
return "https://www.youtube.com/playlist?list=" + id;
|
||||
}
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
package org.schabi.newpipe.extractor.services.youtube.linkHandler;
|
||||
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.encodeUrlUtf8;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.search.filter.FilterItem;
|
||||
import org.schabi.newpipe.extractor.services.youtube.search.filter.YoutubeFilters;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public final class YoutubeSearchQueryHandlerFactory extends SearchQueryHandlerFactory {
|
||||
|
||||
private static final YoutubeSearchQueryHandlerFactory INSTANCE =
|
||||
new YoutubeSearchQueryHandlerFactory();
|
||||
|
||||
public static final String ALL = "all";
|
||||
public static final String VIDEOS = "videos";
|
||||
public static final String CHANNELS = "channels";
|
||||
|
@ -27,8 +22,12 @@ public final class YoutubeSearchQueryHandlerFactory extends SearchQueryHandlerFa
|
|||
public static final String MUSIC_PLAYLISTS = "music_playlists";
|
||||
public static final String MUSIC_ARTISTS = "music_artists";
|
||||
|
||||
private static final String SEARCH_URL = "https://www.youtube.com/results?search_query=";
|
||||
private static final String MUSIC_SEARCH_URL = "https://music.youtube.com/search?q=";
|
||||
private static final YoutubeSearchQueryHandlerFactory INSTANCE =
|
||||
new YoutubeSearchQueryHandlerFactory();
|
||||
|
||||
private YoutubeSearchQueryHandlerFactory() {
|
||||
super(new YoutubeFilters());
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static YoutubeSearchQueryHandlerFactory getInstance() {
|
||||
|
@ -37,73 +36,10 @@ public final class YoutubeSearchQueryHandlerFactory extends SearchQueryHandlerFa
|
|||
|
||||
@Override
|
||||
public String getUrl(final String searchString,
|
||||
@Nonnull final List<String> contentFilters,
|
||||
final String sortFilter)
|
||||
throws ParsingException, UnsupportedOperationException {
|
||||
try {
|
||||
if (!contentFilters.isEmpty()) {
|
||||
final String contentFilter = contentFilters.get(0);
|
||||
switch (contentFilter) {
|
||||
case VIDEOS:
|
||||
return SEARCH_URL + encodeUrlUtf8(searchString)
|
||||
+ "&sp=EgIQAfABAQ%253D%253D";
|
||||
case CHANNELS:
|
||||
return SEARCH_URL + encodeUrlUtf8(searchString)
|
||||
+ "&sp=EgIQAvABAQ%253D%253D";
|
||||
case PLAYLISTS:
|
||||
return SEARCH_URL + encodeUrlUtf8(searchString)
|
||||
+ "&sp=EgIQA_ABAQ%253D%253D";
|
||||
case MUSIC_SONGS:
|
||||
case MUSIC_VIDEOS:
|
||||
case MUSIC_ALBUMS:
|
||||
case MUSIC_PLAYLISTS:
|
||||
case MUSIC_ARTISTS:
|
||||
return MUSIC_SEARCH_URL + encodeUrlUtf8(searchString);
|
||||
}
|
||||
}
|
||||
|
||||
return SEARCH_URL + encodeUrlUtf8(searchString) + "&sp=8AEB";
|
||||
} catch (final UnsupportedEncodingException e) {
|
||||
throw new ParsingException("Could not encode query", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getAvailableContentFilter() {
|
||||
return new String[]{
|
||||
ALL,
|
||||
VIDEOS,
|
||||
CHANNELS,
|
||||
PLAYLISTS,
|
||||
MUSIC_SONGS,
|
||||
MUSIC_VIDEOS,
|
||||
MUSIC_ALBUMS,
|
||||
MUSIC_PLAYLISTS
|
||||
// MUSIC_ARTISTS
|
||||
};
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static String getSearchParameter(final String contentFilter) {
|
||||
if (isNullOrEmpty(contentFilter)) {
|
||||
return "8AEB";
|
||||
}
|
||||
|
||||
switch (contentFilter) {
|
||||
case VIDEOS:
|
||||
return "EgIQAfABAQ%3D%3D";
|
||||
case CHANNELS:
|
||||
return "EgIQAvABAQ%3D%3D";
|
||||
case PLAYLISTS:
|
||||
return "EgIQA_ABAQ%3D%3D";
|
||||
case MUSIC_SONGS:
|
||||
case MUSIC_VIDEOS:
|
||||
case MUSIC_ALBUMS:
|
||||
case MUSIC_PLAYLISTS:
|
||||
case MUSIC_ARTISTS:
|
||||
return "";
|
||||
default:
|
||||
return "8AEB";
|
||||
}
|
||||
@Nonnull final List<FilterItem> selectedContentFilter,
|
||||
final List<FilterItem> selectedSortFilter) throws ParsingException {
|
||||
searchFilters.setSelectedContentFilter(selectedContentFilter);
|
||||
searchFilters.setSelectedSortFilter(selectedSortFilter);
|
||||
return searchFilters.evaluateSelectedFilters(searchString);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import static org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper
|
|||
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.search.filter.FilterItem;
|
||||
import org.schabi.newpipe.extractor.utils.Utils;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
|
@ -44,8 +45,8 @@ public final class YoutubeTrendingLinkHandlerFactory extends ListLinkHandlerFact
|
|||
}
|
||||
|
||||
public String getUrl(final String id,
|
||||
final List<String> contentFilters,
|
||||
final String sortFilter)
|
||||
final List<FilterItem> contentFilters,
|
||||
final List<FilterItem> sortFilter)
|
||||
throws ParsingException, UnsupportedOperationException {
|
||||
return "https://www.youtube.com/feed/trending";
|
||||
}
|
||||
|
|
|
@ -0,0 +1,504 @@
|
|||
// Created by evermind-zz 2022, licensed GNU GPL version 3 or later
|
||||
|
||||
package org.schabi.newpipe.extractor.services.youtube.search.filter;
|
||||
|
||||
import org.schabi.newpipe.extractor.search.filter.BaseSearchFilters;
|
||||
import org.schabi.newpipe.extractor.search.filter.FilterContainer;
|
||||
import org.schabi.newpipe.extractor.search.filter.FilterGroup;
|
||||
import org.schabi.newpipe.extractor.search.filter.FilterItem;
|
||||
import org.schabi.newpipe.extractor.services.youtube.search.filter.protobuf.DateFilter;
|
||||
import org.schabi.newpipe.extractor.services.youtube.search.filter.protobuf.Features;
|
||||
import org.schabi.newpipe.extractor.services.youtube.search.filter.protobuf.LengthFilter;
|
||||
import org.schabi.newpipe.extractor.services.youtube.search.filter.protobuf.SortOrder;
|
||||
import org.schabi.newpipe.extractor.services.youtube.search.filter.protobuf.TypeFilter;
|
||||
import org.schabi.newpipe.extractor.utils.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
|
||||
public class YoutubeFilters extends BaseSearchFilters {
|
||||
public static final String UTF_8 = "UTF-8";
|
||||
|
||||
/**
|
||||
* 'ALL' this is the default search content filter.
|
||||
* It has all sort filters that are available.
|
||||
*/
|
||||
public static final String ALL = "all";
|
||||
public static final String VIDEOS = "videos";
|
||||
public static final String CHANNELS = "channels";
|
||||
public static final String PLAYLISTS = "playlists";
|
||||
// public static final String MOVIES = "movies";
|
||||
|
||||
public static final int ID_CF_MAIN_GRP = 0;
|
||||
public static final int ID_CF_MAIN_ALL = 1;
|
||||
public static final int ID_CF_MAIN_VIDEOS = 2;
|
||||
public static final int ID_CF_MAIN_CHANNELS = 3;
|
||||
public static final int ID_CF_MAIN_PLAYLISTS = 4;
|
||||
// public static final int ID_CF_MAIN_MOVIES = 5;
|
||||
public static final int ID_CF_MAIN_YOUTUBE_MUSIC_SONGS = 6;
|
||||
public static final int ID_CF_MAIN_YOUTUBE_MUSIC_VIDEOS = 7;
|
||||
public static final int ID_CF_MAIN_YOUTUBE_MUSIC_ALBUMS = 8;
|
||||
public static final int ID_CF_MAIN_YOUTUBE_MUSIC_PLAYLISTS = 9;
|
||||
public static final int ID_CF_MAIN_YOUTUBE_MUSIC_ARTISTS = 10;
|
||||
public static final int ID_SF_SORT_BY_GRP = 11;
|
||||
public static final int ID_SF_SORT_BY_RELEVANCE = 12;
|
||||
public static final int ID_SF_SORT_BY_RATING = 13;
|
||||
public static final int ID_SF_SORT_BY_DATE = 14;
|
||||
public static final int ID_SF_SORT_BY_VIEWS = 15;
|
||||
public static final int ID_SF_UPLOAD_DATE_GRP = 16;
|
||||
public static final int ID_SF_UPLOAD_DATE_ALL = 17;
|
||||
public static final int ID_SF_UPLOAD_DATE_HOUR = 18;
|
||||
public static final int ID_SF_UPLOAD_DATE_DAY = 19;
|
||||
public static final int ID_SF_UPLOAD_DATE_WEEK = 20;
|
||||
public static final int ID_SF_UPLOAD_DATE_MONTH = 21;
|
||||
public static final int ID_SF_UPLOAD_DATE_YEAR = 22;
|
||||
public static final int ID_SF_DURATION_GRP = 23;
|
||||
public static final int ID_SF_DURATION_ALL = 24;
|
||||
public static final int ID_SF_DURATION_SHORT = 25;
|
||||
public static final int ID_SF_DURATION_MEDIUM = 26;
|
||||
public static final int ID_SF_DURATION_LONG = 27;
|
||||
public static final int ID_SF_FEATURES_GRP = 28;
|
||||
public static final int ID_SF_FEATURES_LIVE = 29;
|
||||
public static final int ID_SF_FEATURES_4K = 30;
|
||||
public static final int ID_SF_FEATURES_HD = 31;
|
||||
public static final int ID_SF_FEATURES_SUBTITLES = 32;
|
||||
public static final int ID_SF_FEATURES_CCOMMONS = 33;
|
||||
public static final int ID_SF_FEATURES_360 = 34;
|
||||
public static final int ID_SF_FEATURES_VR180 = 35;
|
||||
public static final int ID_SF_FEATURES_3D = 36;
|
||||
public static final int ID_SF_FEATURES_HDR = 37;
|
||||
public static final int ID_SF_FEATURES_LOCATION = 38;
|
||||
public static final int ID_SF_FEATURES_PURCHASED = 39;
|
||||
|
||||
public static final String MUSIC_SONGS = "music_songs";
|
||||
public static final String MUSIC_VIDEOS = "music_videos";
|
||||
public static final String MUSIC_ALBUMS = "music_albums";
|
||||
public static final String MUSIC_PLAYLISTS = "music_playlists";
|
||||
public static final String MUSIC_ARTISTS = "music_artists";
|
||||
|
||||
private static final String SEARCH_URL = "https://www.youtube.com/results?search_query=";
|
||||
private static final String MUSIC_SEARCH_URL = "https://music.youtube.com/search?q=";
|
||||
|
||||
/**
|
||||
* generate the search parameter protobuf 'sp' string that is appended to the search URL.
|
||||
*
|
||||
* @param contentFilterItem the active content filter item
|
||||
* @return the protobuf base64 encoded 'sp' parameter
|
||||
*/
|
||||
private String generateYoutubeSpParameter(final YoutubeContentFilterItem contentFilterItem) {
|
||||
boolean atLeastOneParamSet = false;
|
||||
final YoutubeProtoBufferSearchParameterAccessor.Builder builder =
|
||||
new YoutubeProtoBufferSearchParameterAccessor.Builder();
|
||||
final TypeFilter typeFilter = (contentFilterItem != null)
|
||||
? contentFilterItem.getContentType()
|
||||
: null;
|
||||
|
||||
// set content filter item in builder
|
||||
if (contentFilterItem != null) {
|
||||
atLeastOneParamSet = true;
|
||||
builder.setTypeFilter(typeFilter);
|
||||
}
|
||||
|
||||
if (selectedSortFilter != null) {
|
||||
for (final FilterItem sortItem : selectedSortFilter) {
|
||||
if (checkSortFilterItemAndSetInBuilder(builder, sortItem)) {
|
||||
atLeastOneParamSet = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (atLeastOneParamSet) {
|
||||
return builder.build().getSp();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if suitable youtube sort filter and set it in the builder.
|
||||
*
|
||||
* @param builder the builder for protobuf
|
||||
* @param sortItem the item to check and add
|
||||
* @return true if item was set in the builder
|
||||
*/
|
||||
private boolean checkSortFilterItemAndSetInBuilder(
|
||||
final YoutubeProtoBufferSearchParameterAccessor.Builder builder,
|
||||
final FilterItem sortItem) {
|
||||
boolean atLeastOneParamSet = false;
|
||||
if (sortItem instanceof YoutubeSortOrderSortFilterItem) {
|
||||
final SortOrder sortOrder = ((YoutubeSortOrderSortFilterItem) sortItem).get();
|
||||
if (null != sortOrder) {
|
||||
builder.setSortOrder(sortOrder);
|
||||
atLeastOneParamSet = true;
|
||||
}
|
||||
} else if (sortItem instanceof YoutubeDateSortFilterItem) {
|
||||
final DateFilter dateFilter = ((YoutubeDateSortFilterItem) sortItem).get();
|
||||
if (null != dateFilter) {
|
||||
builder.setDateFilter(dateFilter);
|
||||
atLeastOneParamSet = true;
|
||||
}
|
||||
} else if (sortItem instanceof YoutubeLenSortFilterItem) {
|
||||
final LengthFilter lengthFilter = ((YoutubeLenSortFilterItem) sortItem).get();
|
||||
if (null != lengthFilter) {
|
||||
builder.setLengthFilter(lengthFilter);
|
||||
atLeastOneParamSet = true;
|
||||
}
|
||||
} else if (sortItem instanceof YoutubeFeatureSortFilterItem) {
|
||||
final Features feature = ((YoutubeFeatureSortFilterItem) sortItem).get();
|
||||
if (null != feature) {
|
||||
builder.addFeature(feature);
|
||||
atLeastOneParamSet = true;
|
||||
}
|
||||
}
|
||||
return atLeastOneParamSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String evaluateSelectedFilters(final String searchString) {
|
||||
String sp = null;
|
||||
if (selectedContentFilter != null && !selectedContentFilter.isEmpty()) {
|
||||
// as of now there is just one content filter available
|
||||
final YoutubeContentFilterItem contentFilterItem =
|
||||
(YoutubeContentFilterItem) selectedContentFilter.get(0);
|
||||
|
||||
sp = generateYoutubeSpParameter(contentFilterItem);
|
||||
|
||||
if (contentFilterItem instanceof MusicYoutubeContentFilterItem) {
|
||||
try {
|
||||
return MUSIC_SEARCH_URL
|
||||
+ Utils.encodeUrlUtf8(searchString);
|
||||
} catch (final UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
if (contentFilterItem != null) {
|
||||
contentFilterItem.setParams(sp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return SEARCH_URL
|
||||
+ Utils.encodeUrlUtf8(searchString)
|
||||
+ ((null != sp && !sp.isEmpty()) ? "&sp=" + sp : "");
|
||||
} catch (final UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("checkstyle:MethodLength")
|
||||
@Override
|
||||
protected void init() {
|
||||
/* sort filters */
|
||||
|
||||
/* 'Sort order' filter items */
|
||||
groupsFactory.addFilterItem(new YoutubeSortOrderSortFilterItem(
|
||||
ID_SF_SORT_BY_RELEVANCE, "Relevance", SortOrder.relevance));
|
||||
groupsFactory.addFilterItem(new YoutubeSortOrderSortFilterItem(
|
||||
ID_SF_SORT_BY_RATING, "Rating", SortOrder.rating));
|
||||
groupsFactory.addFilterItem(new YoutubeSortOrderSortFilterItem(
|
||||
ID_SF_SORT_BY_DATE, "Date", SortOrder.date));
|
||||
groupsFactory.addFilterItem(new YoutubeSortOrderSortFilterItem(
|
||||
ID_SF_SORT_BY_VIEWS, "Views", SortOrder.views));
|
||||
|
||||
/* 'Date' filter items */
|
||||
groupsFactory.addFilterItem(new YoutubeDateSortFilterItem(
|
||||
ID_SF_UPLOAD_DATE_ALL, "All", null));
|
||||
groupsFactory.addFilterItem(new YoutubeDateSortFilterItem(
|
||||
ID_SF_UPLOAD_DATE_HOUR, "Hour", DateFilter.hour));
|
||||
groupsFactory.addFilterItem(new YoutubeDateSortFilterItem(
|
||||
ID_SF_UPLOAD_DATE_DAY, "Day", DateFilter.day));
|
||||
groupsFactory.addFilterItem(new YoutubeDateSortFilterItem(
|
||||
ID_SF_UPLOAD_DATE_WEEK, "Week", DateFilter.week));
|
||||
groupsFactory.addFilterItem(new YoutubeDateSortFilterItem(
|
||||
ID_SF_UPLOAD_DATE_MONTH, "Month", DateFilter.month));
|
||||
groupsFactory.addFilterItem(new YoutubeDateSortFilterItem(
|
||||
ID_SF_UPLOAD_DATE_YEAR, "Year", DateFilter.year));
|
||||
|
||||
/* 'Duration' filter items */
|
||||
groupsFactory.addFilterItem(new YoutubeLenSortFilterItem(
|
||||
ID_SF_DURATION_ALL, "All", null));
|
||||
groupsFactory.addFilterItem(new YoutubeLenSortFilterItem(
|
||||
ID_SF_DURATION_SHORT, "Under 4 min", LengthFilter.duration_short));
|
||||
groupsFactory.addFilterItem(new YoutubeLenSortFilterItem(
|
||||
ID_SF_DURATION_MEDIUM, "4-20 min", LengthFilter.duration_medium));
|
||||
groupsFactory.addFilterItem(new YoutubeLenSortFilterItem(
|
||||
ID_SF_DURATION_LONG, "Over 20 min", LengthFilter.duration_long));
|
||||
|
||||
/* 'features' filter items */
|
||||
groupsFactory.addFilterItem(new YoutubeFeatureSortFilterItem(
|
||||
ID_SF_FEATURES_LIVE, "Live", Features.live));
|
||||
groupsFactory.addFilterItem(new YoutubeFeatureSortFilterItem(
|
||||
ID_SF_FEATURES_4K, "4k", Features.is_4k));
|
||||
groupsFactory.addFilterItem(new YoutubeFeatureSortFilterItem(
|
||||
ID_SF_FEATURES_HD, "HD", Features.is_hd));
|
||||
groupsFactory.addFilterItem(new YoutubeFeatureSortFilterItem(
|
||||
ID_SF_FEATURES_SUBTITLES, "Subtitles", Features.subtitles));
|
||||
groupsFactory.addFilterItem(new YoutubeFeatureSortFilterItem(
|
||||
ID_SF_FEATURES_CCOMMONS, "Ccommons", Features.ccommons));
|
||||
groupsFactory.addFilterItem(new YoutubeFeatureSortFilterItem(
|
||||
ID_SF_FEATURES_360, "360°", Features.is_360));
|
||||
groupsFactory.addFilterItem(new YoutubeFeatureSortFilterItem(
|
||||
ID_SF_FEATURES_VR180, "VR180", Features.is_vr180));
|
||||
groupsFactory.addFilterItem(new YoutubeFeatureSortFilterItem(
|
||||
ID_SF_FEATURES_3D, "3d", Features.is_3d));
|
||||
groupsFactory.addFilterItem(new YoutubeFeatureSortFilterItem(
|
||||
ID_SF_FEATURES_HDR, "Hdr", Features.is_hdr));
|
||||
groupsFactory.addFilterItem(new YoutubeFeatureSortFilterItem(
|
||||
ID_SF_FEATURES_LOCATION, "Location", Features.location));
|
||||
groupsFactory.addFilterItem(new YoutubeFeatureSortFilterItem(
|
||||
ID_SF_FEATURES_PURCHASED, "Purchased", Features.purchased));
|
||||
|
||||
final FilterGroup sortByGroup =
|
||||
groupsFactory.createFilterGroup(ID_SF_SORT_BY_GRP, "Sort by", true,
|
||||
ID_SF_SORT_BY_RELEVANCE, new FilterItem[]{
|
||||
groupsFactory.getFilterForId(ID_SF_SORT_BY_RELEVANCE),
|
||||
groupsFactory.getFilterForId(ID_SF_SORT_BY_RATING),
|
||||
groupsFactory.getFilterForId(ID_SF_SORT_BY_DATE),
|
||||
groupsFactory.getFilterForId(ID_SF_SORT_BY_VIEWS),
|
||||
}, null);
|
||||
|
||||
final FilterGroup uploadDateGroup =
|
||||
groupsFactory.createFilterGroup(ID_SF_UPLOAD_DATE_GRP, "Upload Date", true,
|
||||
ID_SF_UPLOAD_DATE_ALL, new FilterItem[]{
|
||||
groupsFactory.getFilterForId(ID_SF_UPLOAD_DATE_ALL),
|
||||
groupsFactory.getFilterForId(ID_SF_UPLOAD_DATE_HOUR),
|
||||
groupsFactory.getFilterForId(ID_SF_UPLOAD_DATE_DAY),
|
||||
groupsFactory.getFilterForId(ID_SF_UPLOAD_DATE_WEEK),
|
||||
groupsFactory.getFilterForId(ID_SF_UPLOAD_DATE_MONTH),
|
||||
groupsFactory.getFilterForId(ID_SF_UPLOAD_DATE_YEAR),
|
||||
}, null);
|
||||
|
||||
final FilterGroup durationGroup =
|
||||
groupsFactory.createFilterGroup(ID_SF_DURATION_GRP, "Duration", true,
|
||||
ID_SF_DURATION_ALL, new FilterItem[]{
|
||||
groupsFactory.getFilterForId(ID_SF_DURATION_ALL),
|
||||
groupsFactory.getFilterForId(ID_SF_DURATION_SHORT),
|
||||
groupsFactory.getFilterForId(ID_SF_DURATION_MEDIUM),
|
||||
groupsFactory.getFilterForId(ID_SF_DURATION_LONG),
|
||||
}, null);
|
||||
|
||||
final FilterGroup featureGroup =
|
||||
groupsFactory.createFilterGroup(ID_SF_FEATURES_GRP, "Features", false,
|
||||
FilterContainer.ITEM_IDENTIFIER_UNKNOWN, new FilterItem[]{
|
||||
groupsFactory.getFilterForId(ID_SF_FEATURES_LIVE),
|
||||
groupsFactory.getFilterForId(ID_SF_FEATURES_4K),
|
||||
groupsFactory.getFilterForId(ID_SF_FEATURES_HD),
|
||||
groupsFactory.getFilterForId(ID_SF_FEATURES_SUBTITLES),
|
||||
groupsFactory.getFilterForId(ID_SF_FEATURES_CCOMMONS),
|
||||
groupsFactory.getFilterForId(ID_SF_FEATURES_360),
|
||||
groupsFactory.getFilterForId(ID_SF_FEATURES_VR180),
|
||||
groupsFactory.getFilterForId(ID_SF_FEATURES_3D),
|
||||
groupsFactory.getFilterForId(ID_SF_FEATURES_HDR),
|
||||
groupsFactory.getFilterForId(ID_SF_FEATURES_LOCATION),
|
||||
// there is not use for that feature ATM.
|
||||
// groupsFactory.getFilterForId(ID_SF_FEATURES_PURCHASED),
|
||||
}, null);
|
||||
|
||||
final FilterGroup[] videoFilters = new FilterGroup[]{
|
||||
sortByGroup,
|
||||
uploadDateGroup,
|
||||
durationGroup,
|
||||
featureGroup
|
||||
};
|
||||
|
||||
final FilterGroup[] channelPlaylistFilters = new FilterGroup[]{sortByGroup};
|
||||
|
||||
/* videoFilters contains all sort filters available */
|
||||
final FilterContainer allSortFilters = new FilterContainer(videoFilters);
|
||||
final FilterContainer sortFiltersForChannelAndPlaylists =
|
||||
new FilterContainer(channelPlaylistFilters);
|
||||
|
||||
addContentFilterTypeAndSortVariant(ID_CF_MAIN_ALL, allSortFilters);
|
||||
addContentFilterTypeAndSortVariant(ID_CF_MAIN_VIDEOS, allSortFilters);
|
||||
addContentFilterTypeAndSortVariant(ID_CF_MAIN_CHANNELS, sortFiltersForChannelAndPlaylists);
|
||||
addContentFilterTypeAndSortVariant(ID_CF_MAIN_PLAYLISTS, sortFiltersForChannelAndPlaylists);
|
||||
|
||||
/*
|
||||
// -> movies are only available for logged in users
|
||||
addContentFilterTypeAndSortVariant(ID_CF_MAIN_MOVIES, new FilterContainer(new FilterGroup[]{
|
||||
sortByGroup,
|
||||
uploadDateGroup,
|
||||
durationGroup,
|
||||
groupsFactory.createSortGroup(ID_SF_FEATURES_GRP, "Features", false,
|
||||
Filter.ITEM_IDENTIFIER_UNKNOWN, new FilterItem[]{
|
||||
groupsFactory.getFilterForId(ID_SF_FEATURES_4K),
|
||||
groupsFactory.getFilterForId(ID_SF_FEATURES_HD),
|
||||
groupsFactory.getFilterForId(ID_SF_FEATURES_360),
|
||||
groupsFactory.getFilterForId(ID_SF_FEATURES_VR180),
|
||||
groupsFactory.getFilterForId(ID_SF_FEATURES_HDR),
|
||||
groupsFactory.getFilterForId(ID_SF_FEATURES_LOCATION),
|
||||
// there is not use for that feature ATM.
|
||||
// groupsFactory.getFilterForId(ID_SF_FEATURES_PURCHASED),
|
||||
|
||||
|
||||
|
||||
}, null)
|
||||
});
|
||||
*/
|
||||
|
||||
/* content filters with sort filters */
|
||||
groupsFactory.addFilterItem(new YoutubeContentFilterItem(
|
||||
ID_CF_MAIN_ALL, ALL, null));
|
||||
groupsFactory.addFilterItem(new YoutubeContentFilterItem(
|
||||
ID_CF_MAIN_VIDEOS, VIDEOS, TypeFilter.video));
|
||||
groupsFactory.addFilterItem(new YoutubeContentFilterItem(
|
||||
ID_CF_MAIN_CHANNELS, CHANNELS, TypeFilter.channel));
|
||||
groupsFactory.addFilterItem(new YoutubeContentFilterItem(
|
||||
ID_CF_MAIN_PLAYLISTS, PLAYLISTS, TypeFilter.playlist));
|
||||
/*
|
||||
// movies are only available for logged in users
|
||||
builder.addFilterItem(new YoutubeContentFilterItem(
|
||||
ID_CF_MAIN_MOVIES, MOVIES, TypeFilter.movie));
|
||||
*/
|
||||
|
||||
/* Youtube Music content filters */
|
||||
groupsFactory.addFilterItem(new MusicYoutubeContentFilterItem(
|
||||
ID_CF_MAIN_YOUTUBE_MUSIC_SONGS, MUSIC_SONGS,
|
||||
"Eg-KAQwIARAAGAAgACgAMABqChAEEAUQAxAKEAk%3D"
|
||||
));
|
||||
groupsFactory.addFilterItem(new MusicYoutubeContentFilterItem(
|
||||
ID_CF_MAIN_YOUTUBE_MUSIC_VIDEOS, MUSIC_VIDEOS,
|
||||
"Eg-KAQwIABABGAAgACgAMABqChAEEAUQAxAKEAk%3D"
|
||||
));
|
||||
groupsFactory.addFilterItem(new MusicYoutubeContentFilterItem(
|
||||
ID_CF_MAIN_YOUTUBE_MUSIC_ALBUMS, MUSIC_ALBUMS,
|
||||
"Eg-KAQwIABAAGAEgACgAMABqChAEEAUQAxAKEAk%3D"
|
||||
));
|
||||
groupsFactory.addFilterItem(new MusicYoutubeContentFilterItem(
|
||||
ID_CF_MAIN_YOUTUBE_MUSIC_PLAYLISTS, MUSIC_PLAYLISTS,
|
||||
"Eg-KAQwIABAAGAAgACgBMABqChAEEAUQAxAKEAk%3D"
|
||||
));
|
||||
groupsFactory.addFilterItem(new MusicYoutubeContentFilterItem(
|
||||
ID_CF_MAIN_YOUTUBE_MUSIC_ARTISTS, MUSIC_ARTISTS,
|
||||
"Eg-KAQwIABAAGAAgASgAMABqChAEEAUQAxAKEAk%3D"
|
||||
));
|
||||
|
||||
final FilterGroup contentFilterGroup =
|
||||
groupsFactory.createFilterGroup(ID_CF_MAIN_GRP, null, true,
|
||||
ID_CF_MAIN_ALL, new FilterItem[]{
|
||||
groupsFactory.getFilterForId(ID_CF_MAIN_ALL),
|
||||
groupsFactory.getFilterForId(ID_CF_MAIN_VIDEOS),
|
||||
groupsFactory.getFilterForId(ID_CF_MAIN_CHANNELS),
|
||||
groupsFactory.getFilterForId(ID_CF_MAIN_PLAYLISTS),
|
||||
// groupsFactory.getFilterForId(ID_CF_MAIN_MOVIES),
|
||||
groupsFactory.getFilterForId(groupsFactory.addFilterItem(
|
||||
new FilterItem.DividerItem("YouTube Music"))),
|
||||
groupsFactory.getFilterForId(ID_CF_MAIN_YOUTUBE_MUSIC_SONGS),
|
||||
groupsFactory.getFilterForId(ID_CF_MAIN_YOUTUBE_MUSIC_VIDEOS),
|
||||
groupsFactory.getFilterForId(ID_CF_MAIN_YOUTUBE_MUSIC_ALBUMS),
|
||||
groupsFactory.getFilterForId(ID_CF_MAIN_YOUTUBE_MUSIC_PLAYLISTS),
|
||||
groupsFactory.getFilterForId(ID_CF_MAIN_YOUTUBE_MUSIC_ARTISTS),
|
||||
}, allSortFilters);
|
||||
addContentFilterGroup(contentFilterGroup);
|
||||
}
|
||||
|
||||
private void addContentFilterTypeAndSortVariant(final int contentFilterId,
|
||||
final FilterContainer variant) {
|
||||
addContentFilterSortVariant(contentFilterId, variant);
|
||||
}
|
||||
|
||||
private static class YoutubeSortOrderSortFilterItem extends YoutubeSortFilterItem {
|
||||
private final SortOrder sortOrder;
|
||||
|
||||
YoutubeSortOrderSortFilterItem(final int identifier, final String name,
|
||||
final SortOrder sortOrder) {
|
||||
super(identifier, name);
|
||||
this.sortOrder = sortOrder;
|
||||
}
|
||||
|
||||
public SortOrder get() {
|
||||
return sortOrder;
|
||||
}
|
||||
}
|
||||
|
||||
private static class YoutubeDateSortFilterItem extends YoutubeSortFilterItem {
|
||||
private final DateFilter dateFilter;
|
||||
|
||||
YoutubeDateSortFilterItem(final int identifier, final String name,
|
||||
final DateFilter dateFilter) {
|
||||
super(identifier, name);
|
||||
this.dateFilter = dateFilter;
|
||||
}
|
||||
|
||||
public DateFilter get() {
|
||||
return this.dateFilter;
|
||||
}
|
||||
}
|
||||
|
||||
private static class YoutubeLenSortFilterItem extends YoutubeSortFilterItem {
|
||||
private final LengthFilter lengthFilter;
|
||||
|
||||
YoutubeLenSortFilterItem(final int identifier, final String name,
|
||||
final LengthFilter lengthFilter) {
|
||||
super(identifier, name);
|
||||
this.lengthFilter = lengthFilter;
|
||||
}
|
||||
|
||||
public LengthFilter get() {
|
||||
return this.lengthFilter;
|
||||
}
|
||||
}
|
||||
|
||||
private static class YoutubeFeatureSortFilterItem extends YoutubeSortFilterItem {
|
||||
private final Features feature;
|
||||
|
||||
YoutubeFeatureSortFilterItem(final int identifier, final String name,
|
||||
final Features feature) {
|
||||
super(identifier, name);
|
||||
this.feature = feature;
|
||||
}
|
||||
|
||||
public Features get() {
|
||||
return this.feature;
|
||||
}
|
||||
}
|
||||
|
||||
public static class YoutubeSortFilterItem extends FilterItem {
|
||||
|
||||
public YoutubeSortFilterItem(final int identifier, final String name) {
|
||||
super(identifier, name);
|
||||
}
|
||||
}
|
||||
|
||||
public static class YoutubeContentFilterItem extends YoutubeSortFilterItem {
|
||||
protected String params;
|
||||
private TypeFilter contentType = null;
|
||||
|
||||
public YoutubeContentFilterItem(final int identifier, final String name) {
|
||||
super(identifier, name);
|
||||
}
|
||||
|
||||
public YoutubeContentFilterItem(final int identifier, final String name,
|
||||
final TypeFilter contentType) {
|
||||
super(identifier, name);
|
||||
this.params = "";
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
public String getParams() {
|
||||
return params;
|
||||
}
|
||||
|
||||
public void setParams(final String params) {
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
private TypeFilter getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MusicYoutubeContentFilterItem extends YoutubeContentFilterItem {
|
||||
public MusicYoutubeContentFilterItem(final int identifier, final String name,
|
||||
final String params) {
|
||||
super(identifier, name);
|
||||
this.params = params;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
// Created by evermind-zz 2022, licensed GNU GPL version 3 or later
|
||||
|
||||
package org.schabi.newpipe.extractor.services.youtube.search.filter;
|
||||
|
||||
import org.schabi.newpipe.extractor.services.youtube.search.filter.protobuf.DateFilter;
|
||||
import org.schabi.newpipe.extractor.services.youtube.search.filter.protobuf.ExtraFeatures;
|
||||
import org.schabi.newpipe.extractor.services.youtube.search.filter.protobuf.Extras;
|
||||
import org.schabi.newpipe.extractor.services.youtube.search.filter.protobuf.Features;
|
||||
import org.schabi.newpipe.extractor.services.youtube.search.filter.protobuf.Filters;
|
||||
import org.schabi.newpipe.extractor.services.youtube.search.filter.protobuf.LengthFilter;
|
||||
import org.schabi.newpipe.extractor.services.youtube.search.filter.protobuf.SearchRequest;
|
||||
import org.schabi.newpipe.extractor.services.youtube.search.filter.protobuf.SortOrder;
|
||||
import org.schabi.newpipe.extractor.services.youtube.search.filter.protobuf.TypeFilter;
|
||||
import org.schabi.newpipe.extractor.utils.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
|
||||
|
||||
/**
|
||||
* This class interacts with the auto generated proto buffer java files
|
||||
* created by the 'squareup.wire proto buffer' plugin.
|
||||
* <p>
|
||||
* Below proto buffer description file is used:
|
||||
* <a href="file:../main/proto/youtube-content-and-sort-filters.proto"
|
||||
* >youtube-content-and-sort-filters.proto</a>
|
||||
*/
|
||||
public final class YoutubeProtoBufferSearchParameterAccessor {
|
||||
|
||||
private static final String UTF_8 = "UTF-8";
|
||||
|
||||
/**
|
||||
* the base64 urlencoded sp string
|
||||
*/
|
||||
private String searchParameter = "";
|
||||
|
||||
private YoutubeProtoBufferSearchParameterAccessor() {
|
||||
}
|
||||
|
||||
@SuppressWarnings("NewApi")
|
||||
public String encodeSp(final SortOrder sort, final DateFilter date,
|
||||
final TypeFilter type, final LengthFilter length,
|
||||
final Features[] features, final ExtraFeatures[] extraFeatures)
|
||||
throws IOException {
|
||||
|
||||
final Filters.Builder filtersBuilder = new Filters.Builder();
|
||||
if (null != date) {
|
||||
filtersBuilder.date((long) date.getValue());
|
||||
}
|
||||
|
||||
if (null != type) {
|
||||
filtersBuilder.type((long) type.getValue());
|
||||
}
|
||||
|
||||
if (null != length) {
|
||||
filtersBuilder.length((long) length.getValue());
|
||||
}
|
||||
|
||||
if (null != features) {
|
||||
for (final Features feature : features) {
|
||||
setFeatureState(feature, true, filtersBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
final SearchRequest.Builder searchRequestBuilder = new SearchRequest.Builder();
|
||||
if (null != sort) {
|
||||
searchRequestBuilder.sorted((long) sort.getValue());
|
||||
}
|
||||
|
||||
if (null != date || null != type || null != length || null != features
|
||||
// even though extraFeatures is evaluated later. But in the case that extraFeatures
|
||||
// will be the only activated 'feature' we still need to generate Filters here
|
||||
// as it is integral part of the SearchRequest object
|
||||
|| null != extraFeatures) {
|
||||
final Filters filters = filtersBuilder.build();
|
||||
searchRequestBuilder.filter(filters);
|
||||
}
|
||||
|
||||
if (null != extraFeatures && extraFeatures.length > 0) {
|
||||
final Extras.Builder extrasBuilder = new Extras.Builder();
|
||||
for (final ExtraFeatures extra : extraFeatures) {
|
||||
setExtraState(extra, true, extrasBuilder);
|
||||
}
|
||||
final Extras extras = extrasBuilder.build();
|
||||
searchRequestBuilder.extras(extras);
|
||||
}
|
||||
|
||||
final SearchRequest searchRequest = searchRequestBuilder.build();
|
||||
|
||||
final byte[] protoBufEncoded = searchRequest.encode();
|
||||
final String protoBufEncodedBase64 = Base64.getEncoder()
|
||||
.encodeToString(protoBufEncoded);
|
||||
final String urlEncodedBase64EncodedSearchParameter
|
||||
= Utils.encodeUrlUtf8(protoBufEncodedBase64);
|
||||
|
||||
this.searchParameter = urlEncodedBase64EncodedSearchParameter;
|
||||
|
||||
return urlEncodedBase64EncodedSearchParameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a sp parameter back to a {@link SearchRequest} object
|
||||
*
|
||||
* @param urlEncodedBase64EncodedSearchParameter the parameter says it all
|
||||
* @return {@link SearchRequest} with decoded search parameter
|
||||
* @throws IOException
|
||||
*/
|
||||
@SuppressWarnings("NewApi")
|
||||
public SearchRequest decodeSp(final String urlEncodedBase64EncodedSearchParameter)
|
||||
throws IOException {
|
||||
final String urlDecodedBase64EncodedSearchParameter
|
||||
= Utils.decodeUrlUtf8(urlEncodedBase64EncodedSearchParameter);
|
||||
final byte[] decodedSearchParameter
|
||||
= Base64.getDecoder().decode(urlDecodedBase64EncodedSearchParameter);
|
||||
|
||||
return new SearchRequest.Builder().build().adapter().decode(decodedSearchParameter);
|
||||
}
|
||||
|
||||
public String getSp() {
|
||||
return this.searchParameter;
|
||||
}
|
||||
|
||||
private void setExtraState(final ExtraFeatures extra,
|
||||
final boolean enable,
|
||||
final Extras.Builder extrasBuilder) {
|
||||
switch (extra) {
|
||||
case verbatim:
|
||||
extrasBuilder.verbatim(enable);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void setFeatureState(final Features feature,
|
||||
final boolean enable,
|
||||
final Filters.Builder filtersBuilder) {
|
||||
switch (feature) {
|
||||
case live:
|
||||
filtersBuilder.live(enable);
|
||||
break;
|
||||
case is_4k:
|
||||
filtersBuilder.is_4k(enable);
|
||||
break;
|
||||
case is_hd:
|
||||
filtersBuilder.is_hd(enable);
|
||||
break;
|
||||
case subtitles:
|
||||
filtersBuilder.subtitles(enable);
|
||||
break;
|
||||
case ccommons:
|
||||
filtersBuilder.ccommons(enable);
|
||||
break;
|
||||
case is_360:
|
||||
filtersBuilder.is_360(enable);
|
||||
break;
|
||||
case is_vr180:
|
||||
filtersBuilder.is_vr180(enable);
|
||||
break;
|
||||
case is_3d:
|
||||
filtersBuilder.is_3d(enable);
|
||||
break;
|
||||
case is_hdr:
|
||||
filtersBuilder.is_hdr(enable);
|
||||
break;
|
||||
case location:
|
||||
filtersBuilder.location(enable);
|
||||
break;
|
||||
case purchased:
|
||||
filtersBuilder.purchased(enable);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a {@link YoutubeProtoBufferSearchParameterAccessor} Object
|
||||
*/
|
||||
public static class Builder {
|
||||
private final ArrayList<Features> featureList = new ArrayList<>();
|
||||
private final ArrayList<ExtraFeatures> extraFeatureList = new ArrayList<>();
|
||||
private final YoutubeProtoBufferSearchParameterAccessor searchParamGenerator =
|
||||
new YoutubeProtoBufferSearchParameterAccessor();
|
||||
private SortOrder sort = null;
|
||||
private DateFilter date = null;
|
||||
private TypeFilter type = null;
|
||||
private LengthFilter length = null;
|
||||
|
||||
public Builder setSortOrder(final SortOrder sortOrder) {
|
||||
this.sort = sortOrder;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDateFilter(final DateFilter dateFilter) {
|
||||
this.date = dateFilter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTypeFilter(final TypeFilter typeFilter) {
|
||||
this.type = typeFilter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setLengthFilter(final LengthFilter lengthFilter) {
|
||||
this.length = lengthFilter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addFeature(final Features feature) {
|
||||
this.featureList.add(feature);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addExtraFeature(final ExtraFeatures extra) {
|
||||
this.extraFeatureList.add(extra);
|
||||
return this;
|
||||
}
|
||||
|
||||
public YoutubeProtoBufferSearchParameterAccessor build() throws IOException {
|
||||
final Features[] features = this.featureList.toArray(new Features[0]);
|
||||
final ExtraFeatures[] extraFeat = this.extraFeatureList.toArray(new ExtraFeatures[0]);
|
||||
|
||||
this.searchParamGenerator
|
||||
.encodeSp(this.sort, this.date, this.type, this.length, features, extraFeat);
|
||||
return this.searchParamGenerator;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
// Created by evermind-zz 2022, licensed GNU GPL version 3 or later
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
package youtubesearchfilter;
|
||||
|
||||
option java_multiple_files = true;
|
||||
option java_package = "org.schabi.newpipe.extractor.services.youtube.search.filter.protobuf";
|
||||
|
||||
enum ExtraFeatures {
|
||||
verbatim = 0;
|
||||
}
|
||||
|
||||
message Extras {
|
||||
optional bool verbatim = 1;
|
||||
}
|
||||
|
||||
enum Features {
|
||||
live = 0;
|
||||
is_4k = 1;
|
||||
is_hd = 2;
|
||||
subtitles = 3;
|
||||
ccommons = 4;
|
||||
is_360 = 5;
|
||||
is_vr180 = 6;
|
||||
is_3d = 7;
|
||||
is_hdr = 8;
|
||||
location = 9;
|
||||
purchased = 10;
|
||||
}
|
||||
|
||||
message Filters {
|
||||
optional int64 date = 1;
|
||||
optional int64 type = 2;
|
||||
optional int64 length = 3;
|
||||
optional bool is_hd = 4;
|
||||
optional bool subtitles = 5;
|
||||
optional bool ccommons = 6;
|
||||
optional bool is_3d = 7;
|
||||
optional bool live = 8;
|
||||
optional bool purchased = 9;
|
||||
optional bool is_4k = 14;
|
||||
optional bool is_360 = 15;
|
||||
optional bool location = 23;
|
||||
optional bool is_hdr = 25;
|
||||
optional bool is_vr180 = 26;
|
||||
}
|
||||
|
||||
message SearchRequest {
|
||||
optional int64 sorted = 1;
|
||||
optional Filters filter = 2;
|
||||
optional Extras extras = 8;
|
||||
}
|
||||
|
||||
enum SortOrder { relevance=0; rating=1; date=2; views=3; }
|
||||
enum DateFilter { hour=1; day=2; week=3; month=4; year=5; }
|
||||
enum TypeFilter { video=1; channel=2; playlist=3; movie=4; show=5; }
|
||||
enum LengthFilter { duration_short=1; duration_long=2; duration_medium=3; }
|
Loading…
Reference in New Issue