From 7efa8f739f20a4c1880e718e7c706e28ced0a28a Mon Sep 17 00:00:00 2001 From: Emilien Devos <4016501+unixfox@users.noreply.github.com> Date: Sun, 6 Oct 2024 21:38:35 +0200 Subject: [PATCH] add invidious_companion option --- config/config.example.yml | 16 ++++++++ src/invidious/config.cr | 3 ++ src/invidious/videos/parser.cr | 71 ++++++++++++++++++---------------- 3 files changed, 56 insertions(+), 34 deletions(-) diff --git a/config/config.example.yml b/config/config.example.yml index e9eebfdec6..cac226c6d9 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -54,6 +54,22 @@ db: ## #signature_server: +## +## Path to the Invidious companion. +## An external program for loading the video streams from YouTube servers. +## +## When this setting is commented out, Invidious companion is not used. +## +## When this setting is configured and "external_port" is used then +## you need to configure Invidious companion routes into your reverse proxy. +## If "external_port" is not configured then Invidious will proxy the requests +## to Invidious companion. +## +## Accepted values: "http(s)://:" +## Default: +## +#invidious_companion: + ######################################### # diff --git a/src/invidious/config.cr b/src/invidious/config.cr index a097b7f1b4..937b0607a7 100644 --- a/src/invidious/config.cr +++ b/src/invidious/config.cr @@ -138,6 +138,9 @@ class Config # poToken for passing bot attestation property po_token : String? = nil + # Invidious companion + property invidious_companion : String? = nil + # Saved cookies in "name1=value1; name2=value2..." format @[YAML::Field(converter: Preferences::StringToCookies)] property cookies : HTTP::Cookies = HTTP::Cookies.new diff --git a/src/invidious/videos/parser.cr b/src/invidious/videos/parser.cr index fb8935d989..46e31d3a51 100644 --- a/src/invidious/videos/parser.cr +++ b/src/invidious/videos/parser.cr @@ -71,8 +71,9 @@ def extract_video_info(video_id : String) # Stop here if video is not a scheduled livestream or # for LOGIN_REQUIRED when videoDetails element is not found because retrying won't help - if !{"LIVE_STREAM_OFFLINE", "LOGIN_REQUIRED"}.any?(playability_status) || - playability_status == "LOGIN_REQUIRED" && !player_response.dig?("videoDetails") + if (!{"LIVE_STREAM_OFFLINE", "LOGIN_REQUIRED"}.any?(playability_status) || + playability_status == "LOGIN_REQUIRED" && !player_response.dig?("videoDetails")) && + CONFIG.invidious_companion.nil? return { "version" => JSON::Any.new(Video::SCHEMA_VERSION.to_i64), "reason" => JSON::Any.new(reason), @@ -96,7 +97,7 @@ def extract_video_info(video_id : String) end # Don't fetch the next endpoint if the video is unavailable. - if {"OK", "LIVE_STREAM_OFFLINE", "LOGIN_REQUIRED"}.any?(playability_status) + if {"OK", "LIVE_STREAM_OFFLINE", "LOGIN_REQUIRED"}.any?(playability_status) || CONFIG.invidious_companion next_response = YoutubeAPI.next({"videoId": video_id, "params": ""}) player_response = player_response.merge(next_response) end @@ -104,42 +105,44 @@ def extract_video_info(video_id : String) params = parse_video_info(video_id, player_response) params["reason"] = JSON::Any.new(reason) if reason - new_player_response = nil + if CONFIG.invidious_companion.nil? + new_player_response = nil - # Second try in case WEB_CREATOR doesn't work with po_token. - # Only trigger if reason found and po_token configured. - if reason && CONFIG.po_token - client_config.client_type = YoutubeAPI::ClientType::WebEmbeddedPlayer - new_player_response = try_fetch_streaming_data(video_id, client_config) - end + # Second try in case WEB_CREATOR doesn't work with po_token. + # Only trigger if reason found and po_token configured. + if reason && CONFIG.po_token + client_config.client_type = YoutubeAPI::ClientType::WebEmbeddedPlayer + new_player_response = try_fetch_streaming_data(video_id, client_config) + end - # Don't use Android client if po_token is passed because po_token doesn't - # work for Android client. - if reason.nil? && CONFIG.po_token.nil? - # Fetch the video streams using an Android client in order to get the - # decrypted URLs and maybe fix throttling issues (#2194). See the - # following issue for an explanation about decrypted URLs: - # https://github.com/TeamNewPipe/NewPipeExtractor/issues/562 - client_config.client_type = YoutubeAPI::ClientType::AndroidTestSuite - new_player_response = try_fetch_streaming_data(video_id, client_config) - end + # Don't use Android client if po_token is passed because po_token doesn't + # work for Android client. + if reason.nil? && CONFIG.po_token.nil? + # Fetch the video streams using an Android client in order to get the + # decrypted URLs and maybe fix throttling issues (#2194). See the + # following issue for an explanation about decrypted URLs: + # https://github.com/TeamNewPipe/NewPipeExtractor/issues/562 + client_config.client_type = YoutubeAPI::ClientType::AndroidTestSuite + new_player_response = try_fetch_streaming_data(video_id, client_config) + end - # Last hope - # Only trigger if reason found or didn't work wth Android client. - # TvHtml5ScreenEmbed now requires sig helper for it to work but doesn't work with po_token. - if reason && CONFIG.po_token.nil? - client_config.client_type = YoutubeAPI::ClientType::TvHtml5ScreenEmbed - new_player_response = try_fetch_streaming_data(video_id, client_config) - end + # Last hope + # Only trigger if reason found or didn't work wth Android client. + # TvHtml5ScreenEmbed now requires sig helper for it to work but doesn't work with po_token. + if reason && CONFIG.po_token.nil? + client_config.client_type = YoutubeAPI::ClientType::TvHtml5ScreenEmbed + new_player_response = try_fetch_streaming_data(video_id, client_config) + end - # Replace player response and reset reason - if !new_player_response.nil? - # Preserve captions & storyboard data before replacement - new_player_response["storyboards"] = player_response["storyboards"] if player_response["storyboards"]? - new_player_response["captions"] = player_response["captions"] if player_response["captions"]? + # Replace player response and reset reason + if !new_player_response.nil? + # Preserve captions & storyboard data before replacement + new_player_response["storyboards"] = player_response["storyboards"] if player_response["storyboards"]? + new_player_response["captions"] = player_response["captions"] if player_response["captions"]? - player_response = new_player_response - params.delete("reason") + player_response = new_player_response + params.delete("reason") + end end {"captions", "playabilityStatus", "playerConfig", "storyboards"}.each do |f|