Bladeren bron

Merge branch 'master' into output-formatters

Claus Vium 4 jaren geleden
bovenliggende
commit
81c764e87f
59 gewijzigde bestanden met toevoegingen van 330 en 385 verwijderingen
  1. 1 0
      CONTRIBUTORS.md
  2. 1 1
      Dockerfile
  3. 1 1
      Dockerfile.arm
  4. 1 1
      Dockerfile.arm64
  5. 1 1
      Emby.Dlna/ContentDirectory/ControlHandler.cs
  6. 2 2
      Emby.Dlna/Didl/DidlBuilder.cs
  7. 1 1
      Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
  8. 8 4
      Emby.Server.Implementations/ApplicationHost.cs
  9. 1 1
      Emby.Server.Implementations/Channels/ChannelManager.cs
  10. 1 1
      Emby.Server.Implementations/Dto/DtoService.cs
  11. 4 3
      Emby.Server.Implementations/IO/FileRefresher.cs
  12. 1 1
      Emby.Server.Implementations/IO/LibraryMonitor.cs
  13. 0 32
      Emby.Server.Implementations/IO/ManagedFileSystem.cs
  14. 1 31
      Emby.Server.Implementations/IO/StreamHelper.cs
  15. 13 8
      Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
  16. 1 21
      Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
  17. 10 33
      Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
  18. 14 9
      Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
  19. 2 2
      Emby.Server.Implementations/Localization/Core/nb.json
  20. 60 3
      Emby.Server.Implementations/Localization/Core/nn.json
  21. 16 16
      Emby.Server.Implementations/Localization/Core/ta.json
  22. 28 31
      Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
  23. 21 21
      Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs
  24. 6 6
      Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs
  25. 7 7
      Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs
  26. 6 9
      Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs
  27. 7 10
      Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs
  28. 1 3
      Jellyfin.Api/Controllers/DisplayPreferencesController.cs
  29. 6 3
      Jellyfin.Api/Controllers/DlnaServerController.cs
  30. 6 1
      Jellyfin.Api/Controllers/DynamicHlsController.cs
  31. 11 25
      Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs
  32. 4 6
      Jellyfin.Server/CoreAppHost.cs
  33. 2 0
      Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs
  34. 0 36
      Jellyfin.Server/HealthChecks/JellyfinDbHealthCheck.cs
  35. 10 0
      Jellyfin.Server/Jellyfin.Server.csproj
  36. 3 2
      Jellyfin.Server/Startup.cs
  37. 0 0
      Jellyfin.Server/wwwroot/api-docs/redoc/custom.css
  38. 0 0
      Jellyfin.Server/wwwroot/api-docs/swagger/custom.css
  39. 2 9
      MediaBrowser.Controller/IDisplayPreferencesManager.cs
  40. 1 0
      MediaBrowser.Controller/LiveTv/ChannelInfo.cs
  41. 31 20
      MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
  42. 6 0
      MediaBrowser.Model/Configuration/EncodingOptions.cs
  43. 1 1
      MediaBrowser.Model/IO/FileSystemMetadata.cs
  44. 2 2
      MediaBrowser.Model/IO/IFileSystem.cs
  45. 0 1
      MediaBrowser.Model/IO/IShortcutHandler.cs
  46. 0 2
      MediaBrowser.Model/IO/IStreamHelper.cs
  47. 1 0
      MediaBrowser.Model/IO/IZipClient.cs
  48. 5 3
      MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs
  49. 13 5
      MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs
  50. 1 1
      debian/rules
  51. 1 1
      deployment/Dockerfile.docker.amd64
  52. 1 1
      deployment/Dockerfile.docker.arm64
  53. 1 1
      deployment/Dockerfile.docker.armhf
  54. 1 1
      deployment/build.linux.amd64
  55. 1 1
      deployment/build.macos
  56. 1 1
      deployment/build.portable
  57. 1 1
      deployment/build.windows.amd64
  58. 1 1
      fedora/jellyfin.spec
  59. 1 1
      windows/build-jellyfin.ps1

+ 1 - 0
CONTRIBUTORS.md

@@ -78,6 +78,7 @@
  - [nvllsvm](https://github.com/nvllsvm)
  - [nvllsvm](https://github.com/nvllsvm)
  - [nyanmisaka](https://github.com/nyanmisaka)
  - [nyanmisaka](https://github.com/nyanmisaka)
  - [oddstr13](https://github.com/oddstr13)
  - [oddstr13](https://github.com/oddstr13)
+ - [orryverducci](https://github.com/orryverducci)
  - [petermcneil](https://github.com/petermcneil)
  - [petermcneil](https://github.com/petermcneil)
  - [Phlogi](https://github.com/Phlogi)
  - [Phlogi](https://github.com/Phlogi)
  - [pjeanjean](https://github.com/pjeanjean)
  - [pjeanjean](https://github.com/pjeanjean)

+ 1 - 1
Dockerfile

@@ -14,7 +14,7 @@ COPY . .
 ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
 ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
 # because of changes in docker and systemd we need to not build in parallel at the moment
 # because of changes in docker and systemd we need to not build in parallel at the moment
 # see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
 # see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
-RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
+RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none"
 
 
 FROM debian:buster-slim
 FROM debian:buster-slim
 
 

+ 1 - 1
Dockerfile.arm

@@ -21,7 +21,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
 # Discard objs - may cause failures if exists
 # Discard objs - may cause failures if exists
 RUN find . -type d -name obj | xargs -r rm -r
 RUN find . -type d -name obj | xargs -r rm -r
 # Build
 # Build
-RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
+RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none"
 
 
 
 
 FROM multiarch/qemu-user-static:x86_64-arm as qemu
 FROM multiarch/qemu-user-static:x86_64-arm as qemu

+ 1 - 1
Dockerfile.arm64

@@ -21,7 +21,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
 # Discard objs - may cause failures if exists
 # Discard objs - may cause failures if exists
 RUN find . -type d -name obj | xargs -r rm -r
 RUN find . -type d -name obj | xargs -r rm -r
 # Build
 # Build
-RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
+RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none"
 
 
 FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
 FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
 FROM arm64v8/debian:buster-slim
 FROM arm64v8/debian:buster-slim

+ 1 - 1
Emby.Dlna/ContentDirectory/ControlHandler.cs

@@ -1363,7 +1363,7 @@ namespace Emby.Dlna.ContentDirectory
                 };
                 };
             }
             }
 
 
-            Logger.LogError("Error parsing item Id: {id}. Returning user root folder.", id);
+            Logger.LogError("Error parsing item Id: {Id}. Returning user root folder.", id);
 
 
             return new ServerItem(_libraryManager.GetUserRootFolder());
             return new ServerItem(_libraryManager.GetUserRootFolder());
         }
         }

+ 2 - 2
Emby.Dlna/Didl/DidlBuilder.cs

@@ -948,7 +948,7 @@ namespace Emby.Dlna.Didl
             }
             }
             catch (XmlException ex)
             catch (XmlException ex)
             {
             {
-                _logger.LogError(ex, "Error adding xml value: {value}", name);
+                _logger.LogError(ex, "Error adding xml value: {Value}", name);
             }
             }
         }
         }
 
 
@@ -960,7 +960,7 @@ namespace Emby.Dlna.Didl
             }
             }
             catch (XmlException ex)
             catch (XmlException ex)
             {
             {
-                _logger.LogError(ex, "Error adding xml value: {value}", value);
+                _logger.LogError(ex, "Error adding xml value: {Value}", value);
             }
             }
         }
         }
 
 

+ 1 - 1
Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs

@@ -308,7 +308,7 @@ namespace Emby.Server.Implementations.AppBase
             }
             }
             catch (Exception ex)
             catch (Exception ex)
             {
             {
-                Logger.LogError(ex, "Error loading configuration file: {path}", path);
+                Logger.LogError(ex, "Error loading configuration file: {Path}", path);
 
 
                 return Activator.CreateInstance(configurationType);
                 return Activator.CreateInstance(configurationType);
             }
             }

+ 8 - 4
Emby.Server.Implementations/ApplicationHost.cs

@@ -279,6 +279,10 @@ namespace Emby.Server.Implementations
                 Password = ServerConfigurationManager.Configuration.CertificatePassword
                 Password = ServerConfigurationManager.Configuration.CertificatePassword
             };
             };
             Certificate = GetCertificate(CertificateInfo);
             Certificate = GetCertificate(CertificateInfo);
+
+            ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version;
+            ApplicationVersionString = ApplicationVersion.ToString(3);
+            ApplicationUserAgent = Name.Replace(' ', '-') + "/" + ApplicationVersionString;
         }
         }
 
 
         public string ExpandVirtualPath(string path)
         public string ExpandVirtualPath(string path)
@@ -308,16 +312,16 @@ namespace Emby.Server.Implementations
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public Version ApplicationVersion { get; } = typeof(ApplicationHost).Assembly.GetName().Version;
+        public Version ApplicationVersion { get; }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public string ApplicationVersionString { get; } = typeof(ApplicationHost).Assembly.GetName().Version.ToString(3);
+        public string ApplicationVersionString { get; }
 
 
         /// <summary>
         /// <summary>
         /// Gets the current application user agent.
         /// Gets the current application user agent.
         /// </summary>
         /// </summary>
         /// <value>The application user agent.</value>
         /// <value>The application user agent.</value>
-        public string ApplicationUserAgent => Name.Replace(' ', '-') + "/" + ApplicationVersionString;
+        public string ApplicationUserAgent { get; }
 
 
         /// <summary>
         /// <summary>
         /// Gets the email address for use within a comment section of a user agent field.
         /// Gets the email address for use within a comment section of a user agent field.
@@ -1401,7 +1405,7 @@ namespace Emby.Server.Implementations
 
 
             foreach (var assembly in assemblies)
             foreach (var assembly in assemblies)
             {
             {
-                Logger.LogDebug("Found API endpoints in plugin {name}", assembly.FullName);
+                Logger.LogDebug("Found API endpoints in plugin {Name}", assembly.FullName);
                 yield return assembly;
                 yield return assembly;
             }
             }
         }
         }

+ 1 - 1
Emby.Server.Implementations/Channels/ChannelManager.cs

@@ -890,7 +890,7 @@ namespace Emby.Server.Implementations.Channels
             }
             }
             catch (Exception ex)
             catch (Exception ex)
             {
             {
-                _logger.LogError(ex, "Error writing to channel cache file: {path}", path);
+                _logger.LogError(ex, "Error writing to channel cache file: {Path}", path);
             }
             }
         }
         }
 
 

+ 1 - 1
Emby.Server.Implementations/Dto/DtoService.cs

@@ -197,7 +197,7 @@ namespace Emby.Server.Implementations.Dto
                 catch (Exception ex)
                 catch (Exception ex)
                 {
                 {
                     // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions
                     // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions
-                    _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {itemName}", item.Name);
+                    _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {ItemName}", item.Name);
                 }
                 }
             }
             }
 
 

+ 4 - 3
Emby.Server.Implementations/IO/FileRefresher.cs

@@ -149,7 +149,7 @@ namespace Emby.Server.Implementations.IO
                     continue;
                     continue;
                 }
                 }
 
 
-                _logger.LogInformation("{name} ({path}) will be refreshed.", item.Name, item.Path);
+                _logger.LogInformation("{Name} ({Path}) will be refreshed.", item.Name, item.Path);
 
 
                 try
                 try
                 {
                 {
@@ -160,11 +160,11 @@ namespace Emby.Server.Implementations.IO
                     // For now swallow and log.
                     // For now swallow and log.
                     // Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable)
                     // Research item: If an IOException occurs, the item may be in a disconnected state (media unavailable)
                     // Should we remove it from it's parent?
                     // Should we remove it from it's parent?
-                    _logger.LogError(ex, "Error refreshing {name}", item.Name);
+                    _logger.LogError(ex, "Error refreshing {Name}", item.Name);
                 }
                 }
                 catch (Exception ex)
                 catch (Exception ex)
                 {
                 {
-                    _logger.LogError(ex, "Error refreshing {name}", item.Name);
+                    _logger.LogError(ex, "Error refreshing {Name}", item.Name);
                 }
                 }
             }
             }
         }
         }
@@ -214,6 +214,7 @@ namespace Emby.Server.Implementations.IO
             }
             }
         }
         }
 
 
+        /// <inheritdoc />
         public void Dispose()
         public void Dispose()
         {
         {
             _disposed = true;
             _disposed = true;

+ 1 - 1
Emby.Server.Implementations/IO/LibraryMonitor.cs

@@ -88,7 +88,7 @@ namespace Emby.Server.Implementations.IO
                 }
                 }
                 catch (Exception ex)
                 catch (Exception ex)
                 {
                 {
-                    _logger.LogError(ex, "Error in ReportFileSystemChanged for {path}", path);
+                    _logger.LogError(ex, "Error in ReportFileSystemChanged for {Path}", path);
                 }
                 }
             }
             }
         }
         }

