#include "../../../plugins/utils/EpisodeNameParser.hpp" namespace QuickMedia { static bool is_num(char c) { return c >= '0' && c <= '9'; } static bool char_to_num(char c) { return c - '0'; } // Finds and parses SXXEXX where X is a number, returns the index to after the pattern or std::string_view::npos static size_t parse_season_episode_pattern(std::string_view episode_name, int *season, int *episode) { *season = 0; *episode = 0; size_t index = 0; while(true) { index = episode_name.find('S', index); if(index == std::string_view::npos) return std::string_view::npos; if(index + 6 >= episode_name.size()) return std::string_view::npos; if(is_num(episode_name[index + 1]) && is_num(episode_name[index + 2]) && episode_name[index + 3] == 'E' && is_num(episode_name[index + 4]) && is_num(episode_name[index + 5])) { *season = char_to_num(episode_name[index + 1]) * 10 + char_to_num(episode_name[index + 2]); *episode = char_to_num(episode_name[index + 4]) * 10 + char_to_num(episode_name[index + 5]); return index + 6; } ++index; } } 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_real_num(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_real_num(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; } static bool ends_with(std::string_view str, std::string_view substr) { return str.size() >= substr.size() && str.substr(str.size() - substr.size()) == substr; } std::optional episode_name_extract_parts(std::string_view episode_name) { const std::string_view full_episode_name = episode_name; EpisodeNameParts name_parts; int season = 0; int episode = 0; size_t after_season_episode_pattern = parse_season_episode_pattern(episode_name, &season, &episode); const bool has_season = after_season_episode_pattern != std::string_view::npos || 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(after_season_episode_pattern == std::string_view::npos) { 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; } else { name_parts.season = full_episode_name.substr(after_season_episode_pattern - 5, 2); name_parts.episode = full_episode_name.substr(after_season_episode_pattern - 2, 2); } if(episode_name.find("480p") != std::string_view::npos) name_parts.resolution = "480p"; else if(episode_name.find("720p") != std::string_view::npos) name_parts.resolution = "720p"; else if(episode_name.find("1080p") != std::string_view::npos) name_parts.resolution = "1080p"; else if(episode_name.find("2160p") != std::string_view::npos) name_parts.resolution = "4k"; else if(episode_name.find("1280x720") != std::string_view::npos) name_parts.resolution = "720p"; else if(episode_name.find("1920x1080") != std::string_view::npos) name_parts.resolution = "1080p"; else if(episode_name.find("3840x2160") != std::string_view::npos) name_parts.resolution = "4k"; if(ends_with(episode_name, ".mkv")) name_parts.file_ext = ".mkv"; else if(ends_with(episode_name, ".mp4")) name_parts.file_ext = ".mp4"; else if(ends_with(episode_name, ".avi")) name_parts.file_ext = ".avi"; else if(ends_with(episode_name, ".webm")) name_parts.file_ext = ".webm"; else if(ends_with(episode_name, ".wmv")) name_parts.file_ext = ".wmv"; else if(ends_with(episode_name, ".mp3")) name_parts.file_ext = ".mp3"; else if(ends_with(episode_name, ".flac")) name_parts.file_ext = ".flac"; else if(ends_with(episode_name, ".opus")) name_parts.file_ext = ".opus"; else if(ends_with(episode_name, ".wav")) name_parts.file_ext = ".wav"; else if(ends_with(episode_name, ".ogg")) name_parts.file_ext = ".ogg"; return name_parts; } }