aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/utils
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2022-09-04 22:35:17 +0200
committerdec05eba <dec05eba@protonmail.com>2022-09-04 22:35:17 +0200
commite3a151d5d2a71126c275567862a0cd2d471b42bc (patch)
tree6bbfec0cf7a6bae800c96e53f4990046a450f12f /src/plugins/utils
parent87c8a2986d468a3fc897169c1b00fc4695e09d39 (diff)
Dramacool: watch progress
Diffstat (limited to 'src/plugins/utils')
-rw-r--r--src/plugins/utils/EpisodeNameParser.cpp112
-rw-r--r--src/plugins/utils/WatchProgress.cpp124
2 files changed, 236 insertions, 0 deletions
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<EpisodeNameParts> 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 <json/value.h>
+
+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<std::string, WatchProgress> get_watch_progress_for_plugin(const char *plugin_name) {
+ std::unordered_map<std::string, WatchProgress> 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 &timestamp_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