+ 0 - 32
Emby.Server.Implementations/IO/ManagedFileSystem.cs

@@ -398,30 +398,6 @@ namespace Emby.Server.Implementations.IO
             }
             }
         }
         }
 
 
-        public virtual void SetReadOnly(string path, bool isReadOnly)
-        {
-            if (OperatingSystem.Id != OperatingSystemId.Windows)
-            {
-                return;
-            }
-
-            var info = GetExtendedFileSystemInfo(path);
-
-            if (info.Exists && info.IsReadOnly != isReadOnly)
-            {
-                if (isReadOnly)
-                {
-                    File.SetAttributes(path, File.GetAttributes(path) | FileAttributes.ReadOnly);
-                }
-                else
-                {
-                    var attributes = File.GetAttributes(path);
-                    attributes = RemoveAttribute(attributes, FileAttributes.ReadOnly);
-                    File.SetAttributes(path, attributes);
-                }
-            }
-        }
-
         public virtual void SetAttributes(string path, bool isHidden, bool isReadOnly)
         public virtual void SetAttributes(string path, bool isHidden, bool isReadOnly)
         {
         {
             if (OperatingSystem.Id != OperatingSystemId.Windows)
             if (OperatingSystem.Id != OperatingSystemId.Windows)
@@ -707,14 +683,6 @@ namespace Emby.Server.Implementations.IO
             return Directory.EnumerateFileSystemEntries(path, "*", searchOption);
             return Directory.EnumerateFileSystemEntries(path, "*", searchOption);
         }
         }
 
 
-        public virtual void SetExecutable(string path)
-        {
-            if (OperatingSystem.Id == OperatingSystemId.Darwin)
-            {
-                RunProcess("chmod", "+x \"" + path + "\"", Path.GetDirectoryName(path));
-            }
-        }
-
         private static void RunProcess(string path, string args, string workingDirectory)
         private static void RunProcess(string path, string args, string workingDirectory)
         {
         {
             using (var process = Process.Start(new ProcessStartInfo
             using (var process = Process.Start(new ProcessStartInfo

+ 1 - 31
Emby.Server.Implementations/IO/StreamHelper.cs

@@ -11,8 +11,6 @@ namespace Emby.Server.Implementations.IO
 {
 {
     public class StreamHelper : IStreamHelper
     public class StreamHelper : IStreamHelper
     {
     {
-        private const int StreamCopyToBufferSize = 81920;
-
         public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken)
         public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken)
         {
         {
             byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
             byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
@@ -83,37 +81,9 @@ namespace Emby.Server.Implementations.IO
             }
             }
         }
         }
 
 
-        public async Task<int> CopyToAsync(Stream source, Stream destination, CancellationToken cancellationToken)
-        {
-            byte[] buffer = ArrayPool<byte>.Shared.Rent(StreamCopyToBufferSize);
-            try
-            {
-                int totalBytesRead = 0;
-
-                int bytesRead;
-                while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
-                {
-                    var bytesToWrite = bytesRead;
-
-                    if (bytesToWrite > 0)
-                    {
-                        await destination.WriteAsync(buffer, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
-
-                        totalBytesRead += bytesRead;
-                    }
-                }
-
-                return totalBytesRead;
-            }
-            finally
-            {
-                ArrayPool<byte>.Shared.Return(buffer);
-            }
-        }
-
         public async Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken)
         public async Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken)
         {
         {
-            byte[] buffer = ArrayPool<byte>.Shared.Rent(StreamCopyToBufferSize);
+            byte[] buffer = ArrayPool<byte>.Shared.Rent(IODefaults.CopyToBufferSize);
             try
             try
             {
             {
                 int bytesRead;
                 int bytesRead;

+ 13 - 8
Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs

@@ -52,10 +52,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                 _logger.LogInformation("Copying recording stream to file {0}", targetFile);
                 _logger.LogInformation("Copying recording stream to file {0}", targetFile);
 
 
                 // The media source is infinite so we need to handle stopping ourselves
                 // The media source is infinite so we need to handle stopping ourselves
-                var durationToken = new CancellationTokenSource(duration);
-                cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
+                using var durationToken = new CancellationTokenSource(duration);
+                using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token);
 
 
-                await directStreamProvider.CopyToAsync(output, cancellationToken).ConfigureAwait(false);
+                await directStreamProvider.CopyToAsync(output, cancellationTokenSource.Token).ConfigureAwait(false);
             }
             }
 
 
             _logger.LogInformation("Recording completed to file {0}", targetFile);
             _logger.LogInformation("Recording completed to file {0}", targetFile);
@@ -72,7 +72,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                 UserAgent = "Emby/3.0",
                 UserAgent = "Emby/3.0",
 
 
                 // Shouldn't matter but may cause issues
                 // Shouldn't matter but may cause issues
-                DecompressionMethod = CompressionMethods.None
+                DecompressionMethod = CompressionMethods.None,
+                CancellationToken = cancellationToken
             };
             };
 
 
             using (var response = await _httpClient.SendAsync(httpRequestOptions, HttpMethod.Get).ConfigureAwait(false))
             using (var response = await _httpClient.SendAsync(httpRequestOptions, HttpMethod.Get).ConfigureAwait(false))
@@ -88,10 +89,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                     _logger.LogInformation("Copying recording stream to file {0}", targetFile);
                     _logger.LogInformation("Copying recording stream to file {0}", targetFile);
 
 
                     // The media source if infinite so we need to handle stopping ourselves
                     // The media source if infinite so we need to handle stopping ourselves
-                    var durationToken = new CancellationTokenSource(duration);
-                    cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
-
-                    await _streamHelper.CopyUntilCancelled(response.Content, output, 81920, cancellationToken).ConfigureAwait(false);
+                    using var durationToken = new CancellationTokenSource(duration);
+                    using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token);
+
+                    await _streamHelper.CopyUntilCancelled(
+                        response.Content,
+                        output,
+                        IODefaults.CopyToBufferSize,
+                        cancellationTokenSource.Token).ConfigureAwait(false);
                 }
                 }
             }
             }
 
 

+ 1 - 21
Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs

@@ -604,11 +604,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             return Task.CompletedTask;
             return Task.CompletedTask;
         }
         }
 
 
-        public Task DeleteRecordingAsync(string recordingId, CancellationToken cancellationToken)
-        {
-            return Task.CompletedTask;
-        }
-
         public Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
         public Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
         {
         {
             throw new NotImplementedException();
             throw new NotImplementedException();
@@ -808,11 +803,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             return null;
             return null;
         }
         }
 
 
-        public IEnumerable<ActiveRecordingInfo> GetAllActiveRecordings()
-        {
-            return _activeRecordings.Values.Where(i => i.Timer.Status == RecordingStatus.InProgress && !i.CancellationTokenSource.IsCancellationRequested);
-        }
-
         public ActiveRecordingInfo GetActiveRecordingInfo(string path)
         public ActiveRecordingInfo GetActiveRecordingInfo(string path)
         {
         {
             if (string.IsNullOrWhiteSpace(path))
             if (string.IsNullOrWhiteSpace(path))
@@ -1015,16 +1005,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             throw new Exception("Tuner not found.");
             throw new Exception("Tuner not found.");
         }
         }
 
 
-        private MediaSourceInfo CloneMediaSource(MediaSourceInfo mediaSource, bool enableStreamSharing)
-        {
-            var json = _jsonSerializer.SerializeToString(mediaSource);
-            mediaSource = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);
-
-            mediaSource.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture) + "_" + mediaSource.Id;
-
-            return mediaSource;
-        }
-
         public async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken)
         public async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken)
         {
         {
             if (string.IsNullOrWhiteSpace(channelId))
             if (string.IsNullOrWhiteSpace(channelId))
@@ -1654,7 +1634,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
         {
         {
             if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http))
             if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http))
             {
             {
-                return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _config);
+                return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer);
             }
             }
 
 
             return new DirectRecorder(_logger, _httpClient, _streamHelper);
             return new DirectRecorder(_logger, _httpClient, _streamHelper);

+ 10 - 33
Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs

@@ -8,12 +8,9 @@ using System.IO;
 using System.Text;
 using System.Text;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
-using MediaBrowser.Common.Configuration;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
-using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Serialization;
@@ -26,26 +23,24 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
         private readonly ILogger _logger;
         private readonly ILogger _logger;
         private readonly IMediaEncoder _mediaEncoder;
         private readonly IMediaEncoder _mediaEncoder;
         private readonly IServerApplicationPaths _appPaths;
         private readonly IServerApplicationPaths _appPaths;
+        private readonly IJsonSerializer _json;
+        private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
+
         private bool _hasExited;
         private bool _hasExited;
         private Stream _logFileStream;
         private Stream _logFileStream;
         private string _targetPath;
         private string _targetPath;
         private Process _process;
         private Process _process;
-        private readonly IJsonSerializer _json;
-        private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
-        private readonly IServerConfigurationManager _config;
 
 
         public EncodedRecorder(
         public EncodedRecorder(
             ILogger logger,
             ILogger logger,
             IMediaEncoder mediaEncoder,
             IMediaEncoder mediaEncoder,
             IServerApplicationPaths appPaths,
             IServerApplicationPaths appPaths,
-            IJsonSerializer json,
-            IServerConfigurationManager config)
+            IJsonSerializer json)
         {
         {
             _logger = logger;
             _logger = logger;
             _mediaEncoder = mediaEncoder;
             _mediaEncoder = mediaEncoder;
             _appPaths = appPaths;
             _appPaths = appPaths;
             _json = json;
             _json = json;
-            _config = config;
         }
         }
 
 
         private static bool CopySubtitles => false;
         private static bool CopySubtitles => false;
@@ -58,19 +53,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
         public async Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
         public async Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
         {
         {
             // The media source is infinite so we need to handle stopping ourselves
             // The media source is infinite so we need to handle stopping ourselves
-            var durationToken = new CancellationTokenSource(duration);
-            cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
+            using var durationToken = new CancellationTokenSource(duration);
+            using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token);
 
 
-            await RecordFromFile(mediaSource, mediaSource.Path, targetFile, duration, onStarted, cancellationToken).ConfigureAwait(false);
+            await RecordFromFile(mediaSource, mediaSource.Path, targetFile, duration, onStarted, cancellationTokenSource.Token).ConfigureAwait(false);
 
 
             _logger.LogInformation("Recording completed to file {0}", targetFile);
             _logger.LogInformation("Recording completed to file {0}", targetFile);
         }
         }
 
 
-        private EncodingOptions GetEncodingOptions()
-        {
-            return _config.GetConfiguration<EncodingOptions>("encoding");
-        }
-
         private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
         private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
         {
         {
             _targetPath = targetFile;
             _targetPath = targetFile;
@@ -108,7 +98,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                 StartInfo = processStartInfo,
                 StartInfo = processStartInfo,
                 EnableRaisingEvents = true
                 EnableRaisingEvents = true
             };
             };
-            _process.Exited += (sender, args) => OnFfMpegProcessExited(_process, inputFile);
+            _process.Exited += (sender, args) => OnFfMpegProcessExited(_process);
 
 
             _process.Start();
             _process.Start();
 
 
@@ -221,20 +211,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
         }
         }
 
 
         protected string GetOutputSizeParam()
         protected string GetOutputSizeParam()
