Merge branch 'jellyfin:master' into add-sort-by-primier-api

pull/11512/head
scampower3 2 weeks ago committed by GitHub
commit 62ecd0c6a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "8.0.4",
"version": "8.0.5",
"commands": [
"dotnet-ef"
]

@ -38,10 +38,11 @@ body:
label: Jellyfin Version
description: What version of Jellyfin are you running?
options:
- 10.9.0
- 10.8.13
- 10.8.12
- 10.8.11 or older (please specify)
- Unstable (master branch)
- 10.8.12 or older (please specify)
- Weekly unstable (please specify)
- Master branch
validations:
required: true
- type: input

@ -20,18 +20,18 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- name: Setup .NET
uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
with:
dotnet-version: '8.0.x'
- name: Initialize CodeQL
uses: github/codeql-action/init@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3
uses: github/codeql-action/init@b7cec7526559c32f1616476ff32d17ba4c59b2d6 # v3.25.5
with:
languages: ${{ matrix.language }}
queries: +security-extended
- name: Autobuild
uses: github/codeql-action/autobuild@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3
uses: github/codeql-action/autobuild@b7cec7526559c32f1616476ff32d17ba4c59b2d6 # v3.25.5
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3
uses: github/codeql-action/analyze@b7cec7526559c32f1616476ff32d17ba4c59b2d6 # v3.25.5

@ -3,6 +3,8 @@ on:
push:
branches:
- master
tags:
- 'v*'
pull_request_target:
permissions: {}
@ -14,7 +16,7 @@ jobs:
permissions: read-all
steps:
- name: Checkout repository
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
with:
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
@ -39,7 +41,7 @@ jobs:
permissions: read-all
steps:
- name: Checkout repository
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
with:
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
@ -138,10 +140,11 @@ jobs:
No changes to OpenAPI specification found. See history of this comment for previous changes.
publish:
publish-unstable:
name: OpenAPI - Publish Unstable Spec
if: |
github.event_name != 'pull_request_target' &&
${{ ! startsWith(github.ref, 'refs/tags/v') }} &&
contains(github.repository_owner, 'jellyfin')
runs-on: ubuntu-latest
needs:
@ -201,3 +204,67 @@ jobs:
sudo ln -s unstable/${LAST_SPEC} ${TGT_DIR}/jellyfin-openapi-unstable_previous.json
fi
) 200>/run/workflows/openapi-unstable.lock
publish-stable:
name: OpenAPI - Publish Stable Spec
if: |
startsWith(github.ref, 'refs/tags/v') &&
contains(github.repository_owner, 'jellyfin')
runs-on: ubuntu-latest
needs:
- openapi-head
steps:
- name: Set version number
id: version
run: |-
echo "JELLYFIN_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
- name: Download openapi-head
uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
with:
name: openapi-head
path: openapi-head
- name: Upload openapi.json (stable) to repository server
uses: appleboy/scp-action@917f8b81dfc1ccd331fef9e2d61bdc6c8be94634 # v0.1.7
with:
host: "${{ secrets.REPO_HOST }}"
username: "${{ secrets.REPO_USER }}"
key: "${{ secrets.REPO_KEY }}"
source: openapi-head/openapi.json
strip_components: 1
target: "/srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
- name: Move openapi.json (stable) into place
uses: appleboy/ssh-action@029f5b4aeeeb58fdfe1410a5d17f967dacf36262 # v1.0.3
with:
host: "${{ secrets.REPO_HOST }}"
username: "${{ secrets.REPO_USER }}"
key: "${{ secrets.REPO_KEY }}"
debug: false
script_stop: false
script: |
if ! test -d /run/workflows; then
sudo mkdir -p /run/workflows
sudo chown ${{ secrets.REPO_USER }} /run/workflows
fi
(
flock -x -w 300 200 || exit 1
TGT_DIR="/srv/repository/main/openapi"
LAST_SPEC="$( ls -lt ${TGT_DIR}/stable/ | grep 'jellyfin-openapi' | head -1 | awk '{ print $NF }' )"
# If new and previous spec don't differ (diff retcode 0), remove incoming and finish
if diff /srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}/openapi.json ${TGT_DIR}/stable/${LAST_SPEC} &>/dev/null; then
rm -r /srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}
exit 0
fi
# Move new spec into place
sudo mv /srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}/openapi.json ${TGT_DIR}/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}.json
# Delete previous jellyfin-openapi-stable_previous.json
sudo rm ${TGT_DIR}/jellyfin-openapi-stable_previous.json
# Move current jellyfin-openapi-stable.json symlink to jellyfin-openapi-stable_previous.json
sudo mv ${TGT_DIR}/jellyfin-openapi-stable.json ${TGT_DIR}/jellyfin-openapi-stable_previous.json
# Create new jellyfin-openapi-stable.json symlink
sudo ln -s stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}.json ${TGT_DIR}/jellyfin-openapi-stable.json
# Check that the previous openapi stable spec link is correct
if [[ "$( readlink ${TGT_DIR}/jellyfin-openapi-stable_previous.json )" != "stable/${LAST_SPEC}" ]]; then
sudo rm ${TGT_DIR}/jellyfin-openapi-stable_previous.json
sudo ln -s stable/${LAST_SPEC} ${TGT_DIR}/jellyfin-openapi-stable_previous.json
fi
) 200>/run/workflows/openapi-stable.lock

@ -19,7 +19,7 @@ jobs:
runs-on: "${{ matrix.os }}"
steps:
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
- uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
- uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
with:
@ -34,7 +34,7 @@ jobs:
--verbosity minimal
- name: Merge code coverage results
uses: danielpalme/ReportGenerator-GitHub-Action@2a2d60ea1c7e811f54684179af6ac1ae8c1ce69a # 5.2.5
uses: danielpalme/ReportGenerator-GitHub-Action@6b06171d1a131e7fd85121120a1c00c1ed03e033 # 5.3.0
with:
reports: "**/coverage.cobertura.xml"
targetdir: "merged/"

