aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TODO8
-rw-r--r--include/QuickMedia.hpp4
-rw-r--r--src/QuickMedia.cpp162
-rw-r--r--src/plugins/Info.cpp15
4 files changed, 131 insertions, 58 deletions
diff --git a/TODO b/TODO
index 0b9aded..cd2c1b6 100644
--- a/TODO
+++ b/TODO
@@ -138,4 +138,10 @@ Restrict sf::View viewport in Body to x axis as well, to hide the body in the en
Ctrl+F to either bring up search that searches in the body (filtering, of searching for all messages in the room in matrix) or does an autocomplete search. For youtube that would be a google autocomplete search.
The autocomplete search menu should overlay the window (with translucent black overlay) and with the search bar in the middle and autocomplete results below.
Or do not have such an overlay and instead just put the autocomplete list below the search bar and close the autocomplete when pressing ESC or clicking inside the body or moving to another tab.
-Automatically delete old thumbnails and media files. \ No newline at end of file
+Automatically delete old thumbnails and media files.
+Ctrl+I for youtube comments that have a timestamp should have an option to jump to the timestamp in the video.
+Ctrl+R for youtube should have a page for the video title, description, views, upload date, likes/dislikes (and maybe a button to go to the channel instead of having a channels tab?).
+Add a keybinding for going to the front page of the plugin. This would be especially useful for youtube and manga, where you have history, subscriptions and recommendations.
+Reaching end of download menu filename entry should scroll the entry vertically instead of putting the text on multiple lines.
+Ctrl+S saving a video should copy the video from cache if the video was downloaded to cache, instead of downloading it again.
+When synapse adds support for media download http request range then quickmedia should download the last 4096 bytes of mp4 files and move the moov atom and co64 atoms (and others) to the front of the file before mdat, similar to how qt-faststart does it. This is needed for video streaming certain mp4 files. \ No newline at end of file
diff --git a/include/QuickMedia.hpp b/include/QuickMedia.hpp
index b2319ee..82b2ee4 100644
--- a/include/QuickMedia.hpp
+++ b/include/QuickMedia.hpp
@@ -111,7 +111,9 @@ namespace QuickMedia {
bool show_info_page(BodyItem *body_item, bool include_reverse_image_search);
void page_loop_render(sf::RenderWindow &window, std::vector<Tab> &tabs, int selected_tab, TabAssociatedData &tab_associated_data, const Json::Value *json_chapters, Tabs &ui_tabs);
using PageLoopSubmitHandler = std::function<void(const std::vector<Tab> &new_tabs)>;
- void page_loop(std::vector<Tab> &tabs, int start_tab_index = 0, PageLoopSubmitHandler after_submit_handler = nullptr);
+ // Returns false if the page loop was escaped by user navigation (pressing escape) or if there was an error at startup
+ bool page_loop(std::vector<Tab> &tabs, int start_tab_index = 0, PageLoopSubmitHandler after_submit_handler = nullptr);
+ void video_page_download_video(const std::string &url, bool use_youtube_dl, sf::WindowHandle video_player_window = None);
void video_content_page(Page *parent_page, VideoPage *video_page, std::string video_title, bool download_if_streaming_fails, BodyItems &next_play_items, int play_index, int *parent_body_page = nullptr, const std::string &parent_page_search = "");
// Returns -1 to go to previous chapter, 0 to stay on same chapter and 1 to go to next chapter
int image_page(MangaImagesPage *images_page, Body *chapters_body);
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index a61b836..595ae7b 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -289,6 +289,38 @@ namespace QuickMedia {
HistoryType history_type;
};
+ using OptionsPageHandler = std::function<void()>;
+
+ class OptionsPage : public Page {
+ public:
+ OptionsPage(Program *program, std::string title) : Page(program), title(std::move(title)) {}
+ const char* get_title() const override { return title.c_str(); }
+
+ PluginResult submit(const std::string&, const std::string &url, std::vector<Tab>&) override {
+ const int handlers_index = atoi(url.c_str());
+ handlers[handlers_index]();
+ program->set_go_to_previous_page();
+ return PluginResult::OK;
+ }
+
+ bool submit_is_async() override { return false; }
+
+ void add_option(BodyItems &items, std::string title, std::string description, OptionsPageHandler handler) {
+ assert(handler);
+ auto body_item = BodyItem::create(std::move(title));
+ if(!description.empty()) {
+ body_item->set_description(std::move(description));
+ body_item->set_description_color(sf::Color(179, 179, 179));
+ }
+ body_item->url = std::to_string(handlers.size());
+ handlers.push_back(std::move(handler));
+ items.push_back(std::move(body_item));
+ }
+ private:
+ std::string title;
+ std::vector<OptionsPageHandler> handlers;
+ };
+
Program::Program() :
disp(nullptr),
window_size(1280, 720),
@@ -1528,10 +1560,10 @@ namespace QuickMedia {
}
}
- void Program::page_loop(std::vector<Tab> &tabs, int start_tab_index, PageLoopSubmitHandler after_submit_handler) {
+ bool Program::page_loop(std::vector<Tab> &tabs, int start_tab_index, PageLoopSubmitHandler after_submit_handler) {
if(tabs.empty()) {
show_notification("QuickMedia", "No tabs provided!", Urgency::CRITICAL);
- return;
+ return false;
}
malloc_trim(0);
@@ -1845,7 +1877,7 @@ namespace QuickMedia {
redraw = true;
else if(event.type == sf::Event::KeyPressed) {
if(event.key.code == sf::Keyboard::Escape) {
- goto page_end;
+ return false;
} else if(event.key.code == sf::Keyboard::Tab) {
if(tabs[selected_tab].search_bar) tabs[selected_tab].search_bar->set_to_autocomplete();
} else if(event.key.code == sf::Keyboard::Enter) {
@@ -2085,12 +2117,11 @@ namespace QuickMedia {
if(go_to_previous_page) {
go_to_previous_page = false;
- goto page_end;
+ return true;
}
}
- page_end:
- {}
+ return false;
}
static bool youtube_url_extract_id(const std::string &youtube_url, std::string &youtube_video_id) {
@@ -2279,20 +2310,58 @@ namespace QuickMedia {
return false;
}
+ void Program::video_page_download_video(const std::string &url, bool use_youtube_dl, sf::WindowHandle video_player_window) {
+ if(!use_youtube_dl) {
+ download_async_gui(url, FileManagerPage::get_last_accessed_directory(file_manager_start_dir).string(), use_youtube_dl, no_video);
+ return;
+ }
+
+ bool audio_only = false;
+ auto body = create_body();
+
+ auto options_page = std::make_unique<OptionsPage>(this, "Select download option");
+ options_page->add_option(body->items, "Download video and audio", "", [&audio_only](){
+ audio_only = false;
+ });
+ options_page->add_option(body->items, "Download only audio", "", [&audio_only](){
+ audio_only = true;
+ });
+
+ if(video_player_window) {
+ XUnmapWindow(disp, video_player_window);
+ XSync(disp, False);
+ }
+
+ std::vector<Tab> tabs;
+ tabs.push_back(Tab{ std::move(body), std::move(options_page), nullptr });
+ bool selected = page_loop(tabs);
+
+ if(video_player_window) {
+ XMapWindow(disp, video_player_window);
+ XSync(disp, False);
+ }
+
+ if(!selected)
+ return;
+
+ download_async_gui(url, FileManagerPage::get_last_accessed_directory(file_manager_start_dir).string(), true, audio_only);
+ }
+
#define CLEANMASK(mask) ((mask) & (ShiftMask|ControlMask|Mod1Mask|Mod4Mask|Mod5Mask))
void Program::video_content_page(Page *parent_page, VideoPage *video_page, std::string video_title, bool download_if_streaming_fails, BodyItems &next_play_items, int play_index, int *parent_body_page, const std::string &parent_page_search) {
- sf::Clock time_watched_timer;
- bool video_loaded = false;
- const bool is_youtube = strcmp(plugin_name, "youtube") == 0;
- const bool is_matrix = strcmp(plugin_name, "matrix") == 0;
-
PageType previous_page = pop_page_stack();
std::string video_url = video_page->get_url();
std::string original_video_url = video_url;
+ sf::Clock time_watched_timer;
+ bool video_loaded = false;
+ std::string youtube_video_id;
+ const bool is_youtube = youtube_url_extract_id(video_url, youtube_video_id);
+ const bool is_matrix = strcmp(plugin_name, "matrix") == 0;
+
bool video_url_is_local = false;
- if(download_if_streaming_fails) {
+ if(!is_youtube && download_if_streaming_fails) {
Path video_cache_dir = get_cache_dir().join("media");
Path video_path = video_cache_dir;
SHA256 sha256;
@@ -2361,13 +2430,13 @@ namespace QuickMedia {
std::function<void(const char*)> video_event_callback;
- auto load_video_error_check = [this, &related_videos, &channel_url, &video_url, &video_title, &video_player, previous_page, &time_watched_timer, &video_loaded, video_page, &video_event_callback, &on_window_create, &video_player_window, is_matrix](bool resume_video) mutable {
+ auto load_video_error_check = [this, &related_videos, &channel_url, &video_url, &video_title, &video_player, previous_page, &time_watched_timer, &video_loaded, video_page, &video_event_callback, &on_window_create, &video_player_window, is_youtube, is_matrix](bool resume_video) mutable {
time_watched_timer.restart();
video_loaded = false;
video_player_window = None;
watched_videos.insert(video_url);
- video_player = std::make_unique<VideoPlayer>(no_video, use_system_mpv_config, resume_video, is_matrix, video_event_callback, on_window_create, resources_root, get_largest_monitor_height(disp));
+ video_player = std::make_unique<VideoPlayer>(no_video, use_system_mpv_config, resume_video, is_matrix && !is_youtube, video_event_callback, on_window_create, resources_root, get_largest_monitor_height(disp));
VideoPlayer::Error err = video_player->load_video(video_url.c_str(), window.getSystemHandle(), plugin_name, video_title);
if(err != VideoPlayer::Error::OK) {
std::string err_msg = "Failed to play url: ";
@@ -2490,8 +2559,6 @@ namespace QuickMedia {
window_set_fullscreen(disp, window.getSystemHandle(), WindowFullscreenState::TOGGLE);
} else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::C && event.key.control) {
save_video_url_to_clipboard();
- } else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::S && event.key.control) {
- download_async_gui(original_video_url, FileManagerPage::get_last_accessed_directory(file_manager_start_dir).string(), !is_matrix, no_video);
}
}
handle_window_close();
@@ -2512,7 +2579,7 @@ namespace QuickMedia {
} else if(pressed_keysym == XK_f && pressing_ctrl) {
window_set_fullscreen(disp, window.getSystemHandle(), WindowFullscreenState::TOGGLE);
} else if(pressed_keysym == XK_s && pressing_ctrl) {
- download_async_gui(original_video_url, FileManagerPage::get_last_accessed_directory(file_manager_start_dir).string(), !is_matrix, no_video);
+ video_page_download_video(original_video_url, !is_matrix || is_youtube, video_player_window);
} else if(pressed_keysym == XK_r && pressing_ctrl) {
if(!cursor_visible)
window.setMouseCursorVisible(true);
@@ -2540,7 +2607,7 @@ namespace QuickMedia {
tabs.push_back(Tab{std::move(related_videos_body), std::move(related_videos_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
}
if(channels_page) {
- tabs.push_back(Tab{create_body(), std::move(channels_page), create_search_bar("Search...", is_youtube ? 350 : SEARCH_DELAY_FILTER)});
+ tabs.push_back(Tab{create_body(), std::move(channels_page), create_search_bar("Search...", 350)});
}
bool page_changed = false;
@@ -2591,7 +2658,7 @@ namespace QuickMedia {
show_notification("QuickMedia", "Failed to connect to mpv ipc after 10 seconds", Urgency::CRITICAL);
current_page = previous_page;
break;
- } else if(update_err == VideoPlayer::Error::EXITED && video_player->exit_status == 0 && !is_matrix) {
+ } else if(update_err == VideoPlayer::Error::EXITED && video_player->exit_status == 0 && (!is_matrix || is_youtube)) {
std::string new_video_url;
std::string new_video_title;
@@ -2608,7 +2675,7 @@ namespace QuickMedia {
};
find_next_video();
- if(new_video_url.empty() && parent_page && parent_body_page) {
+ if(new_video_url.empty() && parent_page && parent_body_page && video_page->autoplay_next_item()) {
BodyItems new_body_items;
const int fetch_page = (*parent_body_page) + 1;
TaskResult load_next_page_result = run_task_with_loading_screen([parent_page, parent_page_search, fetch_page, &new_body_items] {
@@ -4890,19 +4957,18 @@ namespace QuickMedia {
bool avatar_applied = false;
- auto launch_url = [this, matrix_chat_page, &video_page, &redraw, &avatar_applied](const std::string &url) mutable {
+ auto launch_url = [this, matrix_chat_page, &tabs, MESSAGES_TAB_INDEX, &redraw, &avatar_applied](const std::string &url) mutable {
if(url.empty())
return;
std::string video_id;
if(youtube_url_extract_id(url, video_id)) {
- page_stack.push(PageType::CHAT);
watched_videos.clear();
+ page_stack.push(PageType::CHAT);
current_page = PageType::VIDEO_CONTENT;
- video_page->url = url;
- BodyItems next_items;
- // TODO: Add title
- video_content_page(matrix_chat_page, video_page.get(), "", false, next_items, 0);
+ auto youtube_video_page = std::make_unique<YoutubeVideoPage>(this, url);
+ // TODO: Use real title
+ video_content_page(matrix_chat_page, youtube_video_page.get(), "", false, tabs[MESSAGES_TAB_INDEX].body->items, tabs[MESSAGES_TAB_INDEX].body->get_selected_item());
redraw = true;
avatar_applied = false;
} else {
@@ -6489,7 +6555,7 @@ namespace QuickMedia {
}
loading_bar.set_size(sf::Vector2f(
- std::floor((loading_bar_background.get_size().x - loading_bar_padding_x) * ui_progress),
+ std::floor((loading_bar_background.get_size().x - loading_bar_padding_x * 2.0f) * ui_progress),
loading_bar_height - loading_bar_padding_y * 2.0f));
window.clear(sf::Color(33, 37, 44));
@@ -6516,29 +6582,6 @@ namespace QuickMedia {
exit(exit_code);
}
- class ConfirmationPage : public Page {
- public:
- ConfirmationPage(Program *program, bool *file_overwrite, const std::string &title) : Page(program), file_overwrite(file_overwrite), title(title) {}
- const char* get_title() const override { return title.c_str(); }
- PluginResult submit(const std::string &title, const std::string&, std::vector<Tab>&) override {
- if(title == "Yes")
- *file_overwrite = true;
- else
- *file_overwrite = false;
-
- program->set_go_to_previous_page();
- return PluginResult::OK;
- }
-
- static void add_items(BodyItems &items) {
- items.push_back(BodyItem::create("No"));
- items.push_back(BodyItem::create("Yes"));
- }
- private:
- bool *file_overwrite;
- std::string title;
- };
-
std::string Program::file_save_page(const std::string &filename) {
sf::Vector2f body_pos;
sf::Vector2f body_size;
@@ -6590,10 +6633,10 @@ namespace QuickMedia {
const float bottom_panel_spacing = 10.0f;
Button cancel_button("Cancel", FontLoader::get_font(FontLoader::FontType::LATIN), 16, 100.0f, &rounded_rectangle_shader, get_ui_scale());
- cancel_button.set_background_color(sf::Color(104, 2, 2));
+ cancel_button.set_background_color(sf::Color(41, 45, 50));
Button save_button("Save", FontLoader::get_font(FontLoader::FontType::LATIN), 16, 100.0f, &rounded_rectangle_shader, get_ui_scale());
- save_button.set_background_color(sf::Color(35, 35, 236));
+ save_button.set_background_color(sf::Color(71, 75, 180));
sf::Text file_name_label("File name:", *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(16.0f * get_ui_scale()));
@@ -6621,11 +6664,20 @@ namespace QuickMedia {
} else {
if(std::filesystem::exists(filename_full_path.data)) {
bool overwrite = false;
- std::vector<Tab> tabs;
auto body = create_body();
- ConfirmationPage::add_items(body->items);
- tabs.push_back(Tab{ std::move(body), std::make_unique<ConfirmationPage>(this, &overwrite, "Are you sure you want to overwrite " + filename_full_path.data + "?"), nullptr });
+
+ auto options_page = std::make_unique<OptionsPage>(this, "Are you sure you want to overwrite " + filename_full_path.data + "?");
+ options_page->add_option(body->items, "No", "", [&overwrite](){
+ overwrite = false;
+ });
+ options_page->add_option(body->items, "Yes", "", [&overwrite](){
+ overwrite = true;
+ });
+
+ std::vector<Tab> tabs;
+ tabs.push_back(Tab{ std::move(body), std::move(options_page), nullptr });
page_loop(tabs);
+
if(overwrite)
return std::move(filename_full_path.data);
} else {
diff --git a/src/plugins/Info.cpp b/src/plugins/Info.cpp
index b1943b3..a68033f 100644
--- a/src/plugins/Info.cpp
+++ b/src/plugins/Info.cpp
@@ -1,5 +1,6 @@
#include "../../plugins/Info.hpp"
#include "../../plugins/Saucenao.hpp"
+#include "../../plugins/Youtube.hpp"
#include "../../include/StringUtils.hpp"
#include "../../include/Program.hpp"
#include "../../include/Notification.hpp"
@@ -7,11 +8,18 @@
namespace QuickMedia {
static const char *REVERSE_IMAGE_SEARCH_URL = "reverse-image-search://";
+ static bool is_youtube_url(const std::string &url) {
+ return url.find("youtube.com/") != std::string::npos || url.find("youtu.be/") != std::string::npos;
+ }
+
PluginResult InfoPage::submit(const std::string&, const std::string &url, std::vector<Tab> &result_tabs) {
if(string_starts_with(url, REVERSE_IMAGE_SEARCH_URL)) {
std::string image_url = url.substr(strlen(REVERSE_IMAGE_SEARCH_URL));
result_tabs.push_back(Tab{create_body(), std::make_unique<SaucenaoPage>(program, image_url, false), nullptr});
return PluginResult::OK;
+ } else if(is_youtube_url(url)) {
+ result_tabs.push_back(Tab{nullptr, std::make_unique<YoutubeVideoPage>(program, url), nullptr});
+ return PluginResult::OK;
} else {
const char *launch_program = "xdg-open";
if(!is_program_executable_by_name("xdg-open")) {
@@ -33,7 +41,12 @@ namespace QuickMedia {
// static
std::shared_ptr<BodyItem> InfoPage::add_url(const std::string &url) {
- auto body_item = BodyItem::create("Open " + url + " in a browser");
+ std::string title;
+ if(is_youtube_url(url))
+ title = "Play " + url;
+ else
+ title = "Open " + url + " in a browser";
+ auto body_item = BodyItem::create(std::move(title));
body_item->url = url;
return body_item;
}