-        {
-            var filters = new List<string>();
-
-            filters.Add("yadif=0:-1:0");
-
-            var output = string.Empty;
-
-            if (filters.Count > 0)
-            {
-                output += string.Format(CultureInfo.InvariantCulture, " -vf \"{0}\"", string.Join(",", filters.ToArray()));
-            }
-
-            return output;
-        }
+            => "-vf \"yadif=0:-1:0\"";
 
 
         private void Stop()
         private void Stop()
         {
         {
@@ -291,7 +268,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
         /// <summary>
         /// <summary>
         /// Processes the exited.
         /// Processes the exited.
         /// </summary>
         /// </summary>
-        private void OnFfMpegProcessExited(Process process, string inputFile)
+        private void OnFfMpegProcessExited(Process process)
         {
         {
             using (process)
             using (process)
             {
             {

+ 14 - 9
Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs

@@ -24,14 +24,14 @@ namespace Emby.Server.Implementations.LiveTv.Listings
 {
 {
     public class SchedulesDirect : IListingsProvider
     public class SchedulesDirect : IListingsProvider
     {
     {
+        private const string ApiUrl = "https://json.schedulesdirect.org/20141201";
+
         private readonly ILogger<SchedulesDirect> _logger;
         private readonly ILogger<SchedulesDirect> _logger;
         private readonly IJsonSerializer _jsonSerializer;
         private readonly IJsonSerializer _jsonSerializer;
         private readonly IHttpClient _httpClient;
         private readonly IHttpClient _httpClient;
         private readonly SemaphoreSlim _tokenSemaphore = new SemaphoreSlim(1, 1);
         private readonly SemaphoreSlim _tokenSemaphore = new SemaphoreSlim(1, 1);
         private readonly IApplicationHost _appHost;
         private readonly IApplicationHost _appHost;
 
 
-        private const string ApiUrl = "https://json.schedulesdirect.org/20141201";
-
         public SchedulesDirect(
         public SchedulesDirect(
             ILogger<SchedulesDirect> logger,
             ILogger<SchedulesDirect> logger,
             IJsonSerializer jsonSerializer,
             IJsonSerializer jsonSerializer,
@@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
 
 
             while (start <= end)
             while (start <= end)
             {
             {
-                dates.Add(start.ToString("yyyy-MM-dd"));
+                dates.Add(start.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
                 start = start.AddDays(1);
                 start = start.AddDays(1);
             }
             }
 
 
@@ -367,13 +367,14 @@ namespace Emby.Server.Implementations.LiveTv.Listings
 
 
             if (!string.IsNullOrWhiteSpace(details.originalAirDate))
             if (!string.IsNullOrWhiteSpace(details.originalAirDate))
             {
             {
-                info.OriginalAirDate = DateTime.Parse(details.originalAirDate);
+                info.OriginalAirDate = DateTime.Parse(details.originalAirDate, CultureInfo.InvariantCulture);
                 info.ProductionYear = info.OriginalAirDate.Value.Year;
                 info.ProductionYear = info.OriginalAirDate.Value.Year;
             }
             }
 
 
             if (details.movie != null)
             if (details.movie != null)
             {
             {
-                if (!string.IsNullOrEmpty(details.movie.year) && int.TryParse(details.movie.year, out int year))
+                if (!string.IsNullOrEmpty(details.movie.year)
+                    && int.TryParse(details.movie.year, out int year))
                 {
                 {
                     info.ProductionYear = year;
                     info.ProductionYear = year;
                 }
                 }
@@ -587,7 +588,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
                 return null;
                 return null;
             }
             }
 
 
-            NameValuePair savedToken = null;
+            NameValuePair savedToken;
             if (!_tokens.TryGetValue(username, out savedToken))
             if (!_tokens.TryGetValue(username, out savedToken))
             {
             {
                 savedToken = new NameValuePair();
                 savedToken = new NameValuePair();
@@ -633,7 +634,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
             }
             }
         }
         }
 
 
-        private async Task<HttpResponseInfo> Post(HttpRequestOptions options,
+        private async Task<HttpResponseInfo> Post(
+            HttpRequestOptions options,
             bool enableRetry,
             bool enableRetry,
             ListingsProviderInfo providerInfo)
             ListingsProviderInfo providerInfo)
         {
         {
@@ -663,7 +665,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
             return await Post(options, false, providerInfo).ConfigureAwait(false);
             return await Post(options, false, providerInfo).ConfigureAwait(false);
         }
         }
 
 
-        private async Task<HttpResponseInfo> Get(HttpRequestOptions options,
+        private async Task<HttpResponseInfo> Get(
+            HttpRequestOptions options,
             bool enableRetry,
             bool enableRetry,
             ListingsProviderInfo providerInfo)
             ListingsProviderInfo providerInfo)
         {
         {
@@ -693,7 +696,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
             return await Get(options, false, providerInfo).ConfigureAwait(false);
             return await Get(options, false, providerInfo).ConfigureAwait(false);
         }
         }
 
 
-        private async Task<string> GetTokenInternal(string username, string password,
+        private async Task<string> GetTokenInternal(
+            string username,
+            string password,
             CancellationToken cancellationToken)
             CancellationToken cancellationToken)
         {
         {
             var httpOptions = new HttpRequestOptions()
             var httpOptions = new HttpRequestOptions()

+ 2 - 2
Emby.Server.Implementations/Localization/Core/nb.json

@@ -71,7 +71,7 @@
     "ScheduledTaskFailedWithName": "{0} mislykkes",
     "ScheduledTaskFailedWithName": "{0} mislykkes",
     "ScheduledTaskStartedWithName": "{0} startet",
     "ScheduledTaskStartedWithName": "{0} startet",
     "ServerNameNeedsToBeRestarted": "{0} må startes på nytt",
     "ServerNameNeedsToBeRestarted": "{0} må startes på nytt",
-    "Shows": "Programmer",
+    "Shows": "Program",
     "Songs": "Sanger",
     "Songs": "Sanger",
     "StartupEmbyServerIsLoading": "Jellyfin Server laster. Prøv igjen snart.",
     "StartupEmbyServerIsLoading": "Jellyfin Server laster. Prøv igjen snart.",
     "SubtitleDownloadFailureForItem": "En feil oppstå under nedlasting av undertekster for  {0}",
     "SubtitleDownloadFailureForItem": "En feil oppstå under nedlasting av undertekster for  {0}",
@@ -88,7 +88,7 @@
     "UserOnlineFromDevice": "{0} er tilkoblet fra {1}",
     "UserOnlineFromDevice": "{0} er tilkoblet fra {1}",
     "UserPasswordChangedWithName": "Passordet for {0} er oppdatert",
     "UserPasswordChangedWithName": "Passordet for {0} er oppdatert",
     "UserPolicyUpdatedWithName": "Brukerpolicyen har blitt oppdatert for {0}",
     "UserPolicyUpdatedWithName": "Brukerpolicyen har blitt oppdatert for {0}",
-    "UserStartedPlayingItemWithValues": "{0} har startet avspilling {1}",
+    "UserStartedPlayingItemWithValues": "{0} har startet avspilling {1} på {2}",
     "UserStoppedPlayingItemWithValues": "{0} har stoppet avspilling  {1}",
     "UserStoppedPlayingItemWithValues": "{0} har stoppet avspilling  {1}",
     "ValueHasBeenAddedToLibrary": "{0} har blitt lagt til i mediebiblioteket ditt",
     "ValueHasBeenAddedToLibrary": "{0} har blitt lagt til i mediebiblioteket ditt",
     "ValueSpecialEpisodeName": "Spesialepisode - {0}",
     "ValueSpecialEpisodeName": "Spesialepisode - {0}",

+ 60 - 3
Emby.Server.Implementations/Localization/Core/nn.json

@@ -35,7 +35,7 @@
     "AuthenticationSucceededWithUserName": "{0} Har logga inn",
     "AuthenticationSucceededWithUserName": "{0} Har logga inn",
     "Artists": "Artistar",
     "Artists": "Artistar",
     "Application": "Program",
     "Application": "Program",
-    "AppDeviceValues": "App: {0}, Einheit: {1}",
+    "AppDeviceValues": "App: {0}, Eining: {1}",
     "Albums": "Album",
     "Albums": "Album",
     "NotificationOptionServerRestartRequired": "Tenaren krev omstart",
     "NotificationOptionServerRestartRequired": "Tenaren krev omstart",
     "NotificationOptionPluginUpdateInstalled": "Tilleggsprogram-oppdatering vart installert",
     "NotificationOptionPluginUpdateInstalled": "Tilleggsprogram-oppdatering vart installert",
@@ -43,7 +43,7 @@
     "NotificationOptionPluginInstalled": "Tilleggsprogram installert",
     "NotificationOptionPluginInstalled": "Tilleggsprogram installert",
     "NotificationOptionPluginError": "Tilleggsprogram feila",
     "NotificationOptionPluginError": "Tilleggsprogram feila",
     "NotificationOptionNewLibraryContent": "Nytt innhald er lagt til",
     "NotificationOptionNewLibraryContent": "Nytt innhald er lagt til",
-    "NotificationOptionInstallationFailed": "Installasjonen feila",
+    "NotificationOptionInstallationFailed": "Installasjonsfeil",
     "NotificationOptionCameraImageUploaded": "Kamerabilde vart lasta opp",
     "NotificationOptionCameraImageUploaded": "Kamerabilde vart lasta opp",
     "NotificationOptionAudioPlaybackStopped": "Lydavspilling stoppa",
     "NotificationOptionAudioPlaybackStopped": "Lydavspilling stoppa",
     "NotificationOptionAudioPlayback": "Lydavspilling påbyrja",
     "NotificationOptionAudioPlayback": "Lydavspilling påbyrja",
@@ -56,5 +56,62 @@
     "MusicVideos": "Musikkvideoar",
     "MusicVideos": "Musikkvideoar",
     "Music": "Musikk",
     "Music": "Musikk",
     "Movies": "Filmar",
     "Movies": "Filmar",
-    "MixedContent": "Blanda innhald"
+    "MixedContent": "Blanda innhald",
+    "Sync": "Synkronisera",
+    "TaskDownloadMissingSubtitlesDescription": "Søk Internettet for manglande undertekstar basert på metadatainnstillingar.",
+    "TaskDownloadMissingSubtitles": "Last ned manglande undertekstar",
+    "TaskRefreshChannelsDescription": "Oppdater internettkanalinformasjon.",
+    "TaskRefreshChannels": "Oppdater kanalar",
+    "TaskCleanTranscodeDescription": "Slett transkodefiler som er meir enn ein dag gamal.",
+    "TaskCleanTranscode": "Reins transkodemappe",
+    "TaskUpdatePluginsDescription": "Laster ned og installerer oppdateringar for programtillegg som er sette opp til å oppdaterast automatisk.",
+    "TaskUpdatePlugins": "Oppdaterer programtillegg",
+    "TaskRefreshPeopleDescription": "Oppdaterer metadata for skodespelarar og regissørar i mediebiblioteket ditt.",
+    "TaskRefreshPeople": "Oppdater personar",
+    "TaskCleanLogsDescription": "Slett loggfiler som er meir enn {0} dagar gamle.",
+    "TaskCleanLogs": "Reins loggmappe",
+    "TaskRefreshLibraryDescription": "Skannar mediebiblioteket ditt for nye filer og oppdaterer metadata.",
+    "TaskRefreshLibrary": "Skann mediebibliotek",
+    "TaskRefreshChapterImagesDescription": "Lager miniatyrbilete for videoar som har kapittel.",
+    "TaskRefreshChapterImages": "Trekk ut kapittelbilete",
+    "TaskCleanCacheDescription": "Slettar mellomlagra filer som ikkje lengre trengst av systemet.",
+    "TaskCleanCache": "Rens mappe for hurtiglager",
+    "TasksChannelsCategory": "Internettkanalar",
+    "TasksApplicationCategory": "Applikasjon",
+    "TasksLibraryCategory": "Bibliotek",
+    "TasksMaintenanceCategory": "Vedlikehald",
+    "VersionNumber": "Versjon {0}",
+    "ValueSpecialEpisodeName": "Spesialepisode - {0}",
+    "ValueHasBeenAddedToLibrary": "{0} har blitt lagt til i mediebiblioteket ditt",
+    "UserStoppedPlayingItemWithValues": "{0} har fullført avspeling {1} på {2}",
+    "UserStartedPlayingItemWithValues": "{0} spelar {1} på {2}",
+    "UserPolicyUpdatedWithName": "Brukarreglar har blitt oppdatert for {0}",
+    "UserPasswordChangedWithName": "Passordet for {0} er oppdatert",
+    "UserOnlineFromDevice": "{0} er direktekopla frå {1}",
+    "UserOfflineFromDevice": "{0} har kopla frå {1}",
+    "UserLockedOutWithName": "Brukar {0} har blitt utestengd",
+    "UserDownloadingItemWithValues": "{0} lastar ned {1}",
+    "UserDeletedWithName": "Brukar {0} er sletta",
+    "UserCreatedWithName": "Brukar {0} er oppretta",
+    "User": "Brukar",
+    "TvShows": "TV-seriar",
+    "System": "System",
+    "SubtitleDownloadFailureFromForItem": "Feila å laste ned undertekstar frå {0} for {1}",
+    "StartupEmbyServerIsLoading": "Jellyfintenaren laster. Prøv igjen om litt.",
+    "Songs": "Songar",
+    "Shows": "Program",
+    "ServerNameNeedsToBeRestarted": "{0} må omstartast",
+    "ScheduledTaskStartedWithName": "{0} starta",
+    "ScheduledTaskFailedWithName": "{0} feila",
+    "ProviderValue": "Leverandør: {0}",
+    "PluginUpdatedWithName": "{0} blei oppdatert",
+    "PluginUninstalledWithName": "{0} blei avinstallert",
+    "PluginInstalledWithName": "{0} blei installert",
+    "Plugin": "Programtillegg",
+    "Playlists": "Speleliste",
+    "Photos": "Foto",
+    "NotificationOptionVideoPlaybackStopped": "Videoavspeling stoppa",
+    "NotificationOptionVideoPlayback": "Videoavspeling starta",
+    "NotificationOptionUserLockedOut": "Brukar er utestengd",
+    "NotificationOptionTaskFailed": "Planlagt oppgåve feila"
 }
 }

+ 16 - 16
Emby.Server.Implementations/Localization/Core/ta.json

@@ -18,7 +18,7 @@
     "MessageServerConfigurationUpdated": "சேவையக அமைப்புகள் புதுப்பிக்கப்பட்டன",
     "MessageServerConfigurationUpdated": "சேவையக அமைப்புகள் புதுப்பிக்கப்பட்டன",
     "MessageApplicationUpdatedTo": "ஜெல்லிஃபின் சேவையகம் {0} இற்கு புதுப்பிக்கப்பட்டது",
     "MessageApplicationUpdatedTo": "ஜெல்லிஃபின் சேவையகம் {0} இற்கு புதுப்பிக்கப்பட்டது",
     "MessageApplicationUpdated": "ஜெல்லிஃபின் சேவையகம் புதுப்பிக்கப்பட்டது",
     "MessageApplicationUpdated": "ஜெல்லிஃபின் சேவையகம் புதுப்பிக்கப்பட்டது",
-    "Inherit": "மரபரிமையாகப் பெறு",
+    "Inherit": "மரபரிமையாகப் பெறு",
     "HeaderRecordingGroups": "பதிவு குழுக்கள்",
     "HeaderRecordingGroups": "பதிவு குழுக்கள்",
     "HeaderCameraUploads": "புகைப்பட பதிவேற்றங்கள்",
     "HeaderCameraUploads": "புகைப்பட பதிவேற்றங்கள்",
     "Folders": "கோப்புறைகள்",
     "Folders": "கோப்புறைகள்",
@@ -31,7 +31,7 @@
     "TaskDownloadMissingSubtitles": "விடுபட்டுபோன வசன வரிகளைப் பதிவிறக்கு",
     "TaskDownloadMissingSubtitles": "விடுபட்டுபோன வசன வரிகளைப் பதிவிறக்கு",
     "TaskRefreshChannels": "சேனல்களை புதுப்பி",
     "TaskRefreshChannels": "சேனல்களை புதுப்பி",
     "TaskUpdatePlugins": "உட்செருகிகளை புதுப்பி",
     "TaskUpdatePlugins": "உட்செருகிகளை புதுப்பி",
-    "TaskRefreshLibrary": "மீடியா நூலகத்தை ஆராய்",
+    "TaskRefreshLibrary": "ஊடக நூலகத்தை ஆராய்",
     "TasksChannelsCategory": "இணைய சேனல்கள்",
     "TasksChannelsCategory": "இணைய சேனல்கள்",
     "TasksApplicationCategory": "செயலி",
     "TasksApplicationCategory": "செயலி",
     "TasksLibraryCategory": "நூலகம்",
     "TasksLibraryCategory": "நூலகம்",
@@ -46,7 +46,7 @@
     "Sync": "ஒத்திசைவு",
     "Sync": "ஒத்திசைவு",
     "StartupEmbyServerIsLoading": "ஜெல்லிஃபின் சேவையகம் துவங்குகிறது. சிறிது நேரம் கழித்து முயற்சிக்கவும்.",
     "StartupEmbyServerIsLoading": "ஜெல்லிஃபின் சேவையகம் துவங்குகிறது. சிறிது நேரம் கழித்து முயற்சிக்கவும்.",
     "Songs": "பாடல்கள்",
     "Songs": "பாடல்கள்",
-    "Shows": "தொடர்கள்",
+    "Shows": "நிகழ்ச்சிகள்",
     "ServerNameNeedsToBeRestarted": "{0} மறுதொடக்கம் செய்யப்பட வேண்டும்",
     "ServerNameNeedsToBeRestarted": "{0} மறுதொடக்கம் செய்யப்பட வேண்டும்",
     "ScheduledTaskStartedWithName": "{0} துவங்கியது",
     "ScheduledTaskStartedWithName": "{0} துவங்கியது",
     "ScheduledTaskFailedWithName": "{0} தோல்வியடைந்தது",
     "ScheduledTaskFailedWithName": "{0} தோல்வியடைந்தது",
@@ -67,20 +67,20 @@
     "NotificationOptionAudioPlayback": "ஒலி இசைக்கத் துவங்கியுள்ளது",
     "NotificationOptionAudioPlayback": "ஒலி இசைக்கத் துவங்கியுள்ளது",
     "NotificationOptionApplicationUpdateInstalled": "செயலி புதுப்பிக்கப்பட்டது",
     "NotificationOptionApplicationUpdateInstalled": "செயலி புதுப்பிக்கப்பட்டது",
     "NotificationOptionApplicationUpdateAvailable": "செயலியினை புதுப்பிக்கலாம்",
     "NotificationOptionApplicationUpdateAvailable": "செயலியினை புதுப்பிக்கலாம்",
-    "NameSeasonUnknown": "பருவம் அறியப்படாதவை",
+    "NameSeasonUnknown": "அறியப்படாத பருவம்",
     "NameSeasonNumber": "பருவம் {0}",
     "NameSeasonNumber": "பருவம் {0}",
     "NameInstallFailed": "{0} நிறுவல் தோல்வியடைந்தது",
     "NameInstallFailed": "{0} நிறுவல் தோல்வியடைந்தது",
     "MusicVideos": "இசைப்படங்கள்",
     "MusicVideos": "இசைப்படங்கள்",
     "Music": "இசை",
     "Music": "இசை",
     "Movies": "திரைப்படங்கள்",
     "Movies": "திரைப்படங்கள்",
-    "Latest": "புதிய",
+    "Latest": "புதியவை",
     "LabelRunningTimeValue": "ஓடும் நேரம்: {0}",
     "LabelRunningTimeValue": "ஓடும் நேரம்: {0}",
     "LabelIpAddressValue": "ஐபி முகவரி: {0}",
     "LabelIpAddressValue": "ஐபி முகவரி: {0}",
     "ItemRemovedWithName": "{0} நூலகத்திலிருந்து அகற்றப்பட்டது",
     "ItemRemovedWithName": "{0} நூலகத்திலிருந்து அகற்றப்பட்டது",
     "ItemAddedWithName": "{0} நூலகத்தில் சேர்க்கப்பட்டது",
     "ItemAddedWithName": "{0} நூலகத்தில் சேர்க்கப்பட்டது",
-    "HeaderNextUp": "அடுத்ததாக",
+    "HeaderNextUp": "அடுத்தத",
     "HeaderLiveTV": "நேரடித் தொலைக்காட்சி",
     "HeaderLiveTV": "நேரடித் தொலைக்காட்சி",
-    "HeaderFavoriteSongs": "பிடித்த பாட்டுகள்",
+    "HeaderFavoriteSongs": "பிடித்த பாட்கள்",
     "HeaderFavoriteShows": "பிடித்த தொடர்கள்",
     "HeaderFavoriteShows": "பிடித்த தொடர்கள்",
     "HeaderFavoriteEpisodes": "பிடித்த அத்தியாயங்கள்",
     "HeaderFavoriteEpisodes": "பிடித்த அத்தியாயங்கள்",
     "HeaderFavoriteArtists": "பிடித்த கலைஞர்கள்",
     "HeaderFavoriteArtists": "பிடித்த கலைஞர்கள்",
@@ -93,25 +93,25 @@
     "Channels": "சேனல்கள்",
     "Channels": "சேனல்கள்",
     "Books": "புத்தகங்கள்",
     "Books": "புத்தகங்கள்",
     "AuthenticationSucceededWithUserName": "{0} வெற்றிகரமாக அங்கீகரிக்கப்பட்டது",
     "AuthenticationSucceededWithUserName": "{0} வெற்றிகரமாக அங்கீகரிக்கப்பட்டது",
-    "Artists": "கலைஞர்",
+    "Artists": "கலைஞர்கள்",
     "Application": "செயலி",
     "Application": "செயலி",
     "Albums": "ஆல்பங்கள்",
     "Albums": "ஆல்பங்கள்",
     "NewVersionIsAvailable": "ஜெல்லிஃபின் சேவையகத்தின் புதிய பதிப்பு பதிவிறக்கத்திற்கு கிடைக்கிறது.",
     "NewVersionIsAvailable": "ஜெல்லிஃபின் சேவையகத்தின் புதிய பதிப்பு பதிவிறக்கத்திற்கு கிடைக்கிறது.",
-    "MessageNamedServerConfigurationUpdatedWithValue": "சேவையக உள்ளமைவு பிரிவு {0 புதுப்பிக்கப்பட்டது",
+    "MessageNamedServerConfigurationUpdatedWithValue": "சேவையக உள்ளமைவு பிரிவு {0} புதுப்பிக்கப்பட்டது",
     "TaskCleanCacheDescription": "கணினிக்கு இனி தேவைப்படாத தற்காலிக கோப்புகளை நீக்கு.",
     "TaskCleanCacheDescription": "கணினிக்கு இனி தேவைப்படாத தற்காலிக கோப்புகளை நீக்கு.",
     "UserOfflineFromDevice": "{0} இலிருந்து {1} துண்டிக்கப்பட்டுள்ளது",
     "UserOfflineFromDevice": "{0} இலிருந்து {1} துண்டிக்கப்பட்டுள்ளது",
-    "SubtitleDownloadFailureFromForItem": "வசன வரிகள் {0 } இலிருந்து {1} க்கு பதிவிறக்கத் தவறிவிட்டன",
-    "TaskDownloadMissingSubtitlesDescription": "மெட்டாடேட்டா உள்ளமைவின் அடிப்படையில் வசன வரிகள் காணாமல் போனதற்கு இணையத்தைத் தேடுகிறது.",
+    "SubtitleDownloadFailureFromForItem": "வசன வரிகள் {0} இலிருந்து {1} க்கு பதிவிறக்கத் தவறிவிட்டன",
+    "TaskDownloadMissingSubtitlesDescription": "மீத்தரவு உள்ளமைவின் அடிப்படையில் வசன வரிகள் காணாமல் போனதற்கு இணையத்தைத் தேடுகிறது.",
     "TaskCleanTranscodeDescription": "டிரான்ஸ்கோட் கோப்புகளை ஒரு நாளுக்கு மேல் பழையதாக நீக்குகிறது.",
     "TaskCleanTranscodeDescription": "டிரான்ஸ்கோட் கோப்புகளை ஒரு நாளுக்கு மேல் பழையதாக நீக்குகிறது.",
-    "TaskUpdatePluginsDescription": "தானாகவே புதுப்பிக்க கட்டமைக்கப்பட்ட செருகுநிரல்களுக்கான புதுப்பிப்புகளை பதிவிறக்குகிறது மற்றும் நிறுவுகிறது.",
-    "TaskRefreshPeopleDescription": "உங்கள் மீடியா நூலகத்தில் உள்ள நடிகர்கள் மற்றும் இயக்குனர்களுக்கான மெட்டாடேட்டாவை புதுப்பிக்கும்.",
+    "TaskUpdatePluginsDescription": "தானாகவே புதுப்பிக்க கட்டமைக்கப்பட்ட உட்செருகிகளுக்கான புதுப்பிப்புகளை பதிவிறக்குகிறது மற்றும் நிறுவுகிறது.",
+    "TaskRefreshPeopleDescription": "உங்கள் ஊடக நூலகத்தில் உள்ள நடிகர்கள் மற்றும் இயக்குனர்களுக்கான மீத்தரவை புதுப்பிக்கும்.",
     "TaskCleanLogsDescription": "{0} நாட்களுக்கு மேல் இருக்கும் பதிவு கோப்புகளை நீக்கும்.",
     "TaskCleanLogsDescription": "{0} நாட்களுக்கு மேல் இருக்கும் பதிவு கோப்புகளை நீக்கும்.",
-    "TaskCleanLogs": "பதிவு அடைவ சுத்தம் செய்யுங்கள்",
-    "TaskRefreshLibraryDescription": "புதிய கோப்புகளுக்காக உங்கள் மீடியா நூலகத்தை ஸ்கேன் செய்து மீத்தரவை புதுப்பிக்கும்.",
+    "TaskCleanLogs": "பதிவு அடைவ சுத்தம் செய்யுங்கள்",
+    "TaskRefreshLibraryDescription": "புதிய கோப்புகளுக்காக உங்கள் ஊடக நூலகத்தை ஆராய்ந்து மீத்தரவை புதுப்பிக்கும்.",
     "TaskRefreshChapterImagesDescription": "அத்தியாயங்களைக் கொண்ட வீடியோக்களுக்கான சிறு உருவங்களை உருவாக்குகிறது.",
     "TaskRefreshChapterImagesDescription": "அத்தியாயங்களைக் கொண்ட வீடியோக்களுக்கான சிறு உருவங்களை உருவாக்குகிறது.",
     "ValueHasBeenAddedToLibrary": "உங்கள் மீடியா நூலகத்தில் {0} சேர்க்கப்பட்டது",
     "ValueHasBeenAddedToLibrary": "உங்கள் மீடியா நூலகத்தில் {0} சேர்க்கப்பட்டது",
     "UserOnlineFromDevice": "{1} இருந்து {0} ஆன்லைன்",
     "UserOnlineFromDevice": "{1} இருந்து {0} ஆன்லைன்",
     "HomeVideos": "முகப்பு வீடியோக்கள்",
     "HomeVideos": "முகப்பு வீடியோக்கள்",
-    "UserStoppedPlayingItemWithValues": "{2} இல் {1} முடித்துவிட்டது",
+    "UserStoppedPlayingItemWithValues": "{0} {2} இல் {1} முடித்துவிட்டது",
     "UserStartedPlayingItemWithValues": "{0} {2}இல் {1} ஐ இயக்குகிறது"
     "UserStartedPlayingItemWithValues": "{0} {2}இல் {1} ஐ இயக்குகிறது"
 }
 }

+ 28 - 31
Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs

@@ -5,10 +5,10 @@ using System.Linq;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Tasks;
 using MediaBrowser.Model.Tasks;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
-using MediaBrowser.Model.Globalization;
 
 
 namespace Emby.Server.Implementations.ScheduledTasks.Tasks
 namespace Emby.Server.Implementations.ScheduledTasks.Tasks
 {
 {
@@ -21,10 +21,8 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
         /// Gets or sets the application paths.
         /// Gets or sets the application paths.
         /// </summary>
         /// </summary>
         /// <value>The application paths.</value>
         /// <value>The application paths.</value>
-        private IApplicationPaths ApplicationPaths { get; set; }
-
+        private readonly IApplicationPaths _applicationPaths;
         private readonly ILogger<DeleteCacheFileTask> _logger;
         private readonly ILogger<DeleteCacheFileTask> _logger;
-
         private readonly IFileSystem _fileSystem;
         private readonly IFileSystem _fileSystem;
         private readonly ILocalizationManager _localization;
         private readonly ILocalizationManager _localization;
 
 
@@ -37,20 +35,41 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
             IFileSystem fileSystem,
             IFileSystem fileSystem,
             ILocalizationManager localization)
             ILocalizationManager localization)
         {
         {
-            ApplicationPaths = appPaths;
+            _applicationPaths = appPaths;
             _logger = logger;
             _logger = logger;
             _fileSystem = fileSystem;
             _fileSystem = fileSystem;
             _localization = localization;
             _localization = localization;
         }
         }
 
 
+        /// <inheritdoc />
+        public string Name => _localization.GetLocalizedString("TaskCleanCache");
+
+        /// <inheritdoc />
+        public string Description => _localization.GetLocalizedString("TaskCleanCacheDescription");
+
+        /// <inheritdoc />
+        public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
+
+        /// <inheritdoc />
+        public string Key => "DeleteCacheFiles";
+
+        /// <inheritdoc />
+        public bool IsHidden => false;
+
+        /// <inheritdoc />
+        public bool IsEnabled => true;
+
+        /// <inheritdoc />
+        public bool IsLogged => true;
+
         /// <summary>
         /// <summary>
         /// Creates the triggers that define when the task will run.
         /// Creates the triggers that define when the task will run.
         /// </summary>
         /// </summary>
         /// <returns>IEnumerable{BaseTaskTrigger}.</returns>
         /// <returns>IEnumerable{BaseTaskTrigger}.</returns>
         public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
         public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
         {
         {
-            return new[] {
-
+            return new[]
+            {
                 // Every so often
                 // Every so often
                 new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
                 new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
             };
             };
@@ -68,7 +87,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
 
 
             try
             try
             {
             {
-                DeleteCacheFilesFromDirectory(cancellationToken, ApplicationPaths.CachePath, minDateModified, progress);
+                DeleteCacheFilesFromDirectory(cancellationToken, _applicationPaths.CachePath, minDateModified, progress);
             }
             }
             catch (DirectoryNotFoundException)
             catch (DirectoryNotFoundException)
             {
             {
@@ -81,7 +100,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
 
 
             try
             try
             {
             {
-                DeleteCacheFilesFromDirectory(cancellationToken, ApplicationPaths.TempDirectory, minDateModified, progress);
+                DeleteCacheFilesFromDirectory(cancellationToken, _applicationPaths.TempDirectory, minDateModified, progress);
             }
             }
             catch (DirectoryNotFoundException)
             catch (DirectoryNotFoundException)
             {
             {
@@ -91,7 +110,6 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
             return Task.CompletedTask;
             return Task.CompletedTask;
         }
         }
 
 
-
         /// <summary>
         /// <summary>
         /// Deletes the cache files from directory with a last write time less than a given date.
         /// Deletes the cache files from directory with a last write time less than a given date.
         /// </summary>
         /// </summary>
@@ -164,26 +182,5 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
                 _logger.LogError(ex, "Error deleting file {path}", path);
                 _logger.LogError(ex, "Error deleting file {path}", path);
             }
             }
         }
         }
-
-        /// <inheritdoc />
-        public string Name => _localization.GetLocalizedString("TaskCleanCache");
-
-        /// <inheritdoc />
-        public string Description => _localization.GetLocalizedString("TaskCleanCacheDescription");
-
-        /// <inheritdoc />
-        public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
-
-        /// <inheritdoc />
-        public string Key => "DeleteCacheFiles";
-
-        /// <inheritdoc />
-        public bool IsHidden => false;
-
-        /// <inheritdoc />
-        public bool IsEnabled => true;
-
-        /// <inheritdoc />
-        public bool IsLogged => true;
     }
     }
 }
 }

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

@@ -34,6 +34,27 @@ namespace Emby.Server.Implementations.ScheduledTasks
             _localization = localization;
             _localization = localization;
         }
         }
 
 
+        /// <inheritdoc />
+        public string Name => _localization.GetLocalizedString("TaskUpdatePlugins");
+
+        /// <inheritdoc />
+        public string Description => _localization.GetLocalizedString("TaskUpdatePluginsDescription");
+
+        /// <inheritdoc />
+        public string Category => _localization.GetLocalizedString("TasksApplicationCategory");
+
+        /// <inheritdoc />
+        public string Key => "PluginUpdates";
+
+        /// <inheritdoc />
+        public bool IsHidden => false;
+
+        /// <inheritdoc />
+        public bool IsEnabled => true;
+
+        /// <inheritdoc />
+        public bool IsLogged => true;
+
         /// <summary>
         /// <summary>
         /// Creates the triggers that define when the task will run.
         /// Creates the triggers that define when the task will run.
         /// </summary>
         /// </summary>
@@ -98,26 +119,5 @@ namespace Emby.Server.Implementations.ScheduledTasks
 
 
             progress.Report(100);
             progress.Report(100);
         }
         }
-
-        /// <inheritdoc />
-        public string Name => _localization.GetLocalizedString("TaskUpdatePlugins");
-
-        /// <inheritdoc />
-        public string Description => _localization.GetLocalizedString("TaskUpdatePluginsDescription");
-
-        /// <inheritdoc />
-        public string Category => _localization.GetLocalizedString("TasksApplicationCategory");
-
-        /// <inheritdoc />
-        public string Key => "PluginUpdates";
-
-        /// <inheritdoc />
-        public bool IsHidden => false;
-
-        /// <inheritdoc />
-        public bool IsEnabled => true;
-
-        /// <inheritdoc />
-        public bool IsLogged => true;
     }
     }
 }
 }

