aboutsummaryrefslogtreecommitdiff
path: root/src/QuickMedia.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/QuickMedia.cpp')
-rw-r--r--src/QuickMedia.cpp298
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));