Przeglądaj źródła

Merge branch 'jellyfin:master' into feature/EFUserData

JPVenson 5 miesięcy temu
rodzic
commit
fe1aab034e
40 zmienionych plików z 173 dodań i 149 usunięć
  1. 0 28
      .devcontainer/Dev - Server Ffmpeg/devcontainer.json
  2. 2 2
      .devcontainer/devcontainer.json
  3. 1 1
      .devcontainer/install-ffmpeg.sh
  4. 3 3
      .github/workflows/ci-codeql-analysis.yml
  5. 2 2
      .github/workflows/ci-openapi.yml
  6. 6 5
      .vscode/extensions.json
  7. 1 0
      CONTRIBUTORS.md
  8. 6 6
      Directory.Packages.props
  9. 2 1
      Emby.Server.Implementations/Localization/Core/mk.json
  10. 1 1
      Emby.Server.Implementations/Localization/Core/ro.json
  11. 2 1
      Emby.Server.Implementations/Localization/Core/zh-HK.json
  12. 16 16
      Emby.Server.Implementations/Localization/Ratings/us.csv
  13. 17 20
      Emby.Server.Implementations/Plugins/PluginManager.cs
  14. 5 5
      Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
  15. 1 1
      Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs
  16. 1 1
      Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
  17. 1 1
      Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupCollectionAndPlaylistPathsTask.cs
  18. 1 1
      Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
  19. 1 1
      Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
  20. 2 2
      Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs
  21. 1 1
      Emby.Server.Implementations/ScheduledTasks/Tasks/MediaSegmentExtractionTask.cs
  22. 1 1
      Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs
  23. 1 1
      Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs
  24. 2 2
      Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs
  25. 1 1
      Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs
  26. 5 8
      Jellyfin.Api/Controllers/DynamicHlsController.cs
  27. 2 2
      Jellyfin.Api/Controllers/LiveTvController.cs
  28. 1 1
      Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs
  29. 1 1
      Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs
  30. 2 2
      MediaBrowser.Model/Configuration/LibraryOptions.cs
  31. 0 5
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  32. 1 0
      MediaBrowser.Model/Net/MimeTypes.cs
  33. 1 21
      MediaBrowser.Model/Tasks/TaskTriggerInfo.cs
  34. 28 0
      MediaBrowser.Model/Tasks/TaskTriggerInfoType.cs
  35. 1 1
      MediaBrowser.Providers/Lyric/LyricScheduledTask.cs
  36. 1 1
      MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
  37. 1 1
      MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs
  38. 1 1
      src/Jellyfin.LiveTv/Channels/RefreshChannelsScheduledTask.cs
  39. 1 1
      src/Jellyfin.LiveTv/Guide/RefreshGuideScheduledTask.cs
  40. 50 0
      tests/Jellyfin.LiveTv.Tests/Listings/ListingsManagerTests.cs

+ 0 - 28
.devcontainer/Dev - Server Ffmpeg/devcontainer.json

@@ -1,28 +0,0 @@
-{
-    "name": "Development Jellyfin Server - FFmpeg",
-    "image":"mcr.microsoft.com/devcontainers/dotnet:9.0-jammy",
-    // restores nuget packages, installs the dotnet workloads and installs the dev https certificate
-    "postStartCommand": "dotnet restore; dotnet workload update; dotnet dev-certs https --trust; sudo bash \"./.devcontainer/Dev - Server Ffmpeg/install-ffmpeg.sh\"",
-    // reads the extensions list and installs them
-    "postAttachCommand": "cat .vscode/extensions.json | jq -r .recommendations[] | xargs -n 1 code --install-extension",
-    "features": {
-        "ghcr.io/devcontainers/features/dotnet:2": {
-            "version": "none",
-            "dotnetRuntimeVersions": "9.0",
-            "aspNetCoreRuntimeVersions": "9.0"
-        },
-        "ghcr.io/devcontainers-contrib/features/apt-packages:1": {
-            "preserve_apt_list": false,
-            "packages": ["libfontconfig1"]
-        },
-        "ghcr.io/devcontainers/features/docker-in-docker:2": {
-            "dockerDashComposeVersion": "v2"
-        },
-        "ghcr.io/devcontainers/features/github-cli:1": {},
-        "ghcr.io/eitsupi/devcontainer-features/jq-likes:2": {}
-    },
-    "hostRequirements": {
-        "memory": "8gb",
-        "cpus": 4
-    }
-}

