aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/Youtube.cpp
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2021-06-25 12:44:53 +0200
committerdec05eba <dec05eba@protonmail.com>2021-06-25 12:44:53 +0200
commit38202de4f953fca28aa884246ced0aadf0d25a4d (patch)
tree7a0a35a32404f1929238444d13a6c626856cc791 /src/plugins/Youtube.cpp
parent738f2b1a89a5445a1f0f94229f2fc0637b7c4e71 (diff)
Add a http server proxy for better youtube downloading (bypassing rate limit cased by http range header). Fix youtube live streams
Diffstat (limited to 'src/plugins/Youtube.cpp')
-rw-r--r--src/plugins/Youtube.cpp129
1 files changed, 65 insertions, 64 deletions
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<CommandArg> 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<MediaChapter> &chapters) {
- hls_manifest_url.clear();
+ PluginResult YoutubeVideoPage::parse_video_response(Json::Value &json_root, std::string &title, std::string &channel_url, std::vector<MediaChapter> &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<CommandArg> 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<CommandArg> 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<MediaChapter> &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<CommandArg> 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<CommandArg> 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");