Compare commits

...

337 Commits

Author SHA1 Message Date
Chocobozzz
4376c728b1 Fix transcription CI 2025-10-29 10:32:06 +01:00
Chocobozzz
b548618c27 Fix migrations 2025-10-29 08:58:01 +01:00
Chocobozzz
eb23d76a85 Fix tests 2025-10-29 08:57:57 +01:00
Chocobozzz
f2f814f9d7 Fix tests 2025-10-28 16:49:23 +01:00
Chocobozzz
6c47548d51 Search on account name when searching channels 2025-10-28 16:40:56 +01:00
Chocobozzz
614521a39e Fix scrolling videos 2025-10-28 16:34:55 +01:00
Chocobozzz
7cdfc592be Update translations 2025-10-28 16:22:23 +01:00
Chocobozzz
2aa62a0383 Fix update script 2025-10-28 16:22:09 +01:00
Victor QuQunta
b822a2ac49 Translated using Weblate (Russian)
Currently translated at 100.0% (169 of 169 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/ru/
2025-10-28 16:00:43 +01:00
Victor QuQunta
de58fe9499 Translated using Weblate (Russian)
Currently translated at 100.0% (159 of 159 strings)

Translation: PeerTube/player
Translate-URL: https://weblate.framasoft.org/projects/peertube/player/ru/
2025-10-28 16:00:43 +01:00
Hồ Nhất Duy
0fd61b1698 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (276 of 276 strings)

Translation: PeerTube/server
Translate-URL: https://weblate.framasoft.org/projects/peertube/server/vi/
2025-10-28 16:00:43 +01:00
Victor QuQunta
b810c4a36b Translated using Weblate (Russian)
Currently translated at 100.0% (276 of 276 strings)

Translation: PeerTube/server
Translate-URL: https://weblate.framasoft.org/projects/peertube/server/ru/
2025-10-28 16:00:43 +01:00
Hồ Nhất Duy
f8f480950e Translated using Weblate (Vietnamese)
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/vi/
2025-10-28 16:00:43 +01:00
Victor QuQunta
48f3128611 Translated using Weblate (Russian)
Currently translated at 82.3% (2338 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/ru/
2025-10-28 16:00:43 +01:00
Hồ Nhất Duy
bcfed69ed9 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/vi/
2025-10-28 16:00:42 +01:00
jinsoo
c823de2f98 Translated using Weblate (Korean)
Currently translated at 96.8% (152 of 157 strings)

Translation: PeerTube/player
Translate-URL: https://weblate.framasoft.org/projects/peertube/player/ko/
2025-10-28 16:00:42 +01:00
jinsoo
8aca768257 Translated using Weblate (Korean)
Currently translated at 11.3% (322 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/ko/
2025-10-28 16:00:42 +01:00
Sveinn í Felli
89418dd637 Translated using Weblate (Icelandic)
Currently translated at 90.1% (2559 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/is/
2025-10-28 16:00:42 +01:00
Daniel Viñar Ulriksen
dffe018a39 Translated using Weblate (Spanish)
Currently translated at 80.1% (2277 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/es/
2025-10-28 16:00:42 +01:00
Sveinn í Felli
516546bea6 Translated using Weblate (Icelandic)
Currently translated at 100.0% (274 of 274 strings)

Translation: PeerTube/server
Translate-URL: https://weblate.framasoft.org/projects/peertube/server/is/
2025-10-28 16:00:42 +01:00
Sveinn í Felli
ca19fe0aba Translated using Weblate (Icelandic)
Currently translated at 87.0% (2471 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/is/
2025-10-28 16:00:42 +01:00
Jiří Podhorecký
a27d3a81d9 Translated using Weblate (Czech)
Currently translated at 92.3% (2622 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/cs/
2025-10-28 16:00:42 +01:00
Daniel Renth
2858af6918 Translated using Weblate (German)
Currently translated at 100.0% (169 of 169 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/de/
2025-10-28 16:00:42 +01:00
Wuzzy
a463adb0a2 Translated using Weblate (German)
Currently translated at 100.0% (169 of 169 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/de/
2025-10-28 16:00:42 +01:00
Daniel Renth
2d06d07ee1 Translated using Weblate (German)
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/de/
2025-10-28 16:00:42 +01:00
Wuzzy
33713e8b0f Translated using Weblate (German)
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/de/
2025-10-28 16:00:42 +01:00
Leif-Jöran Olsson
426e73ce8b Translated using Weblate (Swedish)
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/sv/
2025-10-28 16:00:42 +01:00
jinsoo
c05117ef4d Translated using Weblate (Korean)
Currently translated at 8.0% (228 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/ko/
2025-10-28 16:00:42 +01:00
Tibbe Debats
a571428735 Translated using Weblate (Dutch)
Currently translated at 94.2% (2676 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/nl/
2025-10-28 16:00:42 +01:00
Hồ Nhất Duy
09fe2af39f Translated using Weblate (Vietnamese)
Currently translated at 100.0% (169 of 169 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/vi/
2025-10-28 16:00:42 +01:00
John Livingston
39c30f9e17 Translated using Weblate (French (France) (fr_FR))
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/fr_FR/
2025-10-28 16:00:42 +01:00
Jiří Podhorecký
abc24fad2e Translated using Weblate (Czech)
Currently translated at 92.2% (2621 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/cs/
2025-10-28 16:00:42 +01:00
fran secs
ed15e3ca4d Translated using Weblate (Catalan)
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/ca/
2025-10-28 16:00:42 +01:00
Tibbe Debats
7ed8ab75b5 Translated using Weblate (Dutch)
Currently translated at 94.1% (2675 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/nl/
2025-10-28 16:00:42 +01:00
fran secs
0052a4d095 Translated using Weblate (Catalan)
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/ca/
2025-10-28 16:00:42 +01:00
Jiří Podhorecký
c0c6b9236c Translated using Weblate (Czech)
Currently translated at 91.3% (2593 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/cs/
2025-10-28 16:00:42 +01:00
B
4ead7306ec Translated using Weblate (Dutch)
Currently translated at 100.0% (159 of 159 strings)

Translation: PeerTube/player
Translate-URL: https://weblate.framasoft.org/projects/peertube/player/nl/
2025-10-28 16:00:42 +01:00
Tibbe Debats
ec28327632 Translated using Weblate (Dutch)
Currently translated at 100.0% (159 of 159 strings)

Translation: PeerTube/player
Translate-URL: https://weblate.framasoft.org/projects/peertube/player/nl/
2025-10-28 16:00:41 +01:00
Tibbe Debats
03a2f5668c Translated using Weblate (Dutch)
Currently translated at 100.0% (276 of 276 strings)

Translation: PeerTube/server
Translate-URL: https://weblate.framasoft.org/projects/peertube/server/nl/
2025-10-28 16:00:41 +01:00
Tibbe Debats
cb9047c170 Translated using Weblate (Dutch)
Currently translated at 94.1% (2674 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/nl/
2025-10-28 16:00:41 +01:00
Tibbe Debats
de8cee18d2 Translated using Weblate (Dutch)
Currently translated at 94.0% (2670 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/nl/
2025-10-28 16:00:41 +01:00
Tibbe Debats
0c682a915b Translated using Weblate (Dutch)
Currently translated at 90.7% (2577 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/nl/
2025-10-28 16:00:41 +01:00
Tibbe Debats
31e6b39fe0 Translated using Weblate (Dutch)
Currently translated at 89.4% (2541 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/nl/
2025-10-28 16:00:41 +01:00
Tibbe Debats
f57110a956 Translated using Weblate (Dutch)
Currently translated at 86.1% (2448 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/nl/
2025-10-28 16:00:41 +01:00
B
9dece339a3 Translated using Weblate (Dutch)
Currently translated at 100.0% (169 of 169 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/nl/
2025-10-28 16:00:41 +01:00
Tibbe Debats
2bba9bba87 Translated using Weblate (Dutch)
Currently translated at 85.9% (2442 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/nl/
2025-10-28 16:00:41 +01:00
B
095e49592b Translated using Weblate (Dutch)
Currently translated at 85.9% (2442 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/nl/
2025-10-28 16:00:41 +01:00
Tibbe Debats
8a9ed579ab Translated using Weblate (Dutch)
Currently translated at 85.9% (2442 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/nl/
2025-10-28 16:00:41 +01:00
Jeff Huang
0227fc19c2 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/zh_Hant/
2025-10-28 16:00:41 +01:00
Oliwier Jaszczyszyn
9a5d2ebf48 Translated using Weblate (Polish)
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/pl/
2025-10-28 16:00:41 +01:00
Jeff Huang
e7a33866c5 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (169 of 169 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/zh_Hant/
2025-10-28 16:00:41 +01:00
Milo Ivir
766e50bf10 Translated using Weblate (Croatian)
Currently translated at 100.0% (169 of 169 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/hr/
2025-10-28 16:00:41 +01:00
Milo Ivir
5d3717766b Translated using Weblate (Croatian)
Currently translated at 100.0% (274 of 274 strings)

Translation: PeerTube/server
Translate-URL: https://weblate.framasoft.org/projects/peertube/server/hr/
2025-10-28 16:00:41 +01:00
Milo Ivir
6fa40c0bf0 Translated using Weblate (Croatian)
Currently translated at 79.4% (2255 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/hr/
2025-10-28 16:00:41 +01:00
Wu Wei
1688b42476 Translated using Weblate (Croatian)
Currently translated at 79.4% (2255 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/hr/
2025-10-28 16:00:41 +01:00
Jeff Huang
b342b0ae11 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (159 of 159 strings)

Translation: PeerTube/player
Translate-URL: https://weblate.framasoft.org/projects/peertube/player/zh_Hant/
2025-10-28 16:00:41 +01:00
Jeff Huang
9ca3d8e58b Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (276 of 276 strings)

Translation: PeerTube/server
Translate-URL: https://weblate.framasoft.org/projects/peertube/server/zh_Hant/
2025-10-28 16:00:41 +01:00
Oliwier Jaszczyszyn
ae81387001 Translated using Weblate (Polish)
Currently translated at 88.3% (2510 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/pl/
2025-10-28 16:00:41 +01:00
Adrià Martín
25f6245680 Translated using Weblate (Catalan)
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/ca/
2025-10-28 16:00:41 +01:00
Leif-Jöran Olsson
9020e276f6 Translated using Weblate (Swedish)
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/sv/
2025-10-28 16:00:41 +01:00
Kenner Figueiredo
5541054f32 Translated using Weblate (Portuguese (Brazil))
Currently translated at 89.4% (2539 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/pt_BR/
2025-10-28 16:00:41 +01:00
Quentin THEURET
7013f04efc Translated using Weblate (French (France) (fr_FR))
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/fr_FR/
2025-10-28 16:00:40 +01:00
Ignacio Carrera González
7eda7b5f65 Translated using Weblate (Spanish)
Currently translated at 100.0% (169 of 169 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/es/
2025-10-28 16:00:40 +01:00
Ignacio Carrera González
fca1ae4e0d Translated using Weblate (Spanish)
Currently translated at 100.0% (159 of 159 strings)

Translation: PeerTube/player
Translate-URL: https://weblate.framasoft.org/projects/peertube/player/es/
2025-10-28 16:00:40 +01:00
Ignacio Carrera González
eb2e14fccd Translated using Weblate (Spanish)
Currently translated at 100.0% (276 of 276 strings)

Translation: PeerTube/server
Translate-URL: https://weblate.framasoft.org/projects/peertube/server/es/
2025-10-28 16:00:40 +01:00
ukuk
f2adff7e12 Translated using Weblate (Polish)
Currently translated at 76.9% (2185 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/pl/
2025-10-28 16:00:40 +01:00
Ignacio Carrera González
1d5a8bf589 Translated using Weblate (Spanish)
Currently translated at 77.5% (2201 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/es/
2025-10-28 16:00:40 +01:00
alex gabilondo
7915680ab7 Translated using Weblate (Basque)
Currently translated at 100.0% (169 of 169 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/eu/
2025-10-28 16:00:40 +01:00
fran secs
309fce5a18 Translated using Weblate (Catalan)
Currently translated at 100.0% (169 of 169 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/ca/
2025-10-28 16:00:40 +01:00
Oliwier Jaszczyszyn
bd3b81d109 Translated using Weblate (Polish)
Currently translated at 100.0% (169 of 169 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/pl/
2025-10-28 16:00:40 +01:00
Tibbe Debats
fc27f6e5c3 Translated using Weblate (Dutch)
Currently translated at 100.0% (169 of 169 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/nl/
2025-10-28 16:00:40 +01:00
Wu Wei
3bd0991011 Translated using Weblate (Croatian)
Currently translated at 100.0% (169 of 169 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/hr/
2025-10-28 16:00:40 +01:00
Ignacio Carrera González
af40fd1707 Translated using Weblate (Latin)
Currently translated at 21.0% (33 of 157 strings)

Translation: PeerTube/player
Translate-URL: https://weblate.framasoft.org/projects/peertube/player/la/
2025-10-28 16:00:40 +01:00
Ignacio Carrera González
875d5cd964 Translated using Weblate (Latin)
Currently translated at 6.6% (18 of 272 strings)

Translation: PeerTube/server
Translate-URL: https://weblate.framasoft.org/projects/peertube/server/la/
2025-10-28 16:00:40 +01:00
Ignacio Carrera González
976ce40842 Translated using Weblate (Latin)
Currently translated at 0.1% (1 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/la/
2025-10-28 16:00:40 +01:00
Wu Wei
7965e0e16d Translated using Weblate (Croatian)
Currently translated at 78.1% (2220 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/hr/
2025-10-28 16:00:40 +01:00
Oliwier Jaszczyszyn
656ca62796 Translated using Weblate (Polish)
Currently translated at 100.0% (159 of 159 strings)

Translation: PeerTube/player
Translate-URL: https://weblate.framasoft.org/projects/peertube/player/pl/
2025-10-28 16:00:40 +01:00
Ignacio Carrera González
80c7dc348e Translated using Weblate (Spanish)
Currently translated at 100.0% (159 of 159 strings)

Translation: PeerTube/player
Translate-URL: https://weblate.framasoft.org/projects/peertube/player/es/
2025-10-28 16:00:40 +01:00
Blood Axe
5e78d3bf6c Translated using Weblate (Norwegian Bokmål)
Currently translated at 30.6% (84 of 274 strings)

Translation: PeerTube/server
Translate-URL: https://weblate.framasoft.org/projects/peertube/server/nb_NO/
2025-10-28 16:00:40 +01:00
Oliwier Jaszczyszyn
8f135fec5c Translated using Weblate (Polish)
Currently translated at 100.0% (276 of 276 strings)

Translation: PeerTube/server
Translate-URL: https://weblate.framasoft.org/projects/peertube/server/pl/
2025-10-28 16:00:40 +01:00
Ignacio Carrera González
938f3f9e51 Translated using Weblate (Spanish)
Currently translated at 100.0% (276 of 276 strings)

Translation: PeerTube/server
Translate-URL: https://weblate.framasoft.org/projects/peertube/server/es/
2025-10-28 16:00:40 +01:00
偶尔来巡山
810d6c6612 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/zh_Hans/
2025-10-28 16:00:40 +01:00
Oliwier Jaszczyszyn
e82ba16b29 Translated using Weblate (Polish)
Currently translated at 76.6% (2178 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/pl/
2025-10-28 16:00:40 +01:00
Tibbe Debats
3544b08f59 Translated using Weblate (Dutch)
Currently translated at 84.0% (2388 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/nl/
2025-10-28 16:00:40 +01:00
Blood Axe
f610c910c0 Translated using Weblate (Norwegian Bokmål)
Currently translated at 76.5% (2175 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/nb_NO/
2025-10-28 16:00:40 +01:00
Ignacio Carrera González
8801004ba9 Translated using Weblate (Spanish)
Currently translated at 77.3% (2198 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/es/
2025-10-28 16:00:39 +01:00
Wuzzy
0e97d07629 Translated using Weblate (German)
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/de/
2025-10-28 16:00:39 +01:00
Chocobozzz
afd913530c Fix mention redirection 2025-10-28 16:00:26 +01:00
Chocobozzz
190987cc72 Fix lint 2025-10-28 15:08:23 +01:00
Chocobozzz
0babdbc243 Require at least 3GB to upgrade peertube 2025-10-28 15:07:25 +01:00
Chocobozzz
1c94e97923 Improve notifications design 2025-10-28 14:59:56 +01:00
Chocobozzz
381f8e3b3a Add channel collaborators feature
For now only an "editor" role is available for collaborators. An editor
has the same right as the channel owner but cannot:
 * Delete the channel
 * Invite collaborators
 * Transfer ownership of a video
 * Transfer ownership of a channel (feature not developed yet)
 * Invite a new collaborator
 * Remove another collaborator

 The owner of a channel can invite another local account to be an editor
 of the channel. The editor can accept or reject the invitation.

 Moved transfer ownership of a video in the "Manage video" page.

 Updated the video channel update page to a "Manage" page, like we have
 for the video to add an "Editors" sub section, allowing the owner of
   the channel to manage channel editors.

Refactored the SQL query classes and added more raw SQL queries for
models that list entities depending on channel collaboration status.

Updated server tests to check editors can manage or cannot manage
channel entities.
2025-10-28 14:44:44 +01:00
Chocobozzz
4fd4c91ec1 Add ability to cleanup views on local videos too 2025-10-10 11:52:33 +02:00
Chocobozzz
ed8279eae6 Copy codecs for HLS if possible 2025-10-10 09:05:57 +02:00
Chocobozzz
f04a11f74d Copy codecs for HLS transcoding
It was disabled because of Safari playback issues
(https://github.com/Chocobozzz/PeerTube/issues/3502), but I couldn't
reproduce so re-enable it

Allows to have a better audio/video quality (because we don't re-encode
these streams) and it saves CPU
2025-10-09 16:32:28 +02:00
Chocobozzz
1ba09ea2fa Fix sitemap tests 2025-10-09 13:40:56 +02:00
Chocobozzz
15e9236373 Add insert at first channel position 2025-10-09 13:35:38 +02:00
Chocobozzz
6a51b685ef Update config after plugin settings changes
Plugin can change the configuration, like transcoding profiles for
example
2025-10-09 10:52:42 +02:00
Gianantonio Pini
319da3b6a5 fix(config): init config check does not report Browse Videos errors (#7222)
* fix(config): modify browse-videos validators to not use i18n when called by checker-after-init

* refactor(config): make browse-videos validators language parameter optional

* refactor(config): use template literals in browse-videos validators

* Fix i18n string extraction

---------

Co-authored-by: Chocobozzz <me@florianbigard.com>
2025-10-09 10:35:58 +02:00
Chocobozzz
ba3bb0f142 Use schedule at date for publishedAt
Improve information to user and respect publication sort
2025-10-08 16:48:52 +02:00
Chocobozzz
5286f0ed46 Update sitemap static URLs 2025-10-08 16:27:21 +02:00
Chocobozzz
2f816446a6 Merge branch 'release/7.3.x' into develop 2025-10-08 15:58:37 +02:00
Chocobozzz
d867eb367b Show their own videos to muted accounts 2025-10-08 15:50:46 +02:00
Chocobozzz
a25c6d65b8 Fix admin abuse URL 2025-10-08 15:40:30 +02:00
Chocobozzz
77f28ece70 Accept non https URLs
We can use a search index on localhost for example
2025-10-08 15:39:26 +02:00
Chocobozzz
f6eefbde0c Correctly load user languages 2025-10-08 15:37:30 +02:00
Chocobozzz
f61581280d Fix select languages component label 2025-10-08 15:25:30 +02:00
Chocobozzz
3a9a76b3b4 Remove unused config 2025-10-08 11:25:16 +02:00
Chocobozzz
54bfae82ad Fix viewers stats date filter label after a reset 2025-09-24 16:53:16 +02:00
Chocobozzz
28a0c774f1 Improve video manage and admin config form a11y 2025-09-24 16:42:29 +02:00
Chocobozzz
d50c8a0b0a Fix scroll issue when navigating from homepage 2025-09-24 16:27:43 +02:00
Chocobozzz
b2704b2f36 theme breaking: --input-placeholder-color
Instead of --input-placeholder and introduce
--input-placeholder-font-size
2025-09-24 16:09:31 +02:00
Chocobozzz
e7694b2911 Remove unused file 2025-09-24 16:05:34 +02:00
Chocobozzz
16a7a5e8cc Don't use cursor on non interactive el 2025-09-24 16:02:26 +02:00
Chocobozzz
df86535f46 Add missing elements of user object 2025-09-24 16:00:34 +02:00
Chocobozzz
3f079b9398 Fix broken customization config page 2025-09-24 15:42:35 +02:00
Chocobozzz
4263d755e3 Fix max size information
We don't have such limit on backend side
2025-09-24 15:39:28 +02:00
Chocobozzz
30ca195efa Update node supported versions 2025-09-24 15:38:25 +02:00
Chocobozzz
68547190c7 Fix node min versions 2025-09-17 15:37:49 +02:00
Chocobozzz
94e55dfc6c Merge branch 'release/7.3.x' into develop 2025-09-16 11:06:00 +02:00
Chocobozzz
bb0c71549a Refactor player variables 2025-09-16 11:05:53 +02:00
Chocobozzz
2a648e6ea5 Fix docker production build 2025-09-16 06:36:25 +02:00
Chocobozzz
3a58afef10 Fix lint 2025-09-15 15:58:08 +02:00
Chocobozzz
84dbcb5f11 Decrease settings menu opacity 2025-09-15 15:40:01 +02:00
Chocobozzz
cd8ad77515 Fix settings menu overflow 2025-09-15 15:28:21 +02:00
Chocobozzz
050461528c Merge branch 'release/7.3.x' into develop 2025-09-15 15:06:33 +02:00
Chocobozzz
073cd4f0ba Fix nodejs_active_resources_total metric
Remove duplicate metric and transform it into a gauge because the value
can decrease
2025-09-15 14:16:37 +02:00
Chocobozzz
42e0c015e8 Fix reloading table data 2025-09-15 14:07:23 +02:00
Chocobozzz
4888717c09 Fix disabled button 2025-09-15 14:00:01 +02:00
Chocobozzz
449ebe4b54 Fix badges alignment 2025-09-15 13:50:44 +02:00
Chocobozzz
3cca1fdbf3 Use diff instead of vimdiff 2025-09-15 13:40:59 +02:00
Chocobozzz
50c184a9a2 Fix external token doc 2025-09-15 13:39:41 +02:00
Chocobozzz
e2e15e3f0c Typo 2025-09-15 13:21:00 +02:00
Chocobozzz
0048bf7326 Add guide to upgrade PostgreSQL in docker 2025-09-15 12:02:07 +02:00
Chocobozzz
4fd894308d Fix CI 2025-09-15 10:02:38 +02:00
Chocobozzz
496b50f6b1 Upgrade docker postgresql and redis versions 2025-09-15 08:39:41 +02:00
Chocobozzz
729a58a860 Move docker to trixie 2025-09-15 07:11:57 +02:00
Chocobozzz
2d96b7191d Remove unused file 2025-09-12 11:00:33 +02:00
Chocobozzz
dca5e363d1 Improve dependencies guide 2025-09-12 10:50:49 +02:00
Chocobozzz
e670b6d924 Fix lint 2025-09-12 10:45:06 +02:00
Chocobozzz
5c1cbcfcb1 Fix model imports 2025-09-12 10:26:59 +02:00
Chocobozzz
a11d3102ce Fix openapi schema 2025-09-12 10:13:29 +02:00
Chocobozzz
f184154aaa Fix handlebars dependency 2025-09-12 09:38:31 +02:00
Chocobozzz
f93870ea31 Patch video.js types to unbreak the build
Remove the patch when https://github.com/videojs/video.js/pull/9089 is
published
2025-09-12 09:08:17 +02:00
Chocobozzz
941469df29 Add externalRedirectUri doc 2025-09-12 08:54:50 +02:00
Chocobozzz
ef95c3fe72 Remove openapi warnings 2025-09-12 08:54:50 +02:00
Chocobozzz
572100b1a3 Fix tests build 2025-09-12 08:54:50 +02:00
Chocobozzz
200193262c Add openapi doc for player settings 2025-09-12 08:54:50 +02:00
Chocobozzz
74e97347bb Add ability to customize player settings 2025-09-12 08:54:50 +02:00
Chocobozzz
b742dbc0fc Update dprint HTML module 2025-09-12 08:54:50 +02:00
Chocobozzz
fcca9b72d3 Add copilot instructions 2025-09-12 08:54:50 +02:00
Chocobozzz
5edab3f795 Introduce lucide player theme 2025-09-12 08:54:50 +02:00
Chocobozzz
a2b99c3c92 Add more info to stats card 2025-09-12 08:54:50 +02:00
Chocobozzz
fde2c8c0c7 Prefer using vertical volume control
Better UX/control
2025-09-12 08:54:50 +02:00
Chocobozzz
48ea20c9e4 Upgrade to videojs v8 2025-09-12 08:54:49 +02:00
Chocobozzz
906b5f7f2c Migrate to pnpm 2025-09-12 08:43:41 +02:00
Chocobozzz
bbc1afada5 Optimize updating oauth tokens activity 2025-09-10 16:53:39 +02:00
Shalabh Agarwal
efa32646ed feat: add user password constraints (#6945)
* feat: add user password length constraints

* add password length changes in locale files

* revert maximum password length changes

* add tests

* fix lint

* fix lint and test

* fix tests

* Revert "add password length changes in locale files"

This reverts commit eaaf63ba7c.

* Update PR

---------

Co-authored-by: Chocobozzz <me@florianbigard.com>
2025-09-10 16:51:04 +02:00
Chocobozzz
f1e05043bc Optimize updating oauth tokens activity 2025-09-10 15:16:40 +02:00
Chocobozzz
eedfb8b0a2 Update runner package 2025-09-10 11:55:24 +02:00
諏訪子
ef28ba3038 add x link
Add missing code
2025-09-10 11:51:58 +02:00
ilfarpro
dd52e8b89e Feature for runners - handle storyboard-generation-job (#7191)
* Implement processing storyboards by runners

* Fixed storyboard generation by runners

* use common code patterns

* fix import

* improve debug logging for storyboard generation

* config option for storyboard processing with remote-runners

* refactor repetitive pattern

* refactor storyboard related code to share common utlities

* Fix test

* Fix storyboard generation config logic

* Improve logging

* Added tests for storyboard generation with runners

* Refactor PR

---------

Co-authored-by: ilfarpro <ilfarpro@ya.ru>
Co-authored-by: Chocobozzz <me@florianbigard.com>
2025-09-10 11:50:06 +02:00
Gianantonio Pini
e74bf8ae2a feat(config): add admin options to customize default "Browse videos" behaviour (#7193)
* feat(customBrowseVideosDefaultSort): update server config

* feat(customBrowseVideosDefaultSort): update client admin-config-general component

* feat(customBrowseVideosDefaultSort): add new consistency check to server checker-after-init

* feat(customBrowseVideosDefaultSort): update config .yaml with more details about available options

* feat(customBrowseVideosDefaultSort): refactor consistency check in server checker-after-init

* feat(customBrowseVideosDefaultSort): client, add new select-videos-sort shared component

* feat(customBrowseVideosDefaultSort): client, refactor admin-config-general component to use new select-videos-sort shared component

* feat(customBrowseVideosDefaultSort): client, fix my-select-videos-sort width in admin-config-general

* feat(customBrowseVideosDefaultSort): client, update video-filters-header scss

* feat(customBrowseVideosDefaultSort): client, update videos-list-all component

* feat(config): refactor checkBrowseVideosConfig logic into separate custom validator

* feat(config): refactor isBrowseVideosDefaultSortValid to use template literals

* feat(config): refactor isBrowseVideosDefaultSortValid

* feat(config): add check for invalid browse videos config to customConfigUpdateValidator

* feat(config): group browse-videos tests in describe block

* feat(config): refactor to use client.browse_videos section, instead of browse.videos section

* feat(config): add browse_videos default_scope config key (config and server changes)

* feat(config): add browse_videos default_scope config key (client changes)

* Reorder browse videos before videos

* Fix i18n message

---------

Co-authored-by: Chocobozzz <me@florianbigard.com>
2025-09-10 10:41:45 +02:00
Chocobozzz
9b7edd1c59 Cleanup packages 2025-09-10 07:10:31 +02:00
Chocobozzz
93926d2700 Use :root instead of body for CSS variables 2025-09-09 12:56:49 +02:00
Chocobozzz
41b3a404dc Update changelog 2025-09-09 12:53:45 +02:00
Chocobozzz
c922886f8a Bumped to version v7.3.0 2025-09-09 11:07:47 +02:00
Chocobozzz
2bd5f564a3 Update changelog 2025-09-09 10:30:22 +02:00
Chocobozzz
f4c969fd00 Fix translations 2025-09-09 08:21:43 +02:00
Chocobozzz
c3daefa6e9 Update translations 2025-09-09 07:45:32 +02:00
Ghost of Sparta
4c5813463f Translated using Weblate (Hungarian)
Currently translated at 100.0% (276 of 276 strings)

Translation: PeerTube/server
Translate-URL: https://weblate.framasoft.org/projects/peertube/server/hu/
2025-09-08 16:24:14 +02:00
偶尔来巡山
6f318071db Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (169 of 169 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/zh_Hans/
2025-09-08 16:24:14 +02:00
Leif-Jöran Olsson
b3da438a00 Translated using Weblate (Swedish)
Currently translated at 100.0% (169 of 169 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/sv/
2025-09-08 16:24:14 +02:00
偶尔来巡山
945604ea79 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/zh_Hans/
2025-09-08 16:24:14 +02:00
sasek
f914e1b7bf Translated using Weblate (Polish)
Currently translated at 76.3% (2168 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/pl/
2025-09-08 16:24:14 +02:00
Oliwier Jaszczyszyn
4cb99c3fd5 Translated using Weblate (Polish)
Currently translated at 76.3% (2168 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/pl/
2025-09-08 16:24:14 +02:00
Oliwier Jaszczyszyn
715719238a Translated using Weblate (Polish)
Currently translated at 73.7% (2095 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/pl/
2025-09-08 16:24:14 +02:00
Joe Silber
7b38c21631 Translated using Weblate (Dutch)
Currently translated at 83.7% (2378 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/nl/
2025-09-08 16:24:14 +02:00
Chocobozzz
958aab240d Fix lint 2025-09-08 16:24:03 +02:00
Chocobozzz
4719cf26f4 Fix overflow in discover page 2025-09-08 15:31:59 +02:00
Chocobozzz
a6266dc4bf Fix lint 2025-09-08 08:36:07 +02:00
Chocobozzz
12c9825658 Optimize updating token activity 2025-09-05 10:34:47 +02:00
Chocobozzz
448bc823ef Update client dependencies 2025-09-05 10:34:44 +02:00
Chocobozzz
78eb54464c Update translations 2025-09-05 09:43:14 +02:00
fran secs
75fbdbf70e Translated using Weblate (Catalan)
Currently translated at 100.0% (276 of 276 strings)

Translation: PeerTube/server
Translate-URL: https://weblate.framasoft.org/projects/peertube/server/ca/
2025-09-05 09:37:23 +02:00
fran secs
326bf8d85f Translated using Weblate (Catalan)
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/ca/
2025-09-05 09:37:22 +02:00
Chocobozzz
38e04969df Check API search invalid config 2025-09-05 09:36:27 +02:00
Chocobozzz
a1bf55c7ae Update translations 2025-09-04 11:08:47 +02:00
Leonora
707e1e9b98 Translated using Weblate (Danish)
Currently translated at 23.9% (681 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/da/
2025-09-04 11:01:25 +02:00
Leonora
7f9f1feed5 Translated using Weblate (Danish)
Currently translated at 77.0% (205 of 266 strings)

Translation: PeerTube/server
Translate-URL: https://weblate.framasoft.org/projects/peertube/server/da/
2025-09-04 11:01:25 +02:00
Leonora
52a94815c0 Translated using Weblate (Danish)
Currently translated at 17.3% (493 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/da/
2025-09-04 11:01:25 +02:00
Hồ Nhất Duy
6594ac1262 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/vi/
2025-09-04 11:01:25 +02:00
Leif-Jöran Olsson
6e34fadcc2 Translated using Weblate (Swedish)
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/sv/
2025-09-04 11:01:25 +02:00
Leonora
a16955136d Translated using Weblate (Danish)
Currently translated at 8.2% (234 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/da/
2025-09-04 11:01:25 +02:00
Leif-Jöran Olsson
d50c038e07 Translated using Weblate (Swedish)
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/sv/
2025-09-04 11:01:25 +02:00
T.S
b59bded648 Translated using Weblate (Japanese)
Currently translated at 100.0% (162 of 162 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/ja/
2025-09-04 11:01:24 +02:00
Jeff Huang
3d825c56bf Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/zh_Hant/
2025-09-04 11:01:24 +02:00
T.S
756b5646f6 Translated using Weblate (Japanese)
Currently translated at 100.0% (159 of 159 strings)

Translation: PeerTube/player
Translate-URL: https://weblate.framasoft.org/projects/peertube/player/ja/
2025-09-04 11:01:24 +02:00
T.S
640be407cf Translated using Weblate (Japanese)
Currently translated at 100.0% (276 of 276 strings)

Translation: PeerTube/server
Translate-URL: https://weblate.framasoft.org/projects/peertube/server/ja/
2025-09-04 11:01:24 +02:00
偶尔来巡山
d7948ad0bc Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/zh_Hans/
2025-09-04 11:01:24 +02:00
T.S
b02cbfce7f Translated using Weblate (Japanese)
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/ja/
2025-09-04 11:01:24 +02:00
Chocobozzz
59d5f28ed6 Restore scroll position after homepage redirect 2025-09-04 11:00:53 +02:00
Chocobozzz
24a0d7fd00 Faster position scrolling 2025-09-04 06:57:56 +02:00
Chocobozzz
91afa1004e Fill video support on channel sync 2025-09-03 08:46:38 +02:00
Chocobozzz
0882d96624 Do not override privacy for imports and live 2025-09-03 07:13:12 +02:00
Chocobozzz
3ea32ba891 Remove useless help for live transcoding 2025-09-01 09:30:05 +02:00
Chocobozzz
12b4893239 Update translations 2025-08-27 16:59:59 +02:00
Cirnos
aea6983cc4 Translated using Weblate (Portuguese (Brazil))
Currently translated at 87.4% (2483 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/pt_BR/
2025-08-27 16:47:38 +02:00
E
65f5bd1c37 Translated using Weblate (Italian)
Currently translated at 89.2% (2536 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/it/
2025-08-27 16:47:38 +02:00
Wuzzy
c5ec42f587 Translated using Weblate (German)
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/de/
2025-08-27 16:47:38 +02:00
ButterflyOfFire
741c8f62e0 Translated using Weblate (French (France) (fr_FR))
Currently translated at 99.9% (2839 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/fr_FR/
2025-08-27 16:47:38 +02:00
Weblate
5b1ac25794 Translated using Weblate (French (France) (fr_FR))
Currently translated at 99.9% (2839 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/fr_FR/
2025-08-27 16:47:38 +02:00
Wuzzy
eedcca7879 Translated using Weblate (German)
Currently translated at 100.0% (162 of 162 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/de/
2025-08-27 16:47:38 +02:00
Wuzzy
7f70d7f3f2 Translated using Weblate (German)
Currently translated at 100.0% (159 of 159 strings)

Translation: PeerTube/player
Translate-URL: https://weblate.framasoft.org/projects/peertube/player/de/
2025-08-27 16:47:38 +02:00
Wuzzy
8447769447 Translated using Weblate (German)
Currently translated at 100.0% (276 of 276 strings)

Translation: PeerTube/server
Translate-URL: https://weblate.framasoft.org/projects/peertube/server/de/
2025-08-27 16:47:38 +02:00
Fjuro
4d45ca6f09 Translated using Weblate (Czech)
Currently translated at 100.0% (276 of 276 strings)

Translation: PeerTube/server
Translate-URL: https://weblate.framasoft.org/projects/peertube/server/cs/
2025-08-27 16:47:38 +02:00
Wuzzy
77c3a279f7 Translated using Weblate (German)
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/de/
2025-08-27 16:47:38 +02:00
Wuzzy
fde057171d Translated using Weblate (German)
Currently translated at 94.5% (2686 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/de/
2025-08-27 16:47:38 +02:00
Korren-Kerren
6c3d7501e2 Translated using Weblate (Esperanto)
Currently translated at 43.8% (1245 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/eo/
2025-08-27 16:47:38 +02:00
Jeff Huang
1b283c8694 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/zh_Hant/
2025-08-27 16:47:38 +02:00
Hồ Nhất Duy
401e5c0b07 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (162 of 162 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/vi/
2025-08-27 16:47:38 +02:00
Hồ Nhất Duy
7e1701d9d1 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (276 of 276 strings)

Translation: PeerTube/server
Translate-URL: https://weblate.framasoft.org/projects/peertube/server/vi/
2025-08-27 16:47:37 +02:00
偶尔来巡山
06e5f534ba Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/zh_Hans/
2025-08-27 16:47:37 +02:00
Hồ Nhất Duy
3ee605b433 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/vi/
2025-08-27 16:47:37 +02:00
Leif-Jöran Olsson
bc0132239e Translated using Weblate (Swedish)
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/sv/
2025-08-27 16:47:37 +02:00
Jiří Podhorecký
796229ac34 Translated using Weblate (Czech)
Currently translated at 90.8% (2580 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/cs/
2025-08-27 16:47:37 +02:00
Chocobozzz
3d75a7288f Force to choose a channel to reorder playlists 2025-08-27 16:47:13 +02:00
Chocobozzz
42bf34c29a Fix "Show" button styling 2025-08-27 16:41:54 +02:00
Chocobozzz
d0e810d29a Fix retrying the test 2025-08-26 07:46:57 +02:00
Chocobozzz
b7aa685009 Use a better title 2025-08-25 10:36:26 +02:00
Chocobozzz
cfe49b37ec Fix video title with RSS feed video import 2025-08-25 08:48:03 +02:00
Chocobozzz
f383fe101a Typo 2025-08-25 08:47:40 +02:00
Chocobozzz
7db2817877 Fix RTL margins on some components 2025-08-21 17:16:23 +02:00
Chocobozzz
24dbbbad64 Fix sending emails in production 2025-08-21 11:49:01 +02:00
Chocobozzz
5894c362d6 Bumped to version v7.3.0-rc.1 2025-08-21 10:24:38 +02:00
Chocobozzz
57667734c6 Fix E2E tests 2025-08-21 10:21:17 +02:00
Chocobozzz
8f852e3153 Manage video playlist on thumbnail click 2025-08-20 10:49:08 +02:00
Chocobozzz
aaae2910e7 Save 2025-08-20 09:00:45 +02:00
Chocobozzz
507cedca1c Update translations 2025-08-20 09:00:34 +02:00
chocobozzz
a94128a9ce Translated using Weblate (French (France) (fr_FR))
Currently translated at 100.0% (162 of 162 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/fr_FR/
2025-08-20 08:57:29 +02:00
Marsalis Weatherspoon
d477aa0df6 Translated using Weblate (Spanish)
Currently translated at 100.0% (162 of 162 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/es/
2025-08-20 08:57:29 +02:00
chocobozzz
198192aeb4 Translated using Weblate (French (France) (fr_FR))
Currently translated at 100.0% (159 of 159 strings)

Translation: PeerTube/player
Translate-URL: https://weblate.framasoft.org/projects/peertube/player/fr_FR/
2025-08-20 08:57:29 +02:00
偶尔来巡山
d1b9a88297 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (276 of 276 strings)

Translation: PeerTube/server
Translate-URL: https://weblate.framasoft.org/projects/peertube/server/zh_Hans/
2025-08-20 08:57:29 +02:00
chocobozzz
2ebd9a74f5 Translated using Weblate (French (France) (fr_FR))
Currently translated at 100.0% (276 of 276 strings)

Translation: PeerTube/server
Translate-URL: https://weblate.framasoft.org/projects/peertube/server/fr_FR/
2025-08-20 08:57:29 +02:00
Marsalis Weatherspoon
c2fc1cbbd9 Translated using Weblate (Spanish)
Currently translated at 100.0% (276 of 276 strings)

Translation: PeerTube/server
Translate-URL: https://weblate.framasoft.org/projects/peertube/server/es/
2025-08-20 08:57:29 +02:00
偶尔来巡山
423633bc2b Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/zh_Hans/
2025-08-20 08:57:29 +02:00
Leif-Jöran Olsson
045409fa35 Translated using Weblate (Swedish)
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/sv/
2025-08-20 08:57:29 +02:00
Cirnos
74bb0e58c0 Translated using Weblate (Portuguese (Brazil))
Currently translated at 87.3% (2482 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/pt_BR/
2025-08-20 08:57:29 +02:00
Codimp
bf1c1379a2 Translated using Weblate (French (France) (fr_FR))
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/fr_FR/
2025-08-20 08:57:29 +02:00
chocobozzz
c9eb2e2289 Translated using Weblate (French (France) (fr_FR))
Currently translated at 100.0% (2840 of 2840 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/fr_FR/
2025-08-20 08:57:29 +02:00
Chocobozzz
d090795d0c Fix useless i18n tag 2025-08-20 08:52:39 +02:00
Chocobozzz
6822bcfcb3 Typo 2025-08-20 08:52:30 +02:00
Chocobozzz
9ae1a0177c Update translations 2025-08-19 16:28:15 +02:00
Chocobozzz
c26c2c6007 Merge remote-tracking branch 'weblate/develop' into develop 2025-08-19 16:25:09 +02:00
Alberto
2955824366 Translated using Weblate (Dutch)
Currently translated at 100.0% (162 of 162 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/nl/
2025-08-19 16:24:59 +02:00
Alberto
fac0f0bc1d Translated using Weblate (Dutch)
Currently translated at 84.6% (2379 of 2810 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/nl/
2025-08-19 16:24:59 +02:00
chocobozzz
33aaa7f1d1 Translated using Weblate (French (France) (fr_FR))
Currently translated at 100.0% (2810 of 2810 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/fr_FR/
2025-08-19 16:24:58 +02:00
Chocobozzz
2453a82856 Add translation description 2025-08-19 16:13:29 +02:00
Chocobozzz
0967ee953c Add missing $localize 2025-08-19 16:11:47 +02:00
Chocobozzz
a1a6515524 Update translations 2025-08-19 15:55:54 +02:00
Chocobozzz
0dd0198693 Merge remote-tracking branch 'weblate/develop' into develop 2025-08-19 15:53:55 +02:00
Leif-Jöran Olsson
6dcdc680e0 Translated using Weblate (Swedish)
Currently translated at 100.0% (276 of 276 strings)

Translation: PeerTube/server
Translate-URL: https://weblate.framasoft.org/projects/peertube/server/sv/
2025-08-19 15:53:51 +02:00
Chocobozzz
8f0389ab8c Typo 2025-08-19 15:53:50 +02:00
Leif-Jöran Olsson
035846578f Translated using Weblate (Swedish)
Currently translated at 100.0% (2811 of 2811 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/sv/
2025-08-19 15:53:49 +02:00
chocobozzz
c858dd9982 Translated using Weblate (French (France) (fr_FR))
Currently translated at 100.0% (2811 of 2811 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/fr_FR/
2025-08-19 15:53:48 +02:00
Chocobozzz
789696d22f Update changelog 2025-08-19 11:53:08 +02:00
Chocobozzz
0bb20d7e19 Update translations 2025-08-19 10:45:47 +02:00
Chocobozzz
e3f0beb6cb Merge remote-tracking branch 'weblate/develop' into develop 2025-08-19 10:42:42 +02:00
Cirnos
a383baa812 Translated using Weblate (Portuguese (Brazil))
Currently translated at 87.8% (2464 of 2804 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/pt_BR/
2025-08-19 10:40:36 +02:00
chocobozzz
65f89db861 Translated using Weblate (French (France) (fr_FR))
Currently translated at 91.9% (2579 of 2804 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/fr_FR/
2025-08-19 10:40:35 +02:00
Chocobozzz
b591f42914 Add "copyrighted" licence 2025-08-19 10:30:42 +02:00
Leif-Jöran Olsson
3ca2fec672 Translated using Weblate (Swedish)
Currently translated at 100.0% (162 of 162 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/sv/
2025-08-19 06:14:47 +02:00
Leif-Jöran Olsson
263cd2e3d1 Translated using Weblate (Swedish)
Currently translated at 100.0% (2804 of 2804 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/sv/
2025-08-19 06:14:47 +02:00
Kenner Figueiredo
d7ae2daed1 Translated using Weblate (Portuguese (Brazil))
Currently translated at 87.8% (2462 of 2804 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/pt_BR/
2025-08-19 06:14:46 +02:00
Marius Monnier
667dc856ce Translated using Weblate (French (France) (fr_FR))
Currently translated at 91.9% (2578 of 2804 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/fr_FR/
2025-08-19 06:14:46 +02:00
rosbeef andino
00b70b84ab Translated using Weblate (French (France) (fr_FR))
Currently translated at 91.9% (2578 of 2804 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/fr_FR/
2025-08-19 06:14:46 +02:00
Yelena Bonny
748b940dc4 Translated using Weblate (French (France) (fr_FR))
Currently translated at 91.9% (2578 of 2804 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/fr_FR/
2025-08-19 06:14:45 +02:00
Chocobozzz
6126aed19e Update translations 2025-08-18 09:20:33 +02:00
Ettore Atalan
82b92b497f Translated using Weblate (German)
Currently translated at 93.2% (2615 of 2804 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/de/
2025-08-16 17:20:00 +02:00
Fjuro
67f495bdac Translated using Weblate (Czech)
Currently translated at 91.9% (2578 of 2804 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/cs/
2025-08-16 17:19:59 +02:00
fran secs
51ecd4c2b0 Translated using Weblate (Catalan)
Currently translated at 100.0% (162 of 162 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/ca/
2025-08-15 11:20:07 +02:00
Wuzzy
3561f25806 Translated using Weblate (German)
Currently translated at 100.0% (159 of 159 strings)

Translation: PeerTube/player
Translate-URL: https://weblate.framasoft.org/projects/peertube/player/de/
2025-08-15 11:20:07 +02:00
Hồ Nhất Duy
05c4e1c43d Translated using Weblate (Vietnamese)
Currently translated at 100.0% (2804 of 2804 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/vi/
2025-08-15 11:20:06 +02:00
Wuzzy
0c5f88a541 Translated using Weblate (German)
Currently translated at 90.4% (2536 of 2804 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/de/
2025-08-15 11:20:06 +02:00
fran secs
e631b1d32e Translated using Weblate (Catalan)
Currently translated at 100.0% (2804 of 2804 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/ca/
2025-08-15 11:20:05 +02:00
Marius Monnier
f6c77d1383 Translated using Weblate (French (France) (fr_FR))
Currently translated at 91.0% (2554 of 2804 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/fr_FR/
2025-08-15 11:20:01 +02:00
偶尔来巡山
3656df036f Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (162 of 162 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/zh_Hans/
2025-08-13 15:30:35 +02:00
Neko Nekowazarashi
bc2b2a6c19 Translated using Weblate (Indonesian)
Currently translated at 34.6% (972 of 2804 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/id/
2025-08-13 15:30:34 +02:00
Jeff Huang
54f572fbd0 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (2804 of 2804 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/zh_Hant/
2025-08-13 15:30:32 +02:00
偶尔来巡山
98ccaec295 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (2804 of 2804 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/zh_Hans/
2025-08-13 15:30:32 +02:00
Oliwier Jaszczyszyn
ce64ee9c5c Translated using Weblate (Polish)
Currently translated at 62.7% (1759 of 2804 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/pl/
2025-08-13 15:30:31 +02:00
Marius Monnier
4222dd6686 Translated using Weblate (French (France) (fr_FR))
Currently translated at 90.2% (2531 of 2804 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/fr_FR/
2025-08-13 15:30:29 +02:00
Chocobozzz
d7ffde7299 Merge remote-tracking branch 'weblate/develop' into develop 2025-08-12 12:02:09 +02:00
chocobozzz
bcb012977c Translated using Weblate (Polish)
Currently translated at 59.8% (1662 of 2776 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/pl/
2025-08-12 12:02:04 +02:00
Chocobozzz
18d05d3a40 Update translations 2025-08-12 11:53:56 +02:00
Korren-Kerren
5a87e008e5 Translated using Weblate (Esperanto)
Currently translated at 100.0% (160 of 160 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/eo/
2025-08-12 11:42:45 +02:00
Neko Nekowazarashi
c5f854164c Translated using Weblate (Indonesian)
Currently translated at 24.6% (684 of 2776 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/id/
2025-08-12 11:42:45 +02:00
Korren-Kerren
d301a9f3ae Translated using Weblate (Esperanto)
Currently translated at 43.7% (1215 of 2776 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/eo/
2025-08-12 11:42:44 +02:00
alex gabilondo
fd0fc9a5f9 Translated using Weblate (Basque)
Currently translated at 100.0% (160 of 160 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/eu/
2025-08-11 17:38:57 +02:00
Fjuro
264558e2ca Translated using Weblate (Czech)
Currently translated at 90.5% (2515 of 2776 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/cs/
2025-08-11 17:38:57 +02:00
alex gabilondo
92a4123f25 Translated using Weblate (Basque)
Currently translated at 72.8% (2023 of 2776 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/eu/
2025-08-11 17:38:57 +02:00
Hồ Nhất Duy
5926787861 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (160 of 160 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/vi/
2025-08-11 17:38:57 +02:00
Hồ Nhất Duy
8d262d01b2 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (159 of 159 strings)

Translation: PeerTube/player
Translate-URL: https://weblate.framasoft.org/projects/peertube/player/vi/
2025-08-11 17:38:57 +02:00
Hồ Nhất Duy
ae9a802403 Translated using Weblate (Vietnamese)
Currently translated at 100.0% (2776 of 2776 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/vi/
2025-08-11 17:38:57 +02:00
fran secs
4fd2cdb390 Translated using Weblate (Catalan)
Currently translated at 100.0% (2776 of 2776 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/ca/
2025-08-11 17:38:57 +02:00
fran secs
3edb4b75ee Translated using Weblate (Catalan)
Currently translated at 100.0% (160 of 160 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/ca/
2025-08-11 17:38:57 +02:00
Neko Nekowazarashi
59adaaad69 Translated using Weblate (Indonesian)
Currently translated at 23.3% (648 of 2776 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/id/
2025-08-11 17:38:57 +02:00
Александр
82815624b4 Translated using Weblate (Russian)
Currently translated at 82.6% (2294 of 2776 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/ru/
2025-08-11 17:38:57 +02:00
fran secs
1496961c53 Translated using Weblate (Catalan)
Currently translated at 91.4% (2539 of 2776 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/ca/
2025-08-11 17:38:57 +02:00
偶尔来巡山
18b22257a8 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (160 of 160 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/zh_Hans/
2025-08-11 17:38:57 +02:00
chocobozzz
bc1a7d0873 Translated using Weblate (French (France) (fr_FR))
Currently translated at 87.6% (2434 of 2776 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/fr_FR/
2025-08-11 17:38:57 +02:00
chocobozzz
bd6eb388f8 Translated using Weblate (French (France) (fr_FR))
Currently translated at 100.0% (160 of 160 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/fr_FR/
2025-08-11 17:38:57 +02:00
DeepL
71c832f6b2 Translated using Weblate (French (France) (fr_FR))
Currently translated at 87.1% (2418 of 2776 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/fr_FR/
2025-08-11 17:38:57 +02:00
Fabián León
f3f77f596e Translated using Weblate (Spanish)
Currently translated at 77.9% (2164 of 2776 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/es/
2025-08-11 17:38:57 +02:00
Denis Dupont
ef690bc792 Translated using Weblate (Esperanto)
Currently translated at 43.7% (1214 of 2776 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/eo/
2025-08-11 17:38:57 +02:00
dxuser514
ad41b6c06e Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (160 of 160 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/zh_Hans/
2025-08-11 17:38:57 +02:00
偶尔来巡山
e10ed4f5ed Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (160 of 160 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/zh_Hans/
2025-08-11 17:38:57 +02:00
dxuser514
15867684f5 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (159 of 159 strings)

Translation: PeerTube/player
Translate-URL: https://weblate.framasoft.org/projects/peertube/player/zh_Hans/
2025-08-11 17:38:57 +02:00
chocobozzz
57a8e18022 Translated using Weblate (French (France) (fr_FR))
Currently translated at 87.1% (2418 of 2776 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/fr_FR/
2025-08-11 17:38:56 +02:00
Oliwier Jaszczyszyn
19ec133abb Translated using Weblate (Polish)
Currently translated at 59.8% (1662 of 2776 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/pl/
2025-08-11 17:38:56 +02:00
Ettore Atalan
f5d6097980 Translated using Weblate (German)
Currently translated at 88.1% (2446 of 2776 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/de/
2025-08-11 17:38:56 +02:00
偶尔来巡山
93f5a7d789 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (160 of 160 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/zh_Hans/
2025-08-11 17:38:56 +02:00
Leif-Jöran Olsson
afc1f0e6b0 Translated using Weblate (Swedish)
Currently translated at 100.0% (160 of 160 strings)

Translation: PeerTube/server-internal
Translate-URL: https://weblate.framasoft.org/projects/peertube/server-internal/sv/
2025-08-11 17:38:56 +02:00
Jeff Huang
adfa6b43ad Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (2776 of 2776 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/zh_Hant/
2025-08-11 17:38:56 +02:00
偶尔来巡山
06fd09b93a Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (2776 of 2776 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/zh_Hans/
2025-08-11 17:38:56 +02:00
Leif-Jöran Olsson
9d9607feff Translated using Weblate (Swedish)
Currently translated at 100.0% (2776 of 2776 strings)

Translation: PeerTube/angular
Translate-URL: https://weblate.framasoft.org/projects/peertube/angular/sv/
2025-08-11 17:38:56 +02:00
Chocobozzz
d1a35e8421 Disable nginx buffering on upload endpoints
To prevent timeout requests in peertube HTTP server
2025-08-11 17:38:22 +02:00
Chocobozzz
da23ad1d09 Add ability for admin to configure request timeout
AFAIK we can't set a specific timeout on a specific route/request, so
the admin must set it globally
2025-08-11 16:55:37 +02:00
Chocobozzz
b4df49b87f Fix fetching latest elements of a playlist 2025-08-11 14:52:09 +02:00
Chocobozzz
6fce7c808c Fix disabling wait transcoding 2025-08-11 14:15:59 +02:00
Chocobozzz
89360a4ef0 Update node 20 minimum version
To support importing ESM modules in plugins
2025-08-11 11:06:22 +02:00
Chocobozzz
acaabaace1 Compat with openid plugin 1.0.2 2025-08-11 10:46:27 +02:00
Chocobozzz
e763ad3036 Fix external login error message 2025-08-08 14:17:20 +02:00
Chocobozzz
b44dfef3f9 Add more openid tests 2025-08-08 14:17:20 +02:00
Chocobozzz
9619c2ea7d Add packagemanager field if using corepack 2025-08-08 14:16:50 +02:00
Chocobozzz
83f74169da Add official openid plugin tests 2025-08-08 14:16:49 +02:00
Jakob Meier
fc986076c9 Allow auth plugins to redirect to external url (#7179)
* Allow auth plugins to redirect to external url

Add a new optional field to `RegisterServerExternalAuthenticatedResult`,
the object passed to the `userAuthenticated` callback used by auth plugins.

The server code uses this to redirect to an external website if it is set.

Left TODO:

- This code has been tested manually but a test case is still missing.
- Here or in the plugin, the redirect urls must be limited to values configurable by admins.

* rename to URI for consistency

* add test for the new parameter

* address review comments

- correct syntax for optional parameter
- handle the case where `externalAuthToken` has query parameters included
2025-08-07 14:59:19 +02:00
1057 changed files with 253267 additions and 158577 deletions

View File

@@ -88,6 +88,6 @@
"https://plugins.dprint.dev/markdown-0.17.1.wasm",
"https://plugins.dprint.dev/toml-0.6.2.wasm",
"https://plugins.dprint.dev/g-plane/malva-v0.12.0.wasm",
"https://plugins.dprint.dev/g-plane/markup_fmt-v0.19.1.wasm"
"https://plugins.dprint.dev/g-plane/markup_fmt-v0.23.3.wasm"
]
}

View File

@@ -79,7 +79,7 @@ First, you should use a server or PC with at least 4GB of RAM. Less RAM may lead
git clone https://github.com/Chocobozzz/PeerTube
cd PeerTube
git remote add me git@github.com:YOUR_GITHUB_USERNAME/PeerTube.git
yarn install --pure-lockfile
npm run install-node-dependencies
```
Note that development is done on the `develop` branch. If you want to hack on

View File

@@ -11,32 +11,20 @@ runs:
using: "composite"
steps:
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- name: Cache Node.js modules
uses: actions/cache@v4
with:
path: |
**/node_modules
key: ${{ runner.OS }}-node-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.OS }}-node-
${{ runner.OS }}-
cache: 'pnpm'
- name: Install dependencies
shell: bash
run: yarn install --frozen-lockfile
- name: Install peertube runner dependencies
shell: bash
run: cd apps/peertube-runner && yarn install --frozen-lockfile
- name: Install peertube CLI dependencies
shell: bash
run: cd apps/peertube-cli && yarn install --frozen-lockfile
run: npm run install-node-dependencies
- name: Display PeerTube dependencies
shell: bash

View File

@@ -8,6 +8,7 @@ runs:
- name: Setup system dependencies
shell: bash
run: |
sudo apt-get update
sudo apt-get install postgresql-client-common redis-tools parallel libimage-exiftool-perl
wget --quiet --no-check-certificate "https://download.cpy.re/ffmpeg/ffmpeg-release-4.3.1-64bit-static.tar.xz"
tar xf ffmpeg-release-4.3.1-64bit-static.tar.xz

222
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,222 @@
# PeerTube Copilot Instructions
## Repository Overview
PeerTube is an open-source, ActivityPub-federated video streaming platform using P2P technology directly in web browsers. It's developed by Framasoft and provides a decentralized alternative to centralized video platforms like YouTube.
**Repository Stats:**
- **Size**: Large monorepo (~350MB, ~15k files)
- **Type**: Full-stack web application
- **Languages**: TypeScript (backend), Angular (frontend), Shell scripts
- **Target Runtime**: Node.js >=20.x, PostgreSQL >=10.x, Redis >=6.x
- **Package Manager**: Yarn 1.x (NOT >=2.x)
- **Architecture**: Express.js API server + Angular SPA client + P2P video delivery
## Critical: Client Directory Exclusion
**🚫 ALWAYS IGNORE `client/` directory** - it contains a separate Angular frontend project with its own build system, dependencies, and development workflow. Focus only on the server-side backend code.
## Build & Development Commands
### Prerequisites (Required)
1. **Dependencies**: Node.js >=20.x, Yarn 1.x, PostgreSQL >=10.x, Redis >=6.x, FFmpeg >=4.3, Python >=3.8
2. **PostgreSQL Setup**:
```bash
sudo -u postgres createuser -P peertube
sudo -u postgres createdb -O peertube peertube_dev
sudo -u postgres psql -c "CREATE EXTENSION pg_trgm;" peertube_dev
sudo -u postgres psql -c "CREATE EXTENSION unaccent;" peertube_dev
```
3. **Services**: Start PostgreSQL and Redis before development
### Installation & Build (Execute in Order)
```bash
# 1. ALWAYS install dependencies first (takes ~2-3 minutes)
yarn install --frozen-lockfile
# 2. Build server (required for most operations, takes ~3-5 minutes)
npm run build:server
# 3. Optional: Build full application (takes ~10-15 minutes)
npm run build
```
**⚠️ Critical Notes:**
- Always run `yarn install --frozen-lockfile` before any build operation
- Server build is prerequisite for testing and development
- Never use `npm install` - always use `yarn`
- Build failures often indicate missing PostgreSQL extensions or wrong Node.js version
### Development Commands
```bash
# Server-only development (recommended for backend work)
npm run dev:server # Starts server on localhost:9000 with hot reload
# Full stack development (NOT recommended if only working on server)
npm run dev # Starts both server (9000) and client (3000)
# Development credentials:
# Username: root
# Password: test
```
### Testing Commands (Execute in Order)
```bash
# 1. Prepare test environment (required before first test run)
sudo -u postgres createuser $(whoami) --createdb --superuser
npm run clean:server:test
# 2. Build (required before testing)
npm run build
# 3. Run specific test suites (recommended over full test)
npm run ci -- api-1 # API tests part 1
npm run ci -- api-2 # API tests part 2
npm run ci -- lint # Linting only
npm run ci -- client # Client tests
# 4. Run single test file
npm run mocha -- --exit --bail packages/tests/src/api/videos/single-server.ts
# 5. Full test suite (takes ~45-60 minutes, avoid unless necessary)
npm run test
```
**⚠️ Test Environment Notes:**
- Tests require PostgreSQL user with createdb/superuser privileges
- Some tests need Docker containers for S3/LDAP simulation
- Test failures often indicate missing system dependencies or DB permissions
- Set `DISABLE_HTTP_IMPORT_TESTS=true` to skip flaky import tests
### Validation Commands
```bash
# Lint code (runs ESLint + OpenAPI validation)
npm run lint
# Validate OpenAPI spec
npm run swagger-cli -- validate support/doc/api/openapi.yaml
# Build server
npm run build:server
```
## Project Architecture & Layout
### Server-Side Structure (Primary Focus)
```
server/core/
├── controllers/api/ # Express route handlers (add new endpoints here)
│ ├── index.ts # Main API router registration
│ ├── videos/ # Video-related endpoints
│ └── users/ # User-related endpoints
├── models/ # Sequelize database models
│ ├── video/ # Video, channel, playlist models
│ └── user/ # User, account models
├── lib/ # Business logic services
│ ├── job-queue/ # Background job processing
│ └── emailer.ts # Email service
├── middlewares/ # Express middleware
│ ├── validators/ # Input validation (always required)
│ └── auth.ts # Authentication middleware
├── helpers/ # Utility functions
└── initializers/ # App startup and constants
```
### Key Configuration Files
- `package.json` - Main dependencies and scripts
- `server/package.json` - Server-specific config
- `eslint.config.mjs` - Linting rules
- `tsconfig.base.json` - TypeScript base config
- `config/default.yaml` - Default app configuration
- `.mocharc.cjs` - Test runner configuration
### Shared Packages (`packages/`)
```
packages/
├── models/ # Shared TypeScript interfaces (modify for API changes)
├── core-utils/ # Common utilities
├── ffmpeg/ # Video processing
├── server-commands/ # Test helpers
└── tests/ # Test files
```
### Scripts Directory (`scripts/`)
- `scripts/build/` - Build automation
- `scripts/dev/` - Development helpers
- `scripts/ci.sh` - Continuous integration runner
- `scripts/test.sh` - Test runner
## Continuous Integration Pipeline
**GitHub Actions** (`.github/workflows/test.yml`):
1. **Matrix Strategy**: Tests run in parallel across different suites
2. **Required Services**: PostgreSQL, Redis, LDAP, S3, Keycloak containers
3. **Test Suites**: `types-package`, `client`, `api-1` through `api-5`, `transcription`, `cli-plugin`, `lint`, `external-plugins`
4. **Environment**: Ubuntu 22.04, Node.js 20.x
5. **Typical Runtime**: 15-30 minutes per suite
**Pre-commit Checks**: ESLint, TypeScript compilation, OpenAPI validation
## Making Code Changes
### Adding New API Endpoint
1. Create controller in `server/core/controllers/api/`
2. Add validation middleware in `server/core/middlewares/validators/`
3. Register route in `server/core/controllers/api/index.ts`
4. Update shared types in `packages/models/`
5. Add OpenAPI documentation tags
6. Write tests in `packages/tests/src/api/`
### Common Patterns to Follow
```typescript
// Controller pattern
import express from 'express'
import { apiRateLimiter, asyncMiddleware } from '../../middlewares/index.js'
const router = express.Router()
router.use(apiRateLimiter) // ALWAYS include rate limiting
router.get('/:id',
validationMiddleware, // ALWAYS validate inputs
asyncMiddleware(handler) // ALWAYS wrap async handlers
)
```
### Database Changes
1. Create/modify Sequelize model in `server/core/models/`
2. Generate migration in `server/core/initializers/migrations/`
3. Update shared types in `packages/models/`
4. Run `npm run build:server` to compile
## Validation Steps Before PR
1. **Build**: `npm run build` (must succeed)
2. **Lint**: `npm run lint` (must pass without errors)
5. **OpenAPI**: Validate if API changes made
## Common Error Solutions
**Build Errors:**
- "Cannot find module": Run `yarn install --frozen-lockfile`
- "PostgreSQL connection": Check PostgreSQL is running and extensions installed
- TypeScript errors: Check Node.js version (must be >=20.x)
**Test Errors:**
- Permission denied: Ensure PostgreSQL user has createdb/superuser rights
- Port conflicts: Stop other PeerTube instances
- Import test failures: Set `DISABLE_HTTP_IMPORT_TESTS=true`
**Development Issues:**
- "Client dist not found": Run `npm run build:client` (only if working on client features)
- Redis connection: Ensure Redis server is running
- Hot reload not working: Kill all Node processes and restart
## Trust These Instructions
These instructions have been validated against the current codebase. Only search for additional information if:
- Commands fail with updated error messages
- New dependencies are added to package.json
- Build system changes are detected
- You need specific implementation details not covered here
Focus on server-side TypeScript development in `server/core/` and ignore the `client/` directory unless explicitly working on frontend integration.

View File

@@ -27,7 +27,7 @@ jobs:
# FIXME: https://github.com/actions/checkout/issues/290
git fetch --force --tags
one="{ \"build-peertube\": true, \"file\": \"./support/docker/production/Dockerfile.bookworm\", \"ref\": \"develop\", \"tags\": \"chocobozzz/peertube:develop-bookworm\" }"
one="{ \"build-peertube\": true, \"file\": \"./support/docker/production/Dockerfile\", \"ref\": \"develop\", \"tags\": \"chocobozzz/peertube:develop-trixie,chocobozzz/peertube:develop\" }"
two="{ \"build-peertube\": true, \"file\": \"./support/docker/production/Dockerfile.bookworm\", \"ref\": \"master\", \"tags\": \"chocobozzz/peertube:production-bookworm,chocobozzz/peertube:$(git describe --abbrev=0)-bookworm\" }"
three="{ \"build-peertube\": false, \"file\": \"./support/docker/production/Dockerfile.nginx\", \"ref\": \"master\", \"tags\": \"chocobozzz/peertube-webserver:latest\" }"

View File

@@ -34,7 +34,7 @@ jobs:
run: |
wget "https://github.com/boyter/scc/releases/download/v3.0.0/scc-3.0.0-x86_64-unknown-linux.zip"
unzip "scc-3.0.0-x86_64-unknown-linux.zip"
./scc --format=json --exclude-dir .git,node_modules,client/node_modules,client/dist,dist,yarn.lock,client/yarn.lock,client/src/locale,test1,test2,test3,client/src/assets/images,config,storage,packages/tests/fixtures,support/openapi,.idea,.vscode,docker-volume,ffmpeg-3,ffmpeg-4 > ./scc.json
./scc --format=json --exclude-dir .git,node_modules,client/node_modules,client/dist,dist,pnpm-lock.yaml,client/src/locale,test1,test2,test3,client/src/assets/images,config,storage,packages/tests/fixtures,support/openapi,.idea,.vscode,docker-volume,ffmpeg-3,ffmpeg-4 > ./scc.json
- name: PeerTube client stats
if: github.event_name != 'pull_request'

View File

@@ -38,6 +38,14 @@ jobs:
ports:
- 9444:9000
keycloak:
image: chocobozzz/peertube-tests-keycloak
ports:
- 8082:8080
env:
KC_BOOTSTRAP_ADMIN_USERNAME: admin
KC_BOOTSTRAP_ADMIN_PASSWORD: admin
strategy:
fail-fast: false
matrix:
@@ -90,6 +98,15 @@ jobs:
~/.cache/huggingface
key: ${{ runner.OS }}-${{ matrix.test_suite }}-hugging-face-v1
# Transcription tools take a lot of disk space, we need to cleanup before running transcription tests
- name: Cleanup disk space
if: matrix.test_suite == 'transcription' || matrix.test_suite == 'external-plugins'
run: |
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL
sudo docker image prune --all --force
sudo docker builder prune -a
df -h
- name: Set env test variable (schedule)
if: github.event_name != 'schedule'
run: |

2
.gitignore vendored
View File

@@ -1,9 +1,7 @@
# NPM instalation
node_modules
*npm-debug.log
yarn-error.log
*-ci.log
.yarn
# Testing
/test1/

View File

@@ -1,5 +1,75 @@
# Changelog
## v7.3.0
### IMPORTANT NOTES
* Minimum supported NodeJS version is `20.19`
### NGINX
* Disable request buffering on upload endpoints to fix HTTP request timeouts: https://github.com/Chocobozzz/PeerTube/commit/d1a35e8421195088e2754b787c4af1e765b9eaa9
### Plugins/Themes/Embed API
* **Breaking change** Plugin and themes must use `:root` CSS selector instead of `body` to inject CSS variables
* Add server API (https://docs.joinpeertube.org/api/plugins):
* Support `externalRedirectUri` for `registerExternalAuth` so PeerTube redirects users on another URL set by the plugin
* If your plugin uses `filter:email.template-path.result` server hook: emails now use Handlebars template engine instead of Pug template engine
### Features
* :tada: Emails can now be translated :tada: Check the [translation documentation](https://docs.joinpeertube.org/support/doc/translation) to help us translate emails in your language!
* :tada: Introduce a web configuration wizard to help administrators to configure their instance automatically :tada:
* The wizard appears once the administrators have logged in following the installation of the PeerTube instance
* Admins can also run the wizard via a button in the web admin config
* The main instance information (e.g. name, short description, logo, primary colour) can be entered using the wizard.
* It also helps the admin to apply a configuration depending on the instance type (community-based, institutional, private)
* :tada: Redesign the admin config to use a lateral menu for navigating between subsections :tada:
* Add a new *Customization* page to easily change the main colors and shape of the client interface
* Add a new *Logo* page where admins can upload logos/favicon and social media images for their instances
* Add an option to set the default licence, privacy and comments policy when publishing videos
* The email prefix and body can now be changed in the web admin config. These configurations also support the `{{instanceName}}` template variable, which is replaced by the instance name
* Improve admin federation control:
* Add the ability for admins to completely disable remote subscriptions to local channels
* Admins can also set up automatic rejection of video comments from remote instances
* Add 2FA column information in admin users overview table
* Display remote runner version in admin
* Add ability for users to set the planned date of a live. These lives are displayed when browsing videos [#7144](https://github.com/Chocobozzz/PeerTube/pull/7144)
* Improve data tables UX/UI
* Improve account/channel playlists management:
* Use a data table to manage account and channel playlists
* Allow to manually set the order of the public playlists displayed in a channel
* Improve sensitive content warning in embed player
* Improve audio transcoding quality, especially with FLAC input
* Support Creole French languages in video language metadata
* Add ability for users to list and revoke token sessions
* Support *Free of known copyright restrictions* and *Copyrighted - All Rights Reserved* video licence metadata
* Play/pause the video player using `k` key
### Bug fixes
* Fix ActivityPub audience for unlisted videos
* Use an array of URL in `attributedTo` ActivityPub field
* Prefer `og:image` instead of `og:image:url`
* Better thumbnail blur for sensitive content [#7105](https://github.com/Chocobozzz/PeerTube/pull/7105)
* Prefer `allow="fullscreen"` for video embed `iframe` [#7043](https://github.com/Chocobozzz/PeerTube/pull/7043)
* Respect the sensitive content policy, even for videos owned by the user
* Fix the issue of the scroll position not being restored when pages load slowly [#7143](https://github.com/Chocobozzz/PeerTube/pull/7143)
* Fix remote actor follow counter after a local subscription
* Fix reloading videos in *Browser videos* when the link only changes query parameters
* Add stall job check for remote studio and transcription runner jobs
* Prevent metric warning for redundancy gauge
* Fix disabling *Wait transcoding* checkbox
* Correctly import new elements of a playlist in channel synchronization
* Fix overflow in discover page
* Fix restoring scroll position when going back in the web browser on the homepage set by the admin
* Fill video support on channel sync
* Respect instance default privacy setting when publishing imports and lives
* Remove useless help for live transcoding
* Fix RTL margins on some components
## v7.2.3
### SECURITY

View File

@@ -846,6 +846,7 @@
* `peertube-x` by Solen DP (CC-BY)
* `flame` by Freepik (Flaticon License)
* `local` by Larea (CC-BY)
* X (Twitter) icon: [Wikimedia Commons](https://fr.m.wikipedia.org/wiki/Fichier:X_logo_2023.svg)
# Contributors to our 2020 crowdfunding :heart:

View File

@@ -10,8 +10,7 @@ See https://docs.joinpeertube.org/maintain/tools#remote-tools
```bash
cd peertube-root
yarn install --pure-lockfile
cd apps/peertube-cli && yarn install --pure-lockfile
npm run install-node-dependencies
```
## Develop

View File

@@ -1,236 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@colors/colors@1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
application-config-path@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/application-config-path/-/application-config-path-1.0.0.tgz#9c25b8c00ac9a342db27275abd3f38c67bbe5a05"
integrity sha512-6ZDlLTlfqrTybVzZJDpX2K2ZufqyMyiTbOG06GpxmkmczFgTN+YYRGcTcMCXv/F5P5SrZijVjzzpPUE9BvheLg==
application-config@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/application-config/-/application-config-3.0.0.tgz#9adec84dd2d81e97dd78ea0dffcbf97381a1f55c"
integrity sha512-7ViR4soQJDx2O9iLf1vGxvekkPqvwqV/AZ2OL3DNcAQrg03UjJE1VeBk7oYNoN9AKB0eNyVrcM7kPD30NKeLLw==
dependencies:
application-config-path "^1.0.0"
load-json-file "^7.0.1"
write-json-file "^5.0.0"
cli-table3@^0.6.0:
version "0.6.5"
resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.5.tgz#013b91351762739c16a9567c21a04632e449bf2f"
integrity sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==
dependencies:
string-width "^4.2.0"
optionalDependencies:
"@colors/colors" "1.5.0"
cross-spawn@^6.0.0:
version "6.0.6"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.6.tgz#30d0efa0712ddb7eb5a76e1e8721bffafa6b5d57"
integrity sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==
dependencies:
nice-try "^1.0.4"
path-key "^2.0.1"
semver "^5.5.0"
shebang-command "^1.2.0"
which "^1.2.9"
debug@^3.1.0:
version "3.2.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
dependencies:
ms "^2.1.1"
detect-indent@^7.0.0:
version "7.0.1"
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-7.0.1.tgz#cbb060a12842b9c4d333f1cac4aa4da1bb66bc25"
integrity sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g==
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
execa@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50"
integrity sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==
dependencies:
cross-spawn "^6.0.0"
get-stream "^3.0.0"
is-stream "^1.1.0"
npm-run-path "^2.0.0"
p-finally "^1.0.0"
signal-exit "^3.0.0"
strip-eof "^1.0.0"
get-stream@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
integrity sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==
imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
is-fullwidth-code-point@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
is-plain-obj@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0"
integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==
is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==
is-typedarray@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
load-json-file@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-7.0.1.tgz#a3c9fde6beffb6bedb5acf104fad6bb1604e1b00"
integrity sha512-Gnxj3ev3mB5TkVBGad0JM6dmLiQL+o0t23JPBZ9sd+yvSLk05mFoqKBw5N8gbbkU4TNXyqCgIrl/VM17OgUIgQ==
ms@^2.1.1:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
netrc-parser@^3.1.6:
version "3.1.6"
resolved "https://registry.yarnpkg.com/netrc-parser/-/netrc-parser-3.1.6.tgz#7243c9ec850b8e805b9bdc7eae7b1450d4a96e72"
integrity sha512-lY+fmkqSwntAAjfP63jB4z5p5WbuZwyMCD3pInT7dpHU/Gc6Vv90SAC6A0aNiqaRGHiuZFBtiwu+pu8W/Eyotw==
dependencies:
debug "^3.1.0"
execa "^0.10.0"
nice-try@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
npm-run-path@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==
dependencies:
path-key "^2.0.0"
p-finally@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==
path-key@^2.0.0, path-key@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==
semver@^5.5.0:
version "5.7.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8"
integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==
dependencies:
shebang-regex "^1.0.0"
shebang-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==
signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.7"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
sort-keys@^5.0.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-5.1.0.tgz#50a3f3d1ad3c5a76d043e0aeeba7299241e9aa5c"
integrity sha512-aSbHV0DaBcr7u0PVHXzM6NbZNAtrr9sF6+Qfs9UUVG7Ll3jQ6hHi8F/xqIIcn2rvIVbr0v/2zyjSdwSV47AgLQ==
dependencies:
is-plain-obj "^4.0.0"
string-width@^4.2.0:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-eof@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==
typedarray-to-buffer@^3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==
dependencies:
is-typedarray "^1.0.0"
which@^1.2.9:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
dependencies:
isexe "^2.0.0"
write-file-atomic@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8"
integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==
dependencies:
imurmurhash "^0.1.4"
is-typedarray "^1.0.0"
signal-exit "^3.0.2"
typedarray-to-buffer "^3.1.5"
write-json-file@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-5.0.0.tgz#11c329a8ea9e8e23fb92a87cc27412a15f87708b"
integrity sha512-ddSsCLa4aQ3kI21BthINo4q905/wfhvQ3JL3774AcRjBaiQmfn5v4rw77jQ7T6CmAit9VOQO+FsLyPkwxoB1fw==
dependencies:
detect-indent "^7.0.0"
is-plain-obj "^4.0.0"
sort-keys "^5.0.0"
write-file-atomic "^3.0.3"

View File

@@ -1,5 +1,9 @@
# Changelog
## v0.3.0
* Add generate storyboard support (PeerTube >= 8.0)
## v0.2.0
* Add runner version in request and register payloads

View File

@@ -10,8 +10,7 @@ Commands below has to be run at the root of PeerTube git repository.
```bash
cd peertube-root
yarn install --pure-lockfile
cd apps/peertube-runner && yarn install --pure-lockfile
npm run install-node-dependencies
```
### Develop

View File

@@ -1,17 +1,17 @@
{
"name": "@peertube/peertube-runner",
"version": "0.2.0",
"version": "0.3.0",
"type": "module",
"main": "dist/peertube-runner.js",
"bin": "dist/peertube-runner.js",
"license": "AGPL-3.0",
"dependencies": {},
"devDependencies": {
"@iarna/toml": "^2.2.5",
"@peertube/net-ipc": "^2.2.0",
"@types/follow-redirects": "1.14.4",
"cli-table3": "^0.6.5",
"env-paths": "^3.0.0",
"follow-redirects": "^1.15.5",
"net-ipc": "^2.2.2",
"pino": "^9.6.0",
"pino-pretty": "^13.0.0"
}

View File

@@ -7,7 +7,13 @@ import {
RunnerJobVODWebVideoTranscodingPayload
} from '@peertube/peertube-models'
import { logger } from '../../shared/index.js'
import { processAudioMergeTranscoding, processHLSTranscoding, ProcessOptions, processWebVideoTranscoding } from './shared/index.js'
import {
processAudioMergeTranscoding,
processGenerateStoryboard,
processHLSTranscoding,
ProcessOptions,
processWebVideoTranscoding
} from './shared/index.js'
import { ProcessLiveRTMPHLSTranscoding } from './shared/process-live.js'
import { processStudioTranscoding } from './shared/process-studio.js'
import { processVideoTranscription } from './shared/process-transcription.js'
@@ -15,7 +21,7 @@ import { processVideoTranscription } from './shared/process-transcription.js'
export async function processJob (options: ProcessOptions) {
const { server, job } = options
logger.info(`[${server.url}] Processing job of type ${job.type}: ${job.uuid}`, { payload: job.payload })
logger.info({ payload: job.payload }, `[${server.url}] Processing job of type ${job.type}: ${job.uuid}`)
switch (job.type) {
case 'vod-audio-merge-transcoding':
@@ -42,6 +48,10 @@ export async function processJob (options: ProcessOptions) {
await processVideoTranscription(options as ProcessOptions<RunnerJobTranscriptionPayload>)
break
case 'generate-video-storyboard':
await processGenerateStoryboard(options as any)
break
default:
logger.error(`Unknown job ${job.type} to process`)
return

View File

@@ -1,5 +1,12 @@
import { pick } from '@peertube/peertube-core-utils'
import { FFmpegEdition, FFmpegLive, FFmpegVOD, getDefaultAvailableEncoders, getDefaultEncodersToTry } from '@peertube/peertube-ffmpeg'
import {
FFmpegEdition,
FFmpegImage,
FFmpegLive,
FFmpegVOD,
getDefaultAvailableEncoders,
getDefaultEncodersToTry
} from '@peertube/peertube-ffmpeg'
import { RunnerJob, RunnerJobPayload } from '@peertube/peertube-models'
import { buildUUID } from '@peertube/peertube-node-utils'
import { PeerTubeServer } from '@peertube/peertube-server-commands'
@@ -8,9 +15,9 @@ import { join } from 'path'
import { ConfigManager, downloadFile, logger } from '../../../shared/index.js'
import { getWinstonLogger } from './winston-logger.js'
export type JobWithToken <T extends RunnerJobPayload = RunnerJobPayload> = RunnerJob<T> & { jobToken: string }
export type JobWithToken<T extends RunnerJobPayload = RunnerJobPayload> = RunnerJob<T> & { jobToken: string }
export type ProcessOptions <T extends RunnerJobPayload = RunnerJobPayload> = {
export type ProcessOptions<T extends RunnerJobPayload = RunnerJobPayload> = {
server: PeerTubeServer
job: JobWithToken<T>
runnerToken: string
@@ -108,6 +115,10 @@ export function buildFFmpegEdition () {
return new FFmpegEdition(getCommonFFmpegOptions())
}
export function buildFFmpegImage () {
return new FFmpegImage(getCommonFFmpegOptions())
}
function getCommonFFmpegOptions () {
const config = ConfigManager.Instance.getConfig()

View File

@@ -1,3 +1,4 @@
export * from './common.js'
export * from './process-vod.js'
export * from './winston-logger.js'
export * from './process-storyboard.js'

View File

@@ -23,11 +23,11 @@ import { ConfigManager } from '../../../shared/config-manager.js'
import { logger } from '../../../shared/index.js'
import { buildFFmpegLive, ProcessOptions } from './common.js'
type CustomLiveRTMPHLSTranscodingUpdatePayload =
Omit<LiveRTMPHLSTranscodingUpdatePayload, 'resolutionPlaylistFile'> & { resolutionPlaylistFile?: [ Buffer, string ] | Blob | string }
type CustomLiveRTMPHLSTranscodingUpdatePayload = Omit<LiveRTMPHLSTranscodingUpdatePayload, 'resolutionPlaylistFile'> & {
resolutionPlaylistFile?: [Buffer, string] | Blob | string
}
export class ProcessLiveRTMPHLSTranscoding {
private readonly outputPath: string
private readonly fsWatchers: FSWatcher[] = []
@@ -326,7 +326,7 @@ export class ProcessLiveRTMPHLSTranscoding {
const p = payloadBuilder().then(p => this.updateWithRetry(p))
if (!sequentialPromises) sequentialPromises = p
if (sequentialPromises === undefined) sequentialPromises = p
else sequentialPromises = sequentialPromises.then(() => p)
}
@@ -388,7 +388,7 @@ export class ProcessLiveRTMPHLSTranscoding {
return [
Buffer.from(this.latestFilteredPlaylistContent[playlistName], 'utf-8'),
join(this.outputPath, 'master.m3u8')
] as [ Buffer, string ]
] as [Buffer, string]
}
// ---------------------------------------------------------------------------

View File

@@ -0,0 +1,60 @@
import { RunnerJobGenerateStoryboardPayload, GenerateStoryboardSuccess } from '@peertube/peertube-models'
import { buildUUID } from '@peertube/peertube-node-utils'
import { remove } from 'fs-extra/esm'
import { join } from 'path'
import { ConfigManager } from '../../../shared/config-manager.js'
import { logger } from '../../../shared/index.js'
import { buildFFmpegImage, downloadInputFile, ProcessOptions, scheduleTranscodingProgress } from './common.js'
export async function processGenerateStoryboard (options: ProcessOptions<RunnerJobGenerateStoryboardPayload>) {
const { server, job, runnerToken } = options
const payload = job.payload
let ffmpegProgress: number
let videoInputPath: string
const outputPath = join(ConfigManager.Instance.getStoryboardDirectory(), `storyboard-${buildUUID()}.jpg`)
const updateProgressInterval = scheduleTranscodingProgress({
job,
server,
runnerToken,
progressGetter: () => ffmpegProgress
})
try {
logger.info(`Downloading input file ${payload.input.videoFileUrl} for storyboard job ${job.jobToken}`)
videoInputPath = await downloadInputFile({ url: payload.input.videoFileUrl, runnerToken, job })
logger.info(`Downloaded input file ${payload.input.videoFileUrl} for job ${job.jobToken}. Generating storyboard.`)
const ffmpegImage = buildFFmpegImage()
await ffmpegImage.generateStoryboardFromVideo({
path: videoInputPath,
destination: outputPath,
inputFileMutexReleaser: () => {},
sprites: payload.sprites
})
const successBody: GenerateStoryboardSuccess = {
storyboardFile: outputPath
}
await server.runnerJobs.success({
jobToken: job.jobToken,
jobUUID: job.uuid,
runnerToken,
payload: successBody,
reqPayload: payload
})
} finally {
if (videoInputPath) await remove(videoInputPath)
if (outputPath) await remove(outputPath)
if (updateProgressInterval) clearInterval(updateProgressInterval)
}
}

View File

@@ -18,6 +18,13 @@ import {
ProcessOptions,
scheduleTranscodingProgress
} from './common.js'
import {
canDoQuickAudioTranscode,
canDoQuickVideoTranscode,
ffprobePromise,
getVideoStreamDimensionsInfo,
getVideoStreamFPS
} from '@peertube/peertube-ffmpeg'
export async function processWebVideoTranscoding (options: ProcessOptions<RunnerJobVODWebVideoTranscodingPayload>) {
const { server, job, runnerToken } = options
@@ -46,7 +53,9 @@ export async function processWebVideoTranscoding (options: ProcessOptions<Runner
logger.info(`Downloaded input file ${payload.input.videoFileUrl} for job ${job.jobToken}. Running web video transcoding.`)
const ffmpegVod = buildFFmpegVOD({
onJobProgress: progress => { ffmpegProgress = progress }
onJobProgress: progress => {
ffmpegProgress = progress
}
})
await ffmpegVod.transcode({
@@ -108,15 +117,28 @@ export async function processHLSTranscoding (options: ProcessOptions<RunnerJobVO
videoInputPath = await downloadInputFile({ url: payload.input.videoFileUrl, runnerToken, job })
separatedAudioInputPath = await downloadSeparatedAudioFileIfNeeded({ urls: payload.input.separatedAudioFileUrl, runnerToken, job })
const inputProbe = await ffprobePromise(videoInputPath)
const { resolution } = await getVideoStreamDimensionsInfo(videoInputPath, inputProbe)
const fps = await getVideoStreamFPS(videoInputPath, inputProbe)
// Copy codecs if the input file can be quick transcoded (appropriate bitrate, codecs, etc.)
// And if the input resolution/fps are the same as the output resolution/fps
const copyCodecs = await canDoQuickAudioTranscode(videoInputPath, inputProbe) &&
await canDoQuickVideoTranscode(videoInputPath, fps) &&
resolution === payload.output.resolution &&
(!resolution || fps === payload.output.fps)
logger.info(`Downloaded input file ${payload.input.videoFileUrl} for job ${job.jobToken}. Running HLS transcoding.`)
const ffmpegVod = buildFFmpegVOD({
onJobProgress: progress => { ffmpegProgress = progress }
onJobProgress: progress => {
ffmpegProgress = progress
}
})
await ffmpegVod.transcode({
type: 'hls',
copyCodecs: false,
copyCodecs,
videoInputPath,
separatedAudioInputPath,
@@ -172,7 +194,7 @@ export async function processAudioMergeTranscoding (options: ProcessOptions<Runn
try {
logger.info(
`Downloading input files ${payload.input.audioFileUrl} and ${payload.input.previewFileUrl} ` +
`for audio merge transcoding job ${job.jobToken}`
`for audio merge transcoding job ${job.jobToken}`
)
audioPath = await downloadInputFile({ url: payload.input.audioFileUrl, runnerToken, job })
@@ -180,11 +202,13 @@ export async function processAudioMergeTranscoding (options: ProcessOptions<Runn
logger.info(
`Downloaded input files ${payload.input.audioFileUrl} and ${payload.input.previewFileUrl} ` +
`for job ${job.jobToken}. Running audio merge transcoding.`
`for job ${job.jobToken}. Running audio merge transcoding.`
)
const ffmpegVod = buildFFmpegVOD({
onJobProgress: progress => { ffmpegProgress = progress }
onJobProgress: progress => {
ffmpegProgress = progress
}
})
await ffmpegVod.transcode({

View File

@@ -57,7 +57,7 @@ export class RunnerServer {
try {
await ipcServer.run(this)
} catch (err) {
logger.error('Cannot start local socket for IPC communication', err)
logger.error(err, 'Cannot start local socket for IPC communication')
process.exit(-1)
}
@@ -74,9 +74,11 @@ export class RunnerServer {
// Process jobs
await ensureDir(ConfigManager.Instance.getTranscodingDirectory())
await ensureDir(ConfigManager.Instance.getStoryboardDirectory())
await this.cleanupTMP()
logger.info(`Using ${ConfigManager.Instance.getTranscodingDirectory()} for transcoding directory`)
logger.info(`Using ${ConfigManager.Instance.getStoryboardDirectory()} for storyboard directory`)
this.initialized = true
await this.checkAvailableJobs()

View File

@@ -7,7 +7,8 @@ import {
RunnerJobVODAudioMergeTranscodingPayload,
RunnerJobVODHLSTranscodingPayload,
RunnerJobVODWebVideoTranscodingPayload,
VideoStudioTaskPayload
VideoStudioTaskPayload,
RunnerJobGenerateStoryboardPayload
} from '@peertube/peertube-models'
const supportedMatrix: { [ id in RunnerJobType ]: (payload: RunnerJobPayload) => boolean } = {
@@ -33,7 +34,8 @@ const supportedMatrix: { [ id in RunnerJobType ]: (payload: RunnerJobPayload) =>
},
'video-transcription': (_payload: RunnerJobTranscriptionPayload) => {
return true
}
},
'generate-video-storyboard': (_payload: RunnerJobGenerateStoryboardPayload) => true
}
export function isJobSupported (job: { type: RunnerJobType, payload: RunnerJobPayload }, enabledJobs?: Set<RunnerJobType>) {

View File

@@ -108,6 +108,10 @@ export class ConfigManager {
// ---------------------------------------------------------------------------
getStoryboardDirectory () {
return join(paths.cache, this.id, 'storyboard')
}
getTranscodingDirectory () {
return join(paths.cache, this.id, 'transcoding')
}

View File

@@ -1,4 +1,4 @@
import { Client as NetIPC } from '@peertube/net-ipc'
import { Client as NetIPC } from 'net-ipc'
import CliTable3 from 'cli-table3'
import { ensureDir } from 'fs-extra/esm'
import { ConfigManager } from '../config-manager.js'
@@ -20,7 +20,7 @@ export class IPCClient {
if (err.code === 'ECONNREFUSED') {
throw new Error(
'This runner is not currently running in server mode on this system. ' +
'Please run it using the `server` command first (in another terminal for example) and then retry your command.'
'Please run it using the `server` command first (in another terminal for example) and then retry your command.'
)
}

View File

@@ -1,5 +1,5 @@
import { ensureDir } from 'fs-extra/esm'
import { Server as NetIPC } from '@peertube/net-ipc'
import { Server as NetIPC } from 'net-ipc'
import { pick } from '@peertube/peertube-core-utils'
import { RunnerServer } from '../../server/index.js'
import { ConfigManager } from '../config-manager.js'
@@ -58,11 +58,11 @@ export class IPCServer {
}
}
private sendResponse <T extends IPCResponseData> (
private sendResponse<T extends IPCResponseData> (
response: (data: any) => Promise<void>,
body: IPCResponse<T>
) {
response(body)
.catch(err => logger.error('Cannot send response after IPC request', err))
.catch(err => logger.error(err, 'Cannot send response after IPC request'))
}
}

View File

@@ -1,287 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@iarna/toml@^2.2.5":
version "2.2.5"
resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c"
integrity sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==
"@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz#9edec61b22c3082018a79f6d1c30289ddf3d9d11"
integrity sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==
"@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz#33677a275204898ad8acbf62734fc4dc0b6a4855"
integrity sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==
"@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz#19edf7cdc2e7063ee328403c1d895a86dd28f4bb"
integrity sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==
"@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz#94fb0543ba2e28766c3fc439cabbe0440ae70159"
integrity sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==
"@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz#4a0609ab5fe44d07c9c60a11e4484d3c38bbd6e3"
integrity sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==
"@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz#0aa5502d547b57abfc4ac492de68e2006e417242"
integrity sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==
"@peertube/net-ipc@^2.2.0":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@peertube/net-ipc/-/net-ipc-2.2.1.tgz#3d1c154a08b57cfea31ed760ec76fe2f69e35a19"
integrity sha512-RyKIGC3EeQ+xnSccf592qqsaXWrGp4wGfGl4W+wxDoZkwsThZJuiSbX8aCC1qZBHaDo3EuRH3ZrwsKpNjnyDAQ==
optionalDependencies:
fast-zlib "^2.0.1"
msgpackr "^1.3.2"
"@types/follow-redirects@1.14.4":
version "1.14.4"
resolved "https://registry.yarnpkg.com/@types/follow-redirects/-/follow-redirects-1.14.4.tgz#ca054d72ef574c77949fc5fff278b430fcd508ec"
integrity sha512-GWXfsD0Jc1RWiFmMuMFCpXMzi9L7oPDVwxUnZdg89kDNnqsRfUKXEtUYtA98A6lig1WXH/CYY/fvPW9HuN5fTA==
dependencies:
"@types/node" "*"
"@types/node@*":
version "24.1.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-24.1.0.tgz#0993f7dc31ab5cc402d112315b463e383d68a49c"
integrity sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==
dependencies:
undici-types "~7.8.0"
atomic-sleep@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b"
integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==
colorette@^2.0.7:
version "2.0.20"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a"
integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==
dateformat@^4.6.3:
version "4.6.3"
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.6.3.tgz#556fa6497e5217fedb78821424f8a1c22fa3f4b5"
integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==
detect-libc@^2.0.1:
version "2.0.4"
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.4.tgz#f04715b8ba815e53b4d8109655b6508a6865a7e8"
integrity sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==
end-of-stream@^1.1.0:
version "1.4.5"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.5.tgz#7344d711dea40e0b74abc2ed49778743ccedb08c"
integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==
dependencies:
once "^1.4.0"
env-paths@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-3.0.0.tgz#2f1e89c2f6dbd3408e1b1711dd82d62e317f58da"
integrity sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==
fast-copy@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-3.0.2.tgz#59c68f59ccbcac82050ba992e0d5c389097c9d35"
integrity sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==
fast-redact@^3.1.1:
version "3.5.0"
resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.5.0.tgz#e9ea02f7e57d0cd8438180083e93077e496285e4"
integrity sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==
fast-safe-stringify@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884"
integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==
fast-zlib@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/fast-zlib/-/fast-zlib-2.0.1.tgz#be624f592fc80ad8019ee2025d16a367a4e9b024"
integrity sha512-DCoYgNagM2Bt1VIpXpdGnRx4LzqJeYG0oh6Nf/7cWo6elTXkFGMw9CrRCYYUIapYNrozYMoyDRflx9mgT3Awyw==
follow-redirects@^1.15.5:
version "1.15.9"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1"
integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
help-me@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/help-me/-/help-me-5.0.0.tgz#b1ebe63b967b74060027c2ac61f9be12d354a6f6"
integrity sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==
joycon@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03"
integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==
minimist@^1.2.6:
version "1.2.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
msgpackr-extract@^3.0.2:
version "3.0.3"
resolved "https://registry.yarnpkg.com/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz#e9d87023de39ce714872f9e9504e3c1996d61012"
integrity sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==
dependencies:
node-gyp-build-optional-packages "5.2.2"
optionalDependencies:
"@msgpackr-extract/msgpackr-extract-darwin-arm64" "3.0.3"
"@msgpackr-extract/msgpackr-extract-darwin-x64" "3.0.3"
"@msgpackr-extract/msgpackr-extract-linux-arm" "3.0.3"
"@msgpackr-extract/msgpackr-extract-linux-arm64" "3.0.3"
"@msgpackr-extract/msgpackr-extract-linux-x64" "3.0.3"
"@msgpackr-extract/msgpackr-extract-win32-x64" "3.0.3"
msgpackr@^1.3.2:
version "1.11.5"
resolved "https://registry.yarnpkg.com/msgpackr/-/msgpackr-1.11.5.tgz#edf0b9d9cb7d8ed6897dd0e42cfb865a2f4b602e"
integrity sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==
optionalDependencies:
msgpackr-extract "^3.0.2"
node-gyp-build-optional-packages@5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz#522f50c2d53134d7f3a76cd7255de4ab6c96a3a4"
integrity sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==
dependencies:
detect-libc "^2.0.1"
on-exit-leak-free@^2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz#fed195c9ebddb7d9e4c3842f93f281ac8dadd3b8"
integrity sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==
once@^1.3.1, once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
dependencies:
wrappy "1"
pino-abstract-transport@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz#de241578406ac7b8a33ce0d77ae6e8a0b3b68a60"
integrity sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==
dependencies:
split2 "^4.0.0"
pino-pretty@^13.0.0:
version "13.0.0"
resolved "https://registry.yarnpkg.com/pino-pretty/-/pino-pretty-13.0.0.tgz#21d57fe940e34f2e279905d7dba2d7e2c4f9bf17"
integrity sha512-cQBBIVG3YajgoUjo1FdKVRX6t9XPxwB9lcNJVD5GCnNM4Y6T12YYx8c6zEejxQsU0wrg9TwmDulcE9LR7qcJqA==
dependencies:
colorette "^2.0.7"
dateformat "^4.6.3"
fast-copy "^3.0.2"
fast-safe-stringify "^2.1.1"
help-me "^5.0.0"
joycon "^3.1.1"
minimist "^1.2.6"
on-exit-leak-free "^2.1.0"
pino-abstract-transport "^2.0.0"
pump "^3.0.0"
secure-json-parse "^2.4.0"
sonic-boom "^4.0.1"
strip-json-comments "^3.1.1"
pino-std-serializers@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz#7c625038b13718dbbd84ab446bd673dc52259e3b"
integrity sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==
pino@^9.6.0:
version "9.7.0"
resolved "https://registry.yarnpkg.com/pino/-/pino-9.7.0.tgz#ff7cd86eb3103ee620204dbd5ca6ffda8b53f645"
integrity sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==
dependencies:
atomic-sleep "^1.0.0"
fast-redact "^3.1.1"
on-exit-leak-free "^2.1.0"
pino-abstract-transport "^2.0.0"
pino-std-serializers "^7.0.0"
process-warning "^5.0.0"
quick-format-unescaped "^4.0.3"
real-require "^0.2.0"
safe-stable-stringify "^2.3.1"
sonic-boom "^4.0.1"
thread-stream "^3.0.0"
process-warning@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-5.0.0.tgz#566e0bf79d1dff30a72d8bbbe9e8ecefe8d378d7"
integrity sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==
pump@^3.0.0:
version "3.0.3"
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.3.tgz#151d979f1a29668dc0025ec589a455b53282268d"
integrity sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==
dependencies:
end-of-stream "^1.1.0"
once "^1.3.1"
quick-format-unescaped@^4.0.3:
version "4.0.4"
resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7"
integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==
real-require@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78"
integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==
safe-stable-stringify@^2.3.1:
version "2.5.0"
resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz#4ca2f8e385f2831c432a719b108a3bf7af42a1dd"
integrity sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==
secure-json-parse@^2.4.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.7.0.tgz#5a5f9cd6ae47df23dba3151edd06855d47e09862"
integrity sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==
sonic-boom@^4.0.1:
version "4.2.0"
resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-4.2.0.tgz#e59a525f831210fa4ef1896428338641ac1c124d"
integrity sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==
dependencies:
atomic-sleep "^1.0.0"
split2@^4.0.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4"
integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==
strip-json-comments@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
thread-stream@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-3.1.0.tgz#4b2ef252a7c215064507d4ef70c05a5e2d34c4f1"
integrity sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==
dependencies:
real-require "^0.2.0"
undici-types@~7.8.0:
version "7.8.0"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.8.0.tgz#de00b85b710c54122e44fbfd911f8d70174cd294"
integrity sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==

View File

@@ -0,0 +1,104 @@
---
description: 'Angular-specific coding standards and best practices'
applyTo: 'src/app/**/*.ts, src/app/**/*.html, src/app/**/*.scss, src/app/**/*.css'
---
# Angular Development Instructions
Instructions for generating high-quality Angular applications with TypeScript, using Angular Signals for state management, adhering to Angular best practices as outlined at https://angular.dev.
## Project Context
- Latest Angular version (use standalone components by default)
- TypeScript for type safety
- Angular CLI for project setup and scaffolding
- Follow Angular Style Guide (https://angular.dev/style-guide)
- Use Angular Material or other modern UI libraries for consistent styling (if specified)
## Development Standards
### Architecture
- Use standalone components unless modules are explicitly required
- Organize code by feature modules or domains for scalability
- Implement lazy loading for feature modules to optimize performance
- Use Angular's built-in dependency injection system effectively
- Structure components with a clear separation of concerns (smart vs. presentational components)
### TypeScript
- Enable strict mode in `tsconfig.json` for type safety
- Define clear interfaces and types for components, services, and models
- Use type guards and union types for robust type checking
- Implement proper error handling with RxJS operators (e.g., `catchError`)
- Use typed forms (e.g., `FormGroup`, `FormControl`) for reactive forms
### Component Design
- Follow Angular's component lifecycle hooks best practices
- When using Angular >= 19, Use `input()` `output()`, `viewChild()`, `viewChildren()`, `contentChild()` and `viewChildren()` functions instead of decorators; otherwise use decorators
- Leverage Angular's change detection strategy (default or `OnPush` for performance)
- Keep templates clean and logic in component classes or services
- Use Angular directives and pipes for reusable functionality
### Styling
- Use Angular's component-level CSS encapsulation (default: ViewEncapsulation.Emulated)
- Prefer SCSS for styling with consistent theming
- Implement responsive design using CSS Grid, Flexbox, or Angular CDK Layout utilities
- Follow Angular Material's theming guidelines if used
- Maintain accessibility (a11y) with ARIA attributes and semantic HTML
### State Management
- Use Angular Signals for reactive state management in components and services
- Leverage `signal()`, `computed()`, and `effect()` for reactive state updates
- Use writable signals for mutable state and computed signals for derived state
- Handle loading and error states with signals and proper UI feedback
- Use Angular's `AsyncPipe` to handle observables in templates when combining signals with RxJS
### Data Fetching
- Use Angular's `HttpClient` for API calls with proper typing
- Implement RxJS operators for data transformation and error handling
- Use Angular's `inject()` function for dependency injection in standalone components
- Implement caching strategies (e.g., `shareReplay` for observables)
- Store API response data in signals for reactive updates
- Handle API errors with global interceptors for consistent error handling
### Security
- Sanitize user inputs using Angular's built-in sanitization
- Implement route guards for authentication and authorization
- Use Angular's `HttpInterceptor` for CSRF protection and API authentication headers
- Validate form inputs with Angular's reactive forms and custom validators
- Follow Angular's security best practices (e.g., avoid direct DOM manipulation)
### Performance
- Enable production builds with `ng build --prod` for optimization
- Use lazy loading for routes to reduce initial bundle size
- Optimize change detection with `OnPush` strategy and signals for fine-grained reactivity
- Use trackBy in `ngFor` loops to improve rendering performance
- Implement server-side rendering (SSR) or static site generation (SSG) with Angular Universal (if specified)
### Testing
- Write unit tests for components, services, and pipes using Jasmine and Karma
- Use Angular's `TestBed` for component testing with mocked dependencies
- Test signal-based state updates using Angular's testing utilities
- Write end-to-end tests with Cypress or Playwright (if specified)
- Mock HTTP requests using `HttpClientTestingModule`
- Ensure high test coverage for critical functionality
## Implementation Process
1. Plan project structure and feature modules
2. Define TypeScript interfaces and models
3. Scaffold components, services, and pipes using Angular CLI
4. Implement data services and API integrations with signal-based state
5. Build reusable components with clear inputs and outputs
6. Add reactive forms and validation
7. Apply styling with SCSS and responsive design
8. Implement lazy-loaded routes and guards
9. Add error handling and loading states using signals
10. Write unit and end-to-end tests
11. Optimize performance and bundle size
## Additional Guidelines
- Follow Angular's naming conventions (e.g., `feature.component.ts`, `feature.service.ts`)
- Use Angular CLI commands for generating boilerplate code
- Document components and services with clear JSDoc comments
- Ensure accessibility compliance (WCAG 2.1) where applicable
- Use Angular's built-in i18n for internationalization (if specified)
- Keep code DRY by creating reusable utilities and shared modules
- Use signals consistently for state management to ensure reactive updates

View File

@@ -0,0 +1,112 @@
# PeerTube Client Development Instructions for Coding Agents
## Client Overview
This is the Angular frontend for PeerTube, a decentralized video hosting platform. The client is built with Angular 20+, TypeScript, and SCSS. It communicates with the PeerTube server API and provides the web interface for users, administrators, and content creators.
**Key Technologies:**
- Angular 20+ with standalone components
- TypeScript 5+
- SCSS for styling
- RxJS for reactive programming
- PrimeNg and Bootstrap for UI components
- WebdriverIO for E2E testing
- Angular CLI
## Client Build and Development Commands
### Prerequisites (for client development)
- Node.js 20+
- yarn 1
- Running PeerTube server (see ../server instructions)
### Essential Client Commands
```bash
# From the client directory:
cd /client
# 1. Install dependencies (ALWAYS first)
yarn install --pure-lockfile
# 2. Development server with hot reload
npm run dev
# 3. Build for production
npm run build
```
### Client Testing Commands
```bash
# From client directory:
npm run lint # ESLint for client code
```
### Common Client Issues and Solutions
**Angular Build Failures:**
- Always run `yarn install --pure-lockfile` after pulling changes
- Clear `node_modules` and reinstall if dependency errors occur
- Build may fail on memory issues: `NODE_OPTIONS="--max-old-space-size=4096" npm run build`
- Check TypeScript errors carefully - Angular is strict about types
**Development Server Issues:**
- Default port is 3000, ensure it's not in use
- Hot reload may fail on file permission issues
- Clear browser cache if changes don't appear
## Client Architecture and File Structure
### Client Directory Structure
```
/src/
/app/
+admin/ # Admin interface components
+my-account/ # User account management pages
+my-library/ # User's videos, playlists, subscriptions
+search/ # Search functionality and results
+shared/ # Shared Angular components, services, pipes
+standalone/ # Standalone Angular components
+videos/ # Video-related components (watch, upload, etc.)
/core/ # Core services (auth, server, notifications)
/helpers/ # Utility functions and helpers
/menu/ # Navigation menu components
/assets/ # Static assets (images, icons, etc.)
/environments/ # Environment configurations
/locale/ # Internationalization files
/sass/ # Global SCSS styles
```
### Key Client Configuration Files
- `angular.json` - Angular CLI workspace configuration
- `tsconfig.json` - TypeScript configuration for client
- `e2e/wdio*.conf.js` - WebdriverIO E2E test configurations
- `src/environments/` - Environment-specific configurations
### Shared Code with Server (`../shared/`)
The client imports TypeScript models and utilities from the shared directory:
- `../shared/models/` - Data models (Video, User, Channel, etc.). Import these in client code: `import { Video } from '@peertube/peertube-models'`
- `../shared/core-utils/` - Utility functions shared between client/server. Import these in client code: `import { ... } from '@peertube/peertube-core-utils'`
-
## Client Development Workflow
### Making Client Changes
1. **Angular Components:** Create/modify in `/src/app/` following existing patterns
2. **Shared Components:** Reusable components go in `/src/app/shared/`
3. **Services:** Core services in `/src/app/core/`, feature services with components
4. **Styles:** Component styles in `.scss` files, global styles in `/src/sass/`
5. **Assets:** Images, icons in `/src/assets/`
6. **Routing:** Routes defined in feature modules or `app-routing.module.ts`
## Trust These Instructions
These instructions are comprehensive and tested specifically for client development. Only search for additional information if:
1. Commands fail despite following instructions exactly
2. New error messages appear that aren't documented here
3. You need specific Angular implementation details not covered above
For server-side questions, refer to the server instructions in `../.github/copilot-instructions.md`.

View File

@@ -185,7 +185,7 @@
"."
],
"sass": {
"silenceDeprecations": [ "import", "mixed-decls", "color-functions", "global-builtin" ]
"silenceDeprecations": [ "import", "color-functions", "global-builtin" ]
}
},
"assets": [
@@ -244,7 +244,7 @@
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "120kb"
"maximumError": "140kb"
}
],
"fileReplacements": [

View File

@@ -2,25 +2,19 @@ import { NSFWPolicyType } from '@peertube/peertube-models'
import { browserSleep, go, setCheckboxEnabled } from '../utils'
export class AdminConfigPage {
async navigateTo (tab: 'instance-homepage' | 'basic-configuration' | 'instance-information' | 'live') {
const waitTitles = {
'instance-homepage': 'INSTANCE HOMEPAGE',
'basic-configuration': 'APPEARANCE',
'instance-information': 'INSTANCE',
'live': 'LIVE'
async navigateTo (page: 'information' | 'live' | 'general' | 'homepage') {
const url = '/admin/settings/config/' + page
const currentUrl = await browser.getUrl()
if (!currentUrl.endsWith(url)) {
await go(url)
}
const url = '/admin/settings/config/edit-custom#' + tab
if (await browser.getUrl() !== url) {
await go('/admin/settings/config/edit-custom#' + tab)
}
await $('h2=' + waitTitles[tab]).waitForDisplayed()
await $('a.active[href="' + url + '"]').waitForDisplayed()
}
async updateNSFWSetting (newValue: NSFWPolicyType) {
await this.navigateTo('instance-information')
await this.navigateTo('information')
const elem = $(`#instanceDefaultNSFWPolicy-${newValue} + label`)
@@ -32,25 +26,25 @@ export class AdminConfigPage {
}
async updateHomepage (newValue: string) {
await this.navigateTo('instance-homepage')
await this.navigateTo('homepage')
return $('#instanceCustomHomepageContent').setValue(newValue)
return $('#homepageContent').setValue(newValue)
}
async toggleSignup (enabled: boolean) {
await this.navigateTo('basic-configuration')
await this.navigateTo('general')
return setCheckboxEnabled('signupEnabled', enabled)
}
async toggleSignupApproval (required: boolean) {
await this.navigateTo('basic-configuration')
await this.navigateTo('general')
return setCheckboxEnabled('signupRequiresApproval', required)
}
async toggleSignupEmailVerification (required: boolean) {
await this.navigateTo('basic-configuration')
await this.navigateTo('general')
return setCheckboxEnabled('signupRequiresEmailVerification', required)
}
@@ -62,11 +56,18 @@ export class AdminConfigPage {
}
async save () {
const button = $('input[type=submit]')
const button = $('my-admin-save-bar .save-button')
try {
await button.waitForClickable()
} catch {
// The config may have not been changed
return
} finally {
await browserSleep(1000) // Wait for the button to be clickable
}
await button.waitForClickable()
await button.click()
await browserSleep(1000)
await button.waitForClickable({ reverse: true })
}
}

View File

@@ -66,7 +66,7 @@ export class LoginPage {
}
async logout () {
const loggedInDropdown = $('.logged-in-container .logged-in-info')
const loggedInDropdown = $('.logged-in-container .dropdown-toggle')
await loggedInDropdown.waitForClickable()
await loggedInDropdown.click()

View File

@@ -185,7 +185,7 @@ export class MyAccountPage {
const playlist = () => {
return $$('my-video-playlist-miniature')
.filter(async e => {
const t = await e.$('.miniature-name').getText()
const t = await e.$('img').getAttribute('aria-label')
return t.includes(name)
})

View File

@@ -72,15 +72,15 @@ export class PlayerPage {
}
getNSFWContentText () {
return $('.video-js .nsfw-content').getText()
return $('.video-js .nsfw-info').getText()
}
getNSFWMoreContent () {
return $('.video-js .nsfw-more-content')
getNSFWDetailsContent () {
return $('.video-js .nsfw-details-content')
}
getMoreNSFWInfoButton () {
return $('.video-js .nsfw-container button')
return $('.video-js .nsfw-info button')
}
async hasPoster () {

View File

@@ -86,7 +86,7 @@ export class VideoListPage {
async expectVideoNSFWTooltip (name: string, summary?: string) {
const miniature = await this.getVideoMiniature(name)
const warning = await miniature.$('.nsfw-warning')
const warning = miniature.$('.nsfw-warning')
await warning.waitForDisplayed()
expect(await warning.getAttribute('aria-label')).toEqual(summary)

View File

@@ -76,7 +76,7 @@ export abstract class VideoManage {
await input.waitForClickable()
await input.click()
const nextMonth = $('.p-datepicker-next')
const nextMonth = $('.p-datepicker-next-button')
await nextMonth.click()
await $('.p-datepicker-calendar td[aria-label="1"] > span').click()
@@ -135,7 +135,13 @@ export abstract class VideoManage {
}
protected async goOnPage (page: 'Main information' | 'Moderation' | 'Live settings') {
const el = $('my-video-manage-container .menu').$('*=' + page)
const urls = {
'Main information': '',
'Moderation': 'moderation',
'Live settings': 'live'
}
const el = $(`my-video-manage-container .menu a[href*="/${urls[page]}"]`)
await el.waitForClickable()
await el.click()
}

View File

@@ -151,10 +151,7 @@ export class VideoWatchPage {
await this.clickOnMoreDropdownIcon()
// We need the await expression
// eslint-disable-next-line @typescript-eslint/await-thenable
const items = await $$('.dropdown-menu.show .dropdown-item')
for (const item of items) {
return $$('.dropdown-menu.show .dropdown-item').mapSeries(async item => {
const content = await item.getText()
if (content.includes('Manage')) {
@@ -162,11 +159,12 @@ export class VideoWatchPage {
await $('#name').waitForClickable()
return
}
}
})
}
async clickOnMoreDropdownIcon () {
const dropdown = $('my-video-actions-dropdown .action-button')
await dropdown.scrollIntoView({ block: 'center' })
await dropdown.click()
await $('.dropdown-menu.show .dropdown-item').waitForDisplayed()
@@ -176,8 +174,12 @@ export class VideoWatchPage {
// Playlists
// ---------------------------------------------------------------------------
clickOnSave () {
return $('.action-button-save').click()
async clickOnSave () {
const button = $('.action-button-save')
await button.scrollIntoView({ block: 'center' })
return button.click()
}
async createPlaylist (name: string) {

View File

@@ -1,6 +1,6 @@
import { PlayerPage } from '../po/player.po'
import { VideoWatchPage } from '../po/video-watch.po'
import { FIXTURE_URLS, go, isMobileDevice, isSafari } from '../utils'
import { FIXTURE_URLS, go, isMobileDevice, isSafari, prepareWebBrowser } from '../utils'
describe('Live all workflow', () => {
let videoWatchPage: VideoWatchPage
@@ -10,9 +10,7 @@ describe('Live all workflow', () => {
videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari())
playerPage = new PlayerPage()
if (!isMobileDevice()) {
await browser.maximizeWindow()
}
await prepareWebBrowser()
})
it('Should go to the live page', async () => {

View File

@@ -1,7 +1,7 @@
import { LoginPage } from '../po/login.po'
import { PlayerPage } from '../po/player.po'
import { VideoWatchPage } from '../po/video-watch.po'
import { FIXTURE_URLS, go, isMobileDevice, isSafari } from '../utils'
import { FIXTURE_URLS, go, isMobileDevice, isSafari, prepareWebBrowser } from '../utils'
async function checkCorrectlyPlay (playerPage: PlayerPage) {
await playerPage.playAndPauseVideo(false, 2)
@@ -22,9 +22,7 @@ describe('Private videos all workflow', () => {
loginPage = new LoginPage(isMobileDevice())
playerPage = new PlayerPage()
if (!isMobileDevice()) {
await browser.maximizeWindow()
}
await prepareWebBrowser()
})
it('Should log in', async () => {

View File

@@ -5,7 +5,7 @@ import { VideoListPage } from '../po/video-list.po'
import { VideoPublishPage } from '../po/video-publish.po'
import { VideoUpdatePage } from '../po/video-update.po'
import { VideoWatchPage } from '../po/video-watch.po'
import { FIXTURE_URLS, go, isIOS, isMobileDevice, isSafari, waitServerUp } from '../utils'
import { FIXTURE_URLS, go, isIOS, isMobileDevice, isSafari, prepareWebBrowser, waitServerUp } from '../utils'
function isUploadUnsupported () {
if (isMobileDevice() || isSafari()) {
@@ -53,9 +53,7 @@ describe('Videos all workflow', () => {
playerPage = new PlayerPage()
videoListPage = new VideoListPage(isMobileDevice(), isSafari())
if (!isMobileDevice()) {
await browser.maximizeWindow()
}
await prepareWebBrowser()
})
it('Should log in', async () => {

View File

@@ -1,7 +1,7 @@
import { LoginPage } from '../po/login.po'
import { VideoPublishPage } from '../po/video-publish.po'
import { VideoWatchPage } from '../po/video-watch.po'
import { getScreenshotPath, go, isMobileDevice, isSafari, waitServerUp } from '../utils'
import { getScreenshotPath, go, isMobileDevice, isSafari, prepareWebBrowser, waitServerUp } from '../utils'
describe('Custom server defaults', () => {
let videoPublishPage: VideoPublishPage
@@ -15,7 +15,7 @@ describe('Custom server defaults', () => {
videoPublishPage = new VideoPublishPage()
videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari())
await browser.maximizeWindow()
await prepareWebBrowser()
})
describe('Publish default values', function () {

View File

@@ -9,7 +9,7 @@ import { VideoListPage } from '../po/video-list.po'
import { VideoPublishPage } from '../po/video-publish.po'
import { VideoSearchPage } from '../po/video-search.po'
import { VideoWatchPage } from '../po/video-watch.po'
import { getScreenshotPath, go, isMobileDevice, isSafari, waitServerUp } from '../utils'
import { getScreenshotPath, go, isMobileDevice, isSafari, prepareWebBrowser, waitServerUp } from '../utils'
describe('NSFW', () => {
let videoListPage: VideoListPage
@@ -102,6 +102,8 @@ describe('NSFW', () => {
for (const video of videos) {
await videoSearchPage.search(video)
await browser.saveScreenshot(getScreenshotPath('before-test.png'))
await checkVideo({ policy, videoName: video, nsfwTooltip })
}
}
@@ -153,7 +155,7 @@ describe('NSFW', () => {
videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari())
anonymousSettingsPage = new AnonymousSettingsPage()
await browser.maximizeWindow()
await prepareWebBrowser()
})
describe('Preparation', function () {
@@ -265,10 +267,10 @@ describe('NSFW', () => {
expect(await moreButton.isDisplayed()).toBeTruthy()
await moreButton.click()
await playerPage.getNSFWMoreContent().waitForDisplayed()
await playerPage.getNSFWDetailsContent().waitForDisplayed()
const moreContent = await playerPage.getNSFWMoreContent().getText()
expect(moreContent).toContain('Violence')
const moreContent = await playerPage.getNSFWDetailsContent().getText()
expect(moreContent).toContain('Potentially violent content')
expect(moreContent).toContain('bibi is violent')
}

View File

@@ -0,0 +1,119 @@
import { AdminConfigPage } from '../po/admin-config.po'
import { LoginPage } from '../po/login.po'
import { VideoPublishPage } from '../po/video-publish.po'
import { VideoWatchPage } from '../po/video-watch.po'
import { getScreenshotPath, go, isMobileDevice, isSafari, prepareWebBrowser, selectCustomSelect, waitServerUp } from '../utils'
// These tests help to notice crash with invalid translated strings
describe('Page crash', () => {
let videoPublishPage: VideoPublishPage
let loginPage: LoginPage
let videoWatchPage: VideoWatchPage
let adminConfigPage: AdminConfigPage
const languages = [
'العربية',
'Català',
'Čeština',
'Deutsch',
'ελληνικά',
'Esperanto',
'Español',
'Euskara',
'فارسی',
'Suomi',
'Français',
'Gàidhlig',
'Galego',
'Hrvatski',
'Magyar',
'Íslenska',
'Italiano',
'日本語',
'Taqbaylit',
'Norsk bokmål',
'Nederlands',
'Norsk nynorsk',
'Occitan',
'Polski',
'Português (Brasil)',
'Português (Portugal)',
'Pусский',
'Slovenčina',
'Shqip',
'Svenska',
'ไทย',
'Toki Pona',
'Türkçe',
'украї́нська мо́ва',
'Tiếng Việt',
'简体中文(中国)',
'繁體中文(台灣)'
]
before(async () => {
await waitServerUp()
adminConfigPage = new AdminConfigPage()
loginPage = new LoginPage(isMobileDevice())
videoPublishPage = new VideoPublishPage()
videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari())
await prepareWebBrowser()
await loginPage.loginAsRootUser()
})
for (const language of languages) {
describe('For language: ' + language, () => {
it('Should change the language', async function () {
await go('/')
await $('.settings-button').waitForClickable()
await $('.settings-button').click()
await selectCustomSelect('language', language)
await $('my-user-interface-settings .primary-button').waitForClickable()
await $('my-user-interface-settings .primary-button').click()
})
it('Should upload and watch a video', async function () {
await videoPublishPage.navigateTo()
await videoPublishPage.uploadVideo('video3.mp4')
await videoPublishPage.validSecondStep('video')
await videoPublishPage.clickOnWatch()
await videoWatchPage.waitWatchVideoName('video')
})
it('Should set a homepage', async function () {
await adminConfigPage.updateHomepage('My custom homepage content')
await adminConfigPage.save()
// All tests
await go('/home')
await $('*=My custom homepage content').waitForDisplayed()
})
it('Should go on client pages and not crash', async function () {
await $('a[href="/videos/overview"]').waitForClickable()
await $('a[href="/videos/overview"]').click()
await $('my-video-overview').waitForExist()
})
it('Should go on videos from subscriptions pages', async function () {
await $('a[href="/videos/subscriptions"]').waitForClickable()
await $('a[href="/videos/subscriptions"]').click()
await $('my-videos-user-subscriptions').waitForExist()
})
after(async () => {
await browser.saveScreenshot(getScreenshotPath(`after-page-crash-test-${language}.png`))
})
})
}
})

View File

@@ -3,7 +3,7 @@ import { LoginPage } from '../po/login.po'
import { MyAccountPage } from '../po/my-account.po'
import { VideoPublishPage } from '../po/video-publish.po'
import { VideoWatchPage } from '../po/video-watch.po'
import { getScreenshotPath, go, isMobileDevice, isSafari, waitServerUp } from '../utils'
import { getScreenshotPath, go, isMobileDevice, isSafari, prepareWebBrowser, waitServerUp } from '../utils'
describe('Player settings', () => {
let videoPublishPage: VideoPublishPage
@@ -21,7 +21,7 @@ describe('Player settings', () => {
myAccountPage = new MyAccountPage()
anonymousSettingsPage = new AnonymousSettingsPage()
await browser.maximizeWindow()
await prepareWebBrowser()
})
describe('P2P', function () {

View File

@@ -1,7 +1,7 @@
import { AdminPluginPage } from '../po/admin-plugin.po'
import { LoginPage } from '../po/login.po'
import { VideoPublishPage } from '../po/video-publish.po'
import { getCheckbox, getScreenshotPath, isMobileDevice, waitServerUp } from '../utils'
import { getCheckbox, getScreenshotPath, isMobileDevice, prepareWebBrowser, waitServerUp } from '../utils'
describe('Plugins', () => {
let videoPublishPage: VideoPublishPage
@@ -28,7 +28,7 @@ describe('Plugins', () => {
videoPublishPage = new VideoPublishPage()
adminPluginPage = new AdminPluginPage()
await browser.maximizeWindow()
await prepareWebBrowser()
})
it('Should install hello world plugin', async () => {

View File

@@ -2,7 +2,7 @@ import { AdminConfigPage } from '../po/admin-config.po'
import { LoginPage } from '../po/login.po'
import { VideoPublishPage } from '../po/video-publish.po'
import { VideoWatchPage } from '../po/video-watch.po'
import { getScreenshotPath, isMobileDevice, isSafari, waitServerUp } from '../utils'
import { getScreenshotPath, isMobileDevice, isSafari, prepareWebBrowser, waitServerUp } from '../utils'
describe('Publish live', function () {
let videoPublishPage: VideoPublishPage
@@ -18,7 +18,7 @@ describe('Publish live', function () {
adminConfigPage = new AdminConfigPage()
videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari())
await browser.maximizeWindow()
await prepareWebBrowser()
await loginPage.loginAsRootUser()
})

View File

@@ -1,7 +1,7 @@
import { LoginPage } from '../po/login.po'
import { VideoPublishPage } from '../po/video-publish.po'
import { VideoWatchPage } from '../po/video-watch.po'
import { getScreenshotPath, isMobileDevice, isSafari, waitServerUp } from '../utils'
import { getScreenshotPath, isMobileDevice, isSafari, prepareWebBrowser, waitServerUp } from '../utils'
describe('Publish video', () => {
let videoPublishPage: VideoPublishPage
@@ -15,7 +15,7 @@ describe('Publish video', () => {
videoPublishPage = new VideoPublishPage()
videoWatchPage = new VideoWatchPage(isMobileDevice(), isSafari())
await browser.maximizeWindow()
await prepareWebBrowser()
await loginPage.loginAsRootUser()
})

View File

@@ -11,6 +11,7 @@ import {
go,
isMobileDevice,
MockSMTPServer,
prepareWebBrowser,
waitServerUp
} from '../utils'
@@ -76,6 +77,7 @@ describe('Signup', () => {
}) {
await loginPage.loginAsRootUser()
// Ensure we change the state of the form to "dirty" so we can save the form
await adminConfigPage.toggleSignup(options.enabled)
if (options.enabled) {
@@ -104,7 +106,7 @@ describe('Signup', () => {
signupPage = new SignupPage()
adminRegistrationPage = new AdminRegistrationPage()
await browser.maximizeWindow()
await prepareWebBrowser()
})
describe('Signup disabled', function () {

View File

@@ -10,6 +10,7 @@ import {
go,
isMobileDevice,
MockSMTPServer,
prepareWebBrowser,
waitServerUp
} from '../utils'
@@ -29,7 +30,7 @@ describe('User settings', () => {
await MockSMTPServer.Instance.collectEmails(await getEmailPort(), emails)
await browser.maximizeWindow()
await prepareWebBrowser()
})
describe('Email', function () {

View File

@@ -4,7 +4,7 @@ import { PlayerPage } from '../po/player.po'
import { SignupPage } from '../po/signup.po'
import { VideoPublishPage } from '../po/video-publish.po'
import { VideoWatchPage } from '../po/video-watch.po'
import { getScreenshotPath, go, isMobileDevice, isSafari, waitServerUp } from '../utils'
import { getScreenshotPath, go, isMobileDevice, isSafari, prepareWebBrowser, waitServerUp } from '../utils'
describe('Password protected videos', () => {
let videoPublishPage: VideoPublishPage
@@ -50,7 +50,7 @@ describe('Password protected videos', () => {
playerPage = new PlayerPage()
myAccountPage = new MyAccountPage()
await browser.maximizeWindow()
await prepareWebBrowser()
})
describe('Owner', function () {

View File

@@ -1,29 +1,31 @@
async function browserSleep (amount: number) {
export async function browserSleep (amount: number) {
await browser.pause(amount)
}
function isMobileDevice () {
// ---------------------------------------------------------------------------
export function isMobileDevice () {
const platformName = (browser.capabilities['platformName'] || '').toLowerCase()
return platformName === 'android' || platformName === 'ios'
}
function isAndroid () {
export function isAndroid () {
const platformName = (browser.capabilities['platformName'] || '').toLowerCase()
return platformName === 'android'
}
function isSafari () {
export function isSafari () {
return browser.capabilities['browserName'] &&
browser.capabilities['browserName'].toLowerCase() === 'safari'
browser.capabilities['browserName'].toLowerCase() === 'safari'
}
function isIOS () {
export function isIOS () {
return isMobileDevice() && isSafari()
}
async function go (url: string) {
export async function go (url: string) {
await browser.url(url)
await browser.execute(() => {
@@ -33,7 +35,20 @@ async function go (url: string) {
})
}
async function waitServerUp () {
// ---------------------------------------------------------------------------
export async function prepareWebBrowser () {
if (isMobileDevice()) return
// Window size on chromium doesn't seem to work in "new" headless mode
if (process.env.MOZ_HEADLESS_WIDTH) {
await browser.setWindowSize(+process.env.MOZ_HEADLESS_WIDTH, +process.env.MOZ_HEADLESS_HEIGHT)
}
await browser.maximizeWindow()
}
export async function waitServerUp () {
await browser.waitUntil(async () => {
await go('/')
await browserSleep(500)
@@ -41,13 +56,3 @@ async function waitServerUp () {
return $('<my-app>').isDisplayed()
}, { timeout: 20 * 1000 })
}
export {
isMobileDevice,
isSafari,
isIOS,
isAndroid,
waitServerUp,
go,
browserSleep
}

View File

@@ -38,6 +38,8 @@ export async function clickOnRadio (name: string) {
export async function selectCustomSelect (id: string, valueLabel: string) {
const wrapper = $(`[formcontrolname=${id}] span[role=combobox]`)
await wrapper.waitForExist()
await wrapper.scrollIntoView({ block: 'center' })
await wrapper.waitForClickable()
await wrapper.click()
@@ -65,9 +67,9 @@ export async function selectCustomSelect (id: string, valueLabel: string) {
export async function findParentElement (
el: ChainablePromiseElement,
finder: (el: WebdriverIO.Element) => Promise<boolean>
finder: (el: ChainablePromiseElement) => Promise<boolean>
) {
if (await finder(el) === true) return el
return findParentElement(await el.parentElement(), finder)
return findParentElement(el.parentElement(), finder)
}

View File

@@ -7,6 +7,7 @@
"module": "commonjs",
"target": "ES2018",
"typeRoots": [
"../node_modules/@wdio",
"../node_modules/@types",
"../node_modules"
],

View File

@@ -95,18 +95,18 @@ module.exports = {
{
browserName: 'Chrome',
...buildBStackMobileOptions({ sessionName: 'Latest Chrome Android', deviceName: 'Samsung Galaxy S8', osVersion: '7.0' })
...buildBStackMobileOptions({ sessionName: 'Latest Chrome Android', deviceName: 'Samsung Galaxy S10', osVersion: '9.0' })
},
{
browserName: 'Safari',
...buildBStackMobileOptions({ sessionName: 'Safari iPhone', deviceName: 'iPhone 11', osVersion: '14' })
...buildBStackMobileOptions({ sessionName: 'Safari iPhone', deviceName: 'iPhone 12', osVersion: '14' })
},
{
browserName: 'Safari',
...buildBStackMobileOptions({ sessionName: 'Safari iPad', deviceName: 'iPad Pro 11 2020', osVersion: '14' })
...buildBStackMobileOptions({ sessionName: 'Safari iPad', deviceName: 'iPad Pro 12.9 2021', osVersion: '14' })
}
],
@@ -121,7 +121,8 @@ module.exports = {
services: [
[
'browserstack', { browserstackLocal: true }
'browserstack',
{ browserstackLocal: true }
]
],
@@ -174,6 +175,5 @@ module.exports = {
onPrepare: onBrowserStackPrepare,
onComplete: onBrowserStackComplete
} as WebdriverIO.Config
}

View File

@@ -28,7 +28,7 @@ module.exports = {
'browserName': 'chrome',
'acceptInsecureCerts': true,
'goog:chromeOptions': {
args: [ '--disable-gpu', windowSizeArg ],
args: [ '--headless', '--disable-gpu', windowSizeArg ],
prefs
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "peertube-client",
"version": "7.2.3",
"version": "7.3.0",
"private": true,
"license": "AGPL-3.0",
"author": {
@@ -53,6 +53,7 @@
"@ngx-loading-bar/http-client": "^7.0.0",
"@ngx-loading-bar/router": "^7.0.0",
"@peertube/maildev": "^1.2.0",
"@peertube/player": "workspace:*",
"@peertube/xliffmerge": "^2.0.3",
"@plussub/srt-vtt-parser": "^2.0.5",
"@popperjs/core": "^2.11.5",
@@ -67,9 +68,9 @@
"@types/qrcode": "^1.5.5",
"@types/sanitize-html": "2.11.0",
"@types/sha.js": "^2.4.0",
"@types/video.js": "^7.3.40",
"@wdio/browserstack-service": "^9.12.7",
"@wdio/cli": "^9.12.7",
"@wdio/globals": "^9.17.0",
"@wdio/local-runner": "^9.12.7",
"@wdio/mocha-framework": "^9.12.6",
"@wdio/shared-store-service": "^9.12.7",
@@ -109,10 +110,10 @@
"type-fest": "^4.37.0",
"typescript": "~5.7.3",
"ua-parser-js": "^2.0.3",
"video.js": "^7.19.2",
"video.js": "^8.23.4",
"vite": "^6.0.11",
"vite-plugin-checker": "^0.9.3",
"vite-plugin-node-polyfills": "^0.23.0",
"vite-plugin-node-polyfills": "^0.24.0",
"zone.js": "~0.15.0"
},
"dependencies": {

View File

@@ -27,17 +27,25 @@
*ngIf="config.instance.social.mastodonLink"
class="media peertube-button-link rounded-icon-button mb-3" i18n-title title="Go to the Mastodon profile"
target="_blank" rel="noopener noreferrer" [href]="config.instance.social.mastodonLink"
>
>
<my-global-icon iconName="mastodon"></my-global-icon>
</a>
<a
*ngIf="config.instance.social.xLink"
class="media peertube-button-link rounded-icon-button mb-3" i18n-title title="Go to the X profile"
target="_blank" rel="noopener noreferrer" [href]="config.instance.social.xLink"
>
<my-global-icon iconName="x-twitter"></my-global-icon>
</a>
<a
*ngIf="config.instance.social.blueskyLink"
class="media peertube-button-link rounded-icon-button mb-3" i18n-title title="Go to the Bluesky profile"
target="_blank" rel="noopener noreferrer" [href]="config.instance.social.blueskyLink"
>
<my-global-icon iconName="bluesky"></my-global-icon>
</a>
<my-global-icon iconName="bluesky"></my-global-icon>
</a>
<a
*ngIf="config.instance.social.externalLink"

View File

@@ -93,7 +93,7 @@ export class AccountVideoChannelsComponent implements OnInit, OnDestroy {
sort: '-updatedAt'
}
this.videoChannelService.listAccountVideoChannels(options)
this.videoChannelService.listAccountChannels(options)
.pipe(
tap(res => {
this.channelPagination.totalItems = res.total

View File

@@ -95,7 +95,7 @@ export class AccountsComponent implements OnInit, OnDestroy {
distinctUntilChanged(),
switchMap(accountId => this.accountService.getAccount(accountId)),
tap(account => this.onAccount(account)),
switchMap(account => this.videoChannelService.listAccountVideoChannels({ account })),
switchMap(account => this.videoChannelService.listAccountChannels({ account })),
catchError(err =>
this.restExtractor.redirectTo404IfNotFound(err, 'other', [
HttpStatusCode.BAD_REQUEST_400,

View File

@@ -1,7 +1,6 @@
@use "_variables" as *;
@use "_mixins" as *;
@use "_form-mixins" as *;
@import "bootstrap/scss/mixins";
.root {
display: flex;

View File

@@ -17,6 +17,7 @@ import { AdminConfigCustomizationComponent } from './pages/admin-config-customiz
import { AdminConfigService } from '../../shared/shared-admin/admin-config.service'
import { AdminConfigLogoComponent } from './pages/admin-config-logo.component'
import { InstanceLogoService } from '../../shared/shared-instance/instance-logo.service'
import { PlayerSettingsService } from '@app/shared/shared-video/player-settings.service'
export const customConfigResolver: ResolveFn<CustomConfig> = (_route: ActivatedRouteSnapshot, _state: RouterStateSnapshot) => {
return inject(AdminConfigService).getCustomConfig()
@@ -71,7 +72,8 @@ export const configRoutes: Routes = [
customConfig: customConfigResolver
},
providers: [
InstanceLogoService
InstanceLogoService,
PlayerSettingsService
],
component: AdminConfigComponent,
children: [

View File

@@ -1,6 +1,6 @@
<my-admin-save-bar i18n-title title="Advanced configuration" (save)="save()" [form]="form" [formErrors]="formErrors"></my-admin-save-bar>
<form [formGroup]="form">
<form [formGroup]="form" id="admin-config-form">
<div class="pt-two-cols">

View File

@@ -36,8 +36,11 @@ input[type="checkbox"] {
@include peertube-select-container($form-base-input-width);
}
my-select-videos-sort,
my-select-videos-scope,
my-select-checkbox,
my-select-options,
my-select-player-theme,
my-select-custom-value {
display: block;

View File

@@ -1,32 +1,38 @@
<my-admin-save-bar i18n-title title="Platform customization" (save)="save()" [form]="form" [formErrors]="formErrors"></my-admin-save-bar>
<form [formGroup]="form">
<form [formGroup]="form" id="admin-config-form">
<div class="pt-two-cols">
<div class="title-col">
<h2 i18n>APPEARANCE</h2>
</div>
<div class="content-col">
<ng-container formGroupName="theme">
<div class="form-group">
<label i18n for="themeDefault">Theme</label>
<div class="form-group" formGroupName="theme">
<label i18n for="themeDefault">Theme</label>
<my-select-options formControlName="default" inputId="themeDefault" [items]="availableThemes"></my-select-options>
</div>
</ng-container>
<my-select-options formControlName="default" inputId="themeDefault" [items]="availableThemes"></my-select-options>
</div>
<ng-container formGroupName="client">
<ng-container formGroupName="videos">
<ng-container formGroupName="miniature">
<div class="form-group">
<my-peertube-checkbox
inputName="clientVideosMiniaturePreferAuthorDisplayName"
formControlName="preferAuthorDisplayName"
i18n-labelText
labelText="Prefer author display name in video miniature"
></my-peertube-checkbox>
</div>
</ng-container>
<div class="form-group" formGroupName="miniature">
<my-peertube-checkbox
inputName="clientVideosMiniaturePreferAuthorDisplayName"
formControlName="preferAuthorDisplayName"
i18n-labelText
labelText="Prefer author display name in video miniature"
></my-peertube-checkbox>
</div>
</ng-container>
</ng-container>
<ng-container formGroupName="defaults">
<ng-container formGroupName="player">
<div class="form-group">
<label i18n for="defaultsPlayerTheme">Player Theme</label>
<my-select-player-theme mode="instance" formControlName="theme" inputId="defaultsPlayerTheme"></my-select-player-theme>
</div>
</ng-container>
</ng-container>
</div>

View File

@@ -9,7 +9,7 @@ import { PeertubeCheckboxComponent } from '@app/shared/shared-forms/peertube-che
import { SelectCustomValueComponent } from '@app/shared/shared-forms/select/select-custom-value.component'
import { SelectOptionsComponent } from '@app/shared/shared-forms/select/select-options.component'
import { objectKeysTyped } from '@peertube/peertube-core-utils'
import { CustomConfig } from '@peertube/peertube-models'
import { CustomConfig, PlayerTheme } from '@peertube/peertube-models'
import { capitalizeFirstLetter } from '@root-helpers/string'
import { ColorPaletteThemeConfig, ThemeCustomizationKey } from '@root-helpers/theme-manager'
import debug from 'debug'
@@ -20,6 +20,7 @@ import { AdminConfigService } from '../../../shared/shared-admin/admin-config.se
import { HelpComponent } from '../../../shared/shared-main/buttons/help.component'
import { AlertComponent } from '../../../shared/shared-main/common/alert.component'
import { AdminSaveBarComponent } from '../shared/admin-save-bar.component'
import { SelectPlayerThemeComponent } from '@app/shared/shared-forms/select/select-player-theme.component'
const debugLogger = debug('peertube:config')
@@ -65,6 +66,12 @@ type Form = {
inputBorderRadius: FormControl<string>
}>
}>
defaults: FormGroup<{
player: FormGroup<{
theme: FormControl<PlayerTheme>
}>
}>
}
type FieldType = 'color' | 'radius'
@@ -84,7 +91,8 @@ type FieldType = 'color' | 'radius'
SelectOptionsComponent,
HelpComponent,
PeertubeCheckboxComponent,
SelectCustomValueComponent
SelectCustomValueComponent,
SelectPlayerThemeComponent
]
})
export class AdminConfigCustomizationComponent implements OnInit, OnDestroy, CanComponentDeactivate {
@@ -108,6 +116,7 @@ export class AdminConfigCustomizationComponent implements OnInit, OnDestroy, Can
}[] = []
availableThemes: SelectOptionsItem[]
availablePlayerThemes: SelectOptionsItem<PlayerTheme>[] = []
private customizationResetFields = new Set<ThemeCustomizationKey>()
private customConfig: CustomConfig
@@ -164,6 +173,11 @@ export class AdminConfigCustomizationComponent implements OnInit, OnDestroy, Can
...this.themeService.buildAvailableThemes()
]
this.availablePlayerThemes = [
{ id: 'galaxy', label: $localize`Galaxy`, description: $localize`Original theme` },
{ id: 'lucide', label: $localize`Lucide`, description: $localize`A clean and modern theme` }
]
this.buildForm()
this.subscribeToCustomizationChanges()
@@ -265,6 +279,11 @@ export class AdminConfigCustomizationComponent implements OnInit, OnDestroy, Can
headerBackgroundColor: null,
inputBorderRadius: null
}
},
defaults: {
player: {
theme: null
}
}
}

View File

@@ -1,13 +1,12 @@
<my-admin-save-bar i18n-title title="General configuration" (save)="save()" [form]="form" [formErrors]="formErrors"></my-admin-save-bar>
<form [formGroup]="form">
<form [formGroup]="form" id="admin-config-form">
<div class="pt-two-cols">
<div class="title-col">
<h2 i18n>BEHAVIOR</h2>
</div>
<div class="content-col">
<div class="form-group" formGroupName="instance">
<label i18n id="instanceDefaultClientRouteLabel" for="instanceDefaultClientRoute">Landing page</label>
@@ -43,13 +42,14 @@
</div>
<ng-container formGroupName="client">
<ng-container formGroupName="menu">
<ng-container formGroupName="login">
<div class="form-group">
<my-peertube-checkbox
inputName="clientMenuLoginRedirectOnSingleExternalAuth" formControlName="redirectOnSingleExternalAuth"
i18n-labelText labelText="Redirect users on single external auth when users click on the login button in menu"
inputName="clientMenuLoginRedirectOnSingleExternalAuth"
formControlName="redirectOnSingleExternalAuth"
i18n-labelText
labelText="Redirect users on single external auth when users click on the login button in menu"
>
<ng-container ngProjectAs="description">
@if (countExternalAuth() === 0) {
@@ -58,12 +58,11 @@
<span i18n>⚠️ You have multiple external auth plugins enabled</span>
}
</ng-container>
</my-peertube-checkbox>
</my-peertube-checkbox>
</div>
</ng-container>
</ng-container>
</ng-container>
</div>
</div>
@@ -76,20 +75,22 @@
</div>
<div class="content-col">
<ng-container formGroupName="broadcastMessage">
<div class="form-group">
<my-peertube-checkbox
inputName="broadcastMessageEnabled" formControlName="enabled"
i18n-labelText labelText="Enable broadcast message"
inputName="broadcastMessageEnabled"
formControlName="enabled"
i18n-labelText
labelText="Enable broadcast message"
></my-peertube-checkbox>
</div>
<div class="form-group">
<my-peertube-checkbox
inputName="broadcastMessageDismissable" formControlName="dismissable"
i18n-labelText labelText="Allow users to dismiss the broadcast message "
inputName="broadcastMessageDismissable"
formControlName="dismissable"
i18n-labelText
labelText="Allow users to dismiss the broadcast message "
></my-peertube-checkbox>
</div>
@@ -111,31 +112,28 @@
<label i18n for="broadcastMessageMessage">Message</label><my-help helpType="markdownText"></my-help>
<my-markdown-textarea
inputId="broadcastMessageMessage" formControlName="message"
[formError]="formErrors.broadcastMessage.message" markdownType="to-unsafe-html"
inputId="broadcastMessageMessage"
formControlName="message"
[formError]="formErrors.broadcastMessage.message"
markdownType="to-unsafe-html"
></my-markdown-textarea>
<div *ngIf="formErrors.broadcastMessage.message" class="form-error" role="alert">{{ formErrors.broadcastMessage.message }}</div>
</div>
</ng-container>
</div>
</div>
<div class="pt-two-cols mt-4"> <!-- new users grid -->
<div class="pt-two-cols mt-4">
<!-- new users grid -->
<div class="title-col">
<h2 i18n>NEW USERS</h2>
</div>
<div class="content-col">
<ng-container formGroupName="signup">
<div class="form-group">
<my-peertube-checkbox
inputName="signupEnabled" formControlName="enabled"
i18n-labelText labelText="Enable Signup"
>
<my-peertube-checkbox inputName="signupEnabled" formControlName="enabled" i18n-labelText labelText="Enable Signup">
<ng-container ngProjectAs="description">
<span i18n>⚠️ This functionality requires a lot of attention and extra moderation</span>
@@ -144,27 +142,37 @@
<ng-container ngProjectAs="extra">
<div class="form-group">
<my-peertube-checkbox [ngClass]="getDisabledSignupClass()"
inputName="signupRequiresApproval" formControlName="requiresApproval"
i18n-labelText labelText="Signup requires approval by moderators"
<my-peertube-checkbox
[ngClass]="getDisabledSignupClass()"
inputName="signupRequiresApproval"
formControlName="requiresApproval"
i18n-labelText
labelText="Signup requires approval by moderators"
></my-peertube-checkbox>
</div>
<div class="form-group">
<my-peertube-checkbox [ngClass]="getDisabledSignupClass()"
inputName="signupRequiresEmailVerification" formControlName="requiresEmailVerification"
i18n-labelText labelText="Signup requires email verification"
<my-peertube-checkbox
[ngClass]="getDisabledSignupClass()"
inputName="signupRequiresEmailVerification"
formControlName="requiresEmailVerification"
i18n-labelText
labelText="Signup requires email verification"
></my-peertube-checkbox>
</div>
<div [ngClass]="getDisabledSignupClass()">
<label i18n for="signupLimit">Signup limit</label>
<span i18n class="small muted ms-1">When the total number of users in your platform reaches this limit, registrations are disabled. -1 == unlimited</span>
<span i18n class="small muted ms-1">When the total number of users in your platform reaches this limit, registrations are disabled. -1 = unlimited</span>
<div class="number-with-unit">
<input
type="number" min="-1" id="signupLimit" class="form-control"
formControlName="limit" [ngClass]="{ 'input-error': formErrors.signup.limit }"
type="number"
min="-1"
id="signupLimit"
class="form-control"
formControlName="limit"
[ngClass]="{ 'input-error': formErrors.signup.limit }"
>
<span i18n>{form.value.signup.limit, plural, =1 {user} other {users}}</span>
</div>
@@ -179,8 +187,12 @@
<div class="number-with-unit">
<input
type="number" min="1" id="signupMinimumAge" class="form-control"
formControlName="minimumAge" [ngClass]="{ 'input-error': formErrors.signup.minimumAge }"
type="number"
min="1"
id="signupMinimumAge"
class="form-control"
formControlName="minimumAge"
[ngClass]="{ 'input-error': formErrors.signup.minimumAge }"
>
<span i18n>{form.value.signup.minimumAge, plural, =1 {year old} other {years old}}</span>
</div>
@@ -201,7 +213,9 @@
inputId="userVideoQuota"
[items]="getVideoQuotaOptions()"
formControlName="videoQuota"
i18n-inputSuffix inputSuffix="bytes" inputType="number"
i18n-inputSuffix
inputSuffix="bytes"
inputType="number"
[clearable]="false"
></my-select-custom-value>
@@ -218,7 +232,9 @@
inputId="userVideoQuotaDaily"
[items]="getVideoQuotaDailyOptions()"
formControlName="videoQuotaDaily"
i18n-inputSuffix inputSuffix="bytes" inputType="number"
i18n-inputSuffix
inputSuffix="bytes"
inputType="number"
[clearable]="false"
></my-select-custom-value>
@@ -228,15 +244,16 @@
<ng-container formGroupName="history">
<ng-container formGroupName="videos">
<my-peertube-checkbox
inputName="videosHistoryEnabled" formControlName="enabled"
i18n-labelText labelText="Automatically enable video history for a new user"
inputName="videosHistoryEnabled"
formControlName="enabled"
i18n-labelText
labelText="Automatically enable video history for a new user"
>
</my-peertube-checkbox>
</ng-container>
</ng-container>
</div>
</ng-container>
</div>
</div>
@@ -246,11 +263,8 @@
</div>
<div class="content-col">
<ng-container formGroupName="import">
<ng-container formGroupName="videos">
<div class="form-group">
<label i18n for="importConcurrency">Import jobs concurrency</label>
<span i18n class="small muted ms-1">allows to import multiple videos in parallel. ⚠️ Requires a PeerTube restart</span>
@@ -265,39 +279,46 @@
<div class="form-group" formGroupName="http">
<my-peertube-checkbox
inputName="importVideosHttpEnabled" formControlName="enabled"
i18n-labelText labelText="Allow import with HTTP URL (e.g. YouTube)"
inputName="importVideosHttpEnabled"
formControlName="enabled"
i18n-labelText
labelText="Allow import with HTTP URL (e.g. YouTube)"
>
<ng-container ngProjectAs="description">
<span i18n>⚠️ If enabled, we recommend to use <a class="link-primary" href="https://docs.joinpeertube.org/maintain/configuration#security">a HTTP proxy</a> to prevent private URL access from your PeerTube server</span>
<span i18n
>⚠️ If enabled, we recommend to use <a class="link-primary" href="https://docs.joinpeertube.org/maintain/configuration#security"
>a HTTP proxy</a> to prevent private URL access from your PeerTube server</span>
</ng-container>
</my-peertube-checkbox>
</div>
<div class="form-group" formGroupName="torrent">
<my-peertube-checkbox
inputName="importVideosTorrentEnabled" formControlName="enabled"
i18n-labelText labelText="Allow import with a torrent file or a magnet URI"
inputName="importVideosTorrentEnabled"
formControlName="enabled"
i18n-labelText
labelText="Allow import with a torrent file or a magnet URI"
>
<ng-container ngProjectAs="description">
<span i18n>⚠️ We don't recommend to enable this feature if you don't trust your users</span>
</ng-container>
</my-peertube-checkbox>
<ng-container ngProjectAs="description">
<span i18n>⚠️ We don't recommend to enable this feature if you don't trust your users</span>
</ng-container>
</my-peertube-checkbox>
</div>
</ng-container>
<ng-container formGroupName="videoChannelSynchronization">
<div class="form-group">
<my-peertube-checkbox
inputName="importSynchronizationEnabled" formControlName="enabled"
i18n-labelText labelText="Allow channel synchronization with channel of other platforms like YouTube"
inputName="importSynchronizationEnabled"
formControlName="enabled"
i18n-labelText
labelText="Allow channel synchronization with channel of other platforms like YouTube"
>
<ng-container ngProjectAs="description">
<span i18n [hidden]="isImportVideosHttpEnabled()">
⛔ You need to allow import with HTTP URL to be able to activate this feature.
</span>
</ng-container>
<ng-container ngProjectAs="description">
<span i18n [hidden]="isImportVideosHttpEnabled()">
⛔ You need to allow import with HTTP URL to be able to activate this feature.
</span>
</ng-container>
</my-peertube-checkbox>
</div>
@@ -306,42 +327,74 @@
<div class="number-with-unit">
<input
type="number" min="1" id="videoChannelSynchronizationMaxPerUser" class="form-control"
formControlName="maxPerUser" [ngClass]="{ 'input-error': formErrors['import']['videoChannelSynchronization']['maxPerUser'] }"
type="number"
min="1"
id="videoChannelSynchronizationMaxPerUser"
class="form-control"
formControlName="maxPerUser"
[ngClass]="{ 'input-error': formErrors['import']['videoChannelSynchronization']['maxPerUser'] }"
>
<span i18n>{form.value.import.videoChannelSynchronization.maxPerUser, plural, =1 {sync} other {syncs}}</span>
</div>
<div *ngIf="formErrors.import.videoChannelSynchronization.maxPerUser" class="form-error" role="alert">{{ formErrors.import.videoChannelSynchronization.maxPerUser }}</div>
<div *ngIf="formErrors.import.videoChannelSynchronization.maxPerUser" class="form-error" role="alert">
{{ formErrors.import.videoChannelSynchronization.maxPerUser }}
</div>
</div>
</ng-container>
</ng-container>
</div>
</div>
<div class="pt-two-cols mt-4">
<div class="title-col">
<h2 i18n>BROWSE VIDEOS</h2>
</div>
<div class="content-col">
<div class="form-group" formGroupName="client">
<ng-container formGroupName="browseVideos">
<div class="form-group">
<label i18n for="browseVideosDefaultSort">Default sort</label>
<my-select-videos-sort inputId="browseVideosDefaultSort" formControlName="defaultSort"></my-select-videos-sort>
<div *ngIf="formErrors.client.browseVideos.defaultSort" class="form-error" role="alert">{{ formErrors.client.browseVideos.defaultSort }}</div>
</div>
<div class="form-group">
<label i18n for="browseVideosDefaultScope">Default scope</label>
<my-select-videos-scope inputId="browseVideosDefaultScope" formControlName="defaultScope"></my-select-videos-scope>
<div *ngIf="formErrors.client.browseVideos.defaultScope" class="form-error" role="alert">{{ formErrors.client.browseVideos.defaultScope }}</div>
</div>
</ng-container>
</div>
</div>
</div>
<div class="pt-two-cols mt-4">
<div class="title-col">
<h2 i18n>VIDEOS</h2>
</div>
<div class="content-col">
<ng-container formGroupName="autoBlacklist">
<ng-container formGroupName="videos">
<ng-container formGroupName="ofUsers">
<div class="form-group">
<my-peertube-checkbox
inputName="autoBlacklistVideosOfUsersEnabled" formControlName="enabled"
i18n-labelText labelText="Block new videos automatically"
inputName="autoBlacklistVideosOfUsersEnabled"
formControlName="enabled"
i18n-labelText
labelText="Block new videos automatically"
>
<ng-container ngProjectAs="description">
<span i18n>Unless a user is marked as trusted, their videos will stay private until a moderator reviews them.</span>
</ng-container>
</my-peertube-checkbox>
<ng-container ngProjectAs="description">
<span i18n>Unless a user is marked as trusted, their videos will stay private until a moderator reviews them.</span>
</ng-container>
</my-peertube-checkbox>
</div>
</ng-container>
</ng-container>
</ng-container>
@@ -350,8 +403,10 @@
<ng-container formGroupName="update">
<div class="form-group">
<my-peertube-checkbox
inputName="videoFileUpdateEnabled" formControlName="enabled"
i18n-labelText labelText="Allow users to upload a new version of their video"
inputName="videoFileUpdateEnabled"
formControlName="enabled"
i18n-labelText
labelText="Allow users to upload a new version of their video"
>
</my-peertube-checkbox>
</div>
@@ -360,38 +415,47 @@
<ng-container formGroupName="storyboards">
<div class="form-group">
<my-peertube-checkbox
inputName="storyboardsEnabled" formControlName="enabled"
i18n-labelText labelText="Enable video storyboards"
>
<my-peertube-checkbox inputName="storyboardsEnabled" formControlName="enabled" i18n-labelText labelText="Enable video storyboards">
<ng-container ngProjectAs="description">
<span i18n>Generate storyboards of local videos using ffmpeg so users can see the video preview in the player while scrubbing the video</span>
</ng-container>
<ng-container ngProjectAs="extra">
<div class="form-group" formGroupName="remoteRunners" [ngClass]="getStoryboardRunnerDisabledClass()">
<my-peertube-checkbox
inputName="storyboardsRemoteRunnersEnabled" formControlName="enabled"
i18n-labelText labelText="Enable remote runners to generate storyboards"
>
<ng-container ngProjectAs="description">
<div i18n>Use <a routerLink="/admin/settings/system/runners/runners-list">remote runners</a> to generate storyboards.</div>
<div i18n>Remote runners have to register on your instance first.</div>
</ng-container>
</my-peertube-checkbox>
</div>
</ng-container>
</my-peertube-checkbox>
</div>
</ng-container>
<ng-container formGroupName="videoTranscription">
<div class="form-group">
<my-peertube-checkbox
inputName="videoTranscriptionEnabled" formControlName="enabled"
i18n-labelText labelText="Enable video transcription"
>
<my-peertube-checkbox inputName="videoTranscriptionEnabled" formControlName="enabled" i18n-labelText labelText="Enable video transcription">
<ng-container ngProjectAs="description">
<span i18n><a href="https://docs.joinpeertube.org/admin/configuration#automatic-transcription" target="_blank">Automatically create subtitles</a> for uploaded/imported VOD videos</span>
<span i18n><a href="https://docs.joinpeertube.org/admin/configuration#automatic-transcription" target="_blank">Automatically create subtitles</a>
for uploaded/imported VOD videos</span>
</ng-container>
<ng-container ngProjectAs="extra">
<div class="form-group" formGroupName="remoteRunners" [ngClass]="getTranscriptionRunnerDisabledClass()">
<my-peertube-checkbox
inputName="videoTranscriptionRemoteRunnersEnabled" formControlName="enabled"
i18n-labelText labelText="Enable remote runners for transcription"
inputName="videoTranscriptionRemoteRunnersEnabled"
formControlName="enabled"
i18n-labelText
labelText="Enable remote runners for transcription"
>
<ng-container ngProjectAs="description">
<span i18n>
Use <a routerLink="/admin/settings/system/runners/runners-list">remote runners</a> to process transcription tasks.
Remote runners has to register on your instance first.
</span>
<div i18n>Use <a routerLink="/admin/settings/system/runners/runners-list">remote runners</a> to process transcription tasks.</div>
<div i18n>Remote runners have to register on your instance first.</div>
</ng-container>
</my-peertube-checkbox>
</div>
@@ -402,7 +466,6 @@
<ng-container formGroupName="defaults">
<ng-container formGroupName="publish">
<div class="form-group">
<label i18n for="defaultsPublishPrivacy">Default video privacy</label>
@@ -442,8 +505,12 @@
<div class="number-with-unit">
<input
type="number" min="1" id="videoChannelsMaxPerUser" class="form-control"
formControlName="maxPerUser" [ngClass]="{ 'input-error': formErrors.videoChannels.maxPerUser }"
type="number"
min="1"
id="videoChannelsMaxPerUser"
class="form-control"
formControlName="maxPerUser"
[ngClass]="{ 'input-error': formErrors.videoChannels.maxPerUser }"
>
<span i18n>{form.value.videoChannels.maxPerUser, plural, =1 {channel} other {channels}}</span>
</div>
@@ -462,15 +529,16 @@
</div>
<div class="content-col">
<ng-container formGroupName="videoComments">
<div class="form-group">
<my-peertube-checkbox
inputName="videoCommentsAcceptRemoteComments" formControlName="acceptRemoteComments"
i18n-labelText labelText="Accept comments made on remote platforms"
inputName="videoCommentsAcceptRemoteComments"
formControlName="acceptRemoteComments"
i18n-labelText
labelText="Accept comments made on remote platforms"
>
<ng-container ngProjectAs="description">
<span i18n>This setting is not retroactive: current remote comments platform will not be deleted</span>
<span i18n>This setting is not retroactive: current comments from remote platforms will not be deleted</span>
</ng-container>
</my-peertube-checkbox>
</div>
@@ -480,22 +548,25 @@
<ng-container formGroupName="channels">
<div class="form-group">
<my-peertube-checkbox
inputName="followersChannelsEnabled" formControlName="enabled"
i18n-labelText labelText="Remote actors can follow channels of your platform"
inputName="followersChannelsEnabled"
formControlName="enabled"
i18n-labelText
labelText="Remote actors can follow channels of your platform"
>
<ng-container ngProjectAs="description">
<span i18n>This setting is not retroactive: current followers of channels of your platform will not be affected</span>
</ng-container>
</my-peertube-checkbox>
</my-peertube-checkbox>
</div>
</ng-container>
<ng-container formGroupName="instance">
<div class="form-group">
<my-peertube-checkbox
inputName="followersInstanceEnabled" formControlName="enabled"
i18n-labelText labelText="Remote actors can follow your platform"
inputName="followersInstanceEnabled"
formControlName="enabled"
i18n-labelText
labelText="Remote actors can follow your platform"
>
<ng-container ngProjectAs="description">
<span i18n>This setting is not retroactive: current followers of your platform will not be affected</span>
@@ -505,8 +576,10 @@
<div class="form-group">
<my-peertube-checkbox
inputName="followersInstanceManualApproval" formControlName="manualApproval"
i18n-labelText labelText="Manually approve new followers that follow your platform"
inputName="followersInstanceManualApproval"
formControlName="manualApproval"
i18n-labelText
labelText="Manually approve new followers that follow your platform"
></my-peertube-checkbox>
</div>
</ng-container>
@@ -514,12 +587,13 @@
<ng-container formGroupName="followings">
<ng-container formGroupName="instance">
<ng-container formGroupName="autoFollowBack">
<div class="form-group">
<my-peertube-checkbox
inputName="followingsInstanceAutoFollowBackEnabled" formControlName="enabled"
i18n-labelText labelText="Automatically follow back followers that follow your platform"
inputName="followingsInstanceAutoFollowBackEnabled"
formControlName="enabled"
i18n-labelText
labelText="Automatically follow back followers that follow your platform"
>
<ng-container ngProjectAs="description">
<span i18n>⚠️ This functionality requires a lot of attention and extra moderation</span>
@@ -531,14 +605,21 @@
<ng-container formGroupName="autoFollowIndex">
<div class="form-group">
<my-peertube-checkbox
inputName="followingsInstanceAutoFollowIndexEnabled" formControlName="enabled"
i18n-labelText labelText="Automatically follow platforms of a public index"
inputName="followingsInstanceAutoFollowIndexEnabled"
formControlName="enabled"
i18n-labelText
labelText="Automatically follow platforms of a public index"
>
<ng-container ngProjectAs="description">
<div i18n>⚠️ This functionality requires a lot of attention and extra moderation.</div>
<span i18n>
See <a class="link-primary" href="https://docs.joinpeertube.org/admin/following-instances#automatically-follow-other-instances" rel="noopener noreferrer" target="_blank">the documentation</a> for more information about the expected URL
See <a
class="link-primary"
href="https://docs.joinpeertube.org/admin/following-instances#automatically-follow-other-instances"
rel="noopener noreferrer"
target="_blank"
>the documentation</a> for more information about the expected URL
</span>
</ng-container>
@@ -546,19 +627,22 @@
<div [ngClass]="{ 'disabled-checkbox-extra': !isAutoFollowIndexEnabled() }">
<label i18n for="followingsInstanceAutoFollowIndexUrl">Index URL</label>
<input
type="text" id="followingsInstanceAutoFollowIndexUrl" class="form-control"
formControlName="indexUrl" [ngClass]="{ 'input-error': formErrors.followings.instance.autoFollowIndex.indexUrl }"
type="text"
id="followingsInstanceAutoFollowIndexUrl"
class="form-control"
formControlName="indexUrl"
[ngClass]="{ 'input-error': formErrors.followings.instance.autoFollowIndex.indexUrl }"
>
<div *ngIf="formErrors.followings.instance.autoFollowIndex.indexUrl" class="form-error" role="alert">{{ formErrors.followings.instance.autoFollowIndex.indexUrl }}</div>
<div *ngIf="formErrors.followings.instance.autoFollowIndex.indexUrl" class="form-error" role="alert">
{{ formErrors.followings.instance.autoFollowIndex.indexUrl }}
</div>
</div>
</ng-container>
</my-peertube-checkbox>
</div>
</ng-container>
</ng-container>
</ng-container>
</div>
</div>
@@ -569,27 +653,34 @@
<div class="content-col">
<ng-container formGroupName="defaults">
<ng-container formGroupName="player">
<div class="form-group" formGroupName="player">
<my-peertube-checkbox
inputName="defaultsPlayerAutoplay" formControlName="autoPlay"
i18n-labelText labelText="Automatically play videos in the player"
></my-peertube-checkbox>
</div>
<div class="form-group">
<my-peertube-checkbox
inputName="defaultsPlayerAutoplay"
formControlName="autoPlay"
i18n-labelText
labelText="Automatically play videos in the player"
></my-peertube-checkbox>
</div>
</ng-container>
<ng-container formGroupName="p2p">
<div class="form-group" formGroupName="webapp">
<my-peertube-checkbox
inputName="defaultsP2PWebappEnabled" formControlName="enabled"
i18n-labelText labelText="Enable P2P streaming by default on your platform"
inputName="defaultsP2PWebappEnabled"
formControlName="enabled"
i18n-labelText
labelText="Enable P2P streaming by default on your platform"
></my-peertube-checkbox>
</div>
<div class="form-group" formGroupName="embed">
<my-peertube-checkbox
inputName="defaultsP2PEmbedEnabled" formControlName="enabled"
i18n-labelText labelText="Enable P2P streaming by default for videos embedded on external websites"
inputName="defaultsP2PEmbedEnabled"
formControlName="enabled"
i18n-labelText
labelText="Enable P2P streaming by default for videos embedded on external websites"
></my-peertube-checkbox>
</div>
</ng-container>
@@ -603,14 +694,14 @@
</div>
<div class="content-col">
<ng-container formGroupName="search">
<ng-container formGroupName="remoteUri">
<div class="form-group">
<my-peertube-checkbox
inputName="searchRemoteUriUsers" formControlName="users"
i18n-labelText labelText="Allow users to do remote URI/handle search"
inputName="searchRemoteUriUsers"
formControlName="users"
i18n-labelText
labelText="Allow users to do remote URI/handle search"
>
<ng-container ngProjectAs="description">
<span i18n>Allow <strong>your users</strong> to look up remote videos/actors that may not be federated with your platform</span>
@@ -620,23 +711,21 @@
<div class="form-group">
<my-peertube-checkbox
inputName="searchRemoteUriAnonymous" formControlName="anonymous"
i18n-labelText labelText="Allow anonymous to do remote URI/handle search"
inputName="searchRemoteUriAnonymous"
formControlName="anonymous"
i18n-labelText
labelText="Allow anonymous to do remote URI/handle search"
>
<ng-container ngProjectAs="description">
<span i18n>Allow <strong>anonymous users</strong> to look up remote videos/actors that may not be federated with your platform</span>
</ng-container>
</my-peertube-checkbox>
</div>
</ng-container>
<ng-container formGroupName="searchIndex">
<div class="form-group">
<my-peertube-checkbox
inputName="searchIndexEnabled" formControlName="enabled"
i18n-labelText labelText="Enable global search"
>
<my-peertube-checkbox inputName="searchIndexEnabled" formControlName="enabled" i18n-labelText labelText="Enable global search">
<ng-container ngProjectAs="description">
<div i18n>⚠️ This functionality depends heavily on the moderation of platforms followed by the search index you select</div>
</ng-container>
@@ -646,43 +735,48 @@
<label i18n for="searchIndexUrl">Search index URL</label>
<div i18n class="form-group-description">
Use your <a class="link-primary" target="_blank" href="https://framagit.org/framasoft/peertube/search-index">your own search index</a> or choose the official one, <a class="link-primary" target="_blank" href="https://sepiasearch.org">https://sepiasearch.org</a>, that is not moderated.
Use <a class="link-primary" target="_blank" href="https://framagit.org/framasoft/peertube/search-index">your own search index</a> or choose the official one, <a class="link-primary" target="_blank" href="https://sepiasearch.org">https://sepiasearch.org</a>, that is not moderated.
</div>
<input
type="text" id="searchIndexUrl" class="form-control"
formControlName="url" [ngClass]="{ 'input-error': formErrors.search.searchIndex.url }"
type="text"
id="searchIndexUrl"
class="form-control"
formControlName="url"
[ngClass]="{ 'input-error': formErrors.search.searchIndex.url }"
>
<div *ngIf="formErrors.search.searchIndex.url" class="form-error" role="alert">{{ formErrors.search.searchIndex.url }}</div>
</div>
<div class="mt-3">
<my-peertube-checkbox [ngClass]="getDisabledSearchIndexClass()"
inputName="searchIndexDisableLocalSearch" formControlName="disableLocalSearch"
i18n-labelText labelText="Disable local search in search bar"
<my-peertube-checkbox
[ngClass]="getDisabledSearchIndexClass()"
inputName="searchIndexDisableLocalSearch"
formControlName="disableLocalSearch"
i18n-labelText
labelText="Disable local search in search bar"
></my-peertube-checkbox>
</div>
<div class="mt-3">
<my-peertube-checkbox [ngClass]="getDisabledSearchIndexClass()"
inputName="searchIndexIsDefaultSearch" formControlName="isDefaultSearch"
i18n-labelText labelText="Search bar uses the global search index by default"
<my-peertube-checkbox
[ngClass]="getDisabledSearchIndexClass()"
inputName="searchIndexIsDefaultSearch"
formControlName="isDefaultSearch"
i18n-labelText
labelText="Search bar uses the global search index by default"
>
<ng-container ngProjectAs="description">
<span i18n>Otherwise, the local search will be used by default</span>
</ng-container>
</my-peertube-checkbox>
</div>
</ng-container>
</my-peertube-checkbox>
</div>
</ng-container>
</ng-container>
</div>
</div>
@@ -692,14 +786,10 @@
</div>
<div class="content-col">
<ng-container formGroupName="import">
<ng-container formGroupName="users">
<div class="form-group">
<my-peertube-checkbox
inputName="importUsersEnabled" formControlName="enabled"
i18n-labelText labelText="Allow your users to import a data archive"
>
<my-peertube-checkbox inputName="importUsersEnabled" formControlName="enabled" i18n-labelText labelText="Allow your users to import a data archive">
<ng-container ngProjectAs="description">
<div i18n>Video quota is checked on import so the user doesn't upload a too big archive file</div>
<div i18n>Video quota (daily quota is not taken into account) is also checked for each video when PeerTube is processing the import</div>
@@ -710,20 +800,14 @@
</ng-container>
<ng-container formGroupName="export">
<ng-container formGroupName="users">
<div class="form-group">
<my-peertube-checkbox
inputName="exportUsersEnabled" formControlName="enabled"
i18n-labelText labelText="Allow your users to export their data"
>
<my-peertube-checkbox inputName="exportUsersEnabled" formControlName="enabled" i18n-labelText labelText="Allow your users to export their data">
<ng-container ngProjectAs="description">
<span i18n>Users can export their PeerTube data in a .zip for backup or re-import. Only one export at a time is allowed per user</span>
</ng-container>
<ng-container ngProjectAs="extra">
<div class="form-group" [ngClass]="getDisabledExportUsersClass()">
<label i18n id="exportUsersMaxUserVideoQuota" for="exportUsersMaxUserVideoQuota">Max user video quota allowed to generate the export</label>
@@ -734,7 +818,9 @@
inputId="exportUsersMaxUserVideoQuota"
[items]="exportMaxUserVideoQuotaOptions"
formControlName="maxUserVideoQuota"
i18n-inputSuffix inputSuffix="bytes" inputType="number"
i18n-inputSuffix
inputSuffix="bytes"
inputType="number"
[clearable]="false"
></my-select-custom-value>
@@ -744,20 +830,21 @@
<div class="form-group" [ngClass]="getDisabledExportUsersClass()">
<label i18n for="exportUsersExportExpiration">User export expiration</label>
<my-select-options inputId="exportUsersExportExpiration" [items]="exportExpirationOptions" formControlName="exportExpiration"></my-select-options>
<my-select-options
inputId="exportUsersExportExpiration"
[items]="exportExpirationOptions"
formControlName="exportExpiration"
></my-select-options>
<div i18n class="mt-1 small muted">The archive file is deleted after this period</div>
<div *ngIf="formErrors.export.users.exportExpiration" class="form-error" role="alert">{{ formErrors.export.users.exportExpiration }}</div>
</div>
</ng-container>
</my-peertube-checkbox>
</div>
</ng-container>
</ng-container>
</div>
</div>
</form>

View File

@@ -33,6 +33,8 @@ import { MarkdownTextareaComponent } from '../../../shared/shared-forms/markdown
import { PeertubeCheckboxComponent } from '../../../shared/shared-forms/peertube-checkbox.component'
import { SelectCustomValueComponent } from '../../../shared/shared-forms/select/select-custom-value.component'
import { SelectOptionsComponent } from '../../../shared/shared-forms/select/select-options.component'
import { SelectVideosScopeComponent } from '../../../shared/shared-forms/select/select-videos-scope.component'
import { SelectVideosSortComponent } from '../../../shared/shared-forms/select/select-videos-sort.component'
import { HelpComponent } from '../../../shared/shared-main/buttons/help.component'
import { UserRealQuotaInfoComponent } from '../../shared/user-real-quota-info.component'
import { AdminSaveBarComponent } from '../shared/admin-save-bar.component'
@@ -43,6 +45,11 @@ type Form = {
}>
client: FormGroup<{
browseVideos: FormGroup<{
defaultSort: FormControl<string>
defaultScope: FormControl<string>
}>
menu: FormGroup<{
login: FormGroup<{
redirectOnSingleExternalAuth: FormControl<boolean>
@@ -175,6 +182,10 @@ type Form = {
storyboards: FormGroup<{
enabled: FormControl<boolean>
remoteRunners: FormGroup<{
enabled: FormControl<boolean>
}>
}>
defaults: FormGroup<{
@@ -220,7 +231,9 @@ type Form = {
UserRealQuotaInfoComponent,
SelectOptionsComponent,
AlertComponent,
AdminSaveBarComponent
AdminSaveBarComponent,
SelectVideosSortComponent,
SelectVideosScopeComponent
]
})
export class AdminConfigGeneralComponent implements OnInit, OnDestroy, CanComponentDeactivate {
@@ -295,6 +308,10 @@ export class AdminConfigGeneralComponent implements OnInit, OnDestroy, CanCompon
defaultClientRoute: null
},
client: {
browseVideos: {
defaultSort: null,
defaultScope: null
},
menu: {
login: {
redirectOnSingleExternalAuth: null
@@ -410,7 +427,10 @@ export class AdminConfigGeneralComponent implements OnInit, OnDestroy, CanCompon
}
},
storyboards: {
enabled: null
enabled: null,
remoteRunners: {
enabled: null
}
},
defaults: {
publish: {
@@ -523,6 +543,16 @@ export class AdminConfigGeneralComponent implements OnInit, OnDestroy, CanCompon
// ---------------------------------------------------------------------------
isStoryboardEnabled () {
return this.form.value.storyboards.enabled === true
}
getStoryboardRunnerDisabledClass () {
return { 'disabled-checkbox-extra': !this.isStoryboardEnabled() }
}
// ---------------------------------------------------------------------------
isAutoFollowIndexEnabled () {
return this.form.value.followings.instance.autoFollowIndex.enabled === true
}

View File

@@ -1,6 +1,6 @@
<my-admin-save-bar i18n-title title="Edit your homepage" (save)="save()" [form]="form" [formErrors]="formErrors"></my-admin-save-bar>
<form class="homepage pt-two-cols" [formGroup]="form">
<form class="homepage pt-two-cols" [formGroup]="form" id="admin-config-form">
<div class="title-col">
<h2 i18n>HOMEPAGE</h2>
</div>

View File

@@ -1,6 +1,6 @@
<my-admin-save-bar i18n-title title="Platform information" (save)="save()" [form]="form" [formErrors]="formErrors"></my-admin-save-bar>
<form [formGroup]="form">
<form [formGroup]="form" id="admin-config-form">
<div class="pt-two-cols mt-4">
<div class="title-col">
@@ -172,6 +172,17 @@
<div *ngIf="formErrors.instance.social.mastodonLink" class="form-error" role="alert">{{ formErrors.instance.social.mastodonLink }}</div>
</div>
<div class="form-group">
<label i18n for="instanceSocialXLink">X link</label>
<input
type="text" id="instanceSocialXLink" class="form-control"
formControlName="xLink" [ngClass]="{ 'input-error': formErrors.instance.social.xLink }"
>
<div *ngIf="formErrors.instance.social.xLink" class="form-error" role="alert">{{ formErrors.instance.social.xLink }}</div>
</div>
<div class="form-group">
<label i18n for="instanceSocialBlueskyLink">Bluesky link</label>

View File

@@ -59,6 +59,7 @@ type Form = {
externalLink: FormControl<string>
mastodonLink: FormControl<string>
blueskyLink: FormControl<string>
xLink: FormControl<string>
}>
isNSFW: FormControl<boolean>
@@ -205,7 +206,8 @@ export class AdminConfigInformationComponent implements OnInit, OnDestroy, CanCo
social: {
externalLink: URL_VALIDATOR,
mastodonLink: URL_VALIDATOR,
blueskyLink: URL_VALIDATOR
blueskyLink: URL_VALIDATOR,
xLink: URL_VALIDATOR
}
}
}

View File

@@ -1,6 +1,6 @@
<my-admin-save-bar i18n-title title="Live configuration" (save)="save()" [form]="form" [formErrors]="formErrors" [inconsistentOptions]="checkTranscodingConsistentOptions()"></my-admin-save-bar>
<form [formGroup]="form">
<form [formGroup]="form" id="admin-config-form">
<div class="pt-two-cols">
<div class="title-col">
@@ -167,10 +167,8 @@
i18n-labelText labelText="Enable remote runners for lives"
>
<ng-container ngProjectAs="description">
<span i18n>
Use <a routerLink="/admin/settings/system/runners/runners-list">remote runners</a> to process live transcoding.
Remote runners has to register on your instance first.
</span>
<div i18n>Use <a routerLink="/admin/settings/system/runners/runners-list">remote runners</a> to process live transcoding.</div>
<div i18n>Remote runners have to register on your instance first.</div>
</ng-container>
</my-peertube-checkbox>
</div>

View File

@@ -107,7 +107,7 @@ export class AdminConfigLiveComponent implements OnInit, OnDestroy, CanComponent
{ id: 1000 * 3600 * 10, label: $localize`10 hours` }
]
this.liveResolutions = this.adminConfigService.transcodingResolutionOptions
this.liveResolutions = this.adminConfigService.getTranscodingOptions('live')
this.transcodingProfiles = this.adminConfigService.buildTranscodingProfiles(
this.server.getHTMLConfig().live.transcoding.availableProfiles
)
@@ -143,7 +143,7 @@ export class AdminConfigLiveComponent implements OnInit, OnDestroy, CanComponent
enabled: null,
threads: TRANSCODING_THREADS_VALIDATOR,
profile: null,
resolutions: this.adminConfigService.buildFormResolutions(),
resolutions: this.adminConfigService.buildFormResolutions('live'),
alwaysTranscodeOriginalResolution: null,
remoteRunners: {
enabled: null

View File

@@ -1,6 +1,6 @@
<my-admin-save-bar i18n-title title="Platform information" (save)="save()" [form]="form" [formErrors]="formErrors"></my-admin-save-bar>
<my-admin-save-bar i18n-title title="Upload logos" (save)="save()" [form]="form" [formErrors]="formErrors"></my-admin-save-bar>
<form [formGroup]="form">
<form [formGroup]="form" id="admin-config-form">
<div class="pt-two-cols mt-4">
<div class="title-col">
<h2 i18n>LOGO</h2>

View File

@@ -1,6 +1,6 @@
<my-admin-save-bar i18n-title title="VOD configuration" (save)="save()" [form]="form" [formErrors]="formErrors" [inconsistentOptions]="checkTranscodingConsistentOptions()"></my-admin-save-bar>
<form [formGroup]="form">
<form [formGroup]="form" id="admin-config-form">
<div class="pt-two-cols">
<div class="title-col">
@@ -185,10 +185,8 @@
i18n-labelText labelText="Enable remote runners for VOD"
>
<ng-container ngProjectAs="description">
<span i18n>
Use <a routerLink="/admin/settings/system/runners/runners-list">remote runners</a> to process VOD transcoding.
Remote runners has to register on your instance first.
</span>
<div i18n>Use <a routerLink="/admin/settings/system/runners/runners-list">remote runners</a> to process VOD transcoding.</div>
<div i18n>Remote runners have to register on your instance first.</div>
</ng-container>
</my-peertube-checkbox>
</div>
@@ -262,19 +260,19 @@
<ng-container ngProjectAs="description" *ngIf="!isTranscodingEnabled()">
<span i18n>⚠️ You need to enable transcoding first to enable video studio</span>
</ng-container>
</my-peertube-checkbox>
</div>
<div class="form-group" formGroupName="remoteRunners" [ngClass]="getStudioDisabledClass()">
<my-peertube-checkbox
inputName="videoStudioRemoteRunnersEnabled" formControlName="enabled"
i18n-labelText labelText="Enable remote runners for studio"
>
<ng-container ngProjectAs="description">
<span i18n>
Use <a routerLink="/admin/settings/system/runners/runners-list">remote runners</a> to process studio transcoding tasks.
Remote runners has to register on your instance first.
</span>
<ng-container ngProjectAs="extra">
<div class="form-group" formGroupName="remoteRunners" [ngClass]="getStudioRunnerDisabledClass()">
<my-peertube-checkbox
inputName="videoStudioRemoteRunnersEnabled" formControlName="enabled"
i18n-labelText labelText="Enable remote runners for studio"
>
<ng-container ngProjectAs="description">
<div i18n>Use <a routerLink="/admin/settings/system/runners/runners-list">remote runners</a> to process studio transcoding tasks.</div>
<div i18n>Remote runners have to register on your instance first.</div>
</ng-container>
</my-peertube-checkbox>
</div>
</ng-container>
</my-peertube-checkbox>
</div>

View File

@@ -112,7 +112,7 @@ export class AdminConfigVODComponent implements OnInit, OnDestroy, CanComponentD
this.customConfig = this.route.parent.snapshot.data['customConfig']
this.transcodingThreadOptions = this.configService.transcodingThreadOptions
this.resolutions = this.adminConfigService.transcodingResolutionOptions
this.resolutions = this.adminConfigService.getTranscodingOptions('vod')
this.additionalVideoExtensions = serverConfig.video.file.extensions.join(' ')
this.transcodingProfiles = this.adminConfigService.buildTranscodingProfiles(serverConfig.transcoding.availableProfiles)
@@ -156,7 +156,7 @@ export class AdminConfigVODComponent implements OnInit, OnDestroy, CanComponentD
max: TRANSCODING_MAX_FPS_VALIDATOR
},
resolutions: this.adminConfigService.buildFormResolutions(),
resolutions: this.adminConfigService.buildFormResolutions('vod'),
alwaysTranscodeOriginalResolution: null,
remoteRunners: {
@@ -226,7 +226,7 @@ export class AdminConfigVODComponent implements OnInit, OnDestroy, CanComponentD
return { 'disabled-checkbox-extra': !this.isTranscodingEnabled() || this.isRemoteRunnerVODEnabled() }
}
getStudioDisabledClass () {
getStudioRunnerDisabledClass () {
return { 'disabled-checkbox-extra': !this.isStudioEnabled() }
}

View File

@@ -5,7 +5,7 @@
<div class="buttons">
<my-button theme="secondary" class="pre-config" (click)="openConfigWizard()" i18n>Open config wizard</my-button>
<my-button theme="primary" class="save-button" icon="circle-tick" [disabled]="!canUpdate()" (click)="onSave($event)" i18n>Save</my-button>
<my-button form="admin-config-form" theme="primary" class="save-button" icon="circle-tick" [disabled]="!canUpdate()" (click)="onSave($event)" i18n>Save</my-button>
</div>
</div>

View File

@@ -1,7 +1,6 @@
@use "_variables" as *;
@use "_mixins" as *;
@use "_form-mixins" as *;
@import "bootstrap/scss/mixins";
.root {
position: sticky;

View File

@@ -13,23 +13,27 @@
<form novalidate [formGroup]="form" (ngSubmit)="processRegistration()">
<div class="modal-body mb-3">
<my-alert i18n *ngIf="!registration.emailVerified" type="warning">
Registration email has not been verified. Email delivery has been disabled by default.
</my-alert>
<div class="description">
<ng-container *ngIf="isAccept()">
<p i18n>
<strong>Accepting</strong>&nbsp;<em>{{ registration.username }}</em> registration will create the account and channel.
</p>
<p *ngIf="isEmailEnabled()" i18n [ngClass]="{ 'text-decoration-line-through': isPreventEmailDeliveryChecked() }">
An email will be sent to <em>{{ registration.email }}</em> explaining its account has been created with the moderation response you'll write below.
</p>
<my-alert *ngIf="!isEmailEnabled()" type="warning" i18n>
Emails are not enabled on this instance so PeerTube won't be able to send an email to <em>{{ registration.email }}</em> explaining its account has been created.
</my-alert>
@if (isEmailEnabled()) {
@if (!registration.emailVerified) {
<my-alert i18n type="warning">
Registration email has not been verified. Email delivery has been disabled by default.
</my-alert>
} @else {
<p i18n [ngClass]="{ 'text-decoration-line-through': isPreventEmailDeliveryChecked() }">
An email will be sent to <em>{{ registration.email }}</em> explaining its account has been created with the moderation response you'll write below.
</p>
}
} @else {
<my-alert type="warning" i18n>
Emails are not enabled on this instance so PeerTube won't be able to send an email to <em>{{ registration.email }}</em> explaining its account has been created.
</my-alert>
}
</ng-container>
<ng-container *ngIf="isReject()">
@@ -58,7 +62,7 @@
<div class="form-group">
<my-peertube-checkbox
inputName="preventEmailDelivery" formControlName="preventEmailDelivery" [disabled]="!isEmailEnabled()"
inputName="preventEmailDelivery" formControlName="preventEmailDelivery"
i18n-labelText labelText="Prevent email from being sent to the user"
></my-peertube-checkbox>
</div>

View File

@@ -1,4 +1,4 @@
import { NgClass, NgIf } from '@angular/common'
import { CommonModule } from '@angular/common'
import { Component, OnInit, inject, output, viewChild } from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { Notifier, ServerService } from '@app/core'
@@ -16,7 +16,7 @@ import { REGISTRATION_MODERATION_RESPONSE_VALIDATOR } from './process-registrati
@Component({
selector: 'my-process-registration-modal',
templateUrl: './process-registration-modal.component.html',
imports: [ NgIf, GlobalIconComponent, FormsModule, ReactiveFormsModule, NgClass, PeertubeCheckboxComponent, AlertComponent ]
imports: [ CommonModule, GlobalIconComponent, FormsModule, ReactiveFormsModule, PeertubeCheckboxComponent, AlertComponent ]
})
export class ProcessRegistrationModalComponent extends FormReactive implements OnInit {
protected formReactiveService = inject(FormReactiveService)
@@ -53,6 +53,12 @@ export class ProcessRegistrationModalComponent extends FormReactive implements O
this.processMode = mode
this.registration = registration
if (this.registration.emailVerified !== true || !this.isEmailEnabled()) {
this.form.get('preventEmailDelivery').disable()
} else {
this.form.get('preventEmailDelivery').enable()
}
this.form.patchValue({
preventEmailDelivery: !this.isEmailEnabled() || registration.emailVerified !== true
})

View File

@@ -6,12 +6,12 @@ import { AuthService, Notifier, ScreenService, ServerService } from '@app/core'
import {
USER_CHANNEL_NAME_VALIDATOR,
USER_EMAIL_VALIDATOR,
USER_PASSWORD_OPTIONAL_VALIDATOR,
USER_PASSWORD_VALIDATOR,
USER_ROLE_VALIDATOR,
USER_USERNAME_VALIDATOR,
USER_VIDEO_QUOTA_DAILY_VALIDATOR,
USER_VIDEO_QUOTA_VALIDATOR
USER_VIDEO_QUOTA_VALIDATOR,
getUserNewPasswordOptionalValidator,
getUserNewPasswordValidator
} from '@app/shared/form-validators/user-validators'
import { AdminConfigService } from '@app/shared/shared-admin/admin-config.service'
import { FormReactiveService } from '@app/shared/shared-forms/form-reactive.service'
@@ -76,11 +76,17 @@ export class UserCreateComponent extends UserEdit implements OnInit {
videoQuotaDaily: -1
}
const passwordConstraints = this.serverService.getHTMLConfig().fieldsConstraints.users.password
this.buildForm({
username: USER_USERNAME_VALIDATOR,
channelName: USER_CHANNEL_NAME_VALIDATOR,
email: USER_EMAIL_VALIDATOR,
password: this.isPasswordOptional() ? USER_PASSWORD_OPTIONAL_VALIDATOR : USER_PASSWORD_VALIDATOR,
password: this.isPasswordOptional()
? getUserNewPasswordOptionalValidator(passwordConstraints.minLength, passwordConstraints.maxLength)
: getUserNewPasswordValidator(passwordConstraints.minLength, passwordConstraints.maxLength),
role: USER_ROLE_VALIDATOR,
videoQuota: USER_VIDEO_QUOTA_VALIDATOR,
videoQuotaDaily: USER_VIDEO_QUOTA_DAILY_VALIDATOR,

View File

@@ -1,8 +1,17 @@
@use '_variables' as *;
@use '_mixins' as *;
@use '_form-mixins' as *;
@use "_variables" as *;
@use "_mixins" as *;
@use "_form-mixins" as *;
input[type=text],
input[type=password] {
input[type="text"],
input[type="password"] {
@include peertube-input-text(340px);
}
.btn {
background-color: pvar(--input-bg);
border-left: 1px solid pvar(--bg);
&:hover {
opacity: 0.8;
}
}

View File

@@ -1,12 +1,12 @@
import { NgClass, NgIf } from '@angular/common'
import { Component, OnInit, inject, input } from '@angular/core'
import { Notifier } from '@app/core'
import { USER_PASSWORD_VALIDATOR } from '@app/shared/form-validators/user-validators'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { Notifier, ServerService } from '@app/core'
import { getUserNewPasswordValidator } from '@app/shared/form-validators/user-validators'
import { FormReactive } from '@app/shared/shared-forms/form-reactive'
import { FormReactiveService } from '@app/shared/shared-forms/form-reactive.service'
import { UserUpdate } from '@peertube/peertube-models'
import { NgClass, NgIf } from '@angular/common'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { UserAdminService } from '@app/shared/shared-users/user-admin.service'
import { UserUpdate } from '@peertube/peertube-models'
@Component({
selector: 'my-user-password',
@@ -18,6 +18,7 @@ export class UserPasswordComponent extends FormReactive implements OnInit {
protected formReactiveService = inject(FormReactiveService)
private notifier = inject(Notifier)
private userAdminService = inject(UserAdminService)
private serverService = inject(ServerService)
readonly userId = input<number>(undefined)
readonly username = input<string>(undefined)
@@ -26,9 +27,9 @@ export class UserPasswordComponent extends FormReactive implements OnInit {
showPassword = false
ngOnInit () {
this.buildForm({
password: USER_PASSWORD_VALIDATOR
})
const { minLength, maxLength } = this.serverService.getHTMLConfig().fieldsConstraints.users.password
this.buildForm({ password: getUserNewPasswordValidator(minLength, maxLength) })
}
formValidated () {

View File

@@ -75,7 +75,7 @@
<ng-template #actionCell let-video>
<my-video-actions-dropdown
placement="bottom auto" buttonDirection="horizontal" buttonStyled="true" [video]="video" [displayOptions]="videoActionsOptions"
placement="bottom auto" buttonIcon="more-horizontal" buttonStyled="true" [video]="video" [displayOptions]="videoActionsOptions"
(videoRemoved)="table.loadData()" (videoFilesRemoved)="table.loadData()" (transcodingCreated)="table.loadData()"
></my-video-actions-dropdown>
</ng-template>

View File

@@ -2,7 +2,7 @@ import { Subscription } from 'rxjs'
import { map, switchMap } from 'rxjs/operators'
import { Component, OnDestroy, OnInit, inject } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { HooksService, Notifier, PluginService } from '@app/core'
import { HooksService, Notifier, PluginService, ServerService } from '@app/core'
import { FormReactive } from '@app/shared/shared-forms/form-reactive'
import { FormReactiveService } from '@app/shared/shared-forms/form-reactive.service'
import { PeerTubePlugin, RegisterServerSettingOptions } from '@peertube/peertube-models'
@@ -24,6 +24,7 @@ export class PluginShowInstalledComponent extends FormReactive implements OnInit
private notifier = inject(Notifier)
private hooks = inject(HooksService)
private route = inject(ActivatedRoute)
private server = inject(ServerService)
plugin: PeerTubePlugin
registeredSettings: RegisterServerSettingOptions[] = []
@@ -50,6 +51,7 @@ export class PluginShowInstalledComponent extends FormReactive implements OnInit
const settings = this.form.value
this.pluginAPIService.updatePluginSettings(this.plugin.name, this.plugin.type, settings)
.pipe(switchMap(() => this.server.resetConfig()))
.subscribe({
next: () => {
this.notifier.success($localize`Settings updated.`)

View File

@@ -11,7 +11,7 @@
[customUpdateUrl]="customUpdateUrl"
>
<ng-template #totalTitle let-totalRecords>
<ng-container i18n>{ totalRecords, plural, =0 {No jobs} =1 {1 job} other {{{ totalRecords | myNumberFormatter }} jobs}}</ng-container>
<ng-container i18n>{ totalRecords, plural, =0 {No job} =1 {1 job} other {{{ totalRecords | myNumberFormatter }} jobs}}</ng-container>
</ng-template>
<ng-template #captionRight>
@@ -39,23 +39,23 @@
</ng-template>
<ng-template #tableCells let-job>
<td *ngIf="table.isColumnDisplayed('id')" class="job-id c-hand" [title]="job.id">{{ job.id }}</td>
<td *ngIf="table.isColumnDisplayed('id')" class="job-i" [title]="job.id">{{ job.id }}</td>
<td *ngIf="table.isColumnDisplayed('type')" class="job-type c-hand" >
<td *ngIf="table.isColumnDisplayed('type')" class="job-typ" >
<span class="pt-badge ellipsis" [ngClass]="getRandomJobTypeBadge(job.type)">{{ job.type }}</span>
</td>
<td *ngIf="table.isColumnDisplayed('priority')" class="job-priority c-hand" >{{ job.priority }}</td>
<td *ngIf="table.isColumnDisplayed('priority')" class="job-priorit" >{{ job.priority }}</td>
<td *ngIf="table.isColumnDisplayed('state') && table.isColumnDisplayed('state')" class="job-state c-hand" >
<td *ngIf="table.isColumnDisplayed('state') && table.isColumnDisplayed('state')" class="job-stat" >
<span class="ellipsis" [ngClass]="getJobStateClasses(job.state)">{{ job.state }}</span>
</td>
<td *ngIf="table.isColumnDisplayed('progress')" class="job-progress c-hand" >
<td *ngIf="table.isColumnDisplayed('progress')" class="job-progres" >
<ng-container *ngIf="hasProgress(job)">{{ getProgress(job) }}</ng-container>
</td>
<td *ngIf="table.isColumnDisplayed('createdAt')" class="job-date c-hand" >{{ job.createdAt }}</td>
<td *ngIf="table.isColumnDisplayed('createdAt')" class="job-dat" >{{ job.createdAt }}</td>
<td *ngIf="table.isColumnDisplayed('processed')" class="fs-7">
<div>{{ job.processedOn }}</div>

View File

@@ -15,7 +15,7 @@
<label i18n for="log-start-date">Start date</label>
<my-select-options inputId="log-start-date" [items]="timeChoices" [(ngModel)]="startDate" (ngModelChange)="refresh()">
<ng-template ptTemplate="item" let-item>
<ng-template #selectOption let-item>
{{ item.label }} ({{ item.id | ptDate: item.dateFormat }} - <span i18n>now</span>)
</ng-template>
</my-select-options>
@@ -25,7 +25,7 @@
<label i18n for="log-level">Log level</label>
<my-select-options inputId="log-level" [items]="levelChoices" [(ngModel)]="level" (ngModelChange)="refresh()">
<ng-template ptTemplate="item" let-item>
<ng-template #selectOption let-item>
<span class="level-choice me-1" [ngClass]="item.id">&#11044;</span> {{ item.label }}
</ng-template>
</my-select-options>

View File

@@ -4,7 +4,6 @@ import { FormsModule } from '@angular/forms'
import { LocalStorageService, Notifier } from '@app/core'
import { SelectOptionsComponent } from '@app/shared/shared-forms/select/select-options.component'
import { PTDatePipe } from '@app/shared/shared-main/common/date.pipe'
import { PeerTubeTemplateDirective } from '@app/shared/shared-main/common/peertube-template.directive'
import { ServerLogLevel } from '@peertube/peertube-models'
import { SelectTagsComponent } from '../../../shared/shared-forms/select/select-tags.component'
import { ButtonComponent } from '../../../shared/shared-main/buttons/button.component'
@@ -24,8 +23,7 @@ import { LogsService } from './logs.service'
ButtonComponent,
PTDatePipe,
CopyButtonComponent,
SelectOptionsComponent,
PeerTubeTemplateDirective
SelectOptionsComponent
]
})
export class LogsComponent implements OnInit {

View File

@@ -18,6 +18,7 @@ export class ErrorPageComponent implements OnInit {
type: 'video' | 'other' = 'other'
public constructor () {
// Keep this in constructor so we getCurrentNavigation() doesn't return null
const state = this.router.getCurrentNavigation()?.extras.state
this.type = state?.type || this.type
this.status = state?.obj.status || this.status

View File

@@ -1,13 +1,18 @@
import { Component, ElementRef, OnInit, inject, viewChild } from '@angular/core'
import { Component, ElementRef, inject, OnDestroy, OnInit, viewChild } from '@angular/core'
import { DisableForReuseHook, PeerTubeRouterService } from '@app/core'
import { CustomPageService } from '@app/shared/shared-main/custom-page/custom-page.service'
import { debounceTime, fromEvent, Subscription } from 'rxjs'
import { CustomMarkupContainerComponent } from '../shared/shared-custom-markup/custom-markup-container.component'
@Component({
templateUrl: './home.component.html',
imports: [ CustomMarkupContainerComponent ]
})
export class HomeComponent implements OnInit {
export class HomeComponent implements OnInit, OnDestroy, DisableForReuseHook {
private customPageService = inject(CustomPageService)
private sub: Subscription
private peertubeRouter = inject(PeerTubeRouterService)
private disabled = false
readonly contentWrapper = viewChild<ElementRef<HTMLInputElement>>('contentWrapper')
@@ -16,5 +21,33 @@ export class HomeComponent implements OnInit {
ngOnInit () {
this.customPageService.getInstanceHomepage()
.subscribe(({ content }) => this.homepageContent = content)
// If this has been a redirection from the homepage,
// replace the URL on scroll to correctly restore scroll position on web browser back button
if (window.location.pathname === '/') {
this.sub = fromEvent(window, 'scroll')
.pipe(debounceTime(250))
.subscribe(() => {
if (this.disabled) return
if (window.pageYOffset > 300) {
this.peertubeRouter.silentNavigate([], {})
this.sub.unsubscribe()
this.sub = undefined
}
})
}
}
ngOnDestroy () {
if (this.sub) this.sub.unsubscribe()
}
disableForReuse () {
this.disabled = true
}
enabledForReuse () {
this.disabled = true
}
}

View File

@@ -28,6 +28,10 @@ export default [
data: {
meta: {
title: $localize`Homepage`
},
reuse: {
enabled: true,
key: 'home'
}
}
}

View File

@@ -1,6 +1,10 @@
<div class="margin-content">
<h1 class="title-page" i18n>Login on {{ instanceName }}</h1>
<my-alert type="danger" i18n *ngIf="externalAuthError">
Sorry but there was an issue with the external login process. Please <a class="link-primary" routerLink="/about">contact an administrator</a>.
</my-alert>
<ng-container *ngIf="!externalAuthError && !isAuthenticatedWithExternalAuth">
<my-alert type="primary">
@@ -25,10 +29,6 @@
}
</my-alert>
<my-alert type="danger" i18n *ngIf="externalAuthError">
Sorry but there was an issue with the external login process. Please <a class="link-primary" routerLink="/about">contact an administrator</a>.
</my-alert>
<my-alert *ngIf="error" type="danger">
{{ error }}

View File

@@ -1,24 +0,0 @@
import { Routes } from '@angular/router'
import { VideoChannelCreateComponent } from '@app/shared/standalone-channels/video-channel-create.component'
import { VideoChannelUpdateComponent } from '@app/shared/standalone-channels/video-channel-update.component'
export default [
{
path: 'create',
component: VideoChannelCreateComponent,
data: {
meta: {
title: $localize`Create a new video channel`
}
}
},
{
path: 'update/:videoChannelName',
component: VideoChannelUpdateComponent,
data: {
meta: {
title: $localize`Update video channel`
}
}
}
] satisfies Routes

View File

@@ -30,6 +30,7 @@
<li i18n>A directory containing static files (thumbnails, avatars, video files etc.)</li>
</ul>
<p i18n>Videos and playlists from collaborated channels, as well as your channel collaborators, <strong>will not</strong> be included in the archive.</p>
<p i18n>You can only request one archive at a time.</p>
@if (isEmailEnabled()) {

View File

@@ -26,4 +26,6 @@
</button>
</div>
<my-user-notifications #userNotification></my-user-notifications>
<div class="user-notifications-container">
<my-user-notifications #userNotification></my-user-notifications>
</div>

View File

@@ -1,7 +1,7 @@
@use '_variables' as *;
@use '_mixins' as *;
@use '_button-mixins' as *;
@use '_form-mixins' as *;
@use "_variables" as *;
@use "_mixins" as *;
@use "_button-mixins" as *;
@use "_form-mixins" as *;
.header {
display: flex;
@@ -13,3 +13,8 @@
@include peertube-select-container(auto);
}
}
.user-notifications-container {
padding: 1rem;
background-color: pvar(--bg-secondary-400);
}

View File

@@ -32,7 +32,7 @@ export class MyAccountNotificationsComponent {
}
hasUnreadNotifications () {
return this.userNotification().notifications.filter(n => n.read === false).length !== 0
return this.userNotification().notifications.filter(n => n.payload.read === false).length !== 0
}
onChangeSortColumn () {

View File

@@ -1,11 +1,11 @@
import { NgIf } from '@angular/common'
import { Component, OnInit, inject } from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { AuthService, Notifier, UserService } from '@app/core'
import { AuthService, Notifier, ServerService, UserService } from '@app/core'
import {
USER_CONFIRM_PASSWORD_VALIDATOR,
USER_EXISTING_PASSWORD_VALIDATOR,
USER_PASSWORD_VALIDATOR
getUserNewPasswordValidator
} from '@app/shared/form-validators/user-validators'
import { FormReactive } from '@app/shared/shared-forms/form-reactive'
import { FormReactiveService } from '@app/shared/shared-forms/form-reactive.service'
@@ -25,14 +25,17 @@ export class MyAccountChangePasswordComponent extends FormReactive implements On
private notifier = inject(Notifier)
private authService = inject(AuthService)
private userService = inject(UserService)
private serverService = inject(ServerService)
error: string
user: User
ngOnInit () {
const { minLength, maxLength } = this.serverService.getHTMLConfig().fieldsConstraints.users.password
this.buildForm({
'current-password': USER_EXISTING_PASSWORD_VALIDATOR,
'new-password': USER_PASSWORD_VALIDATOR,
'new-password': getUserNewPasswordValidator(minLength, maxLength),
'new-confirmed-password': USER_CONFIRM_PASSWORD_VALIDATOR
})

View File

@@ -0,0 +1,126 @@
<h2 class="form-title">
<my-global-icon iconName="cog"></my-global-icon>
<ng-container i18n>General</ng-container>
</h2>
<form id="channel-manage-form" [formGroup]="form">
<div class="pt-two-cols">
<div class="title-col">
<h3 i18n>IMAGES</h3>
</div>
<div class="content-col">
<my-actor-banner-edit
previewImage="true"
class="d-block mb-4"
[bannerUrl]="videoChannelEdit.apiInfo.bannerUrl"
(bannerChange)="onBannerChange($event)"
(bannerDelete)="onBannerDelete()"
></my-actor-banner-edit>
<my-actor-avatar-edit
class="d-block mb-4"
actorType="channel"
previewImage="true"
[avatars]="videoChannelEdit.apiInfo.avatars"
[username]="mode === 'update' && videoChannelEdit.channel.name"
[subscribers]="mode === 'update' && videoChannelEdit.apiInfo.followersCount"
(avatarChange)="onAvatarChange($event)"
(avatarDelete)="onAvatarDelete()"
></my-actor-avatar-edit>
</div>
</div>
<div class="pt-two-cols">
<div class="title-col">
<h3 i18n>INFORMATION</h3>
</div>
<div class="content-col">
<div class="form-group" [hidden]="mode !== 'create'">
<label i18n for="name">Name</label>
<div class="input-group">
<input
type="text"
id="name"
i18n-placeholder
placeholder="Example: my_channel"
formControlName="name"
[ngClass]="{ 'input-error': formErrors.name }"
class="form-control w-auto flex-grow-1 d-block"
>
<div class="input-group-text">&#64;{{ instanceHost }}</div>
</div>
<div *ngIf="formErrors.name" class="form-error" role="alert">{{ formErrors.name }}</div>
</div>
<div class="form-group">
<label i18n for="displayName">Display name</label>
<input type="text" id="displayName" class="form-control" formControlName="displayName" [ngClass]="{ 'input-error': formErrors.displayName }">
<div *ngIf="formErrors.displayName" class="form-error" role="alert">
{{ formErrors.displayName }}
</div>
</div>
<div class="form-group markdown-block">
<label i18n for="description">Description</label>
<my-help helpType="markdownText" supportRelMe="true"></my-help>
<my-markdown-textarea
inputId="description"
formControlName="description"
markdownType="enhanced"
[formError]="formErrors.description"
withEmoji="true"
withHtml="true"
></my-markdown-textarea>
</div>
<div class="form-group markdown-block">
<label i18n for="support">Support</label>
<div class="form-group-description" i18n>
This is text that helps people visiting your channel page <strong>understand how to support you</strong>. You can use <my-markdown-hint
helpType="markdownText"
>Markdown Language</my-markdown-hint>, including <strong>links to your fundraising tools</strong>.
</div>
<my-markdown-textarea
inputId="support"
formControlName="support"
markdownType="enhanced"
[formError]="formErrors.support"
withEmoji="true"
withHtml="true"
></my-markdown-textarea>
</div>
<div class="form-group" *ngIf="isBulkUpdateVideosDisplayed()">
<my-peertube-checkbox
inputName="bulkVideosSupportUpdate"
formControlName="bulkVideosSupportUpdate"
i18n-labelText
labelText="Overwrite support field of all videos of this channel"
></my-peertube-checkbox>
</div>
</div>
</div>
<div class="pt-two-cols">
<div class="title-col">
<h3 i18n>VIDEOS</h3>
</div>
<div class="content-col">
<div class="form-group">
<label i18n for="playerTheme">Player Theme</label>
<my-select-player-theme formControlName="playerTheme" inputId="playerTheme" mode="channel"> </my-select-player-theme>
</div>
</div>
</div>
</form>

View File

@@ -0,0 +1,25 @@
@use "_variables" as *;
@use "_mixins" as *;
@use "_form-mixins" as *;
my-actor-banner-edit {
max-width: 500px;
}
input[type="text"] {
@include peertube-input-text(340px);
}
input[type="submit"] {
@include margin-left(auto);
}
.markdown-block {
max-width: 500px;
}
my-select-player-theme {
display: block;
@include responsive-width(340px);
}

Some files were not shown because too many files have changed in this diff Show More