+ 2 - 2
.devcontainer/devcontainer.json

@@ -1,8 +1,8 @@
 {
     "name": "Development Jellyfin Server",
-    "image":"mcr.microsoft.com/devcontainers/dotnet:9.0-jammy",
+    "image":"mcr.microsoft.com/devcontainers/dotnet:9.0-bookworm",
     // restores nuget packages, installs the dotnet workloads and installs the dev https certificate
-    "postStartCommand": "dotnet restore; dotnet workload update; dotnet dev-certs https --trust",
+    "postStartCommand": "sudo dotnet restore; sudo dotnet workload update; sudo dotnet dev-certs https --trust; sudo bash \"./.devcontainer/install-ffmpeg.sh\"",
     // reads the extensions list and installs them
     "postAttachCommand": "cat .vscode/extensions.json | jq -r .recommendations[] | xargs -n 1 code --install-extension",
     "features": {

+ 1 - 1
.devcontainer/Dev - Server Ffmpeg/install-ffmpeg.sh → .devcontainer/install-ffmpeg.sh

@@ -29,4 +29,4 @@ Signed-By: /etc/apt/keyrings/jellyfin.gpg
 EOF
 
 sudo apt update -y
-sudo apt install jellyfin-ffmpeg6 -y
+sudo apt install jellyfin-ffmpeg7 -y

+ 3 - 3
.github/workflows/ci-codeql-analysis.yml

@@ -27,11 +27,11 @@ jobs:
         dotnet-version: '9.0.x'
 
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@ea9e4e37992a54ee68a9622e985e60c8e8f12d9f # v3.27.4
+      uses: github/codeql-action/init@aa578102511db1f4524ed59b8cc2bae4f6e88195 # v3.27.6
       with:
         languages: ${{ matrix.language }}
         queries: +security-extended
     - name: Autobuild
-      uses: github/codeql-action/autobuild@ea9e4e37992a54ee68a9622e985e60c8e8f12d9f # v3.27.4
+      uses: github/codeql-action/autobuild@aa578102511db1f4524ed59b8cc2bae4f6e88195 # v3.27.6
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@ea9e4e37992a54ee68a9622e985e60c8e8f12d9f # v3.27.4
+      uses: github/codeql-action/analyze@aa578102511db1f4524ed59b8cc2bae4f6e88195 # v3.27.6

+ 2 - 2
.github/workflows/ci-openapi.yml

@@ -172,7 +172,7 @@ jobs:
           strip_components: 1
           target: "/srv/incoming/openapi/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
       - name: Move openapi.json (unstable) into place
-        uses: appleboy/ssh-action@25ce8cbbcb08177468c7ff7ec5cbfa236f9341e1 # v1.1.0
+        uses: appleboy/ssh-action@7eaf76671a0d7eec5d98ee897acda4f968735a17 # v1.2.0
         with:
           host: "${{ secrets.REPO_HOST }}"
           username: "${{ secrets.REPO_USER }}"
@@ -234,7 +234,7 @@ jobs:
           strip_components: 1
           target: "/srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
       - name: Move openapi.json (stable) into place
-        uses: appleboy/ssh-action@25ce8cbbcb08177468c7ff7ec5cbfa236f9341e1 # v1.1.0
+        uses: appleboy/ssh-action@7eaf76671a0d7eec5d98ee897acda4f968735a17 # v1.2.0
         with:
           host: "${{ secrets.REPO_HOST }}"
           username: "${{ secrets.REPO_USER }}"

+ 6 - 5
.vscode/extensions.json

@@ -1,12 +1,13 @@
 {
-	"recommendations": [
+    "recommendations": [
         "ms-dotnettools.csharp",
         "editorconfig.editorconfig",
         "github.vscode-github-actions",
         "ms-dotnettools.vscode-dotnet-runtime",
-        "ms-dotnettools.csdevkit"
-	],
-	"unwantedRecommendations": [
+        "ms-dotnettools.csdevkit",
+        "alexcvzz.vscode-sqlite"
+    ],
+    "unwantedRecommendations": [
 
-	]
+    ]
 }

+ 1 - 0
CONTRIBUTORS.md

@@ -192,6 +192,7 @@
  - [jaina heartles](https://github.com/heartles)
  - [oxixes](https://github.com/oxixes)
  - [elfalem](https://github.com/elfalem)
+ - [Kenneth Cochran](https://github.com/kennethcochran)
  - [benedikt257](https://github.com/benedikt257)
  - [revam](https://github.com/revam)
 

+ 6 - 6
Directory.Packages.props

@@ -4,7 +4,7 @@
   </PropertyGroup>
   <!-- Run "dotnet list package (dash,dash)outdated" to see the latest versions of each package.-->
   <ItemGroup Label="Package Dependencies">
-    <PackageVersion Include="AsyncKeyedLock" Version="7.1.3" />
+    <PackageVersion Include="AsyncKeyedLock" Version="7.1.4" />
     <PackageVersion Include="AutoFixture.AutoMoq" Version="4.18.1" />
     <PackageVersion Include="AutoFixture.Xunit2" Version="4.18.1" />
     <PackageVersion Include="AutoFixture" Version="4.18.1" />
@@ -47,8 +47,8 @@
     <PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
     <PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.0" />
     <PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.0" />
-    <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
-    <PackageVersion Include="MimeTypes" Version="2.4.0" />
+    <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
+    <PackageVersion Include="MimeTypes" Version="2.5.2" />
     <PackageVersion Include="Mono.Nat" Version="3.0.4" />
     <PackageVersion Include="Moq" Version="4.18.4" />
     <PackageVersion Include="NEbml" Version="0.11.0" />
@@ -71,7 +71,7 @@
     <PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.9" />
     <PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
     <PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
-    <PackageVersion Include="Svg.Skia" Version="2.0.0.2" />
+    <PackageVersion Include="Svg.Skia" Version="2.0.0.4" />
     <PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="6.5.0" />
     <PackageVersion Include="Swashbuckle.AspNetCore" Version="6.2.3" />
     <PackageVersion Include="System.Globalization" Version="4.3.0" />
@@ -80,12 +80,12 @@
     <PackageVersion Include="System.Text.Json" Version="9.0.0" />
     <PackageVersion Include="System.Threading.Tasks.Dataflow" Version="9.0.0" />
     <PackageVersion Include="TagLibSharp" Version="2.3.0" />
-    <PackageVersion Include="z440.atl.core" Version="6.8.0" />
+    <PackageVersion Include="z440.atl.core" Version="6.9.0" />
     <PackageVersion Include="TMDbLib" Version="2.2.0" />
     <PackageVersion Include="UTF.Unknown" Version="2.5.1" />
     <PackageVersion Include="Xunit.Priority" Version="1.1.6" />
     <PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
-    <PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" />
+    <PackageVersion Include="Xunit.SkippableFact" Version="1.5.23" />
     <PackageVersion Include="xunit" Version="2.9.2" />
   </ItemGroup>
 </Project>

+ 2 - 1
Emby.Server.Implementations/Localization/Core/mk.json

@@ -131,5 +131,6 @@
     "TaskRefreshTrickplayImages": "Генерирај слики за прегледување (Trickplay)",
     "TaskAudioNormalization": "Нормализација на звукот",
     "TaskRefreshTrickplayImagesDescription": "Креира трикплеј прегледи за видеа во овозможените библиотеки.",
-    "TaskCleanCollectionsAndPlaylistsDescription": "Отстранува ставки од колекциите и плејлистите што веќе не постојат."
+    "TaskCleanCollectionsAndPlaylistsDescription": "Отстранува ставки од колекциите и плејлистите што веќе не постојат.",
+    "TaskExtractMediaSegments": "Скенирање на сегменти на содржина"
 }

+ 1 - 1
Emby.Server.Implementations/Localization/Core/ro.json

@@ -77,7 +77,7 @@
     "HeaderAlbumArtists": "Artiști album",
     "Genres": "Genuri",
     "Folders": "Dosare",
-    "Favorites": "Favorite",
+    "Favorites": "Preferate",
     "FailedLoginAttemptWithUserName": "Încercare de conectare eșuată pentru {0}",
     "DeviceOnlineWithName": "{0} este conectat",
     "DeviceOfflineWithName": "{0} s-a deconectat",

+ 2 - 1
Emby.Server.Implementations/Localization/Core/zh-HK.json

@@ -133,5 +133,6 @@
     "TaskDownloadMissingLyricsDescription": "下載歌詞",
     "TaskCleanCollectionsAndPlaylists": "整理媒體與播放清單",
     "TaskAudioNormalization": "音訊同等化",
-    "TaskAudioNormalizationDescription": "掃描檔案裏的音訊同等化資料。"
+    "TaskAudioNormalizationDescription": "掃描檔案裏的音訊同等化資料。",
+    "TaskCleanCollectionsAndPlaylistsDescription": "從資料庫及播放清單中移除已不存在的項目。"
 }

+ 16 - 16
Emby.Server.Implementations/Localization/Ratings/us.csv

@@ -5,23 +5,23 @@ TV-Y,0
 TV-Y7,7
 TV-Y7-FV,7
 PG,10
+TV-PG,10
+TV-PG-D,10
+TV-PG-L,10
+TV-PG-S,10
+TV-PG-V,10
+TV-PG-DL,10
+TV-PG-DS,10
+TV-PG-DV,10
+TV-PG-LS,10
+TV-PG-LV,10
+TV-PG-SV,10
+TV-PG-DLS,10
+TV-PG-DLV,10
+TV-PG-DSV,10
+TV-PG-LSV,10
+TV-PG-DLSV,10
 PG-13,13
-TV-PG,13
-TV-PG-D,13
-TV-PG-L,13
-TV-PG-S,13
-TV-PG-V,13
-TV-PG-DL,13
-TV-PG-DS,13
-TV-PG-DV,13
-TV-PG-LS,13
-TV-PG-LV,13
-TV-PG-SV,13
-TV-PG-DLS,13
-TV-PG-DLV,13
-TV-PG-DSV,13
-TV-PG-LSV,13
-TV-PG-DLSV,13
 TV-14,14
 TV-14-D,14
 TV-14-L,14

+ 17 - 20
Emby.Server.Implementations/Plugins/PluginManager.cs

@@ -785,30 +785,27 @@ namespace Emby.Server.Implementations.Plugins
 
                 var cleaned = false;
                 var path = entry.Path;
-                if (_config.RemoveOldPlugins)
+                // Attempt a cleanup of old folders.
+                try
                 {
-                    // Attempt a cleanup of old folders.
-                    try
-                    {
-                        _logger.LogDebug("Deleting {Path}", path);
-                        Directory.Delete(path, true);
-                        cleaned = true;
-                    }
+                    _logger.LogDebug("Deleting {Path}", path);
+                    Directory.Delete(path, true);
+                    cleaned = true;
+                }
 #pragma warning disable CA1031 // Do not catch general exception types
-                    catch (Exception e)
+                catch (Exception e)
 #pragma warning restore CA1031 // Do not catch general exception types
-                    {
-                        _logger.LogWarning(e, "Unable to delete {Path}", path);
-                    }
+                {
+                    _logger.LogWarning(e, "Unable to delete {Path}", path);
+                }
 
-                    if (cleaned)
-                    {
-                        versions.RemoveAt(x);
-                    }
-                    else
-                    {
-                        ChangePluginState(entry, PluginStatus.Deleted);
-                    }
+                if (cleaned)
+                {
+                    versions.RemoveAt(x);
+                }
+                else
+                {
+                    ChangePluginState(entry, PluginStatus.Deleted);
                 }
             }
 

+ 5 - 5
Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs

@@ -471,7 +471,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
                     new()
                     {
                         IntervalTicks = TimeSpan.FromDays(1).Ticks,
-                        Type = TaskTriggerInfo.TriggerInterval
+                        Type = TaskTriggerInfoType.IntervalTrigger
                     }
                 ];
             }
@@ -616,7 +616,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
                 MaxRuntimeTicks = info.MaxRuntimeTicks
             };
 
-            if (info.Type.Equals(nameof(DailyTrigger), StringComparison.OrdinalIgnoreCase))
+            if (info.Type == TaskTriggerInfoType.DailyTrigger)
             {
                 if (!info.TimeOfDayTicks.HasValue)
                 {
@@ -626,7 +626,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
                 return new DailyTrigger(TimeSpan.FromTicks(info.TimeOfDayTicks.Value), options);
             }
 
-            if (info.Type.Equals(nameof(WeeklyTrigger), StringComparison.OrdinalIgnoreCase))
+            if (info.Type == TaskTriggerInfoType.WeeklyTrigger)
             {
                 if (!info.TimeOfDayTicks.HasValue)
                 {
@@ -641,7 +641,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
                 return new WeeklyTrigger(TimeSpan.FromTicks(info.TimeOfDayTicks.Value), info.DayOfWeek.Value, options);
             }
 
-            if (info.Type.Equals(nameof(IntervalTrigger), StringComparison.OrdinalIgnoreCase))
+            if (info.Type == TaskTriggerInfoType.IntervalTrigger)
             {
                 if (!info.IntervalTicks.HasValue)
                 {
@@ -651,7 +651,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
                 return new IntervalTrigger(TimeSpan.FromTicks(info.IntervalTicks.Value), options);
             }
 
-            if (info.Type.Equals(nameof(StartupTrigger), StringComparison.OrdinalIgnoreCase))
+            if (info.Type == TaskTriggerInfoType.StartupTrigger)
             {
                 return new StartupTrigger(options);
             }

+ 1 - 1
Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs

@@ -156,7 +156,7 @@ public partial class AudioNormalizationTask : IScheduledTask
         [
             new TaskTriggerInfo
             {
-                Type = TaskTriggerInfo.TriggerInterval,
+                Type = TaskTriggerInfoType.IntervalTrigger,
                 IntervalTicks = TimeSpan.FromHours(24).Ticks
             }
         ];

+ 1 - 1
Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs

@@ -85,7 +85,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
             [
                 new TaskTriggerInfo
                 {
-                    Type = TaskTriggerInfo.TriggerDaily,
+                    Type = TaskTriggerInfoType.DailyTrigger,
                     TimeOfDayTicks = TimeSpan.FromHours(2).Ticks,
                     MaxRuntimeTicks = TimeSpan.FromHours(4).Ticks
                 }

+ 1 - 1
Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupCollectionAndPlaylistPathsTask.cs

@@ -135,6 +135,6 @@ public class CleanupCollectionAndPlaylistPathsTask : IScheduledTask
     /// <inheritdoc />
     public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
     {
-        return [new TaskTriggerInfo() { Type = TaskTriggerInfo.TriggerStartup }];
+        return [new TaskTriggerInfo() { Type = TaskTriggerInfoType.StartupTrigger }];
     }
 }

+ 1 - 1
Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs

@@ -73,7 +73,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
             return
             [
                 // Every so often
-                new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks }
+                new TaskTriggerInfo { Type = TaskTriggerInfoType.IntervalTrigger, IntervalTicks = TimeSpan.FromHours(24).Ticks }
             ];
         }
 

+ 1 - 1
Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs

@@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
         {
             return
             [
-                new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks }
+                new TaskTriggerInfo { Type = TaskTriggerInfoType.IntervalTrigger, IntervalTicks = TimeSpan.FromHours(24).Ticks }
             ];
         }
 

+ 2 - 2
Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs

@@ -69,11 +69,11 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
             [
                 new TaskTriggerInfo
                 {
-                    Type = TaskTriggerInfo.TriggerStartup
+                    Type = TaskTriggerInfoType.StartupTrigger
                 },
                 new TaskTriggerInfo
                 {
-                    Type = TaskTriggerInfo.TriggerInterval,
+                    Type = TaskTriggerInfoType.IntervalTrigger,
                     IntervalTicks = TimeSpan.FromHours(24).Ticks
                 }
             ];

+ 1 - 1
Emby.Server.Implementations/ScheduledTasks/Tasks/MediaSegmentExtractionTask.cs

@@ -111,7 +111,7 @@ public class MediaSegmentExtractionTask : IScheduledTask
     {
         yield return new TaskTriggerInfo
         {
-            Type = TaskTriggerInfo.TriggerInterval,
+            Type = TaskTriggerInfoType.IntervalTrigger,
             IntervalTicks = TimeSpan.FromHours(12).Ticks
         };
     }

+ 1 - 1
Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs

@@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
             return
             [
                 // Every so often
-                new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks }
+                new TaskTriggerInfo { Type = TaskTriggerInfoType.IntervalTrigger, IntervalTicks = TimeSpan.FromHours(24).Ticks }
             ];
         }
 

