Compare commits

...

8 commits

Author SHA1 Message Date
e1a1bf0f79
Feat: Experimental support for external videoplayback proxies 2024-09-27 19:09:50 -03:00
7ded7455b0
Logger: Add color support for different log levels
All checks were successful
Invidious CI / build (push) Successful in 7m41s
2024-09-19 21:53:01 -03:00
53f0a5b1e0
fixup! SigHelper: Handle all errors, lower time between retries
All checks were successful
Invidious CI / build (push) Successful in 8m43s
2024-09-19 15:17:04 -03:00
34a11fe1bb
SigHelper: Handle all errors, lower time between retries
All checks were successful
Invidious CI / build (push) Successful in 9m1s
2024-09-19 14:43:47 -03:00
2db9396dea
SigHelper: Reconnect to signature helper
All checks were successful
Invidious CI / build (push) Successful in 14m43s
2024-09-18 18:14:28 -03:00
Emilien Devos
0c3e5baab0
decrease buffer seconds for saving bandwidth
All checks were successful
Invidious CI / build (push) Successful in 7m43s
2024-09-17 18:02:09 -03:00
73ba53a327
fixup! Feat: backend supports with cookies
All checks were successful
Invidious CI / build (push) Successful in 23m13s
2024-09-16 23:52:14 -03:00
00bcf53d0f
Feat: backend supports with cookies
All checks were successful
Invidious CI / build (push) Successful in 13m45s
2024-09-15 14:42:06 -03:00
13 changed files with 130 additions and 13 deletions

View file

