From 38202de4f953fca28aa884246ced0aadf0d25a4d Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 25 Jun 2021 12:44:53 +0200 Subject: Add a http server proxy for better youtube downloading (bypassing rate limit cased by http range header). Fix youtube live streams --- src/plugins/Youtube.cpp | 129 ++++++++++++++++++++++++------------------------ 1 file changed, 65 insertions(+), 64 deletions(-) (limited to 'src/plugins/Youtube.cpp') diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index 4df1358..f399687 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -2022,9 +2022,9 @@ R"END( } std::string YoutubeVideoPage::get_video_url(int max_height, bool &has_embedded_audio) { - if(!hls_manifest_url.empty()) { + if(!livestream_url.empty()) { has_embedded_audio = true; - return hls_manifest_url; + return livestream_url; } if(video_formats.empty()) { @@ -2100,6 +2100,7 @@ R"END( return result; } + // TODO: Extract innertube_api_key from response? PluginResult YoutubeVideoPage::get_video_info(const std::string &video_id, Json::Value &json_root) { std::vector additional_args = get_cookies(); @@ -2121,65 +2122,13 @@ R"END( return PluginResult::OK; } - PluginResult YoutubeVideoPage::load(std::string &title, std::string &channel_url, std::vector &chapters) { - hls_manifest_url.clear(); + PluginResult YoutubeVideoPage::parse_video_response(Json::Value &json_root, std::string &title, std::string &channel_url, std::vector &chapters) { + livestream_url.clear(); video_formats.clear(); audio_formats.clear(); - - 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()); - return PluginResult::ERR; - } - - #if 0 - std::string request_data = key_api_request_data; - string_replace_all(request_data, "%VIDEO_ID%", video_id); - - std::vector additional_args = { - { "-H", "Content-Type: application/json" }, - { "-H", "x-youtube-client-name: 1" }, - { "-H", youtube_client_version }, - { "--data-raw", std::move(request_data) } - }; - - std::vector cookies = get_cookies(); - additional_args.insert(additional_args.end(), cookies.begin(), cookies.end()); - - Json::Value json_root; - DownloadResult download_result = download_json(json_root, "https://www.youtube.com/youtubei/v1/player?key=" + api_key + "&gl=US&hl=en", additional_args, true); - if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result); - - if(!json_root.isObject()) - return PluginResult::ERR; - - const Json::Value *streaming_data_json = &json_root["streamingData"]; - if(!streaming_data_json->isObject()) { - const Json::Value &playability_status_json = json_root["playabilityStatus"]; - if(playability_status_json.isObject()) { - const Json::Value &status_json = playability_status_json["status"]; - const Json::Value &reason_json = playability_status_json["reason"]; - fprintf(stderr, "Warning: youtube video loading failed, reason: (status: %s, reason: %s), trying with get_video_info endpoint instead\n", status_json.isString() ? status_json.asCString() : "unknown", reason_json.isString() ? reason_json.asCString() : "unknown"); - - json_root = Json::Value(Json::nullValue); - PluginResult result = get_video_info(video_id, json_root); - if(result != PluginResult::OK) - return result; - - if(!json_root.isObject()) - return PluginResult::ERR; - - streaming_data_json = &json_root["streamingData"]; - if(!streaming_data_json->isObject()) - return PluginResult::ERR; - } - return PluginResult::ERR; - } - #else - Json::Value json_root; - PluginResult result = get_video_info(video_id, json_root); - if(result != PluginResult::OK) - return result; + title.clear(); + channel_url.clear(); + chapters.clear(); if(!json_root.isObject()) return PluginResult::ERR; @@ -2187,13 +2136,22 @@ R"END( const Json::Value *streaming_data_json = &json_root["streamingData"]; if(!streaming_data_json->isObject()) return PluginResult::ERR; - #endif // TODO: Verify if this always works (what about copyrighted live streams?), also what about choosing video quality for live stream? Maybe use mpv --hls-bitrate option? const Json::Value &hls_manifest_url_json = (*streaming_data_json)["hlsManifestUrl"]; - if(hls_manifest_url_json.isString()) { - hls_manifest_url = hls_manifest_url_json.asString(); - } else { + if(hls_manifest_url_json.isString()) + livestream_url = hls_manifest_url_json.asString(); + + /* + const Json::Value &dash_manifest_url_json = (*streaming_data_json)["dashManifestUrl"]; + if(livestream_url.empty() && dash_manifest_url_json.isString()) { + // TODO: mpv cant properly play dash videos. Video goes back and replays. + // So for now return here (get_video_info only hash dash stream and no hls stream) which will fallback to the player youtube endpoint which has hls stream. + return PluginResult::ERR; + } + */ + + if(livestream_url.empty()) { parse_formats(*streaming_data_json); if(video_formats.empty() && audio_formats.empty()) return PluginResult::ERR; @@ -2257,6 +2215,49 @@ R"END( return PluginResult::OK; } + PluginResult YoutubeVideoPage::load(std::string &title, std::string &channel_url, std::vector &chapters) { + livestream_url.clear(); + video_formats.clear(); + audio_formats.clear(); + + 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()); + return PluginResult::ERR; + } + + Json::Value json_root; + PluginResult result = get_video_info(video_id, json_root); + if(result != PluginResult::OK) + return result; + + // Getting streams might fail for some videos that do not allow videos to be embedded when using get_video_info endpoint. + // TODO: Does that means for videos that do not allow to be embedded and are age restricted wont work? + result = parse_video_response(json_root, title, channel_url, chapters); + if(result == PluginResult::OK) { + return PluginResult::OK; + } else { + std::string request_data = key_api_request_data; + string_replace_all(request_data, "%VIDEO_ID%", video_id); + + std::vector additional_args = { + { "-H", "Content-Type: application/json" }, + { "-H", "x-youtube-client-name: 1" }, + { "-H", youtube_client_version }, + { "--data-raw", std::move(request_data) } + }; + + std::vector cookies = get_cookies(); + additional_args.insert(additional_args.end(), cookies.begin(), cookies.end()); + + Json::Value json_root; + DownloadResult download_result = download_json(json_root, "https://www.youtube.com/youtubei/v1/player?key=" + api_key + "&gl=US&hl=en", additional_args, true); + if(download_result != DownloadResult::OK) return download_result_to_plugin_result(download_result); + + return parse_video_response(json_root, title, channel_url, chapters); + } + } + void YoutubeVideoPage::mark_watched() { if(playback_url.empty()) { fprintf(stderr, "Failed to mark video as watched because playback_url is empty\n"); @@ -2317,7 +2318,7 @@ R"END( continue; if(is_adaptive) { - // TODO: Fix. Some streams use sq/ instead of index + // TODO: Fix. Some streams use &sq=num instead of index const Json::Value &index_range_json = format["indexRange"]; if(index_range_json.isNull()) { fprintf(stderr, "Ignoring adaptive stream without indexRange\n"); -- cgit v1.2.3