+ 1 - 1
Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs

@@ -58,7 +58,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
             {
                 new TaskTriggerInfo
                 {
-                    Type = TaskTriggerInfo.TriggerInterval,
+                    Type = TaskTriggerInfoType.IntervalTrigger,
                     IntervalTicks = TimeSpan.FromDays(7).Ticks
                 }
             };

+ 2 - 2
Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs

@@ -60,10 +60,10 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
         public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
         {
             // At startup
-            yield return new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerStartup };
+            yield return new TaskTriggerInfo { Type = TaskTriggerInfoType.StartupTrigger };
 
             // Every so often
-            yield return new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks };
+            yield return new TaskTriggerInfo { Type = TaskTriggerInfoType.IntervalTrigger, IntervalTicks = TimeSpan.FromHours(24).Ticks };
         }
 
         /// <inheritdoc />

+ 1 - 1
Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs

@@ -48,7 +48,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
         {
             yield return new TaskTriggerInfo
             {
-                Type = TaskTriggerInfo.TriggerInterval,
+                Type = TaskTriggerInfoType.IntervalTrigger,
                 IntervalTicks = TimeSpan.FromHours(12).Ticks
             };
         }

+ 5 - 8
Jellyfin.Api/Controllers/DynamicHlsController.cs

@@ -1819,16 +1819,13 @@ public class DynamicHlsController : BaseJellyfinApiController
         if (isActualOutputVideoCodecHevc || isActualOutputVideoCodecAv1)
         {
             var requestedRange = state.GetRequestedRangeTypes(state.ActualOutputVideoCodec);
-            var requestHasDOVI = requestedRange.Contains(VideoRangeType.DOVI.ToString(), StringComparison.OrdinalIgnoreCase);
-            var requestHasDOVIWithHDR10 = requestedRange.Contains(VideoRangeType.DOVIWithHDR10.ToString(), StringComparison.OrdinalIgnoreCase);
-            var requestHasDOVIWithHLG = requestedRange.Contains(VideoRangeType.DOVIWithHLG.ToString(), StringComparison.OrdinalIgnoreCase);
-            var requestHasDOVIWithSDR = requestedRange.Contains(VideoRangeType.DOVIWithSDR.ToString(), StringComparison.OrdinalIgnoreCase);
+            // Clients reporting Dolby Vision capabilities with fallbacks may only support the fallback layer.
+            // Only enable Dolby Vision remuxing if the client explicitly declares support for profiles without fallbacks.
+            var clientSupportsDoVi = requestedRange.Contains(VideoRangeType.DOVI.ToString(), StringComparison.OrdinalIgnoreCase);
+            var videoIsDoVi = state.VideoStream.VideoRangeType is VideoRangeType.DOVI or VideoRangeType.DOVIWithHDR10 or VideoRangeType.DOVIWithHLG or VideoRangeType.DOVIWithSDR;
 
             if (EncodingHelper.IsCopyCodec(codec)
-                && ((state.VideoStream.VideoRangeType == VideoRangeType.DOVI && requestHasDOVI)
-                    || (state.VideoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10 && requestHasDOVIWithHDR10)
-                    || (state.VideoStream.VideoRangeType == VideoRangeType.DOVIWithHLG && requestHasDOVIWithHLG)
-                    || (state.VideoStream.VideoRangeType == VideoRangeType.DOVIWithSDR && requestHasDOVIWithSDR)))
+                && (videoIsDoVi && clientSupportsDoVi))
             {
                 if (isActualOutputVideoCodecHevc)
                 {

+ 2 - 2
Jellyfin.Api/Controllers/LiveTvController.cs

@@ -962,9 +962,9 @@ public class LiveTvController : BaseJellyfinApiController
     }
 
     /// <summary>
-    /// Get guid info.
+    /// Get guide info.
     /// </summary>
-    /// <response code="200">Guid info returned.</response>
+    /// <response code="200">Guide info returned.</response>
     /// <returns>An <see cref="OkResult"/> containing the guide info.</returns>
     [HttpGet("GuideInfo")]
     [Authorize(Policy = Policies.LiveTvAccess)]

+ 1 - 1
Jellyfin.Server.Implementations/MediaSegments/MediaSegmentManager.cs

@@ -61,7 +61,7 @@ public class MediaSegmentManager : IMediaSegmentManager
             .Where(e => !libraryOptions.DisabledMediaSegmentProviders.Contains(GetProviderId(e.Name)))
             .OrderBy(i =>
                 {
-                    var index = libraryOptions.MediaSegmentProvideOrder.IndexOf(i.Name);
+                    var index = libraryOptions.MediaSegmentProviderOrder.IndexOf(i.Name);
                     return index == -1 ? int.MaxValue : index;
                 })
             .ToList();

+ 1 - 1
Jellyfin.Server/Migrations/Routines/MigrateRatingLevels.cs

@@ -30,7 +30,7 @@ namespace Jellyfin.Server.Migrations.Routines
         }
 
         /// <inheritdoc/>