@ -51,6 +51,9 @@ videojs.Vhs.xhr.beforeRequest = function(options) {
return options;
};
videojs.Vhs.GOAL_BUFFER_LENGTH = 20;
videojs.Vhs.MAX_GOAL_BUFFER_LENGTH = 30;
var player = videojs('player', options);
player.on('error', function () {

View file

@ -222,6 +222,15 @@ https_only: false
##
#log_level: Info
##
## Enables colors in logs. Useful for debugging purposes
## This is overridden if "-k" or "--colorize"
## are passed on the command line.
##
## Accepted values: true, false
## Default: false
##
#colorize_logs: false
# -----------------------------
# Features

View file

@ -115,6 +115,9 @@ Kemal.config.extra_options do |parser|
parser.on("-l LEVEL", "--log-level=LEVEL", "Log level, one of #{LogLevel.values} (default: #{CONFIG.log_level})") do |log_level|
CONFIG.log_level = LogLevel.parse(log_level)
end
parser.on("-k", "--colorize", "Colorize logs") do
CONFIG.colorize_logs = true
end
parser.on("-v", "--version", "Print version") do
puts SOFTWARE.to_pretty_json
exit
@ -131,7 +134,7 @@ if CONFIG.output.upcase != "STDOUT"
FileUtils.mkdir_p(File.dirname(CONFIG.output))
end
OUTPUT = CONFIG.output.upcase == "STDOUT" ? STDOUT : File.open(CONFIG.output, mode: "a")
LOGGER = Invidious::LogHandler.new(OUTPUT, CONFIG.log_level)
LOGGER = Invidious::LogHandler.new(OUTPUT, CONFIG.log_level, CONFIG.colorize_logs)
# Check table integrity
Invidious::Database.check_integrity(CONFIG)

View file

@ -66,6 +66,8 @@ class Config
property output : String = "STDOUT"
# Default log level, valid YAML values are ints and strings, see src/invidious/helpers/logger.cr
property log_level : LogLevel = LogLevel::Info
# Enables colors in logs. Useful for debugging purposes
property colorize_logs : Bool = false
# Database configuration with separate parameters (username, hostname, etc)
property db : DBConfig? = nil
@ -167,6 +169,11 @@ class Config
# The max resolution the Instance can offer
property max_dash_resolution : Int32?
# List of names of the backends
property backends : Array(String) = [] of String
property external_videoplayback_proxy : String?
# Materialious redirects
property materialious_domain : String?

View file

@ -1,3 +1,5 @@
require "colorize"
enum LogLevel
All = 0
Trace = 1
@ -10,7 +12,7 @@ enum LogLevel
end
class Invidious::LogHandler < Kemal::BaseLogHandler
def initialize(@io : IO = STDOUT, @level = LogLevel::Debug)
def initialize(@io : IO = STDOUT, @level = LogLevel::Debug, @color : Bool = true)
end
def call(context : HTTP::Server::Context)
@ -39,10 +41,23 @@ class Invidious::LogHandler < Kemal::BaseLogHandler
@io.flush
end
def color(level)
case level
when LogLevel::Trace then :cyan
when LogLevel::Debug then :green
when LogLevel::Info then :white
when LogLevel::Warn then :yellow
when LogLevel::Error then :red
when LogLevel::Fatal then :magenta
else :default
end
end
{% for level in %w(trace debug info warn error fatal) %}
def {{level.id}}(message : String)
if LogLevel::{{level.id.capitalize}} >= @level
puts("#{Time.utc} [{{level.id}}] #{message}")
puts("#{Time.utc} [{{level.id}}] #{message}".colorize(color(LogLevel::{{level.id.capitalize}})).toggle(@color))
end
end
{% end %}

View file

@ -176,7 +176,9 @@ module Invidious::SigHelper
@conn : Connection
def initialize(uri_or_path)
@uri_or_path : String
def initialize(@uri_or_path)
@conn = Connection.new(uri_or_path)
listen
end
@ -186,10 +188,27 @@ module Invidious::SigHelper
LOGGER.debug("SigHelper: Multiplexor listening")
# TODO: reopen socket if unexpectedly closed
spawn do
loop do
receive_data
begin
receive_data
# Handle all errors
rescue ex
LOGGER.info("SigHelper: Connection to helper died with '#{ex.message}' trying to reconnect...")
# We close the socket because for some reason is not closed.
@conn.close
loop do
begin
@conn = Connection.new(@uri_or_path)
LOGGER.info("SigHelper: Reconnected to SigHelper!")
rescue ex
LOGGER.debug("SigHelper: Reconnection to helper unsuccessful with error '#{ex.message}' retrying")
sleep 0.5
next
end
break if !@conn.closed?
end
end
Fiber.yield
end
end
@ -306,6 +325,11 @@ module Invidious::SigHelper
return @socket.closed?
end
def remote_address : Socket::IPAddress
return @socket.remote_address
end
def close : Nil
@socket.close if !@socket.closed?
end

View file

@ -35,7 +35,11 @@ module Invidious::Routes::API::Manifest
if local
uri = URI.parse(url)
url = "#{HOST_URL}#{uri.request_target}host/#{uri.host}/"
if CONFIG.external_videoplayback_proxy
url = "#{CONFIG.external_videoplayback_proxy}#{uri.request_target}host/#{uri.host}/"
else
url = "#{HOST_URL}#{uri.request_target}host/#{uri.host}/"
end
end
"<BaseURL>#{url}</BaseURL>"
@ -48,21 +52,24 @@ module Invidious::Routes::API::Manifest
if local
adaptive_fmts.each do |fmt|
fmt["url"] = JSON::Any.new("#{HOST_URL}#{URI.parse(fmt["url"].as_s).request_target}")
if CONFIG.external_videoplayback_proxy
fmt["url"] = JSON::Any.new("#{CONFIG.external_videoplayback_proxy}#{URI.parse(fmt["url"].as_s).request_target}")
else
fmt["url"] = JSON::Any.new("#{HOST_URL}#{URI.parse(fmt["url"].as_s).request_target}")
end
end
end
audio_streams = video.audio_streams.sort_by { |stream| {stream["bitrate"].as_i} }.reverse!
video_streams = video.video_streams.sort_by { |stream| {stream["width"].as_i, stream["fps"].as_i} }.reverse!
# Removes all the resolutions with a height higher than CONFIG.max_dash_resolution
# Removes all the resolutions with a height higher than CONFIG.max_dash_resolution
if CONFIG.max_dash_resolution
video_streams.reject! do |z|
(z["height"].as_i > CONFIG.max_dash_resolution.not_nil!) if z["height"]?
end
end
manifest = XML.build(indent: " ", encoding: "UTF-8") do |xml|
xml.element("MPD", "xmlns": "urn:mpeg:dash:schema:mpd:2011",
"profiles": "urn:mpeg:dash:profile:full:2011", minBufferTime: "PT1.5S", type: "static",

View file

@ -28,6 +28,12 @@ module Invidious::Routes::BeforeAll
extra_media_csp = ""
end
if CONFIG.external_videoplayback_proxy
external_videoplayback_proxy = " #{CONFIG.external_videoplayback_proxy}"
else
external_videoplayback_proxy = ""
end
# Only allow the pages at /embed/* to be embedded
if env.request.resource.starts_with?("/embed")
frame_ancestors = "'self' file: http: https:"
@ -43,7 +49,7 @@ module Invidious::Routes::BeforeAll
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data:",
"font-src 'self' data:",
"connect-src 'self'",
"connect-src 'self'" + external_videoplayback_proxy,
"manifest-src 'self'",
"media-src 'self' blob:" + extra_media_csp,
"child-src 'self' blob:",

View file

@ -0,0 +1,18 @@
{% skip_file if flag?(:api_only) %}
module Invidious::Routes::BackendSwitcher
def self.switch(env)
referer = get_referer(env)
backend_id = env.params.query["backend_id"]
# Checks if there is any alternative domain, like a second domain name,
# TOR or I2P address
if alt = CONFIG.alternative_domains.index(env.request.headers["Host"])
env.response.cookies["SERVER_ID"] = Invidious::User::Cookies.server_id(CONFIG.alternative_domains[alt], backend_id)
else
env.response.cookies["SERVER_ID"] = Invidious::User::Cookies.server_id(CONFIG.domain, backend_id)
end
env.redirect referer
end
end

View file

@ -120,7 +120,7 @@ module Invidious::Routes::Watch
fmt_stream = video.fmt_stream
adaptive_fmts = video.adaptive_fmts
# Removes all the resolutions with a height higher than CONFIG.max_dash_resolution
# Removes all the resolutions with a height higher than CONFIG.max_dash_resolution
if CONFIG.max_dash_resolution
adaptive_fmts.reject! do |z|
(z["height"].as_i > CONFIG.max_dash_resolution.not_nil!) if z["height"]?
@ -135,7 +135,7 @@ module Invidious::Routes::Watch
video_streams = video.video_streams
audio_streams = video.audio_streams
# Removes all the resolutions with a height higher than CONFIG.max_dash_resolution
# Removes all the resolutions with a height higher than CONFIG.max_dash_resolution
if CONFIG.max_dash_resolution
video_streams.reject! do |z|
(z["height"].as_i > CONFIG.max_dash_resolution.not_nil!) if z["height"]?

View file

@ -21,6 +21,7 @@ module Invidious::Routing
get "/privacy", Routes::Misc, :privacy
get "/licenses", Routes::Misc, :licenses
get "/redirect", Routes::Misc, :cross_instance_redirect
get "/switchbackend", Routes::BackendSwitcher, :switch
self.register_channel_routes
self.register_watch_routes

View file

@ -45,5 +45,18 @@ struct Invidious::User
samesite: HTTP::Cookie::SameSite::Lax
)
end
# Server ID (SERVER_ID) cookie used for Sticky Sessions
# Parameter "domain" comes from the global config
def server_id(domain : String?, server_id) : HTTP::Cookie
return HTTP::Cookie.new(
name: "SERVER_ID",
domain: domain,
value: server_id,
secure: false,
http_only: true,
samesite: HTTP::Cookie::SameSite::Lax
)
end
end
end

View file

@ -104,6 +104,17 @@
</div>
</div>
<% if CONFIG.backends %>
<div class="h-box">
<b>Switch Backend:</b>
<% CONFIG.backends.each do | backend | %>
<a href="/switchbackend?backend_id=<%= backend %>">
Backend<%= HTML.escape(backend) %>
</a> |
<% end %>
</div>
<% end %>
<% if CONFIG.banner %>
<div class="h-box">
<h3><%= CONFIG.banner %></h3>