From a8f1ebca36d90d965e8a55ebc766f659db65b0c1 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Thu, 29 Oct 2020 10:14:53 +0100 Subject: Matrix: download unstreamable videos before playing them (mp4) --- TODO | 3 +- include/MessageQueue.hpp | 2 +- include/QuickMedia.hpp | 2 +- src/QuickMedia.cpp | 129 ++++++++++++++++++++++++++++++++++++++++------- src/plugins/Matrix.cpp | 2 +- 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 pop_wait() { + std::unique_lock lock(mutex); if(!running) return std::nullopt; - std::unique_lock 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 &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 #include @@ -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(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 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 video_player; std::unique_ptr 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 comment_navigation_stack; std::stack 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()); -- cgit v1.2.3