@ -24,7 +24,7 @@ jobs:
reactions: '+1'
- name: Checkout the latest code
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
with:
token: ${{ secrets.JF_BOT_TOKEN }}
fetch-depth: 0
@ -51,7 +51,7 @@ jobs:
reactions: eyes
- name: Checkout the latest code
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
with:
token: ${{ secrets.JF_BOT_TOKEN }}
fetch-depth: 0
@ -128,7 +128,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: pull in script
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
with:
repository: jellyfin/jellyfin-triage-script
- name: install python

@ -10,7 +10,7 @@ jobs:
issues: write
steps:
- name: pull in script
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
with:
repository: jellyfin/jellyfin-triage-script
- name: install python

@ -15,7 +15,7 @@ jobs:
if: ${{ github.repository == 'jellyfin/jellyfin' }}
steps:
- name: Apply label
uses: eps1lon/actions-label-merge-conflict@e62d7a53ff8be8b97684bffb6cfbbf3fc1115e2e # v3.0.0
uses: eps1lon/actions-label-merge-conflict@6d74047dcef155976a15e4a124dde2c7fe0c5522 # v3.0.1
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request_target'}}
with:
dirtyLabel: 'merge conflict'

@ -33,7 +33,7 @@ jobs:
yq-version: v4.9.8
- name: Checkout Repository
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
with:
ref: ${{ env.TAG_BRANCH }}
@ -66,7 +66,7 @@ jobs:
NEXT_VERSION: ${{ github.event.inputs.NEXT_VERSION }}
steps:
- name: Checkout Repository
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5
with:
ref: ${{ env.TAG_BRANCH }}

@ -25,15 +25,14 @@
<PackageVersion Include="libse" Version="4.0.5" />
<PackageVersion Include="LrcParser" Version="2023.524.0" />
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="6.1.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="8.0.4" />
<PackageVersion Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.4" />
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="8.0.5" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.5" />
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.4" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.4" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.4" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.4" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.4" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.5" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.5" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.5" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.5" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.5" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
@ -42,8 +41,8 @@
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.4" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.4" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.5" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.5" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />

@ -36,7 +36,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Naming</PackageId>
<VersionPrefix>10.9.0</VersionPrefix>
<VersionPrefix>10.10.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

@ -422,7 +422,7 @@ namespace Emby.Server.Implementations
// Initialize runtime stat collection
if (ConfigurationManager.Configuration.EnableMetrics)
{
DotNetRuntimeStatsBuilder.Default().StartCollecting();
_disposableParts.Add(DotNetRuntimeStatsBuilder.Default().StartCollecting());
}
var networkConfiguration = ConfigurationManager.GetNetworkConfiguration();

@ -80,12 +80,14 @@ namespace Emby.Server.Implementations.IO
public virtual string MakeAbsolutePath(string folderPath, string filePath)
{
// path is actually a stream
if (string.IsNullOrWhiteSpace(filePath) || filePath.Contains("://", StringComparison.Ordinal))
if (string.IsNullOrWhiteSpace(filePath))
{
return filePath;
}
if (filePath.Length > 3 && filePath[1] == ':' && filePath[2] == '/')
var isAbsolutePath = Path.IsPathRooted(filePath) && (!OperatingSystem.IsWindows() || filePath[0] != '\\');
if (isAbsolutePath)
{
// absolute local path
return filePath;
@ -97,17 +99,10 @@ namespace Emby.Server.Implementations.IO
return filePath;
}
var firstChar = filePath[0];
if (firstChar == '/')
{
// for this we don't really know
return filePath;
}
var filePathSpan = filePath.AsSpan();
// relative path
if (firstChar == '\\')
// relative path on windows
if (filePath[0] == '\\')
{
filePathSpan = filePathSpan.Slice(1);
}

@ -128,5 +128,7 @@
"TaskRefreshTrickplayImages": "Trickplay-Bilder generieren",
"TaskRefreshTrickplayImagesDescription": "Erstellt eine Trickplay-Vorschau für Videos in aktivierten Bibliotheken.",
"TaskCleanCollectionsAndPlaylists": "Sammlungen und Playlisten aufräumen",
"TaskCleanCollectionsAndPlaylistsDescription": "Lösche nicht mehr vorhandene Einträge aus den Sammlungen und Playlisten."
"TaskCleanCollectionsAndPlaylistsDescription": "Lösche nicht mehr vorhandene Einträge aus den Sammlungen und Playlisten.",
"TaskAudioNormalization": "Audio Normalisierung",
"TaskAudioNormalizationDescription": "Durchsucht Dateien nach Audionormalisierungsdaten."
}

@ -126,5 +126,9 @@
"External": "Εξωτερικό",
"HearingImpaired": "Με προβλήματα ακοής",
"TaskRefreshTrickplayImages": "Δημιουργήστε εικόνες Trickplay",
"TaskRefreshTrickplayImagesDescription": "Δημιουργεί προεπισκοπήσεις trickplay για βίντεο σε ενεργοποιημένες βιβλιοθήκες."
"TaskRefreshTrickplayImagesDescription": "Δημιουργεί προεπισκοπήσεις trickplay για βίντεο σε ενεργοποιημένες βιβλιοθήκες.",
"TaskAudioNormalization": "Ομοιομορφία ήχου",
"TaskAudioNormalizationDescription": "Ανίχνευση αρχείων για δεδομένα ομοιομορφίας ήχου.",
"TaskCleanCollectionsAndPlaylists": "Καθαρισμός συλλογών και λιστών αναπαραγωγής",
"TaskCleanCollectionsAndPlaylistsDescription": "Αφαιρούνται στοιχεία από τις συλλογές και τις λίστες αναπαραγωγής που δεν υπάρχουν πλέον."
}

@ -13,7 +13,7 @@
"DeviceOfflineWithName": "{0} has disconnected",
"DeviceOnlineWithName": "{0} is connected",
"External": "External",
"FailedLoginAttemptWithUserName": "Failed login try from {0}",
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
"Favorites": "Favorites",
"Folders": "Folders",
"Forced": "Forced",

