From 47bb22a4aee886deb54ca432bdb14747bf2e9160 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 30 Apr 2021 16:33:07 +0200 Subject: Support webp thumbnails --- TODO | 1 - include/AsyncImageLoader.hpp | 3 ++- src/AsyncImageLoader.cpp | 25 ++++++++++++++++++++----- src/FileAnalyzer.cpp | 10 ++++++---- src/plugins/Matrix.cpp | 2 +- src/plugins/Youtube.cpp | 15 ++++++++++++++- 6 files changed, 43 insertions(+), 13 deletions(-) diff --git a/TODO b/TODO index fb29f2a..6d06090 100644 --- a/TODO +++ b/TODO @@ -78,7 +78,6 @@ Implement our own encryption for matrix. This is also needed to make forwarded m Modify matrix sync to download and parse json but not handle it, and then add a function to handle the json. This would allow us to remove all the mutex code if we would call that new method from the main thread (similar to chromium multithreading). Fetch replies/pinned message using multiple threads. Show in room tags list when there is a message in any of the rooms in the tag. -Support webp. Then switch to the youtube thumbnails from the response json instead of hqdefault, to remove the black bars. Show images while they download by showing them as scanlines starting from the top. Needed for slow websites such as 4chan. Use curl parallel download instead of downloading with multiple threads. This can be done with multiple -O parameters. Add functionality to ignore users in matrix. This is done with an ignore request and we wont get messages and invites from that user anymore. Also add option to ignore in the invites page. diff --git a/include/AsyncImageLoader.hpp b/include/AsyncImageLoader.hpp index 4acae5e..0384a71 100644 --- a/include/AsyncImageLoader.hpp +++ b/include/AsyncImageLoader.hpp @@ -2,6 +2,7 @@ #include "../include/Storage.hpp" #include "../include/MessageQueue.hpp" +#include "../include/FileAnalyzer.hpp" #include #include #include @@ -26,7 +27,7 @@ namespace QuickMedia { }; // This function is async - bool create_thumbnail(const Path &thumbnail_path, const Path &thumbnail_path_resized, sf::Vector2i resize_target_size); + bool create_thumbnail(const Path &thumbnail_path, const Path &thumbnail_path_resized, sf::Vector2i resize_target_size, ContentType content_type); constexpr int NUM_IMAGE_LOAD_THREADS = 4; diff --git a/src/AsyncImageLoader.cpp b/src/AsyncImageLoader.cpp index 34df062..6504992 100644 --- a/src/AsyncImageLoader.cpp +++ b/src/AsyncImageLoader.cpp @@ -1,5 +1,4 @@ #include "../include/AsyncImageLoader.hpp" -#include "../include/FileAnalyzer.hpp" #include "../include/DownloadUtils.hpp" #include "../include/Program.hpp" #include "../include/ImageUtils.hpp" @@ -11,18 +10,34 @@ #include namespace QuickMedia { - bool create_thumbnail(const Path &thumbnail_path, const Path &thumbnail_path_resized, sf::Vector2i resize_target_size) { + bool create_thumbnail(const Path &thumbnail_path, const Path &thumbnail_path_resized, sf::Vector2i resize_target_size, ContentType content_type) { + Path input_path = thumbnail_path; + + // TODO: Remove this when imagemagick supports webp + // Convert webp to png + if(content_type == ContentType::IMAGE_WEBP) { + Path result_path_tmp = input_path; + result_path_tmp.append(".tmp.png"); + + const char *args[] = { "ffmpeg", "-y", "-v", "quiet", "-i", input_path.data.c_str(), "--", result_path_tmp.data.c_str(), nullptr}; + int res = exec_program(args, nullptr, nullptr); + if(res != 0) + return false; + + input_path = std::move(result_path_tmp); + } + // > is to only shrink image if smaller than the target size std::string new_size = std::to_string(resize_target_size.x) + "x" + std::to_string(resize_target_size.y) + ">"; // We only want the first frame if its a gif - Path thumbnail_path_first_frame = thumbnail_path; + Path thumbnail_path_first_frame = std::move(input_path); thumbnail_path_first_frame.append("[0]"); Path result_path_tmp = thumbnail_path_resized; result_path_tmp.append(".tmp"); - const char *args[] = { "convert", thumbnail_path_first_frame.data.c_str(), "-thumbnail", new_size.c_str(), result_path_tmp.data.c_str(), nullptr}; + const char *args[] = { "convert", thumbnail_path_first_frame.data.c_str(), "-thumbnail", new_size.c_str(), result_path_tmp.data.c_str(), nullptr}; int convert_res = exec_program(args, nullptr, nullptr); if(convert_res == 0 && rename_atomic(result_path_tmp.data.c_str(), thumbnail_path_resized.data.c_str()) == 0) return true; @@ -46,7 +61,7 @@ namespace QuickMedia { return; } - if(create_thumbnail(thumbnail_path, thumbnail_path_resized, resize_target_size)) { + if(create_thumbnail(thumbnail_path, thumbnail_path_resized, resize_target_size, file_analyzer.get_content_type())) { load_image_from_file(*thumbnail_data->image, thumbnail_path_resized.data); } else { load_image_from_file(*thumbnail_data->image, thumbnail_path.data); diff --git a/src/FileAnalyzer.cpp b/src/FileAnalyzer.cpp index 9f05919..bd13a01 100644 --- a/src/FileAnalyzer.cpp +++ b/src/FileAnalyzer.cpp @@ -21,7 +21,7 @@ namespace QuickMedia { // What about audio ogg files that are not opus? // TODO: Test all of these - static const std::array magic_numbers = { + static const std::array magic_numbers = { MagicNumber{ {'R', 'I', 'F', 'F', -1, -1, -1, -1, 'A', 'V', 'I', ' '}, 12, ContentType::VIDEO_AVI }, MagicNumber{ {0x00, 0x00, 0x00, -1, 'f', 't', 'y', 'p', 'i', 's', 'o', 'm'}, 12, ContentType::VIDEO_MP4 }, MagicNumber{ {0x00, 0x00, 0x00, -1, 'f', 't', 'y', 'p', 'm', 'p', '4', '2'}, 12, ContentType::VIDEO_MP4 }, @@ -47,7 +47,8 @@ namespace QuickMedia { MagicNumber{ {'G', 'I', 'F', '8', '7', 'a'}, 6, ContentType::IMAGE_GIF }, MagicNumber{ {'G', 'I', 'F', '8', '9', 'a'}, 6, ContentType::IMAGE_GIF }, MagicNumber{ {'B', 'M'}, 2, ContentType::IMAGE_BMP }, - MagicNumber{ {'R', 'I', 'F', 'F', -1, -1, -1, -1, 'W', 'E', 'B', 'V', 'P'}, 6, ContentType::IMAGE_WEBP } + MagicNumber{ {'R', 'I', 'F', 'F', -1, -1, -1, -1, 'W', 'E', 'B', 'P'}, 12, ContentType::IMAGE_WEBP }, + MagicNumber{ {'R', 'I', 'F', 'F', -1, -1, -1, -1, 'W', 'E', 'B', 'V', 'P'}, 13, ContentType::IMAGE_WEBP } }; bool is_content_type_video(ContentType content_type) { @@ -121,7 +122,7 @@ namespace QuickMedia { Path destination_path_tmp = destination_path; destination_path_tmp.append(".ftmp"); - const char *program_args[] = { "ffmpeg", "-y", "-v", "quiet", "-i", filepath, "-vframes", "1", "-f", "singlejpeg", destination_path_tmp.data.c_str(), nullptr }; + const char *program_args[] = { "ffmpeg", "-y", "-v", "quiet", "-i", filepath, "-vframes", "1", "-f", "singlejpeg", "--", destination_path_tmp.data.c_str(), nullptr }; std::string ffmpeg_result; if(exec_program(program_args, nullptr, nullptr) != 0) { fprintf(stderr, "Failed to execute ffmpeg, maybe its not installed?\n"); @@ -129,7 +130,8 @@ namespace QuickMedia { } if(width > 0 && height > 0) { - if(create_thumbnail(destination_path_tmp, destination_path, sf::Vector2i(width, height))) { + FileAnalyzer file_analyzer; + if(file_analyzer.load_file(destination_path_tmp.data.c_str(), false) && create_thumbnail(destination_path_tmp, destination_path, sf::Vector2i(width, height), file_analyzer.get_content_type())) { remove(destination_path_tmp.data.c_str()); return true; } diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index 77d8f32..6f8b30b 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -3278,7 +3278,7 @@ namespace QuickMedia { int tmp_file = mkstemp(tmp_filename); if(tmp_file != -1) { std::string thumbnail_path; - if(create_thumbnail(filepath, tmp_filename, thumbnail_max_size)) + if(create_thumbnail(filepath, tmp_filename, thumbnail_max_size, file_analyzer.get_content_type())) thumbnail_path = tmp_filename; else thumbnail_path = filepath; diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index 993c861..cf3bbe4 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -53,10 +53,10 @@ namespace QuickMedia { enum class ThumbnailSize { SMALLEST, + MEDIUM, LARGEST }; - // TODO: Use this in |parse_common_video_item| when QuickMedia supports webp static std::optional yt_json_get_thumbnail(const Json::Value &thumbnail_json, ThumbnailSize thumbnail_size) { if(!thumbnail_json.isObject()) return std::nullopt; @@ -85,6 +85,9 @@ namespace QuickMedia { thumbnails.push_back({ url_json.asCString(), width_json.asInt(), height_json.asInt() }); } + if(thumbnails.empty()) + return std::nullopt; + switch(thumbnail_size) { case ThumbnailSize::SMALLEST: return *std::min_element(thumbnails.begin(), thumbnails.end(), [](const Thumbnail &thumbnail1, const Thumbnail &thumbnail2) { @@ -92,6 +95,14 @@ namespace QuickMedia { int size2 = thumbnail2.width * thumbnail2.height; return size1 < size2; }); + case ThumbnailSize::MEDIUM: { + std::sort(thumbnails.begin(), thumbnails.end(), [](const Thumbnail &thumbnail1, const Thumbnail &thumbnail2) { + int size1 = thumbnail1.width * thumbnail1.height; + int size2 = thumbnail2.width * thumbnail2.height; + return size1 < size2; + }); + return thumbnails[thumbnails.size() / 2]; + } case ThumbnailSize::LARGEST: return *std::max_element(thumbnails.begin(), thumbnails.end(), [](const Thumbnail &thumbnail1, const Thumbnail &thumbnail2) { int size1 = thumbnail1.width * thumbnail1.height; @@ -184,8 +195,10 @@ namespace QuickMedia { body_item->set_description_color(sf::Color(179, 179, 179)); if(scheduled_text.empty()) body_item->url = "https://www.youtube.com/watch?v=" + video_id_str; + body_item->thumbnail_url = "https://img.youtube.com/vi/" + video_id_str + "/hqdefault.jpg"; body_item->thumbnail_size = sf::Vector2i(175, 131); + added_videos.insert(video_id_str); return body_item; } -- cgit v1.2.3