aboutsummaryrefslogtreecommitdiff
path: root/src/plugins
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2021-06-19 13:13:59 +0200
committerdec05eba <dec05eba@protonmail.com>2021-06-19 13:13:59 +0200
commit85c6c916541968f298badb391b718cdf6d81d332 (patch)
treebf69ea6c1d3a67ec7d41786b5cf883f93b3b87c6 /src/plugins
parent3f66a053942be531fe124cc16bb6b2e0eb94934e (diff)
Support youtube description chapters
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/Youtube.cpp52
1 files changed, 51 insertions, 1 deletions
diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp
index a8c58cb..b45b5fe 100644
--- a/src/plugins/Youtube.cpp
+++ b/src/plugins/Youtube.cpp
@@ -5,6 +5,7 @@
#include "../../include/StringUtils.hpp"
#include "../../include/Scale.hpp"
#include "../../include/Notification.hpp"
+#include "../../include/VideoPlayer.hpp"
#include "../../include/Utils.hpp"
#include <json/reader.h>
extern "C" {
@@ -2019,7 +2020,49 @@ R"END(
return chosen_audio_format->base.url;
}
- PluginResult YoutubeVideoPage::load(std::string &title, std::string &channel_url) {
+ // Returns -1 if timestamp is in an invalid format
+ static int youtube_comment_timestamp_to_seconds(const char *str, size_t size) {
+ if(size > 30)
+ return -1;
+
+ char timestamp[32];
+ memcpy(timestamp, str, size);
+ timestamp[size] = '\0';
+
+ int hours = 0;
+ int minutes = 0;
+ int seconds = 0;
+ if(sscanf(timestamp, "%d:%d:%d", &hours, &minutes, &seconds) == 3)
+ return (hours * 60 * 60) + (minutes * 60) + seconds;
+ if(sscanf(timestamp, "%d:%d", &minutes, &seconds) == 2)
+ return (minutes * 60) + seconds;
+ if(sscanf(timestamp, "%d", &seconds) == 1)
+ return seconds;
+ return -1;
+ }
+
+ static std::vector<MediaChapter> youtube_description_extract_chapters(const std::string &description) {
+ std::vector<MediaChapter> result;
+ string_split(description, '\n', [&result](const char *str, size_t size) {
+ const void *first_space_p = memchr(str, ' ', size);
+ if(!first_space_p)
+ return true;
+
+ int timestamp_seconds = youtube_comment_timestamp_to_seconds(str, (const char*)first_space_p - str);
+ if(timestamp_seconds == -1)
+ return true;
+
+ MediaChapter chapter;
+ chapter.start_seconds = timestamp_seconds;
+ chapter.title.assign((const char*)first_space_p, (str + size) - (const char*)first_space_p);
+ chapter.title = strip(chapter.title);
+ result.push_back(std::move(chapter));
+ return true;
+ });
+ return result;
+ }
+
+ PluginResult YoutubeVideoPage::load(std::string &title, std::string &channel_url, std::vector<MediaChapter> &chapters) {
hls_manifest_url.clear();
video_formats.clear();
audio_formats.clear();
@@ -2084,6 +2127,13 @@ R"END(
const Json::Value &channel_id_json = video_details_json["channelId"];
if(channel_id_json.isString())
channel_url = "https://www.youtube.com/channel/" + channel_id_json.asString();
+
+ const Json::Value &description_json = video_details_json["shortDescription"];
+ if(description_json.isString()) {
+ std::string description = description_json.asString();
+ string_replace_all(description, '+', ' ');
+ chapters = youtube_description_extract_chapters(description);
+ }
}
const Json::Value &playback_tracing_json = json_root["playbackTracking"];