@ -124,5 +124,11 @@
"TaskKeyframeExtractorDescription": "Extrae los cuadros clave de los archivos de vídeo para crear listas HLS más precisas. Esta tarea puede tardar un buen rato.",
"TaskKeyframeExtractor": "Extractor de Cuadros Clave",
"External": "Externo",
"HearingImpaired": "Discapacidad Auditiva"
"HearingImpaired": "Discapacidad Auditiva",
"TaskRefreshTrickplayImagesDescription": "Crea previsualizaciones para la barra de reproducción en las bibliotecas habilitadas.",
"TaskRefreshTrickplayImages": "Generar imágenes de la barra de reproducción",
"TaskAudioNormalization": "Normalización de audio",
"TaskAudioNormalizationDescription": "Analiza los archivos para normalizar el audio.",
"TaskCleanCollectionsAndPlaylists": "Limpieza de colecciones y listas de reproducción",
"TaskCleanCollectionsAndPlaylistsDescription": "Quita elementos que ya no existen de colecciones y listas de reproducción."
}

@ -128,5 +128,7 @@
"TaskRefreshTrickplayImages": "Generar miniaturas de línea de tiempo",
"TaskRefreshTrickplayImagesDescription": "Crear miniaturas de tiempo para videos en las librerías habilitadas.",
"TaskCleanCollectionsAndPlaylists": "Limpiar colecciones y listas de reproducción",
"TaskCleanCollectionsAndPlaylistsDescription": "Elimina elementos de colecciones y listas de reproducción que ya no existen."
"TaskCleanCollectionsAndPlaylistsDescription": "Elimina elementos de colecciones y listas de reproducción que ya no existen.",
"TaskAudioNormalization": "Normalización de audio",
"TaskAudioNormalizationDescription": "Escanear archivos para obtener datos de normalización."
}

@ -125,5 +125,7 @@
"TaskKeyframeExtractorDescription": "Eraldab videofailidest võtmekaadreid, et luua täpsemaid HLS-i esitusloendeid. See ülesanne võib kesta pikka aega.",
"TaskKeyframeExtractor": "Võtmekaadri ekstraktor",
"TaskRefreshTrickplayImages": "Loo eelvaate pildid",
"TaskRefreshTrickplayImagesDescription": "Loob eelvaated videotele, kus lubatud."
"TaskRefreshTrickplayImagesDescription": "Loob eelvaated videotele, kus lubatud.",
"TaskAudioNormalization": "Heli Normaliseerimine",
"TaskAudioNormalizationDescription": "Skaneerib faile heli normaliseerimise andmete jaoks."
}

@ -127,5 +127,7 @@
"TaskRefreshTrickplayImages": "Luo Trickplay-kuvat",
"TaskRefreshTrickplayImagesDescription": "Luo Trickplay-esikatselut käytössä olevien kirjastojen videoista.",
"TaskCleanCollectionsAndPlaylistsDescription": "Poistaa kohteet kokoelmista ja soittolistoista joita ei ole enää olemassa.",
"TaskCleanCollectionsAndPlaylists": "Puhdista kokoelmat ja soittolistat"
"TaskCleanCollectionsAndPlaylists": "Puhdista kokoelmat ja soittolistat",
"TaskAudioNormalization": "Äänenvoimakkuuden normalisointi",
"TaskAudioNormalizationDescription": "Etsii tiedostoista äänenvoimakkuuden normalisointitietoja."
}

@ -11,7 +11,7 @@
"Collections": "Collections",
"DeviceOfflineWithName": "{0} s'est déconnecté",
"DeviceOnlineWithName": "{0} est connecté",
"FailedLoginAttemptWithUserName": "Tentative de connexion échoué par {0}",
"FailedLoginAttemptWithUserName": "Tentative de connexion échouée par {0}",
"Favorites": "Favoris",
"Folders": "Dossiers",
"Genres": "Genres",
@ -128,5 +128,7 @@
"TaskRefreshTrickplayImages": "Générer des images Trickplay",
"TaskRefreshTrickplayImagesDescription": "Crée des aperçus Trickplay pour les vidéos dans les médiathèques activées.",
"TaskCleanCollectionsAndPlaylists": "Nettoyer les collections et les listes de lecture",
"TaskCleanCollectionsAndPlaylistsDescription": "Supprimer les liens inexistants des collections et des listes de lecture"
"TaskCleanCollectionsAndPlaylistsDescription": "Supprime les éléments des collections et des listes de lecture qui n'existent plus.",
"TaskAudioNormalization": "Normalisation audio",
"TaskAudioNormalizationDescription": "Analyse les fichiers à la recherche de données de normalisation audio."
}

@ -128,5 +128,7 @@
"TaskRefreshTrickplayImages": "Genera immagini Trickplay",
"TaskRefreshTrickplayImagesDescription": "Crea anteprime trickplay per i video nelle librerie abilitate.",
"TaskCleanCollectionsAndPlaylists": "Ripulire le raccolte e le playlist",
"TaskCleanCollectionsAndPlaylistsDescription": "Rimuove gli elementi dalle raccolte e dalle playlist che non esistono più."
"TaskCleanCollectionsAndPlaylistsDescription": "Rimuove gli elementi dalle raccolte e dalle playlist che non esistono più.",
"TaskAudioNormalization": "Normalizzazione Audio",
"TaskAudioNormalizationDescription": "Scansione files per normalizzazione audio."
}

@ -125,5 +125,9 @@
"External": "外部",
"HearingImpaired": "聴覚障害の方",
"TaskRefreshTrickplayImages": "トリックプレー画像を生成",
"TaskRefreshTrickplayImagesDescription": "有効なライブラリ内のビデオをもとにトリックプレーのプレビューを生成します。"
"TaskRefreshTrickplayImagesDescription": "有効なライブラリ内のビデオをもとにトリックプレーのプレビューを生成します。",
"TaskCleanCollectionsAndPlaylists": "コレクションとプレイリストをクリーンアップ",
"TaskAudioNormalization": "音声の正規化",
"TaskAudioNormalizationDescription": "音声の正規化データのためにファイルをスキャンします。",
"TaskCleanCollectionsAndPlaylistsDescription": "在しなくなったコレクションやプレイリストからアイテムを削除します。"
}

