Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Youtube search filters #124

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
635d147
Changes made:
Bluesir9 Oct 19, 2018
d18c68a
Changes made:
Bluesir9 Nov 8, 2018
7bab982
Synced changes from master into youtube_search_filters branch
Bluesir9 Nov 9, 2018
93239eb
Added .java-version file to gitignore and removed it from repo
Bluesir9 Nov 9, 2018
e788a0c
Merge branch 'master' into youtube_search_filters
Bluesir9 Nov 10, 2018
78bb968
Removed usage of DataTypeConverter class and replaced it with newly a…
Bluesir9 Nov 10, 2018
e906c37
Merge branch 'master' into youtube_search_filters
theScrabi Nov 13, 2018
f71b62f
Changes made:
Bluesir9 Dec 8, 2018
75ad8f9
Changes made:
Bluesir9 Dec 8, 2018
98b67a4
Changes made:
Bluesir9 Dec 16, 2018
b02ca4a
Added top level comment explaining modifications made to the Base64 c…
Bluesir9 Dec 16, 2018
6e8515d
Fix bug where index is used incorrectly
Bluesir9 Jan 3, 2019
5464d73
Merge branch 'master' into youtube_search_filters
theScrabi Mar 23, 2019
cd38c1b
Replace Base64 class introduced with Base64 encoding implementation o…
Bluesir9 Mar 23, 2019
56366eb
Merge branch 'dev' into youtube_search_filters
Bluesir9 Mar 23, 2019
7c80354
Create Base64Utils to work around alleged legacy apache commons codec…
Bluesir9 Mar 23, 2019
8152ccf
Add comment explaining purpose of Base64Utils and make bytes array ar…
Bluesir9 Mar 23, 2019
55b126a
Altered test cases to test all filters and sorters individually and u…
Bluesir9 Apr 9, 2019
2b771cb
Merge remote-tracking branch 'upstream/dev' into youtube_search_filters
Bluesir9 Dec 30, 2019
4cfd07b
Undo unnecessary change made to gitignore file
Bluesir9 Dec 30, 2019
68f9547
Change Movie filter name to Film and Show filter name to Programme
Bluesir9 Dec 30, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ gradle-app.setting

# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties

.java-version
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public abstract class SearchQueryHandlerFactory extends ListLinkHandlerFactory {
///////////////////////////////////

@Override
public abstract String getUrl(String querry, List<String> contentFilter, String sortFilter) throws ParsingException;
public abstract String getUrl(String query, List<String> contentFilter, String sortFilter) throws ParsingException;
public String getSearchString(String url) { return "";}

///////////////////////////////////
Expand All @@ -23,14 +23,14 @@ public abstract class SearchQueryHandlerFactory extends ListLinkHandlerFactory {
public String getId(String url) { return getSearchString(url); }

@Override
public SearchQueryHandler fromQuery(String querry,
public SearchQueryHandler fromQuery(String query,
List<String> contentFilter,
String sortFilter) throws ParsingException {
return new SearchQueryHandler(super.fromQuery(querry, contentFilter, sortFilter));
return new SearchQueryHandler(super.fromQuery(query, contentFilter, sortFilter));
}

public SearchQueryHandler fromQuery(String querry) throws ParsingException {
return fromQuery(querry, new ArrayList<String>(0), "");
public SearchQueryHandler fromQuery(String query) throws ParsingException {
return fromQuery(query, new ArrayList<String>(0), "");
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,83 @@

import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandlerFactory;
import org.schabi.newpipe.extractor.utils.Base64;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class YoutubeSearchQueryHandlerFactory extends SearchQueryHandlerFactory {

public static final String CHARSET_UTF_8 = "UTF-8";

public static final String VIDEOS = "videos";
public static final String CHANNELS = "channels";
public static final String PLAYLISTS = "playlists";
public static final String ALL = "all";
public enum FilterType {
Content((byte)0x10),
Time((byte)0x08),
Duration((byte)0x18);

private final byte value;

FilterType(byte value) {
this.value = value;
}
}

public enum Filter {
All(FilterType.Content,(byte)0),
TobiGr marked this conversation as resolved.
Show resolved Hide resolved
Video(FilterType.Content,(byte)0x01),
Channel(FilterType.Content,(byte)0x02),
Playlist(FilterType.Content,(byte)0x03),
Movie(FilterType.Content,(byte)0x04),
Show(FilterType.Content,(byte)0x05),

Hour(FilterType.Time,(byte)0x01),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NewPipe can not handle key value pair filter yet :/

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure what you mean but this works out if the box. Albeit it looks kinda bad:

Screenshot_1553365615

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this is the issue i ment, it is not possible to add or remove filters like key values. This messes up the UI as you see. The UI should be made ready to handle these filters.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could implement the filters at 2 levels? We would initially ask the user to select a FilterType and after they have chosen that, we can ask them the exact filter they wish to apply falling under that FilterType?

So, clicking on the menu icon the first time, will load a list containing Content, Time, Duration, Feature. If the user chooses Duration, then we will show a second list containing Short, Long. And when the user chooses one of those options, then the search is triggered. Does that make sense?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's finish this PR. 🎉 Thanks for being patient!

I am working on a proper design for the filters. This might take until next Sunday, because the new semesters is about to start and thus there are a bunch of other things to do. But I would not recommend to have endless lists. They come with bad UI/UX, because it is hard to get all the (important) information.

FYI:

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TobiGr Of course, take your time. I was just surprised and happy that it worked functionally out of the box.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put my UI ideas into this issue in the app repo. I guess a UI discussion fits better into the other repo.

Today(FilterType.Time,(byte)0x02),
Week(FilterType.Time,(byte)0x03),
Month(FilterType.Time,(byte)0x04),
Year(FilterType.Time,(byte)0x05),

Short(FilterType.Duration, (byte)0x01),
Long(FilterType.Duration, (byte)0x02);

private final FilterType type;
private final byte value;

Filter(FilterType type, byte value) {
this.type = type;
this.value = value;
}
}

public enum SorterType {
Default((byte)0x08);

private final byte value;

SorterType(byte value) {
this.value = value;
}
}

public enum Sorter {
Relevance(SorterType.Default, (byte)0x00),
Rating(SorterType.Default, (byte)0x01),
Upload_Date(SorterType.Default, (byte)0x02),
View_Count(SorterType.Default, (byte)0x03);

private final SorterType type;
private final byte value;

Sorter(SorterType type, byte value) {
this.type = type;
this.value = value;
}
}

public static YoutubeSearchQueryHandlerFactory getInstance() {
return new YoutubeSearchQueryHandlerFactory();
Expand All @@ -23,31 +87,118 @@ public static YoutubeSearchQueryHandlerFactory getInstance() {
@Override
public String getUrl(String searchString, List<String> contentFilters, String sortFilter) throws ParsingException {
try {
final String url = "https://www.youtube.com/results"
+ "?q=" + URLEncoder.encode(searchString, CHARSET_UTF_8);

if(contentFilters.size() > 0) {
switch (contentFilters.get(0)) {
case VIDEOS: return url + "&sp=EgIQAVAU";
case CHANNELS: return url + "&sp=EgIQAlAU";
case PLAYLISTS: return url + "&sp=EgIQA1AU";
case ALL:
default:
}
String returnURL = getSearchBaseUrl(searchString);
String filterQueryParams = getFilterQueryParams(contentFilters, sortFilter);
if(filterQueryParams != null) {
returnURL = returnURL + "&sp=" + filterQueryParams;
}

return url;
return returnURL;
} catch (UnsupportedEncodingException e) {
throw new ParsingException("Could not encode query", e);
}
}

@Override
public String[] getAvailableContentFilter() {
return new String[] {
ALL,
VIDEOS,
CHANNELS,
PLAYLISTS};
List<String> contentFiltersList = new ArrayList<>();
for(Filter contentFilter : Filter.values()) {
contentFiltersList.add(contentFilter.name());
}
String[] contentFiltersArray = new String[contentFiltersList.size()];
contentFiltersArray = contentFiltersList.toArray(contentFiltersArray);
return contentFiltersArray;
}

@Override
public String[] getAvailableSortFilter() {
List<String> sortFiltersList = new ArrayList<>();
for(Sorter sortFilter : Sorter.values()) {
sortFiltersList.add(sortFilter.name());
}
String[] sortFiltersArray = new String[sortFiltersList.size()];
sortFiltersArray = sortFiltersList.toArray(sortFiltersArray);
return sortFiltersArray;
}

private String getSearchBaseUrl(String searchQuery)
throws UnsupportedEncodingException {
return "https://www.youtube.com/results" +
"?q=" +
URLEncoder.encode(searchQuery, CHARSET_UTF_8);
}

@Nullable
private String getFilterQueryParams(List<String> contentFilters, String sortFilter)
throws UnsupportedEncodingException {
List<Byte> returnList = new ArrayList<>();
List<Byte> sortFilterParams = getSortFiltersQueryParam(sortFilter);
if(!sortFilterParams.isEmpty()) {
returnList.addAll(sortFilterParams);
}
List<Byte> contentFilterParams = getContentFiltersQueryParams(contentFilters);
if(!contentFilterParams.isEmpty()) {
returnList.add((byte)0x12);
returnList.add((byte)contentFilterParams.size());
returnList.addAll(contentFilterParams);
}

if(returnList.isEmpty()) {
return null;
}
return URLEncoder.encode(Base64.encodeToString(convert(returnList), Base64.URL_SAFE));
}

private List<Byte> getContentFiltersQueryParams(List<String> contentFilter) {
if(contentFilter == null || contentFilter.isEmpty()) {
return Collections.emptyList();
}
List<Byte> returnList = new ArrayList<>();
for(String filter : contentFilter) {
List<Byte> byteList = getContentFilterQueryParams(filter);
if(!byteList.isEmpty()) {
returnList.addAll(byteList);
}
}
return returnList;
}

private List<Byte> getContentFilterQueryParams(String filter) {
Filter contentFilter;
try {
contentFilter = Filter.valueOf(filter);
} catch (IllegalArgumentException iae) {
iae.printStackTrace();
System.err.println("Unknown content filter type provided = " + filter +", none will be applied");
return Collections.emptyList();
}
switch (contentFilter) {
case All:
return Collections.emptyList();
default:
return Arrays.asList(contentFilter.type.value, contentFilter.value);
}
}

private List<Byte> getSortFiltersQueryParam(String filter) {
if(filter == null || filter.isEmpty()) {
return Collections.emptyList();
}
Sorter sorter;
try {
sorter = Sorter.valueOf(filter);
} catch (IllegalArgumentException e) {
e.printStackTrace();
theScrabi marked this conversation as resolved.
Show resolved Hide resolved
System.err.println("Unknown sort filter = " + filter + " provided, none applied.");
return Collections.emptyList();
}
return Arrays.asList(sorter.type.value, sorter.value);
}

private byte[] convert(@Nonnull List<Byte> bigByteList) {
byte[] returnArray = new byte[bigByteList.size()];
for (int i = 0; i < bigByteList.size(); i++) {
returnArray[i] = bigByteList.get(i);
}
return returnArray;
}
}
Loading