From 3c9ca2c97ae7a2b39bfe5c5e8a9d7941f9fb1525 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Thu, 8 Aug 2019 04:13:03 +0200 Subject: Youtube: use real search for search suggestions (better results, thumbnails, directly to result) --- README.md | 4 +- include/QuickMedia.hpp | 1 - include/SearchBar.hpp | 1 + plugins/Manganelo.hpp | 6 ++- plugins/Plugin.hpp | 5 +- plugins/Youtube.hpp | 8 ++-- src/QuickMedia.cpp | 27 +++++++---- src/SearchBar.cpp | 3 +- src/plugins/Manganelo.cpp | 4 +- src/plugins/Plugin.cpp | 6 +++ src/plugins/Youtube.cpp | 117 +++++++++++++++++++++++----------------------- 11 files changed, 101 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index cce411a..14ea3ec 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # QuickMedia Native clients of websites with fast access to what you want to see. [Demo with manga](https://beta.lbry.tv/quickmedia_manga-2019-08-05_21.20.46/7). -Press ctrl+t to when hovering over a manga chapter to start tracking manga after that chapter. This only works if AutoMedia is installed and +Press `ctrl + t` when hovering over a manga chapter to start tracking manga after that chapter. This only works if AutoMedia is installed and accessible in PATH environment variable. # Dependencies ## Compile @@ -23,4 +23,4 @@ until all subtitles have been downloaded and loaded. Figure out why memory usage doesn't drop much when killing the video player. Is it a bug in proprietary nvidia drivers on gnu/linux?\ Add grid-view when thumbnails are visible.\ Add scrollbar.\ -Add option to scale image to window size. \ No newline at end of file +Add option to scale image to window size. diff --git a/include/QuickMedia.hpp b/include/QuickMedia.hpp index 2ab4733..7534439 100644 --- a/include/QuickMedia.hpp +++ b/include/QuickMedia.hpp @@ -35,7 +35,6 @@ namespace QuickMedia { std::unique_ptr search_bar; Page current_page; // TODO: Combine these - std::string video_url; std::string images_url; std::string content_title; std::string content_url; diff --git a/include/SearchBar.hpp b/include/SearchBar.hpp index c9f75f0..f1ac3fd 100644 --- a/include/SearchBar.hpp +++ b/include/SearchBar.hpp @@ -23,6 +23,7 @@ namespace QuickMedia { TextUpdateCallback onTextUpdateCallback; TextSubmitCallback onTextSubmitCallback; + int text_autosearch_delay; private: sf::Text text; sf::RectangleShape background; diff --git a/plugins/Manganelo.hpp b/plugins/Manganelo.hpp index 01311e1..5720199 100644 --- a/plugins/Manganelo.hpp +++ b/plugins/Manganelo.hpp @@ -5,12 +5,14 @@ namespace QuickMedia { class Manganelo : public Plugin { public: - SearchResult search(const std::string &url, std::vector> &result_items, Page &next_page) override; + SearchResult search(const std::string &url, std::vector> &result_items) override; SuggestionResult update_search_suggestions(const std::string &text, std::vector> &result_items) override; ImageResult get_image_by_index(const std::string &url, int index, std::string &image_data); ImageResult get_number_of_images(const std::string &url, int &num_images); bool search_suggestions_has_thumbnails() const override { return true; } - bool search_results_has_thumbnails() const override { return true; } + bool search_results_has_thumbnails() const override { return false; } + int get_search_delay() const override { return 150; } + Page get_page_after_search() const override { return Page::EPISODE_LIST; } private: // Caches url. If the same url is requested multiple times then the cache is used ImageResult get_image_urls_for_chapter(const std::string &url); diff --git a/plugins/Plugin.hpp b/plugins/Plugin.hpp index 3df53ca..54d49e5 100644 --- a/plugins/Plugin.hpp +++ b/plugins/Plugin.hpp @@ -44,11 +44,14 @@ namespace QuickMedia { public: virtual ~Plugin() = default; - virtual SearchResult search(const std::string &text, std::vector> &result_items, Page &next_page) = 0; + virtual SearchResult search(const std::string &text, std::vector> &result_items); virtual SuggestionResult update_search_suggestions(const std::string &text, std::vector> &result_items); virtual std::vector> get_related_media(const std::string &url); virtual bool search_suggestions_has_thumbnails() const = 0; virtual bool search_results_has_thumbnails() const = 0; + virtual int get_search_delay() const = 0; + virtual bool search_suggestion_is_search() const { return false; } + virtual Page get_page_after_search() const = 0; protected: std::string url_param_encode(const std::string ¶m) const; }; diff --git a/plugins/Youtube.hpp b/plugins/Youtube.hpp index e8cc2c2..64a0d6b 100644 --- a/plugins/Youtube.hpp +++ b/plugins/Youtube.hpp @@ -5,10 +5,12 @@ namespace QuickMedia { class Youtube : public Plugin { public: - SearchResult search(const std::string &text, std::vector> &result_items, Page &next_page) override; SuggestionResult update_search_suggestions(const std::string &text, std::vector> &result_items) override; std::vector> get_related_media(const std::string &url) override; - bool search_suggestions_has_thumbnails() const override { return false; } - bool search_results_has_thumbnails() const override { return true; } + bool search_suggestions_has_thumbnails() const override { return true; } + bool search_results_has_thumbnails() const override { return false; } + int get_search_delay() const override { return 250; } + bool search_suggestion_is_search() const override { return true; } + Page get_page_after_search() const override { return Page::VIDEO_CONTENT; } }; } \ No newline at end of file diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index d0a0ee7..856fc73 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -41,7 +41,7 @@ namespace QuickMedia { delete current_plugin; } - static SearchResult search_selected_suggestion(Body *body, Plugin *plugin, Page &next_page, std::string &selected_title, std::string &selected_url) { + static SearchResult search_selected_suggestion(Body *body, Plugin *plugin, std::string &selected_title, std::string &selected_url) { BodyItem *selected_item = body->get_selected(); if(!selected_item) return SearchResult::ERR; @@ -49,7 +49,7 @@ namespace QuickMedia { selected_title = selected_item->title; selected_url = selected_item->url; body->clear_items(); - SearchResult search_result = plugin->search(!selected_url.empty() ? selected_url : selected_title, body->items, next_page); + SearchResult search_result = plugin->search(!selected_url.empty() ? selected_url : selected_title, body->items); body->reset_selected(); return search_result; } @@ -84,6 +84,8 @@ namespace QuickMedia { return -1; } + search_bar->text_autosearch_delay = current_plugin->get_search_delay(); + while(window.isOpen()) { switch(current_page) { case Page::EXIT: @@ -93,10 +95,12 @@ namespace QuickMedia { body->draw_thumbnails = current_plugin->search_suggestions_has_thumbnails(); search_suggestion_page(); break; +#if 0 case Page::SEARCH_RESULT: body->draw_thumbnails = current_plugin->search_results_has_thumbnails(); search_result_page(); break; +#endif case Page::VIDEO_CONTENT: body->draw_thumbnails = false; video_content_page(); @@ -201,8 +205,8 @@ namespace QuickMedia { }; search_bar->onTextSubmitCallback = [this](const std::string &text) { - Page next_page; - if(search_selected_suggestion(body, current_plugin, next_page, content_title, content_url) == SearchResult::OK) { + Page next_page = current_plugin->get_page_after_search(); + if(search_selected_suggestion(body, current_plugin, content_title, content_url) == SearchResult::OK) { if(next_page == Page::EPISODE_LIST) { Path content_storage_dir = get_storage_dir().join("manga"); if(create_directory_recursive(content_storage_dir) != 0) { @@ -261,6 +265,7 @@ namespace QuickMedia { } void Program::search_result_page() { + #if 0 search_bar->onTextUpdateCallback = [this](const std::string &text) { body->filter_search_fuzzy(text); body->clamp_selection(); @@ -307,23 +312,24 @@ namespace QuickMedia { search_bar->draw(window); window.display(); } + #endif } void Program::video_content_page() { search_bar->onTextUpdateCallback = nullptr; search_bar->onTextSubmitCallback = nullptr; - watched_videos.insert(video_url); + watched_videos.insert(content_url); std::unique_ptr video_player = nullptr; try { - printf("Play video: %s\n", video_url.c_str()); - video_player.reset(new VideoPlayer(window, window_size.x, window_size.y, video_url.c_str())); + printf("Play video: %s\n", content_url.c_str()); + video_player.reset(new VideoPlayer(window, window_size.x, window_size.y, content_url.c_str())); } catch(VideoInitializationException &e) { show_notification("Video player", "Failed to create video player", Urgency::CRITICAL); video_player = nullptr; } - std::vector> related_media = current_plugin->get_related_media(video_url); + std::vector> related_media = current_plugin->get_related_media(content_url); bool reload = false; if(video_player) { @@ -342,8 +348,8 @@ namespace QuickMedia { if(new_video_url.empty()) return; - video_url = std::move(new_video_url); - related_media = current_plugin->get_related_media(video_url); + content_url = std::move(new_video_url); + related_media = current_plugin->get_related_media(content_url); // TODO: This doesn't seem to work correctly right now, it causes video to become black when changing video (context reset bug). //video_player->load_file(video_url); reload = true; @@ -443,6 +449,7 @@ namespace QuickMedia { } } + // TODO: This code is duplicated in many places. Handle it in one place. if(resized) { search_bar->onWindowResize(window_size); diff --git a/src/SearchBar.cpp b/src/SearchBar.cpp index f7ac48d..1094883 100644 --- a/src/SearchBar.cpp +++ b/src/SearchBar.cpp @@ -12,6 +12,7 @@ namespace QuickMedia { SearchBar::SearchBar(sf::Font &font) : onTextUpdateCallback(nullptr), onTextSubmitCallback(nullptr), + text_autosearch_delay(0), text("Search...", font, 18), show_placeholder(true), updated_search(false) @@ -29,7 +30,7 @@ namespace QuickMedia { } void SearchBar::update() { - if(updated_search && time_since_search_update.getElapsedTime().asMilliseconds() >= 150) { + if(updated_search && time_since_search_update.getElapsedTime().asMilliseconds() >= text_autosearch_delay) { updated_search = false; sf::String str = text.getString(); if(show_placeholder) diff --git a/src/plugins/Manganelo.cpp b/src/plugins/Manganelo.cpp index 3c6dd5b..b1f02a3 100644 --- a/src/plugins/Manganelo.cpp +++ b/src/plugins/Manganelo.cpp @@ -3,9 +3,7 @@ #include namespace QuickMedia { - SearchResult Manganelo::search(const std::string &url, std::vector> &result_items, Page &next_page) { - next_page = Page::EPISODE_LIST; - + SearchResult Manganelo::search(const std::string &url, std::vector> &result_items) { std::string website_data; if(download_to_string(url, website_data) != DownloadResult::OK) return SearchResult::NET_ERR; diff --git a/src/plugins/Plugin.cpp b/src/plugins/Plugin.cpp index 2367cb3..86f5d7d 100644 --- a/src/plugins/Plugin.cpp +++ b/src/plugins/Plugin.cpp @@ -10,6 +10,12 @@ static int accumulate_string(char *data, int size, void *userdata) { } namespace QuickMedia { + SearchResult Plugin::search(const std::string &text, std::vector> &result_items) { + (void)text; + (void)result_items; + return SearchResult::OK; + } + SuggestionResult Plugin::update_search_suggestions(const std::string &text, std::vector> &result_items) { (void)text; (void)result_items; diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index 40d545c..a5670ec 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -12,64 +12,6 @@ namespace QuickMedia { return strstr(str, substr); } - SearchResult Youtube::search(const std::string &text, std::vector> &result_items, Page &next_page) { - next_page = Page::SEARCH_RESULT; - std::string url = "https://youtube.com/results?search_query="; - url += url_param_encode(text); - - std::string website_data; - if(download_to_string(url, website_data) != DownloadResult::OK) - return SearchResult::NET_ERR; - - struct ItemData { - std::vector> *result_items; - size_t index; - }; - ItemData item_data = { &result_items, 0 }; - - QuickMediaHtmlSearch html_search; - int result = quickmedia_html_search_init(&html_search, website_data.c_str()); - if(result != 0) - goto cleanup; - - result = quickmedia_html_find_nodes_xpath(&html_search, "//h3[class=\"yt-lockup-title\"]/a", - [](QuickMediaHtmlNode *node, void *userdata) { - auto *result_items = (std::vector>*)userdata; - const char *href = quickmedia_html_node_get_attribute_value(node, "href"); - const char *title = quickmedia_html_node_get_attribute_value(node, "title"); - // Checking for watch?v helps skipping ads - if(href && title && begins_with(href, "/watch?v=")) { - auto item = std::make_unique(strip(title)); - item->url = std::string("https://www.youtube.com") + href; - result_items->push_back(std::move(item)); - } - }, &result_items); - if(result != 0) - goto cleanup; - - result = quickmedia_html_find_nodes_xpath(&html_search, "//span[class=\"yt-thumb-simple\"]//img", - [](QuickMediaHtmlNode *node, void *userdata) { - ItemData *item_data = (ItemData*)userdata; - if(item_data->index >= item_data->result_items->size()) - return; - - const char *src = quickmedia_html_node_get_attribute_value(node, "src"); - const char *data_thumb = quickmedia_html_node_get_attribute_value(node, "data-thumb"); - - if(src && contains(src, "i.ytimg.com/")) { - (*item_data->result_items)[item_data->index]->thumbnail_url = src; - ++item_data->index; - } else if(data_thumb && contains(data_thumb, "i.ytimg.com/")) { - (*item_data->result_items)[item_data->index]->thumbnail_url = data_thumb; - ++item_data->index; - } - }, &item_data); - - cleanup: - quickmedia_html_search_deinit(&html_search); - return result == 0 ? SearchResult::OK : SearchResult::ERR; - } - static void iterate_suggestion_result(const Json::Value &value, std::vector> &result_items, int &iterate_count) { ++iterate_count; if(value.isArray()) { @@ -83,7 +25,11 @@ namespace QuickMedia { } } + // TODO: Speed this up by using string.find instead of parsing html SuggestionResult Youtube::update_search_suggestions(const std::string &text, std::vector> &result_items) { + // Keep this for backup. This is using search suggestion the same way youtube does it, but the results + // are not as good as doing an actual search. + #if 0 std::string url = "https://clients1.google.com/complete/search?client=youtube&hl=en&gs_rn=64&gs_ri=youtube&ds=yt&cp=7&gs_id=x&q="; url += url_param_encode(text); @@ -125,6 +71,61 @@ namespace QuickMedia { if(!found_search_text) result_items.insert(result_items.begin(), std::make_unique(text)); return SuggestionResult::OK; + #endif + std::string url = "https://youtube.com/results?search_query="; + url += url_param_encode(text); + + std::string website_data; + if(download_to_string(url, website_data) != DownloadResult::OK) + return SuggestionResult::NET_ERR; + + struct ItemData { + std::vector> *result_items; + size_t index; + }; + ItemData item_data = { &result_items, 0 }; + + QuickMediaHtmlSearch html_search; + int result = quickmedia_html_search_init(&html_search, website_data.c_str()); + if(result != 0) + goto cleanup; + + result = quickmedia_html_find_nodes_xpath(&html_search, "//h3[class=\"yt-lockup-title\"]/a", + [](QuickMediaHtmlNode *node, void *userdata) { + auto *result_items = (std::vector>*)userdata; + const char *href = quickmedia_html_node_get_attribute_value(node, "href"); + const char *title = quickmedia_html_node_get_attribute_value(node, "title"); + // Checking for watch?v helps skipping ads + if(href && title && begins_with(href, "/watch?v=")) { + auto item = std::make_unique(strip(title)); + item->url = std::string("https://www.youtube.com") + href; + result_items->push_back(std::move(item)); + } + }, &result_items); + if(result != 0) + goto cleanup; + + result = quickmedia_html_find_nodes_xpath(&html_search, "//span[class=\"yt-thumb-simple\"]//img", + [](QuickMediaHtmlNode *node, void *userdata) { + ItemData *item_data = (ItemData*)userdata; + if(item_data->index >= item_data->result_items->size()) + return; + + const char *src = quickmedia_html_node_get_attribute_value(node, "src"); + const char *data_thumb = quickmedia_html_node_get_attribute_value(node, "data-thumb"); + + if(src && contains(src, "i.ytimg.com/")) { + (*item_data->result_items)[item_data->index]->thumbnail_url = src; + ++item_data->index; + } else if(data_thumb && contains(data_thumb, "i.ytimg.com/")) { + (*item_data->result_items)[item_data->index]->thumbnail_url = data_thumb; + ++item_data->index; + } + }, &item_data); + + cleanup: + quickmedia_html_search_deinit(&html_search); + return result == 0 ? SuggestionResult::OK : SuggestionResult::ERR; } std::vector> Youtube::get_related_media(const std::string &url) { -- cgit v1.2.3