@ -12,16 +12,122 @@
"DeviceOfflineWithName": "{0} inqatgħa",
"DeviceOnlineWithName": "{0} qabad",
"External": "Estern",
"FailedLoginAttemptWithUserName": "Attentat t'aċċess fallut minn {0}",
"FailedLoginAttemptWithUserName": "Tentattiv t'aċċess fallut minn {0}",
"Favorites": "Favoriti",
"Forced": "Furzat",
"Forced": "Sfurzat",
"Genres": "Ġeneri",
"HeaderAlbumArtists": "Artisti tal-album",
"HeaderContinueWatching": "Kompli Segwi",
"HeaderFavoriteAlbums": "Albums Favoriti",
"HeaderFavoriteArtists": "Artisti Favoriti",
"HeaderFavoriteEpisodes": "Episodji Favoriti",
"HeaderFavoriteShows": "Xows Favoriti",
"HeaderFavoriteShows": "Programmi Favoriti",
"HeaderFavoriteSongs": "Kanzunetti Favoriti",
"HeaderNextUp": "Li Jmiss"
"HeaderNextUp": "Li Jmiss",
"SubtitleDownloadFailureFromForItem": "Is-sottotitli naqsu milli jitniżżlu minn {0} għal {1}",
"UserPasswordChangedWithName": "Il-password inbidel għall-utent {0}",
"TaskUpdatePluginsDescription": "Iniżżel u jinstalla aġġornamenti għal plugins li huma kkonfigurati biex jaġġornaw awtomatikament.",
"TaskDownloadMissingSubtitlesDescription": "Ifittex fuq l-internet għal sottotitli neqsin abbażi tal-konfigurazzjoni tal-metadata.",
"TaskOptimizeDatabaseDescription": "Jikkompatti d-database u jaqta' l-ispazju ħieles. It-tħaddim ta' dan il-kompitu wara li tiskennja l-librerija jew tagħmel bidliet oħra li jimplikaw modifiki fid-database jistgħu jtejbu l-prestazzjoni.",
"Default": "Standard",
"Folders": "Folders",
"HeaderLiveTV": "TV Dirett",
"HeaderRecordingGroups": "Gruppi ta' Reġistrazzjoni",
"HearingImpaired": "Nuqqas ta' Smigħ",
"HomeVideos": "Vidjows Personali",
"Inherit": "Jiret",
"ItemAddedWithName": "{0} ġie miżjud mal-librerija",
"ItemRemovedWithName": "{0} tneħħa mil-librerija",
"LabelIpAddressValue": "Indirizz IP: {0}",
"Latest": "Tal-Aħħar",
"MessageApplicationUpdated": "Jellyfin Server ġie aġġornat",
"MessageApplicationUpdatedTo": "JellyFin Server ġie aġġornat għal {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Is-sezzjoni {0} tal-konfigurazzjoni tas-server ġiet aġġornata",
"MessageServerConfigurationUpdated": "Il-konfigurazzjoni tas-server ġiet aġġornata",
"MixedContent": "Kontenut imħallat",
"Movies": "Films",
"Music": "Mużika",
"MusicVideos": "Vidjows tal-Mużika",
"NameInstallFailed": "L-installazzjoni ta' {0} falliet",
"NameSeasonNumber": "Staġun {0}",
"NameSeasonUnknown": "Staġun Mhux Magħruf",
"NewVersionIsAvailable": "Verżjoni ġdida ta' Jellyfin Server hija disponibbli biex titniżżel.",
"NotificationOptionApplicationUpdateAvailable": "Aġġornament tal-applikazzjoni disponibbli",
"NotificationOptionCameraImageUploaded": "Immaġini tal-kamera mtella'",
"LabelRunningTimeValue": "Tul: {0}",
"NotificationOptionApplicationUpdateInstalled": "Aġġornament tal-applikazzjoni ġie installat",
"NotificationOptionAudioPlayback": "Il-playback tal-awdjo beda",
"NotificationOptionAudioPlaybackStopped": "Il-playback tal-awdjo twaqqaf",
"NotificationOptionInstallationFailed": "Installazzjoni falliet",
"NotificationOptionNewLibraryContent": "Kontenut ġdid miżjud",
"NotificationOptionPluginError": "Ħsara fil-plugin",
"NotificationOptionPluginInstalled": "Plugin installat",
"NotificationOptionPluginUninstalled": "Plugin tneħħa",
"NotificationOptionServerRestartRequired": "Meħtieġ l-istartjar mill-ġdid tas-server",
"NotificationOptionTaskFailed": "Falliment tal-kompitu skedat",
"NotificationOptionUserLockedOut": "Utent imsakkar",
"Photos": "Ritratti",
"Playlists": "Playlists",
"Plugin": "Plugin",
"PluginInstalledWithName": "{0} ġie installat",
"PluginUninstalledWithName": "{0} ġie mneħħi",
"PluginUpdatedWithName": "{0} ġie aġġornat",
"ProviderValue": "Fornitur: {0}",
"ScheduledTaskFailedWithName": "{0} falla",
"ScheduledTaskStartedWithName": "{0} beda",
"ServerNameNeedsToBeRestarted": "{0} jeħtieġ li jerġa' jinbeda",
"Songs": "Kanzunetti",
"StartupEmbyServerIsLoading": "Jellyfin Server qed jixgħel. Jekk jogħġbok erġa' pprova dalwaqt.",
"Sync": "Sinkronizza",
"System": "Sistema",
"Undefined": "Mhux Definit",
"User": "Utent",
"UserCreatedWithName": "L-utent {0} inħoloq",
"UserDeletedWithName": "L-utent {0} tħassar",
"UserDownloadingItemWithValues": "{0} qed iniżżel {1}",
"UserLockedOutWithName": "L-utent {0} ġie msakkar",
"UserOfflineFromDevice": "{0} skonnettja minn {1}",
"UserOnlineFromDevice": "{0} huwa online minn {1}",
"NotificationOptionPluginUpdateInstalled": "Aġġornament ta' plugin ġie installat",
"NotificationOptionVideoPlayback": "Il-playback tal-vidjow beda",
"NotificationOptionVideoPlaybackStopped": "Il-playback tal-vidjow waqaf",
"Shows": "Programmi",
"TvShows": "Programmi tat-TV",
"UserPolicyUpdatedWithName": "Il-policy tal-utent ġiet aġġornata għal {0}",
"UserStartedPlayingItemWithValues": "{0} qed iħaddem {1} fuq {2}",
"UserStoppedPlayingItemWithValues": "{0} waqaf iħaddem {1} fuq {2}",
"ValueHasBeenAddedToLibrary": "{0} ġie miżjud mal-librerija tal-midja tiegħek",
"ValueSpecialEpisodeName": "Speċjali - {0}",
"VersionNumber": "Verżjoni {0}",
"TasksMaintenanceCategory": "Manutenzjoni",
"TasksLibraryCategory": "Librerija",
"TasksApplicationCategory": "Applikazzjoni",
"TasksChannelsCategory": "Kanali tal-Internet",
"TaskCleanActivityLog": "Naddaf il-Logg tal-Attività",
"TaskCleanActivityLogDescription": "Iħassar l-entrati tar-reġistru tal-attività eqdem mill-età kkonfigurata.",
"TaskCleanCache": "Naddaf id-Direttorju tal-Cache",
"TaskCleanCacheDescription": "Iħassar il-fajls tal-cache li m'għadhomx meħtieġa mis-sistema.",
"TaskRefreshChapterImages": "Oħroġ l-Immaġini tal-Kapitolu",
"TaskRefreshChapterImagesDescription": "Joħloq thumbnails għal vidjows li għandhom kapitli.",
"TaskAudioNormalization": "Normalizzazzjoni Awdjo",
"TaskAudioNormalizationDescription": "Skennja fajls għal data ta' normalizzazzjoni awdjo.",
"TaskRefreshLibrary": "Skennja l-Librerija tal-Midja",
"TaskRefreshLibraryDescription": "Jiskennja l-librerija tal-midja tiegħek għal fajls ġodda u jġedded il-metadejta.",
"TaskCleanLogs": "Naddaf id-Direttorju tal-Logg",
"TaskCleanLogsDescription": "Iħassar fajls tal-logg eqdem minn {0} ijiem.",
"TaskRefreshPeople": "Aġġorna Persuni",
"TaskRefreshPeopleDescription": "Jaġġorna l-metadejta għall-atturi u d-diretturi fil-librerija tal-midja tiegħek.",
"TaskRefreshTrickplayImages": "Iġġenera Stampi Trickplay",
"TaskRefreshTrickplayImagesDescription": "Joħloq previews trickplay għal vidjows fil-libreriji attivati.",
"TaskUpdatePlugins": "Aġġorna il-Plugins",
"TaskCleanTranscode": "Naddaf id-Direttorju tat-Transcode",
"TaskCleanTranscodeDescription": "Iħassar fajls transcode eqdem minn ġurnata.",
"TaskRefreshChannels": "Aġġorna l-Kanali",
"TaskRefreshChannelsDescription": "Aġġorna l-informazzjoni tal-kanali tal-internet.",
"TaskDownloadMissingSubtitles": "Niżżel is-sottotitli nieqsa",
"TaskOptimizeDatabase": "Ottimizza d-database",
"TaskKeyframeExtractor": "Estrattur ta' Keyframes",
"TaskKeyframeExtractorDescription": "Jiġbed il-keyframes mill-fajls tal-vidjow biex joħloq playlists HLS aktar preċiżi. Dan il-kompitu jista' jdum għal żmien twil.",
"TaskCleanCollectionsAndPlaylists": "Naddaf il-kollezzjonijiet u l-playlists",
"TaskCleanCollectionsAndPlaylistsDescription": "Ineħħi oġġetti minn kollezzjonijiet u playlists li m'għadhomx jeżistu."
}