+ 6 - 6
Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs

@@ -11,7 +11,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
     public class DailyTrigger : ITaskTrigger
     public class DailyTrigger : ITaskTrigger
     {
     {
         /// <summary>
         /// <summary>
-        /// Get the time of day to trigger the task to run.
+        /// Occurs when [triggered].
+        /// </summary>
+        public event EventHandler<EventArgs> Triggered;
+
+        /// <summary>
+        /// Gets or sets the time of day to trigger the task to run.
         /// </summary>
         /// </summary>
         /// <value>The time of day.</value>
         /// <value>The time of day.</value>
         public TimeSpan TimeOfDay { get; set; }
         public TimeSpan TimeOfDay { get; set; }
@@ -69,11 +74,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
             }
             }
         }
         }
 
 
-        /// <summary>
-        /// Occurs when [triggered].
-        /// </summary>
-        public event EventHandler<EventArgs> Triggered;
-
         /// <summary>
         /// <summary>
         /// Called when [triggered].
         /// Called when [triggered].
         /// </summary>
         /// </summary>

+ 7 - 7
Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs

@@ -11,6 +11,13 @@ namespace Emby.Server.Implementations.ScheduledTasks
     /// </summary>
     /// </summary>
     public class IntervalTrigger : ITaskTrigger
     public class IntervalTrigger : ITaskTrigger
     {
     {
+        private DateTime _lastStartDate;
+
+        /// <summary>
+        /// Occurs when [triggered].
+        /// </summary>
+        public event EventHandler<EventArgs> Triggered;
+
         /// <summary>
         /// <summary>
         /// Gets or sets the interval.
         /// Gets or sets the interval.
         /// </summary>
         /// </summary>
@@ -28,8 +35,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
         /// <value>The timer.</value>
         /// <value>The timer.</value>
         private Timer Timer { get; set; }
         private Timer Timer { get; set; }
 
 
-        private DateTime _lastStartDate;
-
         /// <summary>
         /// <summary>
         /// Stars waiting for the trigger action.
         /// Stars waiting for the trigger action.
         /// </summary>
         /// </summary>
@@ -88,11 +93,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
             }
             }
         }
         }
 
 
