diff options
author | dec05eba <dec05eba@protonmail.com> | 2022-09-04 05:01:36 +0200 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2022-09-04 08:44:51 +0200 |
commit | 87c8a2986d468a3fc897169c1b00fc4695e09d39 (patch) | |
tree | bfd1d39d84680389a2bd30c9e1cdde5e844a3a5b /src | |
parent | 84f501f5211f09a09fc5384bf15415d0d0445a96 (diff) |
Add dramacool
Diffstat (limited to 'src')
-rw-r--r-- | src/M3U8.cpp | 74 | ||||
-rw-r--r-- | src/QuickMedia.cpp | 49 | ||||
-rw-r--r-- | src/VideoPlayer.cpp | 5 | ||||
-rw-r--r-- | src/plugins/DramaCool.cpp | 412 | ||||
-rw-r--r-- | src/plugins/HotExamples.cpp | 2 | ||||
-rw-r--r-- | src/plugins/Lbry.cpp | 6 | ||||
-rw-r--r-- | src/plugins/MangaGeneric.cpp | 2 | ||||
-rw-r--r-- | src/plugins/Manganelo.cpp | 16 | ||||
-rw-r--r-- | src/plugins/MediaGeneric.cpp | 6 | ||||
-rw-r--r-- | src/plugins/MyAnimeList.cpp | 10 | ||||
-rw-r--r-- | src/plugins/NyaaSi.cpp | 8 | ||||
-rw-r--r-- | src/plugins/Peertube.cpp | 8 | ||||
-rw-r--r-- | src/plugins/Saucenao.cpp | 4 | ||||
-rw-r--r-- | src/plugins/Soundcloud.cpp | 8 | ||||
-rw-r--r-- | src/plugins/Youtube.cpp | 6 |
15 files changed, 553 insertions, 63 deletions
diff --git a/src/M3U8.cpp b/src/M3U8.cpp new file mode 100644 index 0000000..72421b7 --- /dev/null +++ b/src/M3U8.cpp @@ -0,0 +1,74 @@ +#include "../include/M3U8.hpp" +#include <algorithm> + +namespace QuickMedia { + // static + M3U8Stream M3U8Stream::get_highest_resolution_stream(const std::vector<M3U8Stream> &streams) { + auto streams_copy = streams; + std::sort(streams_copy.begin(), streams_copy.end(), [](const M3U8Stream &stream1, const M3U8Stream &stream2) { + return stream1.height > stream2.height; + }); + return streams_copy.front(); + } + + // TODO: Extract framerate + static bool stream_metadata_from_string(const std::string &metadata_str, M3U8Stream &stream) { + size_t index = metadata_str.find("RESOLUTION="); + if(index == std::string::npos) + return false; + + index += 11; + + int width = 0; + int height = 0; + if(sscanf(metadata_str.c_str() + index, "%dx%d", &width, &height) != 2) + return false; + + stream.width = width; + stream.height = height; + return true; + } + + static bool stream_extract_url(const std::string &m3u8_data, size_t offset, std::string &url) { + if(offset >= m3u8_data.size()) + return false; + + if(m3u8_data[offset] == '#') + return false; + + size_t line_end = m3u8_data.find("\n", offset); + if(line_end == std::string::npos) + line_end = m3u8_data.size(); + + url = m3u8_data.substr(offset, line_end - offset); + return true; + } + + // TODO: Also check for EXT-X-I-FRAME-STREAM-INF? + std::vector<M3U8Stream> m3u8_get_streams(const std::string &m3u8_data) { + std::vector<M3U8Stream> streams; + size_t index = 0; + + while(index < m3u8_data.size()) { + index = m3u8_data.find("#EXT-X-STREAM-INF:", index); + if(index == std::string::npos) + break; + + index += 18; + size_t line_end = m3u8_data.find("\n", index); + if(line_end == std::string::npos) + line_end = m3u8_data.size(); + + std::string stream_metadata = m3u8_data.substr(index, line_end - index); + M3U8Stream stream; + if(stream_metadata_from_string(stream_metadata, stream) && stream_extract_url(m3u8_data, line_end + 1, stream.url)) { + index = line_end + 1 + stream.url.size() + 1; + streams.push_back(std::move(stream)); + } else { + index = line_end + 1; + } + } + + return streams; + } +}
\ No newline at end of file diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 7a18f47..2ac6f90 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -8,6 +8,7 @@ #include "../plugins/MediaGeneric.hpp" #include "../plugins/Youtube.hpp" #include "../plugins/Peertube.hpp" +#include "../plugins/DramaCool.hpp" #include "../plugins/Fourchan.hpp" #include "../plugins/NyaaSi.hpp" #include "../plugins/Matrix.hpp" @@ -88,6 +89,7 @@ static const std::pair<const char*, const char*> valid_plugins[] = { std::make_pair("manga", nullptr), std::make_pair("youtube", "yt_logo_rgb_dark_small.png"), std::make_pair("peertube", "peertube_logo.png"), + std::make_pair("dramacool", "dramacool_logo.png"), std::make_pair("soundcloud", "soundcloud_logo.png"), std::make_pair("lbry", "lbry_logo.png"), std::make_pair("pornhub", "pornhub_logo.png"), @@ -319,7 +321,7 @@ namespace QuickMedia { static void usage() { fprintf(stderr, "usage: quickmedia [plugin] [--no-video] [--dir <directory>] [-e <window>] [youtube-url]\n"); fprintf(stderr, "OPTIONS:\n"); - fprintf(stderr, " plugin The plugin to use. Should be either launcher, 4chan, manga, manganelo, manganelos, mangatown, mangakatana, mangadex, readm, onimanga, local-manga, local-anime, youtube, peertube, lbry, soundcloud, nyaa.si, matrix, saucenao, hotexamples, anilist, file-manager, stdin, pornhub, spankbang, xvideos or xhamster\n"); + fprintf(stderr, " plugin The plugin to use. Should be either launcher, 4chan, manga, manganelo, manganelos, mangatown, mangakatana, mangadex, readm, onimanga, local-manga, local-anime, youtube, peertube, lbry, soundcloud, nyaa.si, matrix, saucenao, hotexamples, anilist, dramacool, file-manager, stdin, pornhub, spankbang, xvideos or xhamster\n"); fprintf(stderr, " --no-video Only play audio when playing a video. Disabled by default\n"); fprintf(stderr, " --upscale-images Upscale low-resolution manga pages using waifu2x-ncnn-vulkan. Disabled by default\n"); fprintf(stderr, " --upscale-images-always Upscale manga pages using waifu2x-ncnn-vulkan, no matter what the original image resolution is. Disabled by default\n"); @@ -1054,6 +1056,7 @@ namespace QuickMedia { pipe_body->set_items({ create_launcher_body_item("4chan", "4chan", resources_root + "icons/4chan_launcher.png"), create_launcher_body_item("AniList", "anilist", resources_root + "images/anilist_logo.png"), + create_launcher_body_item("DramaCool", "dramacool", resources_root + "images/dramacool_logo.png"), create_launcher_body_item("Hot Examples", "hotexamples", ""), create_launcher_body_item("Lbry", "lbry", resources_root + "icons/lbry_launcher.png"), create_launcher_body_item("Local anime", "local-anime", ""), @@ -1282,6 +1285,8 @@ namespace QuickMedia { } else { tabs.push_back(Tab{create_body(false, true), std::make_unique<PeertubeSearchPage>(this, instance), create_search_bar("Search...", 500)}); } + } else if(strcmp(plugin_name, "dramacool") == 0) { + tabs.push_back(Tab{create_body(false, true), std::make_unique<DramaCoolSearchPage>(this), create_search_bar("Search...", 350)}); } else if(strcmp(plugin_name, "pornhub") == 0) { check_youtube_dl_installed(plugin_name); auto search_page = std::make_unique<MediaGenericSearchPage>(this, "https://www.pornhub.com/", mgl::vec2i(320/1.5f, 180/1.5f), true); @@ -3105,7 +3110,7 @@ namespace QuickMedia { bool video_loaded = false; double video_time_pos = 0.0; // Time in media in seconds. Updates every 5 seconds and when starting to watch the video and when seeking. - double video_duration = 0.0; // Time in seconds. 0 if unknown + VideoInfo video_info; // Duration time in seconds. 0 if unknown bool successfully_fetched_video_duration = false; bool successfully_fetched_time_pos = false; bool update_time_pos = false; @@ -3153,7 +3158,6 @@ namespace QuickMedia { int64_t youtube_video_content_length = 0; int64_t youtube_audio_content_length = 0; - std::string channel_url; AsyncTask<void> related_videos_task; EventCallbackFunc video_event_callback; bool go_to_previous_page = false; @@ -3162,16 +3166,14 @@ namespace QuickMedia { std::string audio_url; bool has_embedded_audio = true; - std::vector<MediaChapter> media_chapters; - auto load_video_error_check = [&](std::string start_time = "", bool reuse_media_source = false) mutable { video_player.reset(); - channel_url.clear(); + video_info.channel_url.clear(); video_loaded = false; successfully_fetched_video_duration = false; successfully_fetched_time_pos = false; video_player_window = None; - video_duration = 0.0; + video_info.duration = 0.0; bool is_audio_only = no_video; const int video_max_height = video_get_max_height(); @@ -3200,11 +3202,15 @@ namespace QuickMedia { for(int i = 0; i < num_retries; ++i) { bool cancelled = false; TaskResult load_result = run_task_with_loading_screen([&]() { - video_duration = 0.0; - if(video_page->load(submit_args, new_title, channel_url, video_duration, media_chapters, err_str) != PluginResult::OK) + video_info.duration = 0.0; + video_info.chapters.clear(); + + if(video_page->load(submit_args, video_info, err_str) != PluginResult::OK) return false; - if(video_duration > 0.001) + new_title = video_info.title; + + if(video_info.duration > 0.001) successfully_fetched_video_duration = true; std::string ext; @@ -3304,9 +3310,10 @@ namespace QuickMedia { startup_args.use_youtube_dl = use_youtube_dl && !video_page->is_local(); startup_args.title = video_title; startup_args.start_time = start_time; - startup_args.chapters = std::move(media_chapters); + startup_args.chapters = std::move(video_info.chapters); startup_args.plugin_name = plugin_name; startup_args.cache_on_disk = !video_page->is_local(); + startup_args.referer = video_info.referer; video_player = std::make_unique<VideoPlayer>(std::move(startup_args), video_event_callback, on_window_create); VideoPlayer::Error err = video_player->load_video(); @@ -3519,8 +3526,8 @@ namespace QuickMedia { XFlush(disp); std::vector<Tab> related_pages; - TaskResult related_pages_result = run_task_with_loading_screen([&video_page, &related_videos, &channel_url, &related_pages]{ - return video_page->get_related_pages(related_videos, channel_url, related_pages) == PluginResult::OK; + TaskResult related_pages_result = run_task_with_loading_screen([&video_page, &related_videos, &video_info, &related_pages]{ + return video_page->get_related_pages(related_videos, video_info.channel_url, related_pages) == PluginResult::OK; }); if(related_pages_result == TaskResult::FALSE) { @@ -3528,7 +3535,7 @@ namespace QuickMedia { show_notification("QuickMedia", "Failed to get related pages", Urgency::CRITICAL); } else if(related_pages_result == TaskResult::TRUE && !related_pages.empty()) { if(successfully_fetched_time_pos && successfully_fetched_video_duration) - video_page->set_watch_progress(video_time_pos, video_duration); + video_page->set_watch_progress(video_time_pos, video_info.duration); bool page_changed = false; double resume_start_time = 0.0; @@ -3720,8 +3727,8 @@ namespace QuickMedia { successfully_fetched_video_duration = true; double file_duration = 0.0; video_player->get_duration_in_file(&file_duration); - video_duration = std::max(video_duration, file_duration); - if(video_duration > 0.001) + video_info.duration = std::max(video_info.duration, file_duration); + if(video_info.duration > 0.001) successfully_fetched_video_duration = true; } } @@ -3750,7 +3757,7 @@ namespace QuickMedia { window_size.y = window_size_u.y; if(successfully_fetched_time_pos && successfully_fetched_video_duration) - video_page->set_watch_progress(video_time_pos, video_duration); + video_page->set_watch_progress(video_time_pos, video_info.duration); } void Program::select_episode(BodyItem *item, bool start_from_beginning) { @@ -7644,14 +7651,12 @@ namespace QuickMedia { std::string err_str; for(int i = 0; i < 3; ++i) { task_result = run_task_with_loading_screen([&]{ - std::string channel_url; - std::vector<MediaChapter> chapters; - filename.clear(); - double duration; + VideoInfo video_info; SubmitArgs submit_args; - if(youtube_video_page->load(submit_args, filename, channel_url, duration, chapters, err_str) != PluginResult::OK) + if(youtube_video_page->load(submit_args, video_info, err_str) != PluginResult::OK) return false; + filename = video_info.title; std::string ext; bool has_embedded_audio = true; video_url = no_video ? "" : youtube_video_page->get_video_url(video_max_height, has_embedded_audio, ext); diff --git a/src/VideoPlayer.cpp b/src/VideoPlayer.cpp index 5e34c8e..28da43d 100644 --- a/src/VideoPlayer.cpp +++ b/src/VideoPlayer.cpp @@ -229,6 +229,11 @@ namespace QuickMedia { else args.push_back(ytdl_format.c_str()); + // TODO: Properly escape referer quotes + std::string referer_arg = "--http-header-fields=Referer: " + startup_args.referer; + if(!startup_args.referer.empty()) + args.push_back(referer_arg.c_str()); + std::string mpris_arg; Path mpris_path = get_config_dir_xdg().join("mpv").join("scripts").join("mpris.so"); if(get_file_type(mpris_path) == FileType::REGULAR) diff --git a/src/plugins/DramaCool.cpp b/src/plugins/DramaCool.cpp new file mode 100644 index 0000000..9b419f9 --- /dev/null +++ b/src/plugins/DramaCool.cpp @@ -0,0 +1,412 @@ +#include "../../plugins/DramaCool.hpp" +#include "../../include/Theme.hpp" +#include "../../include/StringUtils.hpp" +#include "../../include/M3U8.hpp" +#include <json/value.h> +#include <quickmedia/HtmlSearch.h> + +// TODO: Add bookmarks page, history, track watch progress, automatically go to next episode, subscribe, etc. + +namespace QuickMedia { + SearchResult DramaCoolSearchPage::search(const std::string &str, BodyItems &result_items) { + if(str.empty()) + return SearchResult::OK; + + std::vector<CommandArg> additional_args = { + { "-H", "x-requested-with: XMLHttpRequest" } + }; + + Json::Value json_root; + DownloadResult result = download_json(json_root, "https://dramacool.cr/search?keyword=" + url_param_encode(str) + "&type=movies", std::move(additional_args), true); + if(result != DownloadResult::OK) return download_result_to_search_result(result); + + if(!json_root.isArray()) + return SearchResult::ERR; + + for(const Json::Value &json_item : json_root) { + if(!json_item.isObject()) + continue; + + const Json::Value &name_json = json_item["name"]; + const Json::Value &status_json = json_item["status"]; + const Json::Value &cover_url_json = json_item["cover"]; + const Json::Value &url_json = json_item["url"]; + + if(!name_json.isString() || !url_json.isString()) + continue; + + auto body_item = BodyItem::create(name_json.asString()); + if(status_json.isString()) { + body_item->set_description(status_json.asString()); + body_item->set_description_color(get_theme().faded_text_color); + } + if(cover_url_json.isString()) { + body_item->thumbnail_url = "https://imagecdn.me/" + url_param_encode(cover_url_json.asString()); + body_item->thumbnail_size = { 150, 225 }; + } + body_item->url = "https://dramacool.cr" + url_json.asString(); + + result_items.push_back(std::move(body_item)); + } + + return SearchResult::OK; + } + + static bool node_get_inner_text(const QuickMediaHtmlChildNode *node, QuickMediaStringView &str) { + if(!node || !node->node.is_tag || !node->node.first_child || node->node.first_child->node.is_tag) + return false; + + str = node->node.first_child->node.name; + return true; + } + + PluginResult DramaCoolSearchPage::submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) { + std::string website_data; + DownloadResult result = download_to_string(args.url, website_data, {}, true); + if(result != DownloadResult::OK) return download_result_to_plugin_result(result); + + BodyItems result_items; + + QuickMediaHtmlSearch html_search; + int res = quickmedia_html_search_init(&html_search, website_data.c_str(), website_data.size()); + if(res != 0) + return PluginResult::ERR; + + quickmedia_html_find_nodes_xpath(&html_search, "//div[class='content']//a", + [](QuickMediaMatchNode *node, void *userdata) { + auto *result_items = (BodyItems*)userdata; + QuickMediaStringView href = quickmedia_html_node_get_attribute_value(node->node, "href"); + if(!href.data || !memmem(href.data, href.size, ".html", 5)) + return 0; + + QuickMediaHtmlChildNode *sub_node = node->node->first_child; + if(!sub_node) + return 0; + + QuickMediaHtmlChildNode *title_node = sub_node->next; + if(!title_node) + return 0; + + QuickMediaHtmlChildNode *time_node = title_node->next; + if(!time_node) + return 0; + + QuickMediaStringView sub_str; + QuickMediaStringView title_str; + QuickMediaStringView time_str; + if(!node_get_inner_text(sub_node, sub_str) || !node_get_inner_text(title_node, title_str) || !node_get_inner_text(time_node, time_str)) + return 0; + + std::string title(title_str.data, title_str.size); + html_unescape_sequences(title); + + std::string time(time_str.data, time_str.size); + html_unescape_sequences(time); + + const bool is_subbed = sub_str.size == 3 && memcmp(sub_str.data, "SUB", 3) == 0; + + auto body_item = BodyItem::create(std::move(title)); + body_item->set_description((is_subbed ? "Subbed" : "Raw") + std::string(" • ") + time); + body_item->set_description_color(get_theme().faded_text_color); + body_item->url = "https://dramacool.cr" + std::string(href.data, href.size); + result_items->push_back(std::move(body_item)); + + return 0; + }, &result_items); + + quickmedia_html_search_deinit(&html_search); + + auto body = create_body(); + body->set_items(std::move(result_items)); + result_tabs.push_back(Tab{ std::move(body), std::make_unique<DramaCoolEpisodesPage>(program), create_search_bar("Search...", SEARCH_DELAY_FILTER) }); + return PluginResult::OK; + } + + struct VideoSources { + //std::string streamsss; + std::string streamtape; + std::string mixdrop; + std::string mp4upload; + }; + + static bool dembed_extract_video_source(const std::string &website_data, const std::string &video_source_url, std::string &video_source) { + size_t st_start = website_data.find(video_source_url); + if(st_start == std::string::npos) + return false; + + st_start += video_source_url.size(); + size_t st_end = website_data.find("\"", st_start); + if(st_end == std::string::npos) + return false; + + video_source = "https://" + video_source_url + website_data.substr(st_start, st_end - st_start); + return true; + } + + static void dembed_extract_video_sources(const std::string &website_data, VideoSources &video_sources) { + //dembed_extract_video_source(website_data, "streamsss.net", video_sources.streamsss); + dembed_extract_video_source(website_data, "streamtape.com", video_sources.streamtape); + dembed_extract_video_source(website_data, "mixdrop.co", video_sources.mixdrop); + dembed_extract_video_source(website_data, "www.mp4upload.com", video_sources.mp4upload); + } + + // TODO: Re-add. It's broken right now (because of the json_url has incorrect value I guess) + /* + static bool streamsss_extract_video_url(Page *page, const std::string &streamsss_url, std::string &url) { + size_t url_end = streamsss_url.find("/e"); + if(url_end == std::string::npos) + return false; + + size_t id_start = streamsss_url.find("e/"); + if(id_start == std::string::npos) + return false; + + id_start += 2; + size_t id_end = streamsss_url.find("?", id_start); + if(id_end == std::string::npos) + id_end = streamsss_url.size(); + + const std::string url_base = streamsss_url.substr(0, url_end); + const std::string id = streamsss_url.substr(id_start, id_end - id_start); + + std::ostringstream id_hex; + id_hex << std::hex; + for(size_t i = 0; i < id.size(); ++i) + id_hex << (int)(unsigned char)id[i]; + + const std::string json_url = url_base + "/sources43/566d337678566f743674494a7c7c" + id_hex.str() + "7c7c346b6767586d6934774855537c7c73747265616d7362/6565417268755339773461447c7c346133383438333436313335376136323337373433383634376337633465366534393338373136643732373736343735373237613763376334363733353737303533366236333463353333363534366137633763373337343732363536313664373336327c7c6b586c3163614468645a47617c7c73747265616d7362"; + + Json::Value json_root; + DownloadResult result = page->download_json(json_root, json_url, {}, true); + if(result != DownloadResult::OK) + return false; + + if(!json_root.isObject()) + return false; + + const Json::Value &stream_data_json = json_root["stream_data"]; + if(!stream_data_json.isObject()) + return false; + + const Json::Value &file_json = stream_data_json["file"]; + if(!file_json.isString()) + return false; + + // TODO: Record resolution and duration + std::string website_data; + result = download_to_string(file_json.asString(), website_data, {}, true); + if(result != DownloadResult::OK) + return false; + + url = M3U8Stream::get_highest_resolution_stream(m3u8_get_streams(website_data)).url; + return true; + } + */ + static bool streamtape_extract_video_url(const std::string &website_data, std::string &url) { + size_t id_start = website_data.find("getElementById('robotlink')"); + if(id_start == std::string::npos) + return false; + + id_start += 27; + id_start = website_data.find("id=", id_start); + if(id_start == std::string::npos) + return false; + + id_start += 3; + size_t id_end = website_data.find("'", id_start); + if(id_end == std::string::npos) + return false; + + url = "https://streamtape.com/get_video?id=" + website_data.substr(id_start, id_end - id_start); + return true; + } + + static bool mixdrop_extract_mdcore_script(const std::string &website_data, const std::string &prefix, std::string &mdcore_script) { + size_t script_start = website_data.find(prefix); + if(script_start == std::string::npos) + return false; + + script_start += prefix.size(); + size_t script_end = website_data.find("\"", script_start); + if(script_end == std::string::npos) + return false; + + mdcore_script = website_data.substr(script_start, script_end - script_start); + return true; + } + + static bool mixdrop_extract_mdcore_parts(const std::string &website_data, std::vector<std::string> &parts) { + size_t mdcore_start = website_data.find(",'|"); + if(mdcore_start == std::string::npos) { + mdcore_start = website_data.find("MDCore|"); + if(mdcore_start == std::string::npos) + return false; + } else { + mdcore_start += 2; + } + + size_t mdcore_end = website_data.find("'", mdcore_start); + if(mdcore_end == std::string::npos) + return false; + + std::string mdcore_str = website_data.substr(mdcore_start, mdcore_end - mdcore_start); + string_split(mdcore_str, '|', [&parts](const char *str, size_t size) { + parts.emplace_back(str, size); + return true; + }); + + return true; + } + + static int mdcore_number_get(char c) { + if(c >= '0' && c <= '9') + return c - '0'; + else if(c >= 'a' && c <= 'z') + return 10 + (c - 'a'); + else + return -1; + } + + static bool mixdrop_extract_video_url(const std::string &website_data, const std::string &prefix, std::string &url) { + std::string mdcore_script; + if(!mixdrop_extract_mdcore_script(website_data, prefix, mdcore_script)) + return false; + + std::vector<std::string> mdcore_parts; + if(!mixdrop_extract_mdcore_parts(website_data, mdcore_parts)) + return false; + + for(size_t i = 0; i < mdcore_script.size();) { + char c = mdcore_script[i]; + int index = mdcore_number_get(c); + + if(index >= 0) { + ++i; + while(i < mdcore_script.size() && ((mdcore_script[i] >= '0' && mdcore_script[i] <= '9') || (mdcore_script[i] >= 'a' && mdcore_script[i] <= 'z'))) { + // 36 = 0-9 + a-z + index = (index * 36) + mdcore_number_get(mdcore_script[i]); + ++i; + } + } else { + url += c; + ++i; + continue; + } + + if(index >= (int)mdcore_parts.size() || mdcore_parts[index].empty()) + url += c; + else + url += mdcore_parts[index]; + } + + if(string_starts_with(url, "//")) + url = "http:" + url; + + return true; + } + + PluginResult DramaCoolEpisodesPage::submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) { + std::string website_data; + DownloadResult result = download_to_string(args.url, website_data, {}, true); + if(result != DownloadResult::OK) return download_result_to_plugin_result(result); + + std::string video_sources_url; + + QuickMediaHtmlSearch html_search; + int res = quickmedia_html_search_init(&html_search, website_data.c_str(), website_data.size()); + if(res != 0) + return PluginResult::ERR; + + quickmedia_html_find_nodes_xpath(&html_search, "//div", + [](QuickMediaMatchNode *node, void *userdata) { + auto *video_sources_url = (std::string*)userdata; + QuickMediaStringView klass = quickmedia_html_node_get_attribute_value(node->node, "class"); + if(!klass.data || !memmem(klass.data, klass.size, "watch_video", 11)) + return 0; + + QuickMediaHtmlChildNode *iframe_node = node->node->first_child; + if(!iframe_node || !iframe_node->node.is_tag || iframe_node->node.name.size != 6 || memcmp(iframe_node->node.name.data, "iframe", 6) != 0) + return 0; + + QuickMediaStringView src = quickmedia_html_node_get_attribute_value(&iframe_node->node, "src"); + if(!src.data) + return 0; + + video_sources_url->assign(src.data, src.size); + return 1; + }, &video_sources_url); + + quickmedia_html_search_deinit(&html_search); + + if(video_sources_url.empty()) + return PluginResult::ERR; + + if(string_starts_with(video_sources_url, "//")) + video_sources_url = "https:" + video_sources_url; + + result = download_to_string(video_sources_url, website_data, {}, true); + if(result != DownloadResult::OK) return download_result_to_plugin_result(result); + + // TODO: Extract all video sources and allow the user to select which one to use. + // Streamtape or mixdrop may not be available or if it's available it may not load properly + // or the quality may be worse or slower than other sources. + // We also want to load the high resolution version of the video. + // TODO: Make videos sources work even when captions are not embedded. + VideoSources video_sources; + dembed_extract_video_sources(website_data, video_sources); + + std::string video_url; + std::string referer; + + if(!video_sources.streamtape.empty() && video_url.empty()) { + result = download_to_string(video_sources.streamtape, website_data, {}, true); + if(result != DownloadResult::OK) return download_result_to_plugin_result(result); + streamtape_extract_video_url(website_data, video_url); + + if(!video_url.empty()) + referer = "https://streamtape.com"; + } + + if(!video_sources.mixdrop.empty() && video_url.empty()) { + result = download_to_string(video_sources.mixdrop, website_data, {}, true); + if(result != DownloadResult::OK) return download_result_to_plugin_result(result); + mixdrop_extract_video_url(website_data, "0.f=\"", video_url); + + if(!video_url.empty()) + referer = "https://mixdrop.co"; + } + + if(!video_sources.mp4upload.empty() && video_url.empty()) { + result = download_to_string(video_sources.mp4upload, website_data, {}, true); + if(result != DownloadResult::OK) return download_result_to_plugin_result(result); + // mp4upload uses the same algorithm as mixdrop but with different format + mixdrop_extract_video_url(website_data, "2.25(\"", video_url); + + if(!video_url.empty()) + referer = "https://www.mp4upload.com"; + } + + if(video_url.empty()) + return PluginResult::ERR; + + /* + if(!video_sources.streamsss.empty() && video_url.empty()) { + streamsss_extract_video_url(this, video_sources.streamsss, video_url); + } + */ + + result_tabs.push_back(Tab{ nullptr, std::make_unique<DramaCoolVideoPage>(program, std::move(video_url), 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.channel_url.clear(); + video_info.duration = 0.0; + video_info.chapters.clear(); + video_info.referer = referer; + err_str.clear(); + return PluginResult::OK; + } +}
\ No newline at end of file diff --git a/src/plugins/HotExamples.cpp b/src/plugins/HotExamples.cpp index fbefad5..7cb811b 100644 --- a/src/plugins/HotExamples.cpp +++ b/src/plugins/HotExamples.cpp @@ -44,7 +44,7 @@ namespace QuickMedia { quickmedia_html_find_nodes_xpath(&html_search, "//div[class='search-result row']//div[class='header']//a", [](QuickMediaMatchNode *node, void *userdata) { auto *item_data = (BodyItems*)userdata; - QuickMediaStringView href = quickmedia_html_node_get_attribute_value(node, "href"); + QuickMediaStringView href = quickmedia_html_node_get_attribute_value(node->node, "href"); if(href.data && memmem(href.data, href.size, "/examples/", 10)) { QuickMediaStringView text = quickmedia_html_node_get_text(node); if(text.data) { diff --git a/src/plugins/Lbry.cpp b/src/plugins/Lbry.cpp index 069bd89..6be0148 100644 --- a/src/plugins/Lbry.cpp +++ b/src/plugins/Lbry.cpp @@ -395,11 +395,11 @@ namespace QuickMedia { return ""; } - PluginResult LbryVideoPage::load(const SubmitArgs &args, std::string &title, std::string&, double &duration, std::vector<MediaChapter>&, std::string &err_str) { + PluginResult LbryVideoPage::load(const SubmitArgs &args, VideoInfo &video_info, std::string &err_str) { streaming_url.clear(); - title = args.title; + video_info.title = args.title; //title = this->title; - duration = 0.0; + video_info.duration = 0.0; return video_get_stream_url(this, url, streaming_url, err_str); } }
\ No newline at end of file diff --git a/src/plugins/MangaGeneric.cpp b/src/plugins/MangaGeneric.cpp index 767f077..2c77773 100644 --- a/src/plugins/MangaGeneric.cpp +++ b/src/plugins/MangaGeneric.cpp @@ -52,7 +52,7 @@ namespace QuickMedia { if(strcmp(field_name, "text") == 0) return quickmedia_html_node_get_text(node); else - return quickmedia_html_node_get_attribute_value(node, field_name); + return quickmedia_html_node_get_attribute_value(node->node, field_name); } static void body_items_prepend_website_url(BodyItems &body_items, const std::string &website_url) { diff --git a/src/plugins/Manganelo.cpp b/src/plugins/Manganelo.cpp index 5a75d5d..ef7f52c 100644 --- a/src/plugins/Manganelo.cpp +++ b/src/plugins/Manganelo.cpp @@ -106,7 +106,7 @@ namespace QuickMedia { result = quickmedia_html_find_nodes_xpath(&html_search, "//ul[class='row-content-chapter']//a", [](QuickMediaMatchNode *node, void *userdata) { auto *item_data = (BodyItems*)userdata; - QuickMediaStringView href = quickmedia_html_node_get_attribute_value(node, "href"); + QuickMediaStringView href = quickmedia_html_node_get_attribute_value(node->node, "href"); QuickMediaStringView text = quickmedia_html_node_get_text(node); if(href.data && text.data) { auto item = BodyItem::create(std::string(text.data, text.size)); @@ -119,7 +119,7 @@ namespace QuickMedia { result = quickmedia_html_find_nodes_xpath(&html_search, "//div[class='chapter-list']//a", [](QuickMediaMatchNode *node, void *userdata) { auto *item_data = (BodyItems*)userdata; - QuickMediaStringView href = quickmedia_html_node_get_attribute_value(node, "href"); + QuickMediaStringView href = quickmedia_html_node_get_attribute_value(node->node, "href"); QuickMediaStringView text = quickmedia_html_node_get_text(node); if(href.data && text.data) { auto item = BodyItem::create(std::string(text.data, text.size)); @@ -136,7 +136,7 @@ namespace QuickMedia { quickmedia_html_find_nodes_xpath(&html_search, "//ul[class='row-content-chapter']//span", [](QuickMediaMatchNode *node, void *userdata) { auto *item_data = (BodyItemContext*)userdata; - QuickMediaStringView class_attr = quickmedia_html_node_get_attribute_value(node, "class"); + QuickMediaStringView class_attr = quickmedia_html_node_get_attribute_value(node->node, "class"); QuickMediaStringView text = quickmedia_html_node_get_text(node); if(text.data && class_attr.data && string_view_contains(class_attr, "chapter-time") && item_data->index < item_data->body_items->size()) { std::string uploaded_date(text.data, text.size); @@ -150,7 +150,7 @@ namespace QuickMedia { quickmedia_html_find_nodes_xpath(&html_search, "//a[class='a-h']", [](QuickMediaMatchNode *node, void *userdata) { std::vector<Creator> *creators = (std::vector<Creator>*)userdata; - QuickMediaStringView href = quickmedia_html_node_get_attribute_value(node, "href"); + QuickMediaStringView href = quickmedia_html_node_get_attribute_value(node->node, "href"); QuickMediaStringView text = quickmedia_html_node_get_text(node); if(href.data && text.data && string_view_contains(href, "/author/story/")) { Creator creator; @@ -281,8 +281,8 @@ namespace QuickMedia { result = quickmedia_html_find_nodes_xpath(&html_search, "//div[class='search-story-item']//a[class='item-img']", [](QuickMediaMatchNode *node, void *userdata) { auto *item_data = (BodyItems*)userdata; - QuickMediaStringView href = quickmedia_html_node_get_attribute_value(node, "href"); - QuickMediaStringView title = quickmedia_html_node_get_attribute_value(node, "title"); + QuickMediaStringView href = quickmedia_html_node_get_attribute_value(node->node, "href"); + QuickMediaStringView title = quickmedia_html_node_get_attribute_value(node->node, "title"); if(href.data && title.data && string_view_contains(href, "/manga/")) { auto body_item = BodyItem::create(std::string(title.data, title.size)); body_item->url.assign(href.data, href.size); @@ -301,7 +301,7 @@ namespace QuickMedia { result = quickmedia_html_find_nodes_xpath(&html_search, "//div[class='search-story-item']//a[class='item-img']//img", [](QuickMediaMatchNode *node, void *userdata) { auto *item_data = (BodyItemContext*)userdata; - QuickMediaStringView src = quickmedia_html_node_get_attribute_value(node, "src"); + QuickMediaStringView src = quickmedia_html_node_get_attribute_value(node->node, "src"); if(src.data && item_data->index < item_data->body_items->size()) { (*item_data->body_items)[item_data->index]->thumbnail_url.assign(src.data, src.size); (*item_data->body_items)[item_data->index]->thumbnail_size = {101, 141}; @@ -334,7 +334,7 @@ namespace QuickMedia { result = quickmedia_html_find_nodes_xpath(&html_search, "//div[class='container-chapter-reader']/img", [](QuickMediaMatchNode *node, void *userdata) { auto *urls = (std::vector<std::string>*)userdata; - QuickMediaStringView src = quickmedia_html_node_get_attribute_value(node, "src"); + QuickMediaStringView src = quickmedia_html_node_get_attribute_value(node->node, "src"); if(src.data) { std::string image_url(src.data, src.size); urls->push_back(std::move(image_url)); diff --git a/src/plugins/MediaGeneric.cpp b/src/plugins/MediaGeneric.cpp index 4ff55da..cc869ef 100644 --- a/src/plugins/MediaGeneric.cpp +++ b/src/plugins/MediaGeneric.cpp @@ -20,7 +20,7 @@ namespace QuickMedia { if(strcmp(field_name, "text") == 0) return quickmedia_html_node_get_text(node); else - return quickmedia_html_node_get_attribute_value(node, field_name); + return quickmedia_html_node_get_attribute_value(node->node, field_name); } static void body_items_prepend_website_url(BodyItems &body_items, const std::string &website_url) { @@ -224,9 +224,9 @@ namespace QuickMedia { return video_url; } - PluginResult MediaGenericVideoPage::load(const SubmitArgs&, std::string&, std::string&, double &duration, std::vector<MediaChapter>&, std::string &err_msg) { + PluginResult MediaGenericVideoPage::load(const SubmitArgs&, VideoInfo &video_info, std::string &err_msg) { video_url.clear(); - duration = 0.0; + video_info.duration = 0.0; if(!search_page->video_custom_handler) { video_url = url; return PluginResult::OK; diff --git a/src/plugins/MyAnimeList.cpp b/src/plugins/MyAnimeList.cpp index b982e3c..aa72750 100644 --- a/src/plugins/MyAnimeList.cpp +++ b/src/plugins/MyAnimeList.cpp @@ -179,7 +179,7 @@ namespace QuickMedia { quickmedia_html_find_nodes_xpath(&html_search, "//img[itemprop='image']", [](QuickMediaMatchNode *node, void *userdata) { std::string *thumbnail_url = (std::string*)userdata; - QuickMediaStringView data_src = quickmedia_html_node_get_attribute_value(node, "data-src"); + QuickMediaStringView data_src = quickmedia_html_node_get_attribute_value(node->node, "data-src"); if(data_src.data) { thumbnail_url->assign(data_src.data, data_src.size); html_unescape_sequences(*thumbnail_url); @@ -223,12 +223,6 @@ namespace QuickMedia { return alt.substr(index + 2); } - static QuickMediaStringView quickmedia_html_node_get_attribute_value(QuickMediaHtmlNode *node, const char *attribute_name) { - QuickMediaMatchNode match_node; - match_node.node = node; - return quickmedia_html_node_get_attribute_value(&match_node, attribute_name); - } - PluginResult MyAnimeListRecommendationsPage::lazy_fetch(BodyItems &result_items) { std::string website_data; DownloadResult download_result = download_to_string(url + "/userrecs", website_data, {}, true); @@ -242,7 +236,7 @@ namespace QuickMedia { quickmedia_html_find_nodes_xpath(&html_search, "//div[class='picSurround']/a", [](QuickMediaMatchNode *node, void *userdata) { BodyItems *result_items = (BodyItems*)userdata; - QuickMediaStringView href = quickmedia_html_node_get_attribute_value(node, "href"); + QuickMediaStringView href = quickmedia_html_node_get_attribute_value(node->node, "href"); if(!href.data) return 0; diff --git a/src/plugins/NyaaSi.cpp b/src/plugins/NyaaSi.cpp index fd24e71..b56ef69 100644 --- a/src/plugins/NyaaSi.cpp +++ b/src/plugins/NyaaSi.cpp @@ -367,7 +367,7 @@ namespace QuickMedia { result = quickmedia_html_find_nodes_xpath(&html_search, "//div[class='panel-body']//div[class='row']//a", [](QuickMediaMatchNode *node, void *userdata) { ResultItemExtra *item_data = (ResultItemExtra*)userdata; - QuickMediaStringView href = quickmedia_html_node_get_attribute_value(node, "href"); + QuickMediaStringView href = quickmedia_html_node_get_attribute_value(node->node, "href"); QuickMediaStringView text = quickmedia_html_node_get_text(node); if(item_data->result_items->empty() && href.data && text.data && href.size >= 6 && memcmp(href.data, "/user/", 6) == 0) { auto body_item = BodyItem::create(""); @@ -410,7 +410,7 @@ namespace QuickMedia { result = quickmedia_html_find_nodes_xpath(&html_search, "//div[class='container']//a", [](QuickMediaMatchNode *node, void *userdata) { std::string *magnet_url = (std::string*)userdata; - QuickMediaStringView href = quickmedia_html_node_get_attribute_value(node, "href"); + QuickMediaStringView href = quickmedia_html_node_get_attribute_value(node->node, "href"); if(magnet_url->empty() && href.data && href.size >= 8 && memcmp(href.data, "magnet:?", 8) == 0) { magnet_url->assign(href.data, href.size); } @@ -434,7 +434,7 @@ namespace QuickMedia { result = quickmedia_html_find_nodes_xpath(&html_search, "//div[id='comments']//a", [](QuickMediaMatchNode *node, void *userdata) { auto *item_data = (BodyItems*)userdata; - QuickMediaStringView href = quickmedia_html_node_get_attribute_value(node, "href"); + QuickMediaStringView href = quickmedia_html_node_get_attribute_value(node->node, "href"); QuickMediaStringView text = quickmedia_html_node_get_text(node); if(href.data && text.data && href.size >= 6 && memcmp(href.data, "/user/", 6) == 0) { auto body_item = BodyItem::create(std::string(text.data, text.size)); @@ -454,7 +454,7 @@ namespace QuickMedia { result = quickmedia_html_find_nodes_xpath(&html_search, "//div[id='comments']//img[class='avatar']", [](QuickMediaMatchNode *node, void *userdata) { auto *item_data = (BodyItemContext*)userdata; - QuickMediaStringView src = quickmedia_html_node_get_attribute_value(node, "src"); + QuickMediaStringView src = quickmedia_html_node_get_attribute_value(node->node, "src"); if(src.data && item_data->index < item_data->body_items->size()) { (*item_data->body_items)[item_data->index]->thumbnail_url.assign(src.data, src.size); (*item_data->body_items)[item_data->index]->thumbnail_size = mgl::vec2i(120, 120); diff --git a/src/plugins/Peertube.cpp b/src/plugins/Peertube.cpp index 4e19f1a..4fb2ac4 100644 --- a/src/plugins/Peertube.cpp +++ b/src/plugins/Peertube.cpp @@ -370,7 +370,7 @@ namespace QuickMedia { } // TODO: Media chapters - PluginResult PeertubeVideoPage::load(const SubmitArgs&, std::string &title, std::string &channel_url, double &duration, std::vector<MediaChapter>&, std::string &err_str) { + PluginResult PeertubeVideoPage::load(const SubmitArgs&, VideoInfo &video_info, std::string &err_str) { Json::Value json_root; std::string err_msg; DownloadResult download_result = download_json(json_root, server + "/api/v1/videos/" + url, {}, true, &err_msg); @@ -384,17 +384,17 @@ namespace QuickMedia { const Json::Value &name_json = json_root["name"]; if(name_json.isString()) - title = name_json.asString(); + video_info.title = name_json.asString(); const Json::Value &duration_json = json_root["duration"]; if(duration_json.isInt64()) - duration = duration_json.asInt64(); + video_info.duration = duration_json.asInt64(); const Json::Value &channel_json = json_root["channel"]; if(channel_json.isObject()) { const Json::Value &channel_url_json = channel_json["url"]; if(channel_url_json.isString()) - channel_url = channel_url_json.asString(); + video_info.channel_url = channel_url_json.asString(); } video_sources.clear(); diff --git a/src/plugins/Saucenao.cpp b/src/plugins/Saucenao.cpp index 6833bea..ac2b622 100644 --- a/src/plugins/Saucenao.cpp +++ b/src/plugins/Saucenao.cpp @@ -131,8 +131,8 @@ namespace QuickMedia { quickmedia_html_find_nodes_xpath(&html_search, "//td[class='resulttableimage']//img", [](QuickMediaMatchNode *node, void *userdata) { BodyItemContext *item_data = (BodyItemContext*)userdata; - QuickMediaStringView src = quickmedia_html_node_get_attribute_value(node, "src"); - QuickMediaStringView data_src = quickmedia_html_node_get_attribute_value(node, "data-src"); + QuickMediaStringView src = quickmedia_html_node_get_attribute_value(node->node, "src"); + QuickMediaStringView data_src = quickmedia_html_node_get_attribute_value(node->node, "data-src"); QuickMediaStringView image_url = data_src.data ? data_src : src; if(image_url.data && item_data->index < item_data->body_items->size()) { (*item_data->body_items)[item_data->index]->thumbnail_url.assign(image_url.data, image_url.size); diff --git a/src/plugins/Soundcloud.cpp b/src/plugins/Soundcloud.cpp index bc89448..0d4d8e1 100644 --- a/src/plugins/Soundcloud.cpp +++ b/src/plugins/Soundcloud.cpp @@ -361,7 +361,7 @@ namespace QuickMedia { result = quickmedia_html_find_nodes_xpath(&html_search, "//script", [](QuickMediaMatchNode *node, void *userdata) { std::vector<std::string> *script_sources = (std::vector<std::string>*)userdata; - QuickMediaStringView src = quickmedia_html_node_get_attribute_value(node, "src"); + QuickMediaStringView src = quickmedia_html_node_get_attribute_value(node->node, "src"); if(src.data && (memmem(src.data, src.size, "sndcdn.com", 10) || memmem(src.data, src.size, "soundcloud.com", 14))) script_sources->push_back(std::string(src.data, src.size)); return 0; @@ -477,14 +477,14 @@ namespace QuickMedia { return PluginResult::OK; } - PluginResult SoundcloudAudioPage::load(const SubmitArgs &args, std::string &title, std::string&, double &duration, std::vector<MediaChapter>&, std::string&) { + PluginResult SoundcloudAudioPage::load(const SubmitArgs &args, VideoInfo &video_info, std::string&) { SoundcloudTrack *track = static_cast<SoundcloudTrack*>(args.extra.get()); if(track) permalink_url = track->permalink_url; - title = args.title; + video_info.title = args.title; //title = this->title; - duration = 0.0; + video_info.duration = 0.0; return PluginResult::OK; } diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index a0c15de..93cb266 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -2423,7 +2423,7 @@ namespace QuickMedia { return PluginResult::OK; } - PluginResult YoutubeVideoPage::load(const SubmitArgs&, std::string &title, std::string &channel_url, double &duration, std::vector<MediaChapter> &chapters, std::string &err_str) { + PluginResult YoutubeVideoPage::load(const SubmitArgs&, VideoInfo &video_info, std::string &err_str) { std::string video_id; if(!youtube_url_extract_id(url, video_id)) { fprintf(stderr, "Failed to extract youtube id from %s\n", url.c_str()); @@ -2530,10 +2530,10 @@ R"END( if(download_result != DownloadResult::OK) continue; - PluginResult result = parse_video_response(json_root, title, channel_url, chapters, err_str); + PluginResult result = parse_video_response(json_root, video_info.title, video_info.channel_url, video_info.chapters, err_str); if(result == PluginResult::OK) { err_str.clear(); - duration = video_details.duration; + video_info.duration = video_details.duration; return PluginResult::OK; } } |