aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2021-03-06 14:25:48 +0100
committerdec05eba <dec05eba@protonmail.com>2021-03-06 14:25:48 +0100
commitf8617f2043ea4ec536c4622df63a77b25268aeb0 (patch)
tree0db0d16a3bbc68c0564f7c7f9166f1cf71f1838b /src
parentd3091854805fc625e2f6109f6f4af579420cd2a5 (diff)
Youtube: fix channel next page, add proper channel search (non filter search)
Diffstat (limited to 'src')
-rw-r--r--src/QuickMedia.cpp94
-rw-r--r--src/plugins/Plugin.cpp1
-rw-r--r--src/plugins/Youtube.cpp253
3 files changed, 222 insertions, 126 deletions
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index 6b480d2..03fe6b2 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -72,62 +72,53 @@ static const XRRModeInfo* get_mode_info(const XRRScreenResources *sr, RRMode id)
return nullptr;
}
-static int get_monitor_max_hz(Display *display) {
+static void for_each_active_monitor_output(Display *display, std::function<void(const XRRModeInfo*)> callback_func) {
XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display));
- if(screen_res) {
- unsigned long max_hz = 0;
- for(int i = 0; i < screen_res->noutput; ++i) {
- XRROutputInfo *out_info = XRRGetOutputInfo(display, screen_res, screen_res->outputs[i]);
- if(out_info && out_info->connection == RR_Connected) {
- XRRCrtcInfo *crt_info = XRRGetCrtcInfo(display, screen_res, out_info->crtc);
- if(crt_info) {
- const XRRModeInfo *mode_info = get_mode_info(screen_res, crt_info->mode);
- if(mode_info) {
- unsigned long total = mode_info->hTotal * mode_info->vTotal;
- if(total > 0)
- max_hz = std::max(max_hz, mode_info->dotClock / total);
- }
- XRRFreeCrtcInfo(crt_info);
- }
+ if(!screen_res)
+ return;
+
+ for(int i = 0; i < screen_res->noutput; ++i) {
+ XRROutputInfo *out_info = XRRGetOutputInfo(display, screen_res, screen_res->outputs[i]);
+ if(out_info && out_info->connection == RR_Connected) {
+ XRRCrtcInfo *crt_info = XRRGetCrtcInfo(display, screen_res, out_info->crtc);
+ if(crt_info) {
+ const XRRModeInfo *mode_info = get_mode_info(screen_res, crt_info->mode);
+ if(mode_info)
+ callback_func(mode_info);
+ XRRFreeCrtcInfo(crt_info);
}
- if(out_info)
- XRRFreeOutputInfo(out_info);
}
- XRRFreeScreenResources(screen_res);
- if(max_hz == 0)
- max_hz = 60;
- return std::min(max_hz, 144UL);
+ if(out_info)
+ XRRFreeOutputInfo(out_info);
}
- return 60;
+
+ XRRFreeScreenResources(screen_res);
+}
+
+static int get_monitor_max_hz(Display *display) {
+ unsigned long max_hz = 0;
+ for_each_active_monitor_output(display, [&max_hz](const XRRModeInfo *mode_info) {
+ unsigned long total = mode_info->hTotal * mode_info->vTotal;
+ if(total > 0)
+ max_hz = std::max(max_hz, (unsigned long)std::round((double)mode_info->dotClock / (double)total));
+ });
+
+ if(max_hz == 0)
+ return 60;
+ return std::min(max_hz, 144UL);
}
static int get_largest_monitor_height(Display *display) {
- XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display));
- if(screen_res) {
- int max_height = 0;
- for(int i = 0; i < screen_res->noutput; ++i) {
- XRROutputInfo *out_info = XRRGetOutputInfo(display, screen_res, screen_res->outputs[i]);
- if(out_info && out_info->connection == RR_Connected) {
- XRRCrtcInfo* crt_info = XRRGetCrtcInfo(display, screen_res, out_info->crtc);
- if(crt_info) {
- const XRRModeInfo *mode_info = get_mode_info(screen_res, crt_info->mode);
- if(mode_info) {
- // Need to get the min of width or height because we want to get the smallest size for monitors in portrait mode, for mobile devices such as pinephone
- int width_or_height = std::min((int)mode_info->width, (int)mode_info->height);
- max_height = std::max(max_height, width_or_height);
- }
- XRRFreeCrtcInfo(crt_info);
- }
- }
- if(out_info)
- XRRFreeOutputInfo(out_info);
- }
- XRRFreeScreenResources(screen_res);
- if(max_height == 0)
- max_height = 720;
- return std::max(max_height, 480);
- }
- return 720;
+ int max_height = 0;
+ for_each_active_monitor_output(display, [&max_height](const XRRModeInfo *mode_info) {
+ // Need to get the min of width or height because we want to get the smallest size for monitors in portrait mode, for mobile devices such as pinephone
+ int width_or_height = std::min((int)mode_info->width, (int)mode_info->height);
+ max_height = std::max(max_height, width_or_height);
+ });
+
+ if(max_height == 0)
+ return 720;
+ return std::max(max_height, 480);
}
static void get_screen_resolution(Display *display, int *width, int *height) {
@@ -1903,7 +1894,7 @@ namespace QuickMedia {
tabs.push_back(Tab{std::move(related_videos_body), std::move(related_videos_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
}
if(channels_page) {
- tabs.push_back(Tab{create_body(), std::move(channels_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
+ tabs.push_back(Tab{create_body(), std::move(channels_page), create_search_bar("Search...", is_youtube ? 350 : SEARCH_DELAY_FILTER)});
}
bool page_changed = false;
@@ -2518,6 +2509,9 @@ namespace QuickMedia {
images_to_upscale_queue.clear();
image_upscale_status.clear();
}
+
+ window_size.x = window.getSize().x;
+ window_size.y = window.getSize().y;
}
static bool is_url_video(const std::string &url) {
diff --git a/src/plugins/Plugin.cpp b/src/plugins/Plugin.cpp
index a391d06..33fe47e 100644
--- a/src/plugins/Plugin.cpp
+++ b/src/plugins/Plugin.cpp
@@ -6,4 +6,5 @@ namespace QuickMedia {
SearchResult download_result_to_search_result(DownloadResult download_result) { return (SearchResult)download_result; }
ImageResult download_result_to_image_result(DownloadResult download_result) { return (ImageResult)download_result; }
PluginResult search_result_to_plugin_result(SearchResult search_result) { return (PluginResult)search_result; }
+ SearchResult plugin_result_to_search_result(PluginResult plugin_result) { return (SearchResult)plugin_result; }
} \ No newline at end of file
diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp
index cc20f2f..b3c9a60 100644
--- a/src/plugins/Youtube.cpp
+++ b/src/plugins/Youtube.cpp
@@ -3,6 +3,7 @@
#include "../../include/NetUtils.hpp"
#include "../../include/StringUtils.hpp"
#include "../../include/Scale.hpp"
+#include <json/writer.h>
#include <string.h>
namespace QuickMedia {
@@ -250,28 +251,10 @@ namespace QuickMedia {
return token_json.asString();
}
- static std::string grid_renderer_get_continuation_token(const Json::Value &grid_renderer_json) {
- const Json::Value &continuations_json = grid_renderer_json["continuations"];
- if(!continuations_json.isArray())
- return "";
-
- for(const Json::Value &continuation_json : continuations_json) {
- if(!continuation_json.isObject())
- continue;
-
- const Json::Value &next_continuation_data_json = continuation_json["nextContinuationData"];
- if(!next_continuation_data_json.isObject())
- continue;
-
- const Json::Value &continuation_item_json = next_continuation_data_json["continuation"];
- if(continuation_item_json.isString())
- return continuation_item_json.asString();
- }
-
- return "";
- }
-
static void parse_item_section_renderer(const Json::Value &item_section_renderer_json, std::unordered_set<std::string> &added_videos, BodyItems &result_items) {
+ if(!item_section_renderer_json.isObject())
+ return;
+
const Json::Value &item_contents_json = item_section_renderer_json["contents"];
if(!item_contents_json.isArray())
return;
@@ -399,9 +382,6 @@ namespace QuickMedia {
if(!grid_renderer_json.isObject())
continue;
- if(new_continuation_token.empty())
- new_continuation_token = grid_renderer_get_continuation_token(grid_renderer_json);
-
const Json::Value &items_json = grid_renderer_json["items"];
if(!items_json.isArray())
continue;
@@ -410,6 +390,9 @@ namespace QuickMedia {
if(!item_json.isObject())
continue;
+ if(new_continuation_token.empty())
+ new_continuation_token = item_section_renderer_get_continuation_token(item_json);
+
const Json::Value &grid_video_renderer = item_json["gridVideoRenderer"];
if(!grid_video_renderer.isObject())
continue;
@@ -429,6 +412,29 @@ namespace QuickMedia {
return body_items;
}
+ static void parse_section_list_renderer(const Json::Value &section_list_renderer_json, std::string &continuation_token, BodyItems &result_items, std::unordered_set<std::string> &added_videos) {
+ if(!section_list_renderer_json.isObject())
+ return;
+
+ const Json::Value &contents2_json = section_list_renderer_json["contents"];
+ if(!contents2_json.isArray())
+ return;
+
+ for(const Json::Value &item_json : contents2_json) {
+ if(!item_json.isObject())
+ continue;
+
+ if(continuation_token.empty())
+ continuation_token = item_section_renderer_get_continuation_token(item_json);
+
+ const Json::Value &item_section_renderer_json = item_json["itemSectionRenderer"];
+ if(!item_section_renderer_json.isObject())
+ continue;
+
+ parse_item_section_renderer(item_section_renderer_json, added_videos, result_items);
+ }
+ }
+
SearchResult YoutubeSearchPage::search(const std::string &str, BodyItems &result_items) {
continuation_token.clear();
current_page = 0;
@@ -474,27 +480,7 @@ namespace QuickMedia {
if(!primary_contents_json.isObject())
continue;
- const Json::Value &section_list_renderer_json = primary_contents_json["sectionListRenderer"];
- if(!section_list_renderer_json.isObject())
- continue;
-
- const Json::Value &contents2_json = section_list_renderer_json["contents"];
- if(!contents2_json.isArray())
- continue;
-
- for(const Json::Value &item_json : contents2_json) {
- if(!item_json.isObject())
- continue;
-
- if(continuation_token.empty())
- continuation_token = item_section_renderer_get_continuation_token(item_json);
-
- const Json::Value &item_section_renderer_json = item_json["itemSectionRenderer"];
- if(!item_section_renderer_json.isObject())
- continue;
-
- parse_item_section_renderer(item_section_renderer_json, added_videos, result_items);
- }
+ parse_section_list_renderer(primary_contents_json["sectionListRenderer"], continuation_token, result_items, added_videos);
}
return SearchResult::OK;
@@ -515,7 +501,7 @@ namespace QuickMedia {
if(strncmp(url.c_str(), "https://www.youtube.com/channel/", 32) == 0) {
// TODO: Make all pages (for all services) lazy fetch in a similar manner!
- result_tabs.push_back(Tab{create_body(), std::make_unique<YoutubeChannelPage>(program, url, "", title), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
+ result_tabs.push_back(Tab{create_body(), std::make_unique<YoutubeChannelPage>(program, url, "", title), create_search_bar("Search...", 350)});
} else {
result_tabs.push_back(Tab{nullptr, std::make_unique<YoutubeVideoPage>(program), nullptr});
}
@@ -592,6 +578,102 @@ namespace QuickMedia {
return PluginResult::OK;
}
+ static std::string channel_url_extract_id(const std::string &channel_url) {
+ size_t index = channel_url.find("channel/");
+ if(index == std::string::npos)
+ return "";
+
+ index += 8;
+ size_t end_index = channel_url.find('/', index);
+ if(end_index == std::string::npos)
+ return channel_url.substr(index);
+
+ return channel_url.substr(index, end_index - index);
+ }
+
+ SearchResult YoutubeChannelPage::search(const std::string &str, BodyItems &result_items) {
+ added_videos.clear();
+ continuation_token.clear();
+ current_page = 0;
+ if(str.empty())
+ return plugin_result_to_search_result(lazy_fetch(result_items));
+
+ std::string next_url = "https://www.youtube.com/youtubei/v1/browse?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8";
+
+ Json::Value request_json(Json::objectValue);
+ Json::Value context_json(Json::objectValue);
+ Json::Value client_json(Json::objectValue);
+ client_json["hl"] = "en";
+ client_json["gl"] = "US";
+ client_json["deviceMake"] = "";
+ client_json["deviceModel"] = "";
+ client_json["userAgent"] = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36";
+ client_json["clientName"] = "WEB";
+ client_json["clientVersion"] = "2.20210304.08.01";
+ client_json["osName"] = "X11";
+ client_json["osVersion"] = "";
+ client_json["originalUrl"] = url + "/videos";
+ context_json["client"] = std::move(client_json);
+ request_json["context"] = std::move(context_json);
+ request_json["browseId"] = channel_url_extract_id(url);
+ request_json["query"] = str;
+ request_json["params"] = "EgZzZWFyY2g%3D";
+ //request_json["continuation"] = current_continuation_token;
+
+ Json::StreamWriterBuilder json_builder;
+ json_builder["commentStyle"] = "None";
+ json_builder["indentation"] = "";
+
+ std::vector<CommandArg> additional_args = {
+ { "-H", "authority: www.youtube.com" },
+ { "-H", "x-origin: https://www.youtube.com" },
+ { "-H", "content-type: application/json" },
+ { "-H", "x-youtube-client-name: 1" },
+ { "-H", "x-youtube-client-version: 2.20200626.03.00" },
+ { "-H", "referer: " + url + "/videos" },
+ { "--data-raw", Json::writeString(json_builder, request_json) }
+ };
+
+ //std::vector<CommandArg> cookies = get_cookies();
+ //additional_args.insert(additional_args.end(), cookies.begin(), cookies.end());
+
+ Json::Value json_root;
+ DownloadResult result = download_json(json_root, next_url, std::move(additional_args), true);
+ if(result != DownloadResult::OK) return download_result_to_search_result(result);
+
+ if(!json_root.isObject())
+ return SearchResult::ERR;
+
+ const Json::Value &contents_json = json_root["contents"];
+ if(!contents_json.isObject())
+ return SearchResult::ERR;
+
+ const Json::Value &two_column_browse_results_renderer_json = contents_json["twoColumnBrowseResultsRenderer"];
+ if(!two_column_browse_results_renderer_json.isObject())
+ return SearchResult::ERR;
+
+ const Json::Value &tabs_json = two_column_browse_results_renderer_json["tabs"];
+ if(!tabs_json.isArray())
+ return SearchResult::ERR;
+
+ for(const Json::Value &json_item : tabs_json) {
+ if(!json_item.isObject())
+ continue;
+
+ const Json::Value &expandable_tab_renderer_json = json_item["expandableTabRenderer"];
+ if(!expandable_tab_renderer_json.isObject())
+ continue;
+
+ const Json::Value &content_json = expandable_tab_renderer_json["content"];
+ if(!content_json.isObject())
+ continue;
+
+ parse_section_list_renderer(content_json["sectionListRenderer"], continuation_token, result_items, added_videos);
+ }
+
+ return SearchResult::OK;
+ }
+
PluginResult YoutubeChannelPage::get_page(const std::string&, int page, BodyItems &result_items) {
while(current_page < page) {
PluginResult plugin_result = search_get_continuation(url, continuation_token, result_items);
@@ -602,14 +684,37 @@ namespace QuickMedia {
}
PluginResult YoutubeChannelPage::search_get_continuation(const std::string &url, const std::string &current_continuation_token, BodyItems &result_items) {
- std::string next_url = "https://www.youtube.com/browse_ajax?ctoken=" + current_continuation_token + "&continuation=" + current_continuation_token;
+ std::string next_url = "https://www.youtube.com/youtubei/v1/browse?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8";
+
+ Json::Value request_json(Json::objectValue);
+ Json::Value context_json(Json::objectValue);
+ Json::Value client_json(Json::objectValue);
+ client_json["hl"] = "en";
+ client_json["gl"] = "US";
+ client_json["deviceMake"] = "";
+ client_json["deviceModel"] = "";
+ client_json["userAgent"] = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36";
+ client_json["clientName"] = "WEB";
+ client_json["clientVersion"] = "2.20210304.08.01";
+ client_json["osName"] = "X11";
+ client_json["osVersion"] = "";
+ client_json["originalUrl"] = url + "/videos";
+ context_json["client"] = std::move(client_json);
+ request_json["context"] = std::move(context_json);
+ request_json["continuation"] = current_continuation_token;
+
+ Json::StreamWriterBuilder json_builder;
+ json_builder["commentStyle"] = "None";
+ json_builder["indentation"] = "";
std::vector<CommandArg> additional_args = {
- { "-H", "x-spf-referer: " + url },
+ { "-H", "authority: www.youtube.com" },
+ { "-H", "x-origin: https://www.youtube.com" },
+ { "-H", "content-type: application/json" },
{ "-H", "x-youtube-client-name: 1" },
- { "-H", "x-spf-previous: " + url },
{ "-H", "x-youtube-client-version: 2.20200626.03.00" },
- { "-H", "referer: " + url }
+ { "-H", "referer: " + url + "/videos" },
+ { "--data-raw", Json::writeString(json_builder, request_json) }
};
//std::vector<CommandArg> cookies = get_cookies();
@@ -619,46 +724,41 @@ namespace QuickMedia {
DownloadResult result = download_json(json_root, next_url, std::move(additional_args), true);
if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
- if(!json_root.isArray())
+ if(!json_root.isObject())
+ return PluginResult::ERR;
+
+ const Json::Value &on_response_received_actions_json = json_root["onResponseReceivedActions"];
+ if(!on_response_received_actions_json.isArray())
return PluginResult::ERR;
std::string new_continuation_token;
- for(const Json::Value &json_item : json_root) {
+ for(const Json::Value &json_item : on_response_received_actions_json) {
if(!json_item.isObject())
continue;
- const Json::Value &response_json = json_item["response"];
- if(!response_json.isObject())
- continue;
-
- const Json::Value &continuation_contents_json = response_json["continuationContents"];
- if(!continuation_contents_json.isObject())
- continue;
-
- const Json::Value &grid_continuation_json = continuation_contents_json["gridContinuation"];
- if(!grid_continuation_json.isObject())
+ const Json::Value &append_continuation_items_action_json = json_item["appendContinuationItemsAction"];
+ if(!append_continuation_items_action_json.isObject())
continue;
- if(new_continuation_token.empty()) {
- // grid_continuation_json is compatible with grid_renderer
- new_continuation_token = grid_renderer_get_continuation_token(grid_continuation_json);
- }
-
- const Json::Value &items_json = grid_continuation_json["items"];
- if(!items_json.isArray())
+ const Json::Value &continuation_items_json = append_continuation_items_action_json["continuationItems"];
+ if(!continuation_items_json.isArray())
continue;
- for(const Json::Value &item_json : items_json) {
+ for(const Json::Value &item_json : continuation_items_json) {
if(!item_json.isObject())
continue;
- const Json::Value &grid_video_renderer = item_json["gridVideoRenderer"];
- if(!grid_video_renderer.isObject())
- continue;
+ if(new_continuation_token.empty())
+ new_continuation_token = item_section_renderer_get_continuation_token(item_json);
- auto body_item = parse_common_video_item(grid_video_renderer, added_videos);
- if(body_item)
- result_items.push_back(std::move(body_item));
+ const Json::Value &grid_video_renderer = item_json["gridVideoRenderer"];
+ if(grid_video_renderer.isObject()) {
+ auto body_item = parse_common_video_item(grid_video_renderer, added_videos);
+ if(body_item)
+ result_items.push_back(std::move(body_item));
+ } else {
+ parse_item_section_renderer(item_json["itemSectionRenderer"], added_videos, result_items);
+ }
}
}
@@ -676,6 +776,7 @@ namespace QuickMedia {
}
PluginResult YoutubeChannelPage::lazy_fetch(BodyItems &result_items) {
+ added_videos.clear();
std::vector<CommandArg> additional_args = {
{ "-H", "x-spf-referer: " + url },
{ "-H", "x-youtube-client-name: 1" },