-        /// <summary>
-        /// Occurs when [triggered].
-        /// </summary>
-        public event EventHandler<EventArgs> Triggered;
-
         /// <summary>
         /// <summary>
         /// Called when [triggered].
         /// Called when [triggered].
         /// </summary>
         /// </summary>

+ 6 - 9
Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs

@@ -12,6 +12,11 @@ namespace Emby.Server.Implementations.ScheduledTasks
     /// </summary>
     /// </summary>
     public class StartupTrigger : ITaskTrigger
     public class StartupTrigger : ITaskTrigger
     {
     {
+        /// <summary>
+        /// Occurs when [triggered].
+        /// </summary>
+        public event EventHandler<EventArgs> Triggered;
+
         public int DelayMs { get; set; }
         public int DelayMs { get; set; }
 
 
         /// <summary>
         /// <summary>
@@ -48,20 +53,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
         {
         {
         }
         }
 
 
-        /// <summary>
-        /// Occurs when [triggered].
-        /// </summary>
-        public event EventHandler<EventArgs> Triggered;
-
         /// <summary>
         /// <summary>
         /// Called when [triggered].
         /// Called when [triggered].
         /// </summary>
         /// </summary>
         private void OnTriggered()
         private void OnTriggered()
         {
         {
-            if (Triggered != null)
-            {
-                Triggered(this, EventArgs.Empty);
-            }
+            Triggered?.Invoke(this, EventArgs.Empty);
         }
         }
     }
     }
 }
 }

+ 7 - 10
Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs

@@ -11,7 +11,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
     public class WeeklyTrigger : ITaskTrigger
     public class WeeklyTrigger : ITaskTrigger
     {
     {
         /// <summary>
         /// <summary>
-        /// Get the time of day to trigger the task to run.
+        /// Occurs when [triggered].
+        /// </summary>
+        public event EventHandler<EventArgs> Triggered;
+
+        /// <summary>
+        /// Gets or sets the time of day to trigger the task to run.
         /// </summary>
         /// </summary>
         /// <value>The time of day.</value>
         /// <value>The time of day.</value>
         public TimeSpan TimeOfDay { get; set; }
         public TimeSpan TimeOfDay { get; set; }
@@ -95,20 +100,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
             }
             }
         }
         }
 
 
-        /// <summary>
-        /// Occurs when [triggered].
-        /// </summary>
-        public event EventHandler<EventArgs> Triggered;
-
         /// <summary>
         /// <summary>
         /// Called when [triggered].
         /// Called when [triggered].
         /// </summary>
         /// </summary>
         private void OnTriggered()
         private void OnTriggered()
         {
         {
-            if (Triggered != null)
-            {
-                Triggered(this, EventArgs.Empty);
-            }
+            Triggered?.Invoke(this, EventArgs.Empty);
         }
         }
     }
     }
 }
 }

+ 1 - 3
Jellyfin.Api/Controllers/DisplayPreferencesController.cs

