aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2020-12-07 19:44:55 +0100
committerdec05eba <dec05eba@protonmail.com>2020-12-07 19:44:55 +0100
commita76a27cf3eded668f23d6c39f3026e75884678c0 (patch)
treedd957c6e934669bd8c35d1fc302f12241c86613b
parente77d6914723d9af61cbdaf22a20fef7b179fc391 (diff)
Youtube: show when scheduled videos begin and disable playing them, show descriptions
-rw-r--r--src/plugins/Youtube.cpp124
1 files 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 <string.h>
@@ -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<Thumbnail> 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<Thumbnail> 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<BodyItem> parse_common_video_item(const Json::Value &video_item_json, std::unordered_set<std::string> &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<std::string> date = yt_json_get_text(video_item_json, "publishedTimeText");
std::optional<std::string> view_count_text = yt_json_get_text(video_item_json, "viewCountText");
std::optional<std::string> owner_text = yt_json_get_text(video_item_json, "shortBylineText");
+ std::optional<std::string> description_snippet = yt_json_get_text(video_item_json, "descriptionSnippet");
std::optional<std::string> 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<std::string> 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<Thumbnail> 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<Thumbnail> 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<BodyItem> 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<Tab> &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<YoutubeChannelPage>(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<Tab> &result_tabs) {
+ PluginResult YoutubeChannelPage::submit(const std::string&, const std::string &url, std::vector<Tab> &result_tabs) {
+ if(url.empty())
+ return PluginResult::OK;
result_tabs.push_back(Tab{nullptr, std::make_unique<YoutubeVideoPage>(program), nullptr});
return PluginResult::OK;
}