aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.config/dotnet-tools.json2
-rw-r--r--.github/workflows/ci-codeql-analysis.yml10
-rw-r--r--.github/workflows/ci-compat.yml4
-rw-r--r--.github/workflows/ci-tests.yml2
-rw-r--r--.github/workflows/commands.yml2
-rw-r--r--.github/workflows/openapi-generate.yml2
-rw-r--r--.github/workflows/openapi-pull-request.yml2
-rw-r--r--CONTRIBUTORS.md33
-rw-r--r--Directory.Packages.props55
-rw-r--r--Emby.Server.Implementations/IO/LibraryMonitor.cs1
-rw-r--r--Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs11
-rw-r--r--Emby.Server.Implementations/Localization/Core/bs.json141
-rw-r--r--Emby.Server.Implementations/Localization/Core/ca.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/fi.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/hr.json24
-rw-r--r--Emby.Server.Implementations/Localization/Core/ht.json5
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-HK.json2
-rw-r--r--Emby.Server.Implementations/Localization/LocalizationManager.cs2
-rw-r--r--Emby.Server.Implementations/Localization/countries.json6
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs4
-rw-r--r--Jellyfin.Api/Controllers/ActivityLogController.cs1
-rw-r--r--Jellyfin.Api/Controllers/ApiKeyController.cs1
-rw-r--r--Jellyfin.Api/Controllers/ArtistsController.cs1
-rw-r--r--Jellyfin.Api/Controllers/ChannelsController.cs1
-rw-r--r--Jellyfin.Api/Controllers/ClientLogController.cs1
-rw-r--r--Jellyfin.Api/Controllers/ConfigurationController.cs1
-rw-r--r--Jellyfin.Api/Controllers/DevicesController.cs1
-rw-r--r--Jellyfin.Api/Controllers/DisplayPreferencesController.cs1
-rw-r--r--Jellyfin.Api/Controllers/GenresController.cs1
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs1
-rw-r--r--Jellyfin.Api/Controllers/LibraryStructureController.cs4
-rw-r--r--Jellyfin.Api/Controllers/LyricsController.cs2
-rw-r--r--Jellyfin.Api/Controllers/MediaSegmentsController.cs1
-rw-r--r--Jellyfin.Api/Controllers/MoviesController.cs3
-rw-r--r--Jellyfin.Api/Controllers/MusicGenresController.cs1
-rw-r--r--Jellyfin.Api/Controllers/PackageController.cs2
-rw-r--r--Jellyfin.Api/Controllers/PersonsController.cs10
-rw-r--r--Jellyfin.Api/Controllers/PlaylistsController.cs1
-rw-r--r--Jellyfin.Api/Controllers/PlaystateController.cs2
-rw-r--r--Jellyfin.Api/Controllers/PluginsController.cs2
-rw-r--r--Jellyfin.Api/Controllers/QuickConnectController.cs1
-rw-r--r--Jellyfin.Api/Controllers/ScheduledTasksController.cs2
-rw-r--r--Jellyfin.Api/Controllers/StudiosController.cs1
-rw-r--r--Jellyfin.Api/Controllers/SuggestionsController.cs1
-rw-r--r--Jellyfin.Api/Controllers/TimeSyncController.cs1
-rw-r--r--Jellyfin.Api/Controllers/TrailersController.cs1
-rw-r--r--Jellyfin.Api/Controllers/TrickplayController.cs2
-rw-r--r--Jellyfin.Api/Controllers/TvShowsController.cs1
-rw-r--r--Jellyfin.Api/Controllers/UniversalAudioController.cs1
-rw-r--r--Jellyfin.Api/Controllers/UserViewsController.cs1
-rw-r--r--Jellyfin.Api/Controllers/VideoAttachmentsController.cs1
-rw-r--r--Jellyfin.Api/Controllers/VideosController.cs1
-rw-r--r--Jellyfin.Api/Controllers/YearsController.cs1
-rw-r--r--Jellyfin.Server.Implementations/Item/PeopleRepository.cs15
-rw-r--r--Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs2
-rw-r--r--Jellyfin.Server/Program.cs3
-rw-r--r--Jellyfin.Server/ServerSetupApp/SetupServer.cs1
-rw-r--r--MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs21
-rw-r--r--MediaBrowser.Controller/Entities/InternalPeopleQuery.cs6
-rw-r--r--MediaBrowser.Controller/LiveTv/ProgramInfo.cs44
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs3
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs6
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs10
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs2
-rw-r--r--MediaBrowser.Model/Entities/MediaStream.cs1
-rw-r--r--MediaBrowser.Model/Net/MimeTypes.cs1
-rw-r--r--MediaBrowser.Model/Providers/SubtitleOptions.cs36
-rw-r--r--MediaBrowser.Providers/Manager/ItemImageProvider.cs4
-rw-r--r--MediaBrowser.Providers/Manager/MetadataService.cs6
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs7
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs43
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs56
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs6
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs6
-rw-r--r--src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/SqliteDatabaseProvider.cs2
-rw-r--r--src/Jellyfin.LiveTv/Listings/XmlTvListingsProvider.cs42
-rw-r--r--tests/Jellyfin.Api.Tests/Controllers/UserControllerTests.cs2
-rw-r--r--tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj5
-rw-r--r--tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj5
-rw-r--r--tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj3
-rw-r--r--tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj8
-rw-r--r--tests/Jellyfin.LiveTv.Tests/Jellyfin.LiveTv.Tests.csproj4
-rw-r--r--tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj6
-rw-r--r--tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj6
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj5
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Subtitles/FilterEventsTests.cs282
-rw-r--r--tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs55
-rw-r--r--tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj8
-rw-r--r--tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj3
-rw-r--r--tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj5
-rw-r--r--tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj8
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs16
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj4
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs23
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Plugins/PluginManagerTests.cs14
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs9
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs2
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs6
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs16
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs8
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryControllerTests.cs6
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs24
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/LiveTvControllerTests.cs10
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/MediaInfoControllerTests.cs6
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs14
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/MusicGenreControllerTests.cs2
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/PersonsControllerTests.cs2
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/PlaystateControllerTests.cs8
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/PluginsControllerTests.cs6
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs24
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs16
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/UserLibraryControllerTests.cs18
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/VideosControllerTests.cs2
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs8
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj10
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Middleware/RobotsRedirectionMiddlewareTests.cs2
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs5
-rw-r--r--tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj8
-rw-r--r--tests/Jellyfin.Server.Tests/ParseNetworkTests.cs16
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj6
120 files changed, 968 insertions, 420 deletions
diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 9cd9c08e75..9b44eff4c6 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"dotnet-ef": {
- "version": "10.0.5",
+ "version": "10.0.7",
"commands": [
"dotnet-ef"
]
diff --git a/.github/workflows/ci-codeql-analysis.yml b/.github/workflows/ci-codeql-analysis.yml
index 5194c7df06..442114dd80 100644
--- a/.github/workflows/ci-codeql-analysis.yml
+++ b/.github/workflows/ci-codeql-analysis.yml
@@ -8,6 +8,10 @@ on:
schedule:
- cron: '24 2 * * 4'
+permissions:
+ contents: read
+ security-events: write
+
jobs:
analyze:
name: Analyze
@@ -28,13 +32,13 @@ jobs:
dotnet-version: '10.0.x'
- name: Initialize CodeQL
- uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
+ uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
with:
languages: ${{ matrix.language }}
queries: +security-extended
- name: Autobuild
- uses: github/codeql-action/autobuild@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
+ uses: github/codeql-action/autobuild@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
+ uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
diff --git a/.github/workflows/ci-compat.yml b/.github/workflows/ci-compat.yml
index f9e2fbc3a6..dd48209a1f 100644
--- a/.github/workflows/ci-compat.yml
+++ b/.github/workflows/ci-compat.yml
@@ -26,7 +26,7 @@ jobs:
dotnet build Jellyfin.Server -o ./out
- name: Upload Head
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: abi-head
retention-days: 14
@@ -65,7 +65,7 @@ jobs:
dotnet build Jellyfin.Server -o ./out
- name: Upload Head
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: abi-base
retention-days: 14
diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml
index fc32cc884d..f0ecb166b4 100644
--- a/.github/workflows/ci-tests.yml
+++ b/.github/workflows/ci-tests.yml
@@ -35,7 +35,7 @@ jobs:
--verbosity minimal
- name: Merge code coverage results
- uses: danielpalme/ReportGenerator-GitHub-Action@cf6fe1b38ed5becc89ffe056c1f240825993be5b # v5.5.4
+ uses: danielpalme/ReportGenerator-GitHub-Action@a003c8fb9ac008fd0fffd5faa4f7d3ecb52e0675 # v5.5.7
with:
reports: "**/coverage.cobertura.xml"
targetdir: "merged/"
diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml
index 2adb8f1010..9d3d99cb71 100644
--- a/.github/workflows/commands.yml
+++ b/.github/workflows/commands.yml
@@ -36,7 +36,7 @@ jobs:
rename:
name: Rename
- if: contains(github.event.comment.body, '@jellyfin-bot rename') && github.event.comment.author_association == 'MEMBER'
+ if: contains(github.event.comment.body, '@jellyfin-bot rename')
runs-on: ubuntu-latest
steps:
- name: pull in script
diff --git a/.github/workflows/openapi-generate.yml b/.github/workflows/openapi-generate.yml
index 255cc49e82..dbfaf9d30b 100644
--- a/.github/workflows/openapi-generate.yml
+++ b/.github/workflows/openapi-generate.yml
@@ -36,7 +36,7 @@ jobs:
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter Jellyfin.Server.Integration.Tests.OpenApiSpecTests
- name: Upload Artifact
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ inputs.artifact }}
path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net10.0/openapi.json
diff --git a/.github/workflows/openapi-pull-request.yml b/.github/workflows/openapi-pull-request.yml
index 563a0a406f..4acd0f4d4f 100644
--- a/.github/workflows/openapi-pull-request.yml
+++ b/.github/workflows/openapi-pull-request.yml
@@ -74,7 +74,7 @@ jobs:
docker run -v /tmp/openapi-report:/data openapitools/openapi-diff:2.1.6 /data/base.json /data/head.json --state -l ERROR --markdown /data/openapi-report.md
- name: Upload Artifact
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: openapi-report
path: /tmp/openapi-report/openapi-report.md
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 4b0cec3c92..c42962786d 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -1,5 +1,6 @@
# Jellyfin Contributors
+ - [0x25CBFC4F](https://github.com/0x25CBFC4F)
- [1337joe](https://github.com/1337joe)
- [97carmine](https://github.com/97carmine)
- [Abbe98](https://github.com/Abbe98)
@@ -14,7 +15,7 @@
- [bilde2910](https://github.com/bilde2910)
- [bfayers](https://github.com/bfayers)
- [BnMcG](https://github.com/BnMcG)
- - [Bond-009](https://github.com/Bond-009)
+ - [Bond_009](https://github.com/Bond-009)
- [brianjmurrell](https://github.com/brianjmurrell)
- [bugfixin](https://github.com/bugfixin)
- [chaosinnovator](https://github.com/chaosinnovator)
@@ -31,6 +32,7 @@
- [DaveChild](https://github.com/DaveChild)
- [DavidFair](https://github.com/DavidFair)
- [Delgan](https://github.com/Delgan)
+ - [DerMaddis](https://github.com/dermaddis)
- [Derpipose](https://github.com/Derpipose)
- [dcrdev](https://github.com/dcrdev)
- [dhartung](https://github.com/dhartung)
@@ -54,6 +56,7 @@
- [geilername](https://github.com/geilername)
- [GermanCoding](https://github.com/GermanCoding)
- [gnattu](https://github.com/gnattu)
+ - [gnuyent](https://github.com/gnuyent)
- [GodTamIt](https://github.com/GodTamIt)
- [grafixeyehero](https://github.com/grafixeyehero)
- [h1nk](https://github.com/h1nk)
@@ -61,6 +64,7 @@
- [HelloWorld017](https://github.com/HelloWorld017)
- [ikomhoog](https://github.com/ikomhoog)
- [iwalton3](https://github.com/iwalton3)
+ - [Jakob Kukla](https://github.com/jakobkukla)
- [jftuga](https://github.com/jftuga)
- [jkhsjdhjs](https://github.com/jkhsjdhjs)
- [jmshrv](https://github.com/jmshrv)
@@ -69,8 +73,10 @@
- [JustAMan](https://github.com/JustAMan)
- [justinfenn](https://github.com/justinfenn)
- [JPVenson](https://github.com/JPVenson)
+ - [JPUC1143](https://github.com/Jpuc1143/)
- [KerryRJ](https://github.com/KerryRJ)
- [Larvitar](https://github.com/Larvitar)
+ - [lbenini](https://github.com/lbenini)
- [LeoVerto](https://github.com/LeoVerto)
- [Liggy](https://github.com/Liggy)
- [lmaonator](https://github.com/lmaonator)
@@ -83,15 +89,19 @@
- [marius-luca-87](https://github.com/marius-luca-87)
- [mark-monteiro](https://github.com/mark-monteiro)
- [MarkCiliaVincenti](https://github.com/MarkCiliaVincenti)
+ - [Martin Reuter](https://github.com/reuterma24)
- [Matt07211](https://github.com/Matt07211)
+ - [Matthew Jones](https://github.com/matthew-jones-uk)
- [Maxr1998](https://github.com/Maxr1998)
- [mcarlton00](https://github.com/mcarlton00)
+ - [Michael McElroy](https://github.com/mcmcelro)
- [mitchfizz05](https://github.com/mitchfizz05)
- [mohd-akram](https://github.com/mohd-akram)
- [MrTimscampi](https://github.com/MrTimscampi)
- [n8225](https://github.com/n8225)
- [Nalsai](https://github.com/Nalsai)
- [Narfinger](https://github.com/Narfinger)
+ - [Nathan McCrina](https://github.com/nfmccrina)
- [NathanPickard](https://github.com/NathanPickard)
- [neilsb](https://github.com/neilsb)
- [nevado](https://github.com/nevado)
@@ -102,6 +112,7 @@
- [OancaAndrei](https://github.com/OancaAndrei)
- [obradovichv](https://github.com/obradovichv)
- [oddstr13](https://github.com/oddstr13)
+ - [olsh](https://github.com/olsh)
- [orryverducci](https://github.com/orryverducci)
- [petermcneil](https://github.com/petermcneil)
- [Phlogi](https://github.com/Phlogi)
@@ -112,6 +123,7 @@
- [RazeLighter777](https://github.com/RazeLighter777)
- [redSpoutnik](https://github.com/redSpoutnik)
- [ringmatter](https://github.com/ringmatter)
+ - [Robert Lützner](https://github.com/rluetzner)
- [ryan-hartzell](https://github.com/ryan-hartzell)
- [s0urcelab](https://github.com/s0urcelab)
- [sachk](https://github.com/sachk)
@@ -127,6 +139,7 @@
- [sl1288](https://github.com/sl1288)
- [Smith00101010](https://github.com/Smith00101010)
- [sorinyo2004](https://github.com/sorinyo2004)
+ - [Soumyadip Auddy](https://github.com/SoumyadipAuddy)
- [sparky8251](https://github.com/sparky8251)
- [spookbits](https://github.com/spookbits)
- [ssenart](https://github.com/ssenart)
@@ -149,6 +162,7 @@
- [twinkybot](https://github.com/twinkybot)
- [Ullmie02](https://github.com/Ullmie02)
- [Unhelpful](https://github.com/Unhelpful)
+ - [Utku Özdemir](https://github.com/utkuozdemir)
- [viaregio](https://github.com/viaregio)
- [vitorsemeano](https://github.com/vitorsemeano)
- [voodoos](https://github.com/voodoos)
@@ -212,6 +226,9 @@
- [martenumberto](https://github.com/martenumberto)
- [ZeusCraft10](https://github.com/ZeusCraft10)
- [MarcoCoreDuo](https://github.com/MarcoCoreDuo)
+ - [LiHRaM](https://github.com/LiHRaM)
+ - [MSalman5230](https://github.com/MSalman5230)
+ - [dwandw](https://github.com/dwandw)
# Emby Contributors
@@ -275,17 +292,3 @@
- [tikuf](https://github.com/tikuf/)
- [Tim Hobbs](https://github.com/timhobbs)
- [SvenVandenbrande](https://github.com/SvenVandenbrande)
- - [olsh](https://github.com/olsh)
- - [lbenini](https://github.com/lbenini)
- - [gnuyent](https://github.com/gnuyent)
- - [Matthew Jones](https://github.com/matthew-jones-uk)
- - [Jakob Kukla](https://github.com/jakobkukla)
- - [Utku Özdemir](https://github.com/utkuozdemir)
- - [JPUC1143](https://github.com/Jpuc1143/)
- - [0x25CBFC4F](https://github.com/0x25CBFC4F)
- - [Robert Lützner](https://github.com/rluetzner)
- - [Nathan McCrina](https://github.com/nfmccrina)
- - [Martin Reuter](https://github.com/reuterma24)
- - [Michael McElroy](https://github.com/mcmcelro)
- - [Soumyadip Auddy](https://github.com/SoumyadipAuddy)
- - [DerMaddis](https://github.com/dermaddis)
diff --git a/Directory.Packages.props b/Directory.Packages.props
index f15f7c7a75..9b073e67a5 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -6,48 +6,48 @@
<ItemGroup Label="Package Dependencies">
<PackageVersion Include="AsyncKeyedLock" Version="8.0.2" />
<PackageVersion Include="AutoFixture.AutoMoq" Version="4.18.1" />
- <PackageVersion Include="AutoFixture.Xunit2" Version="4.18.1" />
+ <PackageVersion Include="AutoFixture.Xunit3" Version="4.19.0" />
<PackageVersion Include="AutoFixture" Version="4.18.1" />
<PackageVersion Include="BDInfo" Version="0.8.0" />
<PackageVersion Include="BitFaster.Caching" Version="2.5.4" />
<PackageVersion Include="BlurHashSharp.SkiaSharp" Version="1.4.0-pre.1" />
<PackageVersion Include="BlurHashSharp" Version="1.4.0-pre.1" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
- <PackageVersion Include="coverlet.collector" Version="8.0.1" />
+ <PackageVersion Include="coverlet.collector" Version="10.0.0" />
<PackageVersion Include="Diacritics" Version="4.1.4" />
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
- <PackageVersion Include="FsCheck.Xunit" Version="3.3.2" />
+ <PackageVersion Include="FsCheck.Xunit.v3" Version="3.3.3" />
<PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="8.3.1.1" />
<PackageVersion Include="ICU4N.Transliterator" Version="60.1.0-alpha.356" />
<PackageVersion Include="IDisposableAnalyzers" Version="4.0.8" />
<PackageVersion Include="Ignore" Version="0.2.1" />
- <PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
+ <PackageVersion Include="Jellyfin.XmlTv" Version="10.12.0-pre1" />
<PackageVersion Include="libse" Version="4.0.12" />
<PackageVersion Include="LrcParser" Version="2025.623.0" />
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="8.0.1" />
- <PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="10.0.5" />
- <PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.5" />
+ <PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="10.0.7" />
+ <PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.7" />
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="4.14.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="5.3.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.3.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="5.3.0" />
- <PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.5" />
- <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.5" />
- <PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.5" />
- <PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.5" />
- <PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.5" />
- <PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.5" />
- <PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.5" />
- <PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.5" />
- <PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.5" />
- <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.5" />
- <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="10.0.5" />
- <PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.5" />
- <PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.5" />
- <PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.5" />
- <PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.5" />
- <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
+ <PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.7" />
+ <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.7" />
+ <PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.7" />
+ <PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.7" />
+ <PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.7" />
+ <PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.7" />
+ <PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.7" />
+ <PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.7" />
+ <PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.7" />
+ <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.7" />
+ <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="10.0.7" />
+ <PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.7" />
+ <PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.7" />
+ <PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.7" />
+ <PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.7" />
+ <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.5.0" />
<PackageVersion Include="MimeTypes" Version="2.5.2" />
<PackageVersion Include="Morestachio" Version="5.0.1.631" />
<PackageVersion Include="Moq" Version="4.18.4" />
@@ -77,14 +77,13 @@
<PackageVersion Include="Svg.Skia" Version="3.4.1" />
<PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="10.1.7" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="10.1.7" />
- <PackageVersion Include="System.Text.Json" Version="10.0.5" />
+ <PackageVersion Include="System.Text.Json" Version="10.0.7" />
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
- <PackageVersion Include="z440.atl.core" Version="7.12.0" />
+ <PackageVersion Include="z440.atl.core" Version="7.13.0" />
<PackageVersion Include="TMDbLib" Version="3.0.0" />
<PackageVersion Include="UTF.Unknown" Version="2.6.0" />
- <PackageVersion Include="Xunit.Priority" Version="1.1.6" />
- <PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
- <PackageVersion Include="Xunit.SkippableFact" Version="1.5.61" />
- <PackageVersion Include="xunit" Version="2.9.3" />
+ <PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
+ <PackageVersion Include="xunit.v3" Version="3.2.2" />
+ <PackageVersion Include="Xunit.v3.Priority" Version="1.1.18" />
</ItemGroup>
</Project>
diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs
index 7cff2a25b6..23bd5cf200 100644
--- a/Emby.Server.Implementations/IO/LibraryMonitor.cs
+++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs
@@ -60,6 +60,7 @@ namespace Emby.Server.Implementations.IO
_fileSystem = fileSystem;
appLifetime.ApplicationStarted.Register(Start);
+ appLifetime.ApplicationStopping.Register(Stop);
}
/// <inheritdoc />
diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs
index 7cc851b73b..ef20ae9bca 100644
--- a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs
+++ b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs
@@ -50,6 +50,10 @@ public class ArtistsValidator
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
var names = _itemRepo.GetAllArtistNames();
+ var existingArtistIds = _libraryManager.GetItemIds(new InternalItemsQuery
+ {
+ IncludeItemTypes = [BaseItemKind.MusicArtist]
+ }).ToHashSet();
var numComplete = 0;
var count = names.Count;
@@ -59,8 +63,13 @@ public class ArtistsValidator
try
{
var item = _libraryManager.GetArtist(name);
+ var isNew = !existingArtistIds.Contains(item.Id);
+ var neverRefreshed = item.DateLastRefreshed == default;
- await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
+ if (isNew || neverRefreshed)
+ {
+ await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
+ }
}
catch (OperationCanceledException)
{
diff --git a/Emby.Server.Implementations/Localization/Core/bs.json b/Emby.Server.Implementations/Localization/Core/bs.json
new file mode 100644
index 0000000000..72b2a1f693
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/bs.json
@@ -0,0 +1,141 @@
+{
+ "Albums": "Albumi",
+ "Artists": "Umjetnici",
+ "Books": "Knjige",
+ "Channels": "Kanalima",
+ "Collections": "Zbirke",
+ "Default": "Zadano",
+ "Favorites": "Omiljeni",
+ "Folders": "Mape",
+ "Genres": "Žanrovi",
+ "HeaderAlbumArtists": "Umjetnici albuma",
+ "HeaderContinueWatching": "Nastavi gledati",
+ "Movies": "Filmovi",
+ "MusicVideos": "Muzički spotovi",
+ "Photos": "Slike",
+ "Playlists": "Plejliste",
+ "Shows": "Pokazuje",
+ "Songs": "Pjesme",
+ "ValueSpecialEpisodeName": "Posebno - {0}",
+ "AppDeviceValues": "Aplikacija: {0}, Uređaj: {1}",
+ "Application": "Prijava",
+ "AuthenticationSucceededWithUserName": "{0} uspješno autentificirano",
+ "CameraImageUploadedFrom": "Nova slika s kamere je postavljena sa {0}",
+ "ChapterNameValue": "Poglavlje {0}",
+ "DeviceOfflineWithName": "{0} se odspojio",
+ "DeviceOnlineWithName": "{0} je povezan",
+ "External": "Vanjsko",
+ "FailedLoginAttemptWithUserName": "Neuspjeli pokušaj prijave sa {0}",
+ "Forced": "Prisilno",
+ "HeaderFavoriteAlbums": "Omiljeni albumi",
+ "HeaderFavoriteArtists": "Omiljeni umjetnici",
+ "HeaderFavoriteEpisodes": "Omiljene epizode",
+ "HeaderFavoriteShows": "Omiljene emisije",
+ "HeaderFavoriteSongs": "Omiljene pjesme",
+ "HeaderLiveTV": "TV uživo",
+ "HeaderNextUp": "Slijedi",
+ "HeaderRecordingGroups": "Grupe za snimanje",
+ "HearingImpaired": "Oštećen sluh",
+ "HomeVideos": "Kućni videozapisi",
+ "Inherit": "Nasljedi",
+ "ItemAddedWithName": "{0} je dodan u biblioteku",
+ "ItemRemovedWithName": "{0} je uklonjen iz biblioteke",
+ "LabelIpAddressValue": "IP adresa: {0}",
+ "LabelRunningTimeValue": "Trajanje: {0}",
+ "Latest": "Posljednje dodano",
+ "MessageApplicationUpdated": "Jellyfin Server je ažuriran",
+ "MessageApplicationUpdatedTo": "Jellyfin Server je ažuriran na {0}",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Sekcija za konfiguraciju servera {0} je ažurirana",
+ "MessageServerConfigurationUpdated": "Konfiguracija servera je ažurirana",
+ "MixedContent": "Miješani sadržaj",
+ "Music": "Muzika",
+ "NameInstallFailed": "{0} instalacija je propala",
+ "NameSeasonNumber": "Sezona {0}",
+ "NameSeasonUnknown": "Sezona nepoznata",
+ "NewVersionIsAvailable": "Dostupna je nova verzija Jellyfin Servera za preuzimanje.",
+ "NotificationOptionApplicationUpdateAvailable": "Dostupno ažuriranje aplikacije",
+ "NotificationOptionApplicationUpdateInstalled": "Ažuriranje aplikacije instalirano",
+ "NotificationOptionAudioPlayback": "Pokrenuto je reproduciranje zvuka",
+ "NotificationOptionAudioPlaybackStopped": "Zaustavljeno je reproduciranje zvuka",
+ "NotificationOptionCameraImageUploaded": "Učitana slika s kamere",
+ "NotificationOptionInstallationFailed": "Neuspjeh instalacije",
+ "NotificationOptionNewLibraryContent": "Dodan novi sadržaj",
+ "NotificationOptionPluginError": "Neuspjeh dodatka",
+ "NotificationOptionPluginInstalled": "Dodatak je instaliran",
+ "NotificationOptionPluginUninstalled": "Dodatak je deinstaliran",
+ "NotificationOptionPluginUpdateInstalled": "Ažuriranje dodatka je instalirano",
+ "NotificationOptionServerRestartRequired": "Potreban je ponovni pokret servera",
+ "NotificationOptionTaskFailed": "Neuspjeh zakazane zadatke",
+ "NotificationOptionUserLockedOut": "Korisnik je zaključan",
+ "NotificationOptionVideoPlayback": "Pokrenuto je reproduciranje videa",
+ "NotificationOptionVideoPlaybackStopped": "Reprodukcija videa je zaustavljena",
+ "Plugin": "Plugin",
+ "PluginInstalledWithName": "{0} je instaliran",
+ "PluginUninstalledWithName": "{0} je deinstaliran",
+ "PluginUpdatedWithName": "{0} je ažurirano",
+ "ProviderValue": "Pružatelj: {0}",
+ "ScheduledTaskFailedWithName": "{0} nije uspjelo",
+ "ScheduledTaskStartedWithName": "{0} počelo",
+ "ServerNameNeedsToBeRestarted": "{0} treba ponovo pokrenuti",
+ "StartupEmbyServerIsLoading": "Jellyfin Server se učitava. Molimo pokušajte ponovo za kratko vrijeme.",
+ "SubtitleDownloadFailureFromForItem": "Podtitlovi nisu uspjeli preuzeti sa {0} za {1}",
+ "Sync": "Sinkronizacija",
+ "System": "Sistem",
+ "TvShows": "TV serije",
+ "Undefined": "Nedefinirano",
+ "User": "Korisnik",
+ "UserCreatedWithName": "Korisnik {0} je kreiran",
+ "UserDeletedWithName": "Korisnik {0} je izbrisan",
+ "UserDownloadingItemWithValues": "{0} preuzima {1}",
+ "UserLockedOutWithName": "Korisnik {0} je zaključan",
+ "UserOfflineFromDevice": "{0} se odspojio od {1}",
+ "UserOnlineFromDevice": "{0} je online od {1}",
+ "UserPasswordChangedWithName": "Lozinka je promijenjena za korisnika {0}",
+ "UserPolicyUpdatedWithName": "Pravila za korisnike su ažurirana za {0}",
+ "UserStartedPlayingItemWithValues": "{0} igra protiv {1} na {2}",
+ "UserStoppedPlayingItemWithValues": "{0} je završio igru protiv {1} na {2}",
+ "ValueHasBeenAddedToLibrary": "{0} je dodan u vašu medijsku biblioteku",
+ "VersionNumber": "Verzija {0}",
+ "TasksMaintenanceCategory": "Održavanje",
+ "TasksLibraryCategory": "Biblioteka",
+ "TasksApplicationCategory": "Prijava",
+ "TasksChannelsCategory": "Internetski kanali",
+ "TaskCleanActivityLog": "Očisti dnevnik aktivnosti",
+ "TaskCleanActivityLogDescription": "Brisanje unosa u dnevnik aktivnosti starijih od konfigurisane starosti.",
+ "TaskCleanCache": "Očistite direktorij keša",
+ "TaskCleanCacheDescription": "Brisanje keš datoteka koje sistemu više nisu potrebne.",
+ "TaskRefreshChapterImages": "Izvadi slike iz poglavlja",
+ "TaskRefreshChapterImagesDescription": "Stvara minijature za videozapise koji imaju poglavlja.",
+ "TaskAudioNormalization": "Normalizacija zvuka",
+ "TaskAudioNormalizationDescription": "Skeneriše datoteke radi podataka za normalizaciju zvuka.",
+ "TaskRefreshLibrary": "Skenerisati medijsku biblioteku",
+ "TaskRefreshLibraryDescription": "Skenerira vašu medijsku biblioteku na nove datoteke i osvježava metapodatke.",
+ "TaskCleanLogs": "Očisti direktorij dnevnika",
+ "TaskCleanLogsDescription": "Brisanje dnevničkih datoteka starijih od {0} dana.",
+ "TaskRefreshPeople": "Osvježite ljude",
+ "TaskRefreshPeopleDescription": "Ažurira metapodatke za glumce i režisere u vašoj medijskoj biblioteci.",
+ "TaskRefreshTrickplayImages": "Generirajte Trickplay slike",
+ "TaskRefreshTrickplayImagesDescription": "Stvara pregled trik-igara za videozapise u omogućenim bibliotekama.",
+ "TaskUpdatePlugins": "Ažuriraj dodatke",
+ "TaskUpdatePluginsDescription": "Preuzima i instalira ažuriranja dodataka koji su konfigurisani da se automatski ažuriraju.",
+ "TaskCleanTranscode": "Očisti Transcode direktorij",
+ "TaskCleanTranscodeDescription": "Brisanje transkodiranih datoteka starijih od jednog dana.",
+ "TaskRefreshChannels": "Osvježi kanale",
+ "TaskRefreshChannelsDescription": "Osvježava informacije o internetskom kanalu.",
+ "TaskDownloadMissingLyrics": "Preuzmi nedostajuće tekstove",
+ "TaskDownloadMissingLyricsDescription": "Preuzmi tekstove pjesama",
+ "TaskDownloadMissingSubtitles": "Preuzmite nedostajuće titlove",
+ "TaskDownloadMissingSubtitlesDescription": "Pretražuje internet u potrazi za nedostajućim titlovima na osnovu konfiguracije metapodataka.",
+ "TaskOptimizeDatabase": "Optimizirajte bazu podataka",
+ "TaskOptimizeDatabaseDescription": "Komprimira bazu podataka i čisti slobodan prostor. Pokretanje ovog zadatka nakon skeniranja biblioteke ili izvođenja drugih promjena koje podrazumijevaju izmjene baze podataka može poboljšati performanse.",
+ "TaskKeyframeExtractor": "Izvađač ključnih sličica",
+ "TaskKeyframeExtractorDescription": "Izvlači ključne okvire iz video datoteka kako bi kreirao preciznije HLS playliste. Ovaj zadatak može trajati dugo.",
+ "TaskCleanCollectionsAndPlaylists": "Očistite kolekcije i playliste",
+ "TaskCleanCollectionsAndPlaylistsDescription": "Uklanja stavke iz kolekcija i playlista koje više ne postoje.",
+ "TaskExtractMediaSegments": "Analiza medijskog segmenta",
+ "TaskExtractMediaSegmentsDescription": "Izvlači ili dobija medijske segmente iz dodataka koji podržavaju MediaSegment.",
+ "TaskMoveTrickplayImages": "Migracija lokacije slike Trickplay",
+ "TaskMoveTrickplayImagesDescription": "Premješta postojeće datoteke trik-igara prema postavkama biblioteke.",
+ "CleanupUserDataTask": "Zadatak čišćenja korisničkih podataka",
+ "CleanupUserDataTaskDescription": "Čisti sve korisničke podatke (stanje praćenja, status omiljenog itd.) sa medija koji više nije prisutan najmanje 90 dana."
+}
diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json
index fce3a614c2..ec5cbf0d43 100644
--- a/Emby.Server.Implementations/Localization/Core/ca.json
+++ b/Emby.Server.Implementations/Localization/Core/ca.json
@@ -63,8 +63,8 @@
"Photos": "Fotos",
"Playlists": "Llistes de reproducció",
"Plugin": "Complement",
- "PluginInstalledWithName": "{0} s'ha instal·lat",
- "PluginUninstalledWithName": "{0} s'ha desinstal·lat",
+ "PluginInstalledWithName": "S'ha instal·lat {0}",
+ "PluginUninstalledWithName": "S'ha desinstal·lat {0}",
"PluginUpdatedWithName": "S'ha actualitzat {0}",
"ProviderValue": "Proveïdor: {0}",
"ScheduledTaskFailedWithName": "{0} ha fallat",
diff --git a/Emby.Server.Implementations/Localization/Core/fi.json b/Emby.Server.Implementations/Localization/Core/fi.json
index 15a04d22cd..79afbb519b 100644
--- a/Emby.Server.Implementations/Localization/Core/fi.json
+++ b/Emby.Server.Implementations/Localization/Core/fi.json
@@ -39,8 +39,8 @@
"Channels": "Kanavat",
"CameraImageUploadedFrom": "Uusi kameran kuva on sirretty lähteestä {0}",
"Books": "Kirjat",
- "AuthenticationSucceededWithUserName": "{0} on todennettu",
- "Artists": "Esittäjät",
+ "AuthenticationSucceededWithUserName": "{0} todennus onnistunut",
+ "Artists": "Artistit",
"Application": "Sovellus",
"AppDeviceValues": "Sovellus: {0}, Laite: {1}",
"Albums": "Albumit",
diff --git a/Emby.Server.Implementations/Localization/Core/hr.json b/Emby.Server.Implementations/Localization/Core/hr.json
index 94db435715..eaeb173c20 100644
--- a/Emby.Server.Implementations/Localization/Core/hr.json
+++ b/Emby.Server.Implementations/Localization/Core/hr.json
@@ -23,7 +23,7 @@
"HeaderFavoriteShows": "Omiljene serije",
"HeaderFavoriteSongs": "Omiljene pjesme",
"HeaderLiveTV": "TV uživo",
- "HeaderNextUp": "Slijedi",
+ "HeaderNextUp": "Sljedeće na redu",
"HeaderRecordingGroups": "Grupa snimka",
"HomeVideos": "Kućni video",
"Inherit": "Naslijedi",
@@ -73,10 +73,10 @@
"Shows": "Emisije",
"Songs": "Pjesme",
"StartupEmbyServerIsLoading": "Jellyfin server se učitava. Pokušajte ponovo uskoro.",
- "SubtitleDownloadFailureFromForItem": "Prijevod nije uspješno preuzet od {0} za {1}",
+ "SubtitleDownloadFailureFromForItem": "Titlovi nisu uspješno preuzeti od {0} za {1}",
"Sync": "Sinkronizacija",
"System": "Sustav",
- "TvShows": "Serije",
+ "TvShows": "TV emisije",
"User": "Korisnik",
"UserCreatedWithName": "Korisnik {0} je kreiran",
"UserDeletedWithName": "Korisnik {0} je obrisan",
@@ -88,26 +88,26 @@
"UserPolicyUpdatedWithName": "Pravila za korisnika ažurirana su za {0}",
"UserStartedPlayingItemWithValues": "{0} je pokrenuo reprodukciju {1} na {2}",
"UserStoppedPlayingItemWithValues": "{0} je završio reprodukciju {1} na {2}",
- "ValueHasBeenAddedToLibrary": "{0} je dodano u medijsku biblioteku",
- "ValueSpecialEpisodeName": "Posebno - {0}",
+ "ValueHasBeenAddedToLibrary": "{0} je dodano u biblioteku medija",
+ "ValueSpecialEpisodeName": "Posebno – {0}",
"VersionNumber": "Verzija {0}",
- "TaskRefreshLibraryDescription": "Skenira medijsku biblioteku radi novih datoteka i osvježava metapodatke.",
- "TaskRefreshLibrary": "Skeniraj medijsku biblioteku",
+ "TaskRefreshLibraryDescription": "Skenira biblioteku medija radi novih datoteka i osvježava metapodatke.",
+ "TaskRefreshLibrary": "Skeniraj biblioteku medija",
"TaskRefreshChapterImagesDescription": "Kreira sličice za videozapise koji imaju poglavlja.",
"TaskRefreshChapterImages": "Izdvoji slike poglavlja",
"TaskCleanCacheDescription": "Briše nepotrebne datoteke iz predmemorije.",
"TaskCleanCache": "Očisti mapu predmemorije",
"TasksApplicationCategory": "Aplikacija",
"TasksMaintenanceCategory": "Održavanje",
- "TaskDownloadMissingSubtitlesDescription": "Pretraži Internet za prijevodima koji nedostaju prema konfiguraciji metapodataka.",
- "TaskDownloadMissingSubtitles": "Preuzmi prijevod koji nedostaje",
+ "TaskDownloadMissingSubtitlesDescription": "Pretraži internet za nedsotajućim titlovima ne osnovi konfiguracije metapodataka.",
+ "TaskDownloadMissingSubtitles": "Preuzmi nedostajuće titlove",
"TaskRefreshChannelsDescription": "Osvježava informacije Internet kanala.",
"TaskRefreshChannels": "Osvježi kanale",
"TaskCleanTranscodeDescription": "Briše transkodirane datoteke starije od jednog dana.",
"TaskCleanTranscode": "Očisti mapu transkodiranja",
"TaskUpdatePluginsDescription": "Preuzima i instalira ažuriranja za dodatke koji su konfigurirani da se ažuriraju automatski.",
"TaskUpdatePlugins": "Ažuriraj dodatke",
- "TaskRefreshPeopleDescription": "Ažurira metapodatke za glumce i redatelje u medijskoj biblioteci.",
+ "TaskRefreshPeopleDescription": "Ažurira metapodatke za glumce i redatelje u biblioteci medija.",
"TaskRefreshPeople": "Osvježi osobe",
"TaskCleanLogsDescription": "Briše zapise dnevnika koji su stariji od {0} dana.",
"TaskCleanLogs": "Očisti mapu dnevnika zapisa",
@@ -119,7 +119,7 @@
"Forced": "Forsirani",
"Default": "Zadano",
"TaskOptimizeDatabase": "Optimiziraj bazu podataka",
- "External": "Vanjski",
+ "External": "Eksterni",
"TaskKeyframeExtractorDescription": "Izvlačenje ključnih okvira iz videozapisa za stvaranje objektivnije HLS liste za reprodukciju. Pokretanje ovog zadatka može potrajati.",
"TaskKeyframeExtractor": "Izvoditelj ključnog okvira",
"TaskOptimizeDatabaseDescription": "Sažima bazu podataka i uklanja prazan prostor. Pokretanje ovog zadatka, može poboljšati performanse nakon provođenja indeksiranja biblioteke ili provođenja drugih promjena koje utječu na bazu podataka.",
@@ -135,7 +135,7 @@
"TaskDownloadMissingLyricsDescription": "Preuzmi tekstove pjesama",
"TaskExtractMediaSegmentsDescription": "Izvlači ili pribavlja dijelove medija iz omogućenih media pluginova.",
"TaskMoveTrickplayImages": "Premjesti mjesto slika brzog pregledavanja",
- "TaskMoveTrickplayImagesDescription": "Premješta postojeće datoteke brzog pregledavanja prema postavkama biblioteke.",
+ "TaskMoveTrickplayImagesDescription": "Premješta postojeće datoteke brzog pregledavanja u postavke biblioteke.",
"CleanupUserDataTask": "Zadatak čišćenja korisničkih podataka",
"CleanupUserDataTaskDescription": "Briše sve korisničke podatke (stanje gledanja, status favorita itd.) s medija koji više nisu prisutni najmanje 90 dana."
}
diff --git a/Emby.Server.Implementations/Localization/Core/ht.json b/Emby.Server.Implementations/Localization/Core/ht.json
index f927d3173a..183c422a85 100644
--- a/Emby.Server.Implementations/Localization/Core/ht.json
+++ b/Emby.Server.Implementations/Localization/Core/ht.json
@@ -58,5 +58,8 @@
"ValueSpecialEpisodeName": "Spesyal - {0}",
"VersionNumber": "Vesyon {0}",
"TasksApplicationCategory": "Aplikasyon",
- "TasksMaintenanceCategory": "Antretyen"
+ "TasksMaintenanceCategory": "Antretyen",
+ "AppDeviceValues": "Aplikasyon: {0}, Aparèy: {1}",
+ "AuthenticationSucceededWithUserName": "{0} otantifye avèk siksè",
+ "CameraImageUploadedFrom": "Une nouvelle image de la caméra a été téléchargée depuis {0}"
}
diff --git a/Emby.Server.Implementations/Localization/Core/zh-HK.json b/Emby.Server.Implementations/Localization/Core/zh-HK.json
index 8ae899a73c..0a454b2938 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-HK.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-HK.json
@@ -29,7 +29,7 @@
"Inherit": "繼承",
"ItemAddedWithName": "{0} 經已加咗入媒體櫃",
"ItemRemovedWithName": "{0} 經已由媒體櫃移除咗",
- "LabelIpAddressValue": "IP 地址:{0}",
+ "LabelIpAddressValue": "IP 位址:{0}",
"LabelRunningTimeValue": "運行時間:{0}",
"Latest": "最新",
"MessageApplicationUpdated": "Jellyfin 經已更新咗",
diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs
index bc80c2b405..6fca5bc1ba 100644
--- a/Emby.Server.Implementations/Localization/LocalizationManager.cs
+++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs
@@ -138,7 +138,7 @@ namespace Emby.Server.Implementations.Localization
string twoCharName = parts[2];
if (string.IsNullOrWhiteSpace(twoCharName))
{
- continue;
+ twoCharName = string.Empty;
}
else if (twoCharName.Contains('-', StringComparison.OrdinalIgnoreCase))
{
diff --git a/Emby.Server.Implementations/Localization/countries.json b/Emby.Server.Implementations/Localization/countries.json
index d92dc880b1..811a7d4094 100644
--- a/Emby.Server.Implementations/Localization/countries.json
+++ b/Emby.Server.Implementations/Localization/countries.json
@@ -750,6 +750,12 @@
"TwoLetterISORegionName": "TJ"
},
{
+ "DisplayName": "Tanzania",
+ "Name": "TZ",
+ "ThreeLetterISORegionName": "TZA",
+ "TwoLetterISORegionName": "TZ"
+ },
+ {
"DisplayName": "Thailand",
"Name": "TH",
"ThreeLetterISORegionName": "THA",
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index 6b8888d244..d4d0f4537b 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -832,10 +832,6 @@ namespace Emby.Server.Implementations.Session
{
data.Played = true;
}
- else
- {
- data.Played = false;
- }
_userDataManager.SaveUserData(user, item, data, UserDataSaveReason.PlaybackStart, CancellationToken.None);
}
diff --git a/Jellyfin.Api/Controllers/ActivityLogController.cs b/Jellyfin.Api/Controllers/ActivityLogController.cs
index 47d3f4b7f7..d6cc0e71a4 100644
--- a/Jellyfin.Api/Controllers/ActivityLogController.cs
+++ b/Jellyfin.Api/Controllers/ActivityLogController.cs
@@ -19,6 +19,7 @@ namespace Jellyfin.Api.Controllers;
/// </summary>
[Route("System/ActivityLog")]
[Authorize(Policy = Policies.RequiresElevation)]
+[Tags("System")]
public class ActivityLogController : BaseJellyfinApiController
{
private readonly IActivityManager _activityManager;
diff --git a/Jellyfin.Api/Controllers/ApiKeyController.cs b/Jellyfin.Api/Controllers/ApiKeyController.cs
index 3363d7bad2..161479e4ca 100644
--- a/Jellyfin.Api/Controllers/ApiKeyController.cs
+++ b/Jellyfin.Api/Controllers/ApiKeyController.cs
@@ -14,6 +14,7 @@ namespace Jellyfin.Api.Controllers;
/// Authentication controller.
/// </summary>
[Route("Auth")]
+[Tags("Authentication")]
public class ApiKeyController : BaseJellyfinApiController
{
private readonly IAuthenticationManager _authenticationManager;
diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs
index 99b0fde06d..f97ab414ce 100644
--- a/Jellyfin.Api/Controllers/ArtistsController.cs
+++ b/Jellyfin.Api/Controllers/ArtistsController.cs
@@ -25,6 +25,7 @@ namespace Jellyfin.Api.Controllers;
/// </summary>
[Route("Artists")]
[Authorize]
+[Tags("Artist")]
public class ArtistsController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs
index 0d85b3a0db..e46ef0e31d 100644
--- a/Jellyfin.Api/Controllers/ChannelsController.cs
+++ b/Jellyfin.Api/Controllers/ChannelsController.cs
@@ -25,6 +25,7 @@ namespace Jellyfin.Api.Controllers;
/// Channels Controller.
/// </summary>
[Authorize]
+[Tags("Channel")]
public class ChannelsController : BaseJellyfinApiController
{
private readonly IChannelManager _channelManager;
diff --git a/Jellyfin.Api/Controllers/ClientLogController.cs b/Jellyfin.Api/Controllers/ClientLogController.cs
index 139888bde8..c213b87940 100644
--- a/Jellyfin.Api/Controllers/ClientLogController.cs
+++ b/Jellyfin.Api/Controllers/ClientLogController.cs
@@ -15,6 +15,7 @@ namespace Jellyfin.Api.Controllers;
/// Client log controller.
/// </summary>
[Authorize]
+[Tags("System")]
public class ClientLogController : BaseJellyfinApiController
{
private const int MaxDocumentSize = 1_000_000;
diff --git a/Jellyfin.Api/Controllers/ConfigurationController.cs b/Jellyfin.Api/Controllers/ConfigurationController.cs
index 9e03fbeb06..ecd667b2e8 100644
--- a/Jellyfin.Api/Controllers/ConfigurationController.cs
+++ b/Jellyfin.Api/Controllers/ConfigurationController.cs
@@ -20,6 +20,7 @@ namespace Jellyfin.Api.Controllers;
/// </summary>
[Route("System")]
[Authorize]
+[Tags("System")]
public class ConfigurationController : BaseJellyfinApiController
{
private readonly IServerConfigurationManager _configurationManager;
diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs
index 50050262f0..eadb8c9855 100644
--- a/Jellyfin.Api/Controllers/DevicesController.cs
+++ b/Jellyfin.Api/Controllers/DevicesController.cs
@@ -19,6 +19,7 @@ namespace Jellyfin.Api.Controllers;
/// Devices Controller.
/// </summary>
[Authorize(Policy = Policies.RequiresElevation)]
+[Tags("Device")]
public class DevicesController : BaseJellyfinApiController
{
private readonly IDeviceManager _deviceManager;
diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
index ef54e9db54..61d40a7268 100644
--- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
+++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
@@ -20,6 +20,7 @@ namespace Jellyfin.Api.Controllers;
/// Display Preferences Controller.
/// </summary>
[Authorize]
+[Tags("DisplayPreference")]
public class DisplayPreferencesController : BaseJellyfinApiController
{
private readonly IDisplayPreferencesManager _displayPreferencesManager;
diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs
index 456e643fd7..39c3f5abcf 100644
--- a/Jellyfin.Api/Controllers/GenresController.cs
+++ b/Jellyfin.Api/Controllers/GenresController.cs
@@ -25,6 +25,7 @@ namespace Jellyfin.Api.Controllers;
/// The genres controller.
/// </summary>
[Authorize]
+[Tags("Genre")]
public class GenresController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index 39760556a6..69cdba6afd 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -30,6 +30,7 @@ namespace Jellyfin.Api.Controllers;
/// </summary>
[Route("")]
[Authorize]
+[Tags("Item")]
public class ItemsController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
diff --git a/Jellyfin.Api/Controllers/LibraryStructureController.cs b/Jellyfin.Api/Controllers/LibraryStructureController.cs
index 117811429a..8136dec177 100644
--- a/Jellyfin.Api/Controllers/LibraryStructureController.cs
+++ b/Jellyfin.Api/Controllers/LibraryStructureController.cs
@@ -75,7 +75,9 @@ public class LibraryStructureController : BaseJellyfinApiController
[HttpPost]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> AddVirtualFolder(
- [FromQuery] string name,
+ [FromQuery]
+ [RegularExpression(@"^(?:\S(?:.*\S)?)$", ErrorMessage = "Library name cannot be empty or have leading/trailing spaces.")]
+ string name,
[FromQuery] CollectionTypeOptions? collectionType,
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] paths,
[FromBody] AddVirtualFolderDto? libraryOptionsDto,
diff --git a/Jellyfin.Api/Controllers/LyricsController.cs b/Jellyfin.Api/Controllers/LyricsController.cs
index 8eb4cadf88..5a27b2719e 100644
--- a/Jellyfin.Api/Controllers/LyricsController.cs
+++ b/Jellyfin.Api/Controllers/LyricsController.cs
@@ -7,7 +7,6 @@ using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Extensions;
-using Jellyfin.Api.Helpers;
using Jellyfin.Extensions;
using MediaBrowser.Common.Api;
using MediaBrowser.Controller.Entities.Audio;
@@ -27,6 +26,7 @@ namespace Jellyfin.Api.Controllers;
/// Lyrics controller.
/// </summary>
[Route("")]
+[Tags("Lyric")]
public class LyricsController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
diff --git a/Jellyfin.Api/Controllers/MediaSegmentsController.cs b/Jellyfin.Api/Controllers/MediaSegmentsController.cs
index b8836d7cf1..65565826a4 100644
--- a/Jellyfin.Api/Controllers/MediaSegmentsController.cs
+++ b/Jellyfin.Api/Controllers/MediaSegmentsController.cs
@@ -20,6 +20,7 @@ namespace Jellyfin.Api.Controllers;
/// Media Segments api.
/// </summary>
[Authorize]
+[Tags("MediaSegment")]
public class MediaSegmentsController : BaseJellyfinApiController
{
private readonly IMediaSegmentManager _mediaSegmentManager;
diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs
index ace9a06395..50d34d0656 100644
--- a/Jellyfin.Api/Controllers/MoviesController.cs
+++ b/Jellyfin.Api/Controllers/MoviesController.cs
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
-using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
@@ -18,6 +17,7 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Api.Controllers;
@@ -26,6 +26,7 @@ namespace Jellyfin.Api.Controllers;
/// Movies controller.
/// </summary>
[Authorize]
+[Tags("Movie")]
public class MoviesController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs
index a6427df67a..48d4ebdc04 100644
--- a/Jellyfin.Api/Controllers/MusicGenresController.cs
+++ b/Jellyfin.Api/Controllers/MusicGenresController.cs
@@ -25,6 +25,7 @@ namespace Jellyfin.Api.Controllers;
/// The music genres controller.
/// </summary>
[Authorize]
+[Tags("MusicGenre")]
public class MusicGenresController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs
index 274e94ee6d..1f8f963f70 100644
--- a/Jellyfin.Api/Controllers/PackageController.cs
+++ b/Jellyfin.Api/Controllers/PackageController.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
-using Jellyfin.Api.Constants;
using MediaBrowser.Common.Api;
using MediaBrowser.Common.Updates;
using MediaBrowser.Controller.Configuration;
@@ -19,6 +18,7 @@ namespace Jellyfin.Api.Controllers;
/// </summary>
[Route("")]
[Authorize(Policy = Policies.RequiresElevation)]
+[Tags("Plugin")]
public class PackageController : BaseJellyfinApiController
{
private readonly IInstallationManager _installationManager;
diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs
index 2b2afb0fe6..8e7026341d 100644
--- a/Jellyfin.Api/Controllers/PersonsController.cs
+++ b/Jellyfin.Api/Controllers/PersonsController.cs
@@ -22,6 +22,7 @@ namespace Jellyfin.Api.Controllers;
/// Persons controller.
/// </summary>
[Authorize]
+[Tags("Person")]
public class PersonsController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
@@ -50,6 +51,9 @@ public class PersonsController : BaseJellyfinApiController
/// <param name="startIndex">Optional. All items with a lower index will be dropped from the response.</param>
/// <param name="limit">Optional. The maximum number of records to return.</param>
/// <param name="searchTerm">The search term.</param>
+ /// <param name="nameStartsWith">Optional. Filter by items whose name starts with the given input string.</param>
+ /// <param name="nameLessThan">Optional. Filter by items whose name will appear before this value when sorted alphabetically.</param>
+ /// <param name="nameStartsWithOrGreater">Optional. Filter by items whose name will appear after this value when sorted alphabetically.</param>
/// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
/// <param name="filters">Optional. Specify additional filters to apply.</param>
/// <param name="isFavorite">Optional filter by items that are marked as favorite, or not. userId is required.</param>
@@ -70,6 +74,9 @@ public class PersonsController : BaseJellyfinApiController
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] string? searchTerm,
+ [FromQuery] string? nameStartsWith,
+ [FromQuery] string? nameLessThan,
+ [FromQuery] string? nameStartsWithOrGreater,
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters,
[FromQuery] bool? isFavorite,
@@ -97,6 +104,9 @@ public class PersonsController : BaseJellyfinApiController
excludePersonTypes)
{
NameContains = searchTerm,
+ NameStartsWith = nameStartsWith,
+ NameLessThan = nameLessThan,
+ NameStartsWithOrGreater = nameStartsWithOrGreater,
User = user,
IsFavorite = !isFavorite.HasValue && isFavoriteInFilters ? true : isFavorite,
AppearsInItemId = appearsInItemId ?? Guid.Empty,
diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs
index 9679180937..048a49ffd4 100644
--- a/Jellyfin.Api/Controllers/PlaylistsController.cs
+++ b/Jellyfin.Api/Controllers/PlaylistsController.cs
@@ -29,6 +29,7 @@ namespace Jellyfin.Api.Controllers;
/// Playlists controller.
/// </summary>
[Authorize]
+[Tags("Playlist")]
public class PlaylistsController : BaseJellyfinApiController
{
private readonly IPlaylistManager _playlistManager;
diff --git a/Jellyfin.Api/Controllers/PlaystateController.cs b/Jellyfin.Api/Controllers/PlaystateController.cs
index ade0906b34..b8361dfd88 100644
--- a/Jellyfin.Api/Controllers/PlaystateController.cs
+++ b/Jellyfin.Api/Controllers/PlaystateController.cs
@@ -6,7 +6,6 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Database.Implementations.Entities;
-using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
@@ -25,6 +24,7 @@ namespace Jellyfin.Api.Controllers;
/// </summary>
[Route("")]
[Authorize]
+[Tags("Session")]
public class PlaystateController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs
index 53b7349e7d..e20efe90a9 100644
--- a/Jellyfin.Api/Controllers/PluginsController.cs
+++ b/Jellyfin.Api/Controllers/PluginsController.cs
@@ -6,7 +6,6 @@ using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
-using Jellyfin.Api.Constants;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Api;
using MediaBrowser.Common.Plugins;
@@ -23,6 +22,7 @@ namespace Jellyfin.Api.Controllers;
/// Plugins controller.
/// </summary>
[Authorize(Policy = Policies.RequiresElevation)]
+[Tags("Plugin")]
public class PluginsController : BaseJellyfinApiController
{
private readonly IInstallationManager _installationManager;
diff --git a/Jellyfin.Api/Controllers/QuickConnectController.cs b/Jellyfin.Api/Controllers/QuickConnectController.cs
index bdb2a4d20b..5c7b38e137 100644
--- a/Jellyfin.Api/Controllers/QuickConnectController.cs
+++ b/Jellyfin.Api/Controllers/QuickConnectController.cs
@@ -16,6 +16,7 @@ namespace Jellyfin.Api.Controllers;
/// <summary>
/// Quick connect controller.
/// </summary>
+[Tags("Authentication")]
public class QuickConnectController : BaseJellyfinApiController
{
private readonly IQuickConnect _quickConnect;
diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs
index 065466cbca..f122d0f5e5 100644
--- a/Jellyfin.Api/Controllers/ScheduledTasksController.cs
+++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
-using Jellyfin.Api.Constants;
using MediaBrowser.Common.Api;
using MediaBrowser.Model.Tasks;
using Microsoft.AspNetCore.Authorization;
@@ -15,6 +14,7 @@ namespace Jellyfin.Api.Controllers;
/// Scheduled Tasks Controller.
/// </summary>
[Authorize(Policy = Policies.RequiresElevation)]
+[Tags("ScheduledTask")]
public class ScheduledTasksController : BaseJellyfinApiController
{
private readonly ITaskManager _taskManager;
diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs
index ad08dc5f9b..a8feb206a4 100644
--- a/Jellyfin.Api/Controllers/StudiosController.cs
+++ b/Jellyfin.Api/Controllers/StudiosController.cs
@@ -22,6 +22,7 @@ namespace Jellyfin.Api.Controllers;
/// Studios controller.
/// </summary>
[Authorize]
+[Tags("Studio")]
public class StudiosController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
diff --git a/Jellyfin.Api/Controllers/SuggestionsController.cs b/Jellyfin.Api/Controllers/SuggestionsController.cs
index e9e404076f..9c5515dd92 100644
--- a/Jellyfin.Api/Controllers/SuggestionsController.cs
+++ b/Jellyfin.Api/Controllers/SuggestionsController.cs
@@ -23,6 +23,7 @@ namespace Jellyfin.Api.Controllers;
/// </summary>
[Route("")]
[Authorize]
+[Tags("Suggestion")]
public class SuggestionsController : BaseJellyfinApiController
{
private readonly IDtoService _dtoService;
diff --git a/Jellyfin.Api/Controllers/TimeSyncController.cs b/Jellyfin.Api/Controllers/TimeSyncController.cs
index d7304cf426..fe6e11f9e2 100644
--- a/Jellyfin.Api/Controllers/TimeSyncController.cs
+++ b/Jellyfin.Api/Controllers/TimeSyncController.cs
@@ -9,6 +9,7 @@ namespace Jellyfin.Api.Controllers;
/// The time sync controller.
/// </summary>
[Route("")]
+[Tags("System")]
public class TimeSyncController : BaseJellyfinApiController
{
/// <summary>
diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs
index 3e4bac89a5..f8c5bd4b87 100644
--- a/Jellyfin.Api/Controllers/TrailersController.cs
+++ b/Jellyfin.Api/Controllers/TrailersController.cs
@@ -15,6 +15,7 @@ namespace Jellyfin.Api.Controllers;
/// The trailers controller.
/// </summary>
[Authorize]
+[Tags("Trailer")]
public class TrailersController : BaseJellyfinApiController
{
private readonly ItemsController _itemsController;
diff --git a/Jellyfin.Api/Controllers/TrickplayController.cs b/Jellyfin.Api/Controllers/TrickplayController.cs
index c9f8b36768..d7a10ce5f6 100644
--- a/Jellyfin.Api/Controllers/TrickplayController.cs
+++ b/Jellyfin.Api/Controllers/TrickplayController.cs
@@ -5,7 +5,6 @@ using System.Text;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Extensions;
-using Jellyfin.Api.Helpers;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Trickplay;
@@ -21,6 +20,7 @@ namespace Jellyfin.Api.Controllers;
/// </summary>
[Route("")]
[Authorize]
+[Tags("TrickPlay")]
public class TrickplayController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs
index c86c9b8f61..e45a100b77 100644
--- a/Jellyfin.Api/Controllers/TvShowsController.cs
+++ b/Jellyfin.Api/Controllers/TvShowsController.cs
@@ -27,6 +27,7 @@ namespace Jellyfin.Api.Controllers;
/// </summary>
[Route("Shows")]
[Authorize]
+[Tags("Show")]
public class TvShowsController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs
index f4e0c86143..d4e9b234c5 100644
--- a/Jellyfin.Api/Controllers/UniversalAudioController.cs
+++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs
@@ -29,6 +29,7 @@ namespace Jellyfin.Api.Controllers;
/// The universal audio controller.
/// </summary>
[Route("")]
+[Tags("Audio")]
public class UniversalAudioController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
diff --git a/Jellyfin.Api/Controllers/UserViewsController.cs b/Jellyfin.Api/Controllers/UserViewsController.cs
index ed4bba2bb1..c1d06bad36 100644
--- a/Jellyfin.Api/Controllers/UserViewsController.cs
+++ b/Jellyfin.Api/Controllers/UserViewsController.cs
@@ -26,6 +26,7 @@ namespace Jellyfin.Api.Controllers;
/// </summary>
[Route("")]
[Authorize]
+[Tags("UserView")]
public class UserViewsController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
diff --git a/Jellyfin.Api/Controllers/VideoAttachmentsController.cs b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs
index b67c6fdb7b..2c8b452c35 100644
--- a/Jellyfin.Api/Controllers/VideoAttachmentsController.cs
+++ b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs
@@ -19,6 +19,7 @@ namespace Jellyfin.Api.Controllers;
/// Attachments controller.
/// </summary>
[Route("Videos")]
+[Tags("Video")]
public class VideoAttachmentsController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs
index 7854edc5ac..394a95ee5f 100644
--- a/Jellyfin.Api/Controllers/VideosController.cs
+++ b/Jellyfin.Api/Controllers/VideosController.cs
@@ -35,6 +35,7 @@ namespace Jellyfin.Api.Controllers;
/// <summary>
/// The videos controller.
/// </summary>
+[Tags("Video")]
public class VideosController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs
index 685334a9f0..aa6464ee7a 100644
--- a/Jellyfin.Api/Controllers/YearsController.cs
+++ b/Jellyfin.Api/Controllers/YearsController.cs
@@ -26,6 +26,7 @@ namespace Jellyfin.Api.Controllers;
/// Years controller.
/// </summary>
[Authorize]
+[Tags("Year")]
public class YearsController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
diff --git a/Jellyfin.Server.Implementations/Item/PeopleRepository.cs b/Jellyfin.Server.Implementations/Item/PeopleRepository.cs
index ad9953d1b6..7147fbfe7d 100644
--- a/Jellyfin.Server.Implementations/Item/PeopleRepository.cs
+++ b/Jellyfin.Server.Implementations/Item/PeopleRepository.cs
@@ -235,6 +235,21 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
query = query.Where(e => e.Name.ToUpper().Contains(nameContainsUpper));
}
+ if (!string.IsNullOrWhiteSpace(filter.NameStartsWith))
+ {
+ query = query.Where(e => e.Name.StartsWith(filter.NameStartsWith.ToLowerInvariant()));
+ }
+
+ if (!string.IsNullOrWhiteSpace(filter.NameLessThan))
+ {
+ query = query.Where(e => e.Name.CompareTo(filter.NameLessThan.ToLowerInvariant()) < 0);
+ }
+
+ if (!string.IsNullOrWhiteSpace(filter.NameStartsWithOrGreater))
+ {
+ query = query.Where(e => e.Name.CompareTo(filter.NameStartsWithOrGreater.ToLowerInvariant()) >= 0);
+ }
+
return query;
}
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index c71c193e2e..a498901481 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -312,7 +312,7 @@ namespace Jellyfin.Server.Extensions
return;
}
- if (prefixLength == NetworkConstants.MinimumIPv4PrefixSize)
+ if ((addr.AddressFamily == AddressFamily.InterNetwork && prefixLength == NetworkConstants.MinimumIPv4PrefixSize) || (addr.AddressFamily == AddressFamily.InterNetworkV6 && prefixLength == NetworkConstants.MinimumIPv6PrefixSize))
{
options.KnownProxies.Add(addr);
}
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index 93f71fdc69..93ba672535 100644
--- a/Jellyfin.Server/Program.cs
+++ b/Jellyfin.Server/Program.cs
@@ -161,7 +161,6 @@ namespace Jellyfin.Server
_loggerFactory,
options,
startupConfig);
- _appHost = appHost;
var configurationCompleted = false;
try
{
@@ -207,6 +206,7 @@ namespace Jellyfin.Server
await jellyfinMigrationService.MigrateStepAsync(JellyfinMigrationStageTypes.CoreInitialisation, appHost.ServiceProvider).ConfigureAwait(false);
await appHost.InitializeServices(startupConfig).ConfigureAwait(false);
+ _appHost = appHost;
await jellyfinMigrationService.MigrateStepAsync(JellyfinMigrationStageTypes.AppInitialisation, appHost.ServiceProvider).ConfigureAwait(false);
await jellyfinMigrationService.CleanupSystemAfterMigration(_logger).ConfigureAwait(false);
@@ -263,6 +263,7 @@ namespace Jellyfin.Server
_appHost = null;
_jellyfinHost?.Dispose();
+ _jellyfinHost = null;
}
}
diff --git a/Jellyfin.Server/ServerSetupApp/SetupServer.cs b/Jellyfin.Server/ServerSetupApp/SetupServer.cs
index 1aa39f97b6..05975929db 100644
--- a/Jellyfin.Server/ServerSetupApp/SetupServer.cs
+++ b/Jellyfin.Server/ServerSetupApp/SetupServer.cs
@@ -142,6 +142,7 @@ public sealed class SetupServer : IDisposable
ThrowIfDisposed();
var retryAfterValue = TimeSpan.FromSeconds(5);
var config = _configurationManager.GetNetworkConfiguration()!;
+ _startupServer?.Dispose();
_startupServer = Host.CreateDefaultBuilder(["hostBuilder:reloadConfigOnChange=false"])
.UseConsoleLifetime()
.UseSerilog()
diff --git a/MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs b/MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs
deleted file mode 100644
index 0445397ad8..0000000000
--- a/MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Model.Providers;
-
-namespace MediaBrowser.Common.Providers
-{
- public class SubtitleConfigurationFactory : IConfigurationFactory
- {
- /// <inheritdoc />
- public IEnumerable<ConfigurationStore> GetConfigurations()
- {
- yield return new ConfigurationStore()
- {
- Key = "subtitles",
- ConfigurationType = typeof(SubtitleOptions)
- };
- }
- }
-}
diff --git a/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs b/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs
index f4b3910b0e..e12ba22343 100644
--- a/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs
+++ b/MediaBrowser.Controller/Entities/InternalPeopleQuery.cs
@@ -42,6 +42,12 @@ namespace MediaBrowser.Controller.Entities
public string NameContains { get; set; }
+ public string NameStartsWith { get; set; }
+
+ public string NameLessThan { get; set; }
+
+ public string NameStartsWithOrGreater { get; set; }
+
public User User { get; set; }
public bool? IsFavorite { get; set; }
diff --git a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
index 3c3ac2471f..905aad17b9 100644
--- a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using System;
@@ -12,45 +10,45 @@ namespace MediaBrowser.Controller.LiveTv
{
public ProgramInfo()
{
- Genres = new List<string>();
+ Genres = [];
- ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
- SeriesProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+ ProviderIds = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
+ SeriesProviderIds = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
}
/// <summary>
/// Gets or sets the id of the program.
/// </summary>
- public string Id { get; set; }
+ public string? Id { get; set; }
/// <summary>
/// Gets or sets the channel identifier.
/// </summary>
/// <value>The channel identifier.</value>
- public string ChannelId { get; set; }
+ public string? ChannelId { get; set; }
/// <summary>
/// Gets or sets the name of the program.
/// </summary>
- public string Name { get; set; }
+ public string? Name { get; set; }
/// <summary>
/// Gets or sets the official rating.
/// </summary>
/// <value>The official rating.</value>
- public string OfficialRating { get; set; }
+ public string? OfficialRating { get; set; }
/// <summary>
/// Gets or sets the overview.
/// </summary>
/// <value>The overview.</value>
- public string Overview { get; set; }
+ public string? Overview { get; set; }
/// <summary>
/// Gets or sets the short overview.
/// </summary>
/// <value>The short overview.</value>
- public string ShortOverview { get; set; }
+ public string? ShortOverview { get; set; }
/// <summary>
/// Gets or sets the start date of the program, in UTC.
@@ -108,25 +106,25 @@ namespace MediaBrowser.Controller.LiveTv
/// Gets or sets the episode title.
/// </summary>
/// <value>The episode title.</value>
- public string EpisodeTitle { get; set; }
+ public string? EpisodeTitle { get; set; }
/// <summary>
/// Gets or sets the image path if it can be accessed directly from the file system.
/// </summary>
/// <value>The image path.</value>
- public string ImagePath { get; set; }
+ public string? ImagePath { get; set; }
/// <summary>
/// Gets or sets the image url if it can be downloaded.
/// </summary>
/// <value>The image URL.</value>
- public string ImageUrl { get; set; }
+ public string? ImageUrl { get; set; }
- public string ThumbImageUrl { get; set; }
+ public string? ThumbImageUrl { get; set; }
- public string LogoImageUrl { get; set; }
+ public string? LogoImageUrl { get; set; }
- public string BackdropImageUrl { get; set; }
+ public string? BackdropImageUrl { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance has image.
@@ -188,19 +186,19 @@ namespace MediaBrowser.Controller.LiveTv
/// Gets or sets the home page URL.
/// </summary>
/// <value>The home page URL.</value>
- public string HomePageUrl { get; set; }
+ public string? HomePageUrl { get; set; }
/// <summary>
/// Gets or sets the series identifier.
/// </summary>
/// <value>The series identifier.</value>
- public string SeriesId { get; set; }
+ public string? SeriesId { get; set; }
/// <summary>
/// Gets or sets the show identifier.
/// </summary>
/// <value>The show identifier.</value>
- public string ShowId { get; set; }
+ public string? ShowId { get; set; }
/// <summary>
/// Gets or sets the season number.
@@ -218,10 +216,10 @@ namespace MediaBrowser.Controller.LiveTv
/// Gets or sets the etag.
/// </summary>
/// <value>The etag.</value>
- public string Etag { get; set; }
+ public string? Etag { get; set; }
- public Dictionary<string, string> ProviderIds { get; set; }
+ public Dictionary<string, string?> ProviderIds { get; set; }
- public Dictionary<string, string> SeriesProviderIds { get; set; }
+ public Dictionary<string, string?> SeriesProviderIds { get; set; }
}
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 73c5b88c8b..770965cab3 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -1331,8 +1331,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
public bool CanExtractSubtitles(string codec)
{
- // TODO is there ever a case when a subtitle can't be extracted??
- return true;
+ return _configurationManager.GetEncodingOptions().EnableSubtitleExtraction;
}
private sealed class ProcessWrapper : IDisposable
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index d3e7b52315..3c6a03713f 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -729,6 +729,9 @@ namespace MediaBrowser.MediaEncoding.Probing
stream.Type = MediaStreamType.Audio;
stream.LocalizedDefault = _localization.GetLocalizedString("Default");
stream.LocalizedExternal = _localization.GetLocalizedString("External");
+ stream.LocalizedLanguage = string.IsNullOrEmpty(stream.Language)
+ ? null
+ : _localization.FindLanguageInfo(stream.Language)?.DisplayName;
stream.Channels = streamInfo.Channels;
@@ -767,6 +770,9 @@ namespace MediaBrowser.MediaEncoding.Probing
stream.LocalizedForced = _localization.GetLocalizedString("Forced");
stream.LocalizedExternal = _localization.GetLocalizedString("External");
stream.LocalizedHearingImpaired = _localization.GetLocalizedString("HearingImpaired");
+ stream.LocalizedLanguage = string.IsNullOrEmpty(stream.Language)
+ ? null
+ : _localization.FindLanguageInfo(stream.Language)?.DisplayName;
if (string.IsNullOrEmpty(stream.Title))
{
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index 9aeac7221e..5920fe3289 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -101,11 +101,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
return ms;
}
- private void FilterEvents(SubtitleTrackInfo track, long startPositionTicks, long endTimeTicks, bool preserveTimestamps)
+ internal void FilterEvents(SubtitleTrackInfo track, long startPositionTicks, long endTimeTicks, bool preserveTimestamps)
{
- // Drop subs that are earlier than what we're looking for
+ // Drop subs that have fully elapsed before the requested start position
track.TrackEvents = track.TrackEvents
- .SkipWhile(i => (i.StartPositionTicks - startPositionTicks) < 0 || (i.EndPositionTicks - startPositionTicks) < 0)
+ .SkipWhile(i => (i.StartPositionTicks - startPositionTicks) < 0 && (i.EndPositionTicks - startPositionTicks) < 0)
.ToArray();
if (endTimeTicks > 0)
@@ -119,8 +119,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
foreach (var trackEvent in track.TrackEvents)
{
- trackEvent.EndPositionTicks -= startPositionTicks;
- trackEvent.StartPositionTicks -= startPositionTicks;
+ trackEvent.EndPositionTicks = Math.Max(0, trackEvent.EndPositionTicks - startPositionTicks);
+ trackEvent.StartPositionTicks = Math.Max(0, trackEvent.StartPositionTicks - startPositionTicks);
}
}
}
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index 75b8c137f7..c9697c685c 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -1555,7 +1555,7 @@ namespace MediaBrowser.Model.Dlna
continue;
}
- if (!subtitleStream.IsExternal && !transcoderSupport.CanExtractSubtitles(subtitleStream.Codec))
+ if (!subtitleStream.IsExternal && playMethod == PlayMethod.Transcode && !transcoderSupport.CanExtractSubtitles(subtitleStream.Codec))
{
continue;
}
diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs
index 11f81ff7d8..4491fb5ace 100644
--- a/MediaBrowser.Model/Entities/MediaStream.cs
+++ b/MediaBrowser.Model/Entities/MediaStream.cs
@@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
-using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using Jellyfin.Data.Enums;
diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs
index 79f8675cbf..c0d1bc86e7 100644
--- a/MediaBrowser.Model/Net/MimeTypes.cs
+++ b/MediaBrowser.Model/Net/MimeTypes.cs
@@ -132,6 +132,7 @@ namespace MediaBrowser.Model.Net
// Type image
new("image/jpeg", ".jpg"),
+ new("image/jpg", ".jpg"),
new("image/tiff", ".tiff"),
new("image/x-png", ".png"),
new("image/x-icon", ".ico"),
diff --git a/MediaBrowser.Model/Providers/SubtitleOptions.cs b/MediaBrowser.Model/Providers/SubtitleOptions.cs
deleted file mode 100644
index 6ea1e14862..0000000000
--- a/MediaBrowser.Model/Providers/SubtitleOptions.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-#nullable disable
-#pragma warning disable CS1591
-
-using System;
-
-namespace MediaBrowser.Model.Providers
-{
- public class SubtitleOptions
- {
- public SubtitleOptions()
- {
- DownloadLanguages = Array.Empty<string>();
-
- SkipIfAudioTrackMatches = true;
- RequirePerfectMatch = true;
- }
-
- public bool SkipIfEmbeddedSubtitlesPresent { get; set; }
-
- public bool SkipIfAudioTrackMatches { get; set; }
-
- public string[] DownloadLanguages { get; set; }
-
- public bool DownloadMovieSubtitles { get; set; }
-
- public bool DownloadEpisodeSubtitles { get; set; }
-
- public string OpenSubtitlesUsername { get; set; }
-
- public string OpenSubtitlesPasswordHash { get; set; }
-
- public bool IsOpenSubtitleVipAccount { get; set; }
-
- public bool RequirePerfectMatch { get; set; }
- }
-}
diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
index e0354dbdfa..727f481b65 100644
--- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs
+++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
@@ -255,7 +255,7 @@ namespace MediaBrowser.Providers.Manager
catch (Exception ex)
{
result.ErrorMessage = ex.Message;
- _logger.LogError(ex, "Error in {Provider}", provider.Name);
+ _logger.LogError(ex, "Error in {Provider} for {Item}", provider.Name, item.Path ?? item.Name);
}
}
@@ -339,7 +339,7 @@ namespace MediaBrowser.Providers.Manager
catch (Exception ex)
{
result.ErrorMessage = ex.Message;
- _logger.LogError(ex, "Error in {Provider}", provider.Name);
+ _logger.LogError(ex, "Error in {Provider} for {Item}", provider.Name, item.Path ?? item.Name);
}
}
diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs
index e9cb46eab5..abdfb1e3b7 100644
--- a/MediaBrowser.Providers/Manager/MetadataService.cs
+++ b/MediaBrowser.Providers/Manager/MetadataService.cs
@@ -820,7 +820,7 @@ namespace MediaBrowser.Providers.Manager
}
catch (Exception ex)
{
- Logger.LogError(ex, "Error in {Provider}", provider.Name);
+ Logger.LogError(ex, "Error in {Provider} for {Item}", provider.Name, logName);
// If a local provider fails, consider that a failure
refreshResult.ErrorMessage = ex.Message;
@@ -886,7 +886,7 @@ namespace MediaBrowser.Providers.Manager
catch (Exception ex)
{
refreshResult.ErrorMessage = ex.Message;
- Logger.LogError(ex, "Error in {Provider}", provider.Name);
+ Logger.LogError(ex, "Error in {Provider} for {Item}", provider.Name, logName);
}
}
@@ -935,7 +935,7 @@ namespace MediaBrowser.Providers.Manager
{
refreshResult.Failures++;
refreshResult.ErrorMessage = ex.Message;
- Logger.LogError(ex, "Error in {Provider}", provider.Name);
+ Logger.LogError(ex, "Error in {Provider} for {Item}", provider.Name, logName);
}
}
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index f8e2aece1f..0bab73180f 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -487,6 +487,13 @@ namespace MediaBrowser.Providers.Manager
return true;
}
+ // Artists without a folder structure that are derived from metadata have no real path in the library,
+ // so GetLibraryOptions returns null. Allow all providers through rather than blocking them.
+ if (item is MusicArtist && libraryTypeOptions is null)
+ {
+ return true;
+ }
+
return _baseItemManager.IsMetadataFetcherEnabled(item, libraryTypeOptions, provider.Name);
}
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
index bde23e842f..fdc2f36469 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
@@ -8,7 +8,6 @@ using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
-using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Chapters;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
@@ -25,7 +24,6 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Providers;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.MediaInfo
@@ -74,7 +72,6 @@ namespace MediaBrowser.Providers.MediaInfo
_subtitleResolver = subtitleResolver;
_mediaAttachmentRepository = mediaAttachmentRepository;
_mediaStreamRepository = mediaStreamRepository;
- _mediaStreamRepository = mediaStreamRepository;
}
public async Task<ItemUpdateType> ProbeVideo<T>(
@@ -366,6 +363,8 @@ namespace MediaBrowser.Providers.MediaInfo
blurayVideoStream.ColorSpace = ffmpegVideoStream.ColorSpace;
blurayVideoStream.ColorTransfer = ffmpegVideoStream.ColorTransfer;
blurayVideoStream.ColorPrimaries = ffmpegVideoStream.ColorPrimaries;
+ blurayVideoStream.BitDepth = ffmpegVideoStream.BitDepth;
+ blurayVideoStream.PixelFormat = ffmpegVideoStream.PixelFormat;
}
}
@@ -549,47 +548,19 @@ namespace MediaBrowser.Providers.MediaInfo
var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.Default ||
options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh;
- var subtitleOptions = _config.GetConfiguration<SubtitleOptions>("subtitles");
-
var libraryOptions = _libraryManager.GetLibraryOptions(video);
- string[] subtitleDownloadLanguages;
- bool skipIfEmbeddedSubtitlesPresent;
- bool skipIfAudioTrackMatches;
- bool requirePerfectMatch;
- bool enabled;
-
- if (libraryOptions.SubtitleDownloadLanguages is null)
- {
- subtitleDownloadLanguages = subtitleOptions.DownloadLanguages;
- skipIfEmbeddedSubtitlesPresent = subtitleOptions.SkipIfEmbeddedSubtitlesPresent;
- skipIfAudioTrackMatches = subtitleOptions.SkipIfAudioTrackMatches;
- requirePerfectMatch = subtitleOptions.RequirePerfectMatch;
- enabled = (subtitleOptions.DownloadEpisodeSubtitles &&
- video is Episode) ||
- (subtitleOptions.DownloadMovieSubtitles &&
- video is Movie);
- }
- else
- {
- subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages;
- skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent;
- skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches;
- requirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch;
- enabled = true;
- }
-
- if (enableSubtitleDownloading && enabled)
+ if (enableSubtitleDownloading && libraryOptions.SubtitleDownloadLanguages is not null)
{
var downloadedLanguages = await new SubtitleDownloader(
_logger,
_subtitleManager).DownloadSubtitles(
video,
currentStreams.Concat(externalSubtitleStreams).ToList(),
- skipIfEmbeddedSubtitlesPresent,
- skipIfAudioTrackMatches,
- requirePerfectMatch,
- subtitleDownloadLanguages,
+ libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent,
+ libraryOptions.SkipSubtitlesIfAudioTrackMatches,
+ libraryOptions.RequirePerfectSubtitleMatch,
+ libraryOptions.SubtitleDownloadLanguages,
libraryOptions.DisabledSubtitleFetchers,
libraryOptions.SubtitleFetcherOrder,
true,
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
index 1134baf92d..f1582febf2 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
@@ -8,14 +8,13 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
-using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Subtitles;
using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
@@ -28,19 +27,24 @@ namespace MediaBrowser.Providers.MediaInfo
private readonly ISubtitleManager _subtitleManager;
private readonly ILogger<SubtitleScheduledTask> _logger;
private readonly ILocalizationManager _localization;
+ private readonly ISubtitleProvider[] _subtitleProviders;
public SubtitleScheduledTask(
ILibraryManager libraryManager,
IServerConfigurationManager config,
ISubtitleManager subtitleManager,
ILogger<SubtitleScheduledTask> logger,
- ILocalizationManager localization)
+ ILocalizationManager localization,
+ IEnumerable<ISubtitleProvider> subtitleProviders)
{
_libraryManager = libraryManager;
_config = config;
_subtitleManager = subtitleManager;
_logger = logger;
_localization = localization;
+ _subtitleProviders = subtitleProviders
+ .OrderBy(i => i is IHasOrder hasOrder ? hasOrder.Order : 0)
+ .ToArray();
}
public string Name => _localization.GetLocalizedString("TaskDownloadMissingSubtitles");
@@ -57,16 +61,9 @@ namespace MediaBrowser.Providers.MediaInfo
public bool IsLogged => true;
- private SubtitleOptions GetOptions()
- {
- return _config.GetConfiguration<SubtitleOptions>("subtitles");
- }
-
/// <inheritdoc />
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
{
- var options = GetOptions();
-
var types = new[] { BaseItemKind.Episode, BaseItemKind.Movie };
var dict = new Dictionary<Guid, BaseItem>();
@@ -81,17 +78,20 @@ namespace MediaBrowser.Providers.MediaInfo
if (libraryOptions.SubtitleDownloadLanguages is null)
{
- subtitleDownloadLanguages = options.DownloadLanguages;
- skipIfEmbeddedSubtitlesPresent = options.SkipIfEmbeddedSubtitlesPresent;
- skipIfAudioTrackMatches = options.SkipIfAudioTrackMatches;
+ // Skip this library if subtitle download languages are not configured
+ continue;
}
- else
+
+ if (_subtitleProviders.All(provider => libraryOptions.DisabledSubtitleFetchers.Contains(provider.Name, StringComparer.OrdinalIgnoreCase)))
{
- subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages;
- skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent;
- skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches;
+ // Skip this library if all subtitle providers are disabled
+ continue;
}
+ subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages;
+ skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent;
+ skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches;
+
foreach (var lang in subtitleDownloadLanguages)
{
var query = new InternalItemsQuery
@@ -144,7 +144,7 @@ namespace MediaBrowser.Providers.MediaInfo
try
{
- await DownloadSubtitles(video as Video, options, cancellationToken).ConfigureAwait(false);
+ await DownloadSubtitles(video as Video, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -160,7 +160,7 @@ namespace MediaBrowser.Providers.MediaInfo
}
}
- private async Task<bool> DownloadSubtitles(Video video, SubtitleOptions options, CancellationToken cancellationToken)
+ private async Task<bool> DownloadSubtitles(Video video, CancellationToken cancellationToken)
{
var mediaStreams = video.GetMediaStreams();
@@ -173,19 +173,15 @@ namespace MediaBrowser.Providers.MediaInfo
if (libraryOptions.SubtitleDownloadLanguages is null)
{
- subtitleDownloadLanguages = options.DownloadLanguages;
- skipIfEmbeddedSubtitlesPresent = options.SkipIfEmbeddedSubtitlesPresent;
- skipIfAudioTrackMatches = options.SkipIfAudioTrackMatches;
- requirePerfectMatch = options.RequirePerfectMatch;
- }
- else
- {
- subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages;
- skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent;
- skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches;
- requirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch;
+ // Subtitle downloading is not configured for this library
+ return true;
}
+ subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages;
+ skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent;
+ skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches;
+ requirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch;
+
var downloadedLanguages = await new SubtitleDownloader(
_logger,
_subtitleManager).DownloadSubtitles(
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs
index 00bd96282c..d8cb6b4b24 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs
@@ -125,7 +125,9 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
if (string.IsNullOrWhiteSpace(overview))
{
- overview = result.strBiographyEN;
+ overview = string.IsNullOrWhiteSpace(result.strBiographyEN)
+ ? result.strBiography
+ : result.strBiographyEN;
}
item.Overview = (overview ?? string.Empty).StripHtml();
@@ -224,6 +226,8 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
public string strTwitter { get; set; }
+ public string strBiography { get; set; }
+
public string strBiographyEN { get; set; }
public string strBiographyDE { get; set; }
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs
index 1323d2604a..9df21596c5 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs
@@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz;
/// <summary>
/// MusicBrainz artist provider.
/// </summary>
-public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, ArtistInfo>, IDisposable
+public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, ArtistInfo>, IDisposable, IHasOrder
{
private readonly ILogger<MusicBrainzArtistProvider> _logger;
private Query _musicBrainzQuery;
@@ -42,6 +42,10 @@ public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, Ar
/// <inheritdoc />
public string Name => "MusicBrainz";
+ /// <inheritdoc />
+ /// Runs first to populate the MusicBrainz artist ID used by downstream providers.
+ public int Order => 0;
+
private void ReloadConfig(object? sender, BasePluginConfiguration e)
{
var configuration = (PluginConfiguration)e;
diff --git a/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/SqliteDatabaseProvider.cs b/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/SqliteDatabaseProvider.cs
index da63df8e29..2b52abcb5b 100644
--- a/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/SqliteDatabaseProvider.cs
+++ b/src/Jellyfin.Database/Jellyfin.Database.Providers.Sqlite/SqliteDatabaseProvider.cs
@@ -61,7 +61,7 @@ public sealed class SqliteDatabaseProvider : IJellyfinDatabaseProvider
var customOptions = databaseConfiguration.CustomProviderOptions?.Options;
var sqliteConnectionBuilder = new SqliteConnectionStringBuilder();
- sqliteConnectionBuilder.DataSource = Path.Combine(_applicationPaths.DataPath, "jellyfin.db");
+ sqliteConnectionBuilder.DataSource = GetOption(customOptions, "path", e => e, () => Path.Combine(_applicationPaths.DataPath, "jellyfin.db"));
sqliteConnectionBuilder.Cache = GetOption(customOptions, "cache", Enum.Parse<SqliteCacheMode>, () => SqliteCacheMode.Default);
sqliteConnectionBuilder.Pooling = GetOption(customOptions, "pooling", e => e.Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase), () => true);
sqliteConnectionBuilder.DefaultTimeout = GetOption(customOptions, "command-timeout", int.Parse, () => 30);
diff --git a/src/Jellyfin.LiveTv/Listings/XmlTvListingsProvider.cs b/src/Jellyfin.LiveTv/Listings/XmlTvListingsProvider.cs
index 7938b7a6e4..318c3a2d36 100644
--- a/src/Jellyfin.LiveTv/Listings/XmlTvListingsProvider.cs
+++ b/src/Jellyfin.LiveTv/Listings/XmlTvListingsProvider.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using System;
@@ -62,21 +60,21 @@ namespace Jellyfin.LiveTv.Listings
_logger.LogInformation("xmltv path: {Path}", info.Path);
string cacheFilename = info.Id + ".xml";
- string cacheFile = Path.Combine(_config.ApplicationPaths.CachePath, "xmltv", cacheFilename);
-
- if (File.Exists(cacheFile) && File.GetLastWriteTimeUtc(cacheFile) >= DateTime.UtcNow.Subtract(_maxCacheAge))
- {
- return cacheFile;
- }
+ string cacheDir = Path.Join(_config.ApplicationPaths.CachePath, "xmltv");
+ string cacheFile = Path.Join(cacheDir, cacheFilename);
- // Must check if file exists as parent directory may not exist.
if (File.Exists(cacheFile))
{
+ if (File.GetLastWriteTimeUtc(cacheFile) >= DateTime.UtcNow.Subtract(_maxCacheAge))
+ {
+ return cacheFile;
+ }
+
File.Delete(cacheFile);
}
else
{
- Directory.CreateDirectory(Path.GetDirectoryName(cacheFile));
+ Directory.CreateDirectory(cacheDir);
}
if (info.Path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
@@ -154,33 +152,37 @@ namespace Jellyfin.LiveTv.Listings
private static ProgramInfo GetProgramInfo(XmlTvProgram program, ListingsProviderInfo info)
{
- string episodeTitle = program.Episode.Title;
+ string? episodeTitle = program.Episode?.Title;
var programCategories = program.Categories.Where(c => !string.IsNullOrWhiteSpace(c)).ToList();
+ var imageUrl = program.Icons.FirstOrDefault()?.Source;
+ var rating = program.Ratings.FirstOrDefault()?.Value;
+ var starRating = program.StarRatings?.FirstOrDefault()?.StarRating;
var programInfo = new ProgramInfo
{
ChannelId = program.ChannelId,
EndDate = program.EndDate.UtcDateTime,
- EpisodeNumber = program.Episode.Episode,
+ EpisodeNumber = program.Episode?.Episode,
EpisodeTitle = episodeTitle,
Genres = programCategories,
StartDate = program.StartDate.UtcDateTime,
Name = program.Title,
Overview = program.Description,
ProductionYear = program.CopyrightDate?.Year,
- SeasonNumber = program.Episode.Series,
- IsSeries = program.Episode.Episode is not null,
+ SeasonNumber = program.Episode?.Series,
+ IsSeries = program.Episode?.Episode is not null,
IsRepeat = program.IsPreviouslyShown && !program.IsNew,
IsPremiere = program.Premiere is not null,
+ IsLive = program.IsLive,
IsKids = programCategories.Any(c => info.KidsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
IsMovie = programCategories.Any(c => info.MovieCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
IsNews = programCategories.Any(c => info.NewsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
IsSports = programCategories.Any(c => info.SportsCategories.Contains(c, StringComparison.OrdinalIgnoreCase)),
- ImageUrl = string.IsNullOrEmpty(program.Icon?.Source) ? null : program.Icon.Source,
- HasImage = !string.IsNullOrEmpty(program.Icon?.Source),
- OfficialRating = string.IsNullOrEmpty(program.Rating?.Value) ? null : program.Rating.Value,
- CommunityRating = program.StarRating,
- SeriesId = program.Episode.Episode is null ? null : program.Title?.GetMD5().ToString("N", CultureInfo.InvariantCulture)
+ ImageUrl = string.IsNullOrEmpty(imageUrl) ? null : imageUrl,
+ HasImage = !string.IsNullOrEmpty(imageUrl),
+ OfficialRating = string.IsNullOrEmpty(rating) ? null : rating,
+ CommunityRating = starRating is null ? null : (float)starRating.Value,
+ SeriesId = program.Episode?.Episode is null ? null : program.Title?.GetMD5().ToString("N", CultureInfo.InvariantCulture)
};
if (string.IsNullOrWhiteSpace(program.ProgramId))
@@ -261,7 +263,7 @@ namespace Jellyfin.LiveTv.Listings
{
Id = c.Id,
Name = c.DisplayName,
- ImageUrl = string.IsNullOrEmpty(c.Icon?.Source) ? null : c.Icon.Source,
+ ImageUrl = string.IsNullOrEmpty(c.Icons.FirstOrDefault()?.Source) ? null : c.Icons.FirstOrDefault()!.Source,
Number = string.IsNullOrWhiteSpace(c.Number) ? c.Id : c.Number
}).ToList();
}
diff --git a/tests/Jellyfin.Api.Tests/Controllers/UserControllerTests.cs b/tests/Jellyfin.Api.Tests/Controllers/UserControllerTests.cs
index e95df16354..60ed740609 100644
--- a/tests/Jellyfin.Api.Tests/Controllers/UserControllerTests.cs
+++ b/tests/Jellyfin.Api.Tests/Controllers/UserControllerTests.cs
@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
-using AutoFixture.Xunit2;
+using AutoFixture.Xunit3;
using Jellyfin.Api.Controllers;
using Jellyfin.Database.Implementations.Entities;
using MediaBrowser.Common.Net;
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
index 6b84c4438f..253eab9d79 100644
--- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
+++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
@@ -3,15 +3,16 @@
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
<PropertyGroup>
<ProjectGuid>{A2FD0A10-8F62-4F9D-B171-FFDF9F0AFA9D}</ProjectGuid>
+ <OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoFixture" />
<PackageReference Include="AutoFixture.AutoMoq" />
- <PackageReference Include="AutoFixture.Xunit2" />
+ <PackageReference Include="AutoFixture.Xunit3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
- <PackageReference Include="xunit" />
+ <PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
index 8fef7fde05..f01d522e11 100644
--- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
+++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
@@ -3,17 +3,18 @@
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
<PropertyGroup>
<ProjectGuid>{DF194677-DFD3-42AF-9F75-D44D5A416478}</ProjectGuid>
+ <OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
- <PackageReference Include="xunit" />
+ <PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" />
- <PackageReference Include="FsCheck.Xunit" />
+ <PackageReference Include="FsCheck.Xunit.v3" />
</ItemGroup>
<ItemGroup>
diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
index 54d93b48cf..7db94f9c81 100644
--- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
+++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
@@ -3,12 +3,13 @@
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
<PropertyGroup>
<ProjectGuid>{462584F7-5023-4019-9EAC-B98CA458C0A0}</ProjectGuid>
+ <OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />
- <PackageReference Include="xunit" />
+ <PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
diff --git a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj
index 0364898298..6921fc8a97 100644
--- a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj
+++ b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj
@@ -1,8 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ </PropertyGroup>
+
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
- <PackageReference Include="xunit" />
+ <PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
@@ -11,7 +15,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
- <PackageReference Include="FsCheck.Xunit" />
+ <PackageReference Include="FsCheck.Xunit.v3" />
</ItemGroup>
<ItemGroup>
diff --git a/tests/Jellyfin.LiveTv.Tests/Jellyfin.LiveTv.Tests.csproj b/tests/Jellyfin.LiveTv.Tests/Jellyfin.LiveTv.Tests.csproj
index bdf6bc383a..a9b19e0104 100644
--- a/tests/Jellyfin.LiveTv.Tests/Jellyfin.LiveTv.Tests.csproj
+++ b/tests/Jellyfin.LiveTv.Tests/Jellyfin.LiveTv.Tests.csproj
@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
+ <OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
@@ -14,12 +15,11 @@
<PackageReference Include="AutoFixture.AutoMoq" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />
- <PackageReference Include="xunit" />
+ <PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="Xunit.SkippableFact" />
<PackageReference Include="coverlet.collector" />
</ItemGroup>
diff --git a/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj b/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj
index eab003715c..47a116ee42 100644
--- a/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Hls.Tests/Jellyfin.MediaEncoding.Hls.Tests.csproj
@@ -1,8 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ </PropertyGroup>
+
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
- <PackageReference Include="xunit" />
+ <PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
diff --git a/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj
index 894bec6aa5..9a58c697f0 100644
--- a/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Keyframes.Tests/Jellyfin.MediaEncoding.Keyframes.Tests.csproj
@@ -1,8 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ </PropertyGroup>
+
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
- <PackageReference Include="xunit" />
+ <PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
index 6b703e7416..c7065c670a 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
@@ -3,6 +3,7 @@
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
<PropertyGroup>
<ProjectGuid>{28464062-0939-4AA7-9F7B-24DDDA61A7C0}</ProjectGuid>
+ <OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
@@ -14,11 +15,11 @@
<ItemGroup>
<PackageReference Include="AutoFixture" />
<PackageReference Include="AutoFixture.AutoMoq" />
- <PackageReference Include="AutoFixture.Xunit2" />
+ <PackageReference Include="AutoFixture.Xunit3" />
<PackageReference Include="coverlet.collector" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />
- <PackageReference Include="xunit" />
+ <PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Subtitles/FilterEventsTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/FilterEventsTests.cs
new file mode 100644
index 0000000000..5f84e85592
--- /dev/null
+++ b/tests/Jellyfin.MediaEncoding.Tests/Subtitles/FilterEventsTests.cs
@@ -0,0 +1,282 @@
+using System;
+using AutoFixture;
+using AutoFixture.AutoMoq;
+using MediaBrowser.MediaEncoding.Subtitles;
+using MediaBrowser.Model.MediaInfo;
+using Xunit;
+
+namespace Jellyfin.MediaEncoding.Subtitles.Tests
+{
+ public class FilterEventsTests
+ {
+ private readonly SubtitleEncoder _encoder;
+
+ public FilterEventsTests()
+ {
+ var fixture = new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true });
+ _encoder = fixture.Create<SubtitleEncoder>();
+ }
+
+ [Fact]
+ public void FilterEvents_SubtitleSpanningSegmentBoundary_IsRetained()
+ {
+ // Subtitle starts at 5s, ends at 15s.
+ // Segment requested from 10s to 20s.
+ // The subtitle is still on screen at 10s and should NOT be dropped.
+ var track = new SubtitleTrackInfo
+ {
+ TrackEvents = new[]
+ {
+ new SubtitleTrackEvent("1", "Still on screen")
+ {
+ StartPositionTicks = TimeSpan.FromSeconds(5).Ticks,
+ EndPositionTicks = TimeSpan.FromSeconds(15).Ticks
+ },
+ new SubtitleTrackEvent("2", "Next subtitle")
+ {
+ StartPositionTicks = TimeSpan.FromSeconds(12).Ticks,
+ EndPositionTicks = TimeSpan.FromSeconds(17).Ticks
+ }
+ }
+ };
+
+ _encoder.FilterEvents(
+ track,
+ startPositionTicks: TimeSpan.FromSeconds(10).Ticks,
+ endTimeTicks: TimeSpan.FromSeconds(20).Ticks,
+ preserveTimestamps: true);
+
+ Assert.Equal(2, track.TrackEvents.Count);
+ Assert.Equal("1", track.TrackEvents[0].Id);
+ Assert.Equal("2", track.TrackEvents[1].Id);
+ }
+
+ [Fact]
+ public void FilterEvents_SubtitleFullyBeforeSegment_IsDropped()
+ {
+ // Subtitle starts at 2s, ends at 5s.
+ // Segment requested from 10s.
+ // The subtitle ended before the segment — should be dropped.
+ var track = new SubtitleTrackInfo
+ {
+ TrackEvents = new[]
+ {
+ new SubtitleTrackEvent("1", "Already gone")
+ {
+ StartPositionTicks = TimeSpan.FromSeconds(2).Ticks,
+ EndPositionTicks = TimeSpan.FromSeconds(5).Ticks
+ },
+ new SubtitleTrackEvent("2", "Visible")
+ {
+ StartPositionTicks = TimeSpan.FromSeconds(12).Ticks,
+ EndPositionTicks = TimeSpan.FromSeconds(17).Ticks
+ }
+ }
+ };
+
+ _encoder.FilterEvents(
+ track,
+ startPositionTicks: TimeSpan.FromSeconds(10).Ticks,
+ endTimeTicks: TimeSpan.FromSeconds(20).Ticks,
+ preserveTimestamps: true);
+
+ Assert.Single(track.TrackEvents);
+ Assert.Equal("2", track.TrackEvents[0].Id);
+ }
+
+ [Fact]
+ public void FilterEvents_SubtitleAfterSegment_IsDropped()
+ {
+ // Segment is 10s-20s, subtitle starts at 25s.
+ var track = new SubtitleTrackInfo
+ {
+ TrackEvents = new[]
+ {
+ new SubtitleTrackEvent("1", "In range")
+ {
+ StartPositionTicks = TimeSpan.FromSeconds(12).Ticks,
+ EndPositionTicks = TimeSpan.FromSeconds(15).Ticks
+ },
+ new SubtitleTrackEvent("2", "After segment")
+ {
+ StartPositionTicks = TimeSpan.FromSeconds(25).Ticks,
+ EndPositionTicks = TimeSpan.FromSeconds(30).Ticks
+ }
+ }
+ };
+
+ _encoder.FilterEvents(
+ track,
+ startPositionTicks: TimeSpan.FromSeconds(10).Ticks,
+ endTimeTicks: TimeSpan.FromSeconds(20).Ticks,
+ preserveTimestamps: true);
+
+ Assert.Single(track.TrackEvents);
+ Assert.Equal("1", track.TrackEvents[0].Id);
+ }
+
+ [Fact]
+ public void FilterEvents_PreserveTimestampsFalse_AdjustsTimestamps()
+ {
+ var track = new SubtitleTrackInfo
+ {
+ TrackEvents = new[]
+ {
+ new SubtitleTrackEvent("1", "Subtitle")
+ {
+ StartPositionTicks = TimeSpan.FromSeconds(15).Ticks,
+ EndPositionTicks = TimeSpan.FromSeconds(20).Ticks
+ }
+ }
+ };
+
+ _encoder.FilterEvents(
+ track,
+ startPositionTicks: TimeSpan.FromSeconds(10).Ticks,
+ endTimeTicks: TimeSpan.FromSeconds(30).Ticks,
+ preserveTimestamps: false);
+
+ Assert.Single(track.TrackEvents);
+ // Timestamps should be shifted back by 10s
+ Assert.Equal(TimeSpan.FromSeconds(5).Ticks, track.TrackEvents[0].StartPositionTicks);
+ Assert.Equal(TimeSpan.FromSeconds(10).Ticks, track.TrackEvents[0].EndPositionTicks);
+ }
+
+ [Fact]
+ public void FilterEvents_PreserveTimestampsTrue_KeepsOriginalTimestamps()
+ {
+ var startTicks = TimeSpan.FromSeconds(15).Ticks;
+ var endTicks = TimeSpan.FromSeconds(20).Ticks;
+
+ var track = new SubtitleTrackInfo
+ {
+ TrackEvents = new[]
+ {
+ new SubtitleTrackEvent("1", "Subtitle")
+ {
+ StartPositionTicks = startTicks,
+ EndPositionTicks = endTicks
+ }
+ }
+ };
+
+ _encoder.FilterEvents(
+ track,
+ startPositionTicks: TimeSpan.FromSeconds(10).Ticks,
+ endTimeTicks: TimeSpan.FromSeconds(30).Ticks,
+ preserveTimestamps: true);
+
+ Assert.Single(track.TrackEvents);
+ Assert.Equal(startTicks, track.TrackEvents[0].StartPositionTicks);
+ Assert.Equal(endTicks, track.TrackEvents[0].EndPositionTicks);
+ }
+
+ [Fact]
+ public void FilterEvents_SubtitleEndingExactlyAtSegmentStart_IsRetained()
+ {
+ // Subtitle ends exactly when the segment begins.
+ // EndPositionTicks == startPositionTicks means (end - start) == 0, not < 0,
+ // so SkipWhile stops and the subtitle is retained.
+ var track = new SubtitleTrackInfo
+ {
+ TrackEvents = new[]
+ {
+ new SubtitleTrackEvent("1", "Boundary subtitle")
+ {
+ StartPositionTicks = TimeSpan.FromSeconds(5).Ticks,
+ EndPositionTicks = TimeSpan.FromSeconds(10).Ticks
+ },
+ new SubtitleTrackEvent("2", "In range")
+ {
+ StartPositionTicks = TimeSpan.FromSeconds(12).Ticks,
+ EndPositionTicks = TimeSpan.FromSeconds(15).Ticks
+ }
+ }
+ };
+
+ _encoder.FilterEvents(
+ track,
+ startPositionTicks: TimeSpan.FromSeconds(10).Ticks,
+ endTimeTicks: TimeSpan.FromSeconds(20).Ticks,
+ preserveTimestamps: true);
+
+ Assert.Equal(2, track.TrackEvents.Count);
+ Assert.Equal("1", track.TrackEvents[0].Id);
+ }
+
+ [Fact]
+ public void FilterEvents_SpanningBoundaryWithTimestampAdjustment_DoesNotProduceNegativeTimestamps()
+ {
+ // Subtitle starts at 5s, ends at 15s.
+ // Segment requested from 10s to 20s, preserveTimestamps = false.
+ // The subtitle spans the boundary and is retained, but shifting
+ // StartPositionTicks by -10s would produce -5s (negative).
+ var track = new SubtitleTrackInfo
+ {
+ TrackEvents = new[]
+ {
+ new SubtitleTrackEvent("1", "Spans boundary")
+ {
+ StartPositionTicks = TimeSpan.FromSeconds(5).Ticks,
+ EndPositionTicks = TimeSpan.FromSeconds(15).Ticks
+ },
+ new SubtitleTrackEvent("2", "Fully in range")
+ {
+ StartPositionTicks = TimeSpan.FromSeconds(12).Ticks,
+ EndPositionTicks = TimeSpan.FromSeconds(17).Ticks
+ }
+ }
+ };
+
+ _encoder.FilterEvents(
+ track,
+ startPositionTicks: TimeSpan.FromSeconds(10).Ticks,
+ endTimeTicks: TimeSpan.FromSeconds(20).Ticks,
+ preserveTimestamps: false);
+
+ Assert.Equal(2, track.TrackEvents.Count);
+ // Subtitle 1: start should be clamped to 0, not -5s
+ Assert.True(track.TrackEvents[0].StartPositionTicks >= 0, "StartPositionTicks must not be negative");
+ Assert.Equal(TimeSpan.FromSeconds(5).Ticks, track.TrackEvents[0].EndPositionTicks);
+ // Subtitle 2: normal shift (12s - 10s = 2s, 17s - 10s = 7s)
+ Assert.Equal(TimeSpan.FromSeconds(2).Ticks, track.TrackEvents[1].StartPositionTicks);
+ Assert.Equal(TimeSpan.FromSeconds(7).Ticks, track.TrackEvents[1].EndPositionTicks);
+ }
+
+ [Fact]
+ public void FilterEvents_NoEndTimeTicks_ReturnsAllFromStartPosition()
+ {
+ var track = new SubtitleTrackInfo
+ {
+ TrackEvents = new[]
+ {
+ new SubtitleTrackEvent("1", "Before")
+ {
+ StartPositionTicks = TimeSpan.FromSeconds(2).Ticks,
+ EndPositionTicks = TimeSpan.FromSeconds(4).Ticks
+ },
+ new SubtitleTrackEvent("2", "After")
+ {
+ StartPositionTicks = TimeSpan.FromSeconds(12).Ticks,
+ EndPositionTicks = TimeSpan.FromSeconds(15).Ticks
+ },
+ new SubtitleTrackEvent("3", "Much later")
+ {
+ StartPositionTicks = TimeSpan.FromSeconds(500).Ticks,
+ EndPositionTicks = TimeSpan.FromSeconds(505).Ticks
+ }
+ }
+ };
+
+ _encoder.FilterEvents(
+ track,
+ startPositionTicks: TimeSpan.FromSeconds(10).Ticks,
+ endTimeTicks: 0,
+ preserveTimestamps: true);
+
+ Assert.Equal(2, track.TrackEvents.Count);
+ Assert.Equal("2", track.TrackEvents[0].Id);
+ Assert.Equal("3", track.TrackEvents[1].Id);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs
index 2c1080ffe3..8269ae58cd 100644
--- a/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs
+++ b/tests/Jellyfin.Model.Tests/Dlna/StreamBuilderTests.cs
@@ -617,5 +617,60 @@ namespace Jellyfin.Model.Tests
return (path, query, filename, extension);
}
+
+ [Theory]
+ // EnableSubtitleExtraction = false, internal subtitles
+ [InlineData("srt", "srt", false, false, PlayMethod.Transcode, SubtitleDeliveryMethod.Encode)]
+ [InlineData("srt", "srt", false, false, PlayMethod.DirectPlay, SubtitleDeliveryMethod.External)]
+ [InlineData("pgssub", "pgssub", false, false, PlayMethod.Transcode, SubtitleDeliveryMethod.Encode)]
+ [InlineData("pgssub", "pgssub", false, false, PlayMethod.DirectPlay, SubtitleDeliveryMethod.External)]
+ [InlineData("pgssub", "srt", false, false, PlayMethod.Transcode, SubtitleDeliveryMethod.Encode)]
+ // EnableSubtitleExtraction = false, external subtitles
+ [InlineData("srt", "srt", false, true, PlayMethod.Transcode, SubtitleDeliveryMethod.External)]
+ // EnableSubtitleExtraction = true, internal subtitles
+ [InlineData("srt", "srt", true, false, PlayMethod.Transcode, SubtitleDeliveryMethod.External)]
+ [InlineData("pgssub", "pgssub", true, false, PlayMethod.Transcode, SubtitleDeliveryMethod.External)]
+ [InlineData("pgssub", "pgssub", true, false, PlayMethod.DirectPlay, SubtitleDeliveryMethod.External)]
+ [InlineData("pgssub", "srt", true, false, PlayMethod.Transcode, SubtitleDeliveryMethod.Encode)]
+ // EnableSubtitleExtraction = true, external subtitles
+ [InlineData("srt", "srt", true, true, PlayMethod.Transcode, SubtitleDeliveryMethod.External)]
+ public void GetSubtitleProfile_RespectsExtractionSetting(
+ string codec,
+ string profileFormat,
+ bool enableSubtitleExtraction,
+ bool isExternal,
+ PlayMethod playMethod,
+ SubtitleDeliveryMethod expectedMethod)
+ {
+ var mediaSource = new MediaSourceInfo();
+ var subtitleStream = new MediaStream
+ {
+ Type = MediaStreamType.Subtitle,
+ Index = 0,
+ IsExternal = isExternal,
+ Path = isExternal ? "/media/sub." + codec : null,
+ Codec = codec,
+ SupportsExternalStream = MediaStream.IsTextFormat(codec)
+ };
+
+ var subtitleProfiles = new[]
+ {
+ new SubtitleProfile { Format = profileFormat, Method = SubtitleDeliveryMethod.External }
+ };
+
+ var transcoderSupport = new Mock<ITranscoderSupport>();
+ transcoderSupport.Setup(t => t.CanExtractSubtitles(It.IsAny<string>())).Returns(enableSubtitleExtraction);
+
+ var result = StreamBuilder.GetSubtitleProfile(
+ mediaSource,
+ subtitleStream,
+ subtitleProfiles,
+ playMethod,
+ transcoderSupport.Object,
+ null,
+ null);
+
+ Assert.Equal(expectedMethod, result.Method);
+ }
}
}
diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
index 8345b610e5..9e2a9a8873 100644
--- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
+++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
@@ -1,15 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ </PropertyGroup>
+
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />
- <PackageReference Include="xunit" />
+ <PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" />
- <PackageReference Include="FsCheck.Xunit" />
+ <PackageReference Include="FsCheck.Xunit.v3" />
</ItemGroup>
<ItemGroup>
diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
index 7c26494487..1f3e42077f 100644
--- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
+++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
@@ -3,12 +3,13 @@
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
<PropertyGroup>
<ProjectGuid>{3998657B-1CCC-49DD-A19F-275DC8495F57}</ProjectGuid>
+ <OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />
- <PackageReference Include="xunit" />
+ <PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
diff --git a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
index 2d7f112109..09ba120a5e 100644
--- a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
+++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
@@ -3,17 +3,18 @@
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
<PropertyGroup>
<ProjectGuid>{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}</ProjectGuid>
+ <OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
- <PackageReference Include="xunit" />
+ <PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" />
- <PackageReference Include="FsCheck.Xunit" />
+ <PackageReference Include="FsCheck.Xunit.v3" />
<PackageReference Include="Moq" />
</ItemGroup>
diff --git a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj
index 1263043a51..990544b5a8 100644
--- a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj
+++ b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj
@@ -1,5 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ </PropertyGroup>
+
<ItemGroup>
<None Include="Test Data\**\*.*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@@ -9,10 +13,10 @@
<ItemGroup>
<PackageReference Include="AutoFixture" />
<PackageReference Include="AutoFixture.AutoMoq" />
- <PackageReference Include="AutoFixture.Xunit2" />
+ <PackageReference Include="AutoFixture.Xunit3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />
- <PackageReference Include="xunit" />
+ <PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
diff --git a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs
index 6997b51ac8..c06279af2d 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/IO/ManagedFileSystemTests.cs
@@ -25,12 +25,12 @@ public class ManagedFileSystemTests
public void MoveDirectory_SameFileSystem_Correct()
=> MoveDirectoryInternal();
- [SkippableFact]
+ [Fact]
public void MoveDirectory_DifferentFileSystem_Correct()
{
const string DestinationParent = "/dev/shm";
- Skip.IfNot(Directory.Exists(DestinationParent));
+ Assert.SkipUnless(Directory.Exists(DestinationParent), $"{DestinationParent} is not available");
MoveDirectoryInternal(DestinationParent);
}
@@ -57,7 +57,7 @@ public class ManagedFileSystemTests
Directory.Delete(destinationDir, true);
}
- [SkippableTheory]
+ [Theory]
[InlineData("/Volumes/Library/Sample/Music/Playlists/", "../Beethoven/Misc/Moonlight Sonata.mp3", "/Volumes/Library/Sample/Music/Beethoven/Misc/Moonlight Sonata.mp3")]
[InlineData("/Volumes/Library/Sample/Music/Playlists/", "../../Beethoven/Misc/Moonlight Sonata.mp3", "/Volumes/Library/Sample/Beethoven/Misc/Moonlight Sonata.mp3")]
[InlineData("/Volumes/Library/Sample/Music/Playlists/", "Beethoven/Misc/Moonlight Sonata.mp3", "/Volumes/Library/Sample/Music/Playlists/Beethoven/Misc/Moonlight Sonata.mp3")]
@@ -67,13 +67,13 @@ public class ManagedFileSystemTests
string filePath,
string expectedAbsolutePath)
{
- Skip.If(OperatingSystem.IsWindows());
+ Assert.SkipWhen(OperatingSystem.IsWindows(), "Unix-only test");
var generatedPath = _sut.MakeAbsolutePath(folderPath, filePath);
Assert.Equal(expectedAbsolutePath, generatedPath);
}
- [SkippableTheory]
+ [Theory]
[InlineData(@"C:\\Volumes\Library\Sample\Music\Playlists\", @"..\Beethoven\Misc\Moonlight Sonata.mp3", @"C:\Volumes\Library\Sample\Music\Beethoven\Misc\Moonlight Sonata.mp3")]
[InlineData(@"C:\\Volumes\Library\Sample\Music\Playlists\", @"..\..\Beethoven\Misc\Moonlight Sonata.mp3", @"C:\Volumes\Library\Sample\Beethoven\Misc\Moonlight Sonata.mp3")]
[InlineData(@"C:\\Volumes\Library\Sample\Music\Playlists\", @"Beethoven\Misc\Moonlight Sonata.mp3", @"C:\Volumes\Library\Sample\Music\Playlists\Beethoven\Misc\Moonlight Sonata.mp3")]
@@ -83,7 +83,7 @@ public class ManagedFileSystemTests
string filePath,
string expectedAbsolutePath)
{
- Skip.IfNot(OperatingSystem.IsWindows());
+ Assert.SkipUnless(OperatingSystem.IsWindows(), "Windows-only test");
var generatedPath = _sut.MakeAbsolutePath(folderPath, filePath);
@@ -100,10 +100,10 @@ public class ManagedFileSystemTests
Assert.Equal(expectedFileName, _sut.GetValidFilename(filename));
}
- [SkippableFact]
+ [Fact]
public void GetFileInfo_DanglingSymlink_ExistsFalse()
{
- Skip.If(OperatingSystem.IsWindows());
+ Assert.SkipWhen(OperatingSystem.IsWindows(), "Unix-only test");
string testFileDir = Path.Combine(Path.GetTempPath(), "jellyfin-test-data");
string testFileName = Path.Combine(testFileDir, Path.GetRandomFileName() + "-danglingsym.link");
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
index 4e2604e6e1..958ffb8b6e 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
+++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
@@ -3,6 +3,7 @@
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
<PropertyGroup>
<ProjectGuid>{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE}</ProjectGuid>
+ <OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
@@ -16,12 +17,11 @@
<PackageReference Include="AutoFixture.AutoMoq" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />
- <PackageReference Include="xunit" />
+ <PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="Xunit.SkippableFact" />
<PackageReference Include="coverlet.collector" />
</ItemGroup>
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs
index e60522bf78..5bcfc580ff 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Localization/LocalizationManagerTests.cs
@@ -22,7 +22,7 @@ namespace Jellyfin.Server.Implementations.Tests.Localization
});
var countries = localizationManager.GetCountries().ToList();
- Assert.Equal(139, countries.Count);
+ Assert.Equal(140, countries.Count);
var germany = countries.FirstOrDefault(x => x.Name.Equals("DE", StringComparison.Ordinal));
Assert.NotNull(germany);
@@ -41,7 +41,7 @@ namespace Jellyfin.Server.Implementations.Tests.Localization
await localizationManager.LoadAll();
var cultures = localizationManager.GetCultures().ToList();
- Assert.Equal(194, cultures.Count);
+ Assert.Equal(496, cultures.Count);
var germany = cultures.FirstOrDefault(x => x.TwoLetterISOLanguageName.Equals("de", StringComparison.Ordinal));
Assert.NotNull(germany);
@@ -99,6 +99,25 @@ namespace Jellyfin.Server.Implementations.Tests.Localization
Assert.Contains("ger", germany.ThreeLetterISOLanguageNames);
}
+ [Theory]
+ [InlineData("mul", "Multiple languages")]
+ [InlineData("und", "Undetermined")]
+ [InlineData("mis", "Uncoded languages")]
+ [InlineData("zxx", "No linguistic content; Not applicable")]
+ public async Task FindLanguageInfo_ISO6392Only_Success(string code, string expectedDisplayName)
+ {
+ var localizationManager = Setup(new ServerConfiguration
+ {
+ UICulture = "en-US"
+ });
+ await localizationManager.LoadAll();
+
+ var culture = localizationManager.FindLanguageInfo(code);
+ Assert.NotNull(culture);
+ Assert.Equal(expectedDisplayName, culture.DisplayName);
+ Assert.Equal(code, culture.ThreeLetterISOLanguageName);
+ }
+
[Fact]
public async Task GetParentalRatings_Default_Success()
{
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Plugins/PluginManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Plugins/PluginManagerTests.cs
index 3d8ea15a31..ede9e61536 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Plugins/PluginManagerTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Plugins/PluginManagerTests.cs
@@ -192,13 +192,13 @@ namespace Jellyfin.Server.Implementations.Tests.Plugins
};
var metafilePath = Path.Combine(_pluginPath, "meta.json");
- await File.WriteAllTextAsync(metafilePath, JsonSerializer.Serialize(partial, _options));
+ await File.WriteAllTextAsync(metafilePath, JsonSerializer.Serialize(partial, _options), TestContext.Current.CancellationToken);
var pluginManager = new PluginManager(new NullLogger<PluginManager>(), null!, null!, _tempPath, new Version(1, 0));
await pluginManager.PopulateManifest(packageInfo, new Version(1, 0), _pluginPath, PluginStatus.Active);
- var resultBytes = await File.ReadAllBytesAsync(metafilePath);
+ var resultBytes = await File.ReadAllBytesAsync(metafilePath, TestContext.Current.CancellationToken);
var result = JsonSerializer.Deserialize<PluginManifest>(resultBytes, _options);
Assert.NotNull(result);
@@ -232,7 +232,7 @@ namespace Jellyfin.Server.Implementations.Tests.Plugins
await pluginManager.PopulateManifest(packageInfo, new Version(1, 0), _pluginPath, PluginStatus.Active);
var metafilePath = Path.Combine(_pluginPath, "meta.json");
- var resultBytes = await File.ReadAllBytesAsync(metafilePath);
+ var resultBytes = await File.ReadAllBytesAsync(metafilePath, TestContext.Current.CancellationToken);
var result = JsonSerializer.Deserialize<PluginManifest>(resultBytes, _options);
Assert.NotNull(result);
@@ -252,13 +252,13 @@ namespace Jellyfin.Server.Implementations.Tests.Plugins
};
var metafilePath = Path.Combine(_pluginPath, "meta.json");
- await File.WriteAllTextAsync(metafilePath, JsonSerializer.Serialize(partial, _options));
+ await File.WriteAllTextAsync(metafilePath, JsonSerializer.Serialize(partial, _options), TestContext.Current.CancellationToken);
var pluginManager = new PluginManager(new NullLogger<PluginManager>(), null!, null!, _tempPath, new Version(1, 0));
await pluginManager.PopulateManifest(packageInfo, new Version(1, 0), _pluginPath, PluginStatus.Active);
- var resultBytes = await File.ReadAllBytesAsync(metafilePath);
+ var resultBytes = await File.ReadAllBytesAsync(metafilePath, TestContext.Current.CancellationToken);
var result = JsonSerializer.Deserialize<PluginManifest>(resultBytes, _options);
Assert.NotNull(result);
@@ -278,13 +278,13 @@ namespace Jellyfin.Server.Implementations.Tests.Plugins
};
var metafilePath = Path.Combine(_pluginPath, "meta.json");
- await File.WriteAllTextAsync(metafilePath, JsonSerializer.Serialize(partial, _options));
+ await File.WriteAllTextAsync(metafilePath, JsonSerializer.Serialize(partial, _options), TestContext.Current.CancellationToken);
var pluginManager = new PluginManager(new NullLogger<PluginManager>(), null!, null!, _tempPath, new Version(1, 0));
await pluginManager.PopulateManifest(packageInfo, new Version(1, 0), _pluginPath, PluginStatus.Active);
- var resultBytes = await File.ReadAllBytesAsync(metafilePath);
+ var resultBytes = await File.ReadAllBytesAsync(metafilePath, TestContext.Current.CancellationToken);
var result = JsonSerializer.Deserialize<PluginManifest>(resultBytes, _options);
Assert.NotNull(result);
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs
index f58a3276ba..92e10c9f92 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Updates/InstallationManagerTests.cs
@@ -51,7 +51,8 @@ namespace Jellyfin.Server.Implementations.Tests.Updates
PackageInfo[] packages = await _installationManager.GetPackages(
"Jellyfin Stable",
"https://repo.jellyfin.org/files/plugin/manifest.json",
- false);
+ false,
+ TestContext.Current.CancellationToken);
Assert.Equal(25, packages.Length);
}
@@ -62,7 +63,8 @@ namespace Jellyfin.Server.Implementations.Tests.Updates
PackageInfo[] packages = await _installationManager.GetPackages(
"Jellyfin Stable",
"https://repo.jellyfin.org/files/plugin/manifest.json",
- false);
+ false,
+ TestContext.Current.CancellationToken);
packages = _installationManager.FilterPackages(packages, "Anime").ToArray();
Assert.Single(packages);
@@ -74,7 +76,8 @@ namespace Jellyfin.Server.Implementations.Tests.Updates
PackageInfo[] packages = await _installationManager.GetPackages(
"Jellyfin Stable",
"https://repo.jellyfin.org/files/plugin/manifest.json",
- false);
+ false,
+ TestContext.Current.CancellationToken);
packages = _installationManager.FilterPackages(packages, id: new Guid("a4df60c5-6ab4-412a-8f79-2cab93fb2bc5")).ToArray();
Assert.Single(packages);
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs
index 96ca96558d..ef084430e8 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/ActivityLogControllerTests.cs
@@ -21,7 +21,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- var response = await client.GetAsync("System/ActivityLog/Entries");
+ var response = await client.GetAsync("System/ActivityLog/Entries", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs
index 8761cf69bc..1973af3f25 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/BrandingControllerTests.cs
@@ -25,13 +25,13 @@ namespace Jellyfin.Server.Integration.Tests
var client = _factory.CreateClient();
// Act
- var response = await client.GetAsync("/Branding/Configuration");
+ var response = await client.GetAsync("/Branding/Configuration", TestContext.Current.CancellationToken);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet);
- await response.Content.ReadFromJsonAsync<BrandingOptions>();
+ await response.Content.ReadFromJsonAsync<BrandingOptions>(TestContext.Current.CancellationToken);
}
[Theory]
@@ -43,7 +43,7 @@ namespace Jellyfin.Server.Integration.Tests
var client = _factory.CreateClient();
// Act
- var response = await client.GetAsync(url);
+ var response = await client.GetAsync(url, TestContext.Current.CancellationToken);
// Assert
Assert.True(response.IsSuccessStatusCode);
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs
index d92dbbd732..32bdc57265 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/DashboardControllerTests.cs
@@ -27,7 +27,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
{
var client = _factory.CreateClient();
- var response = await client.GetAsync("web/ConfigurationPage?name=ThisPageDoesntExists");
+ var response = await client.GetAsync("web/ConfigurationPage?name=ThisPageDoesntExists", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
@@ -37,12 +37,12 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
{
var client = _factory.CreateClient();
- var response = await client.GetAsync("/web/ConfigurationPage?name=TestPlugin");
+ var response = await client.GetAsync("/web/ConfigurationPage?name=TestPlugin", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(MediaTypeNames.Text.Html, response.Content.Headers.ContentType?.MediaType);
StreamReader reader = new StreamReader(typeof(TestPlugin).Assembly.GetManifestResourceStream("Jellyfin.Server.Integration.Tests.TestPage.html")!);
- Assert.Equal(await response.Content.ReadAsStringAsync(), await reader.ReadToEndAsync());
+ Assert.Equal(await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken), await reader.ReadToEndAsync(TestContext.Current.CancellationToken));
}
[Fact]
@@ -50,7 +50,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
{
var client = _factory.CreateClient();
- var response = await client.GetAsync("/web/ConfigurationPage?name=BrokenPage");
+ var response = await client.GetAsync("/web/ConfigurationPage?name=BrokenPage", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
@@ -61,11 +61,11 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- var response = await client.GetAsync("/web/ConfigurationPages");
+ var response = await client.GetAsync("/web/ConfigurationPages", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- _ = await response.Content.ReadFromJsonAsync<ConfigurationPageInfo[]>(_jsonOptions);
+ _ = await response.Content.ReadFromJsonAsync<ConfigurationPageInfo[]>(_jsonOptions, TestContext.Current.CancellationToken);
// TODO: check content
}
@@ -75,13 +75,13 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- var response = await client.GetAsync("/web/ConfigurationPages?enableInMainMenu=true");
+ var response = await client.GetAsync("/web/ConfigurationPages?enableInMainMenu=true", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet);
- var data = await response.Content.ReadFromJsonAsync<ConfigurationPageInfo[]>(_jsonOptions);
+ var data = await response.Content.ReadFromJsonAsync<ConfigurationPageInfo[]>(_jsonOptions, TestContext.Current.CancellationToken);
Assert.NotNull(data);
Assert.Empty(data);
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs
index 64b9bd8e16..165e269814 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs
@@ -28,7 +28,7 @@ public sealed class ItemsControllerTests : IClassFixture<JellyfinApplicationFact
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- var response = await client.GetAsync("Items");
+ var response = await client.GetAsync("Items", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
@@ -40,7 +40,7 @@ public sealed class ItemsControllerTests : IClassFixture<JellyfinApplicationFact
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, Guid.NewGuid()));
+ var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, Guid.NewGuid()), TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
@@ -55,9 +55,9 @@ public sealed class ItemsControllerTests : IClassFixture<JellyfinApplicationFact
var userDto = await AuthHelper.GetUserDtoAsync(client);
- var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, userDto.Id));
+ var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, userDto.Id), TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var items = await response.Content.ReadFromJsonAsync<QueryResult<BaseItemDto>>(_jsonOptions);
+ var items = await response.Content.ReadFromJsonAsync<QueryResult<BaseItemDto>>(_jsonOptions, TestContext.Current.CancellationToken);
Assert.NotNull(items);
}
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryControllerTests.cs
index 6881a92101..edbb46b34c 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryControllerTests.cs
@@ -34,7 +34,7 @@ public sealed class LibraryControllerTests : IClassFixture<JellyfinApplicationFa
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, Guid.NewGuid()));
+ var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, Guid.NewGuid()), TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
@@ -45,7 +45,7 @@ public sealed class LibraryControllerTests : IClassFixture<JellyfinApplicationFa
{
var client = _factory.CreateClient();
- var response = await client.DeleteAsync(string.Format(CultureInfo.InvariantCulture, format, Guid.NewGuid()));
+ var response = await client.DeleteAsync(string.Format(CultureInfo.InvariantCulture, format, Guid.NewGuid()), TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}
@@ -57,7 +57,7 @@ public sealed class LibraryControllerTests : IClassFixture<JellyfinApplicationFa
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- var response = await client.DeleteAsync(string.Format(CultureInfo.InvariantCulture, format, Guid.NewGuid()));
+ var response = await client.DeleteAsync(string.Format(CultureInfo.InvariantCulture, format, Guid.NewGuid()), TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs
index 36f1b726da..2de6408cc6 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/LibraryStructureControllerTests.cs
@@ -9,11 +9,11 @@ using Jellyfin.Extensions.Json;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using Xunit;
-using Xunit.Priority;
+using Xunit.v3.Priority;
namespace Jellyfin.Server.Integration.Tests.Controllers;
-[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
+[TestCaseOrderer(typeof(PriorityOrderer))]
public sealed class LibraryStructureControllerTests : IClassFixture<JellyfinApplicationFactory>
{
private readonly JellyfinApplicationFactory _factory;
@@ -40,7 +40,7 @@ public sealed class LibraryStructureControllerTests : IClassFixture<JellyfinAppl
}
};
- using var response = await client.PostAsJsonAsync("Library/VirtualFolders?name=test&refreshLibrary=true", body, _jsonOptions);
+ using var response = await client.PostAsJsonAsync("Library/VirtualFolders?name=test&refreshLibrary=true", body, _jsonOptions, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
}
@@ -57,7 +57,7 @@ public sealed class LibraryStructureControllerTests : IClassFixture<JellyfinAppl
LibraryOptions = new LibraryOptions()
};
- using var response = await client.PostAsJsonAsync("Library/VirtualFolders/LibraryOptions", body, _jsonOptions);
+ using var response = await client.PostAsJsonAsync("Library/VirtualFolders/LibraryOptions", body, _jsonOptions, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
@@ -76,16 +76,16 @@ public sealed class LibraryStructureControllerTests : IClassFixture<JellyfinAppl
}
};
- using var createResponse = await client.PostAsJsonAsync("Library/VirtualFolders?name=test&refreshLibrary=true", createBody, _jsonOptions);
+ using var createResponse = await client.PostAsJsonAsync("Library/VirtualFolders?name=test&refreshLibrary=true", createBody, _jsonOptions, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NoContent, createResponse.StatusCode);
- await Task.Delay(2000).ConfigureAwait(true);
+ await Task.Delay(2000, TestContext.Current.CancellationToken).ConfigureAwait(true);
- using var response = await client.GetAsync("Library/VirtualFolders");
+ using var response = await client.GetAsync("Library/VirtualFolders", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var library = await response.Content.ReadFromJsonAsAsyncEnumerable<VirtualFolderInfo>(_jsonOptions)
- .FirstOrDefaultAsync(x => string.Equals(x?.Name, "test", StringComparison.Ordinal));
+ var library = await response.Content.ReadFromJsonAsAsyncEnumerable<VirtualFolderInfo>(_jsonOptions, TestContext.Current.CancellationToken)
+ .FirstOrDefaultAsync(x => string.Equals(x?.Name, "test", StringComparison.Ordinal), TestContext.Current.CancellationToken);
Assert.NotNull(library);
var options = library.LibraryOptions;
@@ -99,7 +99,7 @@ public sealed class LibraryStructureControllerTests : IClassFixture<JellyfinAppl
LibraryOptions = options
};
- using var response2 = await client.PostAsJsonAsync("Library/VirtualFolders/LibraryOptions", body, _jsonOptions);
+ using var response2 = await client.PostAsJsonAsync("Library/VirtualFolders/LibraryOptions", body, _jsonOptions, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NoContent, response2.StatusCode);
}
@@ -110,7 +110,7 @@ public sealed class LibraryStructureControllerTests : IClassFixture<JellyfinAppl
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- using var response = await client.DeleteAsync("Library/VirtualFolders?name=doesntExist");
+ using var response = await client.DeleteAsync("Library/VirtualFolders?name=doesntExist", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
@@ -121,7 +121,7 @@ public sealed class LibraryStructureControllerTests : IClassFixture<JellyfinAppl
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- using var response = await client.DeleteAsync("Library/VirtualFolders?name=test&refreshLibrary=true");
+ using var response = await client.DeleteAsync("Library/VirtualFolders?name=test&refreshLibrary=true", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
}
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/LiveTvControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/LiveTvControllerTests.cs
index dd971fa87b..8ca9fb899e 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/LiveTvControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/LiveTvControllerTests.cs
@@ -32,7 +32,7 @@ public sealed class LiveTvControllerTests : IClassFixture<JellyfinApplicationFac
Url = "Test Data/dummy.m3u8"
};
- var response = await client.PostAsJsonAsync("/LiveTv/TunerHosts", body, _jsonOptions);
+ var response = await client.PostAsJsonAsync("/LiveTv/TunerHosts", body, _jsonOptions, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}
@@ -49,12 +49,12 @@ public sealed class LiveTvControllerTests : IClassFixture<JellyfinApplicationFac
Url = "Test Data/dummy.m3u8"
};
- var response = await client.PostAsJsonAsync("/LiveTv/TunerHosts", body, _jsonOptions);
+ var response = await client.PostAsJsonAsync("/LiveTv/TunerHosts", body, _jsonOptions, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet);
- var responseBody = await response.Content.ReadFromJsonAsync<TunerHostInfo>();
+ var responseBody = await response.Content.ReadFromJsonAsync<TunerHostInfo>(TestContext.Current.CancellationToken);
Assert.NotNull(responseBody);
Assert.Equal(body.Type, responseBody.Type);
Assert.Equal(body.Url, responseBody.Url);
@@ -72,7 +72,7 @@ public sealed class LiveTvControllerTests : IClassFixture<JellyfinApplicationFac
Url = "Test Data/dummy.m3u8"
};
- var response = await client.PostAsJsonAsync("/LiveTv/TunerHosts", body, _jsonOptions);
+ var response = await client.PostAsJsonAsync("/LiveTv/TunerHosts", body, _jsonOptions, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
@@ -89,7 +89,7 @@ public sealed class LiveTvControllerTests : IClassFixture<JellyfinApplicationFac
Url = "thisgoesnowhere"
};
- var response = await client.PostAsJsonAsync("/LiveTv/TunerHosts", body, _jsonOptions);
+ var response = await client.PostAsJsonAsync("/LiveTv/TunerHosts", body, _jsonOptions, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaInfoControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaInfoControllerTests.cs
index abc8b60099..194566bbf0 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaInfoControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaInfoControllerTests.cs
@@ -22,7 +22,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- var response = await client.GetAsync("Playback/BitrateTest");
+ var response = await client.GetAsync("Playback/BitrateTest", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(MediaTypeNames.Application.Octet, response.Content.Headers.ContentType?.MediaType);
@@ -36,7 +36,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- var response = await client.GetAsync("Playback/BitrateTest?size=" + size.ToString(CultureInfo.InvariantCulture));
+ var response = await client.GetAsync("Playback/BitrateTest?size=" + size.ToString(CultureInfo.InvariantCulture), TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(MediaTypeNames.Application.Octet, response.Content.Headers.ContentType?.MediaType);
@@ -53,7 +53,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- var response = await client.GetAsync("Playback/BitrateTest?size=" + size.ToString(CultureInfo.InvariantCulture));
+ var response = await client.GetAsync("Playback/BitrateTest?size=" + size.ToString(CultureInfo.InvariantCulture), TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs
index 6699c68346..82fdf715f8 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/MediaStructureControllerTests.cs
@@ -29,7 +29,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
using var postContent = new ByteArrayContent(Array.Empty<byte>());
- var response = await client.PostAsync("Library/VirtualFolders/Name?name=+&newName=test", postContent);
+ var response = await client.PostAsync("Library/VirtualFolders/Name?name=+&newName=test", postContent, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
@@ -41,7 +41,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
using var postContent = new ByteArrayContent(Array.Empty<byte>());
- var response = await client.PostAsync("Library/VirtualFolders/Name?name=test&newName=+", postContent);
+ var response = await client.PostAsync("Library/VirtualFolders/Name?name=test&newName=+", postContent, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
@@ -53,7 +53,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
using var postContent = new ByteArrayContent(Array.Empty<byte>());
- var response = await client.PostAsync("Library/VirtualFolders/Name?name=doesnt+exist&newName=test", postContent);
+ var response = await client.PostAsync("Library/VirtualFolders/Name?name=doesnt+exist&newName=test", postContent, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
@@ -70,7 +70,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
Path = "/this/path/doesnt/exist"
};
- var response = await client.PostAsJsonAsync("Library/VirtualFolders/Paths", data, _jsonOptions);
+ var response = await client.PostAsJsonAsync("Library/VirtualFolders/Paths", data, _jsonOptions, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
@@ -87,7 +87,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
PathInfo = new MediaPathInfo("test")
};
- var response = await client.PostAsJsonAsync("Library/VirtualFolders/Paths/Update", data, _jsonOptions);
+ var response = await client.PostAsJsonAsync("Library/VirtualFolders/Paths/Update", data, _jsonOptions, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
@@ -98,7 +98,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- var response = await client.DeleteAsync("Library/VirtualFolders/Paths?name=+");
+ var response = await client.DeleteAsync("Library/VirtualFolders/Paths?name=+", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
@@ -109,7 +109,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- var response = await client.DeleteAsync("Library/VirtualFolders/Paths?name=none&path=%2Fthis%2Fpath%2Fdoesnt%2Fexist");
+ var response = await client.DeleteAsync("Library/VirtualFolders/Paths?name=none&path=%2Fthis%2Fpath%2Fdoesnt%2Fexist", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/MusicGenreControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/MusicGenreControllerTests.cs
index f9982cf12b..3e14850613 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/MusicGenreControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/MusicGenreControllerTests.cs
@@ -20,7 +20,7 @@ public sealed class MusicGenreControllerTests : IClassFixture<JellyfinApplicatio
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- var response = await client.GetAsync("MusicGenres/Fake-MusicGenre");
+ var response = await client.GetAsync("MusicGenres/Fake-MusicGenre", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/PersonsControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/PersonsControllerTests.cs
index c673773ffc..361edf3eb7 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/PersonsControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/PersonsControllerTests.cs
@@ -20,7 +20,7 @@ public class PersonsControllerTests : IClassFixture<JellyfinApplicationFactory>
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- using var response = await client.GetAsync($"Persons/DoesntExist");
+ using var response = await client.GetAsync($"Persons/DoesntExist", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/PlaystateControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/PlaystateControllerTests.cs
index 3b9ed17787..db271fc5cd 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/PlaystateControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/PlaystateControllerTests.cs
@@ -21,7 +21,7 @@ public class PlaystateControllerTests : IClassFixture<JellyfinApplicationFactory
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- using var response = await client.DeleteAsync($"Users/{Guid.NewGuid()}/PlayedItems/{Guid.NewGuid()}");
+ using var response = await client.DeleteAsync($"Users/{Guid.NewGuid()}/PlayedItems/{Guid.NewGuid()}", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
@@ -31,7 +31,7 @@ public class PlaystateControllerTests : IClassFixture<JellyfinApplicationFactory
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- using var response = await client.PostAsync($"Users/{Guid.NewGuid()}/PlayedItems/{Guid.NewGuid()}", null);
+ using var response = await client.PostAsync($"Users/{Guid.NewGuid()}/PlayedItems/{Guid.NewGuid()}", null, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
@@ -43,7 +43,7 @@ public class PlaystateControllerTests : IClassFixture<JellyfinApplicationFactory
var userDto = await AuthHelper.GetUserDtoAsync(client);
- using var response = await client.DeleteAsync($"Users/{userDto.Id}/PlayedItems/{Guid.NewGuid()}");
+ using var response = await client.DeleteAsync($"Users/{userDto.Id}/PlayedItems/{Guid.NewGuid()}", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
@@ -55,7 +55,7 @@ public class PlaystateControllerTests : IClassFixture<JellyfinApplicationFactory
var userDto = await AuthHelper.GetUserDtoAsync(client);
- using var response = await client.PostAsync($"Users/{userDto.Id}/PlayedItems/{Guid.NewGuid()}", null);
+ using var response = await client.PostAsync($"Users/{userDto.Id}/PlayedItems/{Guid.NewGuid()}", null, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/PluginsControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/PluginsControllerTests.cs
index 547bfcc0ff..c982b9804d 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/PluginsControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/PluginsControllerTests.cs
@@ -24,7 +24,7 @@ public sealed class PluginsControllerTests : IClassFixture<JellyfinApplicationFa
{
var client = _factory.CreateClient();
- var response = await client.GetAsync("/Plugins");
+ var response = await client.GetAsync("/Plugins", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}
@@ -35,11 +35,11 @@ public sealed class PluginsControllerTests : IClassFixture<JellyfinApplicationFa
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- var response = await client.GetAsync("/Plugins");
+ var response = await client.GetAsync("/Plugins", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
Assert.Equal(Encoding.UTF8.BodyName, response.Content.Headers.ContentType?.CharSet);
- _ = await response.Content.ReadFromJsonAsync<PluginInfo[]>(JsonDefaults.Options);
+ _ = await response.Content.ReadFromJsonAsync<PluginInfo[]>(JsonDefaults.Options, TestContext.Current.CancellationToken);
}
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs
index c8ae2a88af..0e5d81a4d6 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/StartupControllerTests.cs
@@ -8,11 +8,11 @@ using System.Threading.Tasks;
using Jellyfin.Api.Models.StartupDtos;
using Jellyfin.Extensions.Json;
using Xunit;
-using Xunit.Priority;
+using Xunit.v3.Priority;
namespace Jellyfin.Server.Integration.Tests.Controllers
{
- [TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
+ [TestCaseOrderer(typeof(PriorityOrderer))]
public sealed class StartupControllerTests : IClassFixture<JellyfinApplicationFactory>
{
private readonly JellyfinApplicationFactory _factory;
@@ -37,14 +37,14 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
PreferredMetadataLanguage = "nl"
};
- using var postResponse = await client.PostAsJsonAsync("/Startup/Configuration", config, _jsonOptions);
+ using var postResponse = await client.PostAsJsonAsync("/Startup/Configuration", config, _jsonOptions, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NoContent, postResponse.StatusCode);
- using var getResponse = await client.GetAsync("/Startup/Configuration");
+ using var getResponse = await client.GetAsync("/Startup/Configuration", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
Assert.Equal(MediaTypeNames.Application.Json, getResponse.Content.Headers.ContentType?.MediaType);
- var newConfig = await getResponse.Content.ReadFromJsonAsync<StartupConfigurationDto>(_jsonOptions);
+ var newConfig = await getResponse.Content.ReadFromJsonAsync<StartupConfigurationDto>(_jsonOptions, TestContext.Current.CancellationToken);
Assert.Equal(config.ServerName, newConfig!.ServerName);
Assert.Equal(config.UICulture, newConfig.UICulture);
Assert.Equal(config.MetadataCountryCode, newConfig.MetadataCountryCode);
@@ -57,11 +57,11 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
{
var client = _factory.CreateClient();
- using var response = await client.GetAsync("/Startup/User");
+ using var response = await client.GetAsync("/Startup/User", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(MediaTypeNames.Application.Json, response.Content.Headers.ContentType?.MediaType);
- var user = await response.Content.ReadFromJsonAsync<StartupUserDto>(_jsonOptions);
+ var user = await response.Content.ReadFromJsonAsync<StartupUserDto>(_jsonOptions, TestContext.Current.CancellationToken);
Assert.NotNull(user);
Assert.NotNull(user.Name);
Assert.NotEmpty(user.Name);
@@ -80,14 +80,14 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
Password = "NewPassword"
};
- var postResponse = await client.PostAsJsonAsync("/Startup/User", user, _jsonOptions);
+ var postResponse = await client.PostAsJsonAsync("/Startup/User", user, _jsonOptions, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NoContent, postResponse.StatusCode);
- var getResponse = await client.GetAsync("/Startup/User");
+ var getResponse = await client.GetAsync("/Startup/User", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
Assert.Equal(MediaTypeNames.Application.Json, getResponse.Content.Headers.ContentType?.MediaType);
- var newUser = await getResponse.Content.ReadFromJsonAsync<StartupUserDto>(_jsonOptions);
+ var newUser = await getResponse.Content.ReadFromJsonAsync<StartupUserDto>(_jsonOptions, TestContext.Current.CancellationToken);
Assert.NotNull(newUser);
Assert.Equal(user.Name, newUser.Name);
Assert.Null(newUser.Password);
@@ -99,7 +99,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
{
var client = _factory.CreateClient();
- var response = await client.PostAsync("/Startup/Complete", new ByteArrayContent(Array.Empty<byte>()));
+ var response = await client.PostAsync("/Startup/Complete", new ByteArrayContent(Array.Empty<byte>()), TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
}
@@ -109,7 +109,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
{
var client = _factory.CreateClient();
- using var response = await client.GetAsync("/Startup/User");
+ using var response = await client.GetAsync("/Startup/User", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
index 04d1b3dc27..7ea56be731 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserControllerTests.cs
@@ -10,11 +10,11 @@ using Jellyfin.Api.Models.UserDtos;
using Jellyfin.Extensions.Json;
using MediaBrowser.Model.Dto;
using Xunit;
-using Xunit.Priority;
+using Xunit.v3.Priority;
namespace Jellyfin.Server.Integration.Tests.Controllers
{
- [TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
+ [TestCaseOrderer(typeof(PriorityOrderer))]
public sealed class UserControllerTests : IClassFixture<JellyfinApplicationFactory>
{
private const string TestUsername = "testUser01";
@@ -41,9 +41,9 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
{
var client = _factory.CreateClient();
- using var response = await client.GetAsync("Users/Public");
+ using var response = await client.GetAsync("Users/Public", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var users = await response.Content.ReadFromJsonAsync<UserDto[]>(_jsonOptions);
+ var users = await response.Content.ReadFromJsonAsync<UserDto[]>(_jsonOptions, TestContext.Current.CancellationToken);
// User are hidden by default
Assert.NotNull(users);
Assert.Empty(users);
@@ -56,9 +56,9 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- using var response = await client.GetAsync("Users");
+ using var response = await client.GetAsync("Users", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var users = await response.Content.ReadFromJsonAsync<UserDto[]>(_jsonOptions);
+ var users = await response.Content.ReadFromJsonAsync<UserDto[]>(_jsonOptions, TestContext.Current.CancellationToken);
Assert.NotNull(users);
Assert.Single(users);
}
@@ -89,7 +89,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
using var response = await CreateUserByName(client, createRequest);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var user = await response.Content.ReadFromJsonAsync<UserDto>(_jsonOptions);
+ var user = await response.Content.ReadFromJsonAsync<UserDto>(_jsonOptions, TestContext.Current.CancellationToken);
Assert.Equal(TestUsername, user!.Name);
_testUserId = user.Id;
@@ -128,7 +128,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
// access token can't be null here as the previous test populated it
client.DefaultRequestHeaders.AddAuthHeader(_accessToken!);
- using var response = await client.DeleteAsync($"User/{Guid.NewGuid()}");
+ using var response = await client.DeleteAsync($"User/{Guid.NewGuid()}", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserLibraryControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserLibraryControllerTests.cs
index 98ad28f5bd..6e4fccd735 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/UserLibraryControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/UserLibraryControllerTests.cs
@@ -28,7 +28,7 @@ public sealed class UserLibraryControllerTests : IClassFixture<JellyfinApplicati
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- var response = await client.GetAsync($"Users/{Guid.NewGuid()}/Items/Root");
+ var response = await client.GetAsync($"Users/{Guid.NewGuid()}/Items/Root", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
@@ -54,7 +54,7 @@ public sealed class UserLibraryControllerTests : IClassFixture<JellyfinApplicati
var rootFolderDto = await AuthHelper.GetRootFolderDtoAsync(client);
- var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, Guid.NewGuid(), rootFolderDto.Id));
+ var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, Guid.NewGuid(), rootFolderDto.Id), TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
@@ -71,7 +71,7 @@ public sealed class UserLibraryControllerTests : IClassFixture<JellyfinApplicati
var userDto = await AuthHelper.GetUserDtoAsync(client);
- var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, userDto.Id, Guid.NewGuid()));
+ var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, userDto.Id, Guid.NewGuid()), TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
@@ -84,9 +84,9 @@ public sealed class UserLibraryControllerTests : IClassFixture<JellyfinApplicati
var userDto = await AuthHelper.GetUserDtoAsync(client);
var rootFolderDto = await AuthHelper.GetRootFolderDtoAsync(client, userDto.Id);
- var response = await client.GetAsync($"Users/{userDto.Id}/Items/{rootFolderDto.Id}");
+ var response = await client.GetAsync($"Users/{userDto.Id}/Items/{rootFolderDto.Id}", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var rootDto = await response.Content.ReadFromJsonAsync<BaseItemDto>(_jsonOptions);
+ var rootDto = await response.Content.ReadFromJsonAsync<BaseItemDto>(_jsonOptions, TestContext.Current.CancellationToken);
Assert.NotNull(rootDto);
}
@@ -99,9 +99,9 @@ public sealed class UserLibraryControllerTests : IClassFixture<JellyfinApplicati
var userDto = await AuthHelper.GetUserDtoAsync(client);
var rootFolderDto = await AuthHelper.GetRootFolderDtoAsync(client, userDto.Id);
- var response = await client.GetAsync($"Users/{userDto.Id}/Items/{rootFolderDto.Id}/Intros");
+ var response = await client.GetAsync($"Users/{userDto.Id}/Items/{rootFolderDto.Id}/Intros", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var rootDto = await response.Content.ReadFromJsonAsync<QueryResult<BaseItemDto>>(_jsonOptions);
+ var rootDto = await response.Content.ReadFromJsonAsync<QueryResult<BaseItemDto>>(_jsonOptions, TestContext.Current.CancellationToken);
Assert.NotNull(rootDto);
}
@@ -116,9 +116,9 @@ public sealed class UserLibraryControllerTests : IClassFixture<JellyfinApplicati
var userDto = await AuthHelper.GetUserDtoAsync(client);
var rootFolderDto = await AuthHelper.GetRootFolderDtoAsync(client, userDto.Id);
- var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, userDto.Id, rootFolderDto.Id));
+ var response = await client.GetAsync(string.Format(CultureInfo.InvariantCulture, format, userDto.Id, rootFolderDto.Id), TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- var rootDto = await response.Content.ReadFromJsonAsync<BaseItemDto[]>(_jsonOptions);
+ var rootDto = await response.Content.ReadFromJsonAsync<BaseItemDto[]>(_jsonOptions, TestContext.Current.CancellationToken);
Assert.NotNull(rootDto);
}
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/VideosControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/VideosControllerTests.cs
index 1916ced12c..e0630ff443 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Controllers/VideosControllerTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/VideosControllerTests.cs
@@ -21,7 +21,7 @@ public sealed class VideosControllerTests : IClassFixture<JellyfinApplicationFac
var client = _factory.CreateClient();
client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client));
- var response = await client.DeleteAsync($"Videos/{Guid.NewGuid()}");
+ var response = await client.DeleteAsync($"Videos/{Guid.NewGuid()}", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs b/tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs
index d2249cdc3b..a343423b4d 100644
--- a/tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/EncodedQueryStringTest.cs
@@ -27,9 +27,9 @@ namespace Jellyfin.Server.Integration.Tests
{
var client = _factory.CreateClient();
- var response = await client.GetAsync("Encoder/UrlDecode?" + sourceUrl);
+ var response = await client.GetAsync("Encoder/UrlDecode?" + sourceUrl, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- string reply = await response.Content.ReadAsStringAsync();
+ string reply = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
Assert.Equal(unencodedUrl, reply);
}
@@ -40,9 +40,9 @@ namespace Jellyfin.Server.Integration.Tests
{
var client = _factory.CreateClient();
- var response = await client.GetAsync("Encoder/UrlArrayDecode?" + sourceUrl);
+ var response = await client.GetAsync("Encoder/UrlArrayDecode?" + sourceUrl, TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- string reply = await response.Content.ReadAsStringAsync();
+ string reply = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
Assert.Equal(unencodedUrl, reply);
}
}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
index 7b0e23788b..7abad8bb84 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
+++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
@@ -1,17 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ </PropertyGroup>
+
<ItemGroup>
<PackageReference Include="AutoFixture" />
<PackageReference Include="AutoFixture.AutoMoq" />
- <PackageReference Include="AutoFixture.Xunit2" />
+ <PackageReference Include="AutoFixture.Xunit3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
- <PackageReference Include="xunit" />
+ <PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="Xunit.Priority" />
+ <PackageReference Include="Xunit.v3.Priority" />
<PackageReference Include="coverlet.collector" />
<PackageReference Include="Moq" />
</ItemGroup>
diff --git a/tests/Jellyfin.Server.Integration.Tests/Middleware/RobotsRedirectionMiddlewareTests.cs b/tests/Jellyfin.Server.Integration.Tests/Middleware/RobotsRedirectionMiddlewareTests.cs
index 1ea79f7deb..baeaf4d0cb 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Middleware/RobotsRedirectionMiddlewareTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/Middleware/RobotsRedirectionMiddlewareTests.cs
@@ -23,7 +23,7 @@ namespace Jellyfin.Server.Integration.Tests.Middleware
AllowAutoRedirect = false
});
- var response = await client.GetAsync("robots.txt");
+ var response = await client.GetAsync("robots.txt", TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.Equal("web/robots.txt", response.Headers.Location?.ToString());
diff --git a/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs
index 62cdd25aec..17a8a55222 100644
--- a/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/OpenApiSpecTests.cs
@@ -3,7 +3,6 @@ using System.Reflection;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
using Xunit;
-using Xunit.Abstractions;
namespace Jellyfin.Server.Integration.Tests
{
@@ -25,7 +24,7 @@ namespace Jellyfin.Server.Integration.Tests
var client = _factory.CreateClient();
// Act
- var response = await client.GetAsync("/api-docs/openapi.json");
+ var response = await client.GetAsync("/api-docs/openapi.json", TestContext.Current.CancellationToken);
// Assert
response.EnsureSuccessStatusCode();
@@ -35,7 +34,7 @@ namespace Jellyfin.Server.Integration.Tests
string outputPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ".", "openapi.json"));
_outputHelper.WriteLine("Writing OpenAPI Spec JSON to '{0}'.", outputPath);
await using var fs = AsyncFile.Create(outputPath);
- await response.Content.CopyToAsync(fs);
+ await response.Content.CopyToAsync(fs, TestContext.Current.CancellationToken);
}
}
}
diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
index 21596e0ed2..3ad5310c6b 100644
--- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
+++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
@@ -1,12 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ </PropertyGroup>
+
<ItemGroup>
<PackageReference Include="AutoFixture" />
<PackageReference Include="AutoFixture.AutoMoq" />
- <PackageReference Include="AutoFixture.Xunit2" />
+ <PackageReference Include="AutoFixture.Xunit3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
- <PackageReference Include="xunit" />
+ <PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
diff --git a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs
index 14f4c33b6b..e788f43b86 100644
--- a/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs
+++ b/tests/Jellyfin.Server.Tests/ParseNetworkTests.cs
@@ -23,8 +23,8 @@ namespace Jellyfin.Server.Tests
true,
true,
new string[] { "192.168.t", "127.0.0.1", "::1", "1234.1232.12.1234" },
- new IPAddress[] { IPAddress.Loopback },
- new IPNetwork[] { new IPNetwork(IPAddress.IPv6Loopback, 128) });
+ new IPAddress[] { IPAddress.Loopback, IPAddress.IPv6Loopback },
+ Array.Empty<IPNetwork>());
data.Add(
true,
@@ -37,8 +37,8 @@ namespace Jellyfin.Server.Tests
true,
true,
new string[] { "::1" },
- Array.Empty<IPAddress>(),
- new IPNetwork[] { new IPNetwork(IPAddress.IPv6Loopback, 128) });
+ new IPAddress[] { IPAddress.IPv6Loopback },
+ Array.Empty<IPNetwork>());
data.Add(
false,
@@ -58,15 +58,15 @@ namespace Jellyfin.Server.Tests
false,
true,
new string[] { "localhost" },
- Array.Empty<IPAddress>(),
- new IPNetwork[] { new IPNetwork(IPAddress.IPv6Loopback, 128) });
+ new IPAddress[] { IPAddress.IPv6Loopback },
+ Array.Empty<IPNetwork>());
data.Add(
true,
true,
new string[] { "localhost" },
- new IPAddress[] { IPAddress.Loopback },
- new IPNetwork[] { new IPNetwork(IPAddress.IPv6Loopback, 128) });
+ new IPAddress[] { IPAddress.Loopback, IPAddress.IPv6Loopback },
+ Array.Empty<IPNetwork>());
return data;
}
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
index 9fe0744de1..3b39fe72d6 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
@@ -1,5 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ </PropertyGroup>
+
<ItemGroup>
<None Include="Test Data\**\*.*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@@ -9,7 +13,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />
- <PackageReference Include="xunit" />
+ <PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>