@@ -153,7 +153,6 @@ namespace Jellyfin.Api.Controllers
             {
             {
                 var itemPreferences = _displayPreferencesManager.GetItemDisplayPreferences(existingDisplayPreferences.UserId, Guid.Parse(key.Substring("landing-".Length)), existingDisplayPreferences.Client);
                 var itemPreferences = _displayPreferencesManager.GetItemDisplayPreferences(existingDisplayPreferences.UserId, Guid.Parse(key.Substring("landing-".Length)), existingDisplayPreferences.Client);
                 itemPreferences.ViewType = Enum.Parse<ViewType>(displayPreferences.ViewType);
                 itemPreferences.ViewType = Enum.Parse<ViewType>(displayPreferences.ViewType);
-                _displayPreferencesManager.SaveChanges(itemPreferences);
             }
             }
 
 
             var itemPrefs = _displayPreferencesManager.GetItemDisplayPreferences(existingDisplayPreferences.UserId, Guid.Empty, existingDisplayPreferences.Client);
             var itemPrefs = _displayPreferencesManager.GetItemDisplayPreferences(existingDisplayPreferences.UserId, Guid.Empty, existingDisplayPreferences.Client);
@@ -167,8 +166,7 @@ namespace Jellyfin.Api.Controllers
                 itemPrefs.ViewType = viewType;
                 itemPrefs.ViewType = viewType;
             }
             }
 
 
-            _displayPreferencesManager.SaveChanges(existingDisplayPreferences);
-            _displayPreferencesManager.SaveChanges(itemPrefs);
+            _displayPreferencesManager.SaveChanges();
 
 
             return NoContent();
             return NoContent();
         }
         }

+ 6 - 3
Jellyfin.Api/Controllers/DlnaServerController.cs

@@ -60,7 +60,8 @@ namespace Jellyfin.Api.Controllers
         /// <response code="200">Dlna content directory returned.</response>
         /// <response code="200">Dlna content directory returned.</response>
         /// <returns>An <see cref="OkResult"/> containing the dlna content directory xml.</returns>
         /// <returns>An <see cref="OkResult"/> containing the dlna content directory xml.</returns>
         [HttpGet("{serverId}/ContentDirectory")]
         [HttpGet("{serverId}/ContentDirectory")]
-        [HttpGet("{serverId}/ContentDirectory.xml", Name = "GetContentDirectory_2")]
+        [HttpGet("{serverId}/ContentDirectory/ContentDirectory", Name = "GetContentDirectory_2")]
+        [HttpGet("{serverId}/ContentDirectory/ContentDirectory.xml", Name = "GetContentDirectory_3")]
         [Produces(MediaTypeNames.Text.Xml)]
         [Produces(MediaTypeNames.Text.Xml)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
@@ -75,7 +76,8 @@ namespace Jellyfin.Api.Controllers
         /// <param name="serverId">Server UUID.</param>
         /// <param name="serverId">Server UUID.</param>
         /// <returns>Dlna media receiver registrar xml.</returns>
         /// <returns>Dlna media receiver registrar xml.</returns>
         [HttpGet("{serverId}/MediaReceiverRegistrar")]
         [HttpGet("{serverId}/MediaReceiverRegistrar")]
-        [HttpGet("{serverId}/MediaReceiverRegistrar.xml", Name = "GetMediaReceiverRegistrar_2")]
+        [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar", Name = "GetMediaReceiverRegistrar_2")]
+        [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar.xml", Name = "GetMediaReceiverRegistrar_3")]
         [Produces(MediaTypeNames.Text.Xml)]
         [Produces(MediaTypeNames.Text.Xml)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
@@ -90,7 +92,8 @@ namespace Jellyfin.Api.Controllers
         /// <param name="serverId">Server UUID.</param>
         /// <param name="serverId">Server UUID.</param>
         /// <returns>Dlna media receiver registrar xml.</returns>
         /// <returns>Dlna media receiver registrar xml.</returns>
         [HttpGet("{serverId}/ConnectionManager")]
         [HttpGet("{serverId}/ConnectionManager")]
-        [HttpGet("{serverId}/ConnectionManager.xml", Name = "GetConnectionManager_2")]
+        [HttpGet("{serverId}/ConnectionManager/ConnectionManager", Name = "GetConnectionManager_2")]
+        [HttpGet("{serverId}/ConnectionManager/ConnectionManager.xml", Name = "GetConnectionManager_3")]
         [Produces(MediaTypeNames.Text.Xml)]
         [Produces(MediaTypeNames.Text.Xml)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]

+ 6 - 1
Jellyfin.Api/Controllers/DynamicHlsController.cs

@@ -1354,15 +1354,20 @@ namespace Jellyfin.Api.Controllers
                 segmentFormat = "mpegts";
                 segmentFormat = "mpegts";
             }
             }
 
 
+            var maxMuxingQueueSize = encodingOptions.MaxMuxingQueueSize > 128
+                ? encodingOptions.MaxMuxingQueueSize.ToString(CultureInfo.InvariantCulture)
+                : "128";
+
             return string.Format(
             return string.Format(
                 CultureInfo.InvariantCulture,
                 CultureInfo.InvariantCulture,
-                "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size 2048 -f hls -max_delay 5000000 -hls_time {6} -individual_header_trailer 0 -hls_segment_type {7} -start_number {8} -hls_segment_filename \"{9}\" -hls_playlist_type vod -hls_list_size 0 -y \"{10}\"",
+                "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size {6} -f hls -max_delay 5000000 -hls_time {7} -individual_header_trailer 0 -hls_segment_type {8} -start_number {9} -hls_segment_filename \"{10}\" -hls_playlist_type vod -hls_list_size 0 -y \"{11}\"",
                 inputModifier,
                 inputModifier,
                 _encodingHelper.GetInputArgument(state, encodingOptions),
                 _encodingHelper.GetInputArgument(state, encodingOptions),
                 threads,
                 threads,
                 mapArgs,
                 mapArgs,
                 GetVideoArguments(state, encodingOptions, startNumber),
                 GetVideoArguments(state, encodingOptions, startNumber),
                 GetAudioArguments(state, encodingOptions),
                 GetAudioArguments(state, encodingOptions),
+                maxMuxingQueueSize,
                 state.SegmentLength.ToString(CultureInfo.InvariantCulture),
                 state.SegmentLength.ToString(CultureInfo.InvariantCulture),
                 segmentFormat,
                 segmentFormat,
                 startNumberParam,
                 startNumberParam,

+ 11 - 25
Jellyfin.Server.Implementations/Users/DisplayPreferencesManager.cs

@@ -14,22 +14,21 @@ namespace Jellyfin.Server.Implementations.Users
     /// </summary>
     /// </summary>
     public class DisplayPreferencesManager : IDisplayPreferencesManager
     public class DisplayPreferencesManager : IDisplayPreferencesManager
     {
     {
-        private readonly JellyfinDbProvider _dbProvider;
+        private readonly JellyfinDb _dbContext;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="DisplayPreferencesManager"/> class.
         /// Initializes a new instance of the <see cref="DisplayPreferencesManager"/> class.
         /// </summary>
         /// </summary>
-        /// <param name="dbProvider">The Jellyfin db provider.</param>
-        public DisplayPreferencesManager(JellyfinDbProvider dbProvider)
+        /// <param name="dbContext">The database context.</param>
+        public DisplayPreferencesManager(JellyfinDb dbContext)
         {
         {
-            _dbProvider = dbProvider;
+            _dbContext = dbContext;
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
         public DisplayPreferences GetDisplayPreferences(Guid userId, string client)
         public DisplayPreferences GetDisplayPreferences(Guid userId, string client)
         {
         {
-            using var dbContext = _dbProvider.CreateContext();
-            var prefs = dbContext.DisplayPreferences
+            var prefs = _dbContext.DisplayPreferences
                 .Include(pref => pref.HomeSections)
                 .Include(pref => pref.HomeSections)
                 .FirstOrDefault(pref =>
                 .FirstOrDefault(pref =>
                     pref.UserId == userId && string.Equals(pref.Client, client));
                     pref.UserId == userId && string.Equals(pref.Client, client));
@@ -37,7 +36,7 @@ namespace Jellyfin.Server.Implementations.Users
             if (prefs == null)
             if (prefs == null)
             {
             {
                 prefs = new DisplayPreferences(userId, client);
                 prefs = new DisplayPreferences(userId, client);
-                dbContext.DisplayPreferences.Add(prefs);
+                _dbContext.DisplayPreferences.Add(prefs);
             }
             }
 
 
             return prefs;
             return prefs;
@@ -46,14 +45,13 @@ namespace Jellyfin.Server.Implementations.Users
         /// <inheritdoc />
         /// <inheritdoc />
         public ItemDisplayPreferences GetItemDisplayPreferences(Guid userId, Guid itemId, string client)
         public ItemDisplayPreferences GetItemDisplayPreferences(Guid userId, Guid itemId, string client)
         {
         {
-            using var dbContext = _dbProvider.CreateContext();
-            var prefs = dbContext.ItemDisplayPreferences
+            var prefs = _dbContext.ItemDisplayPreferences
                 .FirstOrDefault(pref => pref.UserId == userId && pref.ItemId == itemId && string.Equals(pref.Client, client));
                 .FirstOrDefault(pref => pref.UserId == userId && pref.ItemId == itemId && string.Equals(pref.Client, client));
 
 
             if (prefs == null)
             if (prefs == null)
             {
             {
                 prefs = new ItemDisplayPreferences(userId, Guid.Empty, client);
                 prefs = new ItemDisplayPreferences(userId, Guid.Empty, client);
-                dbContext.ItemDisplayPreferences.Add(prefs);
+                _dbContext.ItemDisplayPreferences.Add(prefs);
             }
             }
 
 
             return prefs;
             return prefs;
@@ -62,27 +60,15 @@ namespace Jellyfin.Server.Implementations.Users
         /// <inheritdoc />
         /// <inheritdoc />
         public IList<ItemDisplayPreferences> ListItemDisplayPreferences(Guid userId, string client)
         public IList<ItemDisplayPreferences> ListItemDisplayPreferences(Guid userId, string client)
         {
         {
-            using var dbContext = _dbProvider.CreateContext();
-
-            return dbContext.ItemDisplayPreferences
+            return _dbContext.ItemDisplayPreferences
                 .Where(prefs => prefs.UserId == userId && prefs.ItemId != Guid.Empty && string.Equals(prefs.Client, client))
                 .Where(prefs => prefs.UserId == userId && prefs.ItemId != Guid.Empty && string.Equals(prefs.Client, client))
                 .ToList();
                 .ToList();
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
-        public void SaveChanges(DisplayPreferences preferences)
-        {
-            using var dbContext = _dbProvider.CreateContext();
-            dbContext.Update(preferences);
-            dbContext.SaveChanges();
-        }
-
-        /// <inheritdoc />
-        public void SaveChanges(ItemDisplayPreferences preferences)
+        public void SaveChanges()
         {
         {
-            using var dbContext = _dbProvider.CreateContext();
-            dbContext.Update(preferences);
-            dbContext.SaveChanges();
+            _dbContext.SaveChanges();
         }
         }
     }
     }
 }
 }

+ 4 - 6
Jellyfin.Server/CoreAppHost.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.IO;
 using System.Reflection;
 using System.Reflection;
 using Emby.Drawing;
 using Emby.Drawing;
 using Emby.Server.Implementations;
 using Emby.Server.Implementations;
@@ -15,6 +16,7 @@ using MediaBrowser.Controller.Events;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Activity;
 using MediaBrowser.Model.Activity;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
+using Microsoft.EntityFrameworkCore;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
 
 
@@ -67,12 +69,8 @@ namespace Jellyfin.Server
                 Logger.LogWarning($"Skia not available. Will fallback to {nameof(NullImageEncoder)}.");
                 Logger.LogWarning($"Skia not available. Will fallback to {nameof(NullImageEncoder)}.");
             }
             }
 
 
-            // TODO: Set up scoping and use AddDbContextPool,
-            // can't register as Transient since tracking transient in GC is funky
-            // serviceCollection.AddDbContext<JellyfinDb>(
-            //     options => options
-            //         .UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"),
-            //     ServiceLifetime.Transient);
+            ServiceCollection.AddDbContextPool<JellyfinDb>(
+                 options => options.UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"));
 
 
             ServiceCollection.AddEventServices();
             ServiceCollection.AddEventServices();
             ServiceCollection.AddSingleton<IEventManager, EventManager>();
             ServiceCollection.AddSingleton<IEventManager, EventManager>();

+ 2 - 0
Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs

@@ -39,12 +39,14 @@ namespace Jellyfin.Server.Extensions
                     c.DocumentTitle = "Jellyfin API";
                     c.DocumentTitle = "Jellyfin API";
                     c.SwaggerEndpoint($"/{baseUrl}api-docs/openapi.json", "Jellyfin API");
                     c.SwaggerEndpoint($"/{baseUrl}api-docs/openapi.json", "Jellyfin API");
                     c.RoutePrefix = $"{baseUrl}api-docs/swagger";
                     c.RoutePrefix = $"{baseUrl}api-docs/swagger";
+                    c.InjectStylesheet($"/{baseUrl}api-docs/swagger/custom.css");
                 })
                 })
                 .UseReDoc(c =>
                 .UseReDoc(c =>
                 {
                 {
                     c.DocumentTitle = "Jellyfin API";
                     c.DocumentTitle = "Jellyfin API";
                     c.SpecUrl($"/{baseUrl}api-docs/openapi.json");
                     c.SpecUrl($"/{baseUrl}api-docs/openapi.json");
                     c.RoutePrefix = $"{baseUrl}api-docs/redoc";
                     c.RoutePrefix = $"{baseUrl}api-docs/redoc";
+                    c.InjectStylesheet($"/{baseUrl}api-docs/redoc/custom.css");
                 });
                 });
         }
         }
 
 

+ 0 - 36
Jellyfin.Server/HealthChecks/JellyfinDbHealthCheck.cs

@@ -1,36 +0,0 @@
-using System.Threading;
-using System.Threading.Tasks;
-using Jellyfin.Server.Implementations;
-using Microsoft.Extensions.Diagnostics.HealthChecks;
-
-namespace Jellyfin.Server.HealthChecks
-{
-    /// <summary>
-    /// Checks connectivity to the database.
-    /// </summary>
-    public class JellyfinDbHealthCheck : IHealthCheck
-    {
-        private readonly JellyfinDbProvider _dbProvider;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="JellyfinDbHealthCheck"/> class.
-        /// </summary>
-        /// <param name="dbProvider">The jellyfin db provider.</param>
-        public JellyfinDbHealthCheck(JellyfinDbProvider dbProvider)
-        {
-            _dbProvider = dbProvider;
-        }
-
-        /// <inheritdoc />
-        public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
-        {
-            await using var jellyfinDb = _dbProvider.CreateContext();
-            if (await jellyfinDb.Database.CanConnectAsync(cancellationToken).ConfigureAwait(false))
-            {
-                return HealthCheckResult.Healthy("Database connection successful.");
-            }
-
-            return HealthCheckResult.Unhealthy("Unable to connect to the database.");
-        }
-    }
-}

+ 10 - 0
Jellyfin.Server/Jellyfin.Server.csproj

@@ -44,6 +44,7 @@
     <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.7" />
     <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.7" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.7" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.7" />
     <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="3.1.7" />
     <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="3.1.7" />
+    <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="3.1.7" />
     <PackageReference Include="prometheus-net" Version="3.6.0" />
     <PackageReference Include="prometheus-net" Version="3.6.0" />
     <PackageReference Include="prometheus-net.AspNetCore" Version="3.6.0" />
     <PackageReference Include="prometheus-net.AspNetCore" Version="3.6.0" />
     <PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
     <PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
@@ -64,4 +65,13 @@
     <ProjectReference Include="..\Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj" />
     <ProjectReference Include="..\Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj" />
   </ItemGroup>
   </ItemGroup>
 
 
+  <ItemGroup>
+    <None Update="wwwroot\api-docs\swagger\custom.css">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+    <None Update="wwwroot\api-docs\redoc\custom.css">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+  </ItemGroup>
+
 </Project>
 </Project>

+ 3 - 2
Jellyfin.Server/Startup.cs

@@ -3,7 +3,7 @@ using System.ComponentModel;
 using System.Net.Http.Headers;
 using System.Net.Http.Headers;
 using Jellyfin.Api.TypeConverters;
 using Jellyfin.Api.TypeConverters;
 using Jellyfin.Server.Extensions;
 using Jellyfin.Server.Extensions;
-using Jellyfin.Server.HealthChecks;
+using Jellyfin.Server.Implementations;
 using Jellyfin.Server.Middleware;
 using Jellyfin.Server.Middleware;
 using Jellyfin.Server.Models;
 using Jellyfin.Server.Models;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
@@ -79,7 +79,7 @@ namespace Jellyfin.Server
                 .ConfigurePrimaryHttpMessageHandler(x => new DefaultHttpClientHandler());
                 .ConfigurePrimaryHttpMessageHandler(x => new DefaultHttpClientHandler());
 
 
             services.AddHealthChecks()
             services.AddHealthChecks()
-                .AddCheck<JellyfinDbHealthCheck>("JellyfinDb");
+                .AddDbContextCheck<JellyfinDb>();
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -112,6 +112,7 @@ namespace Jellyfin.Server
                 app.UseHttpsRedirection();
                 app.UseHttpsRedirection();
             }
             }
 
 
+            app.UseStaticFiles();
             app.UseAuthentication();
             app.UseAuthentication();
             app.UseJellyfinApiSwagger(_serverConfigurationManager);
             app.UseJellyfinApiSwagger(_serverConfigurationManager);
             app.UseRouting();
             app.UseRouting();

+ 0 - 0
Jellyfin.Server/wwwroot/api-docs/redoc/custom.css


+ 0 - 0
Jellyfin.Server/wwwroot/api-docs/swagger/custom.css


+ 2 - 9
MediaBrowser.Controller/IDisplayPreferencesManager.cs

@@ -35,15 +35,8 @@ namespace MediaBrowser.Controller
         IList<ItemDisplayPreferences> ListItemDisplayPreferences(Guid userId, string client);
         IList<ItemDisplayPreferences> ListItemDisplayPreferences(Guid userId, string client);
 
 
         /// <summary>
         /// <summary>
-        /// Saves changes to the provided display preferences.
+        /// Saves changes made to the database.
         /// </summary>
         /// </summary>
-        /// <param name="preferences">The display preferences to save.</param>
-        void SaveChanges(DisplayPreferences preferences);
-
-        /// <summary>
-        /// Saves changes to the provided item display preferences.
-        /// </summary>
-        /// <param name="preferences">The item display preferences to save.</param>
-        void SaveChanges(ItemDisplayPreferences preferences);
+        void SaveChanges();
     }
     }
 }
 }