-        public Guid Id => Guid.Parse("{67445D54-B895-4B24-9F4C-35CE0690EA07}");
+        public Guid Id => Guid.Parse("{73DAB92A-178B-48CD-B05B-FE18733ACDC8}");
 
         /// <inheritdoc/>
         public string Name => "MigrateRatingLevels";

+ 2 - 2
MediaBrowser.Model/Configuration/LibraryOptions.cs

@@ -15,7 +15,7 @@ namespace MediaBrowser.Model.Configuration
             TypeOptions = Array.Empty<TypeOptions>();
             DisabledSubtitleFetchers = Array.Empty<string>();
             DisabledMediaSegmentProviders = Array.Empty<string>();
-            MediaSegmentProvideOrder = Array.Empty<string>();
+            MediaSegmentProviderOrder = Array.Empty<string>();
             SubtitleFetcherOrder = Array.Empty<string>();
             DisabledLocalMetadataReaders = Array.Empty<string>();
             DisabledLyricFetchers = Array.Empty<string>();
@@ -99,7 +99,7 @@ namespace MediaBrowser.Model.Configuration
 
         public string[] DisabledMediaSegmentProviders { get; set; }
 
-        public string[] MediaSegmentProvideOrder { get; set; }
+        public string[] MediaSegmentProviderOrder { get; set; }
 
         public bool SkipSubtitlesIfEmbeddedSubtitlesPresent { get; set; }
 

