mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-06-08 00:39:25 +01:00
Merge remote-tracking branch 'upstream/master' into search-rebased
This commit is contained in:
@@ -33,7 +33,7 @@
|
||||
"libfontconfig1"
|
||||
]
|
||||
},
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:2": {
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:3": {
|
||||
"dockerDashComposeVersion": "v2"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/github-cli:1": {},
|
||||
|
||||
@@ -15,7 +15,7 @@ sudo apt-get install software-properties-common -y
|
||||
sudo add-apt-repository universe -y
|
||||
|
||||
sudo mkdir -p /etc/apt/keyrings
|
||||
curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/jellyfin.gpg
|
||||
curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | sudo gpg --batch --yes --dearmor -o /etc/apt/keyrings/jellyfin.gpg
|
||||
export VERSION_OS="$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release )"
|
||||
export VERSION_CODENAME="$( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release )"
|
||||
export DPKG_ARCHITECTURE="$( dpkg --print-architecture )"
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/issue report.yml
vendored
1
.github/ISSUE_TEMPLATE/issue report.yml
vendored
@@ -87,6 +87,7 @@ body:
|
||||
label: Jellyfin Server version
|
||||
description: What version of Jellyfin are you using?
|
||||
options:
|
||||
- 10.11.9
|
||||
- 10.11.8
|
||||
- 10.11.7
|
||||
- 10.11.6
|
||||
|
||||
6
.github/workflows/ci-codeql-analysis.yml
vendored
6
.github/workflows/ci-codeql-analysis.yml
vendored
@@ -32,13 +32,13 @@ jobs:
|
||||
dotnet-version: '10.0.x'
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
queries: +security-extended
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
uses: github/codeql-action/autobuild@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
||||
|
||||
25
.github/workflows/ci-format.yml
vendored
Normal file
25
.github/workflows/ci-format.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Format
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
# Run formatter against the forked branch, but
|
||||
# do not allow access to secrets
|
||||
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflows-in-forked-repositories
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
SDK_VERSION: "10.0.x"
|
||||
|
||||
jobs:
|
||||
format-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
|
||||
with:
|
||||
dotnet-version: ${{ env.SDK_VERSION }}
|
||||
|
||||
- name: Run DotNet Format
|
||||
run: dotnet format --verify-no-changes --verbosity minimal
|
||||
2
.github/workflows/issue-stale.yml
vendored
2
.github/workflows/issue-stale.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ contains(github.repository, 'jellyfin/') }}
|
||||
steps:
|
||||
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
|
||||
- uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
|
||||
with:
|
||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
ascending: true
|
||||
|
||||
2
.github/workflows/pull-request-conflict.yml
vendored
2
.github/workflows/pull-request-conflict.yml
vendored
@@ -4,7 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
issue_comment:
|
||||
|
||||
permissions: {}
|
||||
|
||||
2
.github/workflows/pull-request-stale.yaml
vendored
2
.github/workflows/pull-request-stale.yaml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ contains(github.repository, 'jellyfin/') }}
|
||||
steps:
|
||||
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
|
||||
- uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
|
||||
with:
|
||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
ascending: true
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -19,6 +19,7 @@ local.properties
|
||||
.classpath
|
||||
.settings/
|
||||
.loadpath
|
||||
*.lscache
|
||||
|
||||
# External tool builders
|
||||
.externalToolBuilders/
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
<PackageVersion Include="AutoFixture.Xunit3" Version="4.19.0" />
|
||||
<PackageVersion Include="AutoFixture" Version="4.18.1" />
|
||||
<PackageVersion Include="BDInfo" Version="0.8.0" />
|
||||
<PackageVersion Include="BitFaster.Caching" Version="2.5.4" />
|
||||
<PackageVersion Include="BitFaster.Caching" Version="2.6.0" />
|
||||
<PackageVersion Include="BlurHashSharp.SkiaSharp" Version="1.4.0-pre.1" />
|
||||
<PackageVersion Include="BlurHashSharp" Version="1.4.0-pre.1" />
|
||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageVersion Include="coverlet.collector" Version="10.0.0" />
|
||||
<PackageVersion Include="coverlet.collector" Version="10.0.1" />
|
||||
<PackageVersion Include="Diacritics" Version="4.1.8" />
|
||||
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
|
||||
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
|
||||
@@ -74,7 +74,7 @@
|
||||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="[3.116.1]" />
|
||||
<PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
|
||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
|
||||
<PackageVersion Include="Svg.Skia" Version="3.4.1" />
|
||||
<PackageVersion Include="Svg.Skia" Version="3.7.0" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="10.1.7" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="10.1.7" />
|
||||
<PackageVersion Include="System.Text.Json" Version="10.0.8" />
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace Emby.Naming.ExternalFiles
|
||||
|
||||
if (lastSeparator == -1)
|
||||
{
|
||||
break;
|
||||
break;
|
||||
}
|
||||
|
||||
string currentSlice = languageString[lastSeparator..];
|
||||
|
||||
@@ -132,7 +132,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||
}
|
||||
else
|
||||
{
|
||||
_configurationFactories = [.._configurationFactories, factory];
|
||||
_configurationFactories = [.. _configurationFactories, factory];
|
||||
}
|
||||
|
||||
_configurationStores = _configurationFactories
|
||||
|
||||
@@ -240,15 +240,15 @@ public class ChapterManager : IChapterManager
|
||||
public void SaveChapters(BaseItem item, IReadOnlyList<ChapterInfo> chapters)
|
||||
{
|
||||
if (!Supports(item))
|
||||
{
|
||||
_logger.LogWarning("Attempted to save chapters for unsupported item type {Type}: {Name} ({Id})", item.GetType().Name, item.Name, item.Id);
|
||||
return;
|
||||
}
|
||||
{
|
||||
_logger.LogWarning("Attempted to save chapters for unsupported item type {Type}: {Name} ({Id})", item.GetType().Name, item.Name, item.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove any chapters that are outside of the runtime of the item
|
||||
var validChapters = chapters.Where(c => c.StartPositionTicks < item.RunTimeTicks).ToList();
|
||||
_chapterRepository.SaveChapters(item.Id, validChapters);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ChapterInfo? GetChapter(Guid baseItemId, int index)
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace Emby.Server.Implementations.Library
|
||||
'[' => ']',
|
||||
'(' => ')',
|
||||
'{' => '}',
|
||||
_ => '\0'
|
||||
_ => '\0'
|
||||
};
|
||||
if (attributeCloser != '\0' && (str[attributeEnd] == '=' || str[attributeEnd] == '-'))
|
||||
{
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="namingOptions">The naming options.</param>
|
||||
public SeriesResolver(ILogger<SeriesResolver> logger, NamingOptions namingOptions)
|
||||
public SeriesResolver(ILogger<SeriesResolver> logger, NamingOptions namingOptions)
|
||||
{
|
||||
_logger = logger;
|
||||
_namingOptions = namingOptions;
|
||||
|
||||
@@ -107,5 +107,6 @@
|
||||
"TaskMoveTrickplayImagesDescription": "ينقل ملفات معاينات التنقل الحالية وفقاً لإعدادات المكتبة.",
|
||||
"CleanupUserDataTask": "مهمة تنظيف بيانات المستخدم",
|
||||
"CleanupUserDataTaskDescription": "ينظف جميع بيانات المستخدم (مثل حالة المشاهدة وحالة المفضلة وغيرها) للمحتوى الذي لم يعد موجوداً لمدة 90 يوماً على الأقل.",
|
||||
"Original": "فريد"
|
||||
"Original": "فريد",
|
||||
"LyricDownloadFailureFromForItem": "فشل تحميل الكلمات من {0} إلى {1}"
|
||||
}
|
||||
|
||||
63
Emby.Server.Implementations/Localization/Core/br.json
Normal file
63
Emby.Server.Implementations/Localization/Core/br.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"Artists": "Arzourien",
|
||||
"AuthenticationSucceededWithUserName": "{0} kennasket gant berzh",
|
||||
"Books": "Levrioù",
|
||||
"ChapterNameValue": "Pennad {0}",
|
||||
"Collections": "Dastumadegoù",
|
||||
"Default": "Dre ziouer",
|
||||
"External": "Diavaez",
|
||||
"FailedLoginAttemptWithUserName": "Kennaskañ c'hwitet gant {0}",
|
||||
"Favorites": "Sinedoù",
|
||||
"Folders": "Teuliadoù",
|
||||
"Forced": "Rediet",
|
||||
"Genres": "Doareoù",
|
||||
"HeaderContinueWatching": "Kenderc'hel da sellet",
|
||||
"HeaderFavoriteEpisodes": "Rannoù Karetañ",
|
||||
"HeaderFavoriteShows": "Heuliadennoù Karetañ",
|
||||
"HeaderLiveTV": "TV war-eeun",
|
||||
"HeaderNextUp": "Da c'houde",
|
||||
"HearingImpaired": "Tud fall o C'hleved",
|
||||
"HomeVideos": "Videoioù Personel",
|
||||
"Inherit": "Hêrezhiñ",
|
||||
"LabelIpAddressValue": "Chomlec'h IP : {0}",
|
||||
"LabelRunningTimeValue": "Padelezh : {0}",
|
||||
"Latest": "Diwezhañ",
|
||||
"AppDeviceValues": "Arload : {0}, Trobarzhell : {1}",
|
||||
"LyricDownloadFailureFromForItem": "C'hwitet eo pellgargañ ar c'homzoù eus {0} evit {1}",
|
||||
"MixedContent": "Danvez mesket",
|
||||
"Movies": "Filmoù",
|
||||
"Music": "Sonerezh",
|
||||
"MusicVideos": "Videoioù Sonerezh",
|
||||
"NameInstallFailed": "{0} c'hwitet war ar staliadur",
|
||||
"NameSeasonNumber": "Koulzad {0}",
|
||||
"NameSeasonUnknown": "Koulzad Dianav",
|
||||
"NewVersionIsAvailable": "Ur stumm Servijer Jellyfin nevez a c'haller pellgargañ.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Hizivadur an arload zo da gaout",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Hizivadur an arload staliet",
|
||||
"NotificationOptionAudioPlayback": "Lenn aodio lañset",
|
||||
"NotificationOptionAudioPlaybackStopped": "Lenn aodio ehanet",
|
||||
"Original": "Orin",
|
||||
"Photos": "Fotoioù",
|
||||
"Shows": "Heuliadennoù",
|
||||
"Undefined": "Dianav",
|
||||
"TasksMaintenanceCategory": "Trezalc’h",
|
||||
"TasksLibraryCategory": "Levraoueg",
|
||||
"TasksApplicationCategory": "Arload",
|
||||
"NotificationOptionInstallationFailed": "C'hwitet war staliañ",
|
||||
"NotificationOptionPluginError": "Fazi Askouezh",
|
||||
"NotificationOptionPluginInstalled": "Askouezh staliet",
|
||||
"NotificationOptionPluginUninstalled": "Askouezh distaliet",
|
||||
"ScheduledTaskFailedWithName": "c'hwitadenn war {0}",
|
||||
"TvShows": "Heuliadennoù TV",
|
||||
"VersionNumber": "Stumm {0}",
|
||||
"TasksChannelsCategory": "Chadennoù enlinenn",
|
||||
"TaskAudioNormalization": "Normalizadur an aodio",
|
||||
"TaskRefreshPeople": "Freskaat ar gomedianed",
|
||||
"TaskUpdatePlugins": "Hizivaat an askouezhioù",
|
||||
"TaskRefreshChannels": "Freskaat ar chadennoù",
|
||||
"TaskOptimizeDatabase": "Gwellekaat an diaz roadennoù",
|
||||
"TaskKeyframeExtractor": "Eztenner skeudennoù-alc'hwez",
|
||||
"NotificationOptionCameraImageUploaded": "Karget eo skeudenn ar benveg",
|
||||
"NotificationOptionNewLibraryContent": "Danvez nevez ouzhpennet",
|
||||
"NotificationOptionPluginUpdateInstalled": "Staliet eo hizivadur an askouezh"
|
||||
}
|
||||
@@ -107,5 +107,6 @@
|
||||
"TaskMoveTrickplayImagesDescription": "Mou els fitxers existents d'imatges de previsualització segons la configuració de la mediateca.",
|
||||
"CleanupUserDataTaskDescription": "Neteja totes les dades d'usuari (estat de la visualització, estat dels preferits, etc.) del contingut multimèdia que no ha estat present durant almenys 90 dies.",
|
||||
"CleanupUserDataTask": "Tasca de neteja de dades d'usuari",
|
||||
"Original": "Original"
|
||||
"Original": "Original",
|
||||
"LyricDownloadFailureFromForItem": "No s'han pogut descarregar les lletres des de {0} per a {1}"
|
||||
}
|
||||
|
||||
@@ -107,5 +107,6 @@
|
||||
"TaskMoveTrickplayImagesDescription": "Přesune existující soubory Trickplay podle nastavení knihovny.",
|
||||
"CleanupUserDataTaskDescription": "Odstraní všechna uživatelská data (stav zhlédnutí, oblíbené atd.) z médií, které již neexistují více než 90 dní.",
|
||||
"CleanupUserDataTask": "Pročistit uživatelská data",
|
||||
"Original": "Originál"
|
||||
"Original": "Originál",
|
||||
"LyricDownloadFailureFromForItem": "Nepodařilo se stáhnout texty pro {1} ze služby {0}"
|
||||
}
|
||||
|
||||
@@ -106,5 +106,7 @@
|
||||
"TaskMoveTrickplayImagesDescription": "Flyt eksisterende trickplay-billeder jævnfør biblioteksindstillinger.",
|
||||
"TaskExtractMediaSegmentsDescription": "Udtrækker eller henter mediesegmenter fra plugins som understøtter MediaSegment.",
|
||||
"CleanupUserDataTask": "Brugerdata oprydningsopgave",
|
||||
"CleanupUserDataTaskDescription": "Rydder alle brugerdata (eks. visning- og favoritstatus) fra medier, der har været utilgængelige i mindst 90 dage."
|
||||
"CleanupUserDataTaskDescription": "Rydder alle brugerdata (eks. visning- og favoritstatus) fra medier, der har været utilgængelige i mindst 90 dage.",
|
||||
"LyricDownloadFailureFromForItem": "Sangtekster kunne ikke downloades fra {0} til {1}",
|
||||
"Original": "Original"
|
||||
}
|
||||
|
||||
@@ -107,5 +107,6 @@
|
||||
"TaskMoveTrickplayImages": "Migrar la ubicación de la imagen de Trickplay",
|
||||
"CleanupUserDataTask": "Tarea de limpieza de datos del usuario",
|
||||
"CleanupUserDataTaskDescription": "Limpia todos los datos del usuario (estado de visualización, favoritos, etc.) de los medios que ya no están disponibles desde hace al menos 90 días.",
|
||||
"Original": "Original"
|
||||
"Original": "Original",
|
||||
"LyricDownloadFailureFromForItem": "No se pudieron descargar las letras desde {0} para {1}."
|
||||
}
|
||||
|
||||
@@ -106,5 +106,7 @@
|
||||
"TaskExtractMediaSegmentsDescription": "Eraldab või võtab meedialõigud MediaSegment'i toega pluginatest.",
|
||||
"TaskMoveTrickplayImages": "Muuda trickplay piltide asukoht",
|
||||
"CleanupUserDataTask": "Puhasta kasutajaandmed",
|
||||
"CleanupUserDataTaskDescription": "Puhastab kõik kasutajaandmed (vaatamise olek, lemmikute olek jne) meediast, mida pole enam vähemalt 90 päeva saadaval olnud."
|
||||
"CleanupUserDataTaskDescription": "Puhastab kõik kasutajaandmed (vaatamise olek, lemmikute olek jne) meediast, mida pole enam vähemalt 90 päeva saadaval olnud.",
|
||||
"LyricDownloadFailureFromForItem": "Laulusõnade hankimine teenusest {0} loole {1} nurjus",
|
||||
"Original": "Algne"
|
||||
}
|
||||
|
||||
@@ -106,5 +106,7 @@
|
||||
"TaskMoveTrickplayImages": "Siirrä Trickplay-kuvien sijainti",
|
||||
"TaskMoveTrickplayImagesDescription": "Siirtää olemassa olevia trickplay-tiedostoja kirjaston asetusten mukaan.",
|
||||
"CleanupUserDataTask": "Käyttäjätietojen puhdistustehtävä",
|
||||
"CleanupUserDataTaskDescription": "Puhdistaa kaikki käyttäjätiedot (katselutila, suosikit ym.) medioista, joita ei ole ollut saatavilla yli 90 päivään."
|
||||
"CleanupUserDataTaskDescription": "Puhdistaa kaikki käyttäjätiedot (katselutila, suosikit ym.) medioista, joita ei ole ollut saatavilla yli 90 päivään.",
|
||||
"LyricDownloadFailureFromForItem": "Sanoitusten lataus kohteesta {0} kappaleelle {1} epäonnistui",
|
||||
"Original": "Alkuperäinen"
|
||||
}
|
||||
|
||||
@@ -107,5 +107,6 @@
|
||||
"TaskDownloadMissingSubtitles": "Íosluchtaigh fotheidil ar iarraidh",
|
||||
"CleanupUserDataTask": "Tasc glantacháin sonraí úsáideora",
|
||||
"CleanupUserDataTaskDescription": "Glanann sé gach sonraí úsáideora (stádas faire, stádas is fearr leat srl.) ó mheáin nach bhfuil i láthair a thuilleadh ar feadh 90 lá ar a laghad.",
|
||||
"Original": "Bunaidh"
|
||||
"Original": "Bunaidh",
|
||||
"LyricDownloadFailureFromForItem": "Theip ar liricí a íoslódáil ó {0} do {1}"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"AppDeviceValues": "Program: {0}, Eszköz: {1}",
|
||||
"AppDeviceValues": "alkalmazás: {0}, eszköz: {1}",
|
||||
"Artists": "Előadók",
|
||||
"AuthenticationSucceededWithUserName": "{0} sikeresen hitelesítve",
|
||||
"Books": "Könyvek",
|
||||
@@ -107,5 +107,6 @@
|
||||
"TaskExtractMediaSegmentsDescription": "Kinyeri vagy megszerzi a médiaszegmenseket a MediaSegment támogatással rendelkező bővítményekből.",
|
||||
"CleanupUserDataTaskDescription": "Legalább 90 napja nem elérhető médiákhoz kapcsolódó összes felhasználói adat (pl. megtekintési állapot, kedvencek) törlése.",
|
||||
"CleanupUserDataTask": "Felhasználói adatok tisztítása feladat",
|
||||
"Original": "Eredeti"
|
||||
"Original": "Eredeti",
|
||||
"LyricDownloadFailureFromForItem": "Dalszöveg letöltése {0}-tól {1}-hez sikertelen"
|
||||
}
|
||||
|
||||
@@ -107,5 +107,6 @@
|
||||
"TaskExtractMediaSegments": "Scansiona Segmento Media",
|
||||
"CleanupUserDataTask": "Task di pulizia dei dati utente",
|
||||
"CleanupUserDataTaskDescription": "Pulisce tutti i dati utente (stato di visione, status preferiti, ecc.) dai contenuti non più presenti da almeno 90 giorni.",
|
||||
"Original": "Originale"
|
||||
"Original": "Originale",
|
||||
"LyricDownloadFailureFromForItem": "Scaricamento dei testi non riuscito da {0} per {1}"
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"Genres": "ჟანრები",
|
||||
"TasksApplicationCategory": "აპლიკაცია",
|
||||
"AppDeviceValues": "აპლიკაცია: {0}, მოწყობილობა: {1}",
|
||||
"Artists": "არტისტი",
|
||||
"Artists": "შემსრულებლები",
|
||||
"AuthenticationSucceededWithUserName": "{0} -ის ავთენტიკაცია წარმატებულია",
|
||||
"Books": "წიგნები",
|
||||
"Forced": "იძულებითი",
|
||||
@@ -22,7 +22,7 @@
|
||||
"HearingImpaired": "სმენადაქვეითებული",
|
||||
"LabelRunningTimeValue": "ხანგრძლივობა: {0}",
|
||||
"MixedContent": "შერეული შემცველობა",
|
||||
"MusicVideos": "მუსიკალური ვიდეოები",
|
||||
"MusicVideos": "მუსიკის ვიდეოები",
|
||||
"NotificationOptionInstallationFailed": "დაყენების შეცდომა",
|
||||
"NotificationOptionApplicationUpdateInstalled": "აპლიკაციის განახლება დაყენებულია",
|
||||
"NotificationOptionAudioPlayback": "აუდიოს დაკვრა დაწყებულია",
|
||||
@@ -33,22 +33,22 @@
|
||||
"TasksChannelsCategory": "ინტერნეტ-არხები",
|
||||
"TaskRefreshChannelsDescription": "ინტერნეტ-არხის ინფორმაციის განახლება.",
|
||||
"Collections": "კოლექციები",
|
||||
"Default": "ნაგულისხმები",
|
||||
"Default": "ნაგულისხმევი",
|
||||
"Favorites": "რჩეულები",
|
||||
"Folders": "საქაღალდეები",
|
||||
"HeaderFavoriteShows": "რჩეული სერიალები",
|
||||
"HeaderLiveTV": "ლაივ ტელევიზია",
|
||||
"HeaderLiveTV": "ცოცხალი ტელევიზია",
|
||||
"HeaderNextUp": "შემდეგი",
|
||||
"HomeVideos": "სახლის ვიდეოები",
|
||||
"NameSeasonNumber": "სეზონი {0}",
|
||||
"NameSeasonUnknown": "სეზონი უცნობია",
|
||||
"NotificationOptionPluginError": "მოდულის შეცდომა",
|
||||
"NotificationOptionPluginInstalled": "მოდული დაყენებულია",
|
||||
"NotificationOptionPluginError": "დამატების შეცდომა",
|
||||
"NotificationOptionPluginInstalled": "დამატება დაყენებულია",
|
||||
"NotificationOptionPluginUninstalled": "მოდული წაიშალა",
|
||||
"ScheduledTaskFailedWithName": "{0} ვერ შესრულდა",
|
||||
"ScheduledTaskFailedWithName": "{0} ჩავარდა",
|
||||
"TvShows": "სატელევიზიო სერიალები",
|
||||
"TaskRefreshPeople": "ხალხის განახლება",
|
||||
"TaskUpdatePlugins": "მოდულების განახლება",
|
||||
"TaskUpdatePlugins": "დამატებების განახლება",
|
||||
"TaskRefreshChannels": "არხების განახლება",
|
||||
"TaskOptimizeDatabase": "მონაცემთა ბაზის ოპტიმიზაცია",
|
||||
"TaskKeyframeExtractor": "საკვანძო კადრის გამომღები",
|
||||
@@ -106,5 +106,7 @@
|
||||
"TaskMoveTrickplayImages": "Trickplay სურათების მიგრაცია",
|
||||
"TaskMoveTrickplayImagesDescription": "გადააქვს trickplay ფაილები ბიბლიოთეკის პარამეტრებზე დაყრდნობით.",
|
||||
"CleanupUserDataTask": "მომხმარებლების მონაცემების გასუფთავება",
|
||||
"CleanupUserDataTaskDescription": "ასუფთავებს მომხმარებლების მონაცემებს (ყურების სტატუსი, ფავორიტები ანდ ა.შ) მედია ელემენტებისთვის რომლების 90 დღეზე მეტია აღარ არსებობენ."
|
||||
"CleanupUserDataTaskDescription": "ასუფთავებს მომხმარებლების მონაცემებს (ყურების სტატუსი, ფავორიტები ანდ ა.შ) მედია ელემენტებისთვის რომლების 90 დღეზე მეტია აღარ არსებობენ.",
|
||||
"LyricDownloadFailureFromForItem": "{1}-ისთვის {0}-დან ლირიკის გადმოწერა ჩავარდა",
|
||||
"Original": "ორიგინალი"
|
||||
}
|
||||
|
||||
@@ -2,5 +2,14 @@
|
||||
"AppDeviceValues": "Taupānga: {0}, Pūrere: {1}",
|
||||
"Artists": "Kaiwaiata",
|
||||
"AuthenticationSucceededWithUserName": "{0} has been successfully authenticated",
|
||||
"Books": "Ngā pukapuka"
|
||||
"Books": "Ngā pukapuka",
|
||||
"Default": "Taunoa",
|
||||
"Collections": "Kohinga",
|
||||
"External": "Waho",
|
||||
"Folders": "Kōpaki",
|
||||
"Forced": "Kaha",
|
||||
"Music": "Waiata",
|
||||
"Movies": "Kiriata",
|
||||
"Latest": "Hou",
|
||||
"Inherit": "Riro"
|
||||
}
|
||||
|
||||
@@ -98,5 +98,10 @@
|
||||
"TaskAudioNormalization": "സാധാരണ ശബ്ദ നിലയിലെത്തിലെത്തിക്കുക",
|
||||
"TaskAudioNormalizationDescription": "സാധാരണ ശബ്ദ നിലയിലെത്തിലെത്തിക്കുന്ന ഡാറ്റയ്ക്കായി ഫയലുകൾ സ്കാൻ ചെയ്യുക.",
|
||||
"TaskRefreshTrickplayImages": "ട്രിക്ക് പ്ലേ ചിത്രങ്ങൾ സൃഷ്ടിക്കുക",
|
||||
"TaskRefreshTrickplayImagesDescription": "പ്രവർത്തനക്ഷമമാക്കിയ ലൈബ്രറികളിൽ വീഡിയോകൾക്കായി ട്രിക്ക്പ്ലേ പ്രിവ്യൂകൾ സൃഷ്ടിക്കുന്നു."
|
||||
"TaskRefreshTrickplayImagesDescription": "പ്രവർത്തനക്ഷമമാക്കിയ ലൈബ്രറികളിൽ വീഡിയോകൾക്കായി ട്രിക്ക്പ്ലേ പ്രിവ്യൂകൾ സൃഷ്ടിക്കുന്നു.",
|
||||
"Original": "ഓറിജിനൽ",
|
||||
"TaskDownloadMissingLyrics": "ഇല്ലാത്ത വരികൾ ഡൗൺലോഡ് ചെയ്യുക",
|
||||
"TaskDownloadMissingLyricsDescription": "പാട്ടുകളുടെ വരികൾ ഡൗൺലോഡ് ചെയ്യുന്നു",
|
||||
"TaskExtractMediaSegments": "മീഡിയ സെഗ്മെന്റ് സ്കാൻ",
|
||||
"TaskExtractMediaSegmentsDescription": "മീഡിയസെഗ്മെന്റ് പ്രാപ്തമാക്കിയ പ്ലഗിനുകളിൽ നിന്ന് മീഡിയ സെഗ്മെന്റുകൾ എക്സ്ട്രാക്റ്റുചെയ്യുന്നു അല്ലെങ്കിൽ നേടുന്നു."
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"FailedLoginAttemptWithUserName": "Mislukte aanmeldpoging van {0}",
|
||||
"Favorites": "Favorieten",
|
||||
"Folders": "Mappen",
|
||||
"HeaderContinueWatching": "Verderkijken",
|
||||
"HeaderContinueWatching": "Verder kijken",
|
||||
"HeaderFavoriteEpisodes": "Favoriete afleveringen",
|
||||
"HeaderFavoriteShows": "Favoriete series",
|
||||
"HeaderLiveTV": "Live-tv",
|
||||
|
||||
@@ -106,5 +106,7 @@
|
||||
"TaskExtractMediaSegmentsDescription": "Extrai ou obtém segmentos de mídia de plug-ins habilitados para MediaSegment.",
|
||||
"TaskMoveTrickplayImages": "Migrar o local da imagem do Trickplay",
|
||||
"CleanupUserDataTask": "Tarefa de limpeza de dados do usuário",
|
||||
"CleanupUserDataTaskDescription": "Limpa todos os dados do usuário (estado de visualização, status de favorito, etc.) de mídias que não estão presentes por pelo menos 90 dias."
|
||||
"CleanupUserDataTaskDescription": "Limpa todos os dados do usuário (estado de visualização, status de favorito, etc.) de mídias que não estão presentes por pelo menos 90 dias.",
|
||||
"LyricDownloadFailureFromForItem": "Download das Letras falharam em {0} para o item {1}",
|
||||
"Original": "Original"
|
||||
}
|
||||
|
||||
@@ -107,5 +107,6 @@
|
||||
"TaskMoveTrickplayImages": "Migrar a localização da imagem do Trickplay",
|
||||
"CleanupUserDataTask": "Task de limpeza de dados do usuário",
|
||||
"CleanupUserDataTaskDescription": "Remove todos os dados do usuário (progresso, favoritos etc) de mídias que não estão presentes há pelo menos 90 dias.",
|
||||
"Original": "Original"
|
||||
"Original": "Original",
|
||||
"LyricDownloadFailureFromForItem": "Erro ao descarregar letras de {0} para {1}"
|
||||
}
|
||||
|
||||
@@ -106,5 +106,7 @@
|
||||
"TaskDownloadMissingLyrics": "Descarcă versurile lipsă",
|
||||
"TaskDownloadMissingLyricsDescription": "Descarcă versuri pentru melodii",
|
||||
"CleanupUserDataTask": "Sarcina de curatare a datelor utilizatorului",
|
||||
"CleanupUserDataTaskDescription": "Sterge toate datele utilizatorului (starea vizionarii, starea favoritelor etc.) de pe suporturile media care nu mai sunt prezente timp de cel puțin 90 de zile."
|
||||
"CleanupUserDataTaskDescription": "Sterge toate datele utilizatorului (starea vizionarii, starea favoritelor etc.) de pe suporturile media care nu mai sunt prezente timp de cel puțin 90 de zile.",
|
||||
"LyricDownloadFailureFromForItem": "Versurile nu au putut fi descărcate din {0} pentru {1}",
|
||||
"Original": "Original"
|
||||
}
|
||||
|
||||
@@ -107,5 +107,6 @@
|
||||
"TaskMoveTrickplayImagesDescription": "Перемещает существующие файлы trickplay в соответствии с настройками медиатеки.",
|
||||
"CleanupUserDataTask": "Задача очистки пользовательских данных",
|
||||
"CleanupUserDataTaskDescription": "Очищает все пользовательские данные (состояние просмотра, статус избранного и т.д.) с медиа, отсутствующих по меньшей мере в течение 90 дней.",
|
||||
"Original": "Оригинальный"
|
||||
"Original": "Оригинальный",
|
||||
"LyricDownloadFailureFromForItem": "Не получилось скачать текст песни с {0} для {1}"
|
||||
}
|
||||
|
||||
@@ -107,5 +107,6 @@
|
||||
"TaskMoveTrickplayImagesDescription": "Flyttar befintliga trickplay-filer enligt bibliotekets inställningar.",
|
||||
"CleanupUserDataTaskDescription": "Tar bort all användardata (såsom vad du sett, favoriter med mera) för media som inte funnits på enheten på minst 90 dagar.",
|
||||
"CleanupUserDataTask": "Uppgift för rensning av användardata",
|
||||
"Original": "Original"
|
||||
"Original": "Original",
|
||||
"LyricDownloadFailureFromForItem": "Misslyckades att ladda ner låttexter från {0} för {1}"
|
||||
}
|
||||
|
||||
@@ -82,5 +82,7 @@
|
||||
"TaskRefreshChapterImages": "Sahnadan tasvirini chiqarish",
|
||||
"TaskRefreshChapterImagesDescription": "Sahnalarni o'z ichiga olgan videolar uchun eskizlarni yaratadi.",
|
||||
"TaskRefreshLibrary": "Media kutubxonangizni skanerlash",
|
||||
"TaskCleanLogsDescription": "{0} kundan eski log fayllarni o'chiradi."
|
||||
"TaskCleanLogsDescription": "{0} kundan eski log fayllarni o'chiradi.",
|
||||
"Original": "Original",
|
||||
"LyricDownloadFailureFromForItem": "{0} dan {1} gacha qo'shiq matninin yuklab olishda xatolik ketdi"
|
||||
}
|
||||
|
||||
@@ -106,5 +106,7 @@
|
||||
"TaskExtractMediaSegmentsDescription": "从支持 MediaSegment 的插件中提取或获取媒体分段。",
|
||||
"TaskMoveTrickplayImagesDescription": "根据媒体库设置移动现有的进度条预览图文件。",
|
||||
"CleanupUserDataTask": "用户数据清理任务",
|
||||
"CleanupUserDataTaskDescription": "清理已被删除超过90天的媒体中的所有用户数据(观看状态、收藏夹状态等)。"
|
||||
"CleanupUserDataTaskDescription": "清理已被删除超过90天的媒体中的所有用户数据(观看状态、收藏夹状态等)。",
|
||||
"LyricDownloadFailureFromForItem": "无法从 {0} 下载 {1} 的歌词",
|
||||
"Original": "原始"
|
||||
}
|
||||
|
||||
@@ -318,13 +318,13 @@ namespace Emby.Server.Implementations.Localization
|
||||
// A lot of countries don't explicitly have a separate rating for adult content
|
||||
if (ratings.All(x => x.RatingScore?.Score != 1000))
|
||||
{
|
||||
ratings.Add(new ParentalRating("XXX", new(1000, null)));
|
||||
ratings.Add(new ParentalRating("XXX", new(1000, null)));
|
||||
}
|
||||
|
||||
// A lot of countries don't explicitly have a separate rating for banned content
|
||||
if (ratings.All(x => x.RatingScore?.Score != 1001))
|
||||
{
|
||||
ratings.Add(new ParentalRating("Banned", new(1001, null)));
|
||||
ratings.Add(new ParentalRating("Banned", new(1001, null)));
|
||||
}
|
||||
|
||||
return [.. ratings.OrderBy(r => r.RatingScore?.Score).ThenBy(r => r.RatingScore?.SubScore)];
|
||||
|
||||
@@ -564,7 +564,8 @@ namespace Emby.Server.Implementations.Plugins
|
||||
Id = instance.Id,
|
||||
Status = PluginStatus.Active,
|
||||
Name = instance.Name,
|
||||
Version = instance.Version.ToString()
|
||||
Version = instance.Version.ToString(),
|
||||
ImageResourceName = (instance as IHasEmbeddedImage)?.ImageResourceName
|
||||
})
|
||||
{
|
||||
Instance = instance
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
@@ -386,7 +386,7 @@ namespace Emby.Server.Implementations.Session
|
||||
{
|
||||
if (session is null)
|
||||
{
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(info.MediaSourceId))
|
||||
|
||||
@@ -89,11 +89,11 @@ public class SystemManager : ISystemManager
|
||||
.GetVirtualFolders()
|
||||
.Where(e => !string.IsNullOrWhiteSpace(e.ItemId)) // this should not be null but for some users it is.
|
||||
.Select(e => new LibraryStorageInfo()
|
||||
{
|
||||
Id = Guid.Parse(e.ItemId),
|
||||
Name = e.Name,
|
||||
Folders = e.Locations.Select(f => StorageHelper.GetFreeSpaceOf(f)).ToArray()
|
||||
});
|
||||
{
|
||||
Id = Guid.Parse(e.ItemId),
|
||||
Name = e.Name,
|
||||
Folders = e.Locations.Select(f => StorageHelper.GetFreeSpaceOf(f)).ToArray()
|
||||
});
|
||||
|
||||
return new SystemStorageInfo()
|
||||
{
|
||||
|
||||
@@ -91,18 +91,18 @@ public class AudioController : BaseJellyfinApiController
|
||||
[ProducesAudioFile]
|
||||
public async Task<ActionResult> GetAudioStream(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? container,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? container,
|
||||
[FromQuery] bool? @static,
|
||||
[FromQuery] string? @params,
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
@@ -112,7 +112,7 @@ public class AudioController : BaseJellyfinApiController
|
||||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
||||
[FromQuery][RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
@@ -131,8 +131,8 @@ public class AudioController : BaseJellyfinApiController
|
||||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
@@ -255,18 +255,18 @@ public class AudioController : BaseJellyfinApiController
|
||||
[ProducesAudioFile]
|
||||
public async Task<ActionResult> GetAudioStreamByContainer(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string container,
|
||||
[FromRoute, Required][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string container,
|
||||
[FromQuery] bool? @static,
|
||||
[FromQuery] string? @params,
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
@@ -276,7 +276,7 @@ public class AudioController : BaseJellyfinApiController
|
||||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
||||
[FromQuery][RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
@@ -295,8 +295,8 @@ public class AudioController : BaseJellyfinApiController
|
||||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Attributes;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Dtos;
|
||||
using Jellyfin.Data.Queries;
|
||||
using MediaBrowser.Common.Api;
|
||||
@@ -112,28 +116,31 @@ public class DevicesController : BaseJellyfinApiController
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a device.
|
||||
/// Deletes devices.
|
||||
/// </summary>
|
||||
/// <param name="id">Device Id.</param>
|
||||
/// <param name="id">Device Ids.</param>
|
||||
/// <response code="204">Device deleted.</response>
|
||||
/// <response code="404">Device not found.</response>
|
||||
/// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the device could not be found.</returns>
|
||||
/// <response code="400">A requested device is invalid.</response>
|
||||
/// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="BadRequestResult"/> if a requested device is invalid.</returns>
|
||||
[HttpDelete]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> DeleteDevice([FromQuery, Required] string id)
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult> DeleteDevice([FromQuery] string[] id)
|
||||
{
|
||||
var existingDevice = _deviceManager.GetDevice(id);
|
||||
if (existingDevice is null)
|
||||
var devices = id.Select(_deviceManager.GetDevice).ToArray();
|
||||
if (devices.Any(f => f is null))
|
||||
{
|
||||
return NotFound();
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
var sessions = _deviceManager.GetDevices(new DeviceQuery { DeviceId = id });
|
||||
|
||||
foreach (var session in sessions.Items)
|
||||
foreach (var device in devices)
|
||||
{
|
||||
await _sessionManager.Logout(session).ConfigureAwait(false);
|
||||
var sessions = _deviceManager.GetDevices(new DeviceQuery { DeviceId = device!.Id });
|
||||
|
||||
foreach (var session in sessions.Items)
|
||||
{
|
||||
await _sessionManager.Logout(session).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
|
||||
@@ -167,18 +167,18 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[ProducesPlaylistFile]
|
||||
public async Task<ActionResult> GetLiveHlsStream(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? container,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? container,
|
||||
[FromQuery] bool? @static,
|
||||
[FromQuery] string? @params,
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
@@ -188,7 +188,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
||||
[FromQuery][RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
@@ -207,8 +207,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
@@ -413,12 +413,12 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery, Required] string mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
@@ -428,7 +428,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
||||
[FromQuery][RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
@@ -449,8 +449,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
@@ -586,12 +586,12 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery, Required] string mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
@@ -602,7 +602,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
||||
[FromQuery][RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
@@ -621,8 +621,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
@@ -753,12 +753,12 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
@@ -768,7 +768,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
||||
[FromQuery][RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
@@ -789,8 +789,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
@@ -922,12 +922,12 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
@@ -938,7 +938,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
||||
[FromQuery][RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
@@ -957,8 +957,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
@@ -1092,7 +1092,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromRoute, Required] string playlistId,
|
||||
[FromRoute, Required] int segmentId,
|
||||
[FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string container,
|
||||
[FromRoute, Required][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string container,
|
||||
[FromQuery, Required] long runtimeTicks,
|
||||
[FromQuery, Required] long actualSegmentLengthTicks,
|
||||
[FromQuery] bool? @static,
|
||||
@@ -1100,12 +1100,12 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
@@ -1115,7 +1115,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
||||
[FromQuery][RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
@@ -1136,8 +1136,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
@@ -1274,7 +1274,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromRoute, Required] string playlistId,
|
||||
[FromRoute, Required] int segmentId,
|
||||
[FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string container,
|
||||
[FromRoute, Required][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string container,
|
||||
[FromQuery, Required] long runtimeTicks,
|
||||
[FromQuery, Required] long actualSegmentLengthTicks,
|
||||
[FromQuery] bool? @static,
|
||||
@@ -1282,12 +1282,12 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
@@ -1298,7 +1298,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
||||
[FromQuery][RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
@@ -1317,8 +1317,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
||||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
|
||||
@@ -15,6 +15,7 @@ using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Playlists;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
@@ -276,7 +277,7 @@ public class ItemsController : BaseJellyfinApiController
|
||||
&& user.GetPreference(PreferenceKind.AllowedTags).Length != 0
|
||||
&& !fields.Contains(ItemFields.Tags))
|
||||
{
|
||||
fields = [..fields, ItemFields.Tags];
|
||||
fields = [.. fields, ItemFields.Tags];
|
||||
}
|
||||
|
||||
var dtoOptions = new DtoOptions { Fields = fields }
|
||||
@@ -285,10 +286,21 @@ public class ItemsController : BaseJellyfinApiController
|
||||
var item = _libraryManager.GetParentItem(parentId, userId);
|
||||
QueryResult<BaseItem> result;
|
||||
|
||||
Guid[] linkedChildAncestorIds = [];
|
||||
if (includeItemTypes.Length == 1
|
||||
&& includeItemTypes[0] == BaseItemKind.BoxSet
|
||||
&& item is not BoxSet)
|
||||
&& (includeItemTypes[0] == BaseItemKind.BoxSet || includeItemTypes[0] == BaseItemKind.Playlist)
|
||||
&& item is not BoxSet
|
||||
&& item is not Playlist)
|
||||
{
|
||||
var itemCollectionType = item is IHasCollectionType hct ? hct.CollectionType : null;
|
||||
var targetCollectionType = includeItemTypes[0] == BaseItemKind.BoxSet
|
||||
? CollectionType.boxsets
|
||||
: CollectionType.playlists;
|
||||
if (parentId.HasValue && item is not UserRootFolder && itemCollectionType != targetCollectionType)
|
||||
{
|
||||
linkedChildAncestorIds = [parentId.Value];
|
||||
}
|
||||
|
||||
parentId = null;
|
||||
item = _libraryManager.GetUserRootFolder();
|
||||
}
|
||||
@@ -438,6 +450,7 @@ public class ItemsController : BaseJellyfinApiController
|
||||
MaxPremiereDate = maxPremiereDate?.ToUniversalTime(),
|
||||
AudioLanguages = audioLanguages,
|
||||
SubtitleLanguages = subtitleLanguages,
|
||||
LinkedChildAncestorIds = linkedChildAncestorIds,
|
||||
};
|
||||
|
||||
if (ids.Length != 0 || !string.IsNullOrWhiteSpace(searchTerm))
|
||||
|
||||
@@ -744,10 +744,12 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
/// <param name="programId">Program id.</param>
|
||||
/// <param name="userId">Optional. Attach user data.</param>
|
||||
/// <response code="200">Program returned.</response>
|
||||
/// <response code="404">Program not found.</response>
|
||||
/// <returns>An <see cref="OkResult"/> containing the livetv program.</returns>
|
||||
[HttpGet("Programs/{programId}")]
|
||||
[Authorize(Policy = Policies.LiveTvAccess)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<BaseItemDto>> GetProgram(
|
||||
[FromRoute, Required] string programId,
|
||||
[FromQuery] Guid? userId)
|
||||
@@ -756,8 +758,14 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
var result = await _liveTvManager.GetProgram(programId, CancellationToken.None, user).ConfigureAwait(false);
|
||||
|
||||
return await _liveTvManager.GetProgram(programId, CancellationToken.None, user).ConfigureAwait(false);
|
||||
if (result is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -226,16 +226,32 @@ public class PluginsController : BaseJellyfinApiController
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var imagePath = Path.Combine(plugin.Path, plugin.Manifest.ImagePath ?? string.Empty);
|
||||
if (plugin.Manifest.ImagePath is null || !System.IO.File.Exists(imagePath))
|
||||
if (!string.IsNullOrEmpty(plugin.Manifest.ImagePath))
|
||||
{
|
||||
return NotFound();
|
||||
var imagePath = Path.Combine(plugin.Path, plugin.Manifest.ImagePath);
|
||||
if (!System.IO.File.Exists(imagePath))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
Response.Headers.ContentDisposition = "attachment";
|
||||
return PhysicalFile(imagePath, MimeTypes.GetMimeType(imagePath));
|
||||
}
|
||||
|
||||
Response.Headers.ContentDisposition = "attachment";
|
||||
var resourceName = plugin.Manifest.ImageResourceName;
|
||||
if (!string.IsNullOrEmpty(resourceName) && plugin.Instance is not null)
|
||||
{
|
||||
var stream = plugin.Instance.GetType().Assembly.GetManifestResourceStream(resourceName);
|
||||
if (stream is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
imagePath = Path.Combine(plugin.Path, plugin.Manifest.ImagePath);
|
||||
return PhysicalFile(imagePath, MimeTypes.GetMimeType(imagePath));
|
||||
Response.Headers.ContentDisposition = "attachment";
|
||||
return File(stream, MimeTypes.GetMimeType(resourceName));
|
||||
}
|
||||
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -102,13 +102,13 @@ public class UniversalAudioController : BaseJellyfinApiController
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] int? transcodingAudioChannels,
|
||||
[FromQuery] int? maxStreamingBitrate,
|
||||
[FromQuery] int? audioBitRate,
|
||||
[FromQuery] long? startTimeTicks,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? transcodingContainer,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? transcodingContainer,
|
||||
[FromQuery] MediaStreamProtocol? transcodingProtocol,
|
||||
[FromQuery] int? maxAudioSampleRate,
|
||||
[FromQuery] int? maxAudioBitDepth,
|
||||
|
||||
@@ -88,7 +88,7 @@ public class UserViewsController : BaseJellyfinApiController
|
||||
var folders = _userViewManager.GetUserViews(query);
|
||||
|
||||
var dtoOptions = new DtoOptions();
|
||||
dtoOptions.Fields = [..dtoOptions.Fields, ItemFields.PrimaryImageAspectRatio, ItemFields.DisplayPreferencesId];
|
||||
dtoOptions.Fields = [.. dtoOptions.Fields, ItemFields.PrimaryImageAspectRatio, ItemFields.DisplayPreferencesId];
|
||||
|
||||
var dtos = Array.ConvertAll(folders, i => _dtoService.GetBaseItemDto(i, dtoOptions, user));
|
||||
|
||||
|
||||
@@ -317,18 +317,18 @@ public class VideosController : BaseJellyfinApiController
|
||||
[ProducesVideoFile]
|
||||
public async Task<ActionResult> GetVideoStream(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? container,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? container,
|
||||
[FromQuery] bool? @static,
|
||||
[FromQuery] string? @params,
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
@@ -338,7 +338,7 @@ public class VideosController : BaseJellyfinApiController
|
||||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
||||
[FromQuery][RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
@@ -359,8 +359,8 @@ public class VideosController : BaseJellyfinApiController
|
||||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
@@ -555,18 +555,18 @@ public class VideosController : BaseJellyfinApiController
|
||||
[ProducesVideoFile]
|
||||
public Task<ActionResult> GetVideoStreamByContainer(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string container,
|
||||
[FromRoute, Required][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string container,
|
||||
[FromQuery] bool? @static,
|
||||
[FromQuery] string? @params,
|
||||
[FromQuery] string? tag,
|
||||
[FromQuery] string? deviceProfileId,
|
||||
[FromQuery] string? playSessionId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
||||
[FromQuery] int? segmentLength,
|
||||
[FromQuery] int? minSegments,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
||||
[FromQuery] bool? enableAutoStreamCopy,
|
||||
[FromQuery] bool? allowVideoStreamCopy,
|
||||
[FromQuery] bool? allowAudioStreamCopy,
|
||||
@@ -576,7 +576,7 @@ public class VideosController : BaseJellyfinApiController
|
||||
[FromQuery] int? audioChannels,
|
||||
[FromQuery] int? maxAudioChannels,
|
||||
[FromQuery] string? profile,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
||||
[FromQuery][RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
||||
[FromQuery] float? framerate,
|
||||
[FromQuery] float? maxFramerate,
|
||||
[FromQuery] bool? copyTimestamps,
|
||||
@@ -597,8 +597,8 @@ public class VideosController : BaseJellyfinApiController
|
||||
[FromQuery] int? cpuCoreLimit,
|
||||
[FromQuery] string? liveStreamId,
|
||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
||||
[FromQuery][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
||||
[FromQuery] string? transcodeReasons,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
[FromQuery] int? videoStreamIndex,
|
||||
|
||||
@@ -62,12 +62,12 @@ public static class FileStreamResponseHelpers
|
||||
if (response.Headers.TryGetValues(HeaderNames.AcceptRanges, out var acceptRangesHeaders))
|
||||
{
|
||||
// Prefer upstream server's Accept-Ranges header if available
|
||||
acceptRangesValue = string.Join(", ", acceptRangesHeaders);
|
||||
upstreamSupportsRange |= acceptRangesValue.Contains("bytes", StringComparison.OrdinalIgnoreCase);
|
||||
acceptRangesValue = string.Join(", ", acceptRangesHeaders);
|
||||
upstreamSupportsRange |= acceptRangesValue.Contains("bytes", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
else if (upstreamSupportsRange) // If we got 206 but no Accept-Ranges header, assume bytes
|
||||
{
|
||||
acceptRangesValue = "bytes";
|
||||
acceptRangesValue = "bytes";
|
||||
}
|
||||
|
||||
// Set Accept-Ranges header for the client based on upstream support
|
||||
@@ -76,13 +76,13 @@ public static class FileStreamResponseHelpers
|
||||
// Set Content-Range header if upstream provided it (implies partial content)
|
||||
if (response.Content.Headers.ContentRange is not null)
|
||||
{
|
||||
httpContext.Response.Headers[HeaderNames.ContentRange] = response.Content.Headers.ContentRange.ToString();
|
||||
httpContext.Response.Headers[HeaderNames.ContentRange] = response.Content.Headers.ContentRange.ToString();
|
||||
}
|
||||
|
||||
// Set Content-Length header. For partial content, this is the length of the partial segment.
|
||||
if (response.Content.Headers.ContentLength.HasValue)
|
||||
{
|
||||
httpContext.Response.ContentLength = response.Content.Headers.ContentLength.Value;
|
||||
httpContext.Response.ContentLength = response.Content.Headers.ContentLength.Value;
|
||||
}
|
||||
|
||||
// Set Content-Type header
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Jellyfin.Api.Models.SystemInfoDtos;
|
||||
/// </summary>
|
||||
public record LibraryStorageDto
|
||||
{
|
||||
/// <summary>
|
||||
/// <summary>
|
||||
/// Gets or sets the Library Id.
|
||||
/// </summary>
|
||||
public required Guid Id { get; set; }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Jellyfin.Data.Enums;
|
||||
namespace Jellyfin.Data.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Activity log sorting options.
|
||||
|
||||
@@ -58,9 +58,9 @@ public class ActivityManager : IActivityManager
|
||||
{
|
||||
// TODO switch to LeftJoin in .NET 10.
|
||||
var entries = from a in dbContext.ActivityLogs
|
||||
join u in dbContext.Users on a.UserId equals u.Id into ugj
|
||||
from u in ugj.DefaultIfEmpty()
|
||||
select new ExpandedActivityLog { ActivityLog = a, Username = u.Username };
|
||||
join u in dbContext.Users on a.UserId equals u.Id into ugj
|
||||
from u in ugj.DefaultIfEmpty()
|
||||
select new ExpandedActivityLog { ActivityLog = a, Username = u.Username };
|
||||
|
||||
if (query.HasUserId is not null)
|
||||
{
|
||||
|
||||
@@ -75,9 +75,9 @@ namespace Jellyfin.Server.Implementations.Events.Consumers.Session
|
||||
eventArgs.DeviceName),
|
||||
notificationType,
|
||||
user.Id)
|
||||
{
|
||||
ItemId = eventArgs.Item?.Id.ToString("N", CultureInfo.InvariantCulture),
|
||||
})
|
||||
{
|
||||
ItemId = eventArgs.Item?.Id.ToString("N", CultureInfo.InvariantCulture),
|
||||
})
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Jellyfin.Server.Implementations.Item;
|
||||
/// <summary>
|
||||
/// Handles mapping between BaseItemEntity (database) and BaseItemDto (domain) objects.
|
||||
/// </summary>
|
||||
internal static class BaseItemMapper
|
||||
public static class BaseItemMapper
|
||||
{
|
||||
/// <summary>
|
||||
/// This holds all the types in the running assemblies
|
||||
|
||||
@@ -1020,6 +1020,15 @@ public sealed partial class BaseItemRepository
|
||||
baseQuery = baseQuery.Where(e => e.Parents!.AsQueryable().Any(ancestorFilter));
|
||||
}
|
||||
|
||||
if (filter.LinkedChildAncestorIds.Length > 0)
|
||||
{
|
||||
// Keep folder-like items (BoxSets, Playlists) whose linked children descend from any of the requested ancestor ids.
|
||||
var linkedChildAncestorIds = filter.LinkedChildAncestorIds;
|
||||
baseQuery = baseQuery.Where(e => context.LinkedChildren.Any(lc =>
|
||||
lc.ParentId == e.Id
|
||||
&& lc.Child!.Parents!.Any(a => linkedChildAncestorIds.Contains(a.ParentItemId))));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.AncestorWithPresentationUniqueKey))
|
||||
{
|
||||
baseQuery = baseQuery
|
||||
|
||||
@@ -55,6 +55,7 @@ public class ChapterRepository : IChapterRepository
|
||||
{
|
||||
using var context = _dbProvider.CreateDbContext();
|
||||
return context.Chapters.AsNoTracking().Where(e => e.ItemId.Equals(baseItemId))
|
||||
.OrderBy(e => e.StartPositionTicks)
|
||||
.Select(e => new
|
||||
{
|
||||
chapter = e,
|
||||
@@ -69,18 +70,16 @@ public class ChapterRepository : IChapterRepository
|
||||
public void SaveChapters(Guid itemId, IReadOnlyList<ChapterInfo> chapters)
|
||||
{
|
||||
using var context = _dbProvider.CreateDbContext();
|
||||
using (var transaction = context.Database.BeginTransaction())
|
||||
using var transaction = context.Database.BeginTransaction();
|
||||
context.Chapters.Where(e => e.ItemId.Equals(itemId)).ExecuteDelete();
|
||||
for (var i = 0; i < chapters.Count; i++)
|
||||
{
|
||||
context.Chapters.Where(e => e.ItemId.Equals(itemId)).ExecuteDelete();
|
||||
for (var i = 0; i < chapters.Count; i++)
|
||||
{
|
||||
var chapter = chapters[i];
|
||||
context.Chapters.Add(Map(chapter, i, itemId));
|
||||
}
|
||||
|
||||
context.SaveChanges();
|
||||
transaction.Commit();
|
||||
var chapter = chapters[i];
|
||||
context.Chapters.Add(Map(chapter, i, itemId));
|
||||
}
|
||||
|
||||
context.SaveChanges();
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -110,10 +110,10 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
|
||||
using var context = _dbProvider.CreateDbContext();
|
||||
using var transaction = context.Database.BeginTransaction();
|
||||
var existingPersons = context.Peoples.Select(e => new
|
||||
{
|
||||
item = e,
|
||||
SelectionKey = e.Name.ToLower() + "-" + e.PersonType
|
||||
})
|
||||
{
|
||||
item = e,
|
||||
SelectionKey = e.Name.ToLower() + "-" + e.PersonType
|
||||
})
|
||||
.Where(p => personKeys.Contains(p.SelectionKey))
|
||||
.Select(f => f.item)
|
||||
.ToArray();
|
||||
|
||||
@@ -81,6 +81,8 @@ public class MediaSegmentManager : IMediaSegmentManager
|
||||
|
||||
foreach (var provider in providers)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (!await provider.Supports(baseItem).ConfigureAwait(false))
|
||||
{
|
||||
_logger.LogDebug("Media Segment provider {ProviderName} does not support item with path {MediaPath}", provider.Name, baseItem.Path);
|
||||
@@ -146,6 +148,15 @@ public class MediaSegmentManager : IMediaSegmentManager
|
||||
await CreateSegmentAsync(segment, providerId).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex) when (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_logger.LogDebug(ex, "Provider {ProviderName} aborted segment extraction for {MediaPath} due to shutdown", provider.Name, baseItem.Path);
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Provider {ProviderName} failed to extract segments from {MediaPath}", provider.Name, baseItem.Path);
|
||||
|
||||
@@ -302,7 +302,7 @@ namespace Jellyfin.Server.Implementations.Security
|
||||
}
|
||||
else if (!escaped && token == '=')
|
||||
{
|
||||
key = authorizationHeader[start.. i].Trim().ToString();
|
||||
key = authorizationHeader[start..i].Trim().ToString();
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ internal sealed class CachingOpenApiProvider : ISwaggerProvider
|
||||
|
||||
try
|
||||
{
|
||||
openApiDocument = _swaggerGenerator.GetSwagger(documentName);
|
||||
openApiDocument = _swaggerGenerator.GetSwagger(documentName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
8
Jellyfin.Server/GlobalSuppressions.cs
Normal file
8
Jellyfin.Server/GlobalSuppressions.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
// This file is used by Code Analysis to maintain SuppressMessage
|
||||
// attributes that are applied to this project.
|
||||
// Project-level suppressions either have no target or are given
|
||||
// a specific target and scoped to a namespace, type, member, etc.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1649:File name should match first type name", Justification = "Migration files should follow the EFCore standard in regards to naming.", Scope = "namespaceanddescendants", Target = "~N:Jellyfin.Server.Migrations.Routines")]
|
||||
@@ -16,7 +16,7 @@ namespace Jellyfin.Server.Migrations
|
||||
Applied = new List<(Guid Id, string Name)>();
|
||||
}
|
||||
|
||||
// .Net xml serializer can't handle interfaces
|
||||
// .Net xml serializer can't handle interfaces
|
||||
#pragma warning disable CA1002 // Do not expose generic lists
|
||||
/// <summary>
|
||||
/// Gets the list of applied migration routine names.
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Database.Implementations;
|
||||
using Jellyfin.Server.Implementations.Item;
|
||||
using Jellyfin.Server.Migrations.Stages;
|
||||
using Jellyfin.Server.ServerSetupApp;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
@@ -23,7 +24,7 @@ namespace Jellyfin.Server.Migrations.Routines;
|
||||
/// Removes orphaned extras (items with OwnerId pointing to non-existent items).
|
||||
/// Must run before EF migrations that add FK constraints on OwnerId.
|
||||
/// </summary>
|
||||
[JellyfinMigration("2026-01-13T23:00:00", nameof(CleanupOrphanedExtras), Stage = JellyfinMigrationStageTypes.CoreInitialisation)]
|
||||
[JellyfinMigration("2026-01-13T23:00:00", nameof(CleanupOrphanedExtras), Stage = JellyfinMigrationStageTypes.AppInitialisation)]
|
||||
[JellyfinMigrationBackup(JellyfinDb = true)]
|
||||
public class CleanupOrphanedExtras : IAsyncMigrationRoutine
|
||||
{
|
||||
@@ -37,39 +38,14 @@ public class CleanupOrphanedExtras : IAsyncMigrationRoutine
|
||||
/// <param name="logger">The startup logger.</param>
|
||||
/// <param name="dbContextFactory">The database context factory.</param>
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="itemRepository">The item repository.</param>
|
||||
/// <param name="itemCountService">The item count service.</param>
|
||||
/// <param name="channelManager">The channel manager.</param>
|
||||
/// <param name="recordingsManager">The recordings manager.</param>
|
||||
/// <param name="mediaSourceManager">The media source manager.</param>
|
||||
/// <param name="mediaSegmentManager">The media segments manager.</param>
|
||||
/// <param name="configurationManager">The configuration manager.</param>
|
||||
/// <param name="fileSystem">The file system.</param>
|
||||
public CleanupOrphanedExtras(
|
||||
IStartupLogger<CleanupOrphanedExtras> logger,
|
||||
IDbContextFactory<JellyfinDbContext> dbContextFactory,
|
||||
ILibraryManager libraryManager,
|
||||
IItemRepository itemRepository,
|
||||
IItemCountService itemCountService,
|
||||
IChannelManager channelManager,
|
||||
IRecordingsManager recordingsManager,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IMediaSegmentManager mediaSegmentManager,
|
||||
IServerConfigurationManager configurationManager,
|
||||
IFileSystem fileSystem)
|
||||
ILibraryManager libraryManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_libraryManager = libraryManager;
|
||||
BaseItem.LibraryManager ??= libraryManager;
|
||||
BaseItem.ItemRepository ??= itemRepository;
|
||||
BaseItem.ItemCountService ??= itemCountService;
|
||||
BaseItem.ChannelManager ??= channelManager;
|
||||
BaseItem.MediaSourceManager ??= mediaSourceManager;
|
||||
BaseItem.MediaSegmentManager ??= mediaSegmentManager;
|
||||
BaseItem.ConfigurationManager ??= configurationManager;
|
||||
BaseItem.FileSystem ??= fileSystem;
|
||||
Video.RecordingsManager ??= recordingsManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -78,12 +54,19 @@ public class CleanupOrphanedExtras : IAsyncMigrationRoutine
|
||||
var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using (context.ConfigureAwait(false))
|
||||
{
|
||||
var placeholderOwner = Guid.Parse("00000000-0000-0000-0000-000000000001");
|
||||
#pragma warning disable RS0030 // Do not use banned APIs
|
||||
var orphanedItemIds = await context.BaseItems
|
||||
.Where(b => b.OwnerId.HasValue && !b.OwnerId.Value.Equals(Guid.Empty))
|
||||
.Where(b => !context.BaseItems.Any(parent => parent.Id.Equals(b.OwnerId!.Value)))
|
||||
.Select(b => b.Id)
|
||||
.Where(b => b.OwnerId.HasValue && b.OwnerId == placeholderOwner)
|
||||
.Select(b => new
|
||||
{
|
||||
b.Id,
|
||||
b.Path,
|
||||
b.Type
|
||||
})
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
#pragma warning restore RS0030 // Do not use banned APIs
|
||||
|
||||
if (orphanedItemIds.Count == 0)
|
||||
{
|
||||
@@ -97,11 +80,16 @@ public class CleanupOrphanedExtras : IAsyncMigrationRoutine
|
||||
var itemsToDelete = new List<BaseItem>();
|
||||
foreach (var itemId in orphanedItemIds)
|
||||
{
|
||||
var item = _libraryManager.GetItemById(itemId);
|
||||
if (item is not null)
|
||||
{
|
||||
itemsToDelete.Add(item);
|
||||
}
|
||||
itemsToDelete.Add(BaseItemMapper.DeserializeBaseItem(
|
||||
new Database.Implementations.Entities.BaseItemEntity()
|
||||
{
|
||||
Id = itemId.Id,
|
||||
Path = itemId.Path,
|
||||
Type = itemId.Type
|
||||
},
|
||||
_logger,
|
||||
null,
|
||||
true)!);
|
||||
}
|
||||
|
||||
_libraryManager.DeleteItemsUnsafeFast(itemsToDelete);
|
||||
@@ -284,10 +284,16 @@ public class MergeDuplicatePeople : IAsyncMigrationRoutine
|
||||
return;
|
||||
}
|
||||
|
||||
await context.Peoples
|
||||
.Where(p => idsToDelete.Contains(p.Id))
|
||||
.ExecuteDeleteAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
var idx = 0;
|
||||
foreach (var item in idsToDelete.Chunk(200))
|
||||
{
|
||||
idx++; // humans count at one
|
||||
_logger.LogInformation("Remove batch {BatchNo}/{MaxBatches} duplicate Peoples.", idx, idsToDelete.Count / 200);
|
||||
await context.Peoples
|
||||
.Where(p => item.Contains(p.Id))
|
||||
.ExecuteDeleteAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Removed {Count} duplicate Peoples rows.", idsToDelete.Count);
|
||||
}
|
||||
@@ -90,6 +90,13 @@ public sealed class SetupServer : IDisposable
|
||||
var fileTemplate = await File.ReadAllTextAsync(Path.Combine(AppContext.BaseDirectory, "ServerSetupApp", "index.mstemplate.html")).ConfigureAwait(false);
|
||||
_startupUiRenderer = (await ParserOptionsBuilder.New()
|
||||
.WithTemplate(fileTemplate)
|
||||
.WithFormatter(
|
||||
(Version version, int arg) =>
|
||||
{
|
||||
// version type does not for some stupid reason implement IFormattable which morestachio relies on for ToString support therefor we need to do it manually.
|
||||
return version.ToString(arg);
|
||||
},
|
||||
"ToString")
|
||||
.WithFormatter(
|
||||
(StartupLogTopic logEntry, IEnumerable<StartupLogTopic> children) =>
|
||||
{
|
||||
@@ -237,6 +244,7 @@ public sealed class SetupServer : IDisposable
|
||||
});
|
||||
});
|
||||
|
||||
var version = typeof(Emby.Server.Implementations.ApplicationHost).Assembly.GetName().Version!;
|
||||
app.Run(async (context) =>
|
||||
{
|
||||
context.Response.StatusCode = (int)HttpStatusCode.ServiceUnavailable;
|
||||
@@ -250,7 +258,7 @@ public sealed class SetupServer : IDisposable
|
||||
{
|
||||
{ "isInReportingMode", _isUnhealthy },
|
||||
{ "retryValue", retryAfterValue },
|
||||
{ "version", typeof(Emby.Server.Implementations.ApplicationHost).Assembly.GetName().Version! },
|
||||
{ "version", version },
|
||||
{ "logs", startupLogEntries },
|
||||
{ "networkManagerReady", networkManager is not null },
|
||||
{ "localNetworkRequest", networkManager is not null && context.Connection.RemoteIpAddress is not null && networkManager.IsInLocalNetwork(context.Connection.RemoteIpAddress) }
|
||||
|
||||
@@ -173,9 +173,9 @@
|
||||
<header class="flex-row">
|
||||
|
||||
{{^IF isInReportingMode}}
|
||||
<p>Jellyfin Server {{version}} still starting. Please wait.</p>
|
||||
<p>Jellyfin Server {{version.ToString(2)}} still starting. Please wait.</p>
|
||||
{{#ELSE}}
|
||||
<p>Jellyfin Server has encountered an error and was not able to start.</p>
|
||||
<p>Jellyfin Server {{version.ToString(2)}} has encountered an error and was not able to start.</p>
|
||||
{{/ELSE}}
|
||||
{{/IF}}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user