From fc49d40c0d2f6edbbe9dde1f1b53d6a17e9d9f7d Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sun, 17 Oct 2021 05:53:22 +0200 Subject: Limit image loading to one thread in async image loader --- TODO | 1 + depends/mglpp | 1 + include/AsyncImageLoader.hpp | 12 +++- src/AsyncImageLoader.cpp | 138 +++++++++++++++++++++++++++++++------------ src/Body.cpp | 2 +- 5 files changed, 114 insertions(+), 40 deletions(-) create mode 160000 depends/mglpp diff --git a/TODO b/TODO index e2dc677..ef34c82 100644 --- a/TODO +++ b/TODO @@ -207,3 +207,4 @@ Add flag to quickmedia to use svp, by making svp use the same input ipc socket a Support directly going to a youtube channel for a url. This is helpful for opening channel urls directly with quickmedia and also going to another channel from a youtube description. 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. diff --git a/depends/mglpp b/depends/mglpp new file mode 160000 index 0000000..9294028 --- /dev/null +++ b/depends/mglpp @@ -0,0 +1 @@ +Subproject commit 929402828d99810211c644ce4ecf07b98013be10 diff --git a/include/AsyncImageLoader.hpp b/include/AsyncImageLoader.hpp index 94b225d..7556fbc 100644 --- a/include/AsyncImageLoader.hpp +++ b/include/AsyncImageLoader.hpp @@ -15,6 +15,7 @@ namespace QuickMedia { enum class LoadingState { NOT_LOADED, LOADING, + READY_TO_LOAD, FINISHED_LOADING, APPLIED_TO_TEXTURE }; @@ -24,6 +25,7 @@ namespace QuickMedia { sf::Texture texture; std::unique_ptr image; // Set in another thread. This should be .reset after loading it into |texture|, to save memory size_t counter = 0; + Path thumbnail_path; }; struct ThumbnailLoadData { @@ -70,19 +72,25 @@ namespace QuickMedia { // set |resize_target_size| to {0, 0} to disable resizing. // Note: this method is not thread-safe - void load_thumbnail(const std::string &url, bool local, sf::Vector2i resize_target_size, std::shared_ptr thumbnail_data); + bool load_thumbnail(const std::string &url, bool local, sf::Vector2i resize_target_size, std::shared_ptr thumbnail_data, Path &thumbnail_path); // Returns -1 if all threads are busy int get_free_load_index() const; + void load_create_thumbnail(const Path &thumbnail_path, const Path &thumbnail_path_resized, ThumbnailData *thumbnail_data, sf::Vector2i resize_target_size); + void process_thumbnail(ThumbnailLoadData &thumbnail_load_data); + private: void reset_download(Download &download); private: std::mutex download_mutex; // TODO: Use curl single-threaded multi-download feature instead Download downloads[NUM_IMAGE_LOAD_PARALLEL]; AsyncTask load_thread; - MessageQueue image_load_queue; + MessageQueue image_thumbnail_create_queue; std::unordered_map> thumbnails; size_t counter = 0; + + std::mutex image_load_mutex; + ThumbnailLoadData current_loading_image; }; } diff --git a/src/AsyncImageLoader.cpp b/src/AsyncImageLoader.cpp index 762408c..4661960 100644 --- a/src/AsyncImageLoader.cpp +++ b/src/AsyncImageLoader.cpp @@ -147,29 +147,33 @@ namespace QuickMedia { return true; } - // Create thumbnail and load it. On failure load the original image - static void create_thumbnail(const Path &thumbnail_path, const Path &thumbnail_path_resized, ThumbnailData *thumbnail_data, sf::Vector2i resize_target_size) { + void AsyncImageLoader::load_create_thumbnail(const Path &thumbnail_path, const Path &thumbnail_path_resized, ThumbnailData *thumbnail_data, sf::Vector2i resize_target_size) { FileAnalyzer file_analyzer; if(!file_analyzer.load_file(thumbnail_path.data.c_str(), false)) { - fprintf(stderr, "Failed to convert %s to a thumbnail, using the original image\n", thumbnail_path.data.c_str()); + fprintf(stderr, "Failed to convert %s to a thumbnail\n", thumbnail_path.data.c_str()); + thumbnail_data->image = std::make_unique(); thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; return; } if(is_content_type_video(file_analyzer.get_content_type())) { - if(video_get_first_frame(thumbnail_path.data.c_str(), thumbnail_path_resized.data.c_str(), resize_target_size.x, resize_target_size.y)) - load_image_from_file(*thumbnail_data->image, thumbnail_path_resized.data); - thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; + if(video_get_first_frame(thumbnail_path.data.c_str(), thumbnail_path_resized.data.c_str(), resize_target_size.x, resize_target_size.y)) { + thumbnail_data->loading_state = LoadingState::READY_TO_LOAD; + } else { + fprintf(stderr, "Failed to get first frame of %s\n", thumbnail_path.data.c_str()); + thumbnail_data->image = std::make_unique(); + thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; + } return; } if(create_thumbnail(thumbnail_path, thumbnail_path_resized, resize_target_size, file_analyzer.get_content_type(), true)) { - load_image_from_file(*thumbnail_data->image, thumbnail_path_resized.data); + thumbnail_data->loading_state = LoadingState::READY_TO_LOAD; } else { - load_image_from_file(*thumbnail_data->image, thumbnail_path.data); - fprintf(stderr, "Failed to convert %s to a thumbnail, using the original image\n", thumbnail_path.data.c_str()); + fprintf(stderr, "Failed to convert %s to a thumbnail\n", thumbnail_path.data.c_str()); + thumbnail_data->image = std::make_unique(); + thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; } - thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; } AsyncImageLoader& AsyncImageLoader::get_instance() { @@ -179,7 +183,31 @@ namespace QuickMedia { return *instance; } - static void process_thumbnail(ThumbnailLoadData &thumbnail_load_data) { + void AsyncImageLoader::process_thumbnail(ThumbnailLoadData &thumbnail_load_data) { + Path thumbnail_path_resized = thumbnail_load_data.thumbnail_path; + if(thumbnail_load_data.resize_target_size.x != 0 && thumbnail_load_data.resize_target_size.y != 0) + thumbnail_path_resized.append("_" + std::to_string(thumbnail_load_data.resize_target_size.x) + "x" + std::to_string(thumbnail_load_data.resize_target_size.y)); + + if(get_file_type(thumbnail_path_resized) == FileType::REGULAR) { + fprintf(stderr, "Loaded %s from thumbnail cache\n", thumbnail_path_resized.data.c_str()); + thumbnail_load_data.thumbnail_data->image = std::make_unique(); + thumbnail_load_data.thumbnail_data->loading_state = LoadingState::READY_TO_LOAD; + return; + } + + Path thumbnail_original_path; + if(thumbnail_load_data.local) + thumbnail_original_path = thumbnail_load_data.path; + else + thumbnail_original_path = thumbnail_load_data.thumbnail_path; + + if(thumbnail_load_data.resize_target_size.x != 0 && thumbnail_load_data.resize_target_size.y != 0) + load_create_thumbnail(thumbnail_original_path, thumbnail_path_resized, thumbnail_load_data.thumbnail_data.get(), thumbnail_load_data.resize_target_size); + else + thumbnail_load_data.thumbnail_data->loading_state = LoadingState::READY_TO_LOAD; + } + + static void load_processed_thumbnail(ThumbnailLoadData &thumbnail_load_data) { thumbnail_load_data.thumbnail_data->image = std::make_unique(); Path thumbnail_path_resized = thumbnail_load_data.thumbnail_path; @@ -189,7 +217,6 @@ namespace QuickMedia { if(get_file_type(thumbnail_path_resized) == FileType::REGULAR) { load_image_from_file(*thumbnail_load_data.thumbnail_data->image, thumbnail_path_resized.data); thumbnail_load_data.thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; - fprintf(stderr, "Loaded %s from thumbnail cache\n", thumbnail_path_resized.data.c_str()); return; } @@ -199,12 +226,13 @@ namespace QuickMedia { else thumbnail_original_path = thumbnail_load_data.thumbnail_path; - if(thumbnail_load_data.resize_target_size.x != 0 && thumbnail_load_data.resize_target_size.y != 0) - create_thumbnail(thumbnail_original_path, thumbnail_path_resized, thumbnail_load_data.thumbnail_data.get(), thumbnail_load_data.resize_target_size); - else + if(thumbnail_load_data.resize_target_size.x != 0 && thumbnail_load_data.resize_target_size.y != 0) { + load_image_from_file(*thumbnail_load_data.thumbnail_data->image, thumbnail_path_resized.data); + thumbnail_load_data.thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; + } else { load_image_from_file(*thumbnail_load_data.thumbnail_data->image, thumbnail_original_path.data); - - thumbnail_load_data.thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; + thumbnail_load_data.thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; + } } AsyncImageLoader::AsyncImageLoader() { @@ -215,10 +243,26 @@ namespace QuickMedia { load_thread = AsyncTask([this]() mutable { std::optional thumbnail_load_data_opt; - while(image_load_queue.is_running()) { - thumbnail_load_data_opt = image_load_queue.pop_if_available(); - if(thumbnail_load_data_opt) + while(image_thumbnail_create_queue.is_running()) { + ThumbnailLoadData load_image_data; + { + std::lock_guard lock(image_load_mutex); + if(current_loading_image.thumbnail_data) { + load_image_data = std::move(current_loading_image); + current_loading_image = ThumbnailLoadData(); + } + } + + if(load_image_data.thumbnail_data) { + load_processed_thumbnail(load_image_data); + load_image_data = ThumbnailLoadData(); + } + + thumbnail_load_data_opt = image_thumbnail_create_queue.pop_if_available(); + if(thumbnail_load_data_opt) { process_thumbnail(thumbnail_load_data_opt.value()); + thumbnail_load_data_opt = std::nullopt; + } for(int i = 0; i < NUM_IMAGE_LOAD_PARALLEL; ++i) { Download &download = downloads[i]; @@ -246,7 +290,7 @@ namespace QuickMedia { } AsyncImageLoader::~AsyncImageLoader() { - image_load_queue.close(); + image_thumbnail_create_queue.close(); } static bool download_file_async(const char *url, const char *save_filepath, ReadProgram *read_program) { @@ -260,19 +304,16 @@ namespace QuickMedia { return exec_program_pipe(args, read_program) == 0; } - void AsyncImageLoader::load_thumbnail(const std::string &url, bool local, sf::Vector2i resize_target_size, std::shared_ptr thumbnail_data) { + bool AsyncImageLoader::load_thumbnail(const std::string &url, bool local, sf::Vector2i resize_target_size, std::shared_ptr thumbnail_data, Path &thumbnail_path) { if(thumbnail_data->loading_state != LoadingState::NOT_LOADED) - return; + return true; if(url.empty()) { thumbnail_data->image = std::make_unique(); thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; - return; + return false; } - SHA256 sha256; - sha256.add(url.data(), url.size()); - Path thumbnail_path = get_cache_dir().join("thumbnails").join(sha256.getHash()); if(local) { struct stat file_stat; memset(&file_stat, 0, sizeof(file_stat)); @@ -280,24 +321,24 @@ namespace QuickMedia { fprintf(stderr, "Failed to load thumbnail %s: no such file\n", url.c_str()); thumbnail_data->image = std::make_unique(); thumbnail_data->loading_state = LoadingState::FINISHED_LOADING; - return; + return false; } thumbnail_path.append("_" + std::to_string(file_stat.st_mtim.tv_sec)); thumbnail_data->loading_state = LoadingState::LOADING; - image_load_queue.push({ url, std::move(thumbnail_path), true, thumbnail_data, resize_target_size }); - return; + image_thumbnail_create_queue.push({ url, thumbnail_path, true, thumbnail_data, resize_target_size }); + return true; } if(get_file_type(thumbnail_path) == FileType::REGULAR) { thumbnail_data->loading_state = LoadingState::LOADING; - image_load_queue.push({ url, std::move(thumbnail_path), false, thumbnail_data, resize_target_size }); - return; + image_thumbnail_create_queue.push({ url, thumbnail_path, false, thumbnail_data, resize_target_size }); + return true; } int free_index = get_free_load_index(); if(free_index == -1) - return; + return false; std::lock_guard lock(download_mutex); thumbnail_data->loading_state = LoadingState::LOADING; @@ -305,14 +346,15 @@ namespace QuickMedia { tmp_thumbnail_path.append(".tmp"); if(!download_file_async(url.c_str(), tmp_thumbnail_path.data.c_str(), &downloads[free_index].read_program)) { fprintf(stderr, "Failed to start download of %s\n", url.c_str()); - return; + return false; } downloads[free_index].download_start = get_boottime_milliseconds(); - downloads[free_index].thumbnail_path = std::move(thumbnail_path); + downloads[free_index].thumbnail_path = thumbnail_path; downloads[free_index].thumbnail_data = thumbnail_data; downloads[free_index].resize_target_size = resize_target_size; downloads[free_index].url = url; + return true; } std::shared_ptr AsyncImageLoader::get_thumbnail(const std::string &url, bool local, sf::Vector2i resize_target_size) { @@ -321,7 +363,22 @@ namespace QuickMedia { if(!thumbnail_data) thumbnail_data = std::make_shared(); thumbnail_data->counter = counter; - load_thumbnail(url, local, resize_target_size, thumbnail_data); + + if(thumbnail_data->thumbnail_path.data.empty()) { + SHA256 sha256; + sha256.add(url.data(), url.size()); + thumbnail_data->thumbnail_path = get_cache_dir().join("thumbnails").join(sha256.getHash()); + } + + if(!load_thumbnail(url, local, resize_target_size, thumbnail_data, thumbnail_data->thumbnail_path)) + return thumbnail_data; + + if(thumbnail_data->loading_state == LoadingState::READY_TO_LOAD) { + std::lock_guard lock(image_load_mutex); + if(!current_loading_image.thumbnail_data) + current_loading_image = { url, thumbnail_data->thumbnail_path, local, thumbnail_data, resize_target_size }; + } + return thumbnail_data; } @@ -342,9 +399,16 @@ namespace QuickMedia { } } - image_load_queue.erase_if([&it](ThumbnailLoadData &load_data) { + image_thumbnail_create_queue.erase_if([&it](ThumbnailLoadData &load_data) { return load_data.path.data == it->first; }); + + { + std::lock_guard lock(image_load_mutex); + if(current_loading_image.thumbnail_data == it->second) + current_loading_image = ThumbnailLoadData(); + } + it = thumbnails.erase(it); loaded_textures_changed = true; } else { diff --git a/src/Body.cpp b/src/Body.cpp index d4b6c5c..eb7a1be 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -1070,7 +1070,7 @@ namespace QuickMedia { std::shared_ptr &item = items[index]; assert(item->visible); - int prev_index; + int prev_index = 0; if(attach_side == AttachSide::BOTTOM) { prev_index = get_previous_visible_item(index); if(prev_index == -1) -- cgit v1.2.3