aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2024-04-03 20:49:37 +0200
committerdec05eba <dec05eba@protonmail.com>2024-04-03 20:49:37 +0200
commit4c37a5463c23fcdb165228aae096f2bb0a25b98b (patch)
tree6b863d102551e1d19e93d0864b3f3598f86724b4
parente8cf95fd56bb6cc16f937d06c3554260fd789a92 (diff)
Autoskip youtube sponsors
-rw-r--r--include/VideoPlayer.hpp5
-rw-r--r--plugins/Page.hpp6
-rw-r--r--plugins/Youtube.hpp2
-rw-r--r--src/QuickMedia.cpp29
-rw-r--r--src/VideoPlayer.cpp20
-rw-r--r--src/plugins/Youtube.cpp21
-rw-r--r--video_player/src/main.cpp25
7 files changed, 85 insertions, 23 deletions
diff --git a/include/VideoPlayer.hpp b/include/VideoPlayer.hpp
index 5dc491a..79de8cd 100644
--- a/include/VideoPlayer.hpp
+++ b/include/VideoPlayer.hpp
@@ -66,8 +66,9 @@ namespace QuickMedia {
Error update();
// Returns time in seconds
- Error get_time_in_file(double *result);
- Error get_duration_in_file(double *result);
+ Error get_time_in_file(double *result_seconds);
+ Error set_time_in_file(double time_in_seconds);
+ Error get_duration(double *result);
Error add_subtitle(const std::string &url, const std::string &title, const std::string &lang);
Error cycle_fullscreen();
diff --git a/plugins/Page.hpp b/plugins/Page.hpp
index 97b0340..044759a 100644
--- a/plugins/Page.hpp
+++ b/plugins/Page.hpp
@@ -134,12 +134,18 @@ namespace QuickMedia {
std::string title;
};
+ struct SponsorSegment {
+ double start_seconds;
+ double end_seconds;
+ };
+
struct VideoInfo {
std::string title;
std::string channel_url;
double duration = 0.0;
std::vector<MediaChapter> chapters;
std::string referer;
+ std::vector<SponsorSegment> sponsor_segments;
};
class VideoPage : public Page {
diff --git a/plugins/Youtube.hpp b/plugins/Youtube.hpp
index ac92526..d7008ae 100644
--- a/plugins/Youtube.hpp
+++ b/plugins/Youtube.hpp
@@ -187,7 +187,7 @@ namespace QuickMedia {
void set_watch_progress(int64_t time_pos_sec, int64_t duration_sec) override;
bool autoplay_next_item() override { return goto_next_item; }
private:
- PluginResult parse_video_response(const Json::Value &json_root, std::string &title, std::string &channel_url, std::vector<MediaChapter> &chapters, std::string &err_str);
+ PluginResult parse_video_response(const Json::Value &json_root, std::string &title, std::string &channel_url, std::vector<MediaChapter> &chapters, std::vector<SponsorSegment> &sponsor_segments, std::string &err_str);
void parse_format(const Json::Value &format_json, bool is_adaptive);
void parse_formats(const Json::Value &streaming_data_json);
private:
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index c57540c..ac236b4 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -3252,6 +3252,17 @@ namespace QuickMedia {
return url.find("yt_live_broadcast") != std::string::npos || url.find("manifest/") != std::string::npos;
}
+ static bool find_sponsor_segment_end(const std::vector<SponsorSegment> &sponsor_segments, double current_time_seconds, double &end_time_seconds) {
+ // TODO: Optimize
+ for(const SponsorSegment &sponsor_segments : sponsor_segments) {
+ if(current_time_seconds >= sponsor_segments.start_seconds && current_time_seconds <= sponsor_segments.end_seconds) {
+ end_time_seconds = sponsor_segments.end_seconds;
+ return true;
+ }
+ }
+ return false;
+ }
+
int Program::video_get_max_height() {
if(video_max_height > 0)
return video_max_height;
@@ -3326,13 +3337,15 @@ namespace QuickMedia {
update_duration = false;
successfully_fetched_video_duration = false;
double file_duration = 0.0;
- video_player->get_duration_in_file(&file_duration);
+ video_player->get_duration(&file_duration);
video_info.duration = std::max(video_info.duration, file_duration);
if(video_info.duration > 0.001)
successfully_fetched_video_duration = true;
}
};
+ const bool use_sponsorblock = get_config().youtube.sponsorblock.enable;
+ mgl::Clock sponsorblock_update_clock;
auto update_time_pos_handler = [&](bool force) {
if(!video_player)
return;
@@ -3356,6 +3369,18 @@ namespace QuickMedia {
video_page->set_watch_progress(video_time_pos, video_info.duration);
}
}
+
+ if(use_sponsorblock && (force || sponsorblock_update_clock.get_elapsed_time_seconds() >= 1.0)) {
+ sponsorblock_update_clock.restart();
+ double time_pos = 0.0;
+ if(video_player->get_time_in_file(&time_pos) == VideoPlayer::Error::OK) {
+ double sponsor_end = 0.0;
+ if(find_sponsor_segment_end(video_info.sponsor_segments, time_pos, sponsor_end)) {
+ fprintf(stderr, "Info: skipped sponsor segment\n");
+ video_player->set_time_in_file(sponsor_end);
+ }
+ }
+ }
update_video_duration_handler();
};
@@ -3492,7 +3517,7 @@ namespace QuickMedia {
startup_args.use_youtube_dl = use_youtube_dl && !video_page->is_local();
startup_args.title = video_title;
startup_args.start_time = start_time;
- startup_args.chapters = std::move(video_info.chapters);
+ startup_args.chapters = video_info.chapters;
startup_args.plugin_name = plugin_name;
startup_args.cache_on_disk = !video_page->is_local();
startup_args.referer = video_info.referer;
diff --git a/src/VideoPlayer.cpp b/src/VideoPlayer.cpp
index 5369a07..1049bcf 100644
--- a/src/VideoPlayer.cpp
+++ b/src/VideoPlayer.cpp
@@ -48,6 +48,7 @@ namespace QuickMedia {
static const double READ_TIMEOUT_SEC = 3.0;
static std::string media_chapters_to_ffmetadata_chapters(std::vector<MediaChapter> chapters) {
+ // Required for mpv to display chapters correctly
std::sort(chapters.begin(), chapters.end(), [](const MediaChapter &a, const MediaChapter &b) {
return a.start_seconds < b.start_seconds;
});
@@ -460,7 +461,7 @@ namespace QuickMedia {
return Error::OK;
}
- VideoPlayer::Error VideoPlayer::get_time_in_file(double *result) {
+ VideoPlayer::Error VideoPlayer::get_time_in_file(double *result_seconds) {
Json::Value json_root(Json::objectValue);
json_root["command"] = "time-pos";
@@ -469,11 +470,24 @@ namespace QuickMedia {
if(err != Error::OK)
return err;
- *result = time_pos_json.asDouble();
+ *result_seconds = time_pos_json.asDouble();
return err;
}
- VideoPlayer::Error VideoPlayer::get_duration_in_file(double *result) {
+ VideoPlayer::Error VideoPlayer::set_time_in_file(double time_in_seconds) {
+ Json::Value json_root(Json::objectValue);
+ json_root["command"] = "set-time-pos";
+ json_root["data"] = time_in_seconds;
+
+ Json::Value time_pos_json;
+ Error err = send_command(json_root, &time_pos_json, Json::ValueType::realValue);
+ if(err != Error::OK)
+ return err;
+
+ return err;
+ }
+
+ VideoPlayer::Error VideoPlayer::get_duration(double *result) {
Json::Value json_root(Json::objectValue);
json_root["command"] = "duration";
diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp
index 482843e..bcb6cf6 100644
--- a/src/plugins/Youtube.cpp
+++ b/src/plugins/Youtube.cpp
@@ -3065,7 +3065,7 @@ namespace QuickMedia {
video_details.duration = 0.0;
}
- static void sponsorblock_add_chapters(Page *page, const std::string &url, int min_votes, std::vector<MediaChapter> &chapters) {
+ static void sponsorblock_add_chapters(Page *page, const std::string &url, int min_votes, std::vector<SponsorSegment> &sponsor_segments) {
std::string video_id;
if(!youtube_url_extract_id(url, video_id)) {
fprintf(stderr, "Failed to extract youtube id from %s\n", url.c_str());
@@ -3103,19 +3103,11 @@ namespace QuickMedia {
if(votes_json.asInt() < min_votes)
continue;
- MediaChapter ad_start;
- ad_start.start_seconds = segment_json[0].asDouble();
- ad_start.title = "Ad start";
- chapters.push_back(std::move(ad_start));
-
- MediaChapter ad_end;
- ad_end.start_seconds = segment_json[1].asDouble();
- ad_end.title = "Ad end";
- chapters.push_back(std::move(ad_end));
+ sponsor_segments.push_back({segment_json[0].asDouble(), segment_json[1].asDouble()});
}
}
- PluginResult YoutubeVideoPage::parse_video_response(const Json::Value &json_root, std::string &title, std::string &channel_url, std::vector<MediaChapter> &chapters, std::string &err_str) {
+ PluginResult YoutubeVideoPage::parse_video_response(const Json::Value &json_root, std::string &title, std::string &channel_url, std::vector<MediaChapter> &chapters, std::vector<SponsorSegment> &sponsor_segments, std::string &err_str) {
livestream_url.clear();
video_formats.clear();
audio_formats.clear();
@@ -3123,6 +3115,7 @@ namespace QuickMedia {
title.clear();
channel_url.clear();
chapters.clear();
+ sponsor_segments.clear();
youtube_dl_video_fallback_url.clear();
youtube_dl_audio_fallback_url.clear();
video_details_clear(video_details);
@@ -3202,7 +3195,7 @@ namespace QuickMedia {
fprintf(stderr, "Failed to load youtube video, trying with yt-dlp instead\n");
if(program->youtube_dl_extract_url(url, youtube_dl_video_fallback_url, youtube_dl_audio_fallback_url)) {
if(get_config().youtube.sponsorblock.enable)
- sponsorblock_add_chapters(this, url, get_config().youtube.sponsorblock.min_votes, chapters);
+ sponsorblock_add_chapters(this, url, get_config().youtube.sponsorblock.min_votes, sponsor_segments);
use_youtube_dl_fallback = true;
return PluginResult::OK;
} else {
@@ -3246,7 +3239,7 @@ namespace QuickMedia {
});
if(get_config().youtube.sponsorblock.enable)
- sponsorblock_add_chapters(this, url, get_config().youtube.sponsorblock.min_votes, chapters);
+ sponsorblock_add_chapters(this, url, get_config().youtube.sponsorblock.min_votes, sponsor_segments);
return PluginResult::OK;
}
@@ -3290,7 +3283,7 @@ R"END(
if(download_result != DownloadResult::OK)
continue;
- PluginResult result = parse_video_response(json_root, video_info.title, video_info.channel_url, video_info.chapters, err_str);
+ PluginResult result = parse_video_response(json_root, video_info.title, video_info.channel_url, video_info.chapters, video_info.sponsor_segments, err_str);
if(result == PluginResult::OK) {
err_str.clear();
video_info.duration = video_details.duration;
diff --git a/video_player/src/main.cpp b/video_player/src/main.cpp
index 0342b45..54b5bb4 100644
--- a/video_player/src/main.cpp
+++ b/video_player/src/main.cpp
@@ -51,6 +51,27 @@ static Json::Value handle_json_command_time_pos(mpv_handle *mpv_ctx) {
return response_json;
}
+static Json::Value handle_json_command_set_time_pos(mpv_handle *mpv_ctx, const Json::Value &json_root) {
+ Json::Value response_json(Json::objectValue);
+
+ const Json::Value &data_json = json_root["data"];
+ if(!data_json.isDouble()) {
+ response_json["status"] = "error";
+ response_json["message"] = "expected \"data\" to be a double";
+ return response_json;
+ }
+
+ double time_pos = data_json.asDouble();
+ const int res = mpv_set_property_async(mpv_ctx, 0, "time-pos", MPV_FORMAT_DOUBLE, &time_pos);
+ if(res < 0) {
+ response_json["status"] = "error";
+ response_json["message"] = std::string("set-time-pos: ") + mpv_error_string(res);
+ } else {
+ response_json["status"] = "success";
+ }
+ return response_json;
+}
+
static Json::Value handle_json_command_duration(mpv_handle *mpv_ctx) {
double duration = 0.0;
const int res = mpv_get_property(mpv_ctx, "duration", MPV_FORMAT_DOUBLE, &duration);
@@ -183,6 +204,8 @@ static void handle_json_command(mpv_handle *mpv_ctx, const Json::Value &json_roo
Json::Value response_json;
if(strcmp(command_json.asCString(), "time-pos") == 0) {
response_json = handle_json_command_time_pos(mpv_ctx);
+ } else if(strcmp(command_json.asCString(), "set-time-pos") == 0) {
+ response_json = handle_json_command_set_time_pos(mpv_ctx, json_root);
} else if(strcmp(command_json.asCString(), "duration") == 0) {
response_json = handle_json_command_duration(mpv_ctx);
} else if(strcmp(command_json.asCString(), "sub-add") == 0) {
@@ -192,7 +215,7 @@ static void handle_json_command(mpv_handle *mpv_ctx, const Json::Value &json_roo
} else {
response_json = Json::Value(Json::objectValue);
response_json["status"] = "error";
- response_json["message"] = "invalid command " + command_json.asString() + ", expected time-pos, duration, sub-add or cycle-fullscreen";
+ response_json["message"] = "invalid command " + command_json.asString() + ", expected time-pos, set-time-pos, duration, sub-add or cycle-fullscreen";
}
if(request_id)