@ -11,7 +11,7 @@
"Collections": "Collecties",
"DeviceOfflineWithName": "Verbinding met {0} is verbroken",
"DeviceOnlineWithName": "{0} is verbonden",
"FailedLoginAttemptWithUserName": "Mislukte inlogpoging van {0}",
"FailedLoginAttemptWithUserName": "Mislukte aanmeldpoging van {0}",
"Favorites": "Favorieten",
"Folders": "Mappen",
"Genres": "Genres",

@ -11,7 +11,7 @@
"Collections": "Kolekcje",
"DeviceOfflineWithName": "{0} został rozłączony",
"DeviceOnlineWithName": "{0} połączył się",
"FailedLoginAttemptWithUserName": "Próba logowania przez {0} zakończona niepowodzeniem",
"FailedLoginAttemptWithUserName": "Nieudana próba logowania przez {0}",
"Favorites": "Ulubione",
"Folders": "Foldery",
"Genres": "Gatunki",

@ -111,7 +111,7 @@
"TaskCleanCacheDescription": "Deletar arquivos temporários que não são mais necessários para o sistema.",
"TaskCleanCache": "Limpar Arquivos Temporários",
"TasksChannelsCategory": "Canais da Internet",
"TasksApplicationCategory": "Aplicativo",
"TasksApplicationCategory": "Aplicação",
"TasksLibraryCategory": "Biblioteca",
"TasksMaintenanceCategory": "Manutenção",
"TaskCleanActivityLogDescription": "Apaga o registro de atividades mais antigo que a idade configurada.",
@ -128,5 +128,7 @@
"TaskRefreshTrickplayImages": "Gerar imagens Trickplay",
"TaskRefreshTrickplayImagesDescription": "Cria prévias Trickplay para vídeos em bibliotecas em que o recurso está habilitado.",
"TaskCleanCollectionsAndPlaylists": "Limpe coleções e playlists",
"TaskCleanCollectionsAndPlaylistsDescription": "Remove itens de coleções e playlists que não existem mais."
"TaskCleanCollectionsAndPlaylistsDescription": "Remove itens de coleções e playlists que não existem mais.",
"TaskAudioNormalization": "Normalização de áudio",
"TaskAudioNormalizationDescription": "Examina os ficheiros em busca de dados de normalização de áudio."
}

