From 73bf956af5ff2595c4840397924127e7cf329598 Mon Sep 17 00:00:00 2001
From: epicsam123 <92618898+epicsam123@users.noreply.github.com>
Date: Wed, 19 Feb 2025 21:08:45 -0500
Subject: [PATCH 01/52] captions: provide "w", "o", "-", "+" keydowns for
player from YT
---
assets/css/player.css | 18 +++++++++++++++---
assets/js/player.js | 38 ++++++++++++++++++++++++++++++++++++++
2 files changed, 53 insertions(+), 3 deletions(-)
diff --git a/assets/css/player.css b/assets/css/player.css
index 9cb400ad9..028d5631d 100644
--- a/assets/css/player.css
+++ b/assets/css/player.css
@@ -71,8 +71,10 @@
padding-top: 2em
}
-.video-js.player-style-youtube .vjs-progress-control .vjs-progress-holder, .video-js.player-style-youtube .vjs-progress-control {height: 5px;
-margin-bottom: 10px;}
+.video-js.player-style-youtube .vjs-progress-control .vjs-progress-holder, .video-js.player-style-youtube .vjs-progress-control {
+ height: 5px;
+ margin-bottom: 10px;
+}
ul.vjs-menu-content::-webkit-scrollbar {
display: none;
@@ -82,10 +84,20 @@ ul.vjs-menu-content::-webkit-scrollbar {
cursor: none;
}
+/* Customizable CSS in player.js */
+.vjs-text-track-display > div > div
+{
+ background-color: rgba(0, 0, 0, 0); /* caption window background: toggle with "w" event */
+}
+
+/* Customizable CSS in player.js */
.video-js .vjs-text-track-display > div > div > div {
- background-color: rgba(0, 0, 0, 0.75) !important;
+ font-size: 27px !important; /* Toggle with "-/=" event */
+ background-color: rgba(0, 0, 0, 0.75) !important; /* caption background: toggle with "w" event */
+ color: rgb(255, 255, 255, 1) !important; /* caption text: toggle with "o" event */
border-radius: 9px !important;
padding: 5px !important;
+ line-height: 1.5 !important;
}
.vjs-play-control,
diff --git a/assets/js/player.js b/assets/js/player.js
index 353a52963..c74a68a4b 100644
--- a/assets/js/player.js
+++ b/assets/js/player.js
@@ -2,9 +2,16 @@
var player_data = JSON.parse(document.getElementById('player_data').textContent);
var video_data = JSON.parse(document.getElementById('video_data').textContent);
+var player_css = [...Array.from(document.styleSheets).find(sS => sS.href?.includes('player.css')).cssRules]
+var caption_background_css = player_css.find(rule => rule.selectorText === '.vjs-text-track-display > div > div');
+var caption_text_css = player_css.find(rule => rule.selectorText === '.video-js .vjs-text-track-display > div > div > div');
+
var options = {
liveui: true,
playbackRates: [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0],
+ captionSizes: ['22px', '27px', '32px', '37px'],
+ captionBackground: [0, 0.5, 0.8, 1].map(a => 'rgba(0, 0, 0, ' + a + ')'),
+ captionOpacity: [0.4, 0.7, 1].map(a => 'rgba(255, 255, 255, ' + a + ')'),
controlBar: {
children: [
'playToggle',
@@ -591,6 +598,31 @@ function increase_playback_rate(steps) {
player.playbackRate(options.playbackRates[newIndex]);
}
+function increase_caption_size(steps) {
+ const maxIndex = options.captionSizes.length - 1;
+ const font_size = caption_text_css.style.getPropertyValue('font-size');
+ const curIndex = options.captionSizes.indexOf(font_size);
+ let newIndex = curIndex + steps;
+ newIndex = helpers.clamp(newIndex, 0, maxIndex);
+ caption_text_css.style.setProperty('font-size', options.captionSizes[newIndex], 'important');
+}
+
+function toggle_caption_window() {
+ const numOptions = options.captionBackground.length;
+ const backgroundColor = caption_background_css.style.getPropertyValue('background-color');
+ const curIndex = options.captionBackground.indexOf(backgroundColor);
+ const newIndex = (curIndex + 1) % numOptions;
+ caption_background_css.style.setProperty('background-color', options.captionBackground[newIndex], 'important');
+}
+
+function toggle_caption_opacity() {
+ const numOptions = options.captionOpacity.length;
+ const opacity = caption_text_css.style.getPropertyValue('color');
+ const curIndex = options.captionOpacity.indexOf(opacity);
+ const newIndex = (curIndex + 1) % numOptions;
+ caption_text_css.style.setProperty('color', options.captionOpacity[newIndex], 'important');
+}
+
addEventListener('keydown', function (e) {
if (e.target.tagName.toLowerCase() === 'input') {
// Ignore input when focus is on certain elements, e.g. form fields.
@@ -686,6 +718,12 @@ addEventListener('keydown', function (e) {
case '>': action = increase_playback_rate.bind(this, 1); break;
case '<': action = increase_playback_rate.bind(this, -1); break;
+
+ case '=': action = increase_caption_size.bind(this, 1); break;
+ case '-': action = increase_caption_size.bind(this, -1); break;
+
+ case 'w': action = toggle_caption_window; break;
+ case 'o': action = toggle_caption_opacity; break;
default:
console.info('Unhandled key down event: %s:', decoratedKey, e);
From bc3b3f6d69977e799f4d4e99d5c0283916d0ca83 Mon Sep 17 00:00:00 2001
From: epicsam123 <92618898+epicsam123@users.noreply.github.com>
Date: Thu, 20 Mar 2025 10:09:43 -0400
Subject: [PATCH 02/52] updated caption features to use videojs interface
---
assets/css/player.css | 11 +--------
assets/js/player.js | 52 ++++++++++++++++++++++++-------------------
2 files changed, 30 insertions(+), 33 deletions(-)
diff --git a/assets/css/player.css b/assets/css/player.css
index 028d5631d..60f3ce736 100644
--- a/assets/css/player.css
+++ b/assets/css/player.css
@@ -84,17 +84,8 @@ ul.vjs-menu-content::-webkit-scrollbar {
cursor: none;
}
-/* Customizable CSS in player.js */
-.vjs-text-track-display > div > div
-{
- background-color: rgba(0, 0, 0, 0); /* caption window background: toggle with "w" event */
-}
-
-/* Customizable CSS in player.js */
.video-js .vjs-text-track-display > div > div > div {
- font-size: 27px !important; /* Toggle with "-/=" event */
- background-color: rgba(0, 0, 0, 0.75) !important; /* caption background: toggle with "w" event */
- color: rgb(255, 255, 255, 1) !important; /* caption text: toggle with "o" event */
+ background-color: rgba(0, 0, 0, 0.75) !important;
border-radius: 9px !important;
padding: 5px !important;
line-height: 1.5 !important;
diff --git a/assets/js/player.js b/assets/js/player.js
index c74a68a4b..dce432cb1 100644
--- a/assets/js/player.js
+++ b/assets/js/player.js
@@ -2,16 +2,12 @@
var player_data = JSON.parse(document.getElementById('player_data').textContent);
var video_data = JSON.parse(document.getElementById('video_data').textContent);
-var player_css = [...Array.from(document.styleSheets).find(sS => sS.href?.includes('player.css')).cssRules]
-var caption_background_css = player_css.find(rule => rule.selectorText === '.vjs-text-track-display > div > div');
-var caption_text_css = player_css.find(rule => rule.selectorText === '.video-js .vjs-text-track-display > div > div > div');
-
var options = {
liveui: true,
playbackRates: [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0],
- captionSizes: ['22px', '27px', '32px', '37px'],
- captionBackground: [0, 0.5, 0.8, 1].map(a => 'rgba(0, 0, 0, ' + a + ')'),
- captionOpacity: [0.4, 0.7, 1].map(a => 'rgba(255, 255, 255, ' + a + ')'),
+ fontPercent: [0.5, 0.75, 1.25, 1.5, 1.75, 2, 3, 4],
+ windowOpacity: ['0', '0.5', '1'],
+ textOpacity: ['0.5', '1'],
controlBar: {
children: [
'playToggle',
@@ -543,9 +539,9 @@ const toggle_captions = (function () {
bindChange('off');
track.mode = mode;
setTimeout(function () {
- bindChange('on');
+ bindChange('on');
}, 0);
- }
+ }
bindChange('on');
return function () {
@@ -586,6 +582,13 @@ const toggle_captions = (function () {
};
})();
+// For real-time updates to captions (if currently showing)
+function update_captions() {
+ if (document.body.querySelector('.vjs-text-track-cue')) {
+ toggle_captions(); toggle_captions();
+ }
+}
+
function toggle_fullscreen() {
player.isFullscreen() ? player.exitFullscreen() : player.requestFullscreen();
}
@@ -599,28 +602,31 @@ function increase_playback_rate(steps) {
}
function increase_caption_size(steps) {
- const maxIndex = options.captionSizes.length - 1;
- const font_size = caption_text_css.style.getPropertyValue('font-size');
- const curIndex = options.captionSizes.indexOf(font_size);
+ const maxIndex = options.fontPercent.length - 1;
+ const fontPercent = player.textTrackSettings.getValues().fontPercent || 1.25;
+ const curIndex = options.fontPercent.indexOf(fontPercent);
let newIndex = curIndex + steps;
newIndex = helpers.clamp(newIndex, 0, maxIndex);
- caption_text_css.style.setProperty('font-size', options.captionSizes[newIndex], 'important');
+ player.textTrackSettings.setValues({ fontPercent: options.fontPercent[newIndex] });
+ update_captions();
}
function toggle_caption_window() {
- const numOptions = options.captionBackground.length;
- const backgroundColor = caption_background_css.style.getPropertyValue('background-color');
- const curIndex = options.captionBackground.indexOf(backgroundColor);
+ const numOptions = options.windowOpacity.length;
+ const windowOpacity = player.textTrackSettings.getValues().windowOpacity || '0';
+ const curIndex = options.windowOpacity.indexOf(windowOpacity);
const newIndex = (curIndex + 1) % numOptions;
- caption_background_css.style.setProperty('background-color', options.captionBackground[newIndex], 'important');
+ player.textTrackSettings.setValues({ windowOpacity: options.windowOpacity[newIndex] });
+ update_captions();
}
-
-function toggle_caption_opacity() {
- const numOptions = options.captionOpacity.length;
- const opacity = caption_text_css.style.getPropertyValue('color');
- const curIndex = options.captionOpacity.indexOf(opacity);
+
+ function toggle_caption_opacity() {
+ const numOptions = options.textOpacity.length;
+ const textOpacity = player.textTrackSettings.getValues().textOpacity || '1';
+ const curIndex = options.textOpacity.indexOf(textOpacity);
const newIndex = (curIndex + 1) % numOptions;
- caption_text_css.style.setProperty('color', options.captionOpacity[newIndex], 'important');
+ player.textTrackSettings.setValues({ textOpacity: options.textOpacity[newIndex] });
+ update_captions();
}
addEventListener('keydown', function (e) {
From e67a30b124debf30363e5e576f089b26c46f7c93 Mon Sep 17 00:00:00 2001
From: epicsam123 <92618898+epicsam123@users.noreply.github.com>
Date: Thu, 20 Mar 2025 10:29:26 -0400
Subject: [PATCH 03/52] formatting
---
assets/js/player.js | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/assets/js/player.js b/assets/js/player.js
index dce432cb1..d6f2ec646 100644
--- a/assets/js/player.js
+++ b/assets/js/player.js
@@ -539,9 +539,9 @@ const toggle_captions = (function () {
bindChange('off');
track.mode = mode;
setTimeout(function () {
- bindChange('on');
+ bindChange('on');
}, 0);
- }
+ }
bindChange('on');
return function () {
@@ -584,9 +584,9 @@ const toggle_captions = (function () {
// For real-time updates to captions (if currently showing)
function update_captions() {
- if (document.body.querySelector('.vjs-text-track-cue')) {
- toggle_captions(); toggle_captions();
- }
+ if (document.body.querySelector('.vjs-text-track-cue')) {
+ toggle_captions(); toggle_captions();
+ }
}
function toggle_fullscreen() {
@@ -620,7 +620,7 @@ function toggle_caption_window() {
update_captions();
}
- function toggle_caption_opacity() {
+function toggle_caption_opacity() {
const numOptions = options.textOpacity.length;
const textOpacity = player.textTrackSettings.getValues().textOpacity || '1';
const curIndex = options.textOpacity.indexOf(textOpacity);
From bef2d7b6b515bc90d8a58e3fa9776ab52fc48039 Mon Sep 17 00:00:00 2001
From: Fijxu
Date: Thu, 15 May 2025 01:07:40 -0400
Subject: [PATCH 04/52] CI: Use public ARM64 Github actions runners for ARM64
builds.
Currently, Invidious uses QEMU to build it's ARM64 Invidious image,
which is slow (since we are basically using a virtual machine).
This helps with the speed of building ARM64 binaries for Invidious
on each release/commit.
More information about the public ARM64 runners here:
https://github.com/orgs/community/discussions/148648
CI: Use ARM64 compose file for build-docker-arm64
---
.github/workflows/build-nightly-container.yml | 55 +++++++------------
.github/workflows/build-stable-container.yml | 55 +++++++------------
.github/workflows/ci.yml | 20 ++-----
docker-compose-arm64.yml | 55 +++++++++++++++++++
4 files changed, 101 insertions(+), 84 deletions(-)
create mode 100644 docker-compose-arm64.yml
diff --git a/.github/workflows/build-nightly-container.yml b/.github/workflows/build-nightly-container.yml
index 4149bd0bc..3277c0151 100644
--- a/.github/workflows/build-nightly-container.yml
+++ b/.github/workflows/build-nightly-container.yml
@@ -17,17 +17,27 @@ on:
jobs:
release:
- runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ include:
+ - os: ubuntu-latest
+ platforms: linux/amd64
+ name: "AMD64"
+ dockerfile: "docker/Dockerfile"
+ tag_suffix: ""
+ # GitHub doesn't has a ubuntu-latest-arm runner
+ - os: ubuntu-24.04-arm
+ platforms: linux/arm64/v8
+ name: "ARM64"
+ dockerfile: "docker/Dockerfile.arm64"
+ tag_suffix: "-arm64"
+
+ runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v3
- with:
- platforms: arm64
-
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
@@ -43,45 +53,22 @@ jobs:
uses: docker/metadata-action@v5
with:
images: quay.io/invidious/invidious
+ flavor: |
+ suffix=${{ matrix.tag_suffix }}
tags: |
type=sha,format=short,prefix={{date 'YYYY.MM.DD'}}-,enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
type=raw,value=master,enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
labels: |
quay.expires-after=12w
- - name: Build and push Docker AMD64 image for Push Event
+ - name: Build and push Docker ${{ matrix.name }} image for Push Event
uses: docker/build-push-action@v6
with:
context: .
- file: docker/Dockerfile
- platforms: linux/amd64
+ file: ${{ matrix.dockerfile }}
+ platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
push: true
tags: ${{ steps.meta.outputs.tags }}
build-args: |
"release=1"
-
- - name: Docker meta
- id: meta-arm64
- uses: docker/metadata-action@v5
- with:
- images: quay.io/invidious/invidious
- flavor: |
- suffix=-arm64
- tags: |
- type=sha,format=short,prefix={{date 'YYYY.MM.DD'}}-,enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
- type=raw,value=master,enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
- labels: |
- quay.expires-after=12w
-
- - name: Build and push Docker ARM64 image for Push Event
- uses: docker/build-push-action@v6
- with:
- context: .
- file: docker/Dockerfile.arm64
- platforms: linux/arm64/v8
- labels: ${{ steps.meta-arm64.outputs.labels }}
- push: true
- tags: ${{ steps.meta-arm64.outputs.tags }}
- build-args: |
- "release=1"
diff --git a/.github/workflows/build-stable-container.yml b/.github/workflows/build-stable-container.yml
index 1a23e68ca..1498dc2e7 100644
--- a/.github/workflows/build-stable-container.yml
+++ b/.github/workflows/build-stable-container.yml
@@ -8,17 +8,27 @@ on:
jobs:
release:
- runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ include:
+ - os: ubuntu-latest
+ platforms: linux/amd64
+ name: "AMD64"
+ dockerfile: "docker/Dockerfile"
+ tag_suffix: ""
+ # GitHub doesn't has a ubuntu-latest-arm runner
+ - os: ubuntu-24.04-arm
+ platforms: linux/arm64/v8
+ name: "ARM64"
+ dockerfile: "docker/Dockerfile.arm64"
+ tag_suffix: "-arm64"
+
+ runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v3
- with:
- platforms: arm64
-
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
@@ -36,46 +46,21 @@ jobs:
images: quay.io/invidious/invidious
flavor: |
latest=false
+ suffix=${{ matrix.tag_suffix }}
tags: |
type=semver,pattern={{version}}
type=raw,value=latest
labels: |
quay.expires-after=12w
- - name: Build and push Docker AMD64 image for Push Event
+ - name: Build and push Docker ${{ matrix.name }} image for Push Event
uses: docker/build-push-action@v6
with:
context: .
- file: docker/Dockerfile
- platforms: linux/amd64
+ file: ${{ matrix.dockerfile }}
+ platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
push: true
tags: ${{ steps.meta.outputs.tags }}
build-args: |
"release=1"
-
- - name: Docker meta
- id: meta-arm64
- uses: docker/metadata-action@v5
- with:
- images: quay.io/invidious/invidious
- flavor: |
- latest=false
- suffix=-arm64
- tags: |
- type=semver,pattern={{version}}
- type=raw,value=latest
- labels: |
- quay.expires-after=12w
-
- - name: Build and push Docker ARM64 image for Push Event
- uses: docker/build-push-action@v6
- with:
- context: .
- file: docker/Dockerfile.arm64
- platforms: linux/arm64/v8
- labels: ${{ steps.meta-arm64.outputs.labels }}
- push: true
- tags: ${{ steps.meta-arm64.outputs.tags }}
- build-args: |
- "release=1"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9d6a930ad..c8805d103 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -100,26 +100,16 @@ jobs:
build-docker-arm64:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v3
- with:
- platforms: arm64
+ - name: Build Docker
+ run: docker compose -f docker-compose-arm64.yml build --build-arg release=0
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
-
- - name: Build Docker ARM64 image
- uses: docker/build-push-action@v6
- with:
- context: .
- file: docker/Dockerfile.arm64
- platforms: linux/arm64/v8
- build-args: release=0
+ - name: Run Docker
+ run: docker compose -f docker-compose-arm64.yml up -d
- name: Test Docker
run: while curl -Isf http://localhost:3000; do sleep 1; done
diff --git a/docker-compose-arm64.yml b/docker-compose-arm64.yml
new file mode 100644
index 000000000..ba9e0a3f5
--- /dev/null
+++ b/docker-compose-arm64.yml
@@ -0,0 +1,55 @@
+# Warning: This docker-compose file is made for development purposes.
+# Using it will build an image from the locally cloned repository.
+#
+# If you want to use Invidious in production, see the docker-compose.yml file provided
+# in the installation documentation: https://docs.invidious.io/installation/
+
+version: "3"
+services:
+
+ invidious:
+ build:
+ context: .
+ dockerfile: docker/Dockerfile.arm64
+ restart: unless-stopped
+ ports:
+ - "127.0.0.1:3000:3000"
+ environment:
+ # Please read the following file for a comprehensive list of all available
+ # configuration options and their associated syntax:
+ # https://github.com/iv-org/invidious/blob/master/config/config.example.yml
+ INVIDIOUS_CONFIG: |
+ db:
+ dbname: invidious
+ user: kemal
+ password: kemal
+ host: invidious-db
+ port: 5432
+ check_tables: true
+ # external_port:
+ # domain:
+ # https_only: false
+ # statistics_enabled: false
+ hmac_key: "CHANGE_ME!!"
+ healthcheck:
+ test: wget -nv --tries=1 --spider http://127.0.0.1:3000/api/v1/trending || exit 1
+ interval: 30s
+ timeout: 5s
+ retries: 2
+
+ invidious-db:
+ image: docker.io/library/postgres:14
+ restart: unless-stopped
+ volumes:
+ - postgresdata:/var/lib/postgresql/data
+ - ./config/sql:/config/sql
+ - ./docker/init-invidious-db.sh:/docker-entrypoint-initdb.d/init-invidious-db.sh
+ environment:
+ POSTGRES_DB: invidious
+ POSTGRES_USER: kemal
+ POSTGRES_PASSWORD: kemal
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
+
+volumes:
+ postgresdata:
From cef0097a309847f6075d7e9173c0362dcc83c757 Mon Sep 17 00:00:00 2001
From: Fijxu
Date: Thu, 15 May 2025 15:28:14 -0400
Subject: [PATCH 05/52] CI: fix typo on matrix platforms
---
.github/workflows/build-nightly-container.yml | 4 ++--
.github/workflows/build-stable-container.yml | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/build-nightly-container.yml b/.github/workflows/build-nightly-container.yml
index 3277c0151..6b9d4a87b 100644
--- a/.github/workflows/build-nightly-container.yml
+++ b/.github/workflows/build-nightly-container.yml
@@ -21,13 +21,13 @@ jobs:
matrix:
include:
- os: ubuntu-latest
- platforms: linux/amd64
+ platform: linux/amd64
name: "AMD64"
dockerfile: "docker/Dockerfile"
tag_suffix: ""
# GitHub doesn't has a ubuntu-latest-arm runner
- os: ubuntu-24.04-arm
- platforms: linux/arm64/v8
+ platform: linux/arm64/v8
name: "ARM64"
dockerfile: "docker/Dockerfile.arm64"
tag_suffix: "-arm64"
diff --git a/.github/workflows/build-stable-container.yml b/.github/workflows/build-stable-container.yml
index 1498dc2e7..07a3520b9 100644
--- a/.github/workflows/build-stable-container.yml
+++ b/.github/workflows/build-stable-container.yml
@@ -12,13 +12,13 @@ jobs:
matrix:
include:
- os: ubuntu-latest
- platforms: linux/amd64
+ platform: linux/amd64
name: "AMD64"
dockerfile: "docker/Dockerfile"
tag_suffix: ""
# GitHub doesn't has a ubuntu-latest-arm runner
- os: ubuntu-24.04-arm
- platforms: linux/arm64/v8
+ platform: linux/arm64/v8
name: "ARM64"
dockerfile: "docker/Dockerfile.arm64"
tag_suffix: "-arm64"
From 1d2f4b68133231c66e18d878706e8e263e47a66f Mon Sep 17 00:00:00 2001
From: Fijxu
Date: Thu, 15 May 2025 15:29:24 -0400
Subject: [PATCH 06/52] CI: fix typo on comment about the os used on the ARM64
builder
---
.github/workflows/build-nightly-container.yml | 2 +-
.github/workflows/build-stable-container.yml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/build-nightly-container.yml b/.github/workflows/build-nightly-container.yml
index 6b9d4a87b..1a5abeeac 100644
--- a/.github/workflows/build-nightly-container.yml
+++ b/.github/workflows/build-nightly-container.yml
@@ -25,7 +25,7 @@ jobs:
name: "AMD64"
dockerfile: "docker/Dockerfile"
tag_suffix: ""
- # GitHub doesn't has a ubuntu-latest-arm runner
+ # GitHub doesn't have a ubuntu-latest-arm runner
- os: ubuntu-24.04-arm
platform: linux/arm64/v8
name: "ARM64"
diff --git a/.github/workflows/build-stable-container.yml b/.github/workflows/build-stable-container.yml
index 07a3520b9..7c2a276bd 100644
--- a/.github/workflows/build-stable-container.yml
+++ b/.github/workflows/build-stable-container.yml
@@ -16,7 +16,7 @@ jobs:
name: "AMD64"
dockerfile: "docker/Dockerfile"
tag_suffix: ""
- # GitHub doesn't has a ubuntu-latest-arm runner
+ # GitHub doesn't have a ubuntu-latest-arm runner
- os: ubuntu-24.04-arm
platform: linux/arm64/v8
name: "ARM64"
From 94f0a7a9d22e46e58447810fbb0da05508162fad Mon Sep 17 00:00:00 2001
From: Fijxu
Date: Thu, 15 May 2025 15:31:17 -0400
Subject: [PATCH 07/52] CI: remove --build-arg
Dockerfile and Dockerfile.arm64 already build Invidious without release mode if
`release` argument is not present.
---
.github/workflows/ci.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c8805d103..1bb921016 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -90,7 +90,7 @@ jobs:
- uses: actions/checkout@v4
- name: Build Docker
- run: docker compose build --build-arg release=0
+ run: docker compose build
- name: Run Docker
run: docker compose up -d
@@ -106,7 +106,7 @@ jobs:
- uses: actions/checkout@v4
- name: Build Docker
- run: docker compose -f docker-compose-arm64.yml build --build-arg release=0
+ run: docker compose -f docker-compose-arm64.yml build
- name: Run Docker
run: docker compose -f docker-compose-arm64.yml up -d
From 1d664c759f17b5455d1ffbbe5e276a35dd4202e9 Mon Sep 17 00:00:00 2001
From: Fijxu
Date: Thu, 15 May 2025 16:33:03 -0400
Subject: [PATCH 08/52] CI: Use matrix for `build-docker` on ci.yml
---
.github/workflows/ci.yml | 28 ++++++++++------------------
1 file changed, 10 insertions(+), 18 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1bb921016..51a5052d2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -83,14 +83,22 @@ jobs:
run: crystal build --warnings all --error-on-warnings --error-trace src/invidious.cr
build-docker:
+ strategy:
+ matrix:
+ include:
+ - os: ubuntu-latest
+ docker_compose_file: "docker-compose.yml"
+ # GitHub doesn't have a ubuntu-latest-arm runner
+ - os: ubuntu-24.04-arm
+ docker_compose_file: "docker-compose-arm64.yml"
- runs-on: ubuntu-latest
+ runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Build Docker
- run: docker compose build
+ run: docker compose -f ${{ matrix.docker_compose_file }} build
- name: Run Docker
run: docker compose up -d
@@ -98,22 +106,6 @@ jobs:
- name: Test Docker
run: while curl -Isf http://localhost:3000; do sleep 1; done
- build-docker-arm64:
-
- runs-on: ubuntu-24.04-arm
-
- steps:
- - uses: actions/checkout@v4
-
- - name: Build Docker
- run: docker compose -f docker-compose-arm64.yml build
-
- - name: Run Docker
- run: docker compose -f docker-compose-arm64.yml up -d
-
- - name: Test Docker
- run: while curl -Isf http://localhost:3000; do sleep 1; done
-
lint:
runs-on: ubuntu-latest
From a3375e512edf00c5c0c00089d370392c37bbe550 Mon Sep 17 00:00:00 2001
From: Fijxu
Date: Thu, 15 May 2025 17:43:03 -0400
Subject: [PATCH 09/52] CI: Add name attribute to `build-docker` job
---
.github/workflows/ci.yml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 51a5052d2..80cb81a0a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -88,10 +88,13 @@ jobs:
include:
- os: ubuntu-latest
docker_compose_file: "docker-compose.yml"
+ name: "AMD64"
# GitHub doesn't have a ubuntu-latest-arm runner
- os: ubuntu-24.04-arm
docker_compose_file: "docker-compose-arm64.yml"
+ name: "ARM64"
+ name: Test ${{ matrix.name }} Docker build
runs-on: ${{ matrix.os }}
steps:
From 033a44fab574df56dd63a41d63d089b84cdb31f5 Mon Sep 17 00:00:00 2001
From: Fijxu
Date: Thu, 15 May 2025 17:58:24 -0400
Subject: [PATCH 10/52] CI: Also use `matrix.docker_compose_file` for `Run
Docker` step
---
.github/workflows/ci.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 80cb81a0a..d3b6455a1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -104,7 +104,7 @@ jobs:
run: docker compose -f ${{ matrix.docker_compose_file }} build
- name: Run Docker
- run: docker compose up -d
+ run: docker compose -f ${{ matrix.docker_compose_file }} up -d
- name: Test Docker
run: while curl -Isf http://localhost:3000; do sleep 1; done
From 381074fce1f3e405d8c527a672f524c4700aead5 Mon Sep 17 00:00:00 2001
From: Fijxu
Date: Thu, 15 May 2025 19:38:21 -0400
Subject: [PATCH 11/52] CI: Replace Dockerfile path depending of the os used
---
.github/workflows/ci.yml | 10 +++++---
docker-compose-arm64.yml | 55 ----------------------------------------
2 files changed, 6 insertions(+), 59 deletions(-)
delete mode 100644 docker-compose-arm64.yml
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d3b6455a1..7a5e88501 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -87,11 +87,9 @@ jobs:
matrix:
include:
- os: ubuntu-latest
- docker_compose_file: "docker-compose.yml"
name: "AMD64"
# GitHub doesn't have a ubuntu-latest-arm runner
- os: ubuntu-24.04-arm
- docker_compose_file: "docker-compose-arm64.yml"
name: "ARM64"
name: Test ${{ matrix.name }} Docker build
@@ -100,11 +98,15 @@ jobs:
steps:
- uses: actions/checkout@v4
+ - name: Use ARM64 Dockerfile if ARM64
+ if: ${{ matrix.name }} == "ARM64"
+ run: sed -i 's/Dockerfile/Dockerfile.arm64/' docker-compose.yml
+
- name: Build Docker
- run: docker compose -f ${{ matrix.docker_compose_file }} build
+ run: docker compose build
- name: Run Docker
- run: docker compose -f ${{ matrix.docker_compose_file }} up -d
+ run: docker compose up -d
- name: Test Docker
run: while curl -Isf http://localhost:3000; do sleep 1; done
diff --git a/docker-compose-arm64.yml b/docker-compose-arm64.yml
deleted file mode 100644
index ba9e0a3f5..000000000
--- a/docker-compose-arm64.yml
+++ /dev/null
@@ -1,55 +0,0 @@
-# Warning: This docker-compose file is made for development purposes.
-# Using it will build an image from the locally cloned repository.
-#
-# If you want to use Invidious in production, see the docker-compose.yml file provided
-# in the installation documentation: https://docs.invidious.io/installation/
-
-version: "3"
-services:
-
- invidious:
- build:
- context: .
- dockerfile: docker/Dockerfile.arm64
- restart: unless-stopped
- ports:
- - "127.0.0.1:3000:3000"
- environment:
- # Please read the following file for a comprehensive list of all available
- # configuration options and their associated syntax:
- # https://github.com/iv-org/invidious/blob/master/config/config.example.yml
- INVIDIOUS_CONFIG: |
- db:
- dbname: invidious
- user: kemal
- password: kemal
- host: invidious-db
- port: 5432
- check_tables: true
- # external_port:
- # domain:
- # https_only: false
- # statistics_enabled: false
- hmac_key: "CHANGE_ME!!"
- healthcheck:
- test: wget -nv --tries=1 --spider http://127.0.0.1:3000/api/v1/trending || exit 1
- interval: 30s
- timeout: 5s
- retries: 2
-
- invidious-db:
- image: docker.io/library/postgres:14
- restart: unless-stopped
- volumes:
- - postgresdata:/var/lib/postgresql/data
- - ./config/sql:/config/sql
- - ./docker/init-invidious-db.sh:/docker-entrypoint-initdb.d/init-invidious-db.sh
- environment:
- POSTGRES_DB: invidious
- POSTGRES_USER: kemal
- POSTGRES_PASSWORD: kemal
- healthcheck:
- test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
-
-volumes:
- postgresdata:
From cc643f209a95cbf9fcc7e97ae3587454b35c3bc5 Mon Sep 17 00:00:00 2001
From: Fijxu
Date: Thu, 15 May 2025 17:49:54 -0400
Subject: [PATCH 12/52] CI: Fix build-docker job not checking if Invidious
starts successfully or not
---
.github/workflows/ci.yml | 12 +++++++++++-
docker-compose.yml | 4 ++++
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7a5e88501..27debc1ee 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -105,11 +105,21 @@ jobs:
- name: Build Docker
run: docker compose build
+ - name: Change hmac_key on docker-compose.yml
+ run: sed -i '/hmac_key/s/CHANGE_ME!!/docker-build-hmac-key/' docker-compose.yml
+
- name: Run Docker
run: docker compose up -d
- name: Test Docker
- run: while curl -Isf http://localhost:3000; do sleep 1; done
+ id: test
+ run: curl -If http://localhost:3000 --retry 5 --retry-delay 1 --retry-all-errors
+
+ - name: Print Invidious container logs
+ # Tells Github Actions to always run this step regardless of whether the previous step has failed
+ # Without this expression this step would simply be skipped when the previous step fails.
+ if: success() || steps.test.conclusion == 'failure'
+ run: docker compose logs
lint:
diff --git a/docker-compose.yml b/docker-compose.yml
index afda87266..0de51feb9 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -14,6 +14,10 @@ services:
restart: unless-stopped
ports:
- "127.0.0.1:3000:3000"
+ depends_on:
+ invidious-db:
+ condition: service_healthy
+ restart: true
environment:
# Please read the following file for a comprehensive list of all available
# configuration options and their associated syntax:
From f9472e4e4b910acb9962159e97b37c4d95f8b804 Mon Sep 17 00:00:00 2001
From: epicsam123 <92618898+epicsam123@users.noreply.github.com>
Date: Mon, 19 May 2025 22:34:59 -0400
Subject: [PATCH 13/52] revert format
---
assets/css/player.css | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/assets/css/player.css b/assets/css/player.css
index 60f3ce736..d95549ac7 100644
--- a/assets/css/player.css
+++ b/assets/css/player.css
@@ -71,10 +71,8 @@
padding-top: 2em
}
-.video-js.player-style-youtube .vjs-progress-control .vjs-progress-holder, .video-js.player-style-youtube .vjs-progress-control {
- height: 5px;
- margin-bottom: 10px;
-}
+.video-js.player-style-youtube .vjs-progress-control .vjs-progress-holder, .video-js.player-style-youtube .vjs-progress-control {height: 5px;
+margin-bottom: 10px;}
ul.vjs-menu-content::-webkit-scrollbar {
display: none;
From 6497e1c41888756b0f624df725712bf3b00d49c2 Mon Sep 17 00:00:00 2001
From: Fijxu
Date: Thu, 22 May 2025 16:06:13 -0400
Subject: [PATCH 14/52] YtAPI: Bump client versions
---
src/invidious/yt_backend/youtube_api.cr | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/src/invidious/yt_backend/youtube_api.cr b/src/invidious/yt_backend/youtube_api.cr
index b40092a1b..1f21ddf08 100644
--- a/src/invidious/yt_backend/youtube_api.cr
+++ b/src/invidious/yt_backend/youtube_api.cr
@@ -6,10 +6,10 @@ module YoutubeAPI
extend self
# For Android versions, see https://en.wikipedia.org/wiki/Android_version_history
- private ANDROID_APP_VERSION = "19.32.34"
- private ANDROID_VERSION = "12"
+ private ANDROID_APP_VERSION = "19.35.36"
+ private ANDROID_VERSION = "13"
private ANDROID_USER_AGENT = "com.google.android.youtube/#{ANDROID_APP_VERSION} (Linux; U; Android #{ANDROID_VERSION}; US) gzip"
- private ANDROID_SDK_VERSION = 31_i64
+ private ANDROID_SDK_VERSION = 33_i64
private ANDROID_TS_APP_VERSION = "1.9"
private ANDROID_TS_USER_AGENT = "com.google.android.youtube/1.9 (Linux; U; Android 12; US) gzip"
@@ -49,7 +49,7 @@ module YoutubeAPI
ClientType::Web => {
name: "WEB",
name_proto: "1",
- version: "2.20240814.00.00",
+ version: "2.20250222.10.00",
screen: "WATCH_FULL_SCREEN",
os_name: "Windows",
os_version: WINDOWS_VERSION,
@@ -58,7 +58,7 @@ module YoutubeAPI
ClientType::WebEmbeddedPlayer => {
name: "WEB_EMBEDDED_PLAYER",
name_proto: "56",
- version: "1.20240812.01.00",
+ version: "1.20250219.01.00",
screen: "EMBED",
os_name: "Windows",
os_version: WINDOWS_VERSION,
@@ -67,7 +67,7 @@ module YoutubeAPI
ClientType::WebMobile => {
name: "MWEB",
name_proto: "2",
- version: "2.20240813.02.00",
+ version: "2.20250224.01.00",
os_name: "Android",
os_version: ANDROID_VERSION,
platform: "MOBILE",
@@ -75,7 +75,7 @@ module YoutubeAPI
ClientType::WebScreenEmbed => {
name: "WEB",
name_proto: "1",
- version: "2.20240814.00.00",
+ version: "2.20250222.10.00",
screen: "EMBED",
os_name: "Windows",
os_version: WINDOWS_VERSION,
@@ -84,7 +84,7 @@ module YoutubeAPI
ClientType::WebCreator => {
name: "WEB_CREATOR",
name_proto: "62",
- version: "1.20240918.03.00",
+ version: "1.20241203.01.00",
os_name: "Windows",
os_version: WINDOWS_VERSION,
platform: "DESKTOP",
@@ -170,7 +170,7 @@ module YoutubeAPI
ClientType::TvHtml5 => {
name: "TVHTML5",
name_proto: "7",
- version: "7.20240813.07.00",
+ version: "7.20250219.14.00",
},
ClientType::TvHtml5ScreenEmbed => {
name: "TVHTML5_SIMPLY_EMBEDDED_PLAYER",
From 97354adf0fc359d2898f69613d1ab668aaf6931f Mon Sep 17 00:00:00 2001
From: Fijxu
Date: Thu, 22 May 2025 17:15:45 -0400
Subject: [PATCH 15/52] Update src/invidious/yt_backend/youtube_api.cr
Co-authored-by: syeopite <70992037+syeopite@users.noreply.github.com>
---
src/invidious/yt_backend/youtube_api.cr | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/invidious/yt_backend/youtube_api.cr b/src/invidious/yt_backend/youtube_api.cr
index 1f21ddf08..bedbb978e 100644
--- a/src/invidious/yt_backend/youtube_api.cr
+++ b/src/invidious/yt_backend/youtube_api.cr
@@ -8,7 +8,7 @@ module YoutubeAPI
# For Android versions, see https://en.wikipedia.org/wiki/Android_version_history
private ANDROID_APP_VERSION = "19.35.36"
private ANDROID_VERSION = "13"
- private ANDROID_USER_AGENT = "com.google.android.youtube/#{ANDROID_APP_VERSION} (Linux; U; Android #{ANDROID_VERSION}; US) gzip"
+ private ANDROID_USER_AGENT = "com.google.android.youtube/#{ANDROID_APP_VERSION} (Linux; U; Android #{ANDROID_VERSION}; en_US; SM-S908E Build/TP1A.220624.014) gzip"
private ANDROID_SDK_VERSION = 33_i64
private ANDROID_TS_APP_VERSION = "1.9"
From 3a8d4f333f1ef5b42a4eb0a2e8b5743b646862cb Mon Sep 17 00:00:00 2001
From: Fijxu
Date: Thu, 22 May 2025 17:17:01 -0400
Subject: [PATCH 16/52] update IOS_APP_VERSION
---
src/invidious/yt_backend/youtube_api.cr | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/invidious/yt_backend/youtube_api.cr b/src/invidious/yt_backend/youtube_api.cr
index bedbb978e..5f89d0e64 100644
--- a/src/invidious/yt_backend/youtube_api.cr
+++ b/src/invidious/yt_backend/youtube_api.cr
@@ -17,7 +17,7 @@ module YoutubeAPI
# For Apple device names, see https://gist.github.com/adamawolf/3048717
# For iOS versions, see https://en.wikipedia.org/wiki/IOS_version_history#Releases,
# then go to the dedicated article of the major version you want.
- private IOS_APP_VERSION = "19.32.8"
+ private IOS_APP_VERSION = "20.11.6"
private IOS_USER_AGENT = "com.google.ios.youtube/#{IOS_APP_VERSION} (iPhone14,5; U; CPU iOS 17_6 like Mac OS X;)"
private IOS_VERSION = "17.6.1.21G93" # Major.Minor.Patch.Build
From 09d342b84d4639026b90beb3f95403f6cf93275a Mon Sep 17 00:00:00 2001
From: Fijxu
Date: Thu, 22 May 2025 17:55:46 -0400
Subject: [PATCH 17/52] Update src/invidious/yt_backend/youtube_api.cr
Co-authored-by: syeopite <70992037+syeopite@users.noreply.github.com>
---
src/invidious/yt_backend/youtube_api.cr | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/invidious/yt_backend/youtube_api.cr b/src/invidious/yt_backend/youtube_api.cr
index 5f89d0e64..9f2078c75 100644
--- a/src/invidious/yt_backend/youtube_api.cr
+++ b/src/invidious/yt_backend/youtube_api.cr
@@ -18,8 +18,8 @@ module YoutubeAPI
# For iOS versions, see https://en.wikipedia.org/wiki/IOS_version_history#Releases,
# then go to the dedicated article of the major version you want.
private IOS_APP_VERSION = "20.11.6"
- private IOS_USER_AGENT = "com.google.ios.youtube/#{IOS_APP_VERSION} (iPhone14,5; U; CPU iOS 17_6 like Mac OS X;)"
- private IOS_VERSION = "17.6.1.21G93" # Major.Minor.Patch.Build
+ private IOS_USER_AGENT = "com.google.ios.youtube/#{IOS_APP_VERSION} (iPhone14,5; U; CPU iOS 18_5 like Mac OS X;)"
+ private IOS_VERSION = "18.5.0.22F76" # Major.Minor.Patch.Build
private WINDOWS_VERSION = "10.0"
From 4daf1f081828dd9137e58bf7a2cc79872f7afa6f Mon Sep 17 00:00:00 2001
From: Fijxu
Date: Thu, 12 Jun 2025 01:24:45 -0400
Subject: [PATCH 18/52] Add `TvSimply` client
Data taken from: https://github.com/LuanRT/YouTube.js/commit/8cf658151fc4e4266fadfb7e53dd5db3db693355, https://github.com/LuanRT/YouTube.js/commit/689fb0b90edab6f0e4326a35144541d68f72fe01 and https://github.com/LuanRT/YouTube.js/commit/b15f623dab3acb44eaef33175df2d22d35be2979
---
src/invidious/yt_backend/youtube_api.cr | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/invidious/yt_backend/youtube_api.cr b/src/invidious/yt_backend/youtube_api.cr
index b40092a1b..78915aef3 100644
--- a/src/invidious/yt_backend/youtube_api.cr
+++ b/src/invidious/yt_backend/youtube_api.cr
@@ -42,6 +42,7 @@ module YoutubeAPI
TvHtml5
TvHtml5ScreenEmbed
+ TvSimply
end
# List of hard-coded values used by the different clients
@@ -178,6 +179,11 @@ module YoutubeAPI
version: "2.0",
screen: "EMBED",
},
+ ClientType::TvSimply => {
+ name: "TVHTML5_SIMPLY",
+ name_proto: "74",
+ version: "1.0",
+ },
}
####################################################################
From 37be513e142061067604d7ec1981fe7a309f8713 Mon Sep 17 00:00:00 2001
From: Fijxu
Date: Thu, 12 Jun 2025 01:25:59 -0400
Subject: [PATCH 19/52] Add fallback to TvSimply client
---
src/invidious/videos/parser.cr | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/invidious/videos/parser.cr b/src/invidious/videos/parser.cr
index feb584405..5be593522 100644
--- a/src/invidious/videos/parser.cr
+++ b/src/invidious/videos/parser.cr
@@ -111,7 +111,7 @@ def extract_video_info(video_id : String)
if !CONFIG.invidious_companion.present?
if player_response.dig?("streamingData", "adaptiveFormats", 0, "url").nil?
LOGGER.warn("Missing URLs for adaptive formats, falling back to other YT clients.")
- players_fallback = {YoutubeAPI::ClientType::TvHtml5, YoutubeAPI::ClientType::WebMobile}
+ players_fallback = {YoutubeAPI::ClientType::TvHtml5, YoutubeAPI::ClientType::TvSimply, YoutubeAPI::ClientType::WebMobile}
players_fallback.each do |player_fallback|
client_config.client_type = player_fallback
From 0c96e0977fd805731d8fdbe97afac1ee22b6626a Mon Sep 17 00:00:00 2001
From: Fijxu
Date: Thu, 12 Jun 2025 16:06:04 -0400
Subject: [PATCH 20/52] check for signatureCipher too
---
src/invidious/videos/parser.cr | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/invidious/videos/parser.cr b/src/invidious/videos/parser.cr
index 5be593522..212b3b353 100644
--- a/src/invidious/videos/parser.cr
+++ b/src/invidious/videos/parser.cr
@@ -118,7 +118,7 @@ def extract_video_info(video_id : String)
next if !(player_fallback_response = try_fetch_streaming_data(video_id, client_config))
- if player_fallback_response.dig?("streamingData", "adaptiveFormats", 0, "url")
+ if player_fallback_response.dig?("streamingData", "adaptiveFormats", 0, "url") || player_fallback_response.dig?("streamingData", "adaptiveFormats", 0, "signatureCipher")
streaming_data = player_response["streamingData"].as_h
streaming_data["adaptiveFormats"] = player_fallback_response["streamingData"]["adaptiveFormats"]
player_response["streamingData"] = JSON::Any.new(streaming_data)
From b1e7e0c45e8cfe0ca262dc5774c8ccca3fc6db66 Mon Sep 17 00:00:00 2001
From: Fijxu
Date: Thu, 12 Jun 2025 16:18:01 -0400
Subject: [PATCH 21/52] replace url by signatureCipher if url is not present
---
src/invidious/videos/parser.cr | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/invidious/videos/parser.cr b/src/invidious/videos/parser.cr
index 212b3b353..e58c0e8f2 100644
--- a/src/invidious/videos/parser.cr
+++ b/src/invidious/videos/parser.cr
@@ -146,6 +146,9 @@ def extract_video_info(video_id : String)
if streaming_data = player_response["streamingData"]?
%w[formats adaptiveFormats].each do |key|
streaming_data.as_h[key]?.try &.as_a.each do |format|
+ if format.as_h["url"].nil?
+ format.as_h["url"] = format.as_h["signatureCipher"]
+ end
format.as_h["url"] = JSON::Any.new(convert_url(format))
end
end
From 01cdb384e0629ade15a83f8a2bcb50722d03340c Mon Sep 17 00:00:00 2001
From: Fijxu
Date: Thu, 12 Jun 2025 17:25:19 -0400
Subject: [PATCH 22/52] add suggestions from syeopite
---
src/invidious/videos/parser.cr | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/invidious/videos/parser.cr b/src/invidious/videos/parser.cr
index e58c0e8f2..6892b37c6 100644
--- a/src/invidious/videos/parser.cr
+++ b/src/invidious/videos/parser.cr
@@ -146,10 +146,11 @@ def extract_video_info(video_id : String)
if streaming_data = player_response["streamingData"]?
%w[formats adaptiveFormats].each do |key|
streaming_data.as_h[key]?.try &.as_a.each do |format|
- if format.as_h["url"].nil?
- format.as_h["url"] = format.as_h["signatureCipher"]
+ format = format.as_h
+ if format["url"]?.nil?
+ format["url"] = format["signatureCipher"]
end
- format.as_h["url"] = JSON::Any.new(convert_url(format))
+ format["url"] = JSON::Any.new(convert_url(format))
end
end
From 8cd9d53fb1ff4a8a15d208f587a0a4ce330890bd Mon Sep 17 00:00:00 2001
From: Fijxu
Date: Thu, 12 Jun 2025 18:44:01 -0400
Subject: [PATCH 23/52] show message when connection to the database is not
possible
---
src/invidious.cr | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/invidious.cr b/src/invidious.cr
index 69f8a26cb..d1f84f39a 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -60,7 +60,13 @@ alias IV = Invidious
CONFIG = Config.load
HMAC_KEY = CONFIG.hmac_key
-PG_DB = DB.open CONFIG.database_url
+PG_DB = begin
+ DB.open CONFIG.database_url
+rescue ex
+ puts "Failed to connect to PostgreSQL database: #{ex.cause.try &.message}"
+ puts "Check your 'config.yml' database settings or PostgreSQL settings."
+ exit(1)
+end
ARCHIVE_URL = URI.parse("https://archive.org")
PUBSUB_URL = URI.parse("https://pubsubhubbub.appspot.com")
REDDIT_URL = URI.parse("https://www.reddit.com")
From cf0a68bd77251528713404822a19c411a1c0aaca Mon Sep 17 00:00:00 2001
From: Fijxu
Date: Sun, 15 Jun 2025 16:51:04 -0400
Subject: [PATCH 24/52] store adaptiveFormats data into a variable
---
src/invidious/videos/parser.cr | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/invidious/videos/parser.cr b/src/invidious/videos/parser.cr
index 6892b37c6..178b905b0 100644
--- a/src/invidious/videos/parser.cr
+++ b/src/invidious/videos/parser.cr
@@ -118,9 +118,10 @@ def extract_video_info(video_id : String)
next if !(player_fallback_response = try_fetch_streaming_data(video_id, client_config))
- if player_fallback_response.dig?("streamingData", "adaptiveFormats", 0, "url") || player_fallback_response.dig?("streamingData", "adaptiveFormats", 0, "signatureCipher")
+ adaptive_formats = player_fallback_response.dig?("streamingData", "adaptiveFormats")
+ if adaptive_formats && (adaptive_formats.dig?(0, "url") || adaptive_formats.dig?(0, "signatureCipher"))
streaming_data = player_response["streamingData"].as_h
- streaming_data["adaptiveFormats"] = player_fallback_response["streamingData"]["adaptiveFormats"]
+ streaming_data["adaptiveFormats"] = adaptive_formats
player_response["streamingData"] = JSON::Any.new(streaming_data)
break
end
From d51e1cb0514fe2be4b94f7233e36a8aada542496 Mon Sep 17 00:00:00 2001
From: Fijxu
Date: Sun, 15 Jun 2025 17:45:53 -0400
Subject: [PATCH 25/52] remove fallback to TV client
---
src/invidious/videos/parser.cr | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/invidious/videos/parser.cr b/src/invidious/videos/parser.cr
index 178b905b0..5335aa794 100644
--- a/src/invidious/videos/parser.cr
+++ b/src/invidious/videos/parser.cr
@@ -111,7 +111,7 @@ def extract_video_info(video_id : String)
if !CONFIG.invidious_companion.present?
if player_response.dig?("streamingData", "adaptiveFormats", 0, "url").nil?
LOGGER.warn("Missing URLs for adaptive formats, falling back to other YT clients.")
- players_fallback = {YoutubeAPI::ClientType::TvHtml5, YoutubeAPI::ClientType::TvSimply, YoutubeAPI::ClientType::WebMobile}
+ players_fallback = {YoutubeAPI::ClientType::TvSimply, YoutubeAPI::ClientType::WebMobile}
players_fallback.each do |player_fallback|
client_config.client_type = player_fallback
From 8723fdca06510a2ab64c194fa284f011fd327e42 Mon Sep 17 00:00:00 2001
From: Fijxu
Date: Sat, 21 Jun 2025 12:02:32 -0400
Subject: [PATCH 26/52] Update src/invidious.cr
Co-authored-by: Samantaz Fox
---
src/invidious.cr | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/invidious.cr b/src/invidious.cr
index d1f84f39a..2d244dd20 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -62,8 +62,8 @@ HMAC_KEY = CONFIG.hmac_key
PG_DB = begin
DB.open CONFIG.database_url
-rescue ex
- puts "Failed to connect to PostgreSQL database: #{ex.cause.try &.message}"
+rescue exc
+ puts "Failed to connect to PostgreSQL database: #{exc.cause.try &.message}"
puts "Check your 'config.yml' database settings or PostgreSQL settings."
exit(1)
end
From f3f6937ffcc703f11173653dc250c7f5bac5d736 Mon Sep 17 00:00:00 2001
From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com>
Date: Wed, 25 Jun 2025 22:22:30 -0400
Subject: [PATCH 27/52] Fix community tab not loading
---
src/invidious/channels/community.cr | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/invidious/channels/community.cr b/src/invidious/channels/community.cr
index 49ffd9902..6a2960094 100644
--- a/src/invidious/channels/community.cr
+++ b/src/invidious/channels/community.cr
@@ -3,8 +3,8 @@ private IMAGE_QUALITIES = {320, 560, 640, 1280, 2000}
# TODO: Add "sort_by"
def fetch_channel_community(ucid, cursor, locale, format, thin_mode)
if cursor.nil?
- # Egljb21tdW5pdHk%3D is the protobuf object to load "community"
- initial_data = YoutubeAPI.browse(ucid, params: "Egljb21tdW5pdHk%3D")
+ # EgVwb3N0c_IGBAoCSgA%3D is the protobuf object to load "community"
+ initial_data = YoutubeAPI.browse(ucid, params: "EgVwb3N0c_IGBAoCSgA%3D")
items = [] of JSON::Any
extract_items(initial_data) do |item|
From b9171d9dab7e6791376c4cc899ed3b6fa16e5f19 Mon Sep 17 00:00:00 2001
From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com>
Date: Wed, 25 Jun 2025 22:34:26 -0400
Subject: [PATCH 28/52] Update protobuf for individual community post
---
src/invidious/channels/community.cr | 17 +++++++----------
1 file changed, 7 insertions(+), 10 deletions(-)
diff --git a/src/invidious/channels/community.cr b/src/invidious/channels/community.cr
index 6a2960094..c06884165 100644
--- a/src/invidious/channels/community.cr
+++ b/src/invidious/channels/community.cr
@@ -3,7 +3,7 @@ private IMAGE_QUALITIES = {320, 560, 640, 1280, 2000}
# TODO: Add "sort_by"
def fetch_channel_community(ucid, cursor, locale, format, thin_mode)
if cursor.nil?
- # EgVwb3N0c_IGBAoCSgA%3D is the protobuf object to load "community"
+ # EgVwb3N0c_IGBAoCSgA%3D is the protobuf object to load "posts"
initial_data = YoutubeAPI.browse(ucid, params: "EgVwb3N0c_IGBAoCSgA%3D")
items = [] of JSON::Any
@@ -26,21 +26,18 @@ end
def fetch_channel_community_post(ucid, post_id, locale, format, thin_mode)
object = {
- "2:string" => "community",
- "25:embedded" => {
- "22:string" => post_id.to_s,
- },
- "45:embedded" => {
- "2:varint" => 1_i64,
- "3:varint" => 1_i64,
- },
+ "56:embedded" => {
+ "2:string" => ucid,
+ "3:string" => post_id.to_s,
+ "11:string" => ucid,
+ }
}
params = object.try { |i| Protodec::Any.cast_json(i) }
.try { |i| Protodec::Any.from_json(i) }
.try { |i| Base64.urlsafe_encode(i) }
.try { |i| URI.encode_www_form(i) }
- initial_data = YoutubeAPI.browse(ucid, params: params)
+ initial_data = YoutubeAPI.browse("FEpost_detail", params: params)
items = [] of JSON::Any
extract_items(initial_data) do |item|
From 4155f15bf73ede39434e7aa3878e295d5d203c04 Mon Sep 17 00:00:00 2001
From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com>
Date: Wed, 25 Jun 2025 23:33:28 -0400
Subject: [PATCH 29/52] update resolve_url api to better support new post
endpoint
---
src/invidious/routes/api/v1/misc.cr | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/src/invidious/routes/api/v1/misc.cr b/src/invidious/routes/api/v1/misc.cr
index 4f5b58da2..40f2a4397 100644
--- a/src/invidious/routes/api/v1/misc.cr
+++ b/src/invidious/routes/api/v1/misc.cr
@@ -190,15 +190,30 @@ module Invidious::Routes::API::V1::Misc
sub_endpoint = endpoint["watchEndpoint"]? || endpoint["browseEndpoint"]? || endpoint
params = sub_endpoint.try &.dig?("params")
+
+ if sub_endpoint["browseId"]?.try &.as_s == "FEpost_detail"
+ decoded_protobuf = params.try &.as_s.try { |i| URI.decode_www_form(i) }
+ .try { |i| Base64.decode(i) }
+ .try { |i| IO::Memory.new(i) }
+ .try { |i| Protodec::Any.parse(i) }
+
+ ucid = decoded_protobuf.try(&.["56:0:embedded"]["2:0:string"].as_s)
+ post_id = decoded_protobuf.try(&.["56:0:embedded"]["3:1:string"].as_s)
+ else
+ ucid = sub_endpoint["browseId"]? if sub_endpoint["browseId"]? && sub_endpoint["browseId"]?.try &.as_s.starts_with? "UC"
+ post_id = nil
+ end
rescue ex
return error_json(500, ex)
end
JSON.build do |json|
json.object do
- json.field "ucid", sub_endpoint["browseId"].as_s if sub_endpoint["browseId"]?
+ json.field "browseId", sub_endpoint["browseId"].as_s if sub_endpoint["browseId"]?
+ json.field "ucid", ucid if ucid != nil
json.field "videoId", sub_endpoint["videoId"].as_s if sub_endpoint["videoId"]?
json.field "playlistId", sub_endpoint["playlistId"].as_s if sub_endpoint["playlistId"]?
json.field "startTimeSeconds", sub_endpoint["startTimeSeconds"].as_i if sub_endpoint["startTimeSeconds"]?
+ json.field "postId", post_id if post_id != nil
json.field "params", params.try &.as_s
json.field "pageType", page_type
end
From 436f955e0f20c9e398e3587175f3071dcec153d4 Mon Sep 17 00:00:00 2001
From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com>
Date: Wed, 25 Jun 2025 23:34:30 -0400
Subject: [PATCH 30/52] update fetch_community_post_comments protobuf to match
currently used protobuf, add sort_by option
---
src/invidious/channels/community.cr | 9 +++++++++
src/invidious/comments/youtube.cr | 26 ++++++++++++++-----------
src/invidious/routes/api/v1/channels.cr | 6 ++++--
src/invidious/routes/channels.cr | 2 +-
4 files changed, 29 insertions(+), 14 deletions(-)
diff --git a/src/invidious/channels/community.cr b/src/invidious/channels/community.cr
index c06884165..8927c81bf 100644
--- a/src/invidious/channels/community.cr
+++ b/src/invidious/channels/community.cr
@@ -24,6 +24,15 @@ def fetch_channel_community(ucid, cursor, locale, format, thin_mode)
return extract_channel_community(items, ucid: ucid, locale: locale, format: format, thin_mode: thin_mode)
end
+def decode_ucid_from_post_protobuf(params)
+ decoded_protobuf = params.try { |i| URI.decode_www_form(i) }
+ .try { |i| Base64.decode(i) }
+ .try { |i| IO::Memory.new(i) }
+ .try { |i| Protodec::Any.parse(i) }
+
+ return decoded_protobuf.try(&.["56:0:embedded"]["2:0:string"].as_s)
+end
+
def fetch_channel_community_post(ucid, post_id, locale, format, thin_mode)
object = {
"56:embedded" => {
diff --git a/src/invidious/comments/youtube.cr b/src/invidious/comments/youtube.cr
index 0716fcde6..184036569 100644
--- a/src/invidious/comments/youtube.cr
+++ b/src/invidious/comments/youtube.cr
@@ -16,34 +16,38 @@ module Invidious::Comments
return parse_youtube(id, response, format, locale, thin_mode, sort_by)
end
- def fetch_community_post_comments(ucid, post_id)
+ def fetch_community_post_comments(ucid, post_id, sort_by = "top")
object = {
- "2:string" => "community",
- "25:embedded" => {
- "22:string" => post_id,
- },
- "45:embedded" => {
- "2:varint" => 1_i64,
- "3:varint" => 1_i64,
- },
+ "2:string" => "posts",
"53:embedded" => {
"4:embedded" => {
"6:varint" => 0_i64,
- "27:varint" => 1_i64,
+ "15:varint" => 2_i64,
+ "25:varint" => 0_i64,
"29:string" => post_id,
"30:string" => ucid,
},
+ "7:varint" => 0_i64,
"8:string" => "comments-section",
},
}
+ case sort_by
+ when "top"
+ object["53:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 0_i64
+ when "new", "newest"
+ object["53:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 1_i64
+ else # top
+ object["53:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 0_i64
+ end
+
object_parsed = object.try { |i| Protodec::Any.cast_json(i) }
.try { |i| Protodec::Any.from_json(i) }
.try { |i| Base64.urlsafe_encode(i) }
object2 = {
"80226972:embedded" => {
- "2:string" => ucid,
+ "2:string" => "FEcomment_post_detail_page_web_top_level",
"3:string" => object_parsed,
},
}
diff --git a/src/invidious/routes/api/v1/channels.cr b/src/invidious/routes/api/v1/channels.cr
index a940ee682..503b8c051 100644
--- a/src/invidious/routes/api/v1/channels.cr
+++ b/src/invidious/routes/api/v1/channels.cr
@@ -436,7 +436,7 @@ module Invidious::Routes::API::V1::Channels
if ucid.nil?
response = YoutubeAPI.resolve_url("https://www.youtube.com/post/#{id}")
return error_json(400, "Invalid post ID") if response["error"]?
- ucid = response.dig("endpoint", "browseEndpoint", "browseId").as_s
+ ucid = decode_ucid_from_post_protobuf(response.dig("endpoint", "browseEndpoint", "params").as_s)
else
ucid = ucid.to_s
end
@@ -460,13 +460,15 @@ module Invidious::Routes::API::V1::Channels
format = env.params.query["format"]?
format ||= "json"
+ sort_by = env.params.query["sort_by"]?.try &.downcase
+ sort_by ||= "top"
continuation = env.params.query["continuation"]?
case continuation
when nil, ""
ucid = env.params.query["ucid"]
- comments = Comments.fetch_community_post_comments(ucid, id)
+ comments = Comments.fetch_community_post_comments(ucid, id, sort_by: sort_by)
else
comments = YoutubeAPI.browse(continuation: continuation)
end
diff --git a/src/invidious/routes/channels.cr b/src/invidious/routes/channels.cr
index 508aa3e41..6d2b4465c 100644
--- a/src/invidious/routes/channels.cr
+++ b/src/invidious/routes/channels.cr
@@ -284,7 +284,7 @@ module Invidious::Routes::Channels
response = YoutubeAPI.resolve_url("https://www.youtube.com/post/#{id}")
return error_template(400, "Invalid post ID") if response["error"]?
- ucid = response.dig("endpoint", "browseEndpoint", "browseId").as_s
+ ucid = decode_ucid_from_post_protobuf(response.dig("endpoint", "browseEndpoint", "params").as_s)
post_response = fetch_channel_community_post(ucid, id, locale, "json", thin_mode)
end
From f8febbe2b2fbc618e96cad027619b1acbc8509f4 Mon Sep 17 00:00:00 2001
From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com>
Date: Wed, 25 Jun 2025 23:53:07 -0400
Subject: [PATCH 31/52] format changes
---
src/invidious/channels/community.cr | 12 ++++++------
src/invidious/routes/api/v1/misc.cr | 4 ++--
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/src/invidious/channels/community.cr b/src/invidious/channels/community.cr
index 8927c81bf..43843b119 100644
--- a/src/invidious/channels/community.cr
+++ b/src/invidious/channels/community.cr
@@ -26,9 +26,9 @@ end
def decode_ucid_from_post_protobuf(params)
decoded_protobuf = params.try { |i| URI.decode_www_form(i) }
- .try { |i| Base64.decode(i) }
- .try { |i| IO::Memory.new(i) }
- .try { |i| Protodec::Any.parse(i) }
+ .try { |i| Base64.decode(i) }
+ .try { |i| IO::Memory.new(i) }
+ .try { |i| Protodec::Any.parse(i) }
return decoded_protobuf.try(&.["56:0:embedded"]["2:0:string"].as_s)
end
@@ -36,10 +36,10 @@ end
def fetch_channel_community_post(ucid, post_id, locale, format, thin_mode)
object = {
"56:embedded" => {
- "2:string" => ucid,
- "3:string" => post_id.to_s,
+ "2:string" => ucid,
+ "3:string" => post_id.to_s,
"11:string" => ucid,
- }
+ },
}
params = object.try { |i| Protodec::Any.cast_json(i) }
.try { |i| Protodec::Any.from_json(i) }
diff --git a/src/invidious/routes/api/v1/misc.cr b/src/invidious/routes/api/v1/misc.cr
index 40f2a4397..4ae877a8b 100644
--- a/src/invidious/routes/api/v1/misc.cr
+++ b/src/invidious/routes/api/v1/misc.cr
@@ -197,8 +197,8 @@ module Invidious::Routes::API::V1::Misc
.try { |i| IO::Memory.new(i) }
.try { |i| Protodec::Any.parse(i) }
- ucid = decoded_protobuf.try(&.["56:0:embedded"]["2:0:string"].as_s)
- post_id = decoded_protobuf.try(&.["56:0:embedded"]["3:1:string"].as_s)
+ ucid = decoded_protobuf.try(&.["56:0:embedded"]["2:0:string"].as_s)
+ post_id = decoded_protobuf.try(&.["56:0:embedded"]["3:1:string"].as_s)
else
ucid = sub_endpoint["browseId"]? if sub_endpoint["browseId"]? && sub_endpoint["browseId"]?.try &.as_s.starts_with? "UC"
post_id = nil
From b0c9f87fbea9a527b1e96774de97dc366e76df12 Mon Sep 17 00:00:00 2001
From: Samantaz Fox
Date: Thu, 26 Jun 2025 19:09:52 +0000
Subject: [PATCH 32/52] Fix missing .id to retrieve first playlist video ID
This was missed in the review of PR 5196
---
src/invidious/routes/embed.cr | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/invidious/routes/embed.cr b/src/invidious/routes/embed.cr
index 930e4915a..721a57f83 100644
--- a/src/invidious/routes/embed.cr
+++ b/src/invidious/routes/embed.cr
@@ -20,7 +20,7 @@ module Invidious::Routes::Embed
return error_template(500, ex)
end
- url = "/embed/#{first_playlist_video}?#{env.params.query}"
+ url = "/embed/#{first_playlist_video.id}?#{env.params.query}"
if env.params.query.size > 0
url += "?#{env.params.query}"
From 64ac3b5203a94291bad709ffcaae665e50544485 Mon Sep 17 00:00:00 2001
From: epicsam123 <92618898+epicsam123@users.noreply.github.com>
Date: Thu, 26 Jun 2025 18:40:06 -0400
Subject: [PATCH 33/52] add missing noreferrers
---
assets/js/player.js | 4 ++--
assets/js/watch.js | 2 +-
src/invidious/frontend/watch_page.cr | 2 +-
src/invidious/views/user/data_control.ecr | 2 +-
4 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/assets/js/player.js b/assets/js/player.js
index f32c9b561..1a20c932b 100644
--- a/assets/js/player.js
+++ b/assets/js/player.js
@@ -180,7 +180,7 @@ var shareOptions = {
};
if (location.pathname.startsWith('/embed/')) {
- var overlay_content = '';
+ var overlay_content = '';
player.overlay({
overlays: [
{ start: 'loadstart', content: overlay_content, end: 'playing', align: 'top'},
@@ -450,7 +450,7 @@ if (!video_data.params.listen && video_data.params.annotations) {
if (target === 'current') {
location.href = path;
} else if (target === 'new') {
- open(path, '_blank');
+ open(path, '_blank', 'noopener,noreferrer')
}
});
diff --git a/assets/js/watch.js b/assets/js/watch.js
index d869d40d1..ee9c29e89 100644
--- a/assets/js/watch.js
+++ b/assets/js/watch.js
@@ -141,7 +141,7 @@ function get_reddit_comments() {
\
\
\
- {redditPermalinkText} \
+ {redditPermalinkText} \
\
\
{contentHtml}
\
diff --git a/src/invidious/frontend/watch_page.cr b/src/invidious/frontend/watch_page.cr
index 15d925e34..c0926164e 100644
--- a/src/invidious/frontend/watch_page.cr
+++ b/src/invidious/frontend/watch_page.cr
@@ -34,7 +34,7 @@ module Invidious::Frontend::WatchPage
str << " class=\"pure-form pure-form-stacked\""
str << " action='#{url}'"
str << " method='post'"
- str << " rel='noopener'"
+ str << " rel='noopener noreferrer'"
str << " target='_blank'>"
str << '\n'
diff --git a/src/invidious/views/user/data_control.ecr b/src/invidious/views/user/data_control.ecr
index 9ce42c994..e57926f50 100644
--- a/src/invidious/views/user/data_control.ecr
+++ b/src/invidious/views/user/data_control.ecr
@@ -14,7 +14,7 @@
From 803311713d43860bcdbba81008544ef2d67bc657 Mon Sep 17 00:00:00 2001
From: ChunkyProgrammer <78101139+ChunkyProgrammer@users.noreply.github.com>
Date: Thu, 26 Jun 2025 15:34:45 -0400
Subject: [PATCH 34/52] make `sort_by` code more legible
---
src/invidious/comments/youtube.cr | 40 +++++++++++++++----------------
1 file changed, 20 insertions(+), 20 deletions(-)
diff --git a/src/invidious/comments/youtube.cr b/src/invidious/comments/youtube.cr
index 184036569..e923b2f8d 100644
--- a/src/invidious/comments/youtube.cr
+++ b/src/invidious/comments/youtube.cr
@@ -17,11 +17,20 @@ module Invidious::Comments
end
def fetch_community_post_comments(ucid, post_id, sort_by = "top")
+ case sort_by
+ when "top"
+ sort_by_val = 0_i64
+ when "new", "newest"
+ sort_by_val = 1_i64
+ else # top
+ sort_by_val = 0_i64
+ end
+
object = {
"2:string" => "posts",
"53:embedded" => {
"4:embedded" => {
- "6:varint" => 0_i64,
+ "6:varint" => sort_by_val,
"15:varint" => 2_i64,
"25:varint" => 0_i64,
"29:string" => post_id,
@@ -32,15 +41,6 @@ module Invidious::Comments
},
}
- case sort_by
- when "top"
- object["53:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 0_i64
- when "new", "newest"
- object["53:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 1_i64
- else # top
- object["53:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 0_i64
- end
-
object_parsed = object.try { |i| Protodec::Any.cast_json(i) }
.try { |i| Protodec::Any.from_json(i) }
.try { |i| Base64.urlsafe_encode(i) }
@@ -324,6 +324,15 @@ module Invidious::Comments
end
def produce_continuation(video_id, cursor = "", sort_by = "top")
+ case sort_by
+ when "top"
+ sort_by_val = 0_i64
+ when "new", "newest"
+ sort_by_val = 1_i64
+ else # top
+ sort_by_val = 0_i64
+ end
+
object = {
"2:embedded" => {
"2:string" => video_id,
@@ -344,21 +353,12 @@ module Invidious::Comments
"1:string" => cursor,
"4:embedded" => {
"4:string" => video_id,
- "6:varint" => 0_i64,
+ "6:varint" => sort_by_val,
},
"5:varint" => 20_i64,
},
}
- case sort_by
- when "top"
- object["6:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 0_i64
- when "new", "newest"
- object["6:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 1_i64
- else # top
- object["6:embedded"].as(Hash)["4:embedded"].as(Hash)["6:varint"] = 0_i64
- end
-
continuation = object.try { |i| Protodec::Any.cast_json(i) }
.try { |i| Protodec::Any.from_json(i) }
.try { |i| Base64.urlsafe_encode(i) }
From 227c041b86c97a5197c673af7efadbaf649f812d Mon Sep 17 00:00:00 2001
From: Nami Sunami
Date: Sat, 28 Jun 2025 11:38:31 +0200
Subject: [PATCH 35/52] fix(config.example.yml): Fix typo (effet -> effect)
---
config/config.example.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/config/config.example.yml b/config/config.example.yml
index 8d3e62120..e8ab658bb 100644
--- a/config/config.example.yml
+++ b/config/config.example.yml
@@ -865,7 +865,7 @@ default_user_preferences:
##
## Default dash video quality.
##
- ## Note: this setting only takes effet if the
+ ## Note: this setting only takes effect if the
## 'quality' parameter is set to "dash".
##
## Accepted values:
From 24252b836ceee3bbcfe98a91963439b1bee44dcc Mon Sep 17 00:00:00 2001
From: epicsam123 <92618898+epicsam123@users.noreply.github.com>
Date: Mon, 30 Jun 2025 22:38:30 -0400
Subject: [PATCH 36/52] add back semicolon
---
assets/js/player.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/assets/js/player.js b/assets/js/player.js
index 1a20c932b..7ab3d0e78 100644
--- a/assets/js/player.js
+++ b/assets/js/player.js
@@ -450,7 +450,7 @@ if (!video_data.params.listen && video_data.params.annotations) {
if (target === 'current') {
location.href = path;
} else if (target === 'new') {
- open(path, '_blank', 'noopener,noreferrer')
+ open(path, '_blank', 'noopener,noreferrer');
}
});
From a84bb1d22ed4d59deb50d8ecf72fac1e3a8f3ff4 Mon Sep 17 00:00:00 2001
From: fieryhenry <74794355+fieryhenry@users.noreply.github.com>
Date: Fri, 18 Jul 2025 19:02:50 +0000
Subject: [PATCH 37/52] Fix `TRUE` number of notifications
`update_ticker_count` used to use STORAGE_KEY_STREAM to get the number of notifications which is a boolean value, now it uses STORAGE_KEY_NOTIF_COUNT which is an integer
---
assets/js/notifications.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/assets/js/notifications.js b/assets/js/notifications.js
index 55b7a15c6..b8d73a829 100644
--- a/assets/js/notifications.js
+++ b/assets/js/notifications.js
@@ -77,7 +77,7 @@ function create_notification_stream(subscriptions) {
function update_ticker_count() {
var notification_ticker = document.getElementById('notification_ticker');
- const notification_count = helpers.storage.get(STORAGE_KEY_STREAM);
+ const notification_count = helpers.storage.get(STORAGE_KEY_NOTIF_COUNT);
if (notification_count > 0) {
notification_ticker.innerHTML =
'' + notification_count + ' ';
From 3335bc8c388677517e5c4b86eb917fd3fdace2f8 Mon Sep 17 00:00:00 2001
From: fieryhenry <74794355+fieryhenry@users.noreply.github.com>
Date: Fri, 18 Jul 2025 19:07:41 +0000
Subject: [PATCH 38/52] Get a count of 0 if STORAGE_KEY_NOTIF_COUNT is not
present in storage
Not sure if this is necessary as I think it should always be present in storage, but just in case it isn't
---
assets/js/notifications.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/assets/js/notifications.js b/assets/js/notifications.js
index b8d73a829..16d9866d9 100644
--- a/assets/js/notifications.js
+++ b/assets/js/notifications.js
@@ -77,7 +77,7 @@ function create_notification_stream(subscriptions) {
function update_ticker_count() {
var notification_ticker = document.getElementById('notification_ticker');
- const notification_count = helpers.storage.get(STORAGE_KEY_NOTIF_COUNT);
+ const notification_count = helpers.storage.get(STORAGE_KEY_NOTIF_COUNT) || 0;
if (notification_count > 0) {
notification_ticker.innerHTML =
'' + notification_count + ' ';
From 1ae0f45b0e5dca696986925a06ef4f4b4f43894b Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 12 Aug 2025 15:06:16 +0200
Subject: [PATCH 39/52] Bump actions/checkout from 4 to 5 (#5415)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)
---
updated-dependencies:
- dependency-name: actions/checkout
dependency-version: '5'
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/build-nightly-container.yml | 2 +-
.github/workflows/build-stable-container.yml | 2 +-
.github/workflows/ci.yml | 8 ++++----
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/build-nightly-container.yml b/.github/workflows/build-nightly-container.yml
index 4149bd0bc..2f46997af 100644
--- a/.github/workflows/build-nightly-container.yml
+++ b/.github/workflows/build-nightly-container.yml
@@ -21,7 +21,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
diff --git a/.github/workflows/build-stable-container.yml b/.github/workflows/build-stable-container.yml
index 1a23e68ca..74fbd74b4 100644
--- a/.github/workflows/build-stable-container.yml
+++ b/.github/workflows/build-stable-container.yml
@@ -12,7 +12,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9d6a930ad..7a4a70033 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -48,7 +48,7 @@ jobs:
stable: false
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
with:
submodules: true
@@ -87,7 +87,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Build Docker
run: docker compose build --build-arg release=0
@@ -103,7 +103,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
@@ -131,7 +131,7 @@ jobs:
continue-on-error: true
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
with:
submodules: true
From 875d8e7e41b58cce007cd3cc5fa8d615815c593a Mon Sep 17 00:00:00 2001
From: Eugene Pakhomov
Date: Wed, 13 Aug 2025 13:26:48 +0300
Subject: [PATCH 40/52] Persist caption settings
---
assets/js/player.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/assets/js/player.js b/assets/js/player.js
index f32c9b561..cce0b030a 100644
--- a/assets/js/player.js
+++ b/assets/js/player.js
@@ -5,6 +5,7 @@ var video_data = JSON.parse(document.getElementById('video_data').textContent);
var options = {
liveui: true,
playbackRates: [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0],
+ persistTextTrackSettings: true,
controlBar: {
children: [
'playToggle',
From dd8086e6d9d4fe79e5645cdc494a22877624cc86 Mon Sep 17 00:00:00 2001
From: Kristian Vos
Date: Wed, 13 Aug 2025 15:43:54 +0200
Subject: [PATCH 41/52] fix: fetching channel playlists returned 500 error
---
src/invidious/channels/playlists.cr | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/invidious/channels/playlists.cr b/src/invidious/channels/playlists.cr
index 9b45d0c86..cba1abd9c 100644
--- a/src/invidious/channels/playlists.cr
+++ b/src/invidious/channels/playlists.cr
@@ -6,19 +6,19 @@ def fetch_channel_playlists(ucid, author, continuation, sort_by)
case sort_by
when "last", "last_added"
# Equivalent to "&sort=lad"
- # {"2:string": "playlists", "3:varint": 4, "4:varint": 1, "6:varint": 1}
- "EglwbGF5bGlzdHMYBCABMAE%3D"
+ # {"2:string": "playlists", "3:varint": 4, "4:varint": 1, "6:varint": 1, "110:embedded": {"1:embedded": {"8:string": ""}}}
+ "EglwbGF5bGlzdHMYBCABMAHyBgQKAkIA"
when "oldest", "oldest_created"
# formerly "&sort=da"
# Not available anymore :c or maybe ??
- # {"2:string": "playlists", "3:varint": 2, "4:varint": 1, "6:varint": 1}
- "EglwbGF5bGlzdHMYAiABMAE%3D"
+ # {"2:string": "playlists", "3:varint": 2, "4:varint": 1, "6:varint": 1, "110:embedded": {"1:embedded": {"8:string": ""}}}
+ "EglwbGF5bGlzdHMYAiABMAHyBgQKAkIA"
# {"2:string": "playlists", "3:varint": 1, "4:varint": 1, "6:varint": 1}
# "EglwbGF5bGlzdHMYASABMAE%3D"
when "newest", "newest_created"
# Formerly "&sort=dd"
- # {"2:string": "playlists", "3:varint": 3, "4:varint": 1, "6:varint": 1}
- "EglwbGF5bGlzdHMYAyABMAE%3D"
+ # {"2:string": "playlists", "3:varint": 3, "4:varint": 1, "6:varint": 1, "110:embedded": {"1:embedded": {"8:string": ""}}}
+ "EglwbGF5bGlzdHMYAyABMAHyBgQKAkIA"
end
initial_data = YoutubeAPI.browse(ucid, params: params || "")
From 67f93e55d8e9be4c81a58920c4651d2eb1327fd6 Mon Sep 17 00:00:00 2001
From: syeopite
Date: Sat, 23 Aug 2025 03:35:59 -0700
Subject: [PATCH 42/52] Fix "ex" variable collision in invidious.cr
The exception handling for database connections results in an
`ex` variable which Ameba sees as overshadowing the `ex` used by the
`ex` block arg used to define the HTTP status code 500 handler below.
Although this is a non-issue since the db connection exception handling
will cause Invidious to exit, Ameba's nature as a static checker means
that it isn't aware of this.
The simplest fix without a dirty ameba ignore comment is to rename `ex`
within the Kemal handler block below, since `ex` within a begin rescue
block is a Crystal convention that will also cause Ameba to raise when
not adhered to.
---
src/invidious.cr | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/invidious.cr b/src/invidious.cr
index 2d244dd20..197b150ca 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -62,8 +62,8 @@ HMAC_KEY = CONFIG.hmac_key
PG_DB = begin
DB.open CONFIG.database_url
-rescue exc
- puts "Failed to connect to PostgreSQL database: #{exc.cause.try &.message}"
+rescue ex
+ puts "Failed to connect to PostgreSQL database: #{ex.cause.try &.message}"
puts "Check your 'config.yml' database settings or PostgreSQL settings."
exit(1)
end
@@ -227,8 +227,8 @@ error 404 do |env|
Invidious::Routes::ErrorRoutes.error_404(env)
end
-error 500 do |env, ex|
- error_template(500, ex)
+error 500 do |env, exception|
+ error_template(500, exception)
end
static_headers do |env|
From 89c8b1b901062c729370370116aa9127a39cd214 Mon Sep 17 00:00:00 2001
From: Fijxu
Date: Tue, 2 Sep 2025 10:57:29 -0400
Subject: [PATCH 43/52] CI: fix wrong if statement for build-docker job (#5442)
---
.github/workflows/ci.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 52559825d..ce166b7b7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -99,7 +99,7 @@ jobs:
- uses: actions/checkout@v5
- name: Use ARM64 Dockerfile if ARM64
- if: ${{ matrix.name }} == "ARM64"
+ if: ${{ matrix.name == 'ARM64' }}
run: sed -i 's/Dockerfile/Dockerfile.arm64/' docker-compose.yml
- name: Build Docker
From 324a416fd47cb7adbfdda20622dd0f47b60dd661 Mon Sep 17 00:00:00 2001
From: Emilien <4016501+unixfox@users.noreply.github.com>
Date: Sat, 3 May 2025 01:10:12 +0200
Subject: [PATCH 44/52] initial support for base_url with invidious companion +
proxy invidious_companion
---
config/config.example.yml | 6 ++--
src/invidious/routes/companion.cr | 37 +++++++++++++++++++++
src/invidious/routes/embed.cr | 8 +++--
src/invidious/routes/watch.cr | 8 +++--
src/invidious/routing.cr | 9 ++++-
src/invidious/yt_backend/connection_pool.cr | 34 ++++++++++++++-----
src/invidious/yt_backend/youtube_api.cr | 30 ++++++++++-------
7 files changed, 103 insertions(+), 29 deletions(-)
create mode 100644 src/invidious/routes/companion.cr
diff --git a/config/config.example.yml b/config/config.example.yml
index e8ab658bb..60fbc825e 100644
--- a/config/config.example.yml
+++ b/config/config.example.yml
@@ -75,7 +75,7 @@ db:
## If you are using a reverse proxy then you will probably need to
## configure the public_url to be the same as the domain used for Invidious.
## Also apply when used from an external IP address (without a domain).
-## Examples: https://MYINVIDIOUSDOMAIN or http://192.168.1.100:8282
+## Examples: https://MYINVIDIOUSDOMAIN/companion or http://192.168.1.100:8282/companion
##
## Both parameter can have identical URL when Invidious is hosted in
## an internal network or at home or locally (localhost).
@@ -84,8 +84,8 @@ db:
## Default:
##
#invidious_companion:
-# - private_url: "http://localhost:8282"
-# public_url: "http://localhost:8282"
+# - private_url: "http://localhost:8282/companion"
+# public_url: "http://localhost:8282/companion"
##
## API key for Invidious companion, used for securing the communication
diff --git a/src/invidious/routes/companion.cr b/src/invidious/routes/companion.cr
new file mode 100644
index 000000000..23b62e9dc
--- /dev/null
+++ b/src/invidious/routes/companion.cr
@@ -0,0 +1,37 @@
+module Invidious::Routes::Companion
+ # /companion
+ def self.get_companion(env)
+ url = env.request.path.lchop("/companion")
+
+ begin
+ COMPANION_POOL.client &.get(url, env.request.header) do |resp|
+ return self.proxy_companion(env, resp)
+ end
+ rescue ex
+ end
+ end
+
+ def self.options_companion(env)
+ url = env.request.path.lchop("/companion")
+
+ begin
+ COMPANION_POOL.client &.options(url, env.request.header) do |resp|
+ return self.proxy_companion(env, resp)
+ end
+ rescue ex
+ end
+ end
+
+ private def self.proxy_companion(env, response)
+ env.response.status_code = response.status_code
+ response.headers.each do |key, value|
+ env.response.headers[key] = value
+ end
+
+ if response.status_code >= 300
+ return env.response.headers.delete("Transfer-Encoding")
+ end
+
+ return proxy_file(response, env)
+ end
+end
diff --git a/src/invidious/routes/embed.cr b/src/invidious/routes/embed.cr
index 721a57f83..2fb7bebf6 100644
--- a/src/invidious/routes/embed.cr
+++ b/src/invidious/routes/embed.cr
@@ -209,10 +209,14 @@ module Invidious::Routes::Embed
if CONFIG.invidious_companion.present?
invidious_companion = CONFIG.invidious_companion.sample
+ invidious_companion_urls = CONFIG.invidious_companion.map do |companion|
+ uri =
+ "#{companion.public_url.scheme}://#{companion.public_url.host}#{companion.public_url.port ? ":#{companion.public_url.port}" : ""}"
+ end.join(" ")
env.response.headers["Content-Security-Policy"] =
env.response.headers["Content-Security-Policy"]
- .gsub("media-src", "media-src #{invidious_companion.public_url}")
- .gsub("connect-src", "connect-src #{invidious_companion.public_url}")
+ .gsub("media-src", "media-src #{invidious_companion_urls}")
+ .gsub("connect-src", "connect-src #{invidious_companion_urls}")
end
rendered "embed"
diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr
index e777b3f18..a50a146d3 100644
--- a/src/invidious/routes/watch.cr
+++ b/src/invidious/routes/watch.cr
@@ -194,10 +194,14 @@ module Invidious::Routes::Watch
if CONFIG.invidious_companion.present?
invidious_companion = CONFIG.invidious_companion.sample
+ invidious_companion_urls = CONFIG.invidious_companion.map do |companion|
+ uri =
+ "#{companion.public_url.scheme}://#{companion.public_url.host}#{companion.public_url.port ? ":#{companion.public_url.port}" : ""}"
+ end.join(" ")
env.response.headers["Content-Security-Policy"] =
env.response.headers["Content-Security-Policy"]
- .gsub("media-src", "media-src #{invidious_companion.public_url}")
- .gsub("connect-src", "connect-src #{invidious_companion.public_url}")
+ .gsub("media-src", "media-src #{invidious_companion_urls}")
+ .gsub("connect-src", "connect-src #{invidious_companion_urls}")
end
templated "watch"
diff --git a/src/invidious/routing.cr b/src/invidious/routing.cr
index 46b71f1f9..b95ac7067 100644
--- a/src/invidious/routing.cr
+++ b/src/invidious/routing.cr
@@ -188,7 +188,7 @@ module Invidious::Routing
end
# -------------------
- # Media proxy routes
+ # Proxy routes
# -------------------
def register_api_manifest_routes
@@ -223,6 +223,13 @@ module Invidious::Routing
get "/vi/:id/:name", Routes::Images, :thumbnails
end
+ def register_companion_routes
+ if CONFIG.invidious_companion.present?
+ get "/companion/*", Routes::Companion, :get_companion
+ options "/companion/*", Routes::Companion, :options_companion
+ end
+ end
+
# -------------------
# API routes
# -------------------
diff --git a/src/invidious/yt_backend/connection_pool.cr b/src/invidious/yt_backend/connection_pool.cr
index 0daed46c5..97ce7c403 100644
--- a/src/invidious/yt_backend/connection_pool.cr
+++ b/src/invidious/yt_backend/connection_pool.cr
@@ -46,8 +46,22 @@ struct YoutubeConnectionPool
end
end
+class CompanionWrapper
+ property client : HTTP::Client
+ property companion : Config::CompanionConfig
+
+ def initialize(companion : Config::CompanionConfig)
+ @companion = companion
+ @client = HTTP::Client.new(companion.private_url)
+ end
+
+ def close
+ @client.close
+ end
+end
+
struct CompanionConnectionPool
- property pool : DB::Pool(HTTP::Client)
+ property pool : DB::Pool(CompanionWrapper)
def initialize(capacity = 5, timeout = 5.0)
options = DB::Pool::Options.new(
@@ -57,26 +71,28 @@ struct CompanionConnectionPool
checkout_timeout: timeout
)
- @pool = DB::Pool(HTTP::Client).new(options) do
+ @pool = DB::Pool(CompanionWrapper).new(options) do
companion = CONFIG.invidious_companion.sample
- next make_client(companion.private_url, use_http_proxy: false)
+ client = make_client(companion.private_url, use_http_proxy: false)
+ CompanionWrapper.new(companion: companion)
end
end
def client(&)
- conn = pool.checkout
+ wrapper = pool.checkout
begin
- response = yield conn
+ response = yield wrapper
rescue ex
- conn.close
+ wrapper.client.close
companion = CONFIG.invidious_companion.sample
- conn = make_client(companion.private_url, use_http_proxy: false)
+ client = make_client(companion.private_url, use_http_proxy: false)
+ wrapper = CompanionWrapper.new(companion: companion)
- response = yield conn
+ response = yield wrapper
ensure
- pool.release(conn)
+ pool.release(wrapper)
end
response
diff --git a/src/invidious/yt_backend/youtube_api.cr b/src/invidious/yt_backend/youtube_api.cr
index d287a42f7..f87e3091d 100644
--- a/src/invidious/yt_backend/youtube_api.cr
+++ b/src/invidious/yt_backend/youtube_api.cr
@@ -701,22 +701,28 @@ module YoutubeAPI
# Send the POST request
begin
- response = COMPANION_POOL.client &.post(endpoint, headers: headers, body: data.to_json)
- body = response.body
- if (response.status_code != 200)
- raise Exception.new(
- "Error while communicating with Invidious companion: \
- status code: #{response.status_code} and body: #{body.dump}"
- )
+ response_body = ""
+
+ COMPANION_POOL.client do |wrapper|
+ companion_base_url = wrapper.companion.private_url.path
+ puts "Using companion: #{wrapper.companion.private_url}"
+
+ response = wrapper.client.post(companion_base_url + endpoint, headers: headers, body: data.to_json)
+ response_body = response.body
+
+ if response.status_code != 200
+ raise Exception.new(
+ "Error while communicating with Invidious companion: " \
+ "status code: #{response.status_code} and body: #{response_body.dump}"
+ )
+ end
end
+
+ # Convert result to Hash
+ return JSON.parse(response_body).as_h
rescue ex
raise InfoException.new("Error while communicating with Invidious companion: " + (ex.message || "no extra info found"))
end
-
- # Convert result to Hash
- initial_data = JSON.parse(body).as_h
-
- return initial_data
end
####################################################################
From 42b955d713c54632f1886641dcedac27ee625c8a Mon Sep 17 00:00:00 2001
From: Emilien <4016501+unixfox@users.noreply.github.com>
Date: Sat, 14 Jun 2025 18:01:44 +0200
Subject: [PATCH 45/52] chore: add the suggestions
---
src/invidious/routes/before_all.cr | 1 +
src/invidious/routes/companion.cr | 6 +-----
src/invidious/yt_backend/connection_pool.cr | 15 ++++++++++-----
src/invidious/yt_backend/youtube_api.cr | 16 ++++------------
4 files changed, 16 insertions(+), 22 deletions(-)
diff --git a/src/invidious/routes/before_all.cr b/src/invidious/routes/before_all.cr
index b52696687..63b935ec6 100644
--- a/src/invidious/routes/before_all.cr
+++ b/src/invidious/routes/before_all.cr
@@ -63,6 +63,7 @@ module Invidious::Routes::BeforeAll
"/videoplayback",
"/latest_version",
"/download",
+ "/companion/",
}.any? { |r| env.request.resource.starts_with? r }
if env.request.cookies.has_key? "SID"
diff --git a/src/invidious/routes/companion.cr b/src/invidious/routes/companion.cr
index 23b62e9dc..cd7ed4227 100644
--- a/src/invidious/routes/companion.cr
+++ b/src/invidious/routes/companion.cr
@@ -28,10 +28,6 @@ module Invidious::Routes::Companion
env.response.headers[key] = value
end
- if response.status_code >= 300
- return env.response.headers.delete("Transfer-Encoding")
- end
-
- return proxy_file(response, env)
+ return IO.copy response.body_io, env.response
end
end
diff --git a/src/invidious/yt_backend/connection_pool.cr b/src/invidious/yt_backend/connection_pool.cr
index 97ce7c403..45455a8a7 100644
--- a/src/invidious/yt_backend/connection_pool.cr
+++ b/src/invidious/yt_backend/connection_pool.cr
@@ -46,13 +46,18 @@ struct YoutubeConnectionPool
end
end
-class CompanionWrapper
+# Packages a `HTTP::Client` to an Invidious companion instance alongside the configuration for that instance.
+#
+# This is used as the resource for the `CompanionPool` as to allow the ability to
+# proxy the requests to Invidious companion from Invidious directly.
+# Instead of setting up routes in a reverse proxy.
+struct CompanionWrapper
property client : HTTP::Client
property companion : Config::CompanionConfig
def initialize(companion : Config::CompanionConfig)
@companion = companion
- @client = HTTP::Client.new(companion.private_url)
+ @client = make_client(companion.private_url, use_http_proxy: false)
end
def close
@@ -73,7 +78,7 @@ struct CompanionConnectionPool
@pool = DB::Pool(CompanionWrapper).new(options) do
companion = CONFIG.invidious_companion.sample
- client = make_client(companion.private_url, use_http_proxy: false)
+ make_client(companion.private_url, use_http_proxy: false)
CompanionWrapper.new(companion: companion)
end
end
@@ -84,10 +89,10 @@ struct CompanionConnectionPool
begin
response = yield wrapper
rescue ex
- wrapper.client.close
+ wrapper.close
companion = CONFIG.invidious_companion.sample
- client = make_client(companion.private_url, use_http_proxy: false)
+ make_client(companion.private_url, use_http_proxy: false)
wrapper = CompanionWrapper.new(companion: companion)
response = yield wrapper
diff --git a/src/invidious/yt_backend/youtube_api.cr b/src/invidious/yt_backend/youtube_api.cr
index f87e3091d..4b39acd74 100644
--- a/src/invidious/yt_backend/youtube_api.cr
+++ b/src/invidious/yt_backend/youtube_api.cr
@@ -701,25 +701,17 @@ module YoutubeAPI
# Send the POST request
begin
- response_body = ""
+ response_body = Hash(String, JSON::Any).new
COMPANION_POOL.client do |wrapper|
companion_base_url = wrapper.companion.private_url.path
- puts "Using companion: #{wrapper.companion.private_url}"
- response = wrapper.client.post(companion_base_url + endpoint, headers: headers, body: data.to_json)
- response_body = response.body
-
- if response.status_code != 200
- raise Exception.new(
- "Error while communicating with Invidious companion: " \
- "status code: #{response.status_code} and body: #{response_body.dump}"
- )
+ wrapper.client.post("#{companion_base_url}#{endpoint}", headers: headers, body: data.to_json) do | response |
+ response_body = JSON.parse(response.body_io).as_h
end
end
- # Convert result to Hash
- return JSON.parse(response_body).as_h
+ return response_body
rescue ex
raise InfoException.new("Error while communicating with Invidious companion: " + (ex.message || "no extra info found"))
end
From cba2adc6ef19d52736356a84af21fa513c7b3f74 Mon Sep 17 00:00:00 2001
From: Emilien <4016501+unixfox@users.noreply.github.com>
Date: Sat, 14 Jun 2025 19:10:52 +0200
Subject: [PATCH 46/52] fix csp + progress proxy + allow omit public_url
---
config/config.example.yml | 8 ++++++++
src/invidious/config.cr | 11 +++++++++++
src/invidious/routes/companion.cr | 23 +++++++++++++++++------
src/invidious/routes/embed.cr | 13 ++++++++-----
src/invidious/routes/watch.cr | 13 ++++++++-----
src/invidious/routing.cr | 1 +
6 files changed, 53 insertions(+), 16 deletions(-)
diff --git a/config/config.example.yml b/config/config.example.yml
index 60fbc825e..cabbecfd7 100644
--- a/config/config.example.yml
+++ b/config/config.example.yml
@@ -80,12 +80,20 @@ db:
## Both parameter can have identical URL when Invidious is hosted in
## an internal network or at home or locally (localhost).
##
+## NOTE: If public_url is omitted, Invidious will use its built-in proxy
+## to route companion requests through /companion, which is useful for
+## simple setups where companion runs on the same network. When using
+## the built-in proxy, CSP headers are not modified since requests
+## stay within the same domain.
+##
## Accepted values: "http(s)://:"
## Default:
##
#invidious_companion:
# - private_url: "http://localhost:8282/companion"
# public_url: "http://localhost:8282/companion"
+# # Example with built-in proxy (omit public_url):
+# # - private_url: "http://localhost:8282/companion"
##
## API key for Invidious companion, used for securing the communication
diff --git a/src/invidious/config.cr b/src/invidious/config.cr
index 4d69854c4..e47e405ce 100644
--- a/src/invidious/config.cr
+++ b/src/invidious/config.cr
@@ -82,6 +82,9 @@ class Config
@[YAML::Field(converter: Preferences::URIConverter)]
property public_url : URI = URI.parse("")
+
+ # Indicates if this companion instance uses the built-in proxy
+ property builtin_proxy : Bool = false
end
# Number of threads to use for crawling videos from channels (for updating subscriptions)
@@ -271,6 +274,14 @@ class Config
puts "Config: The value of 'invidious_companion_key' needs to be a size of 16 characters."
exit(1)
end
+
+ # Set public_url to built-in proxy path when omitted
+ config.invidious_companion.each do |companion|
+ if companion.public_url.to_s.empty?
+ companion.public_url = URI.parse("/companion")
+ companion.builtin_proxy = true
+ end
+ end
elsif config.signature_server
puts("WARNING: inv-sig-helper is deprecated. Please switch to Invidious companion: https://docs.invidious.io/companion-installation/")
else
diff --git a/src/invidious/routes/companion.cr b/src/invidious/routes/companion.cr
index cd7ed4227..bcfbad6bf 100644
--- a/src/invidious/routes/companion.cr
+++ b/src/invidious/routes/companion.cr
@@ -1,22 +1,33 @@
module Invidious::Routes::Companion
# /companion
def self.get_companion(env)
- url = env.request.path.lchop("/companion")
+ url = env.request.path
+ if env.request.query
+ url += "?#{env.request.query}"
+ end
begin
- COMPANION_POOL.client &.get(url, env.request.header) do |resp|
- return self.proxy_companion(env, resp)
+ COMPANION_POOL.client do |wrapper|
+ puts env.request.headers
+ wrapper.client.get(url, env.request.headers) do |resp|
+ return self.proxy_companion(env, resp)
+ end
end
rescue ex
end
end
def self.options_companion(env)
- url = env.request.path.lchop("/companion")
+ url = env.request.path
+ if env.request.query
+ url += "?#{env.request.query}"
+ end
begin
- COMPANION_POOL.client &.options(url, env.request.header) do |resp|
- return self.proxy_companion(env, resp)
+ COMPANION_POOL.client do |wrapper|
+ wrapper.client.options(url, env.request.headers) do |resp|
+ return self.proxy_companion(env, resp)
+ end
end
rescue ex
end
diff --git a/src/invidious/routes/embed.cr b/src/invidious/routes/embed.cr
index 2fb7bebf6..1318b2909 100644
--- a/src/invidious/routes/embed.cr
+++ b/src/invidious/routes/embed.cr
@@ -209,14 +209,17 @@ module Invidious::Routes::Embed
if CONFIG.invidious_companion.present?
invidious_companion = CONFIG.invidious_companion.sample
- invidious_companion_urls = CONFIG.invidious_companion.map do |companion|
+ invidious_companion_urls = CONFIG.invidious_companion.reject(&.builtin_proxy).map do |companion|
uri =
"#{companion.public_url.scheme}://#{companion.public_url.host}#{companion.public_url.port ? ":#{companion.public_url.port}" : ""}"
end.join(" ")
- env.response.headers["Content-Security-Policy"] =
- env.response.headers["Content-Security-Policy"]
- .gsub("media-src", "media-src #{invidious_companion_urls}")
- .gsub("connect-src", "connect-src #{invidious_companion_urls}")
+
+ if !invidious_companion_urls.empty?
+ env.response.headers["Content-Security-Policy"] =
+ env.response.headers["Content-Security-Policy"]
+ .gsub("media-src", "media-src #{invidious_companion_urls}")
+ .gsub("connect-src", "connect-src #{invidious_companion_urls}")
+ end
end
rendered "embed"
diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr
index a50a146d3..838934579 100644
--- a/src/invidious/routes/watch.cr
+++ b/src/invidious/routes/watch.cr
@@ -194,14 +194,17 @@ module Invidious::Routes::Watch
if CONFIG.invidious_companion.present?
invidious_companion = CONFIG.invidious_companion.sample
- invidious_companion_urls = CONFIG.invidious_companion.map do |companion|
+ invidious_companion_urls = CONFIG.invidious_companion.reject(&.builtin_proxy).map do |companion|
uri =
"#{companion.public_url.scheme}://#{companion.public_url.host}#{companion.public_url.port ? ":#{companion.public_url.port}" : ""}"
end.join(" ")
- env.response.headers["Content-Security-Policy"] =
- env.response.headers["Content-Security-Policy"]
- .gsub("media-src", "media-src #{invidious_companion_urls}")
- .gsub("connect-src", "connect-src #{invidious_companion_urls}")
+
+ if !invidious_companion_urls.empty?
+ env.response.headers["Content-Security-Policy"] =
+ env.response.headers["Content-Security-Policy"]
+ .gsub("media-src", "media-src #{invidious_companion_urls}")
+ .gsub("connect-src", "connect-src #{invidious_companion_urls}")
+ end
end
templated "watch"
diff --git a/src/invidious/routing.cr b/src/invidious/routing.cr
index b95ac7067..a51bb4b67 100644
--- a/src/invidious/routing.cr
+++ b/src/invidious/routing.cr
@@ -46,6 +46,7 @@ module Invidious::Routing
self.register_api_v1_routes
self.register_api_manifest_routes
self.register_video_playback_routes
+ self.register_companion_routes
end
# -------------------
From 1653dd629e34bf4e5dc081ac15c070607e7d2908 Mon Sep 17 00:00:00 2001
From: Emilien <4016501+unixfox@users.noreply.github.com>
Date: Sat, 14 Jun 2025 22:35:11 +0200
Subject: [PATCH 47/52] fix formatting
---
src/invidious/routes/embed.cr | 2 +-
src/invidious/routes/watch.cr | 2 +-
src/invidious/yt_backend/connection_pool.cr | 2 +-
src/invidious/yt_backend/youtube_api.cr | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/invidious/routes/embed.cr b/src/invidious/routes/embed.cr
index 1318b2909..6b0887d52 100644
--- a/src/invidious/routes/embed.cr
+++ b/src/invidious/routes/embed.cr
@@ -213,7 +213,7 @@ module Invidious::Routes::Embed
uri =
"#{companion.public_url.scheme}://#{companion.public_url.host}#{companion.public_url.port ? ":#{companion.public_url.port}" : ""}"
end.join(" ")
-
+
if !invidious_companion_urls.empty?
env.response.headers["Content-Security-Policy"] =
env.response.headers["Content-Security-Policy"]
diff --git a/src/invidious/routes/watch.cr b/src/invidious/routes/watch.cr
index 838934579..8a4fa2468 100644
--- a/src/invidious/routes/watch.cr
+++ b/src/invidious/routes/watch.cr
@@ -198,7 +198,7 @@ module Invidious::Routes::Watch
uri =
"#{companion.public_url.scheme}://#{companion.public_url.host}#{companion.public_url.port ? ":#{companion.public_url.port}" : ""}"
end.join(" ")
-
+
if !invidious_companion_urls.empty?
env.response.headers["Content-Security-Policy"] =
env.response.headers["Content-Security-Policy"]
diff --git a/src/invidious/yt_backend/connection_pool.cr b/src/invidious/yt_backend/connection_pool.cr
index 45455a8a7..42241d159 100644
--- a/src/invidious/yt_backend/connection_pool.cr
+++ b/src/invidious/yt_backend/connection_pool.cr
@@ -47,7 +47,7 @@ struct YoutubeConnectionPool
end
# Packages a `HTTP::Client` to an Invidious companion instance alongside the configuration for that instance.
-#
+#
# This is used as the resource for the `CompanionPool` as to allow the ability to
# proxy the requests to Invidious companion from Invidious directly.
# Instead of setting up routes in a reverse proxy.
diff --git a/src/invidious/yt_backend/youtube_api.cr b/src/invidious/yt_backend/youtube_api.cr
index 4b39acd74..6fa8ae0ec 100644
--- a/src/invidious/yt_backend/youtube_api.cr
+++ b/src/invidious/yt_backend/youtube_api.cr
@@ -706,7 +706,7 @@ module YoutubeAPI
COMPANION_POOL.client do |wrapper|
companion_base_url = wrapper.companion.private_url.path
- wrapper.client.post("#{companion_base_url}#{endpoint}", headers: headers, body: data.to_json) do | response |
+ wrapper.client.post("#{companion_base_url}#{endpoint}", headers: headers, body: data.to_json) do |response|
response_body = JSON.parse(response.body_io).as_h
end
end
From 5e9d51c06e387ea38108234406cfa0aa5e6fc12c Mon Sep 17 00:00:00 2001
From: syeopite
Date: Wed, 28 May 2025 15:38:49 -0700
Subject: [PATCH 48/52] Refactor `FilteredCompressHandler` to inherit from
stdlib
This changes its behavior to align with the stdlib variant in that
compression is now delayed till the moment that the server begins to
send a response.
This allows the handler to avoid compressing empty responses,and
safeguards against any double compression of content that may occur
if another handler decides to compressi ts response.
This does however come at the drawback(?) of it now removing
`content-length` headers on requests if it exists; since compression
makes the value inaccurate anyway.
See: https://github.com/crystal-lang/crystal/pull/9625
---
src/invidious/helpers/handlers.cr | 23 ++++-------------------
1 file changed, 4 insertions(+), 19 deletions(-)
diff --git a/src/invidious/helpers/handlers.cr b/src/invidious/helpers/handlers.cr
index 13ea9fe95..7c5ef1185 100644
--- a/src/invidious/helpers/handlers.cr
+++ b/src/invidious/helpers/handlers.cr
@@ -61,28 +61,13 @@ class Kemal::ExceptionHandler
end
end
-class FilteredCompressHandler < Kemal::Handler
+class FilteredCompressHandler < HTTP::CompressHandler
exclude ["/videoplayback", "/videoplayback/*", "/vi/*", "/sb/*", "/ggpht/*", "/api/v1/auth/notifications"]
exclude ["/api/v1/auth/notifications", "/data_control"], "POST"
- def call(env)
- return call_next env if exclude_match? env
-
- {% if flag?(:without_zlib) %}
- call_next env
- {% else %}
- request_headers = env.request.headers
-
- if request_headers.includes_word?("Accept-Encoding", "gzip")
- env.response.headers["Content-Encoding"] = "gzip"
- env.response.output = Compress::Gzip::Writer.new(env.response.output, sync_close: true)
- elsif request_headers.includes_word?("Accept-Encoding", "deflate")
- env.response.headers["Content-Encoding"] = "deflate"
- env.response.output = Compress::Deflate::Writer.new(env.response.output, sync_close: true)
- end
-
- call_next env
- {% end %}
+ def call(context)
+ return call_next context if exclude_match? context
+ super
end
end
From 21c13bba9ddee5db02b7584efe5ae5912cbeabd6 Mon Sep 17 00:00:00 2001
From: Emilien <4016501+unixfox@users.noreply.github.com>
Date: Wed, 3 Sep 2025 15:57:48 +0200
Subject: [PATCH 49/52] chore: use api captions from companion when available
---
src/invidious/views/components/player.ecr | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/src/invidious/views/components/player.ecr b/src/invidious/views/components/player.ecr
index af3521025..85fa4373f 100644
--- a/src/invidious/views/components/player.ecr
+++ b/src/invidious/views/components/player.ecr
@@ -65,12 +65,18 @@
<% end %>
<% end %>
- <% preferred_captions.each do |caption| %>
-