From a76a27cf3eded668f23d6c39f3026e75884678c0 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Mon, 7 Dec 2020 19:44:55 +0100 Subject: Youtube: show when scheduled videos begin and disable playing them, show descriptions --- src/plugins/Youtube.cpp | 124 +++++++++++++++++++++++++++++++----------------- 1 file changed, 81 insertions(+), 43 deletions(-) diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index ddaae21..89058f5 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -1,6 +1,7 @@ #include "../../plugins/Youtube.hpp" #include "../../include/Storage.hpp" #include "../../include/NetUtils.hpp" +#include "../../include/StringUtils.hpp" #include "../../include/Scale.hpp" #include @@ -38,6 +39,48 @@ namespace QuickMedia { return std::nullopt; } + struct Thumbnail { + const char *url; + int width; + int height; + }; + + // TODO: Use this in |parse_common_video_item| when QuickMedia supports webp + static std::optional yt_json_get_largest_thumbnail(const Json::Value &thumbnail_json) { + if(!thumbnail_json.isObject()) + return std::nullopt; + + const Json::Value &thumbnails_json = thumbnail_json["thumbnails"]; + if(!thumbnails_json.isArray()) + return std::nullopt; + + std::vector thumbnails; + for(const Json::Value &thumbnail_data_json : thumbnails_json) { + if(!thumbnail_data_json.isObject()) + continue; + + const Json::Value &url_json = thumbnail_data_json["url"]; + if(!url_json.isString()) + continue; + + const Json::Value &width_json = thumbnail_data_json["width"]; + if(!width_json.isInt()) + continue; + + const Json::Value &height_json = thumbnail_data_json["height"]; + if(!height_json.isInt()) + continue; + + thumbnails.push_back({ url_json.asCString(), width_json.asInt(), height_json.asInt() }); + } + + return *std::max_element(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; + }); + } + static std::shared_ptr parse_common_video_item(const Json::Value &video_item_json, std::unordered_set &added_videos) { const Json::Value &video_id_json = video_item_json["videoId"]; if(!video_id_json.isString()) @@ -54,6 +97,7 @@ namespace QuickMedia { std::optional date = yt_json_get_text(video_item_json, "publishedTimeText"); std::optional view_count_text = yt_json_get_text(video_item_json, "viewCountText"); std::optional owner_text = yt_json_get_text(video_item_json, "shortBylineText"); + std::optional description_snippet = yt_json_get_text(video_item_json, "descriptionSnippet"); std::optional length = yt_json_get_text(video_item_json, "lengthText"); if(!length) { const Json::Value &thumbnail_overlays_json = video_item_json["thumbnailOverlays"]; @@ -63,6 +107,25 @@ namespace QuickMedia { length = yt_json_get_text(thumbnail_overlay_json["thumbnailOverlayTimeStatusRenderer"], "text"); } } + + std::string scheduled_text; + const Json::Value &upcoming_event_data_json = video_item_json["upcomingEventData"]; + if(upcoming_event_data_json.isObject()) { + const Json::Value &start_time_json = upcoming_event_data_json["startTime"]; + if(!start_time_json.isString()) + return nullptr; + + std::optional upcoming_event_text = yt_json_get_text(upcoming_event_data_json, "upcomingEventText"); + if(!upcoming_event_text) + return nullptr; + + time_t start_time = strtol(start_time_json.asCString(), nullptr, 10); + struct tm *message_tm = localtime(&start_time); + char time_str[128] = {0}; + strftime(time_str, sizeof(time_str) - 1, "%a %b %d %H:%M", message_tm); + string_replace_all(upcoming_event_text.value(), "DATE_PLACEHOLDER", time_str); + scheduled_text = std::move(upcoming_event_text.value()); + } auto body_item = BodyItem::create(title.value()); std::string desc; @@ -73,6 +136,11 @@ namespace QuickMedia { desc += " • "; desc += date.value(); } + if(!scheduled_text.empty()) { + if(!desc.empty()) + desc += " • "; + desc += scheduled_text; + } if(length) { if(!desc.empty()) desc += '\n'; @@ -83,8 +151,14 @@ namespace QuickMedia { desc += '\n'; desc += owner_text.value(); } + if(description_snippet) { + if(!desc.empty()) + desc += '\n'; + desc += description_snippet.value(); + } body_item->set_description(std::move(desc)); - body_item->url = "https://www.youtube.com/watch?v=" + video_id_str; + 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); @@ -102,47 +176,6 @@ namespace QuickMedia { return parse_common_video_item(video_renderer_json, added_videos); } - struct Thumbnail { - const char *url; - int width; - int height; - }; - - static std::optional yt_json_get_largest_thumbnail(const Json::Value &thumbnail_json) { - if(!thumbnail_json.isObject()) - return std::nullopt; - - const Json::Value &thumbnails_json = thumbnail_json["thumbnails"]; - if(!thumbnails_json.isArray()) - return std::nullopt; - - std::vector thumbnails; - for(const Json::Value &thumbnail_data_json : thumbnails_json) { - if(!thumbnail_data_json.isObject()) - continue; - - const Json::Value &url_json = thumbnail_data_json["url"]; - if(!url_json.isString()) - continue; - - const Json::Value &width_json = thumbnail_data_json["width"]; - if(!width_json.isInt()) - continue; - - const Json::Value &height_json = thumbnail_data_json["height"]; - if(!height_json.isInt()) - continue; - - thumbnails.push_back({ url_json.asCString(), width_json.asInt(), height_json.asInt() }); - } - - return *std::max_element(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; - }); - } - static std::shared_ptr parse_channel_renderer(const Json::Value &channel_renderer_json) { if(!channel_renderer_json.isObject()) return nullptr; @@ -473,6 +506,9 @@ namespace QuickMedia { } PluginResult YoutubeSearchPage::submit(const std::string &title, const std::string &url, std::vector &result_tabs) { + if(url.empty()) + return PluginResult::OK; + if(strncmp(url.c_str(), "https://www.youtube.com/channel/", 32) == 0) { // TODO: Make all pages (for all services) lazy fetch in a similar manner! result_tabs.push_back(Tab{create_body(), std::make_unique(program, url, "", title), create_search_bar("Search...", SEARCH_DELAY_FILTER)}); @@ -628,7 +664,9 @@ namespace QuickMedia { return PluginResult::OK; } - PluginResult YoutubeChannelPage::submit(const std::string&, const std::string&, std::vector &result_tabs) { + PluginResult YoutubeChannelPage::submit(const std::string&, const std::string &url, std::vector &result_tabs) { + if(url.empty()) + return PluginResult::OK; result_tabs.push_back(Tab{nullptr, std::make_unique(program), nullptr}); return PluginResult::OK; } -- cgit v1.2.3