@ -125,5 +125,9 @@
"External": "வெளி",
"HearingImpaired": "செவித்திறன் குறைபாடுடையவர்",
"TaskRefreshTrickplayImages": "முன்னோட்ட படங்களை உருவாக்கு",
"TaskRefreshTrickplayImagesDescription": "செயல்பாட்டில் உள்ள தொகுப்புகளுக்கு முன்னோட்ட படங்களை உருவாக்கும்."
"TaskRefreshTrickplayImagesDescription": "செயல்பாட்டில் உள்ள தொகுப்புகளுக்கு முன்னோட்ட படங்களை உருவாக்கும்.",
"TaskCleanCollectionsAndPlaylists": "சேகரிப்புகள் மற்றும் பிளேலிஸ்ட்களை சுத்தம் செய்யவும்",
"TaskCleanCollectionsAndPlaylistsDescription": "சேகரிப்புகள் மற்றும் பிளேலிஸ்ட்களில் இருந்து உருப்படிகளை நீக்குகிறது.",
"TaskAudioNormalization": "ஆடியோ இயல்பாக்கம்",
"TaskAudioNormalizationDescription": "ஆடியோ இயல்பாக்குதல் தரவுக்காக கோப்புகளை ஸ்கேன் செய்கிறது."
}

@ -321,7 +321,11 @@ namespace Emby.Server.Implementations.Localization
// Try splitting by : to handle "Germany: FSK-18"
if (rating.Contains(':', StringComparison.OrdinalIgnoreCase))
{
return GetRatingLevel(rating.AsSpan().RightPart(':').ToString());
var ratingLevelRightPart = rating.AsSpan().RightPart(':');
if (ratingLevelRightPart.Length != 0)
{
return GetRatingLevel(ratingLevelRightPart.ToString());
}
}
// Handle prefix country code to handle "DE-18"
@ -332,8 +336,12 @@ namespace Emby.Server.Implementations.Localization
// Extract culture from country prefix
var culture = FindLanguageInfo(ratingSpan.LeftPart('-').ToString());
// Check rating system of culture
return GetRatingLevel(ratingSpan.RightPart('-').ToString(), culture?.TwoLetterISOLanguageName);
var ratingLevelRightPart = ratingSpan.RightPart('-');
if (ratingLevelRightPart.Length != 0)
{
// Check rating system of culture
return GetRatingLevel(ratingLevelRightPart.ToString(), culture?.TwoLetterISOLanguageName);
}
}
return null;

@ -90,7 +90,7 @@ public class PlaystateController : BaseJellyfinApiController
return NotFound();
}
var session = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var session = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext, userId).ConfigureAwait(false);
var dto = UpdatePlayedStatus(user, item, true, datePlayed);
foreach (var additionalUserInfo in session.AdditionalUsers)
@ -155,7 +155,7 @@ public class PlaystateController : BaseJellyfinApiController
return NotFound();
}
var session = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var session = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext, userId).ConfigureAwait(false);
var dto = UpdatePlayedStatus(user, item, false, null);
foreach (var additionalUserInfo in session.AdditionalUsers)

@ -117,10 +117,10 @@ public static class RequestHelpers
return user.EnableUserPreferenceAccess;
}
internal static async Task<SessionInfo> GetSession(ISessionManager sessionManager, IUserManager userManager, HttpContext httpContext)
internal static async Task<SessionInfo> GetSession(ISessionManager sessionManager, IUserManager userManager, HttpContext httpContext, Guid? userId = null)
{
var userId = httpContext.User.GetUserId();
var user = userManager.GetUserById(userId);
userId ??= httpContext.User.GetUserId();
var user = userManager.GetUserById(userId.Value);
var session = await sessionManager.LogSessionActivity(
httpContext.User.GetClient(),
httpContext.User.GetVersion(),

@ -18,7 +18,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Data</PackageId>
<VersionPrefix>10.9.0</VersionPrefix>
<VersionPrefix>10.10.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

@ -32,24 +32,20 @@ public class AddDefaultCastReceivers : IMigrationRoutine
/// <inheritdoc />
public void Perform()
{
// Only add if receiver list is empty.
if (_serverConfigurationManager.Configuration.CastReceiverApplications.Length == 0)
{
_serverConfigurationManager.Configuration.CastReceiverApplications = new CastReceiverApplication[]
_serverConfigurationManager.Configuration.CastReceiverApplications =
[
new()
{
new()
{
Id = "F007D354",
Name = "Stable"
},
new()
{
Id = "6F511C87",
Name = "Unstable"
}
};
_serverConfigurationManager.SaveConfiguration();
}
Id = "F007D354",
Name = "Stable"
},
new()
{
Id = "6F511C87",
Name = "Unstable"
}
];
_serverConfigurationManager.SaveConfiguration();
}
}

@ -185,6 +185,7 @@ namespace Jellyfin.Server
}
catch (Exception ex)
{
_restartOnShutdown = false;
_logger.LogCritical(ex, "Error while starting server");
}
finally

@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Common</PackageId>
<VersionPrefix>10.9.0</VersionPrefix>
<VersionPrefix>10.10.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

