aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/M3U8.cpp74
-rw-r--r--src/QuickMedia.cpp49
-rw-r--r--src/VideoPlayer.cpp5
-rw-r--r--src/plugins/DramaCool.cpp412
-rw-r--r--src/plugins/HotExamples.cpp2
-rw-r--r--src/plugins/Lbry.cpp6
-rw-r--r--src/plugins/MangaGeneric.cpp2
-rw-r--r--src/plugins/Manganelo.cpp16
-rw-r--r--src/plugins/MediaGeneric.cpp6
-rw-r--r--src/plugins/MyAnimeList.cpp10
-rw-r--r--src/plugins/NyaaSi.cpp8
-rw-r--r--src/plugins/Peertube.cpp8
-rw-r--r--src/plugins/Saucenao.cpp4
-rw-r--r--src/plugins/Soundcloud.cpp8
-rw-r--r--src/plugins/Youtube.cpp6
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;
}
}