aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.config/dotnet-tools.json2
-rw-r--r--.devcontainer/Dev - Server Ffmpeg/devcontainer.json28
-rw-r--r--.devcontainer/devcontainer.json8
-rw-r--r--.devcontainer/install-ffmpeg.sh (renamed from .devcontainer/Dev - Server Ffmpeg/install-ffmpeg.sh)2
-rw-r--r--.github/ISSUE_TEMPLATE/issue report.yml2
-rw-r--r--.github/workflows/ci-codeql-analysis.yml12
-rw-r--r--.github/workflows/ci-compat.yml14
-rw-r--r--.github/workflows/ci-openapi.yml20
-rw-r--r--.github/workflows/ci-tests.yml8
-rw-r--r--.github/workflows/commands.yml8
-rw-r--r--.github/workflows/issue-template-check.yml4
-rw-r--r--.github/workflows/release-bump-version.yaml4
-rw-r--r--.vscode/extensions.json11
-rw-r--r--.vscode/launch.json6
-rw-r--r--CONTRIBUTORS.md3
-rw-r--r--Directory.Build.props1
-rw-r--r--Directory.Packages.props68
-rw-r--r--Emby.Naming/Common/NamingOptions.cs8
-rw-r--r--Emby.Naming/Emby.Naming.csproj4
-rw-r--r--Emby.Photos/Emby.Photos.csproj2
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs2
-rw-r--r--Emby.Server.Implementations/ConfigurationOptions.cs1
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj2
-rw-r--r--Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs1
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs2
-rw-r--r--Emby.Server.Implementations/Library/UserDataManager.cs1
-rw-r--r--Emby.Server.Implementations/Localization/Core/ar.json5
-rw-r--r--Emby.Server.Implementations/Localization/Core/bg-BG.json10
-rw-r--r--Emby.Server.Implementations/Localization/Core/ca.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/da.json50
-rw-r--r--Emby.Server.Implementations/Localization/Core/el.json8
-rw-r--r--Emby.Server.Implementations/Localization/Core/es_419.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/fi.json7
-rw-r--r--Emby.Server.Implementations/Localization/Core/ga.json143
-rw-r--r--Emby.Server.Implementations/Localization/Core/he.json14
-rw-r--r--Emby.Server.Implementations/Localization/Core/hi.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/hr.json8
-rw-r--r--Emby.Server.Implementations/Localization/Core/ko.json28
-rw-r--r--Emby.Server.Implementations/Localization/Core/mt.json98
-rw-r--r--Emby.Server.Implementations/Localization/Core/nl.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/ro.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/ru.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/sv.json12
-rw-r--r--Emby.Server.Implementations/Localization/Core/uz.json89
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-HK.json9
-rw-r--r--Emby.Server.Implementations/Playlists/PlaylistManager.cs40
-rw-r--r--Emby.Server.Implementations/Plugins/PluginManager.cs2
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs6
-rw-r--r--Jellyfin.Api/Controllers/ItemRefreshController.cs7
-rw-r--r--Jellyfin.Api/Controllers/ItemUpdateController.cs2
-rw-r--r--Jellyfin.Api/Controllers/LibraryController.cs10
-rw-r--r--Jellyfin.Api/Controllers/LiveTvController.cs4
-rw-r--r--Jellyfin.Api/Controllers/MediaSegmentsController.cs2
-rw-r--r--Jellyfin.Api/Controllers/PlaylistsController.cs10
-rw-r--r--Jellyfin.Api/Jellyfin.Api.csproj2
-rw-r--r--Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs5
-rw-r--r--Jellyfin.Data/Entities/User.cs2
-rw-r--r--Jellyfin.Data/Jellyfin.Data.csproj4
-rw-r--r--Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj2
-rw-r--r--Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs40
-rw-r--r--Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs2
-rw-r--r--Jellyfin.Server.Implementations/Users/UserManager.cs2
-rw-r--r--Jellyfin.Server/Helpers/StartupHelpers.cs8
-rw-r--r--Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs20
-rw-r--r--Jellyfin.Server/Jellyfin.Server.csproj2
-rw-r--r--Jellyfin.Server/Migrations/MigrationRunner.cs3
-rw-r--r--Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs4
-rw-r--r--Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs69
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj4
-rw-r--r--MediaBrowser.Common/Plugins/BasePluginOfT.cs2
-rw-r--r--MediaBrowser.Controller/Entities/LinkedChild.cs7
-rw-r--r--MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs13
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj4
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs56
-rw-r--r--MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs12
-rw-r--r--MediaBrowser.Controller/Playlists/IPlaylistManager.cs3
-rw-r--r--MediaBrowser.Controller/Providers/IProviderManager.cs3
-rw-r--r--MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj2
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs10
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj2
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs2
-rw-r--r--MediaBrowser.Model/Configuration/LibraryOptions.cs4
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs8
-rw-r--r--MediaBrowser.Model/Extensions/LibraryOptionsExtension.cs6
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj6
-rw-r--r--MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs2
-rw-r--r--MediaBrowser.Providers/Manager/ItemImageProvider.cs8
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs22
-rw-r--r--MediaBrowser.Providers/MediaBrowser.Providers.csproj2
-rw-r--r--MediaBrowser.Providers/MediaInfo/AudioFileProber.cs28
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs2
-rw-r--r--MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj2
-rw-r--r--README.md4
-rw-r--r--SharedVersion.cs4
-rwxr-xr-xbump_version4
-rw-r--r--fuzz/Emby.Server.Implementations.Fuzz/Emby.Server.Implementations.Fuzz.csproj2
-rwxr-xr-xfuzz/Emby.Server.Implementations.Fuzz/fuzz.sh2
-rw-r--r--fuzz/Jellyfin.Api.Fuzz/Jellyfin.Api.Fuzz.csproj2
-rwxr-xr-xfuzz/Jellyfin.Api.Fuzz/fuzz.sh2
-rw-r--r--global.json2
-rw-r--r--jellyfin.ruleset4
-rw-r--r--src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj2
-rw-r--r--src/Jellyfin.Drawing/Jellyfin.Drawing.csproj2
-rw-r--r--src/Jellyfin.Extensions/Jellyfin.Extensions.csproj4
-rw-r--r--src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs14
-rw-r--r--src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj2
-rw-r--r--src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj2
-rw-r--r--src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj2
-rw-r--r--src/Jellyfin.Networking/Jellyfin.Networking.csproj2
-rw-r--r--src/Jellyfin.Networking/Manager/NetworkManager.cs2
-rw-r--r--tests/Directory.Build.props2
-rw-r--r--tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyArrayModel.cs2
-rw-r--r--tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyIReadOnlyListModel.cs2
-rw-r--r--tests/Jellyfin.LiveTv.Tests/Jellyfin.LiveTv.Tests.csproj4
-rw-r--r--tests/Jellyfin.LiveTv.Tests/Listings/ListingsManagerTests.cs50
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs2
-rw-r--r--tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs3
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/TypedBaseItem/BaseItemKindTests.cs2
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Users/UserManagerTests.cs3
120 files changed, 919 insertions, 401 deletions
diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index a3847dcdf..02afa3f07 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"dotnet-ef": {
- "version": "8.0.8",
+ "version": "8.0.11",
"commands": [
"dotnet-ef"
]
diff --git a/.devcontainer/Dev - Server Ffmpeg/devcontainer.json b/.devcontainer/Dev - Server Ffmpeg/devcontainer.json
deleted file mode 100644
index 0b848d9f3..000000000
--- a/.devcontainer/Dev - Server Ffmpeg/devcontainer.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "name": "Development Jellyfin Server - FFmpeg",
- "image":"mcr.microsoft.com/devcontainers/dotnet:8.0-jammy",
- // restores nuget packages, installs the dotnet workloads and installs the dev https certificate
- "postStartCommand": "dotnet restore; dotnet workload update; dotnet dev-certs https --trust; sudo bash \"./.devcontainer/Dev - Server Ffmpeg/install-ffmpeg.sh\"",
- // reads the extensions list and installs them
- "postAttachCommand": "cat .vscode/extensions.json | jq -r .recommendations[] | xargs -n 1 code --install-extension",
- "features": {
- "ghcr.io/devcontainers/features/dotnet:2": {
- "version": "none",
- "dotnetRuntimeVersions": "8.0",
- "aspNetCoreRuntimeVersions": "8.0"
- },
- "ghcr.io/devcontainers-contrib/features/apt-packages:1": {
- "preserve_apt_list": false,
- "packages": ["libfontconfig1"]
- },
- "ghcr.io/devcontainers/features/docker-in-docker:2": {
- "dockerDashComposeVersion": "v2"
- },
- "ghcr.io/devcontainers/features/github-cli:1": {},
- "ghcr.io/eitsupi/devcontainer-features/jq-likes:2": {}
- },
- "hostRequirements": {
- "memory": "8gb",
- "cpus": 4
- }
-}
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 063901c80..228d4a17c 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -1,15 +1,15 @@
{
"name": "Development Jellyfin Server",
- "image":"mcr.microsoft.com/devcontainers/dotnet:8.0-jammy",
+ "image":"mcr.microsoft.com/devcontainers/dotnet:9.0-bookworm",
// restores nuget packages, installs the dotnet workloads and installs the dev https certificate
- "postStartCommand": "dotnet restore; dotnet workload update; dotnet dev-certs https --trust",
+ "postStartCommand": "sudo dotnet restore; sudo dotnet workload update; sudo dotnet dev-certs https --trust; sudo bash \"./.devcontainer/install-ffmpeg.sh\"",
// reads the extensions list and installs them
"postAttachCommand": "cat .vscode/extensions.json | jq -r .recommendations[] | xargs -n 1 code --install-extension",
"features": {
"ghcr.io/devcontainers/features/dotnet:2": {
"version": "none",
- "dotnetRuntimeVersions": "8.0",
- "aspNetCoreRuntimeVersions": "8.0"
+ "dotnetRuntimeVersions": "9.0",
+ "aspNetCoreRuntimeVersions": "9.0"
},
"ghcr.io/devcontainers-contrib/features/apt-packages:1": {
"preserve_apt_list": false,
diff --git a/.devcontainer/Dev - Server Ffmpeg/install-ffmpeg.sh b/.devcontainer/install-ffmpeg.sh
index c867ef538..842a53255 100644
--- a/.devcontainer/Dev - Server Ffmpeg/install-ffmpeg.sh
+++ b/.devcontainer/install-ffmpeg.sh
@@ -29,4 +29,4 @@ Signed-By: /etc/apt/keyrings/jellyfin.gpg
EOF
sudo apt update -y
-sudo apt install jellyfin-ffmpeg6 -y
+sudo apt install jellyfin-ffmpeg7 -y
diff --git a/.github/ISSUE_TEMPLATE/issue report.yml b/.github/ISSUE_TEMPLATE/issue report.yml
index b52241208..9181a1e7d 100644
--- a/.github/ISSUE_TEMPLATE/issue report.yml
+++ b/.github/ISSUE_TEMPLATE/issue report.yml
@@ -86,7 +86,7 @@ body:
label: Jellyfin Server version
description: What version of Jellyfin are you using?
options:
- - 10.9.11+
+ - 10.10.0+
- Master
- Unstable
- Older*
diff --git a/.github/workflows/ci-codeql-analysis.yml b/.github/workflows/ci-codeql-analysis.yml
index 6ec21dc84..81be3882a 100644
--- a/.github/workflows/ci-codeql-analysis.yml
+++ b/.github/workflows/ci-codeql-analysis.yml
@@ -20,18 +20,18 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup .NET
- uses: actions/setup-dotnet@6bd8b7f7774af54e05809fcc5431931b3eb1ddee # v4.0.1
+ uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
with:
- dotnet-version: '8.0.x'
+ dotnet-version: '9.0.x'
- name: Initialize CodeQL
- uses: github/codeql-action/init@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0
+ uses: github/codeql-action/init@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5
with:
languages: ${{ matrix.language }}
queries: +security-extended
- name: Autobuild
- uses: github/codeql-action/autobuild@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0
+ uses: github/codeql-action/autobuild@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0
+ uses: github/codeql-action/analyze@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5
diff --git a/.github/workflows/ci-compat.yml b/.github/workflows/ci-compat.yml
index 3dde5f21f..2f9a68f41 100644
--- a/.github/workflows/ci-compat.yml
+++ b/.github/workflows/ci-compat.yml
@@ -11,11 +11,16 @@ jobs:
permissions: read-all
steps:
- name: Checkout repository
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
+ - name: Setup .NET
+ uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
+ with:
+ dotnet-version: '9.0.x'
+
- name: Build
run: |
dotnet build Jellyfin.Server -o ./out
@@ -35,12 +40,17 @@ jobs:
permissions: read-all
steps:
- name: Checkout repository
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
fetch-depth: 0
+ - name: Setup .NET
+ uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
+ with:
+ dotnet-version: '9.0.x'
+
- name: Checkout common ancestor
env:
HEAD_REF: ${{ github.head_ref }}
diff --git a/.github/workflows/ci-openapi.yml b/.github/workflows/ci-openapi.yml
index 399c0085b..25b4b9f81 100644
--- a/.github/workflows/ci-openapi.yml
+++ b/.github/workflows/ci-openapi.yml
@@ -16,14 +16,14 @@ jobs:
permissions: read-all
steps:
- name: Checkout repository
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
- name: Setup .NET
- uses: actions/setup-dotnet@6bd8b7f7774af54e05809fcc5431931b3eb1ddee # v4.0.1
+ uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
with:
- dotnet-version: '8.0.x'
+ dotnet-version: '9.0.x'
- name: Generate openapi.json
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
- name: Upload openapi.json
@@ -32,7 +32,7 @@ jobs:
name: openapi-head
retention-days: 14
if-no-files-found: error
- path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net8.0/openapi.json
+ path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net9.0/openapi.json
openapi-base:
name: OpenAPI - BASE
@@ -41,7 +41,7 @@ jobs:
permissions: read-all
steps:
- name: Checkout repository
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
@@ -55,9 +55,9 @@ jobs:
ANCESTOR_REF=$(git merge-base upstream/${{ github.base_ref }} origin/$HEAD_REF)
git checkout --progress --force $ANCESTOR_REF
- name: Setup .NET
- uses: actions/setup-dotnet@6bd8b7f7774af54e05809fcc5431931b3eb1ddee # v4.0.1
+ uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
with:
- dotnet-version: '8.0.x'
+ dotnet-version: '9.0.x'
- name: Generate openapi.json
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
- name: Upload openapi.json
@@ -66,7 +66,7 @@ jobs:
name: openapi-base
retention-days: 14
if-no-files-found: error
- path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net8.0/openapi.json
+ path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net9.0/openapi.json
openapi-diff:
permissions:
@@ -172,7 +172,7 @@ jobs:
strip_components: 1
target: "/srv/incoming/openapi/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
- name: Move openapi.json (unstable) into place
- uses: appleboy/ssh-action@25ce8cbbcb08177468c7ff7ec5cbfa236f9341e1 # v1.1.0
+ uses: appleboy/ssh-action@7eaf76671a0d7eec5d98ee897acda4f968735a17 # v1.2.0
with:
host: "${{ secrets.REPO_HOST }}"
username: "${{ secrets.REPO_USER }}"
@@ -234,7 +234,7 @@ jobs:
strip_components: 1
target: "/srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
- name: Move openapi.json (stable) into place
- uses: appleboy/ssh-action@25ce8cbbcb08177468c7ff7ec5cbfa236f9341e1 # v1.1.0
+ uses: appleboy/ssh-action@7eaf76671a0d7eec5d98ee897acda4f968735a17 # v1.2.0
with:
host: "${{ secrets.REPO_HOST }}"
username: "${{ secrets.REPO_USER }}"
diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml
index 6c9740a85..30aacc7a0 100644
--- a/.github/workflows/ci-tests.yml
+++ b/.github/workflows/ci-tests.yml
@@ -9,7 +9,7 @@ on:
pull_request:
env:
- SDK_VERSION: "8.0.x"
+ SDK_VERSION: "9.0.x"
jobs:
run-tests:
@@ -19,9 +19,9 @@ jobs:
runs-on: "${{ matrix.os }}"
steps:
- - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- - uses: actions/setup-dotnet@6bd8b7f7774af54e05809fcc5431931b3eb1ddee # v4.0.1
+ - uses: actions/setup-dotnet@3e891b0cb619bf60e2c25674b222b8940e2c1c25 # v4.1.0
with:
dotnet-version: ${{ env.SDK_VERSION }}
@@ -34,7 +34,7 @@ jobs:
--verbosity minimal
- name: Merge code coverage results
- uses: danielpalme/ReportGenerator-GitHub-Action@62f9e70ab348d56eee76d446b4db903a85ab0ea8 # v5.3.11
+ uses: danielpalme/ReportGenerator-GitHub-Action@810356ce07a94200154301fb73d878e327b2dd58 # v5.4.1
with:
reports: "**/coverage.cobertura.xml"
targetdir: "merged/"
diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml
index 446483fc1..26b98f973 100644
--- a/.github/workflows/commands.yml
+++ b/.github/workflows/commands.yml
@@ -24,7 +24,7 @@ jobs:
reactions: '+1'
- name: Checkout the latest code
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
token: ${{ secrets.JF_BOT_TOKEN }}
fetch-depth: 0
@@ -51,7 +51,7 @@ jobs:
reactions: eyes
- name: Checkout the latest code
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
token: ${{ secrets.JF_BOT_TOKEN }}
fetch-depth: 0
@@ -128,11 +128,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: pull in script
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
repository: jellyfin/jellyfin-triage-script
- name: install python
- uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0
+ uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: '3.12'
cache: 'pip'
diff --git a/.github/workflows/issue-template-check.yml b/.github/workflows/issue-template-check.yml
index 04aa66edb..b72e552af 100644
--- a/.github/workflows/issue-template-check.yml
+++ b/.github/workflows/issue-template-check.yml
@@ -10,11 +10,11 @@ jobs:
issues: write
steps:
- name: pull in script
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
repository: jellyfin/jellyfin-triage-script
- name: install python
- uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0
+ uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: '3.12'
cache: 'pip'
diff --git a/.github/workflows/release-bump-version.yaml b/.github/workflows/release-bump-version.yaml
index e0ab4f139..0da9c44f8 100644
--- a/.github/workflows/release-bump-version.yaml
+++ b/.github/workflows/release-bump-version.yaml
@@ -33,7 +33,7 @@ jobs:
yq-version: v4.9.8
- name: Checkout Repository
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ env.TAG_BRANCH }}
@@ -66,7 +66,7 @@ jobs:
NEXT_VERSION: ${{ github.event.inputs.NEXT_VERSION }}
steps:
- name: Checkout Repository
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ env.TAG_BRANCH }}
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index 3be946e44..e4205ce0b 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -1,12 +1,13 @@
{
- "recommendations": [
+ "recommendations": [
"ms-dotnettools.csharp",
"editorconfig.editorconfig",
"github.vscode-github-actions",
"ms-dotnettools.vscode-dotnet-runtime",
- "ms-dotnettools.csdevkit"
- ],
- "unwantedRecommendations": [
+ "ms-dotnettools.csdevkit",
+ "alexcvzz.vscode-sqlite"
+ ],
+ "unwantedRecommendations": [
- ]
+ ]
}
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 7e50d4f0a..d97d8de84 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -6,7 +6,7 @@
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
- "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net8.0/jellyfin.dll",
+ "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net9.0/jellyfin.dll",
"args": [],
"cwd": "${workspaceFolder}/Jellyfin.Server",
"console": "internalConsole",
@@ -22,7 +22,7 @@
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
- "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net8.0/jellyfin.dll",
+ "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net9.0/jellyfin.dll",
"args": ["--nowebclient"],
"cwd": "${workspaceFolder}/Jellyfin.Server",
"console": "internalConsole",
@@ -34,7 +34,7 @@
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
- "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net8.0/jellyfin.dll",
+ "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net9.0/jellyfin.dll",
"args": ["--nowebclient", "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"],
"cwd": "${workspaceFolder}/Jellyfin.Server",
"console": "internalConsole",
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index a9deb1c4a..eccc3b0ce 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -192,6 +192,9 @@
- [jaina heartles](https://github.com/heartles)
- [oxixes](https://github.com/oxixes)
- [elfalem](https://github.com/elfalem)
+ - [Kenneth Cochran](https://github.com/kennethcochran)
+ - [benedikt257](https://github.com/benedikt257)
+ - [revam](https://github.com/revam)
# Emby Contributors
diff --git a/Directory.Build.props b/Directory.Build.props
index 44a60ffb5..831188015 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -8,6 +8,7 @@
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <WarningsNotAsErrors>NU1902;NU1903</WarningsNotAsErrors>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 2e8f654af..7234fdfad 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -4,7 +4,7 @@
</PropertyGroup>
<!-- Run "dotnet list package (dash,dash)outdated" to see the latest versions of each package.-->
<ItemGroup Label="Package Dependencies">
- <PackageVersion Include="AsyncKeyedLock" Version="7.0.2" />
+ <PackageVersion Include="AsyncKeyedLock" Version="7.1.4" />
<PackageVersion Include="AutoFixture.AutoMoq" Version="4.18.1" />
<PackageVersion Include="AutoFixture.Xunit2" Version="4.18.1" />
<PackageVersion Include="AutoFixture" Version="4.18.1" />
@@ -17,37 +17,37 @@
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
<PackageVersion Include="FsCheck.Xunit" Version="2.16.6" />
- <PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="7.3.0.2" />
+ <PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="7.3.0.3" />
<PackageVersion Include="ICU4N.Transliterator" Version="60.1.0-alpha.356" />
<PackageVersion Include="IDisposableAnalyzers" Version="4.0.8" />
<PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
<PackageVersion Include="libse" Version="4.0.8" />
<PackageVersion Include="LrcParser" Version="2024.0728.2" />
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="6.1.0" />
- <PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="8.0.10" />
- <PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.10" />
+ <PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="9.0.0" />
+ <PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" />
- <PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.10" />
- <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.10" />
- <PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.10" />
- <PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.10" />
- <PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.10" />
- <PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
- <PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
- <PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
- <PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
- <PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
- <PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.1" />
- <PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
- <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
- <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.10" />
- <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.10" />
- <PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
- <PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.1" />
- <PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.2" />
- <PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.1" />
- <PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.2" />
- <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
+ <PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.0" />
+ <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0" />
+ <PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.0" />
+ <PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.0" />
+ <PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0" />
+ <PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.0" />
+ <PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.0" />
+ <PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
+ <PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.0" />
+ <PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.0" />
+ <PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.0" />
+ <PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
+ <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
+ <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="9.0.0" />
+ <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.0" />
+ <PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.0" />
+ <PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.0" />
+ <PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
+ <PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.0" />
+ <PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.0" />
+ <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageVersion Include="MimeTypes" Version="2.4.0" />
<PackageVersion Include="Mono.Nat" Version="3.0.4" />
<PackageVersion Include="Moq" Version="4.18.4" />
@@ -60,27 +60,27 @@
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.3" />
<PackageVersion Include="Serilog.Enrichers.Thread" Version="4.0.0" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.4" />
- <PackageVersion Include="Serilog.Sinks.Async" Version="2.0.0" />
+ <PackageVersion Include="Serilog.Sinks.Async" Version="2.1.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageVersion Include="Serilog.Sinks.Graylog" Version="3.1.1" />
<PackageVersion Include="SerilogAnalyzer" Version="0.15.0" />
<PackageVersion Include="SharpFuzz" Version="2.1.1" />
- <PackageVersion Include="SkiaSharp" Version="2.88.8" />
- <PackageVersion Include="SkiaSharp.HarfBuzz" Version="2.88.8" />
- <PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.8" />
+ <PackageVersion Include="SkiaSharp" Version="2.88.9" />
+ <PackageVersion Include="SkiaSharp.HarfBuzz" Version="2.88.9" />
+ <PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.9" />
<PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
- <PackageVersion Include="Svg.Skia" Version="2.0.0.1" />
+ <PackageVersion Include="Svg.Skia" Version="2.0.0.4" />
<PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="6.5.0" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.2.3" />
<PackageVersion Include="System.Globalization" Version="4.3.0" />
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
- <PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" />
- <PackageVersion Include="System.Text.Json" Version="8.0.5" />
- <PackageVersion Include="System.Threading.Tasks.Dataflow" Version="8.0.1" />
+ <PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.0" />
+ <PackageVersion Include="System.Text.Json" Version="9.0.0" />
+ <PackageVersion Include="System.Threading.Tasks.Dataflow" Version="9.0.0" />
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
- <PackageVersion Include="z440.atl.core" Version="6.6.0" />
+ <PackageVersion Include="z440.atl.core" Version="6.8.0" />
<PackageVersion Include="TMDbLib" Version="2.2.0" />
<PackageVersion Include="UTF.Unknown" Version="2.5.1" />
<PackageVersion Include="Xunit.Priority" Version="1.1.6" />
diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs
index 333d237a2..48338daf4 100644
--- a/Emby.Naming/Common/NamingOptions.cs
+++ b/Emby.Naming/Common/NamingOptions.cs
@@ -467,6 +467,14 @@ namespace Emby.Naming.Common
{
IsNamed = true
},
+
+ // Anime style expression
+ // "[Group][Series Name][21][1080p][FLAC][HASH]"
+ // "[Group] Series Name [04][BDRIP]"
+ new EpisodeExpression(@"(?:\[(?:[^\]]+)\]\s*)?(?<seriesname>\[[^\]]+\]|[^[\]]+)\s*\[(?<epnumber>[0-9]+)\]")
+ {
+ IsNamed = true
+ },
};
VideoExtraRules = new[]
diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj
index 7eb131575..20b32f3a6 100644
--- a/Emby.Naming/Emby.Naming.csproj
+++ b/Emby.Naming/Emby.Naming.csproj
@@ -6,7 +6,7 @@
</PropertyGroup>
<PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
@@ -36,7 +36,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Naming</PackageId>
- <VersionPrefix>10.10.0</VersionPrefix>
+ <VersionPrefix>10.11.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
diff --git a/Emby.Photos/Emby.Photos.csproj b/Emby.Photos/Emby.Photos.csproj
index 55dbe393c..645a74aea 100644
--- a/Emby.Photos/Emby.Photos.csproj
+++ b/Emby.Photos/Emby.Photos.csproj
@@ -19,7 +19,7 @@
</ItemGroup>
<PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 5292003f0..13516896a 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -607,7 +607,7 @@ namespace Emby.Server.Implementations
// Don't use an empty string password
password = string.IsNullOrWhiteSpace(password) ? null : password;
- var localCert = new X509Certificate2(path, password, X509KeyStorageFlags.UserKeySet);
+ var localCert = X509CertificateLoader.LoadPkcs12FromFile(path, password, X509KeyStorageFlags.UserKeySet);
if (!localCert.HasPrivateKey)
{
Logger.LogError("No private key included in SSL cert {CertificateLocation}.", path);
diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs
index 91791a1c8..a06f6e7fe 100644
--- a/Emby.Server.Implementations/ConfigurationOptions.cs
+++ b/Emby.Server.Implementations/ConfigurationOptions.cs
@@ -17,7 +17,6 @@ namespace Emby.Server.Implementations
{ DefaultRedirectKey, "web/" },
{ FfmpegProbeSizeKey, "1G" },
{ FfmpegAnalyzeDurationKey, "200M" },
- { PlaylistsAllowDuplicatesKey, bool.FalseString },
{ BindToUnixSocketKey, bool.FalseString },
{ SqliteCacheSizeKey, "20000" },
{ FfmpegSkipValidationKey, bool.FalseString },
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index 34276355a..70dd5eb9a 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -37,7 +37,7 @@
</ItemGroup>
<PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
index 82db7c46b..0a3d740cc 100644
--- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
+++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
@@ -122,7 +122,6 @@ namespace Emby.Server.Implementations.Images
}
await ProviderManager.SaveImage(item, outputPath, mimeType, imageType, null, false, cancellationToken).ConfigureAwait(false);
- File.Delete(outputPath);
return ItemUpdateType.ImageUpdate;
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
index a03c1214d..14798dda6 100644
--- a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
@@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
{
if (args.IsDirectory)
{
- // It's a boxset if the path is a directory with [playlist] in its name
+ // It's a playlist if the path is a directory with [playlist] in its name
var filename = Path.GetFileName(Path.TrimEndingDirectorySeparator(args.Path));
if (string.IsNullOrEmpty(filename))
{
diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs
index 62d22b23f..ceb3d65a4 100644
--- a/Emby.Server.Implementations/Library/UserDataManager.cs
+++ b/Emby.Server.Implementations/Library/UserDataManager.cs
@@ -84,7 +84,6 @@ namespace Emby.Server.Implementations.Library
{
ArgumentNullException.ThrowIfNull(user);
ArgumentNullException.ThrowIfNull(item);
- ArgumentNullException.ThrowIfNull(reason);
ArgumentNullException.ThrowIfNull(userDataDto);
var userData = GetUserData(user, item);
diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json
index bd45b0b96..e9c095c67 100644
--- a/Emby.Server.Implementations/Localization/Core/ar.json
+++ b/Emby.Server.Implementations/Localization/Core/ar.json
@@ -131,5 +131,8 @@
"TaskCleanCollectionsAndPlaylistsDescription": "حذف عناصر من المجموعات وقوائم التشغيل التي لم تعد موجودة.",
"TaskAudioNormalization": "تطبيع الصوت",
"TaskAudioNormalizationDescription": "مسح الملفات لتطبيع بيانات الصوت.",
- "TaskDownloadMissingLyrics": "تنزيل عبارات القصيدة"
+ "TaskDownloadMissingLyrics": "تنزيل عبارات القصيدة",
+ "TaskDownloadMissingLyricsDescription": "كلمات",
+ "TaskExtractMediaSegments": "فحص مقاطع الوسائط",
+ "TaskExtractMediaSegmentsDescription": "وسائط"
}
diff --git a/Emby.Server.Implementations/Localization/Core/bg-BG.json b/Emby.Server.Implementations/Localization/Core/bg-BG.json
index 4f95b4880..72f575753 100644
--- a/Emby.Server.Implementations/Localization/Core/bg-BG.json
+++ b/Emby.Server.Implementations/Localization/Core/bg-BG.json
@@ -121,7 +121,7 @@
"TaskCleanActivityLog": "Изчисти дневника с активност",
"TaskOptimizeDatabaseDescription": "Прави базата данни по-компактна и освобождава място. Пускането на тази задача след сканиране на библиотеката или правене на други промени, свързани с модификации на базата данни, може да подобри производителността.",
"TaskOptimizeDatabase": "Оптимизирай базата данни",
- "TaskKeyframeExtractorDescription": "Извличат се ключови кадри от видеофайловете ,за да се създаде по точен ХЛС списък . Задачата може да отнеме много време.",
+ "TaskKeyframeExtractorDescription": "Извличат се ключови кадри от видеофайловете ,за да се създаде по точен HLS списък . Задачата може да отнеме много време.",
"TaskKeyframeExtractor": "Извличане на ключови кадри",
"External": "Външен",
"HearingImpaired": "Увреден слух",
@@ -129,8 +129,12 @@
"TaskRefreshTrickplayImagesDescription": "Създава прегледи на Trickplay за видеа в активирани библиотеки.",
"TaskDownloadMissingLyrics": "Свали липсващи текстове",
"TaskDownloadMissingLyricsDescription": "Свали текстове за песни",
- "TaskCleanCollectionsAndPlaylists": "Изчисти колекциите и плейлистовете",
+ "TaskCleanCollectionsAndPlaylists": "Изчисти колекциите и плейлистите",
"TaskCleanCollectionsAndPlaylistsDescription": "Премахни несъществуващи файлове в колекциите и плейлистите.",
"TaskAudioNormalization": "Нормализиране на звука",
- "TaskAudioNormalizationDescription": "Сканирай файловете за нормализация на звука."
+ "TaskAudioNormalizationDescription": "Сканирай файловете за нормализация на звука.",
+ "TaskExtractMediaSegmentsDescription": "Изважда медиини сегменти от MediaSegment плъгини.",
+ "TaskMoveTrickplayImages": "Мигриране на Локацията за Trickplay изображения",
+ "TaskMoveTrickplayImagesDescription": "Премества съществуващите trickplay изображения спрямо настройките на библиотеката.",
+ "TaskExtractMediaSegments": "Сканиране за сегменти"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json
index 6b3b78fa1..629efdd04 100644
--- a/Emby.Server.Implementations/Localization/Core/ca.json
+++ b/Emby.Server.Implementations/Localization/Core/ca.json
@@ -132,5 +132,6 @@
"TaskAudioNormalization": "Normalització d'Àudio",
"TaskAudioNormalizationDescription": "Escaneja arxius per dades de normalització d'àudio.",
"TaskDownloadMissingLyricsDescription": "Baixar lletres de les cançons",
- "TaskDownloadMissingLyrics": "Baixar lletres que falten"
+ "TaskDownloadMissingLyrics": "Baixar lletres que falten",
+ "TaskExtractMediaSegments": "Escaneig de segments multimèdia"
}
diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json
index 121a0eba8..c17fbc414 100644
--- a/Emby.Server.Implementations/Localization/Core/da.json
+++ b/Emby.Server.Implementations/Localization/Core/da.json
@@ -72,7 +72,7 @@
"ServerNameNeedsToBeRestarted": "{0} skal genstartes",
"Shows": "Serier",
"Songs": "Sange",
- "StartupEmbyServerIsLoading": "Jellyfin Server er i gang med at starte. Forsøg igen om et øjeblik.",
+ "StartupEmbyServerIsLoading": "Jellyfin er i gang med at starte. Prøv igen om et øjeblik.",
"SubtitleDownloadFailureForItem": "Fejlet i download af undertekster for {0}",
"SubtitleDownloadFailureFromForItem": "Undertekster kunne ikke hentes fra {0} til {1}",
"Sync": "Synkroniser",
@@ -93,13 +93,13 @@
"ValueSpecialEpisodeName": "Special - {0}",
"VersionNumber": "Version {0}",
"TaskDownloadMissingSubtitlesDescription": "Søger på internettet efter manglende undertekster baseret på metadata-konfigurationen.",
- "TaskDownloadMissingSubtitles": "Hentede medie mangler undertekster",
+ "TaskDownloadMissingSubtitles": "Hent manglende undertekster",
"TaskUpdatePluginsDescription": "Henter og installerer opdateringer for plugins, som er konfigurerede til at blive opdateret automatisk.",
- "TaskUpdatePlugins": "Opdater Plugins",
+ "TaskUpdatePlugins": "Opdater plugins",
"TaskCleanLogsDescription": "Sletter log-filer som er mere end {0} dage gamle.",
- "TaskCleanLogs": "Ryd Log-mappe",
+ "TaskCleanLogs": "Ryd log-mappe",
"TaskRefreshLibraryDescription": "Scanner dit mediebibliotek for nye filer og opdateret metadata.",
- "TaskRefreshLibrary": "Scan Mediebibliotek",
+ "TaskRefreshLibrary": "Scan mediebibliotek",
"TaskCleanCacheDescription": "Sletter cache-filer som systemet ikke længere bruger.",
"TaskCleanCache": "Ryd cache-mappe",
"TasksChannelsCategory": "Internetkanaler",
@@ -108,33 +108,33 @@
"TasksMaintenanceCategory": "Vedligeholdelse",
"TaskRefreshChapterImages": "Udtræk kapitelbilleder",
"TaskRefreshChapterImagesDescription": "Laver miniaturebilleder for videoer, der har kapitler.",
- "TaskRefreshChannelsDescription": "Opdaterer information for internetkanal.",
- "TaskRefreshChannels": "Opdater Kanaler",
- "TaskCleanTranscodeDescription": "Fjerner transcode-filer, som er mere end 1 dag gammel.",
- "TaskCleanTranscode": "Tøm Transcode-mappen",
- "TaskRefreshPeople": "Opdater Personer",
+ "TaskRefreshChannelsDescription": "Opdaterer information for internetkanaler.",
+ "TaskRefreshChannels": "Opdater kanaler",
+ "TaskCleanTranscodeDescription": "Fjerner omkodningsfiler, som er mere end 1 dag gamle.",
+ "TaskCleanTranscode": "Tøm omkodningsmappen",
+ "TaskRefreshPeople": "Opdater personer",
"TaskRefreshPeopleDescription": "Opdaterer metadata for skuespillere og instruktører i dit mediebibliotek.",
"TaskCleanActivityLogDescription": "Sletter linjer i aktivitetsloggen ældre end den konfigurerede alder.",
- "TaskCleanActivityLog": "Ryd Aktivitetslog",
+ "TaskCleanActivityLog": "Ryd aktivitetslog",
"Undefined": "Udefineret",
"Forced": "Tvunget",
"Default": "Standard",
- "TaskOptimizeDatabaseDescription": "Komprimerer databasen og frigør plads. Denne handling køres efter at have scannet mediebiblioteket, eller efter at have lavet ændringer til databasen, for at højne ydeevnen.",
- "TaskOptimizeDatabase": "Optimér database",
- "TaskKeyframeExtractorDescription": "Udtrækker billeder fra videofiler for at lave mere præcise HLS-playlister. Denne opgave kan tage lang tid.",
- "TaskKeyframeExtractor": "Udtræk af nøglebillede",
+ "TaskOptimizeDatabaseDescription": "Komprimerer databasen for at frigøre plads. Denne handling køres efter at have scannet mediebiblioteket, eller efter at have lavet ændringer til databasen.",
+ "TaskOptimizeDatabase": "Optimer database",
+ "TaskKeyframeExtractorDescription": "Udtrækker rammer fra videofiler for at lave mere præcise HLS-playlister. Denne opgave kan tage lang tid.",
+ "TaskKeyframeExtractor": "Udtræk nøglerammer",
"External": "Ekstern",
"HearingImpaired": "Hørehæmmet",
- "TaskRefreshTrickplayImages": "Generér Trickplay Billeder",
- "TaskRefreshTrickplayImagesDescription": "Laver trickplay forhåndsvisninger for videoer i aktiverede biblioteker.",
+ "TaskRefreshTrickplayImages": "Generer trickplay-billeder",
+ "TaskRefreshTrickplayImagesDescription": "Laver trickplay-billeder for videoer i aktiverede biblioteker.",
"TaskCleanCollectionsAndPlaylists": "Ryd op i samlinger og afspilningslister",
"TaskCleanCollectionsAndPlaylistsDescription": "Fjerner elementer fra samlinger og afspilningslister der ikke eksisterer længere.",
- "TaskAudioNormalizationDescription": "Skanner filer for data vedrørende audio-normalisering.",
- "TaskAudioNormalization": "Audio-normalisering",
- "TaskDownloadMissingLyricsDescription": "Hentede sange mangler sangtekster",
- "TaskDownloadMissingLyrics": "Hentede medie mangler sangtekster",
- "TaskExtractMediaSegments": "Scan mediesegment",
- "TaskMoveTrickplayImages": "Migrer billedelokation for Trickplay",
- "TaskMoveTrickplayImagesDescription": "Flyt eksisterende trickplay-filer jævnfør biblioteksindstillilnger.",
- "TaskExtractMediaSegmentsDescription": "Ekstraherer eller henter mediesegmenter fra plugins som understøtter MediaSegment."
+ "TaskAudioNormalizationDescription": "Skanner filer for data vedrørende lydnormalisering.",
+ "TaskAudioNormalization": "Lydnormalisering",
+ "TaskDownloadMissingLyricsDescription": "Søger på internettet efter manglende sangtekster baseret på metadata-konfigurationen",
+ "TaskDownloadMissingLyrics": "Hent manglende sangtekster",
+ "TaskExtractMediaSegments": "Scan for mediesegmenter",
+ "TaskMoveTrickplayImages": "Migrer billedelokationer for trickplay-billeder",
+ "TaskMoveTrickplayImagesDescription": "Flyt eksisterende trickplay-billeder jævnfør biblioteksindstillinger.",
+ "TaskExtractMediaSegmentsDescription": "Udtrækker eller henter mediesegmenter fra plugins som understøtter MediaSegment."
}
diff --git a/Emby.Server.Implementations/Localization/Core/el.json b/Emby.Server.Implementations/Localization/Core/el.json
index 056a2e475..55f266032 100644
--- a/Emby.Server.Implementations/Localization/Core/el.json
+++ b/Emby.Server.Implementations/Localization/Core/el.json
@@ -130,5 +130,11 @@
"TaskAudioNormalization": "Ομοιομορφία ήχου",
"TaskAudioNormalizationDescription": "Ανίχνευση αρχείων για δεδομένα ομοιομορφίας ήχου.",
"TaskCleanCollectionsAndPlaylists": "Καθαρισμός συλλογών και λιστών αναπαραγωγής",
- "TaskCleanCollectionsAndPlaylistsDescription": "Αφαιρούνται στοιχεία από τις συλλογές και τις λίστες αναπαραγωγής που δεν υπάρχουν πλέον."
+ "TaskCleanCollectionsAndPlaylistsDescription": "Αφαιρούνται στοιχεία από τις συλλογές και τις λίστες αναπαραγωγής που δεν υπάρχουν πλέον.",
+ "TaskMoveTrickplayImages": "Αλλαγή τοποθεσίας εικόνων Trickplay",
+ "TaskDownloadMissingLyrics": "Λήψη στίχων που λείπουν",
+ "TaskMoveTrickplayImagesDescription": "Μετακινεί τα υπάρχοντα αρχεία trickplay σύμφωνα με τις ρυθμίσεις της βιβλιοθήκης.",
+ "TaskDownloadMissingLyricsDescription": "Κατεβάζει στίχους για τραγούδια",
+ "TaskExtractMediaSegments": "Σάρωση τμημάτων πολυμέσων",
+ "TaskExtractMediaSegmentsDescription": "Εξάγει ή βρίσκει τμήματα πολυμέσων από επεκτάσεις που χρησιμοποιούν το MediaSegment."
}
diff --git a/Emby.Server.Implementations/Localization/Core/es_419.json b/Emby.Server.Implementations/Localization/Core/es_419.json
index b458ed423..2534f37c1 100644
--- a/Emby.Server.Implementations/Localization/Core/es_419.json
+++ b/Emby.Server.Implementations/Localization/Core/es_419.json
@@ -131,5 +131,9 @@
"TaskAudioNormalizationDescription": "Analiza los archivos para normalizar el audio.",
"TaskCleanCollectionsAndPlaylists": "Limpieza de colecciones y listas de reproducción",
"TaskDownloadMissingLyrics": "Descargar letra faltante",
- "TaskDownloadMissingLyricsDescription": "Descarga letras de canciones"
+ "TaskDownloadMissingLyricsDescription": "Descarga letras de canciones",
+ "TaskExtractMediaSegmentsDescription": "Extrae u obtiene segmentos de medios de complementos habilitados para MediaSegment.",
+ "TaskMoveTrickplayImagesDescription": "Mueve archivos de trickplay existentes según la configuración de la biblioteca.",
+ "TaskExtractMediaSegments": "Escaneo de segmentos de medios",
+ "TaskMoveTrickplayImages": "Migrar la ubicación de la imagen de Trickplay"
}
diff --git a/Emby.Server.Implementations/Localization/Core/fi.json b/Emby.Server.Implementations/Localization/Core/fi.json
index 8a88cf28e..c9f580cd5 100644
--- a/Emby.Server.Implementations/Localization/Core/fi.json
+++ b/Emby.Server.Implementations/Localization/Core/fi.json
@@ -130,5 +130,10 @@
"TaskCleanCollectionsAndPlaylists": "Puhdista kokoelmat ja soittolistat",
"TaskAudioNormalization": "Äänenvoimakkuuden normalisointi",
"TaskAudioNormalizationDescription": "Etsii tiedostoista äänenvoimakkuuden normalisointitietoja.",
- "TaskDownloadMissingLyrics": "Lataa puuttuva lyriikka"
+ "TaskDownloadMissingLyrics": "Lataa puuttuva lyriikka",
+ "TaskExtractMediaSegments": "Mediasegmentin skannaus",
+ "TaskDownloadMissingLyricsDescription": "Ladataan sanoituksia",
+ "TaskExtractMediaSegmentsDescription": "Poimii tai hankkii mediasegmenttejä MediaSegment-yhteensopivista laajennuksista.",
+ "TaskMoveTrickplayImages": "Siirrä Trickplay-kuvien sijainti",
+ "TaskMoveTrickplayImagesDescription": "Siirtää olemassa olevia trickplay-tiedostoja kirjaston asetusten mukaan."
}
diff --git a/Emby.Server.Implementations/Localization/Core/ga.json b/Emby.Server.Implementations/Localization/Core/ga.json
index b511ed6ba..b8e787c20 100644
--- a/Emby.Server.Implementations/Localization/Core/ga.json
+++ b/Emby.Server.Implementations/Localization/Core/ga.json
@@ -1,16 +1,139 @@
{
"Albums": "Albaim",
- "Artists": "Ealaíontóir",
- "AuthenticationSucceededWithUserName": "{0} fíordheimhnithe",
- "Books": "leabhair",
- "CameraImageUploadedFrom": "Tá íomhá ceamara nua uaslódáilte ó {0}",
+ "Artists": "Ealaíontóirí",
+ "AuthenticationSucceededWithUserName": "D'éirigh le fíordheimhniú {0}",
+ "Books": "Leabhair",
+ "CameraImageUploadedFrom": "Uaslódáladh íomhá ceamara nua ó {0}",
"Channels": "Cainéil",
"ChapterNameValue": "Caibidil {0}",
"Collections": "Bailiúcháin",
- "Default": "Mainneachtain",
- "DeviceOfflineWithName": "scoireadh {0}",
- "DeviceOnlineWithName": "{0} ceangailte",
- "External": "Forimeallach",
- "FailedLoginAttemptWithUserName": "Iarracht ar theip ar fhíordheimhniú ó {0}",
- "Favorites": "Ceanáin"
+ "Default": "Réamhshocrú",
+ "DeviceOfflineWithName": "Tá {0} dícheangailte",
+ "DeviceOnlineWithName": "Tá {0} nasctha",
+ "External": "Seachtrach",
+ "FailedLoginAttemptWithUserName": "Theip ar iarracht logáil isteach ó {0}",
+ "Favorites": "Ceanáin",
+ "TaskExtractMediaSegments": "Scanadh Deighleog na Meán",
+ "TaskMoveTrickplayImages": "Imirce Suíomh Íomhá Trickplay",
+ "TaskDownloadMissingLyrics": "Íosluchtaigh liricí ar iarraidh",
+ "TaskKeyframeExtractor": "Keyframe Eastarraingteoir",
+ "TaskAudioNormalization": "Normalú Fuaime",
+ "TaskAudioNormalizationDescription": "Scanann comhaid le haghaidh sonraí normalaithe fuaime.",
+ "TaskRefreshLibraryDescription": "Déanann sé do leabharlann meán a scanadh le haghaidh comhaid nua agus athnuachana meiteashonraí.",
+ "TaskCleanLogs": "Eolaire Logchomhad Glan",
+ "TaskCleanLogsDescription": "Scriostar comhaid loga atá níos mó ná {0} lá d'aois.",
+ "TaskRefreshPeopleDescription": "Nuashonraítear meiteashonraí d’aisteoirí agus stiúrthóirí i do leabharlann meán.",
+ "TaskRefreshTrickplayImages": "Gin Íomhánna Trickplay",
+ "TaskRefreshTrickplayImagesDescription": "Cruthaíonn sé réamhamhairc trickplay le haghaidh físeáin i leabharlanna cumasaithe.",
+ "TaskRefreshChannels": "Cainéil Athnuaigh",
+ "TaskRefreshChannelsDescription": "Athnuachan eolas faoi chainéil idirlín.",
+ "TaskOptimizeDatabase": "Bunachar sonraí a bharrfheabhsú",
+ "TaskKeyframeExtractorDescription": "Baintear eochairfhrámaí as comhaid físe chun seinmliostaí HLS níos cruinne a chruthú. Féadfaidh an tasc seo a bheith ar siúl ar feadh i bhfad.",
+ "TaskCleanCollectionsAndPlaylistsDescription": "Baintear míreanna as bailiúcháin agus seinmliostaí nach ann dóibh a thuilleadh.",
+ "TaskDownloadMissingLyricsDescription": "Íosluchtaigh liricí do na hamhráin",
+ "TaskUpdatePluginsDescription": "Íoslódálann agus suiteálann nuashonruithe do bhreiseáin atá cumraithe le nuashonrú go huathoibríoch.",
+ "TaskDownloadMissingSubtitlesDescription": "Déanann sé cuardach ar an idirlíon le haghaidh fotheidil atá ar iarraidh bunaithe ar chumraíocht meiteashonraí.",
+ "TaskExtractMediaSegmentsDescription": "Sliocht nó faigheann codanna meán ó bhreiseáin chumasaithe MediaSegment.",
+ "TaskCleanCollectionsAndPlaylists": "Glan suas bailiúcháin agus seinmliostaí",
+ "TaskOptimizeDatabaseDescription": "Comhdhlúthaíonn bunachar sonraí agus gearrtar spás saor in aisce. Má ritheann tú an tasc seo tar éis scanadh a dhéanamh ar an leabharlann nó athruithe eile a dhéanamh a thugann le tuiscint gur cheart go bhfeabhsófaí an fheidhmíocht.",
+ "TaskMoveTrickplayImagesDescription": "Bogtar comhaid trickplay atá ann cheana de réir socruithe na leabharlainne.",
+ "AppDeviceValues": "Aip: {0}, Gléas: {1}",
+ "Application": "Feidhmchlár",
+ "Folders": "Fillteáin",
+ "Forced": "Éigean",
+ "Genres": "Seánraí",
+ "HeaderAlbumArtists": "Ealaíontóirí albam",
+ "HeaderContinueWatching": "Leanúint ar aghaidh ag Breathnú",
+ "HeaderFavoriteAlbums": "Albam is fearr leat",
+ "HeaderFavoriteArtists": "Ealaíontóirí is Fearr",
+ "HeaderFavoriteEpisodes": "Eipeasóid is fearr leat",
+ "HeaderFavoriteShows": "Seónna is Fearr",
+ "HeaderFavoriteSongs": "Amhráin is fearr leat",
+ "HeaderLiveTV": "Teilifís beo",
+ "HeaderNextUp": "Ar Aghaidh Suas",
+ "HeaderRecordingGroups": "Grúpaí Taifeadta",
+ "HearingImpaired": "Lag éisteachta",
+ "HomeVideos": "Físeáin Baile",
+ "Inherit": "Oidhreacht",
+ "ItemAddedWithName": "Cuireadh {0} leis an leabharlann",
+ "ItemRemovedWithName": "Baineadh {0} den leabharlann",
+ "LabelIpAddressValue": "Seoladh IP: {0}",
+ "LabelRunningTimeValue": "Am rite: {0}",
+ "Latest": "Is déanaí",
+ "MessageApplicationUpdated": "Tá Freastalaí Jellyfin nuashonraithe",
+ "MessageApplicationUpdatedTo": "Nuashonraíodh Freastalaí Jellyfin go {0}",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Nuashonraíodh an chuid cumraíochta freastalaí {0}",
+ "MessageServerConfigurationUpdated": "Nuashonraíodh cumraíocht an fhreastalaí",
+ "MixedContent": "Ábhar measctha",
+ "Movies": "Scannáin",
+ "Music": "Ceol",
+ "MusicVideos": "Físeáin Ceoil",
+ "NameInstallFailed": "Theip ar shuiteáil {0}",
+ "NameSeasonNumber": "Séasúr {0}",
+ "NameSeasonUnknown": "Séasúr Anaithnid",
+ "NewVersionIsAvailable": "Tá leagan nua de Jellyfin Server ar fáil le híoslódáil.",
+ "NotificationOptionApplicationUpdateAvailable": "Nuashonrú feidhmchláir ar fáil",
+ "NotificationOptionApplicationUpdateInstalled": "Nuashonrú feidhmchláir suiteáilte",
+ "NotificationOptionAudioPlayback": "Cuireadh tús le hathsheinm fuaime",
+ "NotificationOptionAudioPlaybackStopped": "Cuireadh deireadh le hathsheinm fuaime",
+ "NotificationOptionCameraImageUploaded": "Íosluchtaigh grianghraf ceamara",
+ "NotificationOptionInstallationFailed": "Teip suiteála",
+ "NotificationOptionNewLibraryContent": "Ábhar nua curtha leis",
+ "NotificationOptionPluginError": "Teip breiseán",
+ "NotificationOptionPluginInstalled": "Breiseán suiteáilte",
+ "NotificationOptionPluginUninstalled": "Breiseán díshuiteáilte",
+ "NotificationOptionPluginUpdateInstalled": "Nuashonrú breiseán suiteáilte",
+ "NotificationOptionServerRestartRequired": "Teastaíonn atosú an fhreastalaí",
+ "NotificationOptionTaskFailed": "Teip tasc sceidealta",
+ "NotificationOptionUserLockedOut": "Úsáideoir glasáilte amach",
+ "NotificationOptionVideoPlayback": "Cuireadh tús le hathsheinm físe",
+ "NotificationOptionVideoPlaybackStopped": "Cuireadh deireadh le hathsheinm físe",
+ "Photos": "Grianghraif",
+ "Playlists": "Seinmliostaí",
+ "Plugin": "Breiseán",
+ "PluginInstalledWithName": "Suiteáladh {0}",
+ "PluginUninstalledWithName": "Díshuiteáladh {0}",
+ "PluginUpdatedWithName": "Nuashonraíodh {0}",
+ "ProviderValue": "Soláthraí: {0}",
+ "ScheduledTaskFailedWithName": "Theip ar {0}",
+ "ScheduledTaskStartedWithName": "Thosaigh {0}",
+ "ServerNameNeedsToBeRestarted": "Ní mór {0} a atosú",
+ "Shows": "Seónna",
+ "Songs": "Amhráin",
+ "StartupEmbyServerIsLoading": "Tá freastalaí Jellyfin á luchtú. Bain triail eile as gan mhoill.",
+ "SubtitleDownloadFailureFromForItem": "Theip ar fhotheidil a íoslódáil ó {0} le haghaidh {1}",
+ "Sync": "Sioncrónaigh",
+ "System": "Córas",
+ "TvShows": "Seónna Teilifíse",
+ "Undefined": "Neamhshainithe",
+ "User": "Úsáideoir",
+ "UserCreatedWithName": "Cruthaíodh úsáideoir {0}",
+ "UserDeletedWithName": "Scriosadh úsáideoir {0}",
+ "UserDownloadingItemWithValues": "Tá {0} á íoslódáil {1}",
+ "UserLockedOutWithName": "Tá úsáideoir {0} glasáilte amach",
+ "UserOfflineFromDevice": "Tá {0} dícheangailte ó {1}",
+ "UserOnlineFromDevice": "Tá {0} ar líne ó {1}",
+ "UserPasswordChangedWithName": "Athraíodh pasfhocal don úsáideoir {0}",
+ "UserPolicyUpdatedWithName": "Nuashonraíodh polasaí úsáideora le haghaidh {0}",
+ "UserStartedPlayingItemWithValues": "Tá {0} ag seinnt {1} ar {2}",
+ "UserStoppedPlayingItemWithValues": "Chríochnaigh {0} ag imirt {1} ar {2}",
+ "ValueHasBeenAddedToLibrary": "Cuireadh {0} le do leabharlann meán",
+ "ValueSpecialEpisodeName": "Speisialta - {0}",
+ "VersionNumber": "Leagan {0}",
+ "TasksMaintenanceCategory": "Cothabháil",
+ "TasksLibraryCategory": "Leabharlann",
+ "TasksApplicationCategory": "Feidhmchlár",
+ "TasksChannelsCategory": "Cainéil Idirlín",
+ "TaskCleanActivityLog": "Loga Gníomhaíochta Glan",
+ "TaskCleanActivityLogDescription": "Scrios iontrálacha loga gníomhaíochta atá níos sine ná an aois chumraithe.",
+ "TaskCleanCache": "Eolaire Taisce Glan",
+ "TaskCleanCacheDescription": "Scriostar comhaid taisce nach bhfuil ag teastáil ón gcóras a thuilleadh.",
+ "TaskRefreshChapterImages": "Sliocht Íomhánna Caibidil",
+ "TaskRefreshChapterImagesDescription": "Cruthaíonn mionsamhlacha le haghaidh físeáin a bhfuil caibidlí acu.",
+ "TaskRefreshLibrary": "Scan Leabharlann na Meán",
+ "TaskRefreshPeople": "Daoine Athnuaigh",
+ "TaskUpdatePlugins": "Nuashonraigh Breiseáin",
+ "TaskCleanTranscodeDescription": "Scriostar comhaid traschódaithe níos mó ná lá amháin d'aois.",
+ "TaskCleanTranscode": "Eolaire Transcode Glan",
+ "TaskDownloadMissingSubtitles": "Íosluchtaigh fotheidil ar iarraidh"
}
diff --git a/Emby.Server.Implementations/Localization/Core/he.json b/Emby.Server.Implementations/Localization/Core/he.json
index af57b1693..34d5cf050 100644
--- a/Emby.Server.Implementations/Localization/Core/he.json
+++ b/Emby.Server.Implementations/Localization/Core/he.json
@@ -16,7 +16,7 @@
"Folders": "תיקיות",
"Genres": "ז׳אנרים",
"HeaderAlbumArtists": "אמני האלבום",
- "HeaderContinueWatching": "להמשיך לצפות",
+ "HeaderContinueWatching": "המשך צפייה",
"HeaderFavoriteAlbums": "אלבומים מועדפים",
"HeaderFavoriteArtists": "אמנים מועדפים",
"HeaderFavoriteEpisodes": "פרקים מועדפים",
@@ -32,8 +32,8 @@
"LabelIpAddressValue": "Ip כתובת: {0}",
"LabelRunningTimeValue": "משך צפייה: {0}",
"Latest": "אחרון",
- "MessageApplicationUpdated": "שרת הJellyfin עודכן",
- "MessageApplicationUpdatedTo": "שרת ה־Jellyfin עודכן לגרסה {0}",
+ "MessageApplicationUpdated": "שרת ג'ליפין עודכן",
+ "MessageApplicationUpdatedTo": "שרת ג'ליפין עודכן לגרסה {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "סעיף הגדרת השרת {0} עודכן",
"MessageServerConfigurationUpdated": "תצורת השרת עודכנה",
"MixedContent": "תוכן מעורב",
@@ -43,7 +43,7 @@
"NameInstallFailed": "התקנת {0} נכשלה",
"NameSeasonNumber": "עונה {0}",
"NameSeasonUnknown": "עונה לא ידועה",
- "NewVersionIsAvailable": "גרסה חדשה של שרת Jellyfin זמינה להורדה.",
+ "NewVersionIsAvailable": "גרסה חדשה של שרת ג'ליפין זמינה להורדה.",
"NotificationOptionApplicationUpdateAvailable": "קיים עדכון זמין ליישום",
"NotificationOptionApplicationUpdateInstalled": "עדכון ליישום הותקן",
"NotificationOptionAudioPlayback": "ניגון שמע החל",
@@ -72,7 +72,7 @@
"ServerNameNeedsToBeRestarted": "{0} דורש הפעלה מחדש",
"Shows": "סדרות",
"Songs": "שירים",
- "StartupEmbyServerIsLoading": "שרת Jellyfin בהליכי טעינה. נא לנסות שנית בהקדם.",
+ "StartupEmbyServerIsLoading": "שרת ג'ליפין טוען. נא לנסות שוב בקרוב.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "הורדת כתוביות מ־{0} עבור {1} נכשלה",
"Sync": "סנכרון",
@@ -133,8 +133,8 @@
"TaskCleanCollectionsAndPlaylists": "מנקה אוספים ורשימות השמעה",
"TaskDownloadMissingLyrics": "הורדת מילים חסרות",
"TaskDownloadMissingLyricsDescription": "הורדת מילים לשירים",
- "TaskMoveTrickplayImages": "מעביר את מיקום תמונות Trickplay",
+ "TaskMoveTrickplayImages": "העברת מיקום התמונות",
"TaskExtractMediaSegments": "סריקת מדיה",
"TaskExtractMediaSegmentsDescription": "מחלץ חלקי מדיה מתוספים המאפשרים זאת.",
- "TaskMoveTrickplayImagesDescription": "מזיז קבצי trickplay קיימים בהתאם להגדרות הספרייה."
+ "TaskMoveTrickplayImagesDescription": "הזזת קבצי טריקפליי קיימים בהתאם להגדרות הספרייה."
}
diff --git a/Emby.Server.Implementations/Localization/Core/hi.json b/Emby.Server.Implementations/Localization/Core/hi.json
index 380c08e0d..813b18ad4 100644
--- a/Emby.Server.Implementations/Localization/Core/hi.json
+++ b/Emby.Server.Implementations/Localization/Core/hi.json
@@ -99,7 +99,7 @@
"ValueHasBeenAddedToLibrary": "{0} आपके माध्यम ग्रन्थालय में उपजात हो गया हैं",
"TasksLibraryCategory": "संग्रहालय",
"TaskOptimizeDatabase": "जानकारी प्रवृद्धि",
- "TaskDownloadMissingSubtitles": "असमेत अनुलेख को अवाहरति करें",
+ "TaskDownloadMissingSubtitles": "लापता अनुलेख डाउनलोड करें",
"TaskRefreshLibrary": "माध्यम संग्राहत को छाने",
"TaskCleanActivityLog": "क्रियाकलाप लॉग साफ करें",
"TasksChannelsCategory": "इंटरनेट प्रणाली",
@@ -127,5 +127,7 @@
"TaskRefreshTrickplayImages": "ट्रिकप्लै चित्रों को सृजन करे",
"TaskRefreshTrickplayImagesDescription": "नियत संग्रहों में चलचित्रों का ट्रीकप्लै दर्शनों को सृजन करे.",
"TaskAudioNormalization": "श्रव्य सामान्यीकरण",
- "TaskAudioNormalizationDescription": "श्रव्य सामान्यीकरण के लिए फाइलें अन्वेषण करें"
+ "TaskAudioNormalizationDescription": "श्रव्य सामान्यीकरण के लिए फाइलें अन्वेषण करें",
+ "TaskDownloadMissingLyrics": "लापता गानों के बोल डाउनलोड करेँ",
+ "TaskDownloadMissingLyricsDescription": "गानों के बोल डाउनलोड करता है"
}
diff --git a/Emby.Server.Implementations/Localization/Core/hr.json b/Emby.Server.Implementations/Localization/Core/hr.json
index a7dabaa19..d5ab7fa09 100644
--- a/Emby.Server.Implementations/Localization/Core/hr.json
+++ b/Emby.Server.Implementations/Localization/Core/hr.json
@@ -130,5 +130,11 @@
"TaskAudioNormalization": "Normalizacija zvuka",
"TaskAudioNormalizationDescription": "Skenira datoteke u potrazi za podacima o normalizaciji zvuka.",
"TaskCleanCollectionsAndPlaylistsDescription": "Uklanja stavke iz zbirki i popisa za reprodukciju koje više ne postoje.",
- "TaskCleanCollectionsAndPlaylists": "Očisti zbirke i popise za reprodukciju"
+ "TaskCleanCollectionsAndPlaylists": "Očisti zbirke i popise za reprodukciju",
+ "TaskExtractMediaSegments": "Skeniranje dijelova medija",
+ "TaskDownloadMissingLyrics": "Preuzmi tekstove koji nedostaju",
+ "TaskDownloadMissingLyricsDescription": "Preuzmi tekstove pjesama",
+ "TaskExtractMediaSegmentsDescription": "Izvlači ili pribavlja dijelove medija iz omogućenih media pluginova.",
+ "TaskMoveTrickplayImages": "Preseli lokaciju Trickplay slika",
+ "TaskMoveTrickplayImagesDescription": "Preseli lokaciju Trickplay slika prema postavkama zbirke."
}
diff --git a/Emby.Server.Implementations/Localization/Core/ko.json b/Emby.Server.Implementations/Localization/Core/ko.json
index a739cba35..efc9f61dd 100644
--- a/Emby.Server.Implementations/Localization/Core/ko.json
+++ b/Emby.Server.Implementations/Localization/Core/ko.json
@@ -3,7 +3,7 @@
"AppDeviceValues": "앱: {0}, 장치: {1}",
"Application": "애플리케이션",
"Artists": "아티스트",
- "AuthenticationSucceededWithUserName": "{0}이(가) 성공적으로 인증됨",
+ "AuthenticationSucceededWithUserName": "{0} 사용자가 성공적으로 인증됨",
"Books": "도서",
"CameraImageUploadedFrom": "{0}에서 새로운 카메라 이미지가 업로드됨",
"Channels": "채널",
@@ -70,7 +70,7 @@
"ScheduledTaskFailedWithName": "{0} 실패",
"ScheduledTaskStartedWithName": "{0} 시작",
"ServerNameNeedsToBeRestarted": "{0}를 재시작해야합니다",
- "Shows": "쇼",
+ "Shows": "시리즈",
"Songs": "노래",
"StartupEmbyServerIsLoading": "Jellyfin 서버를 불러오고 있습니다. 잠시 후에 다시 시도하십시오.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
@@ -81,14 +81,14 @@
"User": "사용자",
"UserCreatedWithName": "사용자 {0} 생성됨",
"UserDeletedWithName": "사용자 {0} 삭제됨",
- "UserDownloadingItemWithValues": "{0}이(가) {1}을 다운로드 중입니다",
- "UserLockedOutWithName": "유저 {0} 은(는) 잠금처리 되었습니다",
- "UserOfflineFromDevice": "{1}에서 {0}의 연결이 끊킴",
- "UserOnlineFromDevice": "{0}이 {1}으로 접속",
- "UserPasswordChangedWithName": "사용자 {0}의 비밀번호가 변경되었습니다",
- "UserPolicyUpdatedWithName": "{0}의 사용자 정책이 업데이트되었습니다",
- "UserStartedPlayingItemWithValues": "{2}에서 {0}이 {1} 재생 중",
- "UserStoppedPlayingItemWithValues": "{2}에서 {0}이 {1} 재생을 마침",
+ "UserDownloadingItemWithValues": "{0} 사용자가 {1} 다운로드 중",
+ "UserLockedOutWithName": "{0} 사용자 잠김",
+ "UserOfflineFromDevice": "{0} 사용자의 {1}에서 연결이 끊김",
+ "UserOnlineFromDevice": "{0} 사용자가 {1}에서 접속함",
+ "UserPasswordChangedWithName": "{0} 사용자 비밀번호 변경됨",
+ "UserPolicyUpdatedWithName": "{0} 사용자 정책 업데이트됨",
+ "UserStartedPlayingItemWithValues": "{0} 사용자의 {2}에서 {1} 재생 중",
+ "UserStoppedPlayingItemWithValues": "{0} 사용자의 {2}에서 {1} 재생을 마침",
"ValueHasBeenAddedToLibrary": "{0}가 미디어 라이브러리에 추가되었습니다",
"ValueSpecialEpisodeName": "스페셜 - {0}",
"VersionNumber": "버전 {0}",
@@ -130,5 +130,11 @@
"TaskAudioNormalizationDescription": "오디오의 볼륨 수준을 일정하게 조정하기 위해 파일을 스캔합니다.",
"TaskRefreshTrickplayImages": "비디오 탐색용 미리보기 썸네일 생성",
"TaskRefreshTrickplayImagesDescription": "활성화된 라이브러리에서 비디오의 트릭플레이 미리보기를 생성합니다.",
- "TaskCleanCollectionsAndPlaylistsDescription": "더 이상 존재하지 않는 컬렉션 및 재생 목록에서 항목을 제거합니다."
+ "TaskCleanCollectionsAndPlaylistsDescription": "더 이상 존재하지 않는 컬렉션 및 재생 목록에서 항목을 제거합니다.",
+ "TaskExtractMediaSegments": "미디어 세그먼트 스캔",
+ "TaskExtractMediaSegmentsDescription": "MediaSegment를 지원하는 플러그인에서 미디어 세그먼트를 추출하거나 가져옵니다.",
+ "TaskMoveTrickplayImages": "트릭플레이 이미지 위치 마이그레이션",
+ "TaskMoveTrickplayImagesDescription": "추출된 트릭플레이 이미지를 라이브러리 설정에 따라 이동합니다.",
+ "TaskDownloadMissingLyrics": "누락된 가사 다운로드",
+ "TaskDownloadMissingLyricsDescription": "가사 다운로드"
}
diff --git a/Emby.Server.Implementations/Localization/Core/mt.json b/Emby.Server.Implementations/Localization/Core/mt.json
index c9e11165d..c3da37c58 100644
--- a/Emby.Server.Implementations/Localization/Core/mt.json
+++ b/Emby.Server.Implementations/Localization/Core/mt.json
@@ -1,42 +1,42 @@
{
"Albums": "Albums",
- "AppDeviceValues": "App: {0}, Apparat: {1}",
+ "AppDeviceValues": "Applikazzjoni: {0}, Device: {1}",
"Application": "Applikazzjoni",
"Artists": "Artisti",
"AuthenticationSucceededWithUserName": "{1} awtentikat b'suċċess",
"Books": "Kotba",
- "CameraImageUploadedFrom": "Ttellgħet immaġni ġdida tal-kamera minn {1}",
- "Channels": "Kanali",
+ "CameraImageUploadedFrom": "Ttella' ritratt ġdid tal-kamera minn {1}",
+ "Channels": "Stazzjonijiet",
"ChapterNameValue": "Kapitlu {0}",
"Collections": "Kollezzjonijiet",
- "DeviceOfflineWithName": "{0} inqatgħa",
- "DeviceOnlineWithName": "{0} qabad",
+ "DeviceOfflineWithName": "{0} tneħħa",
+ "DeviceOnlineWithName": "{0} tqabbad",
"External": "Estern",
- "FailedLoginAttemptWithUserName": "Tentattiv t'aċċess fallut minn {0}",
+ "FailedLoginAttemptWithUserName": "Attentat ta' login minn {0}",
"Favorites": "Favoriti",
"Forced": "Sfurzat",
"Genres": "Ġeneri",
"HeaderAlbumArtists": "Artisti tal-album",
- "HeaderContinueWatching": "Kompli Segwi",
+ "HeaderContinueWatching": "Kompli Ara",
"HeaderFavoriteAlbums": "Albums Favoriti",
"HeaderFavoriteArtists": "Artisti Favoriti",
"HeaderFavoriteEpisodes": "Episodji Favoriti",
"HeaderFavoriteShows": "Programmi Favoriti",
"HeaderFavoriteSongs": "Kanzunetti Favoriti",
"HeaderNextUp": "Li Jmiss",
- "SubtitleDownloadFailureFromForItem": "Is-sottotitli naqsu milli jitniżżlu minn {0} għal {1}",
- "UserPasswordChangedWithName": "Il-password inbidel għall-utent {0}",
+ "SubtitleDownloadFailureFromForItem": "Is-sottotitli ma setgħux jitniżżlu minn {0} għal {1}",
+ "UserPasswordChangedWithName": "Il-password għall-utent {0} inbidlet",
"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.",
+ "TaskDownloadMissingSubtitlesDescription": "Ifittex fuq l-internet għal sottotitli neqsin skont il-konfigurazzjoni tal-metadata.",
+ "TaskOptimizeDatabaseDescription": "Jikkompatta d-database u jaqta' l-ispazju ħieles. It-tħaddim ta' dan it-task wara li tiskennja l-librerija jew tagħmel bidliet oħra li jimplikaw modifiki fid-database jistgħu jtejbu l-mod kif jaħdem.",
"Default": "Standard",
"Folders": "Folders",
"HeaderLiveTV": "TV Dirett",
- "HeaderRecordingGroups": "Gruppi ta' Reġistrazzjoni",
+ "HeaderRecordingGroups": "Gruppi ta' Rikordjar",
"HearingImpaired": "Nuqqas ta' Smigħ",
- "HomeVideos": "Vidjows Personali",
+ "HomeVideos": "Filmati Personali",
"Inherit": "Jiret",
- "ItemAddedWithName": "{0} ġie miżjud mal-librerija",
+ "ItemAddedWithName": "{0} żdied fil-librerija",
"ItemRemovedWithName": "{0} tneħħa mil-librerija",
"LabelIpAddressValue": "Indirizz IP: {0}",
"Latest": "Tal-Aħħar",
@@ -47,7 +47,7 @@
"MixedContent": "Kontenut imħallat",
"Movies": "Films",
"Music": "Mużika",
- "MusicVideos": "Vidjows tal-Mużika",
+ "MusicVideos": "Music Videos",
"NameInstallFailed": "L-installazzjoni ta' {0} falliet",
"NameSeasonNumber": "Staġun {0}",
"NameSeasonUnknown": "Staġun Mhux Magħruf",
@@ -58,13 +58,13 @@
"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",
+ "NotificationOptionInstallationFailed": "Falliment tal-Installazzjoni",
+ "NotificationOptionNewLibraryContent": "Kontenut ġdid żdied",
+ "NotificationOptionPluginError": "Falliment fil-plugin",
"NotificationOptionPluginInstalled": "Plugin installat",
"NotificationOptionPluginUninstalled": "Plugin tneħħa",
- "NotificationOptionServerRestartRequired": "Meħtieġ l-istartjar mill-ġdid tas-server",
- "NotificationOptionTaskFailed": "Falliment tal-kompitu skedat",
+ "NotificationOptionServerRestartRequired": "Hemm bżonn li tagħmel restart lis-server",
+ "NotificationOptionTaskFailed": "Falliment tat-task skedat",
"NotificationOptionUserLockedOut": "Utent imsakkar",
"Photos": "Ritratti",
"Playlists": "Playlists",
@@ -75,12 +75,12 @@
"ProviderValue": "Fornitur: {0}",
"ScheduledTaskFailedWithName": "{0} falla",
"ScheduledTaskStartedWithName": "{0} beda",
- "ServerNameNeedsToBeRestarted": "{0} jeħtieġ li jerġa' jinbeda",
+ "ServerNameNeedsToBeRestarted": "{0} jeħtieġ restart",
"Songs": "Kanzunetti",
- "StartupEmbyServerIsLoading": "Jellyfin Server qed jixgħel. Jekk jogħġbok erġa' pprova dalwaqt.",
+ "StartupEmbyServerIsLoading": "Jellyfin Server qed jillowdja. Jekk jogħġbok erġa' pprova ftit tal-ħin oħra.",
"Sync": "Sinkronizza",
"System": "Sistema",
- "Undefined": "Mhux Definit",
+ "Undefined": "Bla Definizzjoni",
"User": "Utent",
"UserCreatedWithName": "L-utent {0} inħoloq",
"UserDeletedWithName": "L-utent {0} tħassar",
@@ -89,45 +89,51 @@
"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}",
+ "NotificationOptionVideoPlayback": "Il-playback tal-filmat beda",
+ "NotificationOptionVideoPlaybackStopped": "Il-playback tal-filmat twaqqaf",
+ "Shows": "Serje",
+ "TvShows": "Serje Televiżivi",
+ "UserPolicyUpdatedWithName": "Il-politka tal-utent ġiet aġġornata għal {0}",
+ "UserStartedPlayingItemWithValues": "{0} qed jara {1} fuq {2}",
+ "UserStoppedPlayingItemWithValues": "{0} waqaf jara {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",
+ "TasksChannelsCategory": "Stazzjonijiet tal-Internet",
"TaskCleanActivityLog": "Naddaf il-Logg tal-Attività",
- "TaskCleanActivityLogDescription": "Iħassar l-entrati tar-reġistru tal-attività eqdem mill-età kkonfigurata.",
+ "TaskCleanActivityLogDescription": "Iħassar id-daħliet tar-reġistru tal-attività eqdem mill-età li kienet 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",
+ "TaskRefreshChapterImages": "Oħroġ ir-Ritratti tal-Kapitlu",
"TaskRefreshChapterImagesDescription": "Joħloq thumbnails għal vidjows li għandhom kapitli.",
- "TaskAudioNormalization": "Normalizzazzjoni Awdjo",
- "TaskAudioNormalizationDescription": "Skennja fajls għal data ta' normalizzazzjoni awdjo.",
+ "TaskAudioNormalization": "Normalizzazzjoni tal-Awdjo",
+ "TaskAudioNormalizationDescription": "Skennja fajls għal data fuq in-normalizzazzjoni tal-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.",
+ "TaskRefreshPeople": "Aġġorna l-Persuni",
+ "TaskRefreshPeopleDescription": "Jaġġorna l-metadata 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.",
+ "TaskRefreshTrickplayImagesDescription": "Joħloq previews trickplay għal videos fil-libreriji li għalihom hi attivata.",
+ "TaskUpdatePlugins": "Aġġorna l-Plugins",
+ "TaskCleanTranscode": "Naddaf id-Direttorju tat-Transcoding",
+ "TaskCleanTranscodeDescription": "Iħassar fajls tat-transcoding li huma eqdem minn ġurnata.",
+ "TaskRefreshChannels": "Aġġorna l-Istazzjonijiet",
+ "TaskRefreshChannelsDescription": "Aġġorna l-informazzjoni tal-istazzjonijiet tal-internet.",
"TaskDownloadMissingSubtitles": "Niżżel is-sottotitli nieqsa",
- "TaskOptimizeDatabase": "Ottimizza d-database",
+ "TaskOptimizeDatabase": "Ottimiżża 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.",
+ "TaskKeyframeExtractorDescription": "Jiġbed il-keyframes mill-fajls tal-videos biex jagħmel playlists HLS aktar preċiżi. Dan it-task jista' jdum żmien twil biex ilesti.",
"TaskCleanCollectionsAndPlaylists": "Naddaf il-kollezzjonijiet u l-playlists",
- "TaskCleanCollectionsAndPlaylistsDescription": "Ineħħi oġġetti minn kollezzjonijiet u playlists li m'għadhomx jeżistu."
+ "TaskCleanCollectionsAndPlaylistsDescription": "Ineħħi oġġetti minn kollezzjonijiet u playlists li m'għadhomx jeżistu.",
+ "TaskDownloadMissingLyrics": "Niżżel il-lirika nieqsa",
+ "TaskDownloadMissingLyricsDescription": "Iniżżel il-lirika għal-kanzunetti",
+ "TaskExtractMediaSegments": "Scan tas-Sezzjoni tal-Midja",
+ "TaskExtractMediaSegmentsDescription": "Jestratta jew iġib sezzjonijiet tal-midja minn plugins attivati tal-MediaSegment.",
+ "TaskMoveTrickplayImages": "Mexxi l-post tat-Trickplay Image",
+ "TaskMoveTrickplayImagesDescription": "Tmexxi l-files tat-trickplay li jeżistu skont kif inhi kkonfigurata l-librerija."
}
diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json
index 2327a73a9..8828eadcb 100644
--- a/Emby.Server.Implementations/Localization/Core/nl.json
+++ b/Emby.Server.Implementations/Localization/Core/nl.json
@@ -11,7 +11,7 @@
"Collections": "Collecties",
"DeviceOfflineWithName": "Verbinding met {0} is verbroken",
"DeviceOnlineWithName": "{0} is verbonden",
- "FailedLoginAttemptWithUserName": "Mislukte aanmeldpoging vanaf {0}",
+ "FailedLoginAttemptWithUserName": "Mislukte aanmeldpoging van {0}",
"Favorites": "Favorieten",
"Folders": "Mappen",
"Genres": "Genres",
@@ -117,7 +117,7 @@
"TaskCleanActivityLogDescription": "Verwijdert activiteitenlogs ouder dan de ingestelde leeftijd.",
"TaskCleanActivityLog": "Activiteitenlogboek legen",
"Undefined": "Niet gedefinieerd",
- "Forced": "Geforceerd",
+ "Forced": "Gedwongen",
"Default": "Standaard",
"TaskOptimizeDatabaseDescription": "Comprimeert de database en trimt vrije ruimte. Het uitvoeren van deze taak kan de prestaties verbeteren, na het scannen van de bibliotheek of andere aanpassingen die invloed hebben op de database.",
"TaskOptimizeDatabase": "Database optimaliseren",
diff --git a/Emby.Server.Implementations/Localization/Core/ro.json b/Emby.Server.Implementations/Localization/Core/ro.json
index bf59e1583..a873c157e 100644
--- a/Emby.Server.Implementations/Localization/Core/ro.json
+++ b/Emby.Server.Implementations/Localization/Core/ro.json
@@ -77,7 +77,7 @@
"HeaderAlbumArtists": "Artiști album",
"Genres": "Genuri",
"Folders": "Dosare",
- "Favorites": "Favorite",
+ "Favorites": "Preferate",
"FailedLoginAttemptWithUserName": "Încercare de conectare eșuată pentru {0}",
"DeviceOnlineWithName": "{0} este conectat",
"DeviceOfflineWithName": "{0} s-a deconectat",
diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json
index 01b8bfbe2..856ccb1ed 100644
--- a/Emby.Server.Implementations/Localization/Core/ru.json
+++ b/Emby.Server.Implementations/Localization/Core/ru.json
@@ -132,5 +132,9 @@
"TaskAudioNormalization": "Нормализация звука",
"TaskAudioNormalizationDescription": "Сканирует файлы на наличие данных о нормализации звука.",
"TaskDownloadMissingLyrics": "Загрузить недостающий текст",
- "TaskDownloadMissingLyricsDescription": "Загружает текст песен"
+ "TaskDownloadMissingLyricsDescription": "Загружает текст песен",
+ "TaskMoveTrickplayImages": "Перенесение местоположения изображений Trickplay",
+ "TaskExtractMediaSegments": "Сканирование медиасегментов",
+ "TaskExtractMediaSegmentsDescription": "Извлекает или получает медиасегменты из плагинов MediaSegment.",
+ "TaskMoveTrickplayImagesDescription": "Перемещает существующие файлы trickplay в соответствии с настройками медиатеки."
}
diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json
index 5cf54522b..60810b45d 100644
--- a/Emby.Server.Implementations/Localization/Core/sv.json
+++ b/Emby.Server.Implementations/Localization/Core/sv.json
@@ -82,13 +82,13 @@
"UserCreatedWithName": "Användaren {0} har skapats",
"UserDeletedWithName": "Användaren {0} har tagits bort",
"UserDownloadingItemWithValues": "{0} laddar ner {1}",
- "UserLockedOutWithName": "Användare {0} har låsts ute",
- "UserOfflineFromDevice": "{0} har avbrutit anslutningen från {1}",
+ "UserLockedOutWithName": "Användare {0} har utelåsts",
+ "UserOfflineFromDevice": "{0} har kopplat ned från {1}",
"UserOnlineFromDevice": "{0} är uppkopplad från {1}",
"UserPasswordChangedWithName": "Lösenordet för {0} har ändrats",
"UserPolicyUpdatedWithName": "Användarpolicyn har uppdaterats för {0}",
- "UserStartedPlayingItemWithValues": "{0} spelar upp {1} på {2}",
- "UserStoppedPlayingItemWithValues": "{0} har avslutat uppspelningen av {1} på {2}",
+ "UserStartedPlayingItemWithValues": "{0} spelar {1} på {2}",
+ "UserStoppedPlayingItemWithValues": "{0} har stoppat uppspelningen av {1} på {2}",
"ValueHasBeenAddedToLibrary": "{0} har lagts till i ditt mediebibliotek",
"ValueSpecialEpisodeName": "Specialavsnitt - {0}",
"VersionNumber": "Version {0}",
@@ -98,8 +98,8 @@
"TaskRefreshChannels": "Uppdatera kanaler",
"TaskCleanTranscodeDescription": "Raderar omkodningsfiler äldre än en dag.",
"TaskCleanTranscode": "Rensa omkodningskatalog",
- "TaskUpdatePluginsDescription": "Laddar ned och installerar uppdateringar till tilläggsprogram som är konfigurerade att uppdateras automatiskt.",
- "TaskUpdatePlugins": "Uppdatera tilläggsprogram",
+ "TaskUpdatePluginsDescription": "Laddar ned och installerar uppdateringar till tillägg som är konfigurerade att uppdateras automatiskt.",
+ "TaskUpdatePlugins": "Uppdatera tillägg",
"TaskRefreshPeopleDescription": "Uppdaterar metadata för skådespelare och regissörer i ditt mediabibliotek.",
"TaskCleanLogsDescription": "Raderar loggfiler som är mer än {0} dagar gamla.",
"TaskCleanLogs": "Rensa loggkatalog",
diff --git a/Emby.Server.Implementations/Localization/Core/uz.json b/Emby.Server.Implementations/Localization/Core/uz.json
index a1b3035f3..150fb7126 100644
--- a/Emby.Server.Implementations/Localization/Core/uz.json
+++ b/Emby.Server.Implementations/Localization/Core/uz.json
@@ -23,5 +23,92 @@
"HeaderLiveTV": "Jonli TV",
"HeaderNextUp": "Keyingisi",
"ItemAddedWithName": "{0} kutbxonaga qo'shildi",
- "LabelIpAddressValue": "IP manzil: {0}"
+ "LabelIpAddressValue": "IP manzil: {0}",
+ "SubtitleDownloadFailureFromForItem": "{0} dan {1} uchun taglavhalarni yuklab boʻlmadi",
+ "UserPasswordChangedWithName": "Foydalanuvchi {0} paroli oʻzgartirildi",
+ "ValueHasBeenAddedToLibrary": "{0} kutubxonaga qoʻshildi",
+ "TaskCleanActivityLogDescription": "Belgilangan yoshdan kattaroq faoliyat jurnali yozuvlarini oʻchiradi.",
+ "TaskAudioNormalization": "Ovozni normallashtirish",
+ "TaskRefreshLibraryDescription": "Media kutubxonasi yangi fayllar uchun skanerlanmoqda va metama'lumotlar yangilanmoqda.",
+ "Default": "Joriy",
+ "HeaderFavoriteAlbums": "Tanlangan albomlar",
+ "HeaderFavoriteArtists": "Tanlangan artistlar",
+ "HeaderFavoriteEpisodes": "Tanlangan epizodlar",
+ "HeaderFavoriteShows": "Tanlangan shoular",
+ "HeaderFavoriteSongs": "Tanlangan qo'shiqlar",
+ "HeaderRecordingGroups": "Yozuvlar guruhi",
+ "HomeVideos": "Uy videolari",
+ "NotificationOptionVideoPlaybackStopped": "Video ijrosi toʻxtatildi",
+ "TvShows": "TV seriallar",
+ "Undefined": "Belgilanmagan",
+ "User": "Foydalanuvchi",
+ "UserCreatedWithName": "{0} foydalanuvchi yaratildi",
+ "TaskCleanCacheDescription": "Tizimga kerak bo'lmagan kesh fayllari o'chiriladi.",
+ "TaskAudioNormalizationDescription": "Ovozni normallashtirish ma'lumotlari uchun fayllarni skanerlaydi.",
+ "PluginInstalledWithName": "{0} - o'rnatildi",
+ "PluginUninstalledWithName": "{0} - o'chirildi",
+ "HearingImpaired": "Yaxshi eshitmaydiganlar uchun",
+ "Inherit": "Meroslangan",
+ "NotificationOptionApplicationUpdateAvailable": "Ilova yangilanishi mavjud",
+ "NotificationOptionApplicationUpdateInstalled": "Ilova yangilanishi oʻrnatildi",
+ "LabelRunningTimeValue": "Davomiyligi",
+ "NotificationOptionAudioPlayback": "Audio tinglash boshlandi",
+ "NotificationOptionAudioPlaybackStopped": "Audio tinglash to'xtatildi",
+ "NotificationOptionCameraImageUploaded": "Kamera tasvirlari yuklandi",
+ "NotificationOptionInstallationFailed": "O'rnatishda hatolik",
+ "NotificationOptionNewLibraryContent": "Yangi tarkib qo'shildi",
+ "NotificationOptionPluginError": "Plagin ishdan chiqdi",
+ "NotificationOptionPluginInstalled": "Plagin o'rnatildi",
+ "NotificationOptionPluginUninstalled": "Plagin o'chirildi",
+ "NotificationOptionPluginUpdateInstalled": "Plagin uchun yangilanish o'rnatildi",
+ "NotificationOptionServerRestartRequired": "Server-ni qayta yuklash lozim",
+ "NotificationOptionTaskFailed": "Rejalashtirilgan vazifa bajarilmadi",
+ "NotificationOptionUserLockedOut": "Foydalanuvchi bloklangan",
+ "NotificationOptionVideoPlayback": "Video ijrosi boshlandi",
+ "Photos": "Surat",
+ "Latest": "So'ngi",
+ "MessageApplicationUpdated": "Jellyfin Server yangilandi",
+ "MessageApplicationUpdatedTo": "Jellyfin Server {0} gacha yangilandi",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Server konfiguratsiyasi ({0}-boʻlim) yangilandi",
+ "MessageServerConfigurationUpdated": "Server konfiguratsiyasi yangilandi",
+ "MixedContent": "Aralashgan tarkib",
+ "Movies": "Kinolar",
+ "Music": "Qo'shiqlar",
+ "MusicVideos": "Musiqali videolar",
+ "NameInstallFailed": "Omadsiz ornatish {0}",
+ "NameSeasonNumber": "{0} Fasl",
+ "NameSeasonUnknown": "Fasl aniqlanmagan",
+ "Playlists": "Pleylistlar",
+ "NewVersionIsAvailable": "Yuklab olish uchun Jellyfin Server ning yangi versiyasi mavjud",
+ "Plugin": "Plagin",
+ "TaskCleanLogs": "Jurnallar katalogini tozalash",
+ "PluginUpdatedWithName": "{0} - yangilandi",
+ "ProviderValue": "Yetkazib beruvchi: {0}",
+ "ScheduledTaskFailedWithName": "{0} - omadsiz",
+ "ScheduledTaskStartedWithName": "{0} - ishga tushirildi",
+ "ServerNameNeedsToBeRestarted": "Qayta yuklash kerak {0}",
+ "Shows": "Teleko'rsatuv",
+ "Songs": "Kompozitsiyalar",
+ "StartupEmbyServerIsLoading": "Jellyfin Server yuklanmoqda. Tez orada qayta urinib koʻring.",
+ "Sync": "Sinxronizatsiya",
+ "System": "Tizim",
+ "UserDeletedWithName": "{0} foydalanuvchisi oʻchirib tashlandi",
+ "UserDownloadingItemWithValues": "{0} yuklanmoqda {1}",
+ "UserLockedOutWithName": "{0} foydalanuvchisi bloklandi",
+ "UserOfflineFromDevice": "{0} {1}dan uzildi",
+ "UserOnlineFromDevice": "{0} {1} dan ulandi",
+ "UserPolicyUpdatedWithName": "{0} foydalanuvchisining siyosatlari yangilandi",
+ "UserStartedPlayingItemWithValues": "{0} - {2} da \"{1}\" ijrosi",
+ "UserStoppedPlayingItemWithValues": "{0} - ijro etish to‘xtatildi {1} {2}",
+ "ValueSpecialEpisodeName": "Maxsus qism – {0}",
+ "VersionNumber": "Versiya {0}",
+ "TasksMaintenanceCategory": "Xizmat ko'rsatish",
+ "TasksLibraryCategory": "Media kutubxona",
+ "TasksApplicationCategory": "Ilova",
+ "TasksChannelsCategory": "Internet kanallari",
+ "TaskCleanActivityLog": "Faoliyat jurnalini tozalash",
+ "TaskCleanCache": "Kesh katalogini tozalash",
+ "TaskRefreshChapterImages": "Sahnadan tasvirini chiqarish",
+ "TaskRefreshChapterImagesDescription": "Sahnalarni o'z ichiga olgan videolar uchun eskizlarni yaratadi.",
+ "TaskRefreshLibrary": "Media kutubxonangizni skanerlash"
}
diff --git a/Emby.Server.Implementations/Localization/Core/zh-HK.json b/Emby.Server.Implementations/Localization/Core/zh-HK.json
index 3ab9774c2..e2f768f1f 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-HK.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-HK.json
@@ -126,5 +126,12 @@
"External": "外部",
"HearingImpaired": "聽力障礙",
"TaskRefreshTrickplayImages": "建立 Trickplay 圖像",
- "TaskRefreshTrickplayImagesDescription": "為已啟用 Trickplay 的媒體庫內的影片建立 Trickplay 預覽圖。"
+ "TaskRefreshTrickplayImagesDescription": "為已啟用 Trickplay 的媒體庫內的影片建立 Trickplay 預覽圖。",
+ "TaskExtractMediaSegments": "掃描媒體段落",
+ "TaskExtractMediaSegmentsDescription": "從MediaSegment中被允許的插件獲取媒體段落。",
+ "TaskDownloadMissingLyrics": "下載欠缺歌詞",
+ "TaskDownloadMissingLyricsDescription": "下載歌詞",
+ "TaskCleanCollectionsAndPlaylists": "整理媒體與播放清單",
+ "TaskAudioNormalization": "音訊同等化",
+ "TaskAudioNormalizationDescription": "掃描檔案裏的音訊同等化資料。"
}
diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs
index 47ff22c0b..daeb7fed8 100644
--- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs
+++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs
@@ -216,14 +216,11 @@ namespace Emby.Server.Implementations.Playlists
var newItems = GetPlaylistItems(newItemIds, user, options)
.Where(i => i.SupportsAddingToPlaylist);
- // Filter out duplicate items, if necessary
- if (!_appConfig.DoPlaylistsAllowDuplicates())
- {
- var existingIds = playlist.LinkedChildren.Select(c => c.ItemId).ToHashSet();
- newItems = newItems
- .Where(i => !existingIds.Contains(i.Id))
- .Distinct();
- }
+ // Filter out duplicate items
+ var existingIds = playlist.LinkedChildren.Select(c => c.ItemId).ToHashSet();
+ newItems = newItems
+ .Where(i => !existingIds.Contains(i.Id))
+ .Distinct();
// Create a list of the new linked children to add to the playlist
var childrenToAdd = newItems
@@ -269,7 +266,7 @@ namespace Emby.Server.Implementations.Playlists
var idList = entryIds.ToList();
- var removals = children.Where(i => idList.Contains(i.Item1.Id));
+ var removals = children.Where(i => idList.Contains(i.Item1.ItemId?.ToString("N", CultureInfo.InvariantCulture)));
playlist.LinkedChildren = children.Except(removals)
.Select(i => i.Item1)
@@ -286,26 +283,39 @@ namespace Emby.Server.Implementations.Playlists
RefreshPriority.High);
}
- public async Task MoveItemAsync(string playlistId, string entryId, int newIndex)
+ public async Task MoveItemAsync(string playlistId, string entryId, int newIndex, Guid callingUserId)
{
if (_libraryManager.GetItemById(playlistId) is not Playlist playlist)
{
throw new ArgumentException("No Playlist exists with the supplied Id");
}
+ var user = _userManager.GetUserById(callingUserId);
var children = playlist.GetManageableItems().ToList();
+ var accessibleChildren = children.Where(c => c.Item2.IsVisible(user)).ToArray();
- var oldIndex = children.FindIndex(i => string.Equals(entryId, i.Item1.Id, StringComparison.OrdinalIgnoreCase));
+ var oldIndexAll = children.FindIndex(i => string.Equals(entryId, i.Item1.ItemId?.ToString("N", CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase));
+ var oldIndexAccessible = accessibleChildren.FindIndex(i => string.Equals(entryId, i.Item1.ItemId?.ToString("N", CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase));
- if (oldIndex == newIndex)
+ if (oldIndexAccessible == newIndex)
{
return;
}
- var item = playlist.LinkedChildren[oldIndex];
+ var newPriorItemIndex = newIndex > oldIndexAccessible ? newIndex : newIndex - 1 < 0 ? 0 : newIndex - 1;
+ var newPriorItemId = accessibleChildren[newPriorItemIndex].Item1.ItemId;
+ var newPriorItemIndexOnAllChildren = children.FindIndex(c => c.Item1.ItemId.Equals(newPriorItemId));
+ var adjustedNewIndex = newPriorItemIndexOnAllChildren + 1;
- var newList = playlist.LinkedChildren.ToList();
+ var item = playlist.LinkedChildren.FirstOrDefault(i => string.Equals(entryId, i.ItemId?.ToString("N", CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase));
+ if (item is null)
+ {
+ _logger.LogWarning("Modified item not found in playlist. ItemId: {ItemId}, PlaylistId: {PlaylistId}", item.ItemId, playlistId);
+ return;
+ }
+
+ var newList = playlist.LinkedChildren.ToList();
newList.Remove(item);
if (newIndex >= newList.Count)
@@ -314,7 +324,7 @@ namespace Emby.Server.Implementations.Playlists
}
else
{
- newList.Insert(newIndex, item);
+ newList.Insert(adjustedNewIndex, item);
}
playlist.LinkedChildren = [.. newList];
diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs
index db82a2900..e7323d9d0 100644
--- a/Emby.Server.Implementations/Plugins/PluginManager.cs
+++ b/Emby.Server.Implementations/Plugins/PluginManager.cs
@@ -835,7 +835,7 @@ namespace Emby.Server.Implementations.Plugins
/// <exception cref="ArgumentNullException">If the <see cref="LocalPlugin"/> is null.</exception>
private bool TryGetPluginDlls(LocalPlugin plugin, out IReadOnlyList<string> whitelistedDlls)
{
- ArgumentNullException.ThrowIfNull(nameof(plugin));
+ ArgumentNullException.ThrowIfNull(plugin);
IReadOnlyList<string> pluginDlls = Directory.GetFiles(plugin.Path, "*.dll", SearchOption.AllDirectories);
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index 6a8ad2bdc..fe2c3d24f 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -1938,7 +1938,11 @@ namespace Emby.Server.Implementations.Session
// Don't report acceleration type for non-admin users.
result = result.Select(r =>
{
- r.TranscodingInfo.HardwareAccelerationType = HardwareAccelerationType.none;
+ if (r.TranscodingInfo is not null)
+ {
+ r.TranscodingInfo.HardwareAccelerationType = HardwareAccelerationType.none;
+ }
+
return r;
});
}
diff --git a/Jellyfin.Api/Controllers/ItemRefreshController.cs b/Jellyfin.Api/Controllers/ItemRefreshController.cs
index d7a8c37c4..7effe61e4 100644
--- a/Jellyfin.Api/Controllers/ItemRefreshController.cs
+++ b/Jellyfin.Api/Controllers/ItemRefreshController.cs
@@ -50,6 +50,7 @@ public class ItemRefreshController : BaseJellyfinApiController
/// <param name="imageRefreshMode">(Optional) Specifies the image refresh mode.</param>
/// <param name="replaceAllMetadata">(Optional) Determines if metadata should be replaced. Only applicable if mode is FullRefresh.</param>
/// <param name="replaceAllImages">(Optional) Determines if images should be replaced. Only applicable if mode is FullRefresh.</param>
+ /// <param name="regenerateTrickplay">(Optional) Determines if trickplay images should be replaced. Only applicable if mode is FullRefresh.</param>
/// <response code="204">Item metadata refresh queued.</response>
/// <response code="404">Item to refresh not found.</response>
/// <returns>An <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if the item could not be found.</returns>
@@ -62,7 +63,8 @@ public class ItemRefreshController : BaseJellyfinApiController
[FromQuery] MetadataRefreshMode metadataRefreshMode = MetadataRefreshMode.None,
[FromQuery] MetadataRefreshMode imageRefreshMode = MetadataRefreshMode.None,
[FromQuery] bool replaceAllMetadata = false,
- [FromQuery] bool replaceAllImages = false)
+ [FromQuery] bool replaceAllImages = false,
+ [FromQuery] bool regenerateTrickplay = false)
{
var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
if (item is null)
@@ -81,7 +83,8 @@ public class ItemRefreshController : BaseJellyfinApiController
|| replaceAllImages
|| replaceAllMetadata,
IsAutomated = false,
- RemoveOldMetadata = replaceAllMetadata
+ RemoveOldMetadata = replaceAllMetadata,
+ RegenerateTrickplay = regenerateTrickplay
};
_providerManager.QueueRefresh(item.Id, refreshOptions, RefreshPriority.High);
diff --git a/Jellyfin.Api/Controllers/ItemUpdateController.cs b/Jellyfin.Api/Controllers/ItemUpdateController.cs
index 4001a6add..d49e0753e 100644
--- a/Jellyfin.Api/Controllers/ItemUpdateController.cs
+++ b/Jellyfin.Api/Controllers/ItemUpdateController.cs
@@ -457,7 +457,7 @@ public class ItemUpdateController : BaseJellyfinApiController
return null;
}
- return (SeriesStatus)Enum.Parse(typeof(SeriesStatus), item.Status, true);
+ return Enum.Parse<SeriesStatus>(item.Status, true);
}
private DateTime NormalizeDateTime(DateTime val)
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index afc93c3a8..1b23683fb 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/Jellyfin.Api/Controllers/LibraryController.cs
@@ -867,6 +867,16 @@ public class LibraryController : BaseJellyfinApiController
.DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
.ToArray();
+ result.MediaSegmentProviders = plugins
+ .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.MediaSegmentProvider))
+ .Select(i => new LibraryOptionInfoDto
+ {
+ Name = i.Name,
+ DefaultEnabled = true
+ })
+ .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
+ .ToArray();
+
var typeOptions = new List<LibraryTypeOptionsDto>();
foreach (var type in types)
diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs
index 0ae8baa67..421f23fa1 100644
--- a/Jellyfin.Api/Controllers/LiveTvController.cs
+++ b/Jellyfin.Api/Controllers/LiveTvController.cs
@@ -962,9 +962,9 @@ public class LiveTvController : BaseJellyfinApiController
}
/// <summary>
- /// Get guid info.
+ /// Get guide info.
/// </summary>
- /// <response code="200">Guid info returned.</response>
+ /// <response code="200">Guide info returned.</response>
/// <returns>An <see cref="OkResult"/> containing the guide info.</returns>
[HttpGet("GuideInfo")]
[Authorize(Policy = Policies.LiveTvAccess)]
diff --git a/Jellyfin.Api/Controllers/MediaSegmentsController.cs b/Jellyfin.Api/Controllers/MediaSegmentsController.cs
index 3dc5167a2..2d1d4e2c8 100644
--- a/Jellyfin.Api/Controllers/MediaSegmentsController.cs
+++ b/Jellyfin.Api/Controllers/MediaSegmentsController.cs
@@ -55,7 +55,7 @@ public class MediaSegmentsController : BaseJellyfinApiController
return NotFound();
}
- var items = await _mediaSegmentManager.GetSegmentsAsync(item.Id, includeSegmentTypes).ConfigureAwait(false);
+ var items = await _mediaSegmentManager.GetSegmentsAsync(item, includeSegmentTypes).ConfigureAwait(false);
return Ok(new QueryResult<MediaSegmentDto>(items.ToArray()));
}
}
diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs
index e6f23b136..1ab36ccc6 100644
--- a/Jellyfin.Api/Controllers/PlaylistsController.cs
+++ b/Jellyfin.Api/Controllers/PlaylistsController.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
+using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
@@ -426,7 +427,7 @@ public class PlaylistsController : BaseJellyfinApiController
return Forbid();
}
- await _playlistManager.MoveItemAsync(playlistId, itemId, newIndex).ConfigureAwait(false);
+ await _playlistManager.MoveItemAsync(playlistId, itemId, newIndex, callingUserId).ConfigureAwait(false);
return NoContent();
}
@@ -514,7 +515,8 @@ public class PlaylistsController : BaseJellyfinApiController
return Forbid();
}
- var items = playlist.GetManageableItems().ToArray();
+ var user = _userManager.GetUserById(callingUserId);
+ var items = playlist.GetManageableItems().Where(i => i.Item2.IsVisible(user)).ToArray();
var count = items.Length;
if (startIndex.HasValue)
{
@@ -529,11 +531,11 @@ public class PlaylistsController : BaseJellyfinApiController
var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
- var user = _userManager.GetUserById(callingUserId);
+
var dtos = _dtoService.GetBaseItemDtos(items.Select(i => i.Item2).ToList(), dtoOptions, user);
for (int index = 0; index < dtos.Count; index++)
{
- dtos[index].PlaylistItemId = items[index].Item1.Id;
+ dtos[index].PlaylistItemId = items[index].Item1.ItemId?.ToString("N", CultureInfo.InvariantCulture);
}
var result = new QueryResult<BaseItemDto>(
diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj
index 5f86a6b6b..25feaa2d7 100644
--- a/Jellyfin.Api/Jellyfin.Api.csproj
+++ b/Jellyfin.Api/Jellyfin.Api.csproj
@@ -6,7 +6,7 @@
</PropertyGroup>
<PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs b/Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs
index d07349bdf..c49243668 100644
--- a/Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs
+++ b/Jellyfin.Api/Models/LibraryDtos/LibraryOptionsResultDto.cs
@@ -29,6 +29,11 @@ public class LibraryOptionsResultDto
public IReadOnlyList<LibraryOptionInfoDto> LyricFetchers { get; set; } = Array.Empty<LibraryOptionInfoDto>();
/// <summary>
+ /// Gets or sets the list of MediaSegment Providers.
+ /// </summary>
+ public IReadOnlyList<LibraryOptionInfoDto> MediaSegmentProviders { get; set; } = Array.Empty<LibraryOptionInfoDto>();
+
+ /// <summary>
/// Gets or sets the type options.
/// </summary>
public IReadOnlyList<LibraryTypeOptionsDto> TypeOptions { get; set; } = Array.Empty<LibraryTypeOptionsDto>();
diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs
index 2c9cc8d78..9bbe9efe8 100644
--- a/Jellyfin.Data/Entities/User.cs
+++ b/Jellyfin.Data/Entities/User.cs
@@ -514,7 +514,7 @@ namespace Jellyfin.Data.Entities
/// </summary>
public void AddDefaultPreferences()
{
- foreach (var val in Enum.GetValues(typeof(PreferenceKind)).Cast<PreferenceKind>())
+ foreach (var val in Enum.GetValues<PreferenceKind>())
{
Preferences.Add(new Preference(val, string.Empty));
}
diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj
index e24e37740..921cf2d8c 100644
--- a/Jellyfin.Data/Jellyfin.Data.csproj
+++ b/Jellyfin.Data/Jellyfin.Data.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
@@ -18,7 +18,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Data</PackageId>
- <VersionPrefix>10.10.0</VersionPrefix>
+ <VersionPrefix>10.11.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
index 20944ee4b..31cf24fb2 100644
--- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
+++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs b/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs
index d641f521b..2d3a25357 100644
--- a/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs
+++ b/Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs
@@ -61,7 +61,7 @@ public class MediaSegmentManager : IMediaSegmentManager
.Where(e => !libraryOptions.DisabledMediaSegmentProviders.Contains(GetProviderId(e.Name)))
.OrderBy(i =>
{
- var index = libraryOptions.MediaSegmentProvideOrder.IndexOf(i.Name);
+ var index = libraryOptions.MediaSegmentProviderOrder.IndexOf(i.Name);
return index == -1 ? int.MaxValue : index;
})
.ToList();
@@ -139,23 +139,53 @@ public class MediaSegmentManager : IMediaSegmentManager
}
/// <inheritdoc />
- public async Task<IEnumerable<MediaSegmentDto>> GetSegmentsAsync(Guid itemId, IEnumerable<MediaSegmentType>? typeFilter)
+ public async Task<IEnumerable<MediaSegmentDto>> GetSegmentsAsync(Guid itemId, IEnumerable<MediaSegmentType>? typeFilter, bool filterByProvider = true)
+ {
+ var baseItem = _libraryManager.GetItemById(itemId);
+
+ if (baseItem is null)
+ {
+ _logger.LogError("Tried to request segments for an invalid item");
+ return [];
+ }
+
+ return await GetSegmentsAsync(baseItem, typeFilter, filterByProvider).ConfigureAwait(false);
+ }
+
+ /// <inheritdoc />
+ public async Task<IEnumerable<MediaSegmentDto>> GetSegmentsAsync(BaseItem item, IEnumerable<MediaSegmentType>? typeFilter, bool filterByProvider = true)
{
using var db = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
var query = db.MediaSegments
- .Where(e => e.ItemId.Equals(itemId));
+ .Where(e => e.ItemId.Equals(item.Id));
if (typeFilter is not null)
{
query = query.Where(e => typeFilter.Contains(e.Type));
}
+ if (filterByProvider)
+ {
+ var libraryOptions = _libraryManager.GetLibraryOptions(item);
+ var providerIds = _segmentProviders
+ .Where(e => !libraryOptions.DisabledMediaSegmentProviders.Contains(GetProviderId(e.Name)))
+ .Select(f => GetProviderId(f.Name))
+ .ToArray();
+ if (providerIds.Length == 0)
+ {
+ return [];
+ }
+
+ query = query.Where(e => providerIds.Contains(e.SegmentProviderId));
+ }
+
return query
.OrderBy(e => e.StartTicks)
.AsNoTracking()
- .ToImmutableList()
- .Select(Map);
+ .AsEnumerable()
+ .Select(Map)
+ .ToArray();
}
private static MediaSegmentDto Map(MediaSegment segment)
diff --git a/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs b/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs
index cfe385106..af57bc134 100644
--- a/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs
+++ b/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs
@@ -238,7 +238,7 @@ public class TrickplayManager : ITrickplayManager
foreach (var tile in existingFiles)
{
var image = _imageEncoder.GetImageSize(tile);
- localTrickplayInfo.Height = Math.Max(localTrickplayInfo.Height, image.Height);
+ localTrickplayInfo.Height = Math.Max(localTrickplayInfo.Height, (int)Math.Ceiling((double)image.Height / localTrickplayInfo.TileHeight));
var bitrate = (int)Math.Ceiling((decimal)new FileInfo(tile).Length * 8 / localTrickplayInfo.TileWidth / localTrickplayInfo.TileHeight / (localTrickplayInfo.Interval / 1000));
localTrickplayInfo.Bandwidth = Math.Max(localTrickplayInfo.Bandwidth, bitrate);
}
diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs
index 1b6635938..c7ae0f4db 100644
--- a/Jellyfin.Server.Implementations/Users/UserManager.cs
+++ b/Jellyfin.Server.Implementations/Users/UserManager.cs
@@ -113,7 +113,7 @@ namespace Jellyfin.Server.Implementations.Users
// This is some regex that matches only on unicode "word" characters, as well as -, _ and @
// In theory this will cut out most if not all 'control' characters which should help minimize any weirdness
// Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), periods (.) and spaces ( )
- [GeneratedRegex(@"^[\w\ \-'._@]+$")]
+ [GeneratedRegex(@"^[\w\ \-'._@+]+$")]
private static partial Regex ValidUsernameRegex();
/// <inheritdoc/>
diff --git a/Jellyfin.Server/Helpers/StartupHelpers.cs b/Jellyfin.Server/Helpers/StartupHelpers.cs
index 0802b23ad..bbf6d31f1 100644
--- a/Jellyfin.Server/Helpers/StartupHelpers.cs
+++ b/Jellyfin.Server/Helpers/StartupHelpers.cs
@@ -292,13 +292,5 @@ public static class StartupHelpers
// Make sure we have all the code pages we can get
// Ref: https://docs.microsoft.com/en-us/dotnet/api/system.text.codepagesencodingprovider.instance?view=netcore-3.0#remarks
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
-
- // Increase the max http request limit
- // The default connection limit is 10 for ASP.NET hosted applications and 2 for all others.
- ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit);
-
- // Disable the "Expect: 100-Continue" header by default
- // http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c
- ServicePointManager.Expect100Continue = false;
}
}
diff --git a/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs b/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs
index 801026c54..901ed55be 100644
--- a/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs
+++ b/Jellyfin.Server/Infrastructure/SymlinkFollowingPhysicalFileResultExecutor.cs
@@ -101,7 +101,7 @@ namespace Jellyfin.Server.Infrastructure
count: null);
}
- private async Task SendFileAsync(string filePath, HttpResponse response, long offset, long? count)
+ private async Task SendFileAsync(string filePath, HttpResponse response, long offset, long? count, CancellationToken cancellationToken = default)
{
var fileInfo = GetFileInfo(filePath);
if (offset < 0 || offset > fileInfo.Length)
@@ -118,6 +118,9 @@ namespace Jellyfin.Server.Infrastructure
// Copied from SendFileFallback.SendFileAsync
const int BufferSize = 1024 * 16;
+ var useRequestAborted = !cancellationToken.CanBeCanceled;
+ var localCancel = useRequestAborted ? response.HttpContext.RequestAborted : cancellationToken;
+
var fileStream = new FileStream(
filePath,
FileMode.Open,
@@ -127,10 +130,17 @@ namespace Jellyfin.Server.Infrastructure
options: FileOptions.Asynchronous | FileOptions.SequentialScan);
await using (fileStream.ConfigureAwait(false))
{
- fileStream.Seek(offset, SeekOrigin.Begin);
- await StreamCopyOperation
- .CopyToAsync(fileStream, response.Body, count, BufferSize, CancellationToken.None)
- .ConfigureAwait(true);
+ try
+ {
+ localCancel.ThrowIfCancellationRequested();
+ fileStream.Seek(offset, SeekOrigin.Begin);
+ await StreamCopyOperation
+ .CopyToAsync(fileStream, response.Body, count, BufferSize, localCancel)
+ .ConfigureAwait(true);
+ }
+ catch (OperationCanceledException) when (useRequestAborted)
+ {
+ }
}
}
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index e18212908..ebb12ba4e 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -8,7 +8,7 @@
<PropertyGroup>
<AssemblyName>jellyfin</AssemblyName>
<OutputType>Exe</OutputType>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
<ServerGarbageCollection>false</ServerGarbageCollection>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs
index 9d4441ac3..2ab130eef 100644
--- a/Jellyfin.Server/Migrations/MigrationRunner.cs
+++ b/Jellyfin.Server/Migrations/MigrationRunner.cs
@@ -47,7 +47,8 @@ namespace Jellyfin.Server.Migrations
typeof(Routines.AddDefaultCastReceivers),
typeof(Routines.UpdateDefaultPluginRepository),
typeof(Routines.FixAudioData),
- typeof(Routines.MoveTrickplayFiles)
+ typeof(Routines.MoveTrickplayFiles),
+ typeof(Routines.RemoveDuplicatePlaylistChildren)
};
/// <summary>
diff --git a/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs b/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs
index 3655a610d..192c170b2 100644
--- a/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs
+++ b/Jellyfin.Server/Migrations/Routines/FixPlaylistOwner.cs
@@ -15,12 +15,12 @@ namespace Jellyfin.Server.Migrations.Routines;
/// </summary>
internal class FixPlaylistOwner : IMigrationRoutine
{
- private readonly ILogger<RemoveDuplicateExtras> _logger;
+ private readonly ILogger<FixPlaylistOwner> _logger;
private readonly ILibraryManager _libraryManager;
private readonly IPlaylistManager _playlistManager;
public FixPlaylistOwner(
- ILogger<RemoveDuplicateExtras> logger,
+ ILogger<FixPlaylistOwner> logger,
ILibraryManager libraryManager,
IPlaylistManager playlistManager)
{
diff --git a/Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs b/Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs
new file mode 100644
index 000000000..f84bccc25
--- /dev/null
+++ b/Jellyfin.Server/Migrations/Routines/RemoveDuplicatePlaylistChildren.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Linq;
+using System.Threading;
+
+using Jellyfin.Data.Enums;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Playlists;
+using Microsoft.Extensions.Logging;
+
+namespace Jellyfin.Server.Migrations.Routines;
+
+/// <summary>
+/// Remove duplicate playlist entries.
+/// </summary>
+internal class RemoveDuplicatePlaylistChildren : IMigrationRoutine
+{
+ private readonly ILogger<RemoveDuplicatePlaylistChildren> _logger;
+ private readonly ILibraryManager _libraryManager;
+ private readonly IPlaylistManager _playlistManager;
+
+ public RemoveDuplicatePlaylistChildren(
+ ILogger<RemoveDuplicatePlaylistChildren> logger,
+ ILibraryManager libraryManager,
+ IPlaylistManager playlistManager)
+ {
+ _logger = logger;
+ _libraryManager = libraryManager;
+ _playlistManager = playlistManager;
+ }
+
+ /// <inheritdoc/>
+ public Guid Id => Guid.Parse("{96C156A2-7A13-4B3B-A8B8-FB80C94D20C0}");
+
+ /// <inheritdoc/>
+ public string Name => "RemoveDuplicatePlaylistChildren";
+
+ /// <inheritdoc/>
+ public bool PerformOnNewInstall => false;
+
+ /// <inheritdoc/>
+ public void Perform()
+ {
+ var playlists = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ IncludeItemTypes = [BaseItemKind.Playlist]
+ })
+ .Cast<Playlist>()
+ .Where(p => !p.OpenAccess || !p.OwnerUserId.Equals(Guid.Empty))
+ .ToArray();
+
+ if (playlists.Length > 0)
+ {
+ foreach (var playlist in playlists)
+ {
+ var linkedChildren = playlist.LinkedChildren;
+ if (linkedChildren.Length > 0)
+ {
+ var nullItemChildren = linkedChildren.Where(c => c.ItemId is null);
+ var deduplicatedChildren = linkedChildren.DistinctBy(c => c.ItemId);
+ var newLinkedChildren = nullItemChildren.Concat(deduplicatedChildren);
+ playlist.LinkedChildren = linkedChildren;
+ playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult();
+ _playlistManager.SavePlaylistFile(playlist);
+ }
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index c1945bf93..de6be4707 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Common</PackageId>
- <VersionPrefix>10.10.0</VersionPrefix>
+ <VersionPrefix>10.11.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
@@ -28,7 +28,7 @@
</ItemGroup>
<PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
diff --git a/MediaBrowser.Common/Plugins/BasePluginOfT.cs b/MediaBrowser.Common/Plugins/BasePluginOfT.cs
index 116e9cef8..bf2f12cb9 100644
--- a/MediaBrowser.Common/Plugins/BasePluginOfT.cs
+++ b/MediaBrowser.Common/Plugins/BasePluginOfT.cs
@@ -193,7 +193,7 @@ namespace MediaBrowser.Common.Plugins
}
catch
{
- var config = (TConfigurationType)Activator.CreateInstance(typeof(TConfigurationType));
+ var config = Activator.CreateInstance<TConfigurationType>();
SaveConfiguration(config);
return config;
}
diff --git a/MediaBrowser.Controller/Entities/LinkedChild.cs b/MediaBrowser.Controller/Entities/LinkedChild.cs
index fd5fef3dc..98e4f525f 100644
--- a/MediaBrowser.Controller/Entities/LinkedChild.cs
+++ b/MediaBrowser.Controller/Entities/LinkedChild.cs
@@ -4,7 +4,6 @@
using System;
using System.Globalization;
-using System.Text.Json.Serialization;
namespace MediaBrowser.Controller.Entities
{
@@ -12,7 +11,6 @@ namespace MediaBrowser.Controller.Entities
{
public LinkedChild()
{
- Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
}
public string Path { get; set; }
@@ -21,9 +19,6 @@ namespace MediaBrowser.Controller.Entities
public string LibraryItemId { get; set; }
- [JsonIgnore]
- public string Id { get; set; }
-
/// <summary>
/// Gets or sets the linked item id.
/// </summary>
@@ -31,6 +26,8 @@ namespace MediaBrowser.Controller.Entities
public static LinkedChild Create(BaseItem item)
{
+ ArgumentNullException.ThrowIfNull(item);
+
var child = new LinkedChild
{
Path = item.Path,
diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs
index f8049cd48..e4806109a 100644
--- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs
+++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs
@@ -50,11 +50,6 @@ namespace MediaBrowser.Controller.Extensions
public const string FfmpegPathKey = "ffmpeg";
/// <summary>
- /// The key for a setting that indicates whether playlists should allow duplicate entries.
- /// </summary>
- public const string PlaylistsAllowDuplicatesKey = "playlists:allowDuplicates";
-
- /// <summary>
/// The key for a setting that indicates whether kestrel should bind to a unix socket.
/// </summary>
public const string BindToUnixSocketKey = "kestrel:socket";
@@ -121,14 +116,6 @@ namespace MediaBrowser.Controller.Extensions
=> configuration.GetValue<bool>(FfmpegImgExtractPerfTradeoffKey);
/// <summary>
- /// Gets a value indicating whether playlists should allow duplicate entries from the <see cref="IConfiguration"/>.
- /// </summary>
- /// <param name="configuration">The configuration to read the setting from.</param>
- /// <returns>True if playlists should allow duplicates, otherwise false.</returns>
- public static bool DoPlaylistsAllowDuplicates(this IConfiguration configuration)
- => configuration.GetValue<bool>(PlaylistsAllowDuplicatesKey);
-
- /// <summary>
/// Gets a value indicating whether kestrel should bind to a unix socket from the <see cref="IConfiguration" />.
/// </summary>
/// <param name="configuration">The configuration to read the setting from.</param>
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index 1ef2eb343..ba4a2a59c 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Controller</PackageId>
- <VersionPrefix>10.10.0</VersionPrefix>
+ <VersionPrefix>10.11.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
@@ -34,7 +34,7 @@
</ItemGroup>
<PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 28f0d1fff..9399679a4 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -2196,7 +2196,10 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var videoFrameRate = videoStream.ReferenceFrameRate;
- if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value)
+ // Add a little tolerance to the framerate check because some videos might record a framerate
+ // that is slightly higher than the intended framerate, but the device can still play it correctly.
+ // 0.05 fps tolerance should be safe enough.
+ if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value + 0.05f)
{
return false;
}
@@ -3318,24 +3321,25 @@ namespace MediaBrowser.Controller.MediaEncoding
&& options.VppTonemappingBrightness >= -100
&& options.VppTonemappingBrightness <= 100)
{
- procampParams += $"=b={options.VppTonemappingBrightness}";
+ procampParams += "procamp_vaapi=b={0}";
doVaVppProcamp = true;
}
if (options.VppTonemappingContrast > 1
&& options.VppTonemappingContrast <= 10)
{
- procampParams += doVaVppProcamp ? ":" : "=";
- procampParams += $"c={options.VppTonemappingContrast}";
+ procampParams += doVaVppProcamp ? ":c={1}" : "procamp_vaapi=c={1}";
doVaVppProcamp = true;
}
- args = "{0}tonemap_vaapi=format={1}:p=bt709:t=bt709:m=bt709:extra_hw_frames=32";
+ args = procampParams + "{2}tonemap_vaapi=format={3}:p=bt709:t=bt709:m=bt709:extra_hw_frames=32";
return string.Format(
CultureInfo.InvariantCulture,
args,
- doVaVppProcamp ? $"procamp_vaapi{procampParams}," : string.Empty,
+ options.VppTonemappingBrightness,
+ options.VppTonemappingContrast,
+ doVaVppProcamp ? "," : string.Empty,
videoFormat ?? "nv12");
}
else
@@ -3523,20 +3527,29 @@ namespace MediaBrowser.Controller.MediaEncoding
{
// tonemapx requires yuv420p10 input for dovi reshaping, let ffmpeg convert the frame when necessary
var tonemapFormat = requireDoviReshaping ? "yuv420p" : outFormat;
-
- var tonemapArgs = $"tonemapx=tonemap={options.TonemappingAlgorithm}:desat={options.TonemappingDesat}:peak={options.TonemappingPeak}:t=bt709:m=bt709:p=bt709:format={tonemapFormat}";
+ var tonemapArgString = "tonemapx=tonemap={0}:desat={1}:peak={2}:t=bt709:m=bt709:p=bt709:format={3}";
if (options.TonemappingParam != 0)
{
- tonemapArgs += $":param={options.TonemappingParam}";
+ tonemapArgString += ":param={4}";
}
var range = options.TonemappingRange;
if (range == TonemappingRange.tv || range == TonemappingRange.pc)
{
- tonemapArgs += $":range={options.TonemappingRange}";
+ tonemapArgString += ":range={5}";
}
+ var tonemapArgs = string.Format(
+ CultureInfo.InvariantCulture,
+ tonemapArgString,
+ options.TonemappingAlgorithm,
+ options.TonemappingDesat,
+ options.TonemappingPeak,
+ tonemapFormat,
+ options.TonemappingParam,
+ options.TonemappingRange);
+
mainFilters.Add(tonemapArgs);
}
else
@@ -4128,31 +4141,46 @@ namespace MediaBrowser.Controller.MediaEncoding
else if (isD3d11vaDecoder || isQsvDecoder)
{
var isRext = IsVideoStreamHevcRext(state);
- var twoPassVppTonemap = isRext;
+ var twoPassVppTonemap = false;
var doVppFullRangeOut = isMjpegEncoder
&& _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
var doVppScaleModeHq = isMjpegEncoder
&& _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
var doVppProcamp = false;
var procampParams = string.Empty;
+ var procampParamsString = string.Empty;
if (doVppTonemap)
{
+ if (isRext)
+ {
+ // VPP tonemap requires p010 input
+ twoPassVppTonemap = true;
+ }
+
if (options.VppTonemappingBrightness != 0
&& options.VppTonemappingBrightness >= -100
&& options.VppTonemappingBrightness <= 100)
{
- procampParams += $":brightness={options.VppTonemappingBrightness}";
+ procampParamsString += ":brightness={0}";
twoPassVppTonemap = doVppProcamp = true;
}
if (options.VppTonemappingContrast > 1
&& options.VppTonemappingContrast <= 10)
{
- procampParams += $":contrast={options.VppTonemappingContrast}";
+ procampParamsString += ":contrast={1}";
twoPassVppTonemap = doVppProcamp = true;
}
- procampParams += doVppProcamp ? ":procamp=1:async_depth=2" : string.Empty;
+ if (doVppProcamp)
+ {
+ procampParamsString += ":procamp=1:async_depth=2";
+ procampParams = string.Format(
+ CultureInfo.InvariantCulture,
+ procampParamsString,
+ options.VppTonemappingBrightness,
+ options.VppTonemappingContrast);
+ }
}
var outFormat = doOclTonemap ? ((doVppTranspose || isRext) ? "p010" : string.Empty) : "nv12";
diff --git a/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs b/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs
index 010d7edb4..672f27eca 100644
--- a/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs
+++ b/MediaBrowser.Controller/MediaSegements/IMediaSegmentManager.cs
@@ -50,8 +50,18 @@ public interface IMediaSegmentManager
/// </summary>
/// <param name="itemId">The id of the <see cref="BaseItem"/>.</param>
/// <param name="typeFilter">filteres all media segments of the given type to be included. If null all types are included.</param>
+ /// <param name="filterByProvider">When set filteres the segments to only return those that which providers are currently enabled on their library.</param>
/// <returns>An enumerator of <see cref="MediaSegmentDto"/>'s.</returns>
- Task<IEnumerable<MediaSegmentDto>> GetSegmentsAsync(Guid itemId, IEnumerable<MediaSegmentType>? typeFilter);
+ Task<IEnumerable<MediaSegmentDto>> GetSegmentsAsync(Guid itemId, IEnumerable<MediaSegmentType>? typeFilter, bool filterByProvider = true);
+
+ /// <summary>
+ /// Obtains all segments accociated with the itemId.
+ /// </summary>
+ /// <param name="item">The <see cref="BaseItem"/>.</param>
+ /// <param name="typeFilter">filteres all media segments of the given type to be included. If null all types are included.</param>
+ /// <param name="filterByProvider">When set filteres the segments to only return those that which providers are currently enabled on their library.</param>
+ /// <returns>An enumerator of <see cref="MediaSegmentDto"/>'s.</returns>
+ Task<IEnumerable<MediaSegmentDto>> GetSegmentsAsync(BaseItem item, IEnumerable<MediaSegmentType>? typeFilter, bool filterByProvider = true);
/// <summary>
/// Gets information about any media segments stored for the given itemId.
diff --git a/MediaBrowser.Controller/Playlists/IPlaylistManager.cs b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs
index 038cbd2d6..497c4a511 100644
--- a/MediaBrowser.Controller/Playlists/IPlaylistManager.cs
+++ b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs
@@ -92,8 +92,9 @@ namespace MediaBrowser.Controller.Playlists
/// <param name="playlistId">The playlist identifier.</param>
/// <param name="entryId">The entry identifier.</param>
/// <param name="newIndex">The new index.</param>
+ /// <param name="callingUserId">The calling user.</param>
/// <returns>Task.</returns>
- Task MoveItemAsync(string playlistId, string entryId, int newIndex);
+ Task MoveItemAsync(string playlistId, string entryId, int newIndex, Guid callingUserId);
/// <summary>
/// Removed all playlists of a user.
diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs
index 38fc5f2cc..0d3a334df 100644
--- a/MediaBrowser.Controller/Providers/IProviderManager.cs
+++ b/MediaBrowser.Controller/Providers/IProviderManager.cs
@@ -77,7 +77,8 @@ namespace MediaBrowser.Controller.Providers
Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken);
/// <summary>
- /// Saves the image.
+ /// Saves the image by giving the image path on filesystem.
+ /// This method will remove the image on the source path after saving it to the destination.
/// </summary>
/// <param name="item">Image to save.</param>
/// <param name="source">Source of image.</param>
diff --git a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
index 05177ac39..8e3c8cf7f 100644
--- a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
+++ b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
@@ -11,7 +11,7 @@
</ItemGroup>
<PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
index b49fbf2ab..23d9ca7ef 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
@@ -150,7 +150,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
private static readonly Dictionary<int, string[]> _filterOptionsDict = new Dictionary<int, string[]>
{
- { 0, new string[] { "scale_cuda", "Output format (default \"same\")" } },
+ { 0, new string[] { "scale_cuda", "format" } },
{ 1, new string[] { "tonemap_cuda", "GPU accelerated HDR to SDR tonemapping" } },
{ 2, new string[] { "tonemap_opencl", "bt2390" } },
{ 3, new string[] { "overlay_opencl", "Action to take when encountering EOF from secondary input" } },
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 826ffd0b7..a34238cd6 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -1035,6 +1035,16 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (exitCode == -1)
{
_logger.LogError("ffmpeg image extraction failed for {ProcessDescription}", processDescription);
+ // Cleanup temp folder here, because the targetDirectory is not returned and the cleanup for failed ffmpeg process is not possible for caller.
+ // Ideally the ffmpeg should not write any files if it fails, but it seems like it is not guaranteed.
+ try
+ {
+ Directory.Delete(targetDirectory, true);
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e, "Failed to delete ffmpeg temp directory {TargetDirectory}", targetDirectory);
+ }
throw new FfmpegException(string.Format(CultureInfo.InvariantCulture, "ffmpeg image extraction failed for {0}", processDescription));
}
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index be63513a7..be7eeda92 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -6,7 +6,7 @@
</PropertyGroup>
<PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index 334796f58..c730f4cda 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -1648,7 +1648,7 @@ namespace MediaBrowser.MediaEncoding.Probing
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 1))
{
- fs.Read(packetBuffer);
+ fs.ReadExactly(packetBuffer);
}
if (packetBuffer[0] == 71)
diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs
index 6054ba34e..590b74304 100644
--- a/MediaBrowser.Model/Configuration/LibraryOptions.cs
+++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs
@@ -15,7 +15,7 @@ namespace MediaBrowser.Model.Configuration
TypeOptions = Array.Empty<TypeOptions>();
DisabledSubtitleFetchers = Array.Empty<string>();
DisabledMediaSegmentProviders = Array.Empty<string>();
- MediaSegmentProvideOrder = Array.Empty<string>();
+ MediaSegmentProviderOrder = Array.Empty<string>();
SubtitleFetcherOrder = Array.Empty<string>();
DisabledLocalMetadataReaders = Array.Empty<string>();
DisabledLyricFetchers = Array.Empty<string>();
@@ -99,7 +99,7 @@ namespace MediaBrowser.Model.Configuration
public string[] DisabledMediaSegmentProviders { get; set; }
- public string[] MediaSegmentProvideOrder { get; set; }
+ public string[] MediaSegmentProviderOrder { get; set; }
public bool SkipSubtitlesIfEmbeddedSubtitlesPresent { get; set; }
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index a25ddc367..767e01202 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -208,6 +208,14 @@ namespace MediaBrowser.Model.Dlna
var longBitrate = Math.Min(transcodingBitrate, playlistItem.AudioBitrate ?? transcodingBitrate);
playlistItem.AudioBitrate = longBitrate > int.MaxValue ? int.MaxValue : Convert.ToInt32(longBitrate);
+
+ // Pure audio transcoding does not support comma separated list of transcoding codec at the moment.
+ // So just use the AudioCodec as is would be safe enough as the _transcoderSupport.CanEncodeToAudioCodec
+ // would fail so this profile will not even be picked up.
+ if (playlistItem.AudioCodecs.Count == 0 && !string.IsNullOrWhiteSpace(transcodingProfile.AudioCodec))
+ {
+ playlistItem.AudioCodecs = [transcodingProfile.AudioCodec];
+ }
}
playlistItem.TranscodeReasons = transcodeReasons;
diff --git a/MediaBrowser.Model/Extensions/LibraryOptionsExtension.cs b/MediaBrowser.Model/Extensions/LibraryOptionsExtension.cs
index 4a814f22a..b088cfb53 100644
--- a/MediaBrowser.Model/Extensions/LibraryOptionsExtension.cs
+++ b/MediaBrowser.Model/Extensions/LibraryOptionsExtension.cs
@@ -18,7 +18,7 @@ public static class LibraryOptionsExtension
{
ArgumentNullException.ThrowIfNull(options);
- return options.CustomTagDelimiters.Select<string, char?>(x =>
+ var delimiterList = options.CustomTagDelimiters.Select<string, char?>(x =>
{
var isChar = char.TryParse(x, out var c);
if (isChar)
@@ -27,6 +27,8 @@ public static class LibraryOptionsExtension
}
return null;
- }).Where(x => x is not null).Select(x => x!.Value).ToArray();
+ }).Where(x => x is not null).Select(x => x!.Value).ToList();
+ delimiterList.Add('\0');
+ return delimiterList.ToArray();
}
}
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index 9489fe190..e9dab6bc8 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -8,13 +8,13 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Model</PackageId>
- <VersionPrefix>10.10.0</VersionPrefix>
+ <VersionPrefix>10.11.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
<PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
@@ -35,7 +35,7 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
-
+
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="MimeTypes">
diff --git a/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs b/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs
index ec54b1afd..98f7c6ce1 100644
--- a/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs
+++ b/MediaBrowser.Model/Playlists/PlaylistCreationRequest.cs
@@ -38,5 +38,5 @@ public class PlaylistCreationRequest
/// <summary>
/// Gets or sets a value indicating whether the playlist is public.
/// </summary>
- public bool? Public { get; set; } = true;
+ public bool? Public { get; set; } = false;
}
diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
index 36a7c2fab..64954818a 100644
--- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs
+++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
@@ -229,9 +229,7 @@ namespace MediaBrowser.Providers.Manager
{
var mimeType = MimeTypes.GetMimeType(response.Path);
- var stream = AsyncFile.OpenRead(response.Path);
-
- await _providerManager.SaveImage(item, stream, mimeType, imageType, null, cancellationToken).ConfigureAwait(false);
+ await _providerManager.SaveImage(item, response.Path, mimeType, imageType, null, null, cancellationToken).ConfigureAwait(false);
}
}
@@ -387,8 +385,8 @@ namespace MediaBrowser.Providers.Manager
item.RemoveImages(images);
- // Cleanup old metadata directory for episodes if empty
- if (item is Episode)
+ // Cleanup old metadata directory for episodes if empty, as long as it's not a virtual item
+ if (item is Episode && !item.IsVirtualItem)
{
var oldLocalMetadataDirectory = Path.Combine(item.ContainingFolderPath, "metadata");
if (_fileSystem.DirectoryExists(oldLocalMetadataDirectory) && !_fileSystem.GetFiles(oldLocalMetadataDirectory).Any())
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index 81a9af68b..e43da1350 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -7,6 +7,7 @@ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Mime;
+using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
using AsyncKeyedLock;
@@ -251,15 +252,29 @@ namespace MediaBrowser.Providers.Manager
}
/// <inheritdoc/>
- public Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool? saveLocallyWithMedia, CancellationToken cancellationToken)
+ public async Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool? saveLocallyWithMedia, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(source))
{
throw new ArgumentNullException(nameof(source));
}
- var fileStream = AsyncFile.OpenRead(source);
- return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken);
+ try
+ {
+ var fileStream = AsyncFile.OpenRead(source);
+ await new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ try
+ {
+ File.Delete(source);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Source file {Source} not found or in use, skip removing", source);
+ }
+ }
}
/// <inheritdoc/>
@@ -1016,7 +1031,6 @@ namespace MediaBrowser.Providers.Manager
/// <inheritdoc/>
public void QueueRefresh(Guid itemId, MetadataRefreshOptions options, RefreshPriority priority)
{
- ArgumentNullException.ThrowIfNull(itemId);
if (itemId.IsEmpty())
{
throw new ArgumentException("Guid can't be empty", nameof(itemId));
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index 9a65852f0..a3e0acf1b 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -28,7 +28,7 @@
</ItemGroup>
<PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
index 27f6d120f..7f1fdbcb8 100644
--- a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
+++ b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
@@ -347,7 +347,8 @@ namespace MediaBrowser.Providers.MediaInfo
|| track.AdditionalFields.TryGetValue("MusicBrainz Artist Id", out musicBrainzArtistTag))
&& !string.IsNullOrEmpty(musicBrainzArtistTag))
{
- audio.TrySetProviderId(MetadataProvider.MusicBrainzArtist, musicBrainzArtistTag);
+ var id = GetFirstMusicBrainzId(musicBrainzArtistTag, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist);
+ audio.TrySetProviderId(MetadataProvider.MusicBrainzArtist, id);
}
}
@@ -357,7 +358,8 @@ namespace MediaBrowser.Providers.MediaInfo
|| track.AdditionalFields.TryGetValue("MusicBrainz Album Artist Id", out musicBrainzReleaseArtistIdTag))
&& !string.IsNullOrEmpty(musicBrainzReleaseArtistIdTag))
{
- audio.TrySetProviderId(MetadataProvider.MusicBrainzAlbumArtist, musicBrainzReleaseArtistIdTag);
+ var id = GetFirstMusicBrainzId(musicBrainzReleaseArtistIdTag, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist);
+ audio.TrySetProviderId(MetadataProvider.MusicBrainzAlbumArtist, id);
}
}
@@ -367,7 +369,8 @@ namespace MediaBrowser.Providers.MediaInfo
|| track.AdditionalFields.TryGetValue("MusicBrainz Album Id", out musicBrainzReleaseIdTag))
&& !string.IsNullOrEmpty(musicBrainzReleaseIdTag))
{
- audio.TrySetProviderId(MetadataProvider.MusicBrainzAlbum, musicBrainzReleaseIdTag);
+ var id = GetFirstMusicBrainzId(musicBrainzReleaseIdTag, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist);
+ audio.TrySetProviderId(MetadataProvider.MusicBrainzAlbum, id);
}
}
@@ -377,7 +380,8 @@ namespace MediaBrowser.Providers.MediaInfo
|| track.AdditionalFields.TryGetValue("MusicBrainz Release Group Id", out musicBrainzReleaseGroupIdTag))
&& !string.IsNullOrEmpty(musicBrainzReleaseGroupIdTag))
{
- audio.TrySetProviderId(MetadataProvider.MusicBrainzReleaseGroup, musicBrainzReleaseGroupIdTag);
+ var id = GetFirstMusicBrainzId(musicBrainzReleaseGroupIdTag, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist);
+ audio.TrySetProviderId(MetadataProvider.MusicBrainzReleaseGroup, id);
}
}
@@ -387,7 +391,8 @@ namespace MediaBrowser.Providers.MediaInfo
|| track.AdditionalFields.TryGetValue("MusicBrainz Release Track Id", out trackMbId))
&& !string.IsNullOrEmpty(trackMbId))
{
- audio.TrySetProviderId(MetadataProvider.MusicBrainzTrack, trackMbId);
+ var id = GetFirstMusicBrainzId(trackMbId, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist);
+ audio.TrySetProviderId(MetadataProvider.MusicBrainzTrack, id);
}
}
@@ -441,5 +446,18 @@ namespace MediaBrowser.Providers.MediaInfo
return items;
}
+
+ // MusicBrainz IDs are multi-value tags, so we need to split them
+ // However, our current provider can only have one single ID, which means we need to pick the first one
+ private string? GetFirstMusicBrainzId(string tag, bool useCustomTagDelimiters, char[] tagDelimiters, string[] whitelist)
+ {
+ var val = tag.Split(InternalValueSeparator).FirstOrDefault();
+ if (val is not null && useCustomTagDelimiters)
+ {
+ val = SplitWithCustomDelimiter(val, tagDelimiters, whitelist).FirstOrDefault();
+ }
+
+ return val;
+ }
}
}
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
index 8d68e2dcf..eef08b251 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
@@ -198,7 +198,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
};
movie.SetProviderId(MetadataProvider.Tmdb, tmdbId);
- movie.SetProviderId(MetadataProvider.Imdb, movieResult.ImdbId);
+ movie.TrySetProviderId(MetadataProvider.Imdb, movieResult.ImdbId);
if (movieResult.BelongsToCollection is not null)
{
movie.SetProviderId(MetadataProvider.TmdbCollection, movieResult.BelongsToCollection.Id.ToString(CultureInfo.InvariantCulture));
diff --git a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj
index c20073eea..b195af96c 100644
--- a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj
+++ b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj
@@ -15,7 +15,7 @@
</ItemGroup>
<PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/README.md b/README.md
index 7da0cb30d..a07fd32ce 100644
--- a/README.md
+++ b/README.md
@@ -74,7 +74,7 @@ These instructions will help you get set up with a local development environment
### Prerequisites
-Before the project can be built, you must first install the [.NET 8.0 SDK](https://dotnet.microsoft.com/download/dotnet) on your system.
+Before the project can be built, you must first install the [.NET 9.0 SDK](https://dotnet.microsoft.com/download/dotnet) on your system.
Instructions to run this project from the command line are included here, but you will also need to install an IDE if you want to debug the server while it is running. Any IDE that supports .NET 6 development will work, but two options are recent versions of [Visual Studio](https://visualstudio.microsoft.com/downloads/) (at least 2022) and [Visual Studio Code](https://code.visualstudio.com/Download).
@@ -131,7 +131,7 @@ A second option is to build the project and then run the resulting executable fi
```bash
dotnet build # Build the project
-cd Jellyfin.Server/bin/Debug/net8.0 # Change into the build output directory
+cd Jellyfin.Server/bin/Debug/net9.0 # Change into the build output directory
```
2. Execute the build output. On Linux, Mac, etc. use `./jellyfin` and on Windows use `jellyfin.exe`.
diff --git a/SharedVersion.cs b/SharedVersion.cs
index f98cfbc74..d26eb31ae 100644
--- a/SharedVersion.cs
+++ b/SharedVersion.cs
@@ -1,4 +1,4 @@
using System.Reflection;
-[assembly: AssemblyVersion("10.10.0")]
-[assembly: AssemblyFileVersion("10.10.0")]
+[assembly: AssemblyVersion("10.11.0")]
+[assembly: AssemblyFileVersion("10.11.0")]
diff --git a/bump_version b/bump_version
index 72bbbfbf5..6d08dc72f 100755
--- a/bump_version
+++ b/bump_version
@@ -28,6 +28,7 @@ jellyfin_subprojects=(
Emby.Naming/Emby.Naming.csproj
src/Jellyfin.Extensions/Jellyfin.Extensions.csproj
)
+issue_template_file="./.github/ISSUE_TEMPLATE/issue report.yml"
new_version="$1"
new_version_sed="$( cut -f1 -d'-' <<<"${new_version}" )"
@@ -56,6 +57,9 @@ for subproject in ${jellyfin_subprojects[@]}; do
sed -i "s|${old_version}|${new_version_sed}|g" ${subproject}
done
+# Set the version in the GitHub issue template file
+sed -i "s|${old_version}|${new_version_sed}|g" ${issue_template_file}
+
# Stage the changed files for commit
git add .
git status -v
diff --git a/fuzz/Emby.Server.Implementations.Fuzz/Emby.Server.Implementations.Fuzz.csproj b/fuzz/Emby.Server.Implementations.Fuzz/Emby.Server.Implementations.Fuzz.csproj
index 73aae3f3d..1373d2fe0 100644
--- a/fuzz/Emby.Server.Implementations.Fuzz/Emby.Server.Implementations.Fuzz.csproj
+++ b/fuzz/Emby.Server.Implementations.Fuzz/Emby.Server.Implementations.Fuzz.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
diff --git a/fuzz/Emby.Server.Implementations.Fuzz/fuzz.sh b/fuzz/Emby.Server.Implementations.Fuzz/fuzz.sh
index 80a5cd7c1..8183bb37a 100755
--- a/fuzz/Emby.Server.Implementations.Fuzz/fuzz.sh
+++ b/fuzz/Emby.Server.Implementations.Fuzz/fuzz.sh
@@ -8,4 +8,4 @@ cp bin/Emby.Server.Implementations.dll .
dotnet build
mkdir -p Findings
-AFL_SKIP_BIN_CHECK=1 afl-fuzz -i "Testcases/$1" -o "Findings/$1" -t 5000 ./bin/Debug/net8.0/Emby.Server.Implementations.Fuzz "$1"
+AFL_SKIP_BIN_CHECK=1 afl-fuzz -i "Testcases/$1" -o "Findings/$1" -t 5000 ./bin/Debug/net9.0/Emby.Server.Implementations.Fuzz "$1"
diff --git a/fuzz/Jellyfin.Api.Fuzz/Jellyfin.Api.Fuzz.csproj b/fuzz/Jellyfin.Api.Fuzz/Jellyfin.Api.Fuzz.csproj
index faac7d976..04c7be11d 100644
--- a/fuzz/Jellyfin.Api.Fuzz/Jellyfin.Api.Fuzz.csproj
+++ b/fuzz/Jellyfin.Api.Fuzz/Jellyfin.Api.Fuzz.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
diff --git a/fuzz/Jellyfin.Api.Fuzz/fuzz.sh b/fuzz/Jellyfin.Api.Fuzz/fuzz.sh
index 96b0192cf..15148e1bb 100755
--- a/fuzz/Jellyfin.Api.Fuzz/fuzz.sh
+++ b/fuzz/Jellyfin.Api.Fuzz/fuzz.sh
@@ -8,4 +8,4 @@ cp bin/Jellyfin.Api.dll .
dotnet build
mkdir -p Findings
-AFL_SKIP_BIN_CHECK=1 afl-fuzz -i "Testcases/$1" -o "Findings/$1" -t 5000 ./bin/Debug/net8.0/Jellyfin.Api.Fuzz "$1"
+AFL_SKIP_BIN_CHECK=1 afl-fuzz -i "Testcases/$1" -o "Findings/$1" -t 5000 ./bin/Debug/net9.0/Jellyfin.Api.Fuzz "$1"
diff --git a/global.json b/global.json
index dbf2988d5..2e13a6387 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "8.0.0",
+ "version": "9.0.0",
"rollForward": "latestMinor"
}
}
diff --git a/jellyfin.ruleset b/jellyfin.ruleset
index 67ffd9a37..ba04a70c2 100644
--- a/jellyfin.ruleset
+++ b/jellyfin.ruleset
@@ -154,6 +154,8 @@
<!-- TODO: enable when false positives are fixed -->
<!-- disable warning CA1508: Avoid dead conditional code -->
<Rule Id="CA1508" Action="Info" />
+ <!-- disable warning CA1515: Consider making public types internal -->
+ <Rule Id="CA1515" Action="Info" />
<!-- disable warning CA1716: Identifiers should not match keywords -->
<Rule Id="CA1716" Action="Info" />
<!-- disable warning CA1720: Identifiers should not contain type names -->
@@ -168,6 +170,8 @@
<Rule Id="CA1812" Action="Info" />
<!-- disable warning CA1822: Member does not access instance data and can be marked as static -->
<Rule Id="CA1822" Action="Info" />
+ <!-- CA1859: Use concrete types when possible for improved performance -->
+ <Rule Id="CA1859" Action="Info" />
<!-- TODO: Enable -->
<!-- CA1861: Prefer 'static readonly' fields over constant array arguments if the called method is called repeatedly and is not mutating the passed array -->
<Rule Id="CA1861" Action="Info" />
diff --git a/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
index 0590ded32..ba402dfe0 100644
--- a/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
+++ b/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
@@ -6,7 +6,7 @@
</PropertyGroup>
<PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<!-- TODO: Remove once we update SkiaSharp > 2.88.5 -->
diff --git a/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj b/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj
index 4a02f90f9..5f4b3fe8d 100644
--- a/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj
+++ b/src/Jellyfin.Drawing/Jellyfin.Drawing.csproj
@@ -6,7 +6,7 @@
</PropertyGroup>
<PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj
index f786cc3b4..1613d83bc 100644
--- a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj
+++ b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
@@ -15,7 +15,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Extensions</PackageId>
- <VersionPrefix>10.10.0</VersionPrefix>
+ <VersionPrefix>10.11.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs
index 936a5a97c..c53ef275b 100644
--- a/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs
+++ b/src/Jellyfin.Extensions/Json/Converters/JsonDelimitedArrayConverter.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
-using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
@@ -71,24 +70,11 @@ namespace Jellyfin.Extensions.Json.Converters
writer.WriteStartArray();
if (value.Length > 0)
{
- var toWrite = value.Length - 1;
foreach (var it in value)
{
- var wrote = false;
if (it is not null)
{
writer.WriteStringValue(it.ToString());
- wrote = true;
- }
-
- if (toWrite > 0)
- {
- if (wrote)
- {
- writer.WriteStringValue(Delimiter.ToString());
- }
-
- toWrite--;
}
}
}
diff --git a/src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj b/src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj
index c58889740..f04c02504 100644
--- a/src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj
+++ b/src/Jellyfin.LiveTv/Jellyfin.LiveTv.csproj
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj b/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj
index ee79802a1..dc581724a 100644
--- a/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj
+++ b/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj b/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj
index c79dcee3c..c826d3d9c 100644
--- a/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj
+++ b/src/Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/src/Jellyfin.Networking/Jellyfin.Networking.csproj b/src/Jellyfin.Networking/Jellyfin.Networking.csproj
index 24b3ecaab..472cdb7ef 100644
--- a/src/Jellyfin.Networking/Jellyfin.Networking.csproj
+++ b/src/Jellyfin.Networking/Jellyfin.Networking.csproj
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/src/Jellyfin.Networking/Manager/NetworkManager.cs b/src/Jellyfin.Networking/Manager/NetworkManager.cs
index 5a13cc417..10aed673b 100644
--- a/src/Jellyfin.Networking/Manager/NetworkManager.cs
+++ b/src/Jellyfin.Networking/Manager/NetworkManager.cs
@@ -57,7 +57,7 @@ public class NetworkManager : INetworkManager, IDisposable
/// <summary>
/// Dictionary containing interface addresses and their subnets.
/// </summary>
- private IReadOnlyList<IPData> _interfaces;
+ private List<IPData> _interfaces;
/// <summary>
/// Unfiltered user defined LAN subnets (<see cref="NetworkConfiguration.LocalNetworkSubnets"/>)
diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props
index bec3481cb..146ad8dc2 100644
--- a/tests/Directory.Build.props
+++ b/tests/Directory.Build.props
@@ -4,7 +4,7 @@
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
<PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
<IsPackable>false</IsPackable>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)/jellyfin-tests.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
diff --git a/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyArrayModel.cs b/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyArrayModel.cs
index ef135278f..76669ea19 100644
--- a/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyArrayModel.cs
+++ b/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyArrayModel.cs
@@ -8,7 +8,7 @@ namespace Jellyfin.Extensions.Tests.Json.Models
/// The generic body model.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
- public class GenericBodyArrayModel<T>
+ public sealed class GenericBodyArrayModel<T>
{
/// <summary>
/// Gets or sets the value.
diff --git a/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyIReadOnlyListModel.cs b/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyIReadOnlyListModel.cs
index 8e7b5a35b..7e6b97afe 100644
--- a/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyIReadOnlyListModel.cs
+++ b/tests/Jellyfin.Extensions.Tests/Json/Models/GenericBodyIReadOnlyListModel.cs
@@ -8,7 +8,7 @@ namespace Jellyfin.Extensions.Tests.Json.Models
/// The generic body <c>IReadOnlyList</c> model.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
- public class GenericBodyIReadOnlyListModel<T>
+ public sealed class GenericBodyIReadOnlyListModel<T>
{
/// <summary>
/// Gets or sets the value.
diff --git a/tests/Jellyfin.LiveTv.Tests/Jellyfin.LiveTv.Tests.csproj b/tests/Jellyfin.LiveTv.Tests/Jellyfin.LiveTv.Tests.csproj
index cf967b84c..fdcf7d61e 100644
--- a/tests/Jellyfin.LiveTv.Tests/Jellyfin.LiveTv.Tests.csproj
+++ b/tests/Jellyfin.LiveTv.Tests/Jellyfin.LiveTv.Tests.csproj
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>net8.0</TargetFramework>
+ <TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
@@ -22,7 +22,7 @@
<PackageReference Include="Xunit.SkippableFact" />
<PackageReference Include="coverlet.collector" />
</ItemGroup>
-
+
<ItemGroup>
<ProjectReference Include="..\..\src\Jellyfin.LiveTv\Jellyfin.LiveTv.csproj" />
</ItemGroup>
diff --git a/tests/Jellyfin.LiveTv.Tests/Listings/ListingsManagerTests.cs b/tests/Jellyfin.LiveTv.Tests/Listings/ListingsManagerTests.cs
new file mode 100644
index 000000000..40934d9c6
--- /dev/null
+++ b/tests/Jellyfin.LiveTv.Tests/Listings/ListingsManagerTests.cs
@@ -0,0 +1,50 @@
+using System;
+using Jellyfin.LiveTv.Configuration;
+using Jellyfin.LiveTv.Listings;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.LiveTv;
+using MediaBrowser.Model.Tasks;
+using Microsoft.Extensions.Logging;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.LiveTv.Tests.Listings;
+
+public class ListingsManagerTests
+{
+ private readonly IConfigurationManager _config;
+ private readonly IListingsProvider[] _listingsProviders;
+ private readonly ILogger<ListingsManager> _logger;
+ private readonly ITaskManager _taskManager;
+ private readonly ITunerHostManager _tunerHostManager;
+
+ public ListingsManagerTests()
+ {
+ _logger = Mock.Of<ILogger<ListingsManager>>();
+ _config = Mock.Of<IConfigurationManager>();
+ _taskManager = Mock.Of<ITaskManager>();
+ _tunerHostManager = Mock.Of<ITunerHostManager>();
+ _listingsProviders = new[] { Mock.Of<IListingsProvider>() };
+ }
+
+ [Fact]
+ public void DeleteListingsProvider_DeletesProvider()
+ {
+ // Arrange
+ var id = "MockId";
+ var manager = new ListingsManager(_logger, _config, _taskManager, _tunerHostManager, _listingsProviders);
+
+ Mock.Get(_config)
+ .Setup(x => x.GetConfiguration(It.IsAny<string>()))
+ .Returns(new LiveTvOptions { ListingProviders = [new ListingsProviderInfo { Id = id }] });
+
+ // Act
+ manager.DeleteListingsProvider(id);
+
+ // Assert
+ Assert.DoesNotContain(
+ _config.GetLiveTvConfiguration().ListingProviders,
+ p => p.Id.Equals(id, StringComparison.Ordinal));
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs
index 406381f14..7bfab570b 100644
--- a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs
@@ -77,6 +77,8 @@ namespace Jellyfin.Naming.Tests.TV
[InlineData("Season 3/The Series S3 E9 - The title.avi", 9)]
[InlineData("Season 3/S003 E009.avi", 9)]
[InlineData("Season 3/Season 3 Episode 9.avi", 9)]
+ [InlineData("[VCB-Studio] Re Zero kara Hajimeru Isekai Seikatsu [21][Ma10p_1080p][x265_flac].mkv", 21)]
+ [InlineData("[CASO&Sumisora][Oda_Nobuna_no_Yabou][04][BDRIP][1920x1080][x264_AAC][7620E503].mp4", 4)]
// [InlineData("Case Closed (1996-2007)/Case Closed - 317.mkv", 317)] // triple digit episode number
// TODO: [InlineData("Season 2/16 12 Some Title.avi", 16)]
diff --git a/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs
index 0c7d2487c..0d99e9af0 100644
--- a/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs
+++ b/tests/Jellyfin.Providers.Tests/Manager/ItemImageProviderTests.cs
@@ -292,6 +292,9 @@ namespace Jellyfin.Providers.Tests.Manager
providerManager.Setup(pm => pm.SaveImage(item, It.IsAny<Stream>(), It.IsAny<string>(), imageType, null, It.IsAny<CancellationToken>()))
.Callback<BaseItem, Stream, string, ImageType, int?, CancellationToken>((callbackItem, _, _, callbackType, _, _) => callbackItem.SetImagePath(callbackType, 0, new FileSystemMetadata()))
.Returns(Task.CompletedTask);
+ providerManager.Setup(pm => pm.SaveImage(item, It.IsAny<string>(), It.IsAny<string>(), imageType, null, null, It.IsAny<CancellationToken>()))
+ .Callback<BaseItem, string, string, ImageType, int?, bool?, CancellationToken>((callbackItem, _, _, callbackType, _, _, _) => callbackItem.SetImagePath(callbackType, 0, new FileSystemMetadata()))
+ .Returns(Task.CompletedTask);
var itemImageProvider = GetItemImageProvider(providerManager.Object, null);
var result = await itemImageProvider.RefreshImages(item, libraryOptions, new List<IImageProvider> { dynamicProvider.Object }, refreshOptions, CancellationToken.None);
diff --git a/tests/Jellyfin.Server.Implementations.Tests/TypedBaseItem/BaseItemKindTests.cs b/tests/Jellyfin.Server.Implementations.Tests/TypedBaseItem/BaseItemKindTests.cs
index 1bd51b246..9a4389e7a 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/TypedBaseItem/BaseItemKindTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/TypedBaseItem/BaseItemKindTests.cs
@@ -35,7 +35,7 @@ namespace Jellyfin.Server.Implementations.Tests.TypedBaseItem
public void EnumParse_GivenValidBaseItemType_ReturnsEnumValue(Type baseItemDescendantType)
{
var enumValue = Enum.Parse<BaseItemKind>(baseItemDescendantType.Name);
- Assert.True(Enum.IsDefined(typeof(BaseItemKind), enumValue));
+ Assert.True(Enum.IsDefined(enumValue));
}
[Theory]
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Users/UserManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Users/UserManagerTests.cs
index 295f558fa..665afe111 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Users/UserManagerTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Users/UserManagerTests.cs
@@ -10,6 +10,9 @@ namespace Jellyfin.Server.Implementations.Tests.Users
[InlineData("this_is_valid")]
[InlineData("this is also valid")]
[InlineData("0@_-' .")]
+ [InlineData("Aa0@_-' .+")]
+ [InlineData("thisisa+testemail@test.foo")]
+ [InlineData("------@@@--+++----@@--abcdefghijklmn---------@----_-_-___-_ .9foo+")]
public void ThrowIfInvalidUsername_WhenValidUsername_DoesNotThrowArgumentException(string username)
{
var ex = Record.Exception(() => UserManager.ThrowIfInvalidUsername(username));