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 | |
parent | 84f501f5211f09a09fc5384bf15415d0d0445a96 (diff) |
Add dramacool
-rw-r--r-- | README.md | 4 | ||||
m--------- | depends/html-search | 0 | ||||
-rw-r--r-- | images/dramacool_logo.png | bin | 0 -> 3699 bytes | |||
-rw-r--r-- | include/M3U8.hpp | 17 | ||||
-rw-r--r-- | include/VideoPlayer.hpp | 1 | ||||
-rw-r--r-- | plugins/DramaCool.hpp | 32 | ||||
-rw-r--r-- | plugins/Lbry.hpp | 2 | ||||
-rw-r--r-- | plugins/MediaGeneric.hpp | 2 | ||||
-rw-r--r-- | plugins/Page.hpp | 12 | ||||
-rw-r--r-- | plugins/Peertube.hpp | 2 | ||||
-rw-r--r-- | plugins/Soundcloud.hpp | 2 | ||||
-rw-r--r-- | plugins/Youtube.hpp | 2 | ||||
-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 |
27 files changed, 620 insertions, 72 deletions
@@ -2,13 +2,13 @@ # QuickMedia A rofi inspired native client for web services. -Currently supported web services: `youtube`, `peertube`, `lbry`, `soundcloud`, `nyaa.si`, `manganelo`, `manganelos`, `mangatown`, `mangakatana`, `mangadex`, `readm`, `onimanga`, `4chan`, `matrix`, `saucenao`, `hotexamples`, `anilist` and _others_.\ +Currently supported web services: `youtube`, `peertube`, `lbry`, `soundcloud`, `nyaa.si`, `manganelo`, `manganelos`, `mangatown`, `mangakatana`, `mangadex`, `readm`, `onimanga`, `4chan`, `matrix`, `saucenao`, `hotexamples`, `anilist`, `dramacool` and _others_.\ QuickMedia also supports reading local manga and watching local anime, see [local manga](#local-manga) and [local anime](#local-anime) ## Usage ``` usage: quickmedia [plugin] [--dir <directory>] [-e <window>] [youtube-url] OPTIONS: - 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 or stdin + 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 or stdin --no-video Only play audio when playing a video. Disabled by default --upscale-images Upscale low-resolution manga pages using waifu2x-ncnn-vulkan. Disabled by default --upscale-images-always Upscale manga pages using waifu2x-ncnn-vulkan, no matter what the original image resolution is. Disabled by default diff --git a/depends/html-search b/depends/html-search -Subproject 38a2d43a7b207feefb9c5ded71ddc941a91fa15 +Subproject d2b8fd13b03503a259275387f07210aaf27e8d9 diff --git a/images/dramacool_logo.png b/images/dramacool_logo.png Binary files differnew file mode 100644 index 0000000..cce22da --- /dev/null +++ b/images/dramacool_logo.png diff --git a/include/M3U8.hpp b/include/M3U8.hpp new file mode 100644 index 0000000..cefec16 --- /dev/null +++ b/include/M3U8.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include <string> +#include <vector> + +namespace QuickMedia { + struct M3U8Stream { + int width = 0; + int height = 0; + int framerate = 0; + std::string url; + + static M3U8Stream get_highest_resolution_stream(const std::vector<M3U8Stream> &streams); + }; + + std::vector<M3U8Stream> m3u8_get_streams(const std::string &m3u8_data); +}
\ No newline at end of file diff --git a/include/VideoPlayer.hpp b/include/VideoPlayer.hpp index 0006bfd..016c830 100644 --- a/include/VideoPlayer.hpp +++ b/include/VideoPlayer.hpp @@ -50,6 +50,7 @@ namespace QuickMedia { std::vector<MediaChapter> chapters; std::string plugin_name; bool cache_on_disk = true; + std::string referer; }; // Important: do not call |get_time_in_file| or |add_subtitle| from the |event_callback| callback diff --git a/plugins/DramaCool.hpp b/plugins/DramaCool.hpp new file mode 100644 index 0000000..428e632 --- /dev/null +++ b/plugins/DramaCool.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "Page.hpp" + +namespace QuickMedia { + class DramaCoolSearchPage : public Page { + public: + DramaCoolSearchPage(Program *program) : Page(program) {} + const char* get_title() const override { return "Search"; } + bool search_is_filter() override { return false; } + SearchResult search(const std::string &str, BodyItems &result_items) override; + PluginResult submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) override; + }; + + class DramaCoolEpisodesPage : public Page { + public: + DramaCoolEpisodesPage(Program *program) : Page(program) {} + const char* get_title() const override { return "Search"; } + bool search_is_filter() override { return true; } + PluginResult submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) override; + }; + + class DramaCoolVideoPage : public VideoPage { + public: + DramaCoolVideoPage(Program *program, std::string url, std::string title, std::string referer) : VideoPage(program, std::move(url), false), title(std::move(title)), referer(std::move(referer)) {} + const char* get_title() const override { return title.c_str(); } + PluginResult load(const SubmitArgs &args, VideoInfo &video_info, std::string &err_str) override; + private: + std::string title; + std::string referer; + }; +} diff --git a/plugins/Lbry.hpp b/plugins/Lbry.hpp index 6198a9b..0eeaf86 100644 --- a/plugins/Lbry.hpp +++ b/plugins/Lbry.hpp @@ -41,7 +41,7 @@ namespace QuickMedia { std::string get_download_url(int max_height) override; std::string get_video_url(int max_height, bool &has_embedded_audio, std::string &ext) override; std::string get_audio_url(std::string &ext) override; - PluginResult load(const SubmitArgs &args, std::string &title, std::string &channel_url, double &duration, std::vector<MediaChapter> &chapters, std::string &err_str) override; + PluginResult load(const SubmitArgs &args, VideoInfo &video_info, std::string &err_str) override; private: std::string title; std::string streaming_url; diff --git a/plugins/MediaGeneric.hpp b/plugins/MediaGeneric.hpp index d9708aa..dabe08d 100644 --- a/plugins/MediaGeneric.hpp +++ b/plugins/MediaGeneric.hpp @@ -96,7 +96,7 @@ namespace QuickMedia { std::string get_download_url(int max_height) override; std::string get_video_url(int max_height, bool &has_embedded_audio, std::string &ext) override; - PluginResult load(const SubmitArgs &args, std::string &title, std::string &channel_url, double &duration, std::vector<MediaChapter> &chapters, std::string &err_str) override; + PluginResult load(const SubmitArgs &args, VideoInfo &video_info, std::string &err_str) override; private: MediaGenericSearchPage *search_page; std::string video_url; diff --git a/plugins/Page.hpp b/plugins/Page.hpp index 6ac0450..2fa2b6c 100644 --- a/plugins/Page.hpp +++ b/plugins/Page.hpp @@ -134,6 +134,14 @@ namespace QuickMedia { std::string title; }; + struct VideoInfo { + std::string title; + std::string channel_url; + double duration = 0.0; + std::vector<MediaChapter> chapters; + std::string referer; + }; + class VideoPage : public Page { public: VideoPage(Program *program, std::string url, bool autoplay = true) : Page(program), url(std::move(url)), autoplay(autoplay) {} @@ -169,8 +177,8 @@ namespace QuickMedia { virtual std::string url_get_playable_url(const std::string &url) { return url; } virtual bool video_should_be_skipped(const std::string &url) { (void)url; return false; } // This needs to be called before the other functions are called - virtual PluginResult load(const SubmitArgs &args, std::string &title, std::string &channel_url, double &duration, std::vector<MediaChapter> &chapters, std::string &err_str) { - (void)args; (void)title; (void)duration; (void)channel_url; (void)chapters; (void)err_str; + virtual PluginResult load(const SubmitArgs &args, VideoInfo &video_info, std::string &err_str) { + (void)args; (void)video_info; (void)err_str; return PluginResult::OK; } virtual void mark_watched() {}; diff --git a/plugins/Peertube.hpp b/plugins/Peertube.hpp index ac1a06b..3028db8 100644 --- a/plugins/Peertube.hpp +++ b/plugins/Peertube.hpp @@ -81,7 +81,7 @@ namespace QuickMedia { std::string get_download_url(int max_height) override; std::string get_video_url(int max_height, bool &has_embedded_audio, std::string &ext) override; std::string get_audio_url(std::string &ext) override; - PluginResult load(const SubmitArgs &args, std::string &title, std::string &channel_url, double &duration, std::vector<MediaChapter> &chapters, std::string &err_str) override; + PluginResult load(const SubmitArgs &args, VideoInfo &video_info, std::string &err_str) override; bool autoplay_next_item() override { return autoplay_next; } //void get_subtitles(SubtitleData &subtitle_data) override; private: diff --git a/plugins/Soundcloud.hpp b/plugins/Soundcloud.hpp index cf1d7dd..b6f4933 100644 --- a/plugins/Soundcloud.hpp +++ b/plugins/Soundcloud.hpp @@ -60,7 +60,7 @@ namespace QuickMedia { public: SoundcloudAudioPage(Program *program, std::string title, const std::string &url, std::string permalink_url) : VideoPage(program, url), title(std::move(title)), permalink_url(std::move(permalink_url)) {} const char* get_title() const override { return ""; } - PluginResult load(const SubmitArgs &args, std::string &title, std::string &channel_url, double &duration, std::vector<MediaChapter> &chapters, std::string &err_str) override; + PluginResult load(const SubmitArgs &args, VideoInfo &video_info, std::string &err_str) override; bool autoplay_next_item() override { return true; } std::string url_get_playable_url(const std::string &url) override; std::string get_download_url(int max_height) override; diff --git a/plugins/Youtube.hpp b/plugins/Youtube.hpp index cea5d8d..1e84af1 100644 --- a/plugins/Youtube.hpp +++ b/plugins/Youtube.hpp @@ -151,7 +151,7 @@ namespace QuickMedia { std::string get_url_timestamp() override; std::string get_video_url(int max_height, bool &has_embedded_audio, std::string &ext) override; std::string get_audio_url(std::string &ext) override; - PluginResult load(const SubmitArgs &args, std::string &title, std::string &channel_url, double &duration, std::vector<MediaChapter> &chapters, std::string &err_str) override; + PluginResult load(const SubmitArgs &args, VideoInfo &video_info, std::string &err_str) override; void mark_watched() override; void get_subtitles(SubtitleData &subtitle_data) override; void set_watch_progress(int64_t time_pos_sec, int64_t duration_sec) override; 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; } } |