+ 0 - 5
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -243,11 +243,6 @@ public class ServerConfiguration : BaseApplicationConfiguration
     /// </summary>
     public int LibraryMetadataRefreshConcurrency { get; set; }
 
-    /// <summary>
-    /// Gets or sets a value indicating whether older plugins should automatically be deleted from the plugin folder.
-    /// </summary>
-    public bool RemoveOldPlugins { get; set; }
-
     /// <summary>
     /// Gets or sets a value indicating whether clients should be allowed to upload logs.
     /// </summary>

+ 1 - 0
MediaBrowser.Model/Net/MimeTypes.cs

@@ -125,6 +125,7 @@ namespace MediaBrowser.Model.Net
             new("audio/vorbis", ".vorbis"),
             new("audio/x-ape", ".ape"),
             new("audio/xsp", ".xsp"),
+            new("audio/x-aac", ".aac"),
             new("audio/x-wavpack", ".wv"),
 
             // Type image

+ 1 - 21
MediaBrowser.Model/Tasks/TaskTriggerInfo.cs

@@ -8,31 +8,11 @@ namespace MediaBrowser.Model.Tasks
     /// </summary>
     public class TaskTriggerInfo
     {
-        /// <summary>
-        /// The daily trigger.
-        /// </summary>
-        public const string TriggerDaily = "DailyTrigger";
-
-        /// <summary>
-        /// The weekly trigger.
-        /// </summary>
-        public const string TriggerWeekly = "WeeklyTrigger";
-
-        /// <summary>
-        /// The interval trigger.
-        /// </summary>
-        public const string TriggerInterval = "IntervalTrigger";
-
-        /// <summary>
-        /// The startup trigger.
-        /// </summary>
-        public const string TriggerStartup = "StartupTrigger";
-
         /// <summary>
         /// Gets or sets the type.
         /// </summary>
         /// <value>The type.</value>
-        public string Type { get; set; }
+        public TaskTriggerInfoType Type { get; set; }
 
         /// <summary>
         /// Gets or sets the time of day.

+ 28 - 0
MediaBrowser.Model/Tasks/TaskTriggerInfoType.cs

@@ -0,0 +1,28 @@
+namespace MediaBrowser.Model.Tasks
+{
+    /// <summary>
+    /// Enum TaskTriggerInfoType.
+    /// </summary>
+    public enum TaskTriggerInfoType
+    {
+        /// <summary>
+        /// The daily trigger.
+        /// </summary>
+        DailyTrigger,
+
+        /// <summary>
+        /// The weekly trigger.
+        /// </summary>
+        WeeklyTrigger,
+
+        /// <summary>
+        /// The interval trigger.
+        /// </summary>
+        IntervalTrigger,
+
+        /// <summary>
+        /// The startup trigger.
+        /// </summary>
+        StartupTrigger
+    }
+}

+ 1 - 1
MediaBrowser.Providers/Lyric/LyricScheduledTask.cs

@@ -162,7 +162,7 @@ public class LyricScheduledTask : IScheduledTask
         [
             new TaskTriggerInfo
             {
-                Type = TaskTriggerInfo.TriggerInterval,
+                Type = TaskTriggerInfoType.IntervalTrigger,
                 IntervalTicks = TimeSpan.FromHours(24).Ticks
             }
         ];

+ 1 - 1
MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs

@@ -217,7 +217,7 @@ namespace MediaBrowser.Providers.MediaInfo
             return new[]
             {
                 // Every so often
-                new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks }
+                new TaskTriggerInfo { Type = TaskTriggerInfoType.IntervalTrigger, IntervalTicks = TimeSpan.FromHours(24).Ticks }
             };
         }
     }

