diff options
Diffstat (limited to 'src/QuickMedia.cpp')
-rw-r--r-- | src/QuickMedia.cpp | 298 |
1 files changed, 36 insertions, 262 deletions
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index f94601c..594b93a 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -248,182 +248,36 @@ static sf::Color interpolate_colors(sf::Color source, sf::Color target, double p } namespace QuickMedia { - // Returns index to item or -1 if not found - static int get_body_item_by_url(Body *body, const std::string &url) { - if(url.empty()) return -1; - for(size_t i = 0; i < body->items.size(); ++i) { - auto &body_item = body->items[i]; - if(body_item->url == url) - return i; - } - return -1; - } - - static Path get_recommended_filepath(const char *plugin_name) { - Path video_history_dir = get_storage_dir().join("recommended"); - if(create_directory_recursive(video_history_dir) != 0) { - std::string err_msg = "Failed to create recommended directory "; - err_msg += video_history_dir.data; - show_notification("QuickMedia", err_msg.c_str(), Urgency::CRITICAL); - exit(1); - } - - Path video_history_filepath = video_history_dir; - return video_history_filepath.join(plugin_name).append(".json"); - } - - // TODO: Make asynchronous - static void fill_recommended_items_from_json(const char *plugin_name, const Json::Value &recommended_json, BodyItems &body_items) { - assert(recommended_json.isObject()); - - const int64_t recommendations_autodelete_period = 60*60*24*20; // 20 days - time_t time_now = time(NULL); - int num_items_deleted = 0; - - std::vector<std::pair<std::string, Json::Value>> recommended_items(recommended_json.size()); - /* TODO: Optimize member access */ - for(auto &member_name : recommended_json.getMemberNames()) { - Json::Value recommended_item = recommended_json[member_name]; - if(recommended_item.isObject()) { - Json::Value recommended_timestamp_json = recommended_item.get("recommended_timestamp", Json::Value::nullSingleton()); - Json::Value watched_timestamp_json = recommended_item.get("watched_timestamp", Json::Value::nullSingleton()); - if(watched_timestamp_json.isNumeric() && time_now - watched_timestamp_json.asInt64() >= recommendations_autodelete_period) { - ++num_items_deleted; - } else if(recommended_timestamp_json.isNumeric() && time_now - recommended_timestamp_json.asInt64() >= recommendations_autodelete_period) { - ++num_items_deleted; - } else if(recommended_timestamp_json.isNull() && watched_timestamp_json.isNull()) { - ++num_items_deleted; - } else { - recommended_items.push_back(std::make_pair(member_name, std::move(recommended_item))); - } - } - } - - if(num_items_deleted > 0) { - // TODO: Is there a better way? - Json::Value new_recommendations(Json::objectValue); - for(auto &recommended : recommended_items) { - new_recommendations[recommended.first] = recommended.second; - } - fprintf(stderr, "Number of old recommendations to delete: %d\n", num_items_deleted); - save_json_to_file_atomic(get_recommended_filepath(plugin_name), new_recommendations); - } - - /* TODO: Better algorithm for recommendations */ - std::sort(recommended_items.begin(), recommended_items.end(), [](std::pair<std::string, Json::Value> &a, std::pair<std::string, Json::Value> &b) { - Json::Value &a_timestamp_json = a.second["recommended_timestamp"]; - Json::Value &b_timestamp_json = b.second["recommended_timestamp"]; - int64_t a_timestamp = 0; - int64_t b_timestamp = 0; - if(a_timestamp_json.isNumeric()) - a_timestamp = a_timestamp_json.asInt64(); - if(b_timestamp_json.isNumeric()) - b_timestamp = b_timestamp_json.asInt64(); - - Json::Value &a_recommended_count_json = a.second["recommended_count"]; - Json::Value &b_recommended_count_json = b.second["recommended_count"]; - int64_t a_recommended_count = 0; - int64_t b_recommended_count = 0; - if(a_recommended_count_json.isNumeric()) - a_recommended_count = a_recommended_count_json.asInt64(); - if(b_recommended_count_json.isNumeric()) - b_recommended_count = b_recommended_count_json.asInt64(); - - /* Put frequently recommended videos on top of recommendations. Each recommendation count is worth 5 minutes */ - a_timestamp += (300 * a_recommended_count); - b_timestamp += (300 * b_recommended_count); - - return a_timestamp > b_timestamp; - }); - - for(auto it = recommended_items.begin(); it != recommended_items.end(); ++it) { - const std::string &recommended_item_id = it->first; - Json::Value &recommended_item = it->second; - - int64_t watched_count = 0; - const Json::Value &watched_count_json = recommended_item["watched_count"]; - if(watched_count_json.isNumeric()) - watched_count = watched_count_json.asInt64(); - - /* TODO: Improve recommendations with some kind of algorithm. Videos we have seen should be recommended in some cases */ - if(watched_count != 0) - continue; - - const Json::Value &recommended_title_json = recommended_item["title"]; - if(!recommended_title_json.isString()) - continue; - - auto body_item = BodyItem::create(recommended_title_json.asString()); - body_item->url = "https://www.youtube.com/watch?v=" + recommended_item_id; - body_item->thumbnail_url = "https://img.youtube.com/vi/" + recommended_item_id + "/mqdefault.jpg"; - body_item->thumbnail_size = sf::Vector2i(192, 108); - body_items.push_back(std::move(body_item)); - - // We dont want more than 150 recommendations - if(body_items.size() == 150) - break; - } - - std::random_shuffle(body_items.begin(), body_items.end()); - } - enum class HistoryType { YOUTUBE, MANGA }; - class HistoryPage : public Page { + class HistoryPage : public LazyFetchPage { public: - HistoryPage(Program *program, Page *search_page, SearchBar *search_bar, HistoryType history_type) : - Page(program), search_page(search_page), search_bar(search_bar), history_type(history_type) {} + HistoryPage(Program *program, Page *search_page, HistoryType history_type) : + LazyFetchPage(program), search_page(search_page), history_type(history_type) {} const char* get_title() const override { return "History"; } PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override { return search_page->submit(title, url, result_tabs); } - void on_navigate_to_page(Body *body) override { - std::string selected_item_url = body->get_selected() ? body->get_selected()->url : ""; - body->clear_items(); + PluginResult lazy_fetch(BodyItems &result_items) override { switch(history_type) { case HistoryType::YOUTUBE: - program->youtube_get_watch_history(body->items); + program->youtube_get_watch_history(result_items); break; case HistoryType::MANGA: - program->manga_get_watch_history(program->get_plugin_name(), body->items); + program->manga_get_watch_history(program->get_plugin_name(), result_items); break; } - body->filter_search_fuzzy(search_bar->get_text()); - int item_to_revert_selection_to = get_body_item_by_url(body, selected_item_url); - if(item_to_revert_selection_to != -1) - body->set_selected_item(item_to_revert_selection_to, false); + return PluginResult::OK; } + bool reload_on_page_change() override { return true; } private: Page *search_page; - SearchBar *search_bar; HistoryType history_type; }; - class RecommendedPage : public Page { - public: - RecommendedPage(Program *program, Page *search_page, SearchBar *search_bar, const char *plugin_name) : Page(program), search_page(search_page), search_bar(search_bar), plugin_name(plugin_name) {} - const char* get_title() const override { return "Recommended"; } - PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override { - return search_page->submit(title, url, result_tabs); - } - void on_navigate_to_page(Body *body) override { - std::string selected_item_url = body->get_selected() ? body->get_selected()->url : ""; - body->clear_items(); - fill_recommended_items_from_json(plugin_name, program->load_recommended_json(), body->items); - body->filter_search_fuzzy(search_bar->get_text()); - int item_to_revert_selection_to = get_body_item_by_url(body, selected_item_url); - if(item_to_revert_selection_to != -1) - body->set_selected_item(item_to_revert_selection_to, false); - } - private: - Page *search_page; - SearchBar *search_bar; - const char *plugin_name; - }; - Program::Program() : disp(nullptr), window_size(1280, 720), @@ -978,47 +832,41 @@ namespace QuickMedia { } else if(strcmp(plugin_name, "manganelo") == 0) { tabs.push_back(Tab{create_body(), std::make_unique<ManganeloSearchPage>(this), create_search_bar("Search...", 400)}); - auto search_bar = create_search_bar("Search...", SEARCH_DELAY_FILTER); - auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), search_bar.get(), HistoryType::MANGA); - tabs.push_back(Tab{create_body(), std::move(history_page), std::move(search_bar)}); + auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), HistoryType::MANGA); + tabs.push_back(Tab{create_body(), std::move(history_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "manganelos") == 0) { auto search_page = std::make_unique<MangaGenericSearchPage>(this, plugin_name, "http://manganelos.com/"); add_manganelos_handlers(search_page.get()); tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 400)}); - auto search_bar = create_search_bar("Search...", SEARCH_DELAY_FILTER); - auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), search_bar.get(), HistoryType::MANGA); - tabs.push_back(Tab{create_body(), std::move(history_page), std::move(search_bar)}); + auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), HistoryType::MANGA); + tabs.push_back(Tab{create_body(), std::move(history_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "mangatown") == 0) { auto search_page = std::make_unique<MangaGenericSearchPage>(this, plugin_name, "https://www.mangatown.com/"); add_mangatown_handlers(search_page.get()); tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 400)}); - auto search_bar = create_search_bar("Search...", SEARCH_DELAY_FILTER); - auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), search_bar.get(), HistoryType::MANGA); - tabs.push_back(Tab{create_body(), std::move(history_page), std::move(search_bar)}); + auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), HistoryType::MANGA); + tabs.push_back(Tab{create_body(), std::move(history_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "mangakatana") == 0) { auto search_page = std::make_unique<MangaGenericSearchPage>(this, plugin_name, "https://mangakatana.com/", false); add_mangakatana_handlers(search_page.get()); tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 400)}); - auto search_bar = create_search_bar("Search...", SEARCH_DELAY_FILTER); - auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), search_bar.get(), HistoryType::MANGA); - tabs.push_back(Tab{create_body(), std::move(history_page), std::move(search_bar)}); + auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), HistoryType::MANGA); + tabs.push_back(Tab{create_body(), std::move(history_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "mangadex") == 0) { tabs.push_back(Tab{create_body(), std::make_unique<MangadexSearchPage>(this), create_search_bar("Search...", 400)}); - auto search_bar = create_search_bar("Search...", SEARCH_DELAY_FILTER); - auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), search_bar.get(), HistoryType::MANGA); - tabs.push_back(Tab{create_body(), std::move(history_page), std::move(search_bar)}); + auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), HistoryType::MANGA); + tabs.push_back(Tab{create_body(), std::move(history_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "readm") == 0) { auto search_page = std::make_unique<MangaGenericSearchPage>(this, plugin_name, "https://readm.org/"); add_readm_handlers(search_page.get()); tabs.push_back(Tab{create_body(), std::move(search_page), create_search_bar("Search...", 400)}); - auto search_bar = create_search_bar("Search...", SEARCH_DELAY_FILTER); - auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), search_bar.get(), HistoryType::MANGA); - tabs.push_back(Tab{create_body(), std::move(history_page), std::move(search_bar)}); + auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), HistoryType::MANGA); + tabs.push_back(Tab{create_body(), std::move(history_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "manga") == 0) { auto manganelo = std::make_unique<ManganeloSearchPage>(this); auto manganelos = std::make_unique<MangaGenericSearchPage>(this, "manganelos", "http://manganelos.com/"); @@ -1069,13 +917,11 @@ namespace QuickMedia { tabs.push_back(Tab{create_body(), std::make_unique<YoutubeSearchPage>(this), create_search_bar("Search...", 350)}); auto history_body = create_body(); - auto history_search_bar = create_search_bar("Search...", SEARCH_DELAY_FILTER); - auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), history_search_bar.get(), HistoryType::YOUTUBE); - tabs.push_back(Tab{std::move(history_body), std::move(history_page), std::move(history_search_bar)}); + auto history_page = std::make_unique<HistoryPage>(this, tabs.front().page.get(), HistoryType::YOUTUBE); + tabs.push_back(Tab{std::move(history_body), std::move(history_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); - auto recommended_search_bar = create_search_bar("Search...", SEARCH_DELAY_FILTER); - auto recommended_page = std::make_unique<RecommendedPage>(this, tabs.front().page.get(), recommended_search_bar.get(), plugin_name); - tabs.push_back(Tab{create_body(), std::move(recommended_page), std::move(recommended_search_bar)}); + auto recommended_page = std::make_unique<YoutubeRecommendedPage>(this); + tabs.push_back(Tab{create_body(), std::move(recommended_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); } else if(strcmp(plugin_name, "pornhub") == 0) { auto search_page = std::make_unique<MediaGenericSearchPage>(this, "https://www.pornhub.com/", sf::Vector2i(320/1.5f, 180/1.5f)); add_pornhub_handlers(search_page.get()); @@ -1267,16 +1113,6 @@ namespace QuickMedia { return json_result; } - // This is not cached because we could have multiple instances of QuickMedia running the same plugin! - // TODO: Find a way to optimize this - Json::Value Program::load_recommended_json() { - Path recommended_filepath = get_recommended_filepath(plugin_name); - Json::Value json_result; - if(!read_file_as_json(recommended_filepath, json_result) || !json_result.isObject()) - json_result = Json::Value(Json::objectValue); - return json_result; - } - void Program::manga_get_watch_history(const char *plugin_name, BodyItems &history_items) { // TOOD: Make generic, instead of checking for plugin Path content_storage_dir = get_storage_dir().join(plugin_name); @@ -1559,13 +1395,16 @@ namespace QuickMedia { if(after_submit_handler) after_submit_handler(new_tabs); - for(Tab &tab : tabs) { - tab.body->clear_cache(); + for(size_t i = 0; i < tabs.size(); ++i) { + tabs[i].body->clear_cache(); + if(tabs[i].page->is_lazy_fetch_page() && static_cast<LazyFetchPage*>(tabs[i].page.get())->reload_on_page_change()) + tab_associated_data[i].lazy_fetch_finished = false; } if(tabs[selected_tab].page->allow_submit_no_selection()) { page_loop(new_tabs, 0, after_submit_handler); } else if(new_tabs.size() == 1 && new_tabs[0].page->get_type() == PageTypez::MANGA_IMAGES) { + page_stack.push(current_page); select_episode(selected_item.get(), false); Body *chapters_body = tabs[selected_tab].body.get(); chapters_body->filter_search_fuzzy(""); // Needed (or not really) to go to the next chapter when reaching the last page of a chapter @@ -1608,9 +1447,11 @@ namespace QuickMedia { window.setKeyRepeatEnabled(true); malloc_trim(0); } else if(new_tabs.size() == 1 && new_tabs[0].page->get_type() == PageTypez::IMAGE_BOARD_THREAD) { + page_stack.push(current_page); current_page = PageType::IMAGE_BOARD_THREAD; image_board_thread_page(static_cast<ImageBoardThreadPage*>(new_tabs[0].page.get()), new_tabs[0].body.get()); } else if(new_tabs.size() == 1 && new_tabs[0].page->get_type() == PageTypez::VIDEO) { + page_stack.push(current_page); current_page = PageType::VIDEO_CONTENT; int selected_index = tabs[selected_tab].body->get_selected_item(); video_content_page(tabs[selected_tab].page.get(), static_cast<VideoPage*>(new_tabs[0].page.get()), selected_item->get_title(), false, tabs[selected_tab].body->items, selected_index, &tab_associated_data[selected_tab].fetched_page, tab_associated_data[selected_tab].update_search_text); @@ -2004,68 +1845,6 @@ namespace QuickMedia { return true; } - void Program::save_recommendations_from_related_videos(const std::string &video_url, const std::string &video_title, const BodyItems &related_media_body_items) { - std::string video_id; - if(!youtube_url_extract_id(video_url, video_id)) { - std::string err_msg = "Failed to extract id of youtube url "; - err_msg += video_url; - err_msg + ", video wont be saved in recommendations"; - show_notification("QuickMedia", err_msg.c_str(), Urgency::LOW); - return; - } - - Json::Value recommended_json = load_recommended_json(); - time_t time_now = time(NULL); - - Json::Value &existing_recommended_json = recommended_json[video_id]; - if(existing_recommended_json.isObject()) { - int64_t watched_count = 0; - Json::Value &watched_count_json = existing_recommended_json["watched_count"]; - if(watched_count_json.isNumeric()) - watched_count = watched_count_json.asInt64(); - existing_recommended_json["watched_count"] = watched_count + 1; - existing_recommended_json["watched_timestamp"] = time_now; - } else { - Json::Value new_content_object(Json::objectValue); - new_content_object["title"] = video_title; - new_content_object["recommended_timestamp"] = time_now; - new_content_object["recommended_count"] = 1; - new_content_object["watched_count"] = 1; - new_content_object["watched_timestamp"] = time_now; - recommended_json[video_id] = std::move(new_content_object); - } - - int saved_recommendation_count = 0; - for(const auto &body_item : related_media_body_items) { - std::string recommended_video_id; - if(youtube_url_extract_id(body_item->url, recommended_video_id)) { - Json::Value &existing_recommendation = recommended_json[recommended_video_id]; - if(existing_recommendation.isObject()) { - int64_t recommended_count = 0; - Json::Value &count_json = existing_recommendation["recommended_count"]; - if(count_json.isNumeric()) - recommended_count = count_json.asInt64(); - existing_recommendation["recommended_count"] = recommended_count + 1; - existing_recommendation["recommended_timestamp"] = time_now; - } else { - Json::Value new_content_object(Json::objectValue); - new_content_object["title"] = body_item->get_title(); - new_content_object["recommended_timestamp"] = time_now; - new_content_object["recommended_count"] = 1; - recommended_json[recommended_video_id] = std::move(new_content_object); - saved_recommendation_count++; - /* TODO: Save more than the first 3 video that hasn't been watched yet? */ - if(saved_recommendation_count == 3) - break; - } - } else { - fprintf(stderr, "Failed to extract id of youtube url %s, video wont be saved in recommendations\n", video_url.c_str()); - } - } - - save_json_to_file_atomic(get_recommended_filepath(plugin_name), recommended_json); - } - static const char *useragent_str = "user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36"; static int accumulate_string_limit_head(char *data, int size, void *userdata) { @@ -2145,7 +1924,6 @@ namespace QuickMedia { void Program::video_content_page(Page *parent_page, VideoPage *video_page, std::string video_title, bool download_if_streaming_fails, BodyItems &next_play_items, int play_index, int *parent_body_page, const std::string &parent_page_search) { sf::Clock time_watched_timer; - bool added_recommendations = false; bool video_loaded = false; const bool is_youtube = strcmp(plugin_name, "youtube") == 0; const bool is_matrix = strcmp(plugin_name, "matrix") == 0; @@ -2224,11 +2002,10 @@ namespace QuickMedia { std::function<void(const char*)> video_event_callback; - auto load_video_error_check = [this, &related_videos, &channel_url, &video_url, &video_title, &video_player, previous_page, &time_watched_timer, &video_loaded, &added_recommendations, video_page, &video_event_callback, &on_window_create, &video_player_window, is_matrix](bool resume_video) mutable { + auto load_video_error_check = [this, &related_videos, &channel_url, &video_url, &video_title, &video_player, previous_page, &time_watched_timer, &video_loaded, video_page, &video_event_callback, &on_window_create, &video_player_window, is_matrix](bool resume_video) mutable { time_watched_timer.restart(); video_loaded = false; video_player_window = None; - added_recommendations = false; watched_videos.insert(video_url); video_player = std::make_unique<VideoPlayer>(no_video, use_system_mpv_config, resume_video, is_matrix, video_event_callback, on_window_create, resources_root, get_largest_monitor_height(disp)); @@ -2394,7 +2171,7 @@ namespace QuickMedia { video_player->quit_and_save_watch_later(); while(true) { VideoPlayer::Error update_err = video_player->update(); - if(update_err != VideoPlayer::Error::OK) + if(update_err != VideoPlayer::Error::OK || !window.isOpen() || current_page == PageType::EXIT) break; std::this_thread::sleep_for(std::chrono::milliseconds(20)); } @@ -2404,6 +2181,9 @@ namespace QuickMedia { } }); + if(!window.isOpen() || current_page == PageType::EXIT) + return; + if(page_changed) { current_page = PageType::VIDEO_CONTENT; //video_player = std::make_unique<VideoPlayer>(no_video, use_system_mpv_config, true, video_event_callback, on_window_create, resources_root); @@ -2500,12 +2280,6 @@ namespace QuickMedia { continue; } - /* Only save recommendations for the video if we have been watching it for 15 seconds */ - if(is_youtube && video_loaded && !added_recommendations && time_watched_timer.getElapsedTime().asSeconds() >= 15) { - added_recommendations = true; - save_recommendations_from_related_videos(video_url, video_title, related_videos); - } - if(video_player_window) { if(!cursor_visible) { std::this_thread::sleep_for(std::chrono::milliseconds(50)); |