@ -421,13 +421,12 @@ namespace MediaBrowser.Controller.Entities
validChildren.Add(child);
}
// That's all the new and changed ones - now see if any have been removed and need cleanup
var itemsRemoved = currentChildren.Values.Except(validChildren).ToList();
var shouldRemove = !IsRoot || allowRemoveRoot;
// If it's an AggregateFolder, don't remove
if (shouldRemove && currentChildren.Count != validChildren.Count)
if (shouldRemove && itemsRemoved.Count > 0)
{
// That's all the new and changed ones - now see if there are any that are missing
var itemsRemoved = currentChildren.Values.Except(validChildren).ToList();
foreach (var item in itemsRemoved)
{
if (item.IsFileProtocol)
@ -595,7 +594,7 @@ namespace MediaBrowser.Controller.Entities
}
var fanoutConcurrency = ConfigurationManager.Configuration.LibraryScanFanoutConcurrency;
var parallelism = fanoutConcurrency > 0 ? fanoutConcurrency : 2 * Environment.ProcessorCount;
var parallelism = fanoutConcurrency > 0 ? fanoutConcurrency : Environment.ProcessorCount;
var actionBlock = new ActionBlock<int>(
async i =>

@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Controller</PackageId>
<VersionPrefix>10.9.0</VersionPrefix>
<VersionPrefix>10.10.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

@ -82,6 +82,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
"av1_amf",
"h264_qsv",
"hevc_qsv",
"mjpeg_qsv",
"av1_qsv",
"h264_nvenc",
"hevc_nvenc",
@ -89,6 +90,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
"h264_vaapi",
"hevc_vaapi",
"av1_vaapi",
"mjpeg_vaapi",
"h264_v4l2m2m",
"h264_videotoolbox",
"hevc_videotoolbox",

@ -848,7 +848,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
inputArg = "-threads " + threads + " " + inputArg; // HW accel args set a different input thread count, only set if disabled
}
var filterParam = encodingHelper.GetVideoProcessingFilterParam(jobState, options, jobState.OutputVideoCodec).Trim();
var filterParam = encodingHelper.GetVideoProcessingFilterParam(jobState, options, vidEncoder).Trim();
if (string.IsNullOrWhiteSpace(filterParam))
{
throw new InvalidOperationException("EncodingHelper returned empty or invalid filter parameters.");

@ -21,7 +21,7 @@ namespace MediaBrowser.Model.Configuration
AutomaticallyAddToCollection = false;
EnablePhotos = true;
SaveSubtitlesWithMedia = true;
SaveLyricsWithMedia = true;
SaveLyricsWithMedia = false;
PathInfos = Array.Empty<MediaPathInfo>();
EnableAutomaticSeriesGrouping = true;
SeasonZeroDisplayName = "Specials";
@ -94,7 +94,7 @@ namespace MediaBrowser.Model.Configuration
public bool SaveSubtitlesWithMedia { get; set; }
[DefaultValue(true)]
[DefaultValue(false)]
public bool SaveLyricsWithMedia { get; set; }
public bool AutomaticallyAddToCollection { get; set; }

@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Model</PackageId>
<VersionPrefix>10.9.0</VersionPrefix>
<VersionPrefix>10.10.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
@ -33,7 +33,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.HttpOverrides" />
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="MimeTypes">
<PrivateAssets>all</PrivateAssets>

@ -1,4 +1,4 @@
using System.Reflection;
[assembly: AssemblyVersion("10.9.0")]
[assembly: AssemblyFileVersion("10.9.0")]
[assembly: AssemblyVersion("10.10.0")]
[assembly: AssemblyFileVersion("10.10.0")]

@ -8,12 +8,14 @@
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<!-- ICU4N.Transliterator only has prerelease versions -->
<NoWarn>NU5104</NoWarn>
</PropertyGroup>
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Extensions</PackageId>
<VersionPrefix>10.9.0</VersionPrefix>
<VersionPrefix>10.10.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

@ -123,7 +123,7 @@ namespace Jellyfin.LiveTv.TunerHosts
return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper);
}
}
else if (response.StatusCode == HttpStatusCode.MethodNotAllowed)
else if (response.StatusCode == HttpStatusCode.MethodNotAllowed || response.StatusCode == HttpStatusCode.NotImplemented)
{
// Fallback to check path extension when the server does not support HEAD method
// Use UriBuilder to remove all query string as GetExtension will include them when used directly

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
@ -11,7 +9,6 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Model.ApiClient;
using MediaBrowser.Model.Net;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
@ -51,25 +48,6 @@ public sealed class AutoDiscoveryHost : BackgroundService
_networkManager = networkManager;
}
private static IPAddress GetBindAddress(IPData intf)
{
if (intf.AddressFamily == AddressFamily.InterNetwork)
{
if (OperatingSystem.IsLinux())
{
return NetworkUtils.GetBroadcastAddress(intf.Subnet);
}
if (OperatingSystem.IsMacOS())
{
// macOS kernel does not allow bind to 127:255:255:255
return IPAddress.IsLoopback(intf.Address) ? intf.Address : NetworkUtils.GetBroadcastAddress(intf.Subnet);
}
}
return intf.Address;
}
/// <inheritdoc />
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
@ -79,23 +57,10 @@ public sealed class AutoDiscoveryHost : BackgroundService
return;
}
var udpServers = new List<Task>();
// Linux needs to bind to the broadcast addresses to receive broadcast traffic
if (OperatingSystem.IsLinux() && networkConfig.EnableIPv4)
{
udpServers.Add(ListenForAutoDiscoveryMessage(IPAddress.Broadcast, IPAddress.Broadcast, stoppingToken));
}
udpServers.AddRange(_networkManager.GetInternalBindAddresses()
.Select(intf => ListenForAutoDiscoveryMessage(
GetBindAddress(intf),
intf.Address,
stoppingToken)));
await Task.WhenAll(udpServers).ConfigureAwait(false);
await ListenForAutoDiscoveryMessage(IPAddress.Any, stoppingToken).ConfigureAwait(false);
}
private async Task ListenForAutoDiscoveryMessage(IPAddress listenAddress, IPAddress respondAddress, CancellationToken cancellationToken)
private async Task ListenForAutoDiscoveryMessage(IPAddress listenAddress, CancellationToken cancellationToken)
{
try
{
@ -110,7 +75,7 @@ public sealed class AutoDiscoveryHost : BackgroundService
var text = Encoding.UTF8.GetString(result.Buffer);
if (text.Contains("who is JellyfinServer?", StringComparison.OrdinalIgnoreCase))
{
await RespondToV2Message(respondAddress, result.RemoteEndPoint, cancellationToken).ConfigureAwait(false);
await RespondToV2Message(result.RemoteEndPoint, udpClient, cancellationToken).ConfigureAwait(false);
}
}
catch (SocketException ex)
@ -130,22 +95,20 @@ public sealed class AutoDiscoveryHost : BackgroundService
}
}
private async Task RespondToV2Message(IPAddress responderIp, IPEndPoint endpoint, CancellationToken cancellationToken)
private async Task RespondToV2Message(IPEndPoint endpoint, UdpClient broadCastUdpClient, CancellationToken cancellationToken)
{
var localUrl = _appHost.GetSmartApiUrl(endpoint.Address);
if (string.IsNullOrEmpty(localUrl))
{
_logger.LogWarning("Unable to respond to server discovery request because the local ip address could not be determined.");
_logger.LogWarning("Unable to respond to server discovery request because the local ip address could not be determined");
return;
}
var response = new ServerDiscoveryInfo(localUrl, _appHost.SystemId, _appHost.FriendlyName);
using var responder = new UdpClient(new IPEndPoint(responderIp, PortNumber));
try
{
_logger.LogDebug("Sending AutoDiscovery response");
await responder
await broadCastUdpClient
.SendAsync(JsonSerializer.SerializeToUtf8Bytes(response).AsMemory(), endpoint, cancellationToken)
.ConfigureAwait(false);
}

@ -412,7 +412,9 @@ public class NetworkManager : INetworkManager, IDisposable
interfaces.RemoveAll(x => x.AddressFamily == AddressFamily.InterNetworkV6);
}
_interfaces = interfaces;
// Users may have complex networking configuration that multiple interfaces sharing the same IP address
// Only return one IP for binding, and let the OS handle the rest
_interfaces = interfaces.DistinctBy(iface => iface.Address).ToList();
}
}
@ -1017,7 +1019,7 @@ public class NetworkManager : INetworkManager, IDisposable
result = string.Empty;
int count = _interfaces.Count;
if (count == 1 && (_interfaces[0].Equals(IPAddress.Any) || _interfaces[0].Equals(IPAddress.IPv6Any)))
if (count == 1 && (_interfaces[0].Address.Equals(IPAddress.Any) || _interfaces[0].Address.Equals(IPAddress.IPv6Any)))
{
// Ignore IPAny addresses.
count = 0;
@ -1049,7 +1051,7 @@ public class NetworkManager : INetworkManager, IDisposable
return true;
}
_logger.LogWarning("{Source}: External request received, no matching external bind address found, trying internal addresses.", source);
_logger.LogDebug("{Source}: External request received, no matching external bind address found, trying internal addresses", source);
}
else
{
@ -1087,7 +1089,7 @@ public class NetworkManager : INetworkManager, IDisposable
if (extResult.Length == 0)
{
result = string.Empty;
_logger.LogWarning("{Source}: External request received, but no external interface found. Need to route through internal network.", source);
_logger.LogDebug("{Source}: External request received, but no external interface found. Need to route through internal network", source);
return false;
}

@ -20,26 +20,37 @@ namespace Jellyfin.Server.Implementations.Tests.IO
_sut = _fixture.Create<ManagedFileSystem>();
}
[Theory]
[SkippableTheory]
[InlineData("/Volumes/Library/Sample/Music/Playlists/", "../Beethoven/Misc/Moonlight Sonata.mp3", "/Volumes/Library/Sample/Music/Beethoven/Misc/Moonlight Sonata.mp3")]
[InlineData("/Volumes/Library/Sample/Music/Playlists/", "../../Beethoven/Misc/Moonlight Sonata.mp3", "/Volumes/Library/Sample/Beethoven/Misc/Moonlight Sonata.mp3")]
[InlineData("/Volumes/Library/Sample/Music/Playlists/", "Beethoven/Misc/Moonlight Sonata.mp3", "/Volumes/Library/Sample/Music/Playlists/Beethoven/Misc/Moonlight Sonata.mp3")]
public void MakeAbsolutePathCorrectlyHandlesRelativeFilePaths(
[InlineData("/Volumes/Library/Sample/Music/Playlists/", "/mnt/Beethoven/Misc/Moonlight Sonata.mp3", "/mnt/Beethoven/Misc/Moonlight Sonata.mp3")]
public void MakeAbsolutePathCorrectlyHandlesRelativeFilePathsOnUnixLike(
string folderPath,
string filePath,
string expectedAbsolutePath)
{
Skip.If(OperatingSystem.IsWindows());
var generatedPath = _sut.MakeAbsolutePath(folderPath, filePath);
Assert.Equal(expectedAbsolutePath, generatedPath);
}
[SkippableTheory]
[InlineData(@"C:\\Volumes\Library\Sample\Music\Playlists\", @"..\Beethoven\Misc\Moonlight Sonata.mp3", @"C:\Volumes\Library\Sample\Music\Beethoven\Misc\Moonlight Sonata.mp3")]
[InlineData(@"C:\\Volumes\Library\Sample\Music\Playlists\", @"..\..\Beethoven\Misc\Moonlight Sonata.mp3", @"C:\Volumes\Library\Sample\Beethoven\Misc\Moonlight Sonata.mp3")]
[InlineData(@"C:\\Volumes\Library\Sample\Music\Playlists\", @"Beethoven\Misc\Moonlight Sonata.mp3", @"C:\Volumes\Library\Sample\Music\Playlists\Beethoven\Misc\Moonlight Sonata.mp3")]
[InlineData(@"C:\\Volumes\Library\Sample\Music\Playlists\", @"D:\\Beethoven\Misc\Moonlight Sonata.mp3", @"D:\\Beethoven\Misc\Moonlight Sonata.mp3")]
public void MakeAbsolutePathCorrectlyHandlesRelativeFilePathsOnWindows(
string folderPath,
string filePath,
string expectedAbsolutePath)
{
Skip.If(!OperatingSystem.IsWindows());
var generatedPath = _sut.MakeAbsolutePath(folderPath, filePath);
if (OperatingSystem.IsWindows())
{
var expectedWindowsPath = expectedAbsolutePath.Replace('/', '\\');
Assert.Equal(expectedWindowsPath, generatedPath.Split(':')[1]);
}
else
{
Assert.Equal(expectedAbsolutePath, generatedPath);
}
Assert.Equal(expectedAbsolutePath, generatedPath);
}
[Theory]

@ -1,5 +1,6 @@
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Emby.Server.Implementations.Localization;
using MediaBrowser.Controller.Configuration;
@ -157,6 +158,20 @@ namespace Jellyfin.Server.Implementations.Tests.Localization
Assert.Null(localizationManager.GetRatingLevel("n/a"));
}
[Theory]
[InlineData("-NO RATING SHOWN-")]
[InlineData(":NO RATING SHOWN:")]
public async Task GetRatingLevel_Split_Success(string value)
{
var localizationManager = Setup(new ServerConfiguration()
{
UICulture = "en-US"
});
await localizationManager.LoadAll();
Assert.Null(localizationManager.GetRatingLevel(value));
}
[Theory]
[InlineData("Default", "Default")]
[InlineData("HeaderLiveTV", "Live TV")]

Loading…
Cancel
Save