+ 1 - 0
MediaBrowser.Controller/LiveTv/ChannelInfo.cs

@@ -62,6 +62,7 @@ namespace MediaBrowser.Controller.LiveTv
         /// </summary>
         /// </summary>
         /// <value><c>null</c> if [has image] contains no value, <c>true</c> if [has image]; otherwise, <c>false</c>.</value>
         /// <value><c>null</c> if [has image] contains no value, <c>true</c> if [has image]; otherwise, <c>false</c>.</value>
         public bool? HasImage { get; set; }
         public bool? HasImage { get; set; }
+
         /// <summary>
         /// <summary>
         /// Gets or sets a value indicating whether this instance is favorite.
         /// Gets or sets a value indicating whether this instance is favorite.
         /// </summary>
         /// </summary>

+ 31 - 20
MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs

@@ -2090,6 +2090,9 @@ namespace MediaBrowser.Controller.MediaEncoding
             var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
             var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
             var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
             var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
 
 
+            // If double rate deinterlacing is enabled and the input framerate is 30fps or below, otherwise the output framerate will be too high for many devices
+            var doubleRateDeinterlace = options.DeinterlaceDoubleRate && (videoStream?.RealFrameRate ?? 60) <= 30;
+
             // When the input may or may not be hardware VAAPI decodable
             // When the input may or may not be hardware VAAPI decodable
             if (isVaapiH264Encoder)
             if (isVaapiH264Encoder)
             {
             {
@@ -2136,35 +2139,38 @@ namespace MediaBrowser.Controller.MediaEncoding
             {
             {
                 if (isVaapiH264Encoder)
                 if (isVaapiH264Encoder)
                 {
                 {
-                    filters.Add(string.Format(CultureInfo.InvariantCulture, "deinterlace_vaapi"));
+                    filters.Add(
+                        string.Format(
+                            CultureInfo.InvariantCulture,
+                            "deinterlace_vaapi=rate={0}",
+                            doubleRateDeinterlace ? "field" : "frame"));
                 }
                 }
             }
             }
 
 
             // Add software deinterlace filter before scaling filter
             // Add software deinterlace filter before scaling filter
-            if (state.DeInterlace("h264", true)
-                || state.DeInterlace("avc", true)
-                || state.DeInterlace("h265", true)
-                || state.DeInterlace("hevc", true))
+            if ((state.DeInterlace("h264", true)
+                 || state.DeInterlace("avc", true)
+                 || state.DeInterlace("h265", true)
+                 || state.DeInterlace("hevc", true))
+                && !isVaapiH264Encoder
+                && !isQsvH264Encoder
+                && !isNvdecH264Decoder)
             {
             {
-                string deintParam;
-                var inputFramerate = videoStream?.RealFrameRate;
-
-                // If it is already 60fps then it will create an output framerate that is much too high for roku and others to handle
-                if (string.Equals(options.DeinterlaceMethod, "yadif_bob", StringComparison.OrdinalIgnoreCase) && (inputFramerate ?? 60) <= 30)
+                if (string.Equals(options.DeinterlaceMethod, "bwdif", StringComparison.OrdinalIgnoreCase))
                 {
                 {
-                    deintParam = "yadif=1:-1:0";
+                    filters.Add(
+                        string.Format(
+                            CultureInfo.InvariantCulture,
+                            "bwdif={0}:-1:0",
+                            doubleRateDeinterlace ? "1" : "0"));
                 }
                 }
                 else
                 else
                 {
                 {
-                    deintParam = "yadif=0:-1:0";
-                }
-
-                if (!string.IsNullOrEmpty(deintParam))
-                {
-                    if (!isVaapiH264Encoder && !isQsvH264Encoder && !isNvdecH264Decoder)
-                    {
-                        filters.Add(deintParam);
-                    }
+                    filters.Add(
+                        string.Format(
+                            CultureInfo.InvariantCulture,
+                            "yadif={0}:-1:0",
+                            doubleRateDeinterlace ? "1" : "0"));
                 }
                 }
             }
             }
 
 
@@ -2397,6 +2403,11 @@ namespace MediaBrowser.Controller.MediaEncoding
                         if (state.DeInterlace("h264", true))
                         if (state.DeInterlace("h264", true))
                         {
                         {
                             inputModifier += " -deint 1";
                             inputModifier += " -deint 1";
+
+                            if (!encodingOptions.DeinterlaceDoubleRate || (videoStream?.RealFrameRate ?? 60) > 30)
+                            {
+                                inputModifier += " -drop_second_field 1";
+                            }
                         }
                         }
                     }
                     }
                 }
                 }

+ 6 - 0
MediaBrowser.Model/Configuration/EncodingOptions.cs

@@ -11,6 +11,8 @@ namespace MediaBrowser.Model.Configuration
 
 
         public double DownMixAudioBoost { get; set; }
         public double DownMixAudioBoost { get; set; }
 
 
+        public int MaxMuxingQueueSize { get; set; }
+
         public bool EnableThrottling { get; set; }
         public bool EnableThrottling { get; set; }
 
 
         public int ThrottleDelaySeconds { get; set; }
         public int ThrottleDelaySeconds { get; set; }
@@ -35,6 +37,8 @@ namespace MediaBrowser.Model.Configuration
 
 
         public string EncoderPreset { get; set; }
         public string EncoderPreset { get; set; }
 
 
+        public bool DeinterlaceDoubleRate { get; set; }
+
         public string DeinterlaceMethod { get; set; }
         public string DeinterlaceMethod { get; set; }
 
 
         public bool EnableDecodingColorDepth10Hevc { get; set; }
         public bool EnableDecodingColorDepth10Hevc { get; set; }
@@ -50,6 +54,7 @@ namespace MediaBrowser.Model.Configuration
         public EncodingOptions()
         public EncodingOptions()
         {
         {
             DownMixAudioBoost = 2;
             DownMixAudioBoost = 2;
+            MaxMuxingQueueSize = 2048;
             EnableThrottling = false;
             EnableThrottling = false;
             ThrottleDelaySeconds = 180;
             ThrottleDelaySeconds = 180;
             EncodingThreadCount = -1;
             EncodingThreadCount = -1;
@@ -57,6 +62,7 @@ namespace MediaBrowser.Model.Configuration
             VaapiDevice = "/dev/dri/renderD128";
             VaapiDevice = "/dev/dri/renderD128";
             H264Crf = 23;
             H264Crf = 23;
             H265Crf = 28;
             H265Crf = 28;
+            DeinterlaceDoubleRate = false;
             DeinterlaceMethod = "yadif";
             DeinterlaceMethod = "yadif";
             EnableDecodingColorDepth10Hevc = true;
             EnableDecodingColorDepth10Hevc = true;
             EnableDecodingColorDepth10Vp9 = true;
             EnableDecodingColorDepth10Vp9 = true;

+ 1 - 1
MediaBrowser.Model/IO/FileSystemMetadata.cs

@@ -56,7 +56,7 @@ namespace MediaBrowser.Model.IO
         public DateTime CreationTimeUtc { get; set; }
         public DateTime CreationTimeUtc { get; set; }
 
 
         /// <summary>
         /// <summary>
-        /// Gets a value indicating whether this instance is directory.
+        /// Gets or sets a value indicating whether this instance is directory.
         /// </summary>
         /// </summary>
         /// <value><c>true</c> if this instance is directory; otherwise, <c>false</c>.</value>
         /// <value><c>true</c> if this instance is directory; otherwise, <c>false</c>.</value>
         public bool IsDirectory { get; set; }
         public bool IsDirectory { get; set; }

+ 2 - 2
MediaBrowser.Model/IO/IFileSystem.cs

@@ -201,9 +201,9 @@ namespace MediaBrowser.Model.IO
         IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false);
         IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false);
 
 
         void SetHidden(string path, bool isHidden);
         void SetHidden(string path, bool isHidden);
