From e3a151d5d2a71126c275567862a0cd2d471b42bc Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sun, 4 Sep 2022 22:35:17 +0200 Subject: Dramacool: watch progress --- src/plugins/DramaCool.cpp | 32 ++++++++- src/plugins/EpisodeNameParser.cpp | 112 ----------------------------- src/plugins/LocalAnime.cpp | 2 +- src/plugins/WatchProgress.cpp | 124 -------------------------------- src/plugins/Youtube.cpp | 2 +- src/plugins/utils/EpisodeNameParser.cpp | 112 +++++++++++++++++++++++++++++ src/plugins/utils/WatchProgress.cpp | 124 ++++++++++++++++++++++++++++++++ 7 files changed, 267 insertions(+), 241 deletions(-) delete mode 100644 src/plugins/EpisodeNameParser.cpp delete mode 100644 src/plugins/WatchProgress.cpp create mode 100644 src/plugins/utils/EpisodeNameParser.cpp create mode 100644 src/plugins/utils/WatchProgress.cpp (limited to 'src/plugins') diff --git a/src/plugins/DramaCool.cpp b/src/plugins/DramaCool.cpp index 9b419f9..4bb1054 100644 --- a/src/plugins/DramaCool.cpp +++ b/src/plugins/DramaCool.cpp @@ -2,6 +2,7 @@ #include "../../include/Theme.hpp" #include "../../include/StringUtils.hpp" #include "../../include/M3U8.hpp" +#include "../../plugins/utils/WatchProgress.hpp" #include #include @@ -118,7 +119,7 @@ namespace QuickMedia { auto body = create_body(); body->set_items(std::move(result_items)); - result_tabs.push_back(Tab{ std::move(body), std::make_unique(program), create_search_bar("Search...", SEARCH_DELAY_FILTER) }); + result_tabs.push_back(Tab{ std::move(body), std::make_unique(program, args.title), create_search_bar("Search...", SEARCH_DELAY_FILTER) }); return PluginResult::OK; } @@ -395,13 +396,13 @@ namespace QuickMedia { } */ - result_tabs.push_back(Tab{ nullptr, std::make_unique(program, std::move(video_url), args.title, std::move(referer)), nullptr }); + result_tabs.push_back(Tab{ nullptr, std::make_unique(program, std::move(video_url), series_name, args.title, std::move(referer)), nullptr }); return PluginResult::OK; } PluginResult DramaCoolVideoPage::load(const SubmitArgs&, VideoInfo &video_info, std::string &err_str) { - video_info.title = title; + video_info.title = episode_name; video_info.channel_url.clear(); video_info.duration = 0.0; video_info.chapters.clear(); @@ -409,4 +410,29 @@ namespace QuickMedia { err_str.clear(); return PluginResult::OK; } + + std::string DramaCoolVideoPage::get_url_timestamp() { + // TODO: Remove very old videos, to not make this file too large which slows this down on slow harddrives + std::unordered_map watch_progress = get_watch_progress_for_plugin("dramacool"); + auto it = watch_progress.find(get_video_id()); + if(it == watch_progress.end()) + return ""; + + // If we are very close to the end then start from the beginning. + // This is the same behavior as mpv. + // This is better because we dont want the video player to stop immediately after we start playing and we dont get any chance to seek. + if(it->second.time_pos_sec + 10.0 >= it->second.duration_sec) + return ""; + else + return std::to_string(it->second.time_pos_sec); + } + + void DramaCoolVideoPage::set_watch_progress(int64_t time_pos_sec, int64_t duration_sec) { + std::string video_id = get_video_id(); + set_watch_progress_for_plugin("dramacool", video_id, time_pos_sec, duration_sec, video_id); + } + + std::string DramaCoolVideoPage::get_video_id() const { + return series_name + "/" + episode_name; + } } \ No newline at end of file diff --git a/src/plugins/EpisodeNameParser.cpp b/src/plugins/EpisodeNameParser.cpp deleted file mode 100644 index cbaae50..0000000 --- a/src/plugins/EpisodeNameParser.cpp +++ /dev/null @@ -1,112 +0,0 @@ -#include "../../plugins/EpisodeNameParser.hpp" - -namespace QuickMedia { - static bool has_season_in_name(std::string_view episode_name) { - size_t sep_count = 0; - size_t index = 0; - while(true) { - size_t next_index = episode_name.find(" - ", index); - if(next_index == std::string_view::npos) - break; - - index = next_index + 3; - ++sep_count; - } - return sep_count >= 2; - } - - static bool is_whitespace(char c) { - return c == ' ' || c == '\n' || c == '\t' || c == '\v'; - } - - static std::string_view strip_left(std::string_view str) { - size_t i = 0; - for(; i < str.size(); ++i) { - if(!is_whitespace(str[i])) - break; - } - return str.substr(i); - } - - static std::string_view strip_right(std::string_view str) { - long i = (long)str.size() - 1; - for(; i >= 0; --i) { - if(!is_whitespace(str[i])) - break; - } - return str.substr(0, i + 1); - } - - static std::string_view episode_name_extract_group(std::string_view &episode_name) { - episode_name = strip_left(episode_name); - if(episode_name[0] == '[') { - size_t group_end_index = episode_name.find(']', 1); - if(group_end_index == std::string_view::npos) - return {}; - - std::string_view group = episode_name.substr(1, group_end_index - 1); - episode_name.remove_prefix(group_end_index + 1); - return group; - } - return {}; - } - - static std::string_view episode_name_extract_anime(std::string_view &episode_name) { - episode_name = strip_left(episode_name); - size_t episode_or_season_sep_index = episode_name.find(" - "); - if(episode_or_season_sep_index == std::string_view::npos) - episode_or_season_sep_index = episode_name.size(); - - std::string_view anime = episode_name.substr(0, episode_or_season_sep_index); - anime = strip_right(anime); - if(episode_or_season_sep_index + 3 > episode_name.size()) - episode_name = {}; - else - episode_name.remove_prefix(episode_or_season_sep_index + 3); - - return anime; - } - - static std::string_view episode_name_extract_season(std::string_view &episode_name) { - return episode_name_extract_anime(episode_name); - } - - static bool is_num_real_char(char c) { - return (c >= '0' && c <= '9') || c == '.'; - } - - static std::string_view episode_name_extract_episode(std::string_view &episode_name) { - episode_name = strip_left(episode_name); - size_t i = 0; - for(; i < episode_name.size(); ++i) { - if(!is_num_real_char(episode_name[i])) - break; - } - - if(i == 0) - return {}; - - std::string_view episode = episode_name.substr(0, i); - episode_name.remove_prefix(i + 1); - return episode; - } - - std::optional episode_name_extract_parts(std::string_view episode_name) { - EpisodeNameParts name_parts; - const bool has_season = has_season_in_name(episode_name); - - name_parts.group = episode_name_extract_group(episode_name); - name_parts.anime = episode_name_extract_anime(episode_name); - if(name_parts.anime.empty()) - return std::nullopt; - - if(has_season) - name_parts.season = episode_name_extract_season(episode_name); - - name_parts.episode = episode_name_extract_episode(episode_name); - if(name_parts.episode.empty()) - return std::nullopt; - - return name_parts; - } -} \ No newline at end of file diff --git a/src/plugins/LocalAnime.cpp b/src/plugins/LocalAnime.cpp index 1bc8ca8..6337adf 100644 --- a/src/plugins/LocalAnime.cpp +++ b/src/plugins/LocalAnime.cpp @@ -6,7 +6,7 @@ #include "../../include/FileAnalyzer.hpp" #include "../../include/ResourceLoader.hpp" #include "../../include/StringUtils.hpp" -#include "../../plugins/EpisodeNameParser.hpp" +#include "../../plugins/utils/EpisodeNameParser.hpp" #include #include #include diff --git a/src/plugins/WatchProgress.cpp b/src/plugins/WatchProgress.cpp deleted file mode 100644 index db12273..0000000 --- a/src/plugins/WatchProgress.cpp +++ /dev/null @@ -1,124 +0,0 @@ -#include "../../plugins/WatchProgress.hpp" -#include "../../include/Storage.hpp" -#include "../../include/Notification.hpp" -#include - -namespace QuickMedia { - double WatchProgress::get_watch_ratio() const { - if(duration_sec == 0) - return 0; - return (double)time_pos_sec / (double)duration_sec; - } - - // We consider having watched the video if the user stopped watching 90% in, because they might skip the ending theme/credits (especially in anime) - bool WatchProgress::has_finished_watching() const { - return get_watch_ratio() >= 0.9; - } - - bool set_watch_progress_for_plugin(const char *plugin_name, const std::string &id, int64_t time_pos_sec, int64_t duration_sec, const std::string &thumbnail_url) { - Path watch_progress_dir = get_storage_dir().join("watch-progress"); - if(create_directory_recursive(watch_progress_dir) != 0) { - show_notification("QuickMedia", "Failed to create " + watch_progress_dir.data + " to set watch progress for " + id, Urgency::CRITICAL); - return false; - } - - Path progress_path = watch_progress_dir; - progress_path.join(plugin_name); - - Json::Value json_root; - if(!read_file_as_json(progress_path, json_root) || !json_root.isObject()) - json_root = Json::Value(Json::objectValue); - - Json::Value watch_progress_json(Json::objectValue); - watch_progress_json["time"] = (int64_t)time_pos_sec; - watch_progress_json["duration"] = (int64_t)duration_sec; - watch_progress_json["thumbnail_url"] = thumbnail_url; - watch_progress_json["timestamp"] = (int64_t)time(nullptr); - json_root[id] = std::move(watch_progress_json); - - if(!save_json_to_file_atomic(progress_path, json_root)) { - show_notification("QuickMedia", "Failed to set watch progress for " + id, Urgency::CRITICAL); - return false; - } - - fprintf(stderr, "Set watch progress for \"%s\" to %d/%d\n", id.c_str(), (int)time_pos_sec, (int)duration_sec); - return true; - } - - std::unordered_map get_watch_progress_for_plugin(const char *plugin_name) { - std::unordered_map watch_progress_map; - Path progress_path = get_storage_dir().join("watch-progress").join(plugin_name); - - Json::Value json_root; - if(!read_file_as_json(progress_path, json_root) || !json_root.isObject()) - return watch_progress_map; - - for(Json::Value::const_iterator it = json_root.begin(); it != json_root.end(); ++it) { - Json::Value key = it.key(); - if(!key.isString()) - continue; - - const Json::Value &time_json = (*it)["time"]; - const Json::Value &duration_json = (*it)["duration"]; - const Json::Value ×tamp_json = (*it)["timestamp"]; - if(!time_json.isInt64() || !duration_json.isInt64() || !timestamp_json.isInt64()) - continue; - - WatchProgress watch_progress; - watch_progress.time_pos_sec = time_json.asInt64(); - watch_progress.duration_sec = duration_json.asInt64(); - watch_progress.timestamp = timestamp_json.asInt64(); - - const Json::Value &thumbnail_url_json = (*it)["thumbnail_url"]; - if(thumbnail_url_json.isString()) - watch_progress.thumbnail_url = thumbnail_url_json.asString(); - - watch_progress_map[key.asString()] = std::move(watch_progress); - } - - return watch_progress_map; - } - - bool toggle_watched_for_plugin_save_to_file(const char *plugin_name, const std::string &id, int64_t duration_sec, const std::string &thumbnail_url, WatchedStatus &watched_status) { - Path local_anime_progress_path = get_storage_dir().join("watch-progress").join(plugin_name); - - Json::Value json_root; - if(!read_file_as_json(local_anime_progress_path, json_root) || !json_root.isObject()) - json_root = Json::Value(Json::objectValue); - - bool watched = false; - Json::Value &watched_item = json_root[id]; - if(watched_item.isObject()) { - const Json::Value &time_json = watched_item["time"]; - const Json::Value &duration_json = watched_item["duration"]; - if(time_json.isInt64() && duration_json.isInt64()) { - WatchProgress watch_progress; - watch_progress.time_pos_sec = time_json.asInt64(); - watch_progress.duration_sec = duration_json.asInt64(); - watched = watch_progress.has_finished_watching(); - } else { - watched = false; - } - } else { - watched_item = Json::Value(Json::objectValue); - watched = false; - } - - if(watched) { - json_root.removeMember(id.c_str()); - } else { - watched_item["time"] = (int64_t)duration_sec; - watched_item["duration"] = (int64_t)duration_sec; - watched_item["thumbnail_url"] = thumbnail_url; - watched_item["timestamp"] = (int64_t)time(nullptr); - } - - if(!save_json_to_file_atomic(local_anime_progress_path, json_root)) { - show_notification("QuickMedia", "Failed to mark " + id + " as " + (watched ? "not watched" : "watched"), Urgency::CRITICAL); - return false; - } - - watched_status = watched ? WatchedStatus::NOT_WATCHED : WatchedStatus::WATCHED; - return true; - } -} \ No newline at end of file diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index 93cb266..caf3a25 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -8,7 +8,7 @@ #include "../../include/Utils.hpp" #include "../../include/Theme.hpp" #include "../../include/Config.hpp" -#include "../../plugins/WatchProgress.hpp" +#include "../../plugins/utils/WatchProgress.hpp" #include "../../include/QuickMedia.hpp" #include #include diff --git a/src/plugins/utils/EpisodeNameParser.cpp b/src/plugins/utils/EpisodeNameParser.cpp new file mode 100644 index 0000000..de2b8ac --- /dev/null +++ b/src/plugins/utils/EpisodeNameParser.cpp @@ -0,0 +1,112 @@ +#include "../../../plugins/utils/EpisodeNameParser.hpp" + +namespace QuickMedia { + static bool has_season_in_name(std::string_view episode_name) { + size_t sep_count = 0; + size_t index = 0; + while(true) { + size_t next_index = episode_name.find(" - ", index); + if(next_index == std::string_view::npos) + break; + + index = next_index + 3; + ++sep_count; + } + return sep_count >= 2; + } + + static bool is_whitespace(char c) { + return c == ' ' || c == '\n' || c == '\t' || c == '\v'; + } + + static std::string_view strip_left(std::string_view str) { + size_t i = 0; + for(; i < str.size(); ++i) { + if(!is_whitespace(str[i])) + break; + } + return str.substr(i); + } + + static std::string_view strip_right(std::string_view str) { + long i = (long)str.size() - 1; + for(; i >= 0; --i) { + if(!is_whitespace(str[i])) + break; + } + return str.substr(0, i + 1); + } + + static std::string_view episode_name_extract_group(std::string_view &episode_name) { + episode_name = strip_left(episode_name); + if(episode_name[0] == '[') { + size_t group_end_index = episode_name.find(']', 1); + if(group_end_index == std::string_view::npos) + return {}; + + std::string_view group = episode_name.substr(1, group_end_index - 1); + episode_name.remove_prefix(group_end_index + 1); + return group; + } + return {}; + } + + static std::string_view episode_name_extract_anime(std::string_view &episode_name) { + episode_name = strip_left(episode_name); + size_t episode_or_season_sep_index = episode_name.find(" - "); + if(episode_or_season_sep_index == std::string_view::npos) + episode_or_season_sep_index = episode_name.size(); + + std::string_view anime = episode_name.substr(0, episode_or_season_sep_index); + anime = strip_right(anime); + if(episode_or_season_sep_index + 3 > episode_name.size()) + episode_name = {}; + else + episode_name.remove_prefix(episode_or_season_sep_index + 3); + + return anime; + } + + static std::string_view episode_name_extract_season(std::string_view &episode_name) { + return episode_name_extract_anime(episode_name); + } + + static bool is_num_real_char(char c) { + return (c >= '0' && c <= '9') || c == '.'; + } + + static std::string_view episode_name_extract_episode(std::string_view &episode_name) { + episode_name = strip_left(episode_name); + size_t i = 0; + for(; i < episode_name.size(); ++i) { + if(!is_num_real_char(episode_name[i])) + break; + } + + if(i == 0) + return {}; + + std::string_view episode = episode_name.substr(0, i); + episode_name.remove_prefix(i + 1); + return episode; + } + + std::optional episode_name_extract_parts(std::string_view episode_name) { + EpisodeNameParts name_parts; + const bool has_season = has_season_in_name(episode_name); + + name_parts.group = episode_name_extract_group(episode_name); + name_parts.anime = episode_name_extract_anime(episode_name); + if(name_parts.anime.empty()) + return std::nullopt; + + if(has_season) + name_parts.season = episode_name_extract_season(episode_name); + + name_parts.episode = episode_name_extract_episode(episode_name); + if(name_parts.episode.empty()) + return std::nullopt; + + return name_parts; + } +} \ No newline at end of file diff --git a/src/plugins/utils/WatchProgress.cpp b/src/plugins/utils/WatchProgress.cpp new file mode 100644 index 0000000..805f6e7 --- /dev/null +++ b/src/plugins/utils/WatchProgress.cpp @@ -0,0 +1,124 @@ +#include "../../../plugins/utils/WatchProgress.hpp" +#include "../../../include/Storage.hpp" +#include "../../../include/Notification.hpp" +#include + +namespace QuickMedia { + double WatchProgress::get_watch_ratio() const { + if(duration_sec == 0) + return 0; + return (double)time_pos_sec / (double)duration_sec; + } + + // We consider having watched the video if the user stopped watching 90% in, because they might skip the ending theme/credits (especially in anime) + bool WatchProgress::has_finished_watching() const { + return get_watch_ratio() >= 0.9; + } + + bool set_watch_progress_for_plugin(const char *plugin_name, const std::string &id, int64_t time_pos_sec, int64_t duration_sec, const std::string &thumbnail_url) { + Path watch_progress_dir = get_storage_dir().join("watch-progress"); + if(create_directory_recursive(watch_progress_dir) != 0) { + show_notification("QuickMedia", "Failed to create " + watch_progress_dir.data + " to set watch progress for " + id, Urgency::CRITICAL); + return false; + } + + Path progress_path = watch_progress_dir; + progress_path.join(plugin_name); + + Json::Value json_root; + if(!read_file_as_json(progress_path, json_root) || !json_root.isObject()) + json_root = Json::Value(Json::objectValue); + + Json::Value watch_progress_json(Json::objectValue); + watch_progress_json["time"] = (int64_t)time_pos_sec; + watch_progress_json["duration"] = (int64_t)duration_sec; + watch_progress_json["thumbnail_url"] = thumbnail_url; + watch_progress_json["timestamp"] = (int64_t)time(nullptr); + json_root[id] = std::move(watch_progress_json); + + if(!save_json_to_file_atomic(progress_path, json_root)) { + show_notification("QuickMedia", "Failed to set watch progress for " + id, Urgency::CRITICAL); + return false; + } + + fprintf(stderr, "Set watch progress for \"%s\" to %d/%d\n", id.c_str(), (int)time_pos_sec, (int)duration_sec); + return true; + } + + std::unordered_map get_watch_progress_for_plugin(const char *plugin_name) { + std::unordered_map watch_progress_map; + Path progress_path = get_storage_dir().join("watch-progress").join(plugin_name); + + Json::Value json_root; + if(!read_file_as_json(progress_path, json_root) || !json_root.isObject()) + return watch_progress_map; + + for(Json::Value::const_iterator it = json_root.begin(); it != json_root.end(); ++it) { + Json::Value key = it.key(); + if(!key.isString()) + continue; + + const Json::Value &time_json = (*it)["time"]; + const Json::Value &duration_json = (*it)["duration"]; + const Json::Value ×tamp_json = (*it)["timestamp"]; + if(!time_json.isInt64() || !duration_json.isInt64() || !timestamp_json.isInt64()) + continue; + + WatchProgress watch_progress; + watch_progress.time_pos_sec = time_json.asInt64(); + watch_progress.duration_sec = duration_json.asInt64(); + watch_progress.timestamp = timestamp_json.asInt64(); + + const Json::Value &thumbnail_url_json = (*it)["thumbnail_url"]; + if(thumbnail_url_json.isString()) + watch_progress.thumbnail_url = thumbnail_url_json.asString(); + + watch_progress_map[key.asString()] = std::move(watch_progress); + } + + return watch_progress_map; + } + + bool toggle_watched_for_plugin_save_to_file(const char *plugin_name, const std::string &id, int64_t duration_sec, const std::string &thumbnail_url, WatchedStatus &watched_status) { + Path local_anime_progress_path = get_storage_dir().join("watch-progress").join(plugin_name); + + Json::Value json_root; + if(!read_file_as_json(local_anime_progress_path, json_root) || !json_root.isObject()) + json_root = Json::Value(Json::objectValue); + + bool watched = false; + Json::Value &watched_item = json_root[id]; + if(watched_item.isObject()) { + const Json::Value &time_json = watched_item["time"]; + const Json::Value &duration_json = watched_item["duration"]; + if(time_json.isInt64() && duration_json.isInt64()) { + WatchProgress watch_progress; + watch_progress.time_pos_sec = time_json.asInt64(); + watch_progress.duration_sec = duration_json.asInt64(); + watched = watch_progress.has_finished_watching(); + } else { + watched = false; + } + } else { + watched_item = Json::Value(Json::objectValue); + watched = false; + } + + if(watched) { + json_root.removeMember(id.c_str()); + } else { + watched_item["time"] = (int64_t)duration_sec; + watched_item["duration"] = (int64_t)duration_sec; + watched_item["thumbnail_url"] = thumbnail_url; + watched_item["timestamp"] = (int64_t)time(nullptr); + } + + if(!save_json_to_file_atomic(local_anime_progress_path, json_root)) { + show_notification("QuickMedia", "Failed to mark " + id + " as " + (watched ? "not watched" : "watched"), Urgency::CRITICAL); + return false; + } + + watched_status = watched ? WatchedStatus::NOT_WATCHED : WatchedStatus::WATCHED; + return true; + } +} \ No newline at end of file -- cgit v1.2.3