aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--TODO1
-rw-r--r--plugins/MediaGeneric.hpp17
-rw-r--r--plugins/Page.hpp1
-rw-r--r--src/QuickMedia.cpp30
-rw-r--r--src/plugins/MediaGeneric.cpp56
6 files changed, 91 insertions, 16 deletions
diff --git a/README.md b/README.md
index 66bc922..0077409 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,7 @@ If you are running arch linux then you can install QuickMedia from aur (https://
`xdg-utils` which provides `xdg-open` needs to be installed when downloading torrents with `nyaa.si` plugin.\
`ffmpeg (and ffprobe which is included in ffmpeg)` needs to be installed to display webp thumbnails, to upload videos with thumbnails on matrix or to merge video and audio when downloading youtube videos.\
`fc-match` (which is part of `fontconfig`) needs to be installed when `use_system_fonts` config is set to `true`.\
-`wget` needs to be installed for coom plugins.
+`wget` needs to be installed for xxx plugins.
## Controls
### General control
Type text and then wait and QuickMedia will automatically search.\
diff --git a/TODO b/TODO
index 258c6ca..c40e839 100644
--- a/TODO
+++ b/TODO
@@ -199,7 +199,6 @@ Support directly going to a youtube channel for a url. This is helpful for openi
Support downloading soundcloud/youtube playlists. Such downloads should also have a different download gui as you would select a folder instead of an output file.
Support downloading .m3u8 files, such as soundcloud music without using youtube-dl.
Fix lbry and peertube download which fail because for lbry all videos are .m3u8 and some peertube videos are .m3u8.
-Fix lbry and peertube download which fail because for lbry all videos are .m3u8 and some peertube videos are .m3u8.
Improve pinephone video load performance by not restarting mpv on next video. Instead start the video in the same video player.
Very large resolutions, such as 7680x2160 (id 272) for video https://www.youtube.com/watch?v=GxaH40zpvYc are not supported by the youtube android video loader. Find a way to fix that.
Use std::move(string) for all places where text.set_string is called.
diff --git a/plugins/MediaGeneric.hpp b/plugins/MediaGeneric.hpp
index 3720ba3..684f319 100644
--- a/plugins/MediaGeneric.hpp
+++ b/plugins/MediaGeneric.hpp
@@ -30,9 +30,11 @@ namespace QuickMedia {
std::string thumbnail_url;
};
+ using MediaVideoUrlCustomHandler = std::function<std::string(const std::string &html_source)>;
using MediaRelatedCustomHandler = std::function<std::vector<MediaRelatedItem>(const std::string &html_source)>;
class MediaGenericSearchPage : public Page {
+ friend class MediaGenericVideoPage;
public:
MediaGenericSearchPage(Program *program, const char *website_url, mgl::vec2i thumbnail_max_size, bool cloudflare_bypass, std::vector<CommandArg> extra_commands = {});
const char* get_title() const override { return "Search"; }
@@ -51,6 +53,8 @@ namespace QuickMedia {
MediaGenericSearchPage& text_handler(std::vector<MediaTextQuery> queries);
// This is optional.
MediaGenericSearchPage& thumbnail_handler(std::vector<MediaThumbnailQuery> queries);
+ // This is optional. Youtube-dl (or yt-dlp) is used instead if this is not used.
+ MediaGenericSearchPage& video_url_custom_handler(MediaVideoUrlCustomHandler handler);
// This is optional.
MediaGenericSearchPage& related_media_text_handler(std::vector<MediaTextQuery> queries);
@@ -61,13 +65,16 @@ namespace QuickMedia {
private:
std::string website_url;
mgl::vec2i thumbnail_max_size;
+
MediaSearchQuery search_query;
std::vector<MediaTextQuery> text_queries;
std::vector<MediaThumbnailQuery> thumbnail_queries;
+ MediaVideoUrlCustomHandler video_custom_handler = nullptr;
+
std::vector<MediaTextQuery> related_media_text_queries;
std::vector<MediaThumbnailQuery> related_media_thumbnail_queries;
MediaRelatedCustomHandler related_custom_handler = nullptr;
- bool cloudflare_bypass;
+ bool cloudflare_bypass = false;
std::vector<CommandArg> extra_commands;
};
@@ -81,11 +88,17 @@ namespace QuickMedia {
class MediaGenericVideoPage : public VideoPage {
public:
- MediaGenericVideoPage(Program *program, MediaGenericSearchPage *search_page, const std::string &url) : VideoPage(program, url), search_page(search_page) {}
+ MediaGenericVideoPage(Program *program, MediaGenericSearchPage *search_page, const std::string &url) :
+ VideoPage(program, url), search_page(search_page) {}
const char* get_title() const override { return ""; }
BodyItems get_related_media(const std::string &url) override;
PluginResult get_related_pages(const BodyItems &related_videos, const std::string &channel_url, std::vector<Tab> &result_tabs) override;
+
+ 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(std::string &title, std::string &channel_url, std::vector<MediaChapter> &chapters, std::string &err_str) override;
private:
MediaGenericSearchPage *search_page;
+ std::string video_url;
};
} \ No newline at end of file
diff --git a/plugins/Page.hpp b/plugins/Page.hpp
index da6405c..e25cb84 100644
--- a/plugins/Page.hpp
+++ b/plugins/Page.hpp
@@ -164,6 +164,7 @@ namespace QuickMedia {
// Only used if |get_video_url| sets |has_embedded_audio| to false.
// Might do a network request.
virtual std::string get_audio_url(std::string &ext) { (void)ext; return ""; }
+ // Might do a network request
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
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index 1ee0816..b788e0b 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -922,6 +922,23 @@ namespace QuickMedia {
media_generic_search_page->search_handler("https://www.xvideos.com/?k=%s&p=%p", 0)
.text_handler({{"//div[id='main']//div[class='thumb-under']//a", "title", "href", "/video"}})
.thumbnail_handler({{"//div[id='main']//div[class='thumb']//img", "data-src", "/videos"}})
+ .video_url_custom_handler([](const std::string &html_source) -> std::string {
+ size_t start_index = html_source.find("html5player.setVideoHLS");
+ if(start_index == std::string::npos)
+ return "";
+
+ start_index += 23;
+ start_index = html_source.find("'", start_index);
+ if(start_index == std::string::npos)
+ return "";
+
+ start_index += 1;
+ size_t end_index = html_source.find("'", start_index);
+ if(end_index == std::string::npos)
+ return "";
+
+ return html_source.substr(start_index, end_index - start_index);
+ })
.related_media_custom_handler([](const std::string &html_source) {
std::vector<MediaRelatedItem> related_items;
size_t related_start = html_source.find("video_related=[");
@@ -969,10 +986,10 @@ namespace QuickMedia {
static void add_xhamster_handlers(MediaGenericSearchPage *media_generic_search_page) {
media_generic_search_page->search_handler("https://xhamster.com/search/%s?page=%p", 1)
- .text_handler({{"//div[class='video-thumb-info']//a", "text", "href", "/videos/"}})
- .thumbnail_handler({{"//img", "src", "/thumb-"}})
- .related_media_text_handler({{"//div[class='video-thumb-info']//a", "text", "href", "/videos/"}})
- .related_media_thumbnail_handler({{"//img", "src", "/thumb-"}});
+ .text_handler({{"//div[data-role='video-list']//div[class='video-thumb-info']//a", "text", "href", "/videos/"}})
+ .thumbnail_handler({{"//div[data-role='video-list']//img", "src", "/thumb-"}})
+ .related_media_text_handler({{"//div[data-role='video-relations']//div[class='video-thumb-info']//a", "text", "href", "/videos/"}})
+ .related_media_thumbnail_handler({{"//div[data-role='video-relations']//img", "src", "/thumb-"}});
}
static void check_youtube_dl_installed(const std::string &plugin_name) {
@@ -1250,15 +1267,12 @@ namespace QuickMedia {
tabs.push_back(Tab{create_body(false, true), std::move(search_page), create_search_bar("Search...", 500)});
use_youtube_dl = true;
} else if(strcmp(plugin_name, "xvideos") == 0) {
- check_youtube_dl_installed(plugin_name);
-
std::vector<CommandArg> extra_commands = {
{ "--header", "Cookie: last_views=%5B%2236247565-" + std::to_string(time(nullptr)) + "%22%5D" }
};
auto search_page = std::make_unique<MediaGenericSearchPage>(this, "https://www.xvideos.com/", mgl::vec2i(352/1.5f, 198/1.5f), true, std::move(extra_commands));
add_xvideos_handlers(search_page.get());
tabs.push_back(Tab{create_body(false, true), std::move(search_page), create_search_bar("Search...", 500)});
- use_youtube_dl = true;
} else if(strcmp(plugin_name, "xhamster") == 0) {
check_youtube_dl_installed(plugin_name);
auto search_page = std::make_unique<MediaGenericSearchPage>(this, "https://xhamster.com/", mgl::vec2i(240, 135), true);
@@ -2854,7 +2868,6 @@ namespace QuickMedia {
return url.find("pornhub.com") != std::string::npos
|| url.find("xhamster.com") != std::string::npos
|| url.find("spankbang.com") != std::string::npos
- || url.find("xvideos.com") != std::string::npos
// TODO: Remove when youtube-dl is no longer required to download soundcloud music
|| is_soundcloud(url);
}
@@ -3235,6 +3248,7 @@ namespace QuickMedia {
return;
std::string url = video_page->get_url();
+ related_videos.clear();
related_videos_task = AsyncTask<void>([&related_videos, url, video_page]() {
video_page->mark_watched();
related_videos = video_page->get_related_media(url);
diff --git a/src/plugins/MediaGeneric.cpp b/src/plugins/MediaGeneric.cpp
index adbac0d..5ee5fbc 100644
--- a/src/plugins/MediaGeneric.cpp
+++ b/src/plugins/MediaGeneric.cpp
@@ -37,7 +37,12 @@ namespace QuickMedia {
}
}
- static PluginResult fetch_page_results(const std::string &url, const std::string &website_url, const std::vector<MediaTextQuery> &text_queries, const std::vector<MediaThumbnailQuery> &thumbnail_queries, mgl::vec2i thumbnail_max_size, MediaRelatedCustomHandler *custom_handler, BodyItems &result_items, bool cloudflare_bypass, const std::vector<CommandArg> &extra_commands) {
+ static PluginResult fetch_page_results(
+ const std::string &url, const std::string &website_url, const std::vector<MediaTextQuery> &text_queries,
+ const std::vector<MediaThumbnailQuery> &thumbnail_queries, mgl::vec2i thumbnail_max_size,
+ MediaRelatedCustomHandler *related_custom_handler,
+ BodyItems &result_items, bool cloudflare_bypass, const std::vector<CommandArg> &extra_commands)
+ {
std::vector<CommandArg> args = extra_commands;
if(!website_url.empty())
args.push_back({ "--header", "referer: " + website_url });
@@ -49,8 +54,8 @@ namespace QuickMedia {
if(website_data.empty())
return PluginResult::OK;
- if(custom_handler && *custom_handler) {
- std::vector<MediaRelatedItem> media_related_items = (*custom_handler)(website_data);
+ if(related_custom_handler && *related_custom_handler) {
+ std::vector<MediaRelatedItem> media_related_items = (*related_custom_handler)(website_data);
for(MediaRelatedItem &media_related_item : media_related_items) {
auto body_item = BodyItem::create(media_related_item.title);
body_item->url = std::move(media_related_item.url);
@@ -163,6 +168,11 @@ namespace QuickMedia {
return *this;
}
+ MediaGenericSearchPage& MediaGenericSearchPage::video_url_custom_handler(MediaVideoUrlCustomHandler handler) {
+ video_custom_handler = std::move(handler);
+ return *this;
+ }
+
MediaGenericSearchPage& MediaGenericSearchPage::related_media_text_handler(std::vector<MediaTextQuery> queries) {
related_media_text_queries = std::move(queries);
related_custom_handler = nullptr;
@@ -176,7 +186,7 @@ namespace QuickMedia {
}
MediaGenericSearchPage& MediaGenericSearchPage::related_media_custom_handler(MediaRelatedCustomHandler handler) {
- related_custom_handler = handler;
+ related_custom_handler = std::move(handler);
related_media_text_queries.clear();
related_media_thumbnail_queries.clear();
return *this;
@@ -199,4 +209,42 @@ namespace QuickMedia {
result_tabs.push_back(Tab{std::move(related_page_body), std::make_unique<MediaGenericRelatedPage>(program, search_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
return PluginResult::OK;
}
+
+ std::string MediaGenericVideoPage::get_download_url(int max_height) {
+ // TODO: Use max_height, if possible
+ (void)max_height;
+ return video_url;
+ }
+
+ std::string MediaGenericVideoPage::get_video_url(int max_height, bool &has_embedded_audio, std::string &ext) {
+ // TODO: Use max_height, if possible
+ (void)max_height;
+ has_embedded_audio = true;
+ ext = "m3u8";
+ return video_url;
+ }
+
+ PluginResult MediaGenericVideoPage::load(std::string&, std::string&, std::vector<MediaChapter>&, std::string &err_msg) {
+ video_url.clear();
+ if(!search_page->video_custom_handler) {
+ video_url = url;
+ return PluginResult::OK;
+ }
+
+ std::vector<CommandArg> args = search_page->extra_commands;
+ if(!url.empty())
+ args.push_back({ "--header", "referer: " + url });
+
+ std::string website_data;
+ if(download_to_string(url, website_data, args, true, true, search_page->cloudflare_bypass) != DownloadResult::OK)
+ return PluginResult::NET_ERR;
+
+ video_url = search_page->video_custom_handler(website_data);
+ if(video_url.empty()) {
+ err_msg = "Failed to extract video url";
+ return PluginResult::ERR;
+ } else {
+ return PluginResult::OK;
+ }
+ }
} \ No newline at end of file