+ 1 - 1
MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs

@@ -63,7 +63,7 @@ public class TrickplayImagesTask : IScheduledTask
         {
             new TaskTriggerInfo
             {
-                Type = TaskTriggerInfo.TriggerDaily,
+                Type = TaskTriggerInfoType.DailyTrigger,
                 TimeOfDayTicks = TimeSpan.FromHours(3).Ticks
             }
         };

+ 1 - 1
src/Jellyfin.LiveTv/Channels/RefreshChannelsScheduledTask.cs

@@ -79,7 +79,7 @@ namespace Jellyfin.LiveTv.Channels
                 // Every so often
                 new TaskTriggerInfo
                 {
-                    Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks
+                    Type = TaskTriggerInfoType.IntervalTrigger, IntervalTicks = TimeSpan.FromHours(24).Ticks
                 }
             };
         }

+ 1 - 1
src/Jellyfin.LiveTv/Guide/RefreshGuideScheduledTask.cs

@@ -66,7 +66,7 @@ public class RefreshGuideScheduledTask : IScheduledTask, IConfigurableScheduledT
         {
             new TaskTriggerInfo
             {
-                Type = TaskTriggerInfo.TriggerInterval,
+                Type = TaskTriggerInfoType.IntervalTrigger,
                 IntervalTicks = TimeSpan.FromHours(24).Ticks
             }
         };