-        void SetReadOnly(string path, bool readOnly);
+
         void SetAttributes(string path, bool isHidden, bool readOnly);
         void SetAttributes(string path, bool isHidden, bool readOnly);
+
         List<FileSystemMetadata> GetDrives();
         List<FileSystemMetadata> GetDrives();
-        void SetExecutable(string path);
     }
     }
 }
 }

+ 0 - 1
MediaBrowser.Model/IO/IShortcutHandler.cs

@@ -22,7 +22,6 @@ namespace MediaBrowser.Model.IO
         /// </summary>
         /// </summary>
         /// <param name="shortcutPath">The shortcut path.</param>
         /// <param name="shortcutPath">The shortcut path.</param>
         /// <param name="targetPath">The target path.</param>
         /// <param name="targetPath">The target path.</param>
-        /// <returns>System.String.</returns>
         void Create(string shortcutPath, string targetPath);
         void Create(string shortcutPath, string targetPath);
     }
     }
 }
 }

+ 0 - 2
MediaBrowser.Model/IO/IStreamHelper.cs

@@ -13,8 +13,6 @@ namespace MediaBrowser.Model.IO
 
 
         Task CopyToAsync(Stream source, Stream destination, int bufferSize, int emptyReadLimit, CancellationToken cancellationToken);
         Task CopyToAsync(Stream source, Stream destination, int bufferSize, int emptyReadLimit, CancellationToken cancellationToken);
 
 
-        Task<int> CopyToAsync(Stream source, Stream destination, CancellationToken cancellationToken);
-
         Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken);
         Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken);
 
 
         Task CopyUntilCancelled(Stream source, Stream target, int bufferSize, CancellationToken cancellationToken);
         Task CopyUntilCancelled(Stream source, Stream target, int bufferSize, CancellationToken cancellationToken);

+ 1 - 0
MediaBrowser.Model/IO/IZipClient.cs

@@ -26,6 +26,7 @@ namespace MediaBrowser.Model.IO
         void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles);
         void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles);
 
 
         void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles);
         void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles);
+
         void ExtractFirstFileFromGz(Stream source, string targetPath, string defaultFileName);
         void ExtractFirstFileFromGz(Stream source, string targetPath, string defaultFileName);
 
 
         /// <summary>
         /// <summary>

+ 5 - 3
MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs

@@ -31,9 +31,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
             _httpClientFactory = httpClientFactory;
             _httpClientFactory = httpClientFactory;
         }
         }
 
 
+        public static string ProviderName => TmdbUtils.ProviderName;
+
+        /// <inheritdoc />
         public string Name => ProviderName;
         public string Name => ProviderName;
 
 
-        public static string ProviderName => TmdbUtils.ProviderName;
+        /// <inheritdoc />
+        public int Order => 0;
 
 
         public bool Supports(BaseItem item)
         public bool Supports(BaseItem item)
         {
         {
@@ -125,8 +129,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
             return profile.Iso_639_1?.ToString();
             return profile.Iso_639_1?.ToString();
         }
         }
 
 
-        public int Order => 0;
-
         public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
         public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
         {
         {
             return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);
             return _httpClientFactory.CreateClient().GetAsync(url, cancellationToken);

+ 13 - 5
MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs

@@ -32,7 +32,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
     {
     {
         const string DataFileName = "info.json";
         const string DataFileName = "info.json";
 
 
-        internal static TmdbPersonProvider Current { get; private set; }
+        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
 
 
         private readonly IJsonSerializer _jsonSerializer;
         private readonly IJsonSerializer _jsonSerializer;
         private readonly IFileSystem _fileSystem;
         private readonly IFileSystem _fileSystem;
@@ -55,6 +55,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
             Current = this;
             Current = this;
         }
         }
 
 
+        internal static TmdbPersonProvider Current { get; private set; }
+
         public string Name => TmdbUtils.ProviderName;
         public string Name => TmdbUtils.ProviderName;
 
 
         public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(PersonLookupInfo searchInfo, CancellationToken cancellationToken)
         public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(PersonLookupInfo searchInfo, CancellationToken cancellationToken)
@@ -95,7 +97,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
                 return new List<RemoteSearchResult>();
                 return new List<RemoteSearchResult>();
             }
             }
 
 
-            var url = string.Format(TmdbUtils.BaseTmdbApiUrl + @"3/search/person?api_key={1}&query={0}", WebUtility.UrlEncode(searchInfo.Name), TmdbUtils.ApiKey);
+            var url = string.Format(
+                CultureInfo.InvariantCulture,
+                TmdbUtils.BaseTmdbApiUrl + @"3/search/person?api_key={1}&query={0}",
+                WebUtility.UrlEncode(searchInfo.Name),
+                TmdbUtils.ApiKey);
 
 
             using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
             using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
             foreach (var header in TmdbUtils.AcceptHeaders)
             foreach (var header in TmdbUtils.AcceptHeaders)
@@ -200,8 +206,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
             return result;
             return result;
         }
         }
 
 
-        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
         /// <summary>
         /// <summary>
         /// Gets the TMDB id.
         /// Gets the TMDB id.
         /// </summary>
         /// </summary>
@@ -226,7 +230,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
                 return;
                 return;
             }
             }
 
 
-            var url = string.Format(TmdbUtils.BaseTmdbApiUrl + @"3/person/{1}?api_key={0}&append_to_response=credits,images,external_ids", TmdbUtils.ApiKey, id);
+            var url = string.Format(
+                CultureInfo.InvariantCulture,
+                TmdbUtils.BaseTmdbApiUrl + @"3/person/{1}?api_key={0}&append_to_response=credits,images,external_ids",
+                TmdbUtils.ApiKey,
+                id);
 
 
             using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
             using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
             foreach (var header in TmdbUtils.AcceptHeaders)
             foreach (var header in TmdbUtils.AcceptHeaders)

+ 1 - 1
debian/rules

@@ -40,7 +40,7 @@ override_dh_clistrip:
 
 
 override_dh_auto_build:
 override_dh_auto_build:
 	dotnet publish --configuration $(CONFIG) --output='$(CURDIR)/usr/lib/jellyfin/bin' --self-contained --runtime $(DOTNETRUNTIME) \
 	dotnet publish --configuration $(CONFIG) --output='$(CURDIR)/usr/lib/jellyfin/bin' --self-contained --runtime $(DOTNETRUNTIME) \
-		"-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none" Jellyfin.Server
+		"-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none" Jellyfin.Server
 
 
 override_dh_auto_clean:
 override_dh_auto_clean:
 	dotnet clean -maxcpucount:1 --configuration $(CONFIG) Jellyfin.Server || true
 	dotnet clean -maxcpucount:1 --configuration $(CONFIG) Jellyfin.Server || true

+ 1 - 1
deployment/Dockerfile.docker.amd64

@@ -12,4 +12,4 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
 
 
 # because of changes in docker and systemd we need to not build in parallel at the moment
 # because of changes in docker and systemd we need to not build in parallel at the moment
 # see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
 # see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
-RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
+RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none"

+ 1 - 1
deployment/Dockerfile.docker.arm64

@@ -12,4 +12,4 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
 
 
 # because of changes in docker and systemd we need to not build in parallel at the moment
 # because of changes in docker and systemd we need to not build in parallel at the moment
 # see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
 # see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
-RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
+RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none"

+ 1 - 1
deployment/Dockerfile.docker.armhf

@@ -12,4 +12,4 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
 
 
 # because of changes in docker and systemd we need to not build in parallel at the moment
 # because of changes in docker and systemd we need to not build in parallel at the moment
 # see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
 # see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
-RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
+RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none"

+ 1 - 1
deployment/build.linux.amd64

@@ -16,7 +16,7 @@ else
 fi
 fi
 
 
 # Build archives
 # Build archives
-dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true"
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none;UseAppHost=true"
 tar -czf jellyfin-server_${version}_linux-amd64.tar.gz -C dist jellyfin-server_${version}
 tar -czf jellyfin-server_${version}_linux-amd64.tar.gz -C dist jellyfin-server_${version}
 rm -rf dist/jellyfin-server_${version}
 rm -rf dist/jellyfin-server_${version}
 
 

+ 1 - 1
deployment/build.macos

@@ -16,7 +16,7 @@ else
 fi
 fi
 
 
 # Build archives
 # Build archives
-dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-x64 --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true"
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-x64 --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none;UseAppHost=true"
 tar -czf jellyfin-server_${version}_macos-amd64.tar.gz -C dist jellyfin-server_${version}
 tar -czf jellyfin-server_${version}_macos-amd64.tar.gz -C dist jellyfin-server_${version}
 rm -rf dist/jellyfin-server_${version}
 rm -rf dist/jellyfin-server_${version}
 
 

+ 1 - 1
deployment/build.portable

@@ -16,7 +16,7 @@ else
 fi
 fi
 
 
 # Build archives
 # Build archives
-dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true"
+dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none;UseAppHost=true"
 tar -czf jellyfin-server_${version}_portable.tar.gz -C dist jellyfin-server_${version}
 tar -czf jellyfin-server_${version}_portable.tar.gz -C dist jellyfin-server_${version}
 rm -rf dist/jellyfin-server_${version}
 rm -rf dist/jellyfin-server_${version}
 
 

+ 1 - 1
deployment/build.windows.amd64

@@ -23,7 +23,7 @@ fi
 output_dir="dist/jellyfin-server_${version}"
 output_dir="dist/jellyfin-server_${version}"
 
 
 # Build binary
 # Build binary
-dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime win-x64 --output ${output_dir}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true"
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime win-x64 --output ${output_dir}/ "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none;UseAppHost=true"
 
 
 # Prepare addins
 # Prepare addins
 addin_build_dir="$( mktemp -d )"
 addin_build_dir="$( mktemp -d )"

+ 1 - 1
fedora/jellyfin.spec

@@ -54,7 +54,7 @@ The Jellyfin media server backend.
 export DOTNET_CLI_TELEMETRY_OPTOUT=1
 export DOTNET_CLI_TELEMETRY_OPTOUT=1
 export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
 export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
 dotnet publish --configuration Release --output='%{buildroot}%{_libdir}/jellyfin' --self-contained --runtime %{dotnet_runtime} \
 dotnet publish --configuration Release --output='%{buildroot}%{_libdir}/jellyfin' --self-contained --runtime %{dotnet_runtime} \
-    "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none" Jellyfin.Server
+    "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none" Jellyfin.Server
 %{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/jellyfin/LICENSE
 %{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/jellyfin/LICENSE
 %{__install} -D -m 0644 %{SOURCE15} %{buildroot}%{_sysconfdir}/systemd/system/jellyfin.service.d/override.conf
 %{__install} -D -m 0644 %{SOURCE15} %{buildroot}%{_sysconfdir}/systemd/system/jellyfin.service.d/override.conf
 %{__install} -D -m 0644 Jellyfin.Server/Resources/Configuration/logging.json %{buildroot}%{_sysconfdir}/jellyfin/logging.json
 %{__install} -D -m 0644 Jellyfin.Server/Resources/Configuration/logging.json %{buildroot}%{_sysconfdir}/jellyfin/logging.json

+ 1 - 1
windows/build-jellyfin.ps1

@@ -40,7 +40,7 @@ function Build-JellyFin {
     Write-Verbose "windowsversion-Architecture: $windowsversion-$Architecture"
     Write-Verbose "windowsversion-Architecture: $windowsversion-$Architecture"
     Write-Verbose "InstallLocation: $ResolvedInstallLocation"
     Write-Verbose "InstallLocation: $ResolvedInstallLocation"
     Write-Verbose "DotNetVerbosity: $DotNetVerbosity"
     Write-Verbose "DotNetVerbosity: $DotNetVerbosity"
-    dotnet publish --self-contained -c $BuildType --output $ResolvedInstallLocation -v $DotNetVerbosity -p:GenerateDocumentationFile=false -p:DebugSymbols=false -p:DebugType=none --runtime `"$windowsversion-$Architecture`" Jellyfin.Server
+    dotnet publish --self-contained -c $BuildType --output $ResolvedInstallLocation -v $DotNetVerbosity -p:GenerateDocumentationFile=true -p:DebugSymbols=false -p:DebugType=none --runtime `"$windowsversion-$Architecture`" Jellyfin.Server
 }
 }
 
 
 function Install-FFMPEG {
 function Install-FFMPEG {