From 9d4879215bb7b3b89122a930f86248cc405536a1 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sun, 13 Sep 2020 17:47:51 +0200 Subject: Add description to mangadex search result, add youtube timestamp --- include/Body.hpp | 18 ++++++++++++++++- src/Body.cpp | 31 +++++++++++++++++++++++------ src/Program.c | 2 +- src/QuickMedia.cpp | 50 +++++++++++++++++++++++++++++++++++------------ src/plugins/Mangadex.cpp | 13 ++++++++++++ src/plugins/Mangatown.cpp | 2 +- src/plugins/Youtube.cpp | 18 ++++++++++++----- 7 files changed, 107 insertions(+), 27 deletions(-) diff --git a/include/Body.hpp b/include/Body.hpp index db502c5..c487ad9 100644 --- a/include/Body.hpp +++ b/include/Body.hpp @@ -19,6 +19,7 @@ namespace QuickMedia { BodyItem(const BodyItem &other) { title = other.title; + description = other.description; url = other.url; thumbnail_url = other.thumbnail_url; attached_content_url = other.attached_content_url; @@ -29,6 +30,10 @@ namespace QuickMedia { title_text = std::make_unique(*other.title_text); else title_text = nullptr; + if(other.description_text) + description_text = std::make_unique(*other.description_text); + else + description_text = nullptr; replies = other.replies; post_number = other.post_number; } @@ -38,7 +43,14 @@ namespace QuickMedia { dirty = true; } - std::string title; + void set_description(std::string new_description) { + description = std::move(new_description); + } + + const std::string& get_title() const { return title; } + const std::string& get_description() const { return description; } + + // TODO: Use a list of strings instead, not all plugins need all of these fields std::string url; std::string thumbnail_url; std::string attached_content_url; @@ -46,9 +58,13 @@ namespace QuickMedia { bool visible; bool dirty; std::unique_ptr title_text; + std::unique_ptr description_text; // Used by image boards for example. The elements are indices to other body items std::vector replies; std::string post_number; + private: + std::string title; + std::string description; }; using BodyItems = std::vector>; diff --git a/src/Body.cpp b/src/Body.cpp index 0297072..b4ab917 100644 --- a/src/Body.cpp +++ b/src/Body.cpp @@ -173,11 +173,16 @@ namespace QuickMedia { if(body_item->dirty) { body_item->dirty = false; if(body_item->title_text) - body_item->title_text->setString(body_item->title); + body_item->title_text->setString(body_item->get_title()); else - body_item->title_text = std::make_unique(body_item->title, font, 14, size.x - 50 - image_padding_x * 2.0f); + body_item->title_text = std::make_unique(body_item->get_title(), bold_font, 16, size.x - 50 - image_padding_x * 2.0f); //body_item->title_text->updateGeometry(); // TODO: Call this to make getHeight work on first frame (called below) } + + if(!body_item->get_description().empty() && !body_item->description_text) { + body_item->description_text = std::make_unique(body_item->get_description(), font, 14, size.x - 50 - image_padding_x * 2.0f); + body_item->description_text->updateGeometry(); + } } // Find the starting row that can be drawn to make selected row visible as well @@ -191,6 +196,9 @@ namespace QuickMedia { if(!item->author.empty()) { item_height += author_text.getCharacterSize() + 2.0f; } + if(item->description_text) { + item_height += item->description_text->getHeight(); + } if(draw_thumbnails && !item->thumbnail_url.empty()) { auto &item_thumbnail = item_thumbnail_textures[item->thumbnail_url]; item_thumbnail.referenced = false; @@ -234,6 +242,9 @@ namespace QuickMedia { if(!item->author.empty()) { item_height += author_text.getCharacterSize() + 2.0f; } + if(item->description_text) { + item_height += item->description_text->getHeight(); + } if(draw_thumbnails && !item->thumbnail_url.empty()) { float image_height = image_max_height; if(item_thumbnail.texture && item_thumbnail.texture->getNativeHandle() != 0) { @@ -318,13 +329,19 @@ namespace QuickMedia { //title_text.setString(item->title); //title_text.setPosition(std::floor(item_pos.x + text_offset_x), std::floor(item_pos.y + padding_y)); //window.draw(title_text); - item->title_text->setString(item->title); item->title_text->setPosition(std::floor(item_pos.x + text_offset_x), std::floor(item_pos.y + padding_y - 4.0f)); item->title_text->setMaxWidth(size.x - text_offset_x - image_padding_x * 2.0f); item->title_text->draw(window); - // TODO: Do the same for non-manga content - const Json::Value &item_progress = content_progress[item->title]; + if(item->description_text) { + item->description_text->setPosition(std::floor(item_pos.x + text_offset_x), std::floor(item_pos.y + padding_y - 4.0f + item->title_text->getHeight())); + item->description_text->setMaxWidth(size.x - text_offset_x - image_padding_x * 2.0f); + item->description_text->draw(window); + } + + // TODO: Do the same for non-manga content. + // TODO: Cache this instead of hash access every item every frame. + const Json::Value &item_progress = content_progress[item->get_title()]; if(item_progress.isObject()) { const Json::Value ¤t_json = item_progress["current"]; const Json::Value &total_json = item_progress["total"]; @@ -366,7 +383,9 @@ namespace QuickMedia { } for(auto &item : items) { - item->visible = string_find_case_insensitive(item->title, text); + item->visible = string_find_case_insensitive(item->get_title(), text); + if(!item->visible && !item->get_description().empty()) + item->visible = string_find_case_insensitive(item->get_description(), text); } } diff --git a/src/Program.c b/src/Program.c index 47b0e33..fe2ae3f 100644 --- a/src/Program.c +++ b/src/Program.c @@ -53,7 +53,7 @@ int exec_program(const char **args, ProgramOutputCallback output_callback, void int result = 0; int status; - char buffer[2048]; + char buffer[4097]; for(;;) { ssize_t bytes_read = read(fd[READ_END], buffer, sizeof(buffer) - 1); diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index a8788d2..da280d8 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -101,7 +101,7 @@ namespace QuickMedia { if(!selected_item) return SearchResult::ERR; - selected_title = selected_item->title; + selected_title = selected_item->get_title(); selected_url = selected_item->url; if(!skip_search) { output_body->clear_items(); @@ -366,11 +366,34 @@ namespace QuickMedia { RECOMMENDED }; + // Returns relative time as a string (approximation) + static std::string timestamp_to_relative_time_str(time_t seconds) { + time_t minutes = seconds / 60; + time_t hours = minutes / 60; + time_t days = hours / 24; + time_t months = days / 30; + time_t years = days / 365; + + if(years >= 1) + return std::to_string(years) + " year" + (years == 1 ? "" : "s") + " ago"; + else if(months >= 1) + return std::to_string(months) + " month" + (months == 1 ? "" : "s") + " ago"; + else if(days >= 1) + return std::to_string(days) + " day" + (days == 1 ? "" : "s") + " ago"; + else if(hours >= 1) + return std::to_string(hours) + " hour" + (hours == 1 ? "" : "s") + " ago"; + else if(minutes >= 1) + return std::to_string(minutes) + " minute" + (minutes == 1 ? "" : "s") + " ago"; + else + return std::to_string(seconds) + " second" + (seconds == 1 ? "" : "s") + " ago"; + } + static void fill_history_items_from_json(const Json::Value &history_json, BodyItems &history_items) { assert(history_json.isArray()); BodyItems body_items; + time_t time_now = time(NULL); for(const Json::Value &item : history_json) { if(!item.isObject()) continue; @@ -385,13 +408,14 @@ namespace QuickMedia { continue; std::string title_str = title.asString(); - //const Json::Value ×tamp = item["timestamp"]; - //if(!timestamp.isNumeric()) - // continue; + const Json::Value ×tamp = item["timestamp"]; + if(!timestamp.isNumeric()) + continue; auto body_item = std::make_unique(std::move(title_str)); 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->set_description(timestamp_to_relative_time_str(std::max(0l, time_now - timestamp.asInt64()))); body_items.push_back(std::move(body_item)); } @@ -1024,7 +1048,7 @@ namespace QuickMedia { existing_recommendation["recommended_timestamp"] = time_now; } else { Json::Value new_content_object(Json::objectValue); - new_content_object["title"] = body_item->title; + new_content_object["title"] = body_item->get_title(); new_content_object["recommended_timestamp"] = time_now; new_content_object["recommended_count"] = 1; recommended_json[recommended_video_id] = std::move(new_content_object); @@ -1155,7 +1179,7 @@ namespace QuickMedia { for(auto it = related_media_body->items.begin(), end = related_media_body->items.end(); it != end; ++it) { if(watched_videos.find((*it)->url) == watched_videos.end()) { new_video_url = (*it)->url; - new_video_title = (*it)->title; + new_video_title = (*it)->get_title(); break; } } @@ -1237,7 +1261,7 @@ namespace QuickMedia { video_player = std::make_unique(current_plugin->use_tor, use_system_mpv_config, video_event_callback, on_window_create); content_url = selected_item->url; - content_title = selected_item->title; + content_title = selected_item->get_title(); load_video_error_check(); } } @@ -1342,7 +1366,7 @@ namespace QuickMedia { void Program::select_episode(BodyItem *item, bool start_from_beginning) { images_url = item->url; - chapter_title = item->title; + chapter_title = item->get_title(); image_index = 0; switch(image_view_mode) { case ImageViewMode::SINGLE: @@ -1406,10 +1430,10 @@ namespace QuickMedia { else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::T && sf::Keyboard::isKeyPressed(sf::Keyboard::LControl)) { BodyItem *selected_item = body->get_selected(); if(selected_item) { - if(track_media(TrackMediaType::HTML, content_title, selected_item->title, content_url) == 0) { - show_notification("Media tracker", "You are now tracking \"" + content_title + "\" after \"" + selected_item->title + "\"", Urgency::LOW); + if(track_media(TrackMediaType::HTML, content_title, selected_item->get_title(), content_url) == 0) { + show_notification("Media tracker", "You are now tracking \"" + content_title + "\" after \"" + selected_item->get_title() + "\"", Urgency::LOW); } else { - show_notification("Media tracker", "Failed to track media \"" + content_title + "\", chapter: \"" + selected_item->title + "\"", Urgency::CRITICAL); + show_notification("Media tracker", "Failed to track media \"" + content_title + "\", chapter: \"" + selected_item->get_title() + "\"", Urgency::CRITICAL); } } } @@ -1829,7 +1853,7 @@ namespace QuickMedia { if(!selected_item) return false; - content_episode = selected_item->title; + content_episode = selected_item->get_title(); content_url = selected_item->url; current_page = Page::CONTENT_DETAILS; body->clear_items(); @@ -1931,7 +1955,7 @@ namespace QuickMedia { if(!selected_item) return false; - content_episode = selected_item->title; + content_episode = selected_item->get_title(); content_url = selected_item->url; current_page = Page::IMAGE_BOARD_THREAD; body->clear_items(); diff --git a/src/plugins/Mangadex.cpp b/src/plugins/Mangadex.cpp index 853f501..7688355 100644 --- a/src/plugins/Mangadex.cpp +++ b/src/plugins/Mangadex.cpp @@ -213,6 +213,19 @@ namespace QuickMedia { } }, &body_item_image_context); + body_item_image_context.index = 0; + result = quickmedia_html_find_nodes_xpath(&html_search, "//div[class='pl-1']", + [](QuickMediaHtmlNode *node, void *userdata) { + auto *item_data = (BodyItemImageContext*)userdata; + const char *text = quickmedia_html_node_get_text(node); + if(text && item_data->index < item_data->body_items->size()) { + std::string desc = strip(text); + std::replace_if(desc.begin(), desc.end(), [](int c) { return c == '\n'; }, ' '); + (*item_data->body_items)[item_data->index]->set_description(std::move(desc)); + item_data->index++; + } + }, &body_item_image_context); + cleanup: quickmedia_html_search_deinit(&html_search); return result == 0 ? SuggestionResult::OK : SuggestionResult::ERR; diff --git a/src/plugins/Mangatown.cpp b/src/plugins/Mangatown.cpp index 8dcf8d3..cb3217b 100644 --- a/src/plugins/Mangatown.cpp +++ b/src/plugins/Mangatown.cpp @@ -33,7 +33,7 @@ namespace QuickMedia { int chapter_num = result_items.size(); for(auto &body_item : result_items) { - body_item->title = "Ch. " + std::to_string(chapter_num); + body_item->set_title("Ch. " + std::to_string(chapter_num)); chapter_num--; } diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index 46128ec..3b4a752 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -62,11 +62,8 @@ namespace QuickMedia { auto body_item = std::make_unique(title); /* TODO: Make date a different color */ - /* TODO: Do not append to title, messes up history.. */ - /*if(date) { - body_item->title += '\n'; - body_item->title += date; - }*/ + if(date) + body_item->set_description(date); body_item->url = "https://www.youtube.com/watch?v=" + video_id_str; body_item->thumbnail_url = std::move(thumbnail_url); added_videos.insert(video_id_str); @@ -447,6 +444,14 @@ namespace QuickMedia { std::string thumbnail_url = "https://img.youtube.com/vi/" + video_id_str + "/hqdefault.jpg"; + const char *date = nullptr; + const Json::Value &published_time_text_json = compact_video_renderer_json["publishedTimeText"]; + if(published_time_text_json.isObject()) { + const Json::Value &text_json = published_time_text_json["simpleText"]; + if(text_json.isString()) + date = text_json.asCString(); + } + const char *title = nullptr; const Json::Value &title_json = compact_video_renderer_json["title"]; if(title_json.isObject()) { @@ -460,6 +465,9 @@ namespace QuickMedia { return nullptr; auto body_item = std::make_unique(title); + /* TODO: Make date a different color */ + if(date) + body_item->set_description(date); body_item->url = "https://www.youtube.com/watch?v=" + video_id_str; body_item->thumbnail_url = std::move(thumbnail_url); added_videos.insert(video_id_str); -- cgit v1.2.3