+ 50 - 0
tests/Jellyfin.LiveTv.Tests/Listings/ListingsManagerTests.cs

@@ -0,0 +1,50 @@
+using System;
+using Jellyfin.LiveTv.Configuration;
+using Jellyfin.LiveTv.Listings;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.LiveTv;
+using MediaBrowser.Model.Tasks;
+using Microsoft.Extensions.Logging;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.LiveTv.Tests.Listings;
+
+public class ListingsManagerTests
+{
+    private readonly IConfigurationManager _config;
+    private readonly IListingsProvider[] _listingsProviders;
+    private readonly ILogger<ListingsManager> _logger;
+    private readonly ITaskManager _taskManager;
+    private readonly ITunerHostManager _tunerHostManager;
+
+    public ListingsManagerTests()
+    {
+        _logger = Mock.Of<ILogger<ListingsManager>>();
+        _config = Mock.Of<IConfigurationManager>();
+        _taskManager = Mock.Of<ITaskManager>();
+        _tunerHostManager = Mock.Of<ITunerHostManager>();
+        _listingsProviders = new[] { Mock.Of<IListingsProvider>() };
+    }
+
+    [Fact]
+    public void DeleteListingsProvider_DeletesProvider()
+    {
+        // Arrange
+        var id = "MockId";
+        var manager = new ListingsManager(_logger, _config, _taskManager, _tunerHostManager, _listingsProviders);
+
+        Mock.Get(_config)
+            .Setup(x => x.GetConfiguration(It.IsAny<string>()))
+            .Returns(new LiveTvOptions { ListingProviders = [new ListingsProviderInfo { Id = id }] });
+
+        // Act
+        manager.DeleteListingsProvider(id);
+
+        // Assert
+        Assert.DoesNotContain(
+            _config.GetLiveTvConfiguration().ListingProviders,
+            p => p.Id.Equals(id, StringComparison.Ordinal));
+    }
+}