From 6443ce7df2d690c5a03dc68cb6866f5d7d4e3fba Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 11 Jun 2021 06:33:35 +0200 Subject: Make get_related_videos async, readd mark-watched --- src/plugins/Youtube.cpp | 135 +++++++++++++++++++++++++++++++----------------- 1 file changed, 88 insertions(+), 47 deletions(-) (limited to 'src/plugins/Youtube.cpp') diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index 9cec69c..04c29e0 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -13,6 +13,7 @@ extern "C" { #include #include #include +#include namespace QuickMedia { bool youtube_url_extract_id(const std::string &youtube_url, std::string &youtube_video_id) { @@ -1691,7 +1692,7 @@ namespace QuickMedia { return ""; } - BodyItems YoutubeVideoPage::get_related_media(const std::string &url, std::string &channel_url) { + BodyItems YoutubeVideoPage::get_related_media(const std::string &url) { BodyItems result_items; std::string modified_url = remove_index_from_playlist_url(url); @@ -1721,56 +1722,12 @@ namespace QuickMedia { if(!json_item.isObject()) continue; - const Json::Value &player_response_json = json_item["playerResponse"]; - if(channel_url.empty()) { - if(player_response_json.isObject()) { - const Json::Value &video_details_json = player_response_json["videoDetails"]; - if(video_details_json.isObject()) { - 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(); - } - } - } - if(xsrf_token.empty()) { const Json::Value &xsrf_token_json = json_item["xsrf_token"]; if(xsrf_token_json.isString()) xsrf_token = xsrf_token_json.asString(); } - if(player_response_json.isObject()) { - const Json::Value &playback_tracing_json = player_response_json["playbackTracking"]; - if(playback_tracing_json.isObject()) { - if(playback_url.empty()) { - const Json::Value &video_stats_playback_url_json = playback_tracing_json["videostatsPlaybackUrl"]; - if(video_stats_playback_url_json.isObject()) { - const Json::Value &base_url_json = video_stats_playback_url_json["baseUrl"]; - if(base_url_json.isString()) - playback_url = base_url_json.asString(); - } - } - - if(watchtime_url.empty()) { - const Json::Value &video_stats_watchtime_url_json = playback_tracing_json["videostatsWatchtimeUrl"]; - if(video_stats_watchtime_url_json.isObject()) { - const Json::Value &base_url_json = video_stats_watchtime_url_json["baseUrl"]; - if(base_url_json.isString()) - watchtime_url = base_url_json.asString(); - } - } - - if(tracking_url.empty()) { - const Json::Value &p_tracking_url_json = playback_tracing_json["ptrackingUrl"]; - if(p_tracking_url_json.isObject()) { - const Json::Value &base_url_json = p_tracking_url_json["baseUrl"]; - if(base_url_json.isString()) - tracking_url = base_url_json.asString(); - } - } - } - } - const Json::Value &response_json = json_item["response"]; if(!response_json.isObject()) continue; @@ -1900,7 +1857,7 @@ namespace QuickMedia { return audio_formats.front().base.url; } - PluginResult YoutubeVideoPage::load() { + PluginResult YoutubeVideoPage::load(std::string &channel_url) { video_formats.clear(); audio_formats.clear(); @@ -1919,7 +1876,7 @@ namespace QuickMedia { additional_args.insert(additional_args.end(), cookies.begin(), cookies.end()); std::string response; - DownloadResult download_result = download_to_string("https://www.youtube.com/get_video_info?html5=1&video_id=" + video_id + "&eurl=https://www.youtube.googleapis.com/v/" + video_id, response, std::move(additional_args), true); // TODO: true? + DownloadResult download_result = download_to_string("https://www.youtube.com/get_video_info?html5=1&video_id=" + video_id + "&eurl=https://www.youtube.googleapis.com/v/" + video_id, response, std::move(additional_args), true); if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result); std::string player_response_param = url_extract_param(response, "player_response"); @@ -1946,6 +1903,43 @@ namespace QuickMedia { if(video_formats.empty() && audio_formats.empty()) return PluginResult::ERR; + const Json::Value &video_details_json = json_root["videoDetails"]; + if(video_details_json.isObject()) { + 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 &playback_tracing_json = json_root["playbackTracking"]; + if(playback_tracing_json.isObject()) { + if(playback_url.empty()) { + const Json::Value &video_stats_playback_url_json = playback_tracing_json["videostatsPlaybackUrl"]; + if(video_stats_playback_url_json.isObject()) { + const Json::Value &base_url_json = video_stats_playback_url_json["baseUrl"]; + if(base_url_json.isString()) + playback_url = base_url_json.asString(); + } + } + + if(watchtime_url.empty()) { + const Json::Value &video_stats_watchtime_url_json = playback_tracing_json["videostatsWatchtimeUrl"]; + if(video_stats_watchtime_url_json.isObject()) { + const Json::Value &base_url_json = video_stats_watchtime_url_json["baseUrl"]; + if(base_url_json.isString()) + watchtime_url = base_url_json.asString(); + } + } + + if(tracking_url.empty()) { + const Json::Value &p_tracking_url_json = playback_tracing_json["ptrackingUrl"]; + if(p_tracking_url_json.isObject()) { + const Json::Value &base_url_json = p_tracking_url_json["baseUrl"]; + if(base_url_json.isString()) + tracking_url = base_url_json.asString(); + } + } + } + std::sort(video_formats.begin(), video_formats.end(), [](const YoutubeVideoFormat &format1, const YoutubeVideoFormat &format2) { return format1.base.bitrate > format2.base.bitrate; }); @@ -1957,6 +1951,53 @@ namespace QuickMedia { return PluginResult::OK; } + static bool generate_random_characters(char *buffer, int buffer_size, const char *alphabet, size_t alphabet_size) { + int fd = open("/dev/urandom", O_RDONLY); + if(fd == -1) { + perror("/dev/urandom"); + return false; + } + + if(read(fd, buffer, buffer_size) < buffer_size) { + fprintf(stderr, "Failed to read %d bytes from /dev/urandom\n", buffer_size); + close(fd); + return false; + } + + for(int i = 0; i < buffer_size; ++i) { + unsigned char c = *(unsigned char*)&buffer[i]; + buffer[i] = alphabet[c % alphabet_size]; + } + close(fd); + return true; + } + + void YoutubeVideoPage::mark_watched() { + if(playback_url.empty()) { + fprintf(stderr, "Failed to mark video as watched because playback_url is empty\n"); + return; + } + + std::vector additional_args = { + { "-H", "x-youtube-client-name: 1" }, + { "-H", "x-youtube-client-version: 2.20200626.03.00" } + }; + + std::vector cookies = get_cookies(); + additional_args.insert(additional_args.end(), cookies.begin(), cookies.end()); + + std::string cpn; + cpn.resize(16); + generate_random_characters(cpn.data(), cpn.size(), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_", 64); + + std::string response; + DownloadResult download_result = download_to_string(playback_url + "&ver=2&cpn=" + cpn, response, std::move(additional_args), true); + if(download_result != DownloadResult::OK) { + fprintf(stderr, "Failed to mark video as watched because http request failed\n"); + return; + } + } + static bool parse_cipher_format(const Json::Value &format, YoutubeFormat &youtube_format) { std::map cipher_params; const Json::Value &cipher_json = format["cipher"]; -- cgit v1.2.3