aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2020-10-29 10:14:53 +0100
committerdec05eba <dec05eba@protonmail.com>2020-10-29 10:14:53 +0100
commita8f1ebca36d90d965e8a55ebc766f659db65b0c1 (patch)
tree6daefa8768e44a7cd21f096821321dd150e42c8d
parent620123fbd6c18dc48a25cc735565f6d8d85f8639 (diff)
Matrix: download unstreamable videos before playing them (mp4)
-rw-r--r--TODO3
-rw-r--r--include/MessageQueue.hpp2
-rw-r--r--include/QuickMedia.hpp2
-rw-r--r--src/QuickMedia.cpp129
-rw-r--r--src/plugins/Matrix.cpp2
5 files changed, 115 insertions, 23 deletions
diff --git a/TODO b/TODO
index 6023aed..ca6e65a 100644
--- a/TODO
+++ b/TODO
@@ -122,4 +122,5 @@ For messages that mention us we only want a notification for the last edited ver
Fetch replies/pinned message using multiple threads.
Replying to edited message shows incorrect body in matrix.
Show in room tags list when there is a message in any of the rooms in the tag.
-Apply current search filter when adding new rooms to the room list. \ No newline at end of file
+Apply current search filter when adding new rooms to the room list.
+Cancel video download when pressing escape or closing window (important in matrix). \ No newline at end of file
diff --git a/include/MessageQueue.hpp b/include/MessageQueue.hpp
index 174a227..df331ac 100644
--- a/include/MessageQueue.hpp
+++ b/include/MessageQueue.hpp
@@ -20,9 +20,9 @@ namespace QuickMedia {
}
std::optional<T> pop_wait() {
+ std::unique_lock<std::mutex> lock(mutex);
if(!running)
return std::nullopt;
- std::unique_lock<std::mutex> lock(mutex);
while(data_queue.empty() && running) cv.wait(lock);
if(!running)
return std::nullopt;
diff --git a/include/QuickMedia.hpp b/include/QuickMedia.hpp
index bdbafef..99bab7a 100644
--- a/include/QuickMedia.hpp
+++ b/include/QuickMedia.hpp
@@ -56,7 +56,7 @@ namespace QuickMedia {
private:
void base_event_handler(sf::Event &event, PageType previous_page, Body *body, SearchBar *search_bar, bool handle_key_press = true, bool handle_searchbar = true);
void page_loop(std::vector<Tab> &tabs);
- void video_content_page(Page *page, std::string video_url, std::string video_title);
+ void video_content_page(Page *page, std::string video_url, std::string video_title, bool download_if_streaming_fails);
// 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);
void image_continuous_page(MangaImagesPage *images_page);
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index c4532cd..ba9a680 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -20,6 +20,7 @@
#include "../include/Entry.hpp"
#include "../include/NetUtils.hpp"
#include "../include/SfmlFixes.hpp"
+#include "../external/hash-library/sha256.h"
#include <assert.h>
#include <cmath>
@@ -675,7 +676,7 @@ namespace QuickMedia {
}
}
window.clear(back_color);
- load_sprite.setPosition(window_size.x * 0.5f - loading_icon_size.x * 0.5f, window_size.y * 0.5f - loading_icon_size.y * 0.5f);
+ load_sprite.setPosition(window_size.x * 0.5f, window_size.y * 0.5f);
load_sprite.setRotation(timer.getElapsedTime().asSeconds() * 400.0);
window.draw(load_sprite);
window.display();
@@ -1100,7 +1101,7 @@ namespace QuickMedia {
image_board_thread_page(static_cast<ImageBoardThreadPage*>(new_tabs[0].page.get()), new_tabs[0].body.get());
} else if(new_tabs.size() == 1 && new_tabs[0].page->get_type() == PageTypez::VIDEO) {
current_page = PageType::VIDEO_CONTENT;
- video_content_page(new_tabs[0].page.get(), selected_item->url, selected_item->get_title());
+ video_content_page(new_tabs[0].page.get(), selected_item->url, selected_item->get_title(), false);
} else if(new_tabs.size() == 1 && new_tabs[0].page->get_type() == PageTypez::CHAT) {
current_page = PageType::CHAT;
current_chat_room = matrix->get_room_by_id(selected_item->url);
@@ -1562,15 +1563,109 @@ namespace QuickMedia {
save_json_to_file_atomic(get_recommended_filepath(plugin_name), recommended_json);
}
+ static const char *useragent_str = "user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36";
+
+ static int accumulate_string_limit_head(char *data, int size, void *userdata) {
+ std::string *str = (std::string*)userdata;
+ str->append(data, size);
+ if(str->size() >= 40)
+ return 1;
+ return 0;
+ }
+
+ static bool video_url_is_non_streamable_mp4(const char *url) {
+ std::string result;
+ const char *args[] = { "curl", "-sLf", "-r", "0-40", "-H", useragent_str, "--", url, nullptr };
+ exec_program(args, accumulate_string_limit_head, &result);
+ return (result.size() >= 40)
+ && (memcmp(&result[4], "ftypisom", 8) == 0 || memcmp(&result[4], "ftypmp42", 8) == 0 || memcmp(&result[4], "ftymp42", 7) == 0 || memcmp(&result[4], "fty3gp5", 7) == 0)
+ && (memcmp(&result[36], "moov", 4) != 0);
+ }
+
#define CLEANMASK(mask) ((mask) & (ShiftMask|ControlMask|Mod1Mask|Mod4Mask|Mod5Mask))
- void Program::video_content_page(Page *page, std::string video_url, std::string video_title) {
+ void Program::video_content_page(Page *page, std::string video_url, std::string video_title, bool download_if_streaming_fails) {
sf::Clock time_watched_timer;
bool added_recommendations = false;
bool video_loaded = false;
PageType previous_page = pop_page_stack();
+ sf::Sprite load_sprite(loading_icon);
+ sf::Vector2u loading_icon_size = loading_icon.getSize();
+ load_sprite.setOrigin(loading_icon_size.x * 0.5f, loading_icon_size.y * 0.5f);
+
+ bool video_url_is_local = false;
+ if(download_if_streaming_fails && video_url_is_non_streamable_mp4(video_url.c_str())) {
+ fprintf(stderr, "%s is detected to be a non-streamable mp4 file, downloading it before playing it...\n", video_url.c_str());
+ Path video_cache_dir = get_cache_dir().join("video");
+ if(create_directory_recursive(video_cache_dir) != 0) {
+ show_notification("QuickMedia", "Failed to create video cache directory", Urgency::CRITICAL);
+ current_page = previous_page;
+ return;
+ }
+
+ Path video_path = video_cache_dir;
+ SHA256 sha256;
+ sha256.add(video_url.data(), video_url.size());
+ video_path.join(sha256.getHash());
+ if(get_file_type(video_path) == FileType::REGULAR) {
+ fprintf(stderr, "%s is found in cache. Playing from cache...\n", video_url.c_str());
+ video_url = std::move(video_path.data);
+ video_url_is_local = true;
+ } else {
+ std::future<DownloadResult> download_future = std::async(std::launch::async, [this, &video_path, video_url]() {
+ return download_to_file(video_url, video_path.data, {}, use_tor, true);
+ });
+
+ window_size.x = window.getSize().x;
+ window_size.y = window.getSize().y;
+ sf::Clock timer;
+ sf::Event event;
+ while(window.isOpen()) {
+ while(window.pollEvent(event)) {
+ if(event.type == sf::Event::Closed) {
+ // TODO: Remove this
+ if(download_future.valid())
+ download_future.get();
+ current_page = previous_page;
+ window.close();
+ return;
+ } else if(event.type == sf::Event::Resized) {
+ window_size.x = event.size.width;
+ window_size.y = event.size.height;
+ sf::FloatRect visible_area(0, 0, window_size.x, window_size.y);
+ window.setView(sf::View(visible_area));
+ } else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape) {
+ // TODO: Remove this
+ if(download_future.valid())
+ download_future.get();
+ current_page = previous_page;
+ return;
+ }
+ }
+
+ if(is_future_ready(download_future)) {
+ if(download_future.get() != DownloadResult::OK) {
+ show_notification("QuickMedia", "Failed to download " + video_url, Urgency::CRITICAL);
+ current_page = previous_page;
+ return;
+ }
+ video_url = std::move(video_path.data);
+ video_url_is_local = true;
+ break;
+ }
+
+ window.clear(back_color);
+ load_sprite.setPosition(window_size.x * 0.5f, window_size.y * 0.5f);
+ load_sprite.setRotation(timer.getElapsedTime().asSeconds() * 400.0);
+ window.draw(load_sprite);
+ window.display();
+ }
+ }
+ }
+
+ time_watched_timer.restart();
std::unique_ptr<VideoPlayer> video_player;
std::unique_ptr<sf::RenderWindow> related_media_window;
sf::Vector2f related_media_window_size;
@@ -1691,10 +1786,6 @@ namespace QuickMedia {
sf::RectangleShape rect;
rect.setFillColor(sf::Color::Red);
- sf::Sprite load_sprite(loading_icon);
- sf::Vector2u loading_icon_size = loading_icon.getSize();
- load_sprite.setOrigin(loading_icon_size.x * 0.5f, loading_icon_size.y * 0.5f);
-
XEvent xev;
bool cursor_visible = true;
sf::Clock cursor_hide_timer;
@@ -1703,8 +1794,8 @@ namespace QuickMedia {
bool is_pornhub = strcmp(plugin_name, "pornhub") == 0;
bool supports_url_timestamp = is_youtube || is_pornhub;
- auto save_video_url_to_clipboard = [&video_url, &video_player_window, &video_player, &supports_url_timestamp]() {
- if(!video_player_window)
+ auto save_video_url_to_clipboard = [&video_url_is_local, &video_url, &video_player_window, &video_player, &supports_url_timestamp]() {
+ if(!video_player_window || video_url_is_local)
return;
if(supports_url_timestamp) {
@@ -1718,7 +1809,7 @@ namespace QuickMedia {
}
};
- while (current_page == PageType::VIDEO_CONTENT) {
+ while (current_page == PageType::VIDEO_CONTENT && window.isOpen()) {
while (window.pollEvent(event)) {
base_event_handler(event, previous_page, related_media_body.get(), nullptr, true, false);
if(event.type == sf::Event::Resized && related_media_window) {
@@ -1834,7 +1925,7 @@ namespace QuickMedia {
if(!video_loaded) {
window.clear(back_color);
- load_sprite.setPosition(window_size.x * 0.5f - loading_icon_size.x * 0.5f, window_size.y * 0.5f - loading_icon_size.y * 0.5f);
+ load_sprite.setPosition(window_size.x * 0.5f, window_size.y * 0.5f);
load_sprite.setRotation(time_watched_timer.getElapsedTime().asSeconds() * 400.0);
window.draw(load_sprite);
window.display();
@@ -2170,7 +2261,7 @@ namespace QuickMedia {
while(window.pollEvent(event)) {}
// TODO: Show to user if a certain page is missing (by checking page name (number) and checking if some are skipped)
- while (current_page == PageType::IMAGES) {
+ while (current_page == PageType::IMAGES && window.isOpen()) {
while(window.pollEvent(event)) {
if (event.type == sf::Event::Closed) {
current_page = PageType::EXIT;
@@ -2338,7 +2429,7 @@ namespace QuickMedia {
show_notification("QuickMedia", "Failed to save manga progress", Urgency::CRITICAL);
}
- while(current_page == PageType::IMAGES_CONTINUOUS) {
+ while(current_page == PageType::IMAGES_CONTINUOUS && window.isOpen()) {
window.clear(back_color);
ImageViewerAction action = image_viewer.draw(window);
switch(action) {
@@ -2546,7 +2637,7 @@ namespace QuickMedia {
std::stack<int> comment_navigation_stack;
std::stack<int> comment_page_scroll_stack;
- while (current_page == PageType::IMAGE_BOARD_THREAD) {
+ while (current_page == PageType::IMAGE_BOARD_THREAD && window.isOpen()) {
while (window.pollEvent(event)) {
if(navigation_stage == NavigationStage::REPLYING) {
comment_input.process_event(event);
@@ -2595,7 +2686,7 @@ namespace QuickMedia {
current_page = PageType::VIDEO_CONTENT;
watched_videos.clear();
// TODO: Use real title
- video_content_page(thread_page, selected_item->attached_content_url, "No title.webm");
+ video_content_page(thread_page, selected_item->attached_content_url, "No title.webm", true);
redraw = true;
} else {
if(downloading_image && load_image_future.valid())
@@ -2902,7 +2993,7 @@ namespace QuickMedia {
auto body = create_body();
- while (current_page == PageType::CHAT_LOGIN) {
+ while (current_page == PageType::CHAT_LOGIN && window.isOpen()) {
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed) {
current_page = PageType::EXIT;
@@ -3396,7 +3487,7 @@ namespace QuickMedia {
watched_videos.clear();
current_page = PageType::VIDEO_CONTENT;
// TODO: Add title
- video_content_page(video_page.get(), url, "No title");
+ video_content_page(video_page.get(), url, "No title", true);
redraw = true;
} else {
const char *launch_program = "xdg-open";
@@ -3458,7 +3549,7 @@ namespace QuickMedia {
bool prev_no_video = no_video;
no_video = is_audio;
// TODO: Add title
- video_content_page(video_page.get(), *selected_url, "No title");
+ video_content_page(video_page.get(), *selected_url, "No title", true);
no_video = prev_no_video;
redraw = true;
return true;
@@ -3492,7 +3583,7 @@ namespace QuickMedia {
SyncData sync_data;
- while (current_page == PageType::CHAT) {
+ while (current_page == PageType::CHAT && window.isOpen()) {
sf::Int32 frame_time_ms = frame_timer.restart().asMilliseconds();
while (window.pollEvent(event)) {
base_event_handler(event, PageType::EXIT, tabs[selected_tab].body.get(), nullptr, false, false);
diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp
index ede0821..a702841 100644
--- a/src/plugins/Matrix.cpp
+++ b/src/plugins/Matrix.cpp
@@ -1503,7 +1503,7 @@ namespace QuickMedia {
}
info_json.AddMember("thumbnail_url", rapidjson::StringRef(thumbnail_info->content_uri.c_str()), request_data.GetAllocator());
- info_json.AddMember("info", std::move(thumbnail_info_json), request_data.GetAllocator());
+ info_json.AddMember("thumbnail_info", std::move(thumbnail_info_json), request_data.GetAllocator());
}
request_data.AddMember("info", std::move(info_json), request_data.GetAllocator());