Просмотр исходного кода

Merge branch 'master' into register-services-correctly

Mark Monteiro 5 лет назад
Родитель
Сommit
9728aa8b0a
58 измененных файлов с 1060 добавлено и 1262 удалено
  1. 1 0
      CONTRIBUTORS.md
  2. 1 1
      Dockerfile.arm
  3. 10 17
      Emby.Server.Implementations/ApplicationHost.cs
  4. 0 152
      Emby.Server.Implementations/Diagnostics/CommonProcess.cs
  5. 0 14
      Emby.Server.Implementations/Diagnostics/ProcessFactory.cs
  6. 6 8
      Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
  7. 16 18
      Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
  8. 37 35
      Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
  9. 4 2
      Emby.Server.Implementations/Localization/Core/da.json
  10. 23 1
      Emby.Server.Implementations/Localization/Core/en-GB.json
  11. 1 1
      Emby.Server.Implementations/Localization/Core/es_DO.json
  12. 24 2
      Emby.Server.Implementations/Localization/Core/fa.json
  13. 40 18
      Emby.Server.Implementations/Localization/Core/fi.json
  14. 1 1
      Emby.Server.Implementations/Localization/Core/fr.json
  15. 1 1
      Emby.Server.Implementations/Localization/Core/hu.json
  16. 5 1
      Emby.Server.Implementations/Localization/Core/pt.json
  17. 117 1
      Emby.Server.Implementations/Localization/Core/ur_PK.json
  18. 8 21
      MediaBrowser.Api/ApiEntryPoint.cs
  19. 20 38
      MediaBrowser.Api/BaseApiService.cs
  20. 7 15
      MediaBrowser.Api/ChannelService.cs
  21. 7 9
      MediaBrowser.Api/Devices/DeviceService.cs
  22. 1 6
      MediaBrowser.Api/EnvironmentService.cs
  23. 2 2
      MediaBrowser.Api/FilterService.cs
  24. 12 14
      MediaBrowser.Api/Images/ImageService.cs
  25. 12 14
      MediaBrowser.Api/Images/RemoteImageService.cs
  26. 9 2
      MediaBrowser.Api/ItemLookupService.cs
  27. 9 19
      MediaBrowser.Api/ItemUpdateService.cs
  28. 50 145
      MediaBrowser.Api/Library/LibraryService.cs
  29. 4 8
      MediaBrowser.Api/Library/LibraryStructureService.cs
  30. 5 7
      MediaBrowser.Api/LiveTv/LiveTvService.cs
  31. 1 1
      MediaBrowser.Api/Movies/MoviesService.cs
  32. 184 222
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  33. 31 42
      MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
  34. 4 6
      MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
  35. 7 16
      MediaBrowser.Api/Playback/MediaInfoService.cs
  36. 2 2
      MediaBrowser.Api/Playback/UniversalAudioService.cs
  37. 1 3
      MediaBrowser.Api/PluginService.cs
  38. 2 6
      MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs
  39. 40 58
      MediaBrowser.Api/SearchService.cs
  40. 6 9
      MediaBrowser.Api/Subtitles/SubtitleService.cs
  41. 2 5
      MediaBrowser.Api/System/SystemService.cs
  42. 1 4
      MediaBrowser.Api/TranscodingJob.cs
  43. 2 11
      MediaBrowser.Api/TvShowsService.cs
  44. 1 6
      MediaBrowser.Api/UserLibrary/ArtistsService.cs
  45. 12 22
      MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
  46. 14 19
      MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
  47. 2 4
      MediaBrowser.Api/UserLibrary/ItemsService.cs
  48. 2 11
      MediaBrowser.Api/VideosService.cs
  49. 80 0
      MediaBrowser.Common/Extensions/ProcessExtensions.cs
  50. 30 32
      MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
  51. 31 34
      MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
  52. 1 0
      MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
  53. 44 37
      MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
  54. 76 71
      MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
  55. 0 23
      MediaBrowser.Model/Diagnostics/IProcess.cs
  56. 0 24
      MediaBrowser.Model/Diagnostics/IProcessFactory.cs
  57. 6 0
      nuget.config
  58. 45 21
      tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs

+ 1 - 0
CONTRIBUTORS.md

@@ -128,6 +128,7 @@
  - [xosdy](https://github.com/xosdy)
  - [xosdy](https://github.com/xosdy)
  - [XVicarious](https://github.com/XVicarious)
  - [XVicarious](https://github.com/XVicarious)
  - [YouKnowBlom](https://github.com/YouKnowBlom)
  - [YouKnowBlom](https://github.com/YouKnowBlom)
+ - [KristupasSavickas](https://github.com/KristupasSavickas)
 
 
 # Emby Contributors
 # Emby Contributors
 
 

+ 1 - 1
Dockerfile.arm

@@ -74,4 +74,4 @@ VOLUME /cache /config /media
 ENTRYPOINT ["./jellyfin/jellyfin", \
 ENTRYPOINT ["./jellyfin/jellyfin", \
     "--datadir", "/config", \
     "--datadir", "/config", \
     "--cachedir", "/cache", \
     "--cachedir", "/cache", \
-    "--ffmpeg", "/usr/lib/jellyfin-ffmpeg"]
+    "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]

+ 10 - 17
Emby.Server.Implementations/ApplicationHost.cs

@@ -30,7 +30,6 @@ using Emby.Server.Implementations.Configuration;
 using Emby.Server.Implementations.Cryptography;
 using Emby.Server.Implementations.Cryptography;
 using Emby.Server.Implementations.Data;
 using Emby.Server.Implementations.Data;
 using Emby.Server.Implementations.Devices;
 using Emby.Server.Implementations.Devices;
-using Emby.Server.Implementations.Diagnostics;
 using Emby.Server.Implementations.Dto;
 using Emby.Server.Implementations.Dto;
 using Emby.Server.Implementations.HttpServer;
 using Emby.Server.Implementations.HttpServer;
 using Emby.Server.Implementations.HttpServer.Security;
 using Emby.Server.Implementations.HttpServer.Security;
@@ -86,7 +85,6 @@ using MediaBrowser.MediaEncoding.BdInfo;
 using MediaBrowser.Model.Activity;
 using MediaBrowser.Model.Activity;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Cryptography;
 using MediaBrowser.Model.Cryptography;
-using MediaBrowser.Model.Diagnostics;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
@@ -574,8 +572,6 @@ namespace Emby.Server.Implementations
 
 
             serviceCollection.AddSingleton(_xmlSerializer);
             serviceCollection.AddSingleton(_xmlSerializer);
 
 
-            serviceCollection.AddSingleton<IProcessFactory, ProcessFactory>();
-
             serviceCollection.AddSingleton<IStreamHelper, StreamHelper>();
             serviceCollection.AddSingleton<IStreamHelper, StreamHelper>();
 
 
             serviceCollection.AddSingleton<ICryptoProvider, CryptographyProvider>();
             serviceCollection.AddSingleton<ICryptoProvider, CryptographyProvider>();
@@ -1462,15 +1458,17 @@ namespace Emby.Server.Implementations
                 throw new NotSupportedException();
                 throw new NotSupportedException();
             }
             }
 
 
-            var process = Resolve<IProcessFactory>().Create(new ProcessOptions
+            var process = new Process
             {
             {
-                FileName = url,
-                EnableRaisingEvents = true,
-                UseShellExecute = true,
-                ErrorDialog = false
-            });
-
-            process.Exited += ProcessExited;
+                StartInfo = new ProcessStartInfo
+                {
+                    FileName = url,
+                    UseShellExecute = true,
+                    ErrorDialog = false
+                },
+                EnableRaisingEvents = true
+            };
+            process.Exited += (sender, args) => ((Process)sender).Dispose();
 
 
             try
             try
             {
             {
@@ -1483,11 +1481,6 @@ namespace Emby.Server.Implementations
             }
             }
         }
         }
 
 
-        private static void ProcessExited(object sender, EventArgs e)
-        {
-            ((IProcess)sender).Dispose();
-        }
-
         public virtual void EnableLoopback(string appName)
         public virtual void EnableLoopback(string appName)
         {
         {
         }
         }

+ 0 - 152
Emby.Server.Implementations/Diagnostics/CommonProcess.cs

@@ -1,152 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Diagnostics;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Diagnostics;
-
-namespace Emby.Server.Implementations.Diagnostics
-{
-    public class CommonProcess : IProcess
-    {
-        private readonly Process _process;
-
-        private bool _disposed = false;
-        private bool _hasExited;
-
-        public CommonProcess(ProcessOptions options)
-        {
-            StartInfo = options;
-
-            var startInfo = new ProcessStartInfo
-            {
-                Arguments = options.Arguments,
-                FileName = options.FileName,
-                WorkingDirectory = options.WorkingDirectory,
-                UseShellExecute = options.UseShellExecute,
-                CreateNoWindow = options.CreateNoWindow,
-                RedirectStandardError = options.RedirectStandardError,
-                RedirectStandardInput = options.RedirectStandardInput,
-                RedirectStandardOutput = options.RedirectStandardOutput,
-                ErrorDialog = options.ErrorDialog
-            };
-
-
-            if (options.IsHidden)
-            {
-                startInfo.WindowStyle = ProcessWindowStyle.Hidden;
-            }
-
-            _process = new Process
-            {
-                StartInfo = startInfo
-            };
-
-            if (options.EnableRaisingEvents)
-            {
-                _process.EnableRaisingEvents = true;
-                _process.Exited += OnProcessExited;
-            }
-        }
-
-        public event EventHandler Exited;
-
-        public ProcessOptions StartInfo { get; }
-
-        public StreamWriter StandardInput => _process.StandardInput;
-
-        public StreamReader StandardError => _process.StandardError;
-
-        public StreamReader StandardOutput => _process.StandardOutput;
-
-        public int ExitCode => _process.ExitCode;
-
-        private bool HasExited
-        {
-            get
-            {
-                if (_hasExited)
-                {
-                    return true;
-                }
-
-                try
-                {
-                    _hasExited = _process.HasExited;
-                }
-                catch (InvalidOperationException)
-                {
-                    _hasExited = true;
-                }
-
-                return _hasExited;
-            }
-        }
-
-        public void Start()
-        {
-            _process.Start();
-        }
-
-        public void Kill()
-        {
-            _process.Kill();
-        }
-
-        public bool WaitForExit(int timeMs)
-        {
-            return _process.WaitForExit(timeMs);
-        }
-
-        public Task<bool> WaitForExitAsync(int timeMs)
-        {
-            // Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true.
-
-            if (HasExited)
-            {
-                return Task.FromResult(true);
-            }
-
-            timeMs = Math.Max(0, timeMs);
-
-            var tcs = new TaskCompletionSource<bool>();
-
-            var cancellationToken = new CancellationTokenSource(timeMs).Token;
-
-            _process.Exited += (sender, args) => tcs.TrySetResult(true);
-
-            cancellationToken.Register(() => tcs.TrySetResult(HasExited));
-
-            return tcs.Task;
-        }
-
-        public void Dispose()
-        {
-            Dispose(true);
-            GC.SuppressFinalize(this);
-        }
-
-        protected virtual void Dispose(bool disposing)
-        {
-            if (_disposed)
-            {
-                return;
-            }
-
-            if (disposing)
-            {
-                _process?.Dispose();
-            }
-
-            _disposed = true;
-        }
-
-        private void OnProcessExited(object sender, EventArgs e)
-        {
-            _hasExited = true;
-            Exited?.Invoke(this, e);
-        }
-    }
-}

+ 0 - 14
Emby.Server.Implementations/Diagnostics/ProcessFactory.cs

@@ -1,14 +0,0 @@
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Diagnostics;
-
-namespace Emby.Server.Implementations.Diagnostics
-{
-    public class ProcessFactory : IProcessFactory
-    {
-        public IProcess Create(ProcessOptions options)
-        {
-            return new CommonProcess(options);
-        }
-    }
-}

+ 6 - 8
Emby.Server.Implementations/HttpServer/HttpListenerHost.cs

@@ -239,7 +239,7 @@ namespace Emby.Server.Implementations.HttpServer
             }
             }
         }
         }
 
 
-        private async Task ErrorHandler(Exception ex, IRequest httpReq, bool logExceptionStackTrace)
+        private async Task ErrorHandler(Exception ex, IRequest httpReq, bool logExceptionStackTrace, string urlToLog)
         {
         {
             try
             try
             {
             {
@@ -247,11 +247,11 @@ namespace Emby.Server.Implementations.HttpServer
 
 
                 if (logExceptionStackTrace)
                 if (logExceptionStackTrace)
                 {
                 {
-                    _logger.LogError(ex, "Error processing request");
+                    _logger.LogError(ex, "Error processing request. URL: {Url}", urlToLog);
                 }
                 }
                 else
                 else
                 {
                 {
-                    _logger.LogError("Error processing request: {Message}", ex.Message);
+                    _logger.LogError("Error processing request: {Message}. URL: {Url}", ex.Message.TrimEnd('.'), urlToLog);
                 }
                 }
 
 
                 var httpRes = httpReq.Response;
                 var httpRes = httpReq.Response;
@@ -271,7 +271,7 @@ namespace Emby.Server.Implementations.HttpServer
             }
             }
             catch (Exception errorEx)
             catch (Exception errorEx)
             {
             {
-                _logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response)");
+                _logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response). URL: {Url}", urlToLog);
             }
             }
         }
         }
 
 
@@ -456,7 +456,7 @@ namespace Emby.Server.Implementations.HttpServer
             var stopWatch = new Stopwatch();
             var stopWatch = new Stopwatch();
             stopWatch.Start();
             stopWatch.Start();
             var httpRes = httpReq.Response;
             var httpRes = httpReq.Response;
-            string urlToLog = null;
+            string urlToLog = GetUrlToLog(urlString);
             string remoteIp = httpReq.RemoteIp;
             string remoteIp = httpReq.RemoteIp;
 
 
             try
             try
@@ -502,8 +502,6 @@ namespace Emby.Server.Implementations.HttpServer
                     return;
                     return;
                 }
                 }
 
 
-                urlToLog = GetUrlToLog(urlString);
-
                 if (string.Equals(localPath, _baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase)
                 if (string.Equals(localPath, _baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase)
                     || string.Equals(localPath, _baseUrlPrefix, StringComparison.OrdinalIgnoreCase)
                     || string.Equals(localPath, _baseUrlPrefix, StringComparison.OrdinalIgnoreCase)
                     || string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)
                     || string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)
@@ -553,7 +551,7 @@ namespace Emby.Server.Implementations.HttpServer
                     || ex is OperationCanceledException
                     || ex is OperationCanceledException
                     || ex is SecurityException
                     || ex is SecurityException
                     || ex is FileNotFoundException;
                     || ex is FileNotFoundException;
-                await ErrorHandler(ex, httpReq, ignoreStackTrace).ConfigureAwait(false);
+                await ErrorHandler(ex, httpReq, !ignoreStackTrace, urlToLog).ConfigureAwait(false);
             }
             }
             finally
             finally
             {
             {

+ 16 - 18
Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs

@@ -3,6 +3,7 @@
 using System;
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Globalization;
 using System.Globalization;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
@@ -25,7 +26,6 @@ using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Diagnostics;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Events;
@@ -61,7 +61,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
         private readonly ILibraryManager _libraryManager;
         private readonly ILibraryManager _libraryManager;
         private readonly IProviderManager _providerManager;
         private readonly IProviderManager _providerManager;
         private readonly IMediaEncoder _mediaEncoder;
         private readonly IMediaEncoder _mediaEncoder;
-        private readonly IProcessFactory _processFactory;
         private readonly IMediaSourceManager _mediaSourceManager;
         private readonly IMediaSourceManager _mediaSourceManager;
         private readonly IStreamHelper _streamHelper;
         private readonly IStreamHelper _streamHelper;
 
 
@@ -88,8 +87,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             ILibraryManager libraryManager,
             ILibraryManager libraryManager,
             ILibraryMonitor libraryMonitor,
             ILibraryMonitor libraryMonitor,
             IProviderManager providerManager,
             IProviderManager providerManager,
-            IMediaEncoder mediaEncoder,
-            IProcessFactory processFactory)
+            IMediaEncoder mediaEncoder)
         {
         {
             Current = this;
             Current = this;
 
 
@@ -102,7 +100,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             _libraryMonitor = libraryMonitor;
             _libraryMonitor = libraryMonitor;
             _providerManager = providerManager;
             _providerManager = providerManager;
             _mediaEncoder = mediaEncoder;
             _mediaEncoder = mediaEncoder;
-            _processFactory = processFactory;
             _liveTvManager = (LiveTvManager)liveTvManager;
             _liveTvManager = (LiveTvManager)liveTvManager;
             _jsonSerializer = jsonSerializer;
             _jsonSerializer = jsonSerializer;
             _mediaSourceManager = mediaSourceManager;
             _mediaSourceManager = mediaSourceManager;
@@ -1662,7 +1659,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, _processFactory, _config);
+                return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _config);
             }
             }
 
 
             return new DirectRecorder(_logger, _httpClient, _streamHelper);
             return new DirectRecorder(_logger, _httpClient, _streamHelper);
@@ -1683,16 +1680,19 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
 
             try
             try
             {
             {
-                var process = _processFactory.Create(new ProcessOptions
+                var process = new Process
                 {
                 {
-                    Arguments = GetPostProcessArguments(path, options.RecordingPostProcessorArguments),
-                    CreateNoWindow = true,
-                    EnableRaisingEvents = true,
-                    ErrorDialog = false,
-                    FileName = options.RecordingPostProcessor,
-                    IsHidden = true,
-                    UseShellExecute = false
-                });
+                    StartInfo = new ProcessStartInfo
+                    {
+                        Arguments = GetPostProcessArguments(path, options.RecordingPostProcessorArguments),
+                        CreateNoWindow = true,
+                        ErrorDialog = false,
+                        FileName = options.RecordingPostProcessor,
+                        WindowStyle = ProcessWindowStyle.Hidden,
+                        UseShellExecute = false
+                    },
+                    EnableRaisingEvents = true
+                };
 
 
                 _logger.LogInformation("Running recording post processor {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
                 _logger.LogInformation("Running recording post processor {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
 
 
@@ -1712,11 +1712,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
 
         private void Process_Exited(object sender, EventArgs e)
         private void Process_Exited(object sender, EventArgs e)
         {
         {
-            using (var process = (IProcess)sender)
+            using (var process = (Process)sender)
             {
             {
                 _logger.LogInformation("Recording post-processing script completed with exit code {ExitCode}", process.ExitCode);
                 _logger.LogInformation("Recording post-processing script completed with exit code {ExitCode}", process.ExitCode);
-
-                process.Dispose();
             }
             }
         }
         }
 
 

+ 37 - 35
Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs

@@ -2,6 +2,7 @@
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Globalization;
 using System.Globalization;
 using System.IO;
 using System.IO;
 using System.Text;
 using System.Text;
@@ -13,7 +14,6 @@ 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.Configuration;
-using MediaBrowser.Model.Diagnostics;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Serialization;
@@ -29,8 +29,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
         private bool _hasExited;
         private bool _hasExited;
         private Stream _logFileStream;
         private Stream _logFileStream;
         private string _targetPath;
         private string _targetPath;
-        private IProcess _process;
-        private readonly IProcessFactory _processFactory;
+        private Process _process;
         private readonly IJsonSerializer _json;
         private readonly IJsonSerializer _json;
         private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
         private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
         private readonly IServerConfigurationManager _config;
         private readonly IServerConfigurationManager _config;
@@ -40,14 +39,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             IMediaEncoder mediaEncoder,
             IMediaEncoder mediaEncoder,
             IServerApplicationPaths appPaths,
             IServerApplicationPaths appPaths,
             IJsonSerializer json,
             IJsonSerializer json,
-            IProcessFactory processFactory,
             IServerConfigurationManager config)
             IServerConfigurationManager config)
         {
         {
             _logger = logger;
             _logger = logger;
             _mediaEncoder = mediaEncoder;
             _mediaEncoder = mediaEncoder;
             _appPaths = appPaths;
             _appPaths = appPaths;
             _json = json;
             _json = json;
-            _processFactory = processFactory;
             _config = config;
             _config = config;
         }
         }
 
 
@@ -79,7 +76,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             _targetPath = targetFile;
             _targetPath = targetFile;
             Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
             Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
 
 
-            var process = _processFactory.Create(new ProcessOptions
+            var processStartInfo = new ProcessStartInfo
             {
             {
                 CreateNoWindow = true,
                 CreateNoWindow = true,
                 UseShellExecute = false,
                 UseShellExecute = false,
@@ -90,14 +87,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                 FileName = _mediaEncoder.EncoderPath,
                 FileName = _mediaEncoder.EncoderPath,
                 Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile, duration),
                 Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile, duration),
 
 
-                IsHidden = true,
-                ErrorDialog = false,
-                EnableRaisingEvents = true
-            });
-
-            _process = process;
+                WindowStyle = ProcessWindowStyle.Hidden,
+                ErrorDialog = false
+            };
 
 
-            var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
+            var commandLineLogMessage = processStartInfo.FileName + " " + processStartInfo.Arguments;
             _logger.LogInformation(commandLineLogMessage);
             _logger.LogInformation(commandLineLogMessage);
 
 
             var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid() + ".txt");
             var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid() + ".txt");
@@ -109,16 +103,21 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
             var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
             _logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length);
             _logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length);
 
 
-            process.Exited += (sender, args) => OnFfMpegProcessExited(process, inputFile);
+            _process = new Process
+            {
+                StartInfo = processStartInfo,
+                EnableRaisingEvents = true
+            };
+            _process.Exited += (sender, args) => OnFfMpegProcessExited(_process, inputFile);
 
 
-            process.Start();
+            _process.Start();
 
 
             cancellationToken.Register(Stop);
             cancellationToken.Register(Stop);
 
 
             onStarted();
             onStarted();
 
 
             // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
             // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
-            StartStreamingLog(process.StandardError.BaseStream, _logFileStream);
+            StartStreamingLog(_process.StandardError.BaseStream, _logFileStream);
 
 
             _logger.LogInformation("ffmpeg recording process started for {0}", _targetPath);
             _logger.LogInformation("ffmpeg recording process started for {0}", _targetPath);
 
 
@@ -292,30 +291,33 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
         /// <summary>
         /// <summary>
         /// Processes the exited.
         /// Processes the exited.
         /// </summary>
         /// </summary>
-        private void OnFfMpegProcessExited(IProcess process, string inputFile)
+        private void OnFfMpegProcessExited(Process process, string inputFile)
         {
         {
-            _hasExited = true;
+            using (process)
+            {
+                _hasExited = true;
 
 
-            _logFileStream?.Dispose();
-            _logFileStream = null;
+                _logFileStream?.Dispose();
+                _logFileStream = null;
 
 
-            var exitCode = process.ExitCode;
+                var exitCode = process.ExitCode;
 
 
-            _logger.LogInformation("FFMpeg recording exited with code {ExitCode} for {Path}", exitCode, _targetPath);
+                _logger.LogInformation("FFMpeg recording exited with code {ExitCode} for {Path}", exitCode, _targetPath);
 
 
-            if (exitCode == 0)
-            {
-                _taskCompletionSource.TrySetResult(true);
-            }
-            else
-            {
-                _taskCompletionSource.TrySetException(
-                    new Exception(
-                        string.Format(
-                            CultureInfo.InvariantCulture,
-                            "Recording for {0} failed. Exit code {1}",
-                            _targetPath,
-                            exitCode)));
+                if (exitCode == 0)
+                {
+                    _taskCompletionSource.TrySetResult(true);
+                }
+                else
+                {
+                    _taskCompletionSource.TrySetException(
+                        new Exception(
+                            string.Format(
+                                CultureInfo.InvariantCulture,
+                                "Recording for {0} failed. Exit code {1}",
+                                _targetPath,
+                                exitCode)));
+                }
             }
             }
         }
         }
 
 

+ 4 - 2
Emby.Server.Implementations/Localization/Core/da.json

@@ -1,5 +1,5 @@
 {
 {
-    "Albums": "Album",
+    "Albums": "Albums",
     "AppDeviceValues": "App: {0}, Enhed: {1}",
     "AppDeviceValues": "App: {0}, Enhed: {1}",
     "Application": "Applikation",
     "Application": "Applikation",
     "Artists": "Kunstnere",
     "Artists": "Kunstnere",
@@ -106,5 +106,7 @@
     "TasksChannelsCategory": "Internet Kanaler",
     "TasksChannelsCategory": "Internet Kanaler",
     "TasksApplicationCategory": "Applikation",
     "TasksApplicationCategory": "Applikation",
     "TasksLibraryCategory": "Bibliotek",
     "TasksLibraryCategory": "Bibliotek",
-    "TasksMaintenanceCategory": "Vedligeholdelse"
+    "TasksMaintenanceCategory": "Vedligeholdelse",
+    "TaskRefreshChapterImages": "Udtræk Kapitel billeder",
+    "TaskRefreshChapterImagesDescription": "Lav miniaturebilleder for videoer der har kapitler."
 }
 }

+ 23 - 1
Emby.Server.Implementations/Localization/Core/en-GB.json

@@ -92,5 +92,27 @@
     "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
     "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
     "ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
     "ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
     "ValueSpecialEpisodeName": "Special - {0}",
     "ValueSpecialEpisodeName": "Special - {0}",
-    "VersionNumber": "Version {0}"
+    "VersionNumber": "Version {0}",
+    "TaskDownloadMissingSubtitlesDescription": "Searches the internet for missing subtitles based on metadata configuration.",
+    "TaskDownloadMissingSubtitles": "Download missing subtitles",
+    "TaskRefreshChannelsDescription": "Refreshes internet channel information.",
+    "TaskRefreshChannels": "Refresh Channels",
+    "TaskCleanTranscodeDescription": "Deletes transcode files more than one day old.",
+    "TaskCleanTranscode": "Clean Transcode Directory",
+    "TaskUpdatePluginsDescription": "Downloads and installs updates for plugins that are configured to update automatically.",
+    "TaskUpdatePlugins": "Update Plugins",
+    "TaskRefreshPeopleDescription": "Updates metadata for actors and directors in your media library.",
+    "TaskRefreshPeople": "Refresh People",
+    "TaskCleanLogsDescription": "Deletes log files that are more than {0} days old.",
+    "TaskCleanLogs": "Clean Log Directory",
+    "TaskRefreshLibraryDescription": "Scans your media library for new files and refreshes metadata.",
+    "TaskRefreshLibrary": "Scan Media Library",
+    "TaskRefreshChapterImagesDescription": "Creates thumbnails for videos that have chapters.",
+    "TaskRefreshChapterImages": "Extract Chapter Images",
+    "TaskCleanCacheDescription": "Deletes cache files no longer needed by the system.",
+    "TaskCleanCache": "Clean Cache Directory",
+    "TasksChannelsCategory": "Internet Channels",
+    "TasksApplicationCategory": "Application",
+    "TasksLibraryCategory": "Library",
+    "TasksMaintenanceCategory": "Maintenance"
 }
 }

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

@@ -5,7 +5,7 @@
     "Collections": "Colecciones",
     "Collections": "Colecciones",
     "Artists": "Artistas",
     "Artists": "Artistas",
     "DeviceOnlineWithName": "{0} está conectado",
     "DeviceOnlineWithName": "{0} está conectado",
-    "DeviceOfflineWithName": "{0} ha desconectado",
+    "DeviceOfflineWithName": "{0} se ha desconectado",
     "ChapterNameValue": "Capítulo {0}",
     "ChapterNameValue": "Capítulo {0}",
     "CameraImageUploadedFrom": "Se ha subido una nueva imagen de cámara desde {0}",
     "CameraImageUploadedFrom": "Se ha subido una nueva imagen de cámara desde {0}",
     "AuthenticationSucceededWithUserName": "{0} autenticado con éxito",
     "AuthenticationSucceededWithUserName": "{0} autenticado con éxito",

+ 24 - 2
Emby.Server.Implementations/Localization/Core/fa.json

@@ -23,7 +23,7 @@
     "HeaderFavoriteEpisodes": "قسمت‌های مورد علاقه",
     "HeaderFavoriteEpisodes": "قسمت‌های مورد علاقه",
     "HeaderFavoriteShows": "سریال‌های مورد علاقه",
     "HeaderFavoriteShows": "سریال‌های مورد علاقه",
     "HeaderFavoriteSongs": "آهنگ‌های مورد علاقه",
     "HeaderFavoriteSongs": "آهنگ‌های مورد علاقه",
-    "HeaderLiveTV": "پخش زنده تلویزیون",
+    "HeaderLiveTV": "تلویزیون زنده",
     "HeaderNextUp": "قسمت بعدی",
     "HeaderNextUp": "قسمت بعدی",
     "HeaderRecordingGroups": "گروه‌های ضبط",
     "HeaderRecordingGroups": "گروه‌های ضبط",
     "HomeVideos": "ویدیوهای خانگی",
     "HomeVideos": "ویدیوهای خانگی",
@@ -92,5 +92,27 @@
     "UserStoppedPlayingItemWithValues": "{0} پخش {1} را بر روی {2} به پایان رساند",
     "UserStoppedPlayingItemWithValues": "{0} پخش {1} را بر روی {2} به پایان رساند",
     "ValueHasBeenAddedToLibrary": "{0} به کتابخانه‌ی رسانه‌ی شما افزوده شد",
     "ValueHasBeenAddedToLibrary": "{0} به کتابخانه‌ی رسانه‌ی شما افزوده شد",
     "ValueSpecialEpisodeName": "ویژه - {0}",
     "ValueSpecialEpisodeName": "ویژه - {0}",
-    "VersionNumber": "نسخه {0}"
+    "VersionNumber": "نسخه {0}",
+    "TaskCleanTranscodeDescription": "فایل‌های کدگذاری که قدیمی‌تر از یک روز هستند را حذف می‌کند.",
+    "TaskCleanTranscode": "پاکسازی مسیر کد گذاری",
+    "TaskUpdatePluginsDescription": "دانلود و نصب به روز رسانی افزونه‌هایی که برای به روز رسانی خودکار پیکربندی شده‌اند.",
+    "TaskDownloadMissingSubtitlesDescription": "جستجوی زیرنویس‌های ناموجود در اینترنت بر اساس پیکربندی ابرداده‌ها.",
+    "TaskDownloadMissingSubtitles": "دانلود زیرنویس‌های ناموجود",
+    "TaskRefreshChannelsDescription": "اطلاعات کانال اینترنتی را تازه سازی می‌کند.",
+    "TaskRefreshChannels": "تازه سازی کانال‌ها",
+    "TaskUpdatePlugins": "به روز رسانی افزونه‌ها",
+    "TaskRefreshPeopleDescription": "ابرداده‌ها برای بازیگران و کارگردانان در کتابخانه رسانه شما به روزرسانی می شوند.",
+    "TaskRefreshPeople": "تازه سازی افراد",
+    "TaskCleanLogsDescription": "واقعه نگارهایی را که قدیمی تر {0} روز هستند را حذف می کند.",
+    "TaskCleanLogs": "پاکسازی مسیر واقعه نگار",
+    "TaskRefreshLibraryDescription": "کتابخانه رسانه شما را اسکن می‌کند و ابرداده‌ها را تازه سازی می‌کند.",
+    "TaskRefreshLibrary": "اسکن کتابخانه رسانه",
+    "TaskRefreshChapterImagesDescription": "عکس‌های کوچک برای ویدیوهایی که سکانس دارند ایجاد می‌کند.",
+    "TaskRefreshChapterImages": "استخراج عکس‌های سکانس",
+    "TaskCleanCacheDescription": "فایل‌های حافظه موقت که توسط سیستم دیگر مورد نیاز نیستند حذف می‌شوند.",
+    "TaskCleanCache": "پاکسازی مسیر حافظه موقت",
+    "TasksChannelsCategory": "کانال‌های داخلی",
+    "TasksApplicationCategory": "برنامه",
+    "TasksLibraryCategory": "کتابخانه",
+    "TasksMaintenanceCategory": "تعمیر"
 }
 }

+ 40 - 18
Emby.Server.Implementations/Localization/Core/fi.json

@@ -1,5 +1,5 @@
 {
 {
-    "HeaderLiveTV": "TV-lähetykset",
+    "HeaderLiveTV": "Suorat lähetykset",
     "NewVersionIsAvailable": "Uusi versio Jellyfin palvelimesta on ladattavissa.",
     "NewVersionIsAvailable": "Uusi versio Jellyfin palvelimesta on ladattavissa.",
     "NameSeasonUnknown": "Tuntematon Kausi",
     "NameSeasonUnknown": "Tuntematon Kausi",
     "NameSeasonNumber": "Kausi {0}",
     "NameSeasonNumber": "Kausi {0}",
@@ -19,12 +19,12 @@
     "ItemAddedWithName": "{0} lisättiin kirjastoon",
     "ItemAddedWithName": "{0} lisättiin kirjastoon",
     "Inherit": "Periytyä",
     "Inherit": "Periytyä",
     "HomeVideos": "Kotivideot",
     "HomeVideos": "Kotivideot",
-    "HeaderRecordingGroups": "Nauhoitusryhmät",
+    "HeaderRecordingGroups": "Nauhoiteryhmät",
     "HeaderNextUp": "Seuraavaksi",
     "HeaderNextUp": "Seuraavaksi",
     "HeaderFavoriteSongs": "Lempikappaleet",
     "HeaderFavoriteSongs": "Lempikappaleet",
     "HeaderFavoriteShows": "Lempisarjat",
     "HeaderFavoriteShows": "Lempisarjat",
     "HeaderFavoriteEpisodes": "Lempijaksot",
     "HeaderFavoriteEpisodes": "Lempijaksot",
-    "HeaderCameraUploads": "Kameralataukset",
+    "HeaderCameraUploads": "Kamerasta Lähetetyt",
     "HeaderFavoriteArtists": "Lempiartistit",
     "HeaderFavoriteArtists": "Lempiartistit",
     "HeaderFavoriteAlbums": "Lempialbumit",
     "HeaderFavoriteAlbums": "Lempialbumit",
     "HeaderContinueWatching": "Jatka katsomista",
     "HeaderContinueWatching": "Jatka katsomista",
@@ -63,10 +63,10 @@
     "UserPasswordChangedWithName": "Salasana vaihdettu käyttäjälle {0}",
     "UserPasswordChangedWithName": "Salasana vaihdettu käyttäjälle {0}",
     "UserOnlineFromDevice": "{0} on paikalla osoitteesta {1}",
     "UserOnlineFromDevice": "{0} on paikalla osoitteesta {1}",
     "UserOfflineFromDevice": "{0} yhteys katkaistu {1}",
     "UserOfflineFromDevice": "{0} yhteys katkaistu {1}",
-    "UserLockedOutWithName": "Käyttäjä {0} kirjautui ulos",
-    "UserDownloadingItemWithValues": "{0} latautumassa {1}",
-    "UserDeletedWithName": "Poistettiin käyttäjä {0}",
-    "UserCreatedWithName": "Luotiin käyttäjä {0}",
+    "UserLockedOutWithName": "Käyttäjä {0} lukittu",
+    "UserDownloadingItemWithValues": "{0} lataa {1}",
+    "UserDeletedWithName": "Käyttäjä {0} poistettu",
+    "UserCreatedWithName": "Käyttäjä {0} luotu",
     "TvShows": "TV-Ohjelmat",
     "TvShows": "TV-Ohjelmat",
     "Sync": "Synkronoi",
     "Sync": "Synkronoi",
     "SubtitleDownloadFailureFromForItem": "Tekstityksen lataaminen epäonnistui {0} - {1}",
     "SubtitleDownloadFailureFromForItem": "Tekstityksen lataaminen epäonnistui {0} - {1}",
@@ -74,22 +74,44 @@
     "Songs": "Kappaleet",
     "Songs": "Kappaleet",
     "Shows": "Ohjelmat",
     "Shows": "Ohjelmat",
     "ServerNameNeedsToBeRestarted": "{0} vaatii uudelleenkäynnistyksen",
     "ServerNameNeedsToBeRestarted": "{0} vaatii uudelleenkäynnistyksen",
-    "ProviderValue": "Palveluntarjoaja: {0}",
+    "ProviderValue": "Tarjoaja: {0}",
     "Plugin": "Liitännäinen",
     "Plugin": "Liitännäinen",
-    "NotificationOptionVideoPlaybackStopped": "Videon toistaminen pysäytetty",
-    "NotificationOptionVideoPlayback": "Videon toistaminen aloitettu",
-    "NotificationOptionUserLockedOut": "Käyttäjä kirjautui ulos",
-    "NotificationOptionTaskFailed": "Ajastetun tehtävän ongelma",
+    "NotificationOptionVideoPlaybackStopped": "Videon toisto pysäytetty",
+    "NotificationOptionVideoPlayback": "Videon toisto aloitettu",
+    "NotificationOptionUserLockedOut": "Käyttäjä lukittu",
+    "NotificationOptionTaskFailed": "Ajastettu tehtävä epäonnistui",
     "NotificationOptionServerRestartRequired": "Palvelimen uudelleenkäynnistys vaaditaan",
     "NotificationOptionServerRestartRequired": "Palvelimen uudelleenkäynnistys vaaditaan",
-    "NotificationOptionPluginUpdateInstalled": "Liitännäinen päivitetty",
+    "NotificationOptionPluginUpdateInstalled": "Lisäosan päivitys asennettu",
     "NotificationOptionPluginUninstalled": "Liitännäinen poistettu",
     "NotificationOptionPluginUninstalled": "Liitännäinen poistettu",
     "NotificationOptionPluginInstalled": "Liitännäinen asennettu",
     "NotificationOptionPluginInstalled": "Liitännäinen asennettu",
     "NotificationOptionPluginError": "Ongelma liitännäisessä",
     "NotificationOptionPluginError": "Ongelma liitännäisessä",
     "NotificationOptionNewLibraryContent": "Uutta sisältöä lisätty",
     "NotificationOptionNewLibraryContent": "Uutta sisältöä lisätty",
     "NotificationOptionInstallationFailed": "Asennus epäonnistui",
     "NotificationOptionInstallationFailed": "Asennus epäonnistui",
-    "NotificationOptionCameraImageUploaded": "Kuva ladattu kamerasta",
-    "NotificationOptionAudioPlaybackStopped": "Audion toisto pysäytetty",
-    "NotificationOptionAudioPlayback": "Audion toisto aloitettu",
-    "NotificationOptionApplicationUpdateInstalled": "Ohjelmistopäivitys asennettu",
-    "NotificationOptionApplicationUpdateAvailable": "Ohjelmistopäivitys saatavilla"
+    "NotificationOptionCameraImageUploaded": "Kameran kuva ladattu",
+    "NotificationOptionAudioPlaybackStopped": "Äänen toisto lopetettu",
+    "NotificationOptionAudioPlayback": "Toistetaan ääntä",
+    "NotificationOptionApplicationUpdateInstalled": "Uusi sovellusversio asennettu",
+    "NotificationOptionApplicationUpdateAvailable": "Sovelluksesta on uusi versio saatavilla",
+    "TasksMaintenanceCategory": "Ylläpito",
+    "TaskDownloadMissingSubtitlesDescription": "Etsii puuttuvia tekstityksiä videon metadatatietojen pohjalta.",
+    "TaskDownloadMissingSubtitles": "Lataa puuttuvat tekstitykset",
+    "TaskRefreshChannelsDescription": "Päivittää internet-kanavien tiedot.",
+    "TaskRefreshChannels": "Päivitä kanavat",
+    "TaskCleanTranscodeDescription": "Poistaa transkoodatut tiedostot jotka ovat yli päivän vanhoja.",
+    "TaskCleanTranscode": "Puhdista transkoodaushakemisto",
+    "TaskUpdatePluginsDescription": "Lataa ja asentaa päivitykset liitännäisille jotka on asetettu päivittymään automaattisesti.",
+    "TaskUpdatePlugins": "Päivitä liitännäiset",
+    "TaskRefreshPeopleDescription": "Päivittää näyttelijöiden ja ohjaajien mediatiedot kirjastossasi.",
+    "TaskRefreshPeople": "Päivitä henkilöt",
+    "TaskCleanLogsDescription": "Poistaa lokitiedostot jotka ovat yli {0} päivää vanhoja.",
+    "TaskCleanLogs": "Puhdista lokihakemisto",
+    "TaskRefreshLibraryDescription": "Skannaa mediakirjastosi uusien tiedostojen varalle, sekä virkistää metatiedot.",
+    "TaskRefreshLibrary": "Skannaa mediakirjasto",
+    "TaskRefreshChapterImagesDescription": "Luo pienoiskuvat videoille joissa on lukuja.",
+    "TaskRefreshChapterImages": "Eristä lukujen kuvat",
+    "TaskCleanCacheDescription": "Poistaa järjestelmälle tarpeettomat väliaikaistiedostot.",
+    "TaskCleanCache": "Tyhjennä välimuisti-hakemisto",
+    "TasksChannelsCategory": "Internet kanavat",
+    "TasksApplicationCategory": "Sovellus",
+    "TasksLibraryCategory": "Kirjasto"
 }
 }

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

@@ -5,7 +5,7 @@
     "Artists": "Artistes",
     "Artists": "Artistes",
     "AuthenticationSucceededWithUserName": "{0} authentifié avec succès",
     "AuthenticationSucceededWithUserName": "{0} authentifié avec succès",
     "Books": "Livres",
     "Books": "Livres",
-    "CameraImageUploadedFrom": "Une nouvelle photo a été chargée depuis {0}",
+    "CameraImageUploadedFrom": "Une nouvelle photographie a été chargée depuis {0}",
     "Channels": "Chaînes",
     "Channels": "Chaînes",
     "ChapterNameValue": "Chapitre {0}",
     "ChapterNameValue": "Chapitre {0}",
     "Collections": "Collections",
     "Collections": "Collections",

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

@@ -71,7 +71,7 @@
     "ScheduledTaskFailedWithName": "{0} sikertelen",
     "ScheduledTaskFailedWithName": "{0} sikertelen",
     "ScheduledTaskStartedWithName": "{0} elkezdve",
     "ScheduledTaskStartedWithName": "{0} elkezdve",
     "ServerNameNeedsToBeRestarted": "{0}-t újra kell indítani",
     "ServerNameNeedsToBeRestarted": "{0}-t újra kell indítani",
-    "Shows": "Műsorok",
+    "Shows": "Sorozatok",
     "Songs": "Dalok",
     "Songs": "Dalok",
     "StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek, próbáld újra hamarosan.",
     "StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek, próbáld újra hamarosan.",
     "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
     "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",

+ 5 - 1
Emby.Server.Implementations/Localization/Core/pt.json

@@ -91,5 +91,9 @@
     "CameraImageUploadedFrom": "Uma nova imagem da câmara foi enviada a partir de {0}",
     "CameraImageUploadedFrom": "Uma nova imagem da câmara foi enviada a partir de {0}",
     "AuthenticationSucceededWithUserName": "{0} autenticado com sucesso",
     "AuthenticationSucceededWithUserName": "{0} autenticado com sucesso",
     "Application": "Aplicação",
     "Application": "Aplicação",
-    "AppDeviceValues": "Aplicação {0}, Dispositivo: {1}"
+    "AppDeviceValues": "Aplicação {0}, Dispositivo: {1}",
+    "TaskCleanCache": "Limpar Diretório de Cache",
+    "TasksApplicationCategory": "Aplicação",
+    "TasksLibraryCategory": "Biblioteca",
+    "TasksMaintenanceCategory": "Manutenção"
 }
 }

+ 117 - 1
Emby.Server.Implementations/Localization/Core/ur_PK.json

@@ -1 +1,117 @@
-{}
+{
+    "HeaderFavoriteAlbums": "پسندیدہ البمز",
+    "HeaderNextUp": "اگلا",
+    "HeaderFavoriteArtists": "پسندیدہ فنکار",
+    "HeaderAlbumArtists": "البم کے فنکار",
+    "Movies": "فلمیں",
+    "HeaderFavoriteEpisodes": "پسندیدہ اقساط",
+    "Collections": "مجموعہ",
+    "Folders": "فولڈرز",
+    "HeaderLiveTV": "براہ راست ٹی وی",
+    "Channels": "چینل",
+    "HeaderContinueWatching": "دیکھنا جاری رکھیں",
+    "Playlists": "پلے لسٹس",
+    "ValueSpecialEpisodeName": "خاص - {0}",
+    "Shows": "شوز",
+    "Genres": "انواع",
+    "Artists": "فنکار",
+    "Sync": "مطابقت",
+    "Photos": "تصوریں",
+    "Albums": "البم",
+    "Favorites": "پسندیدہ",
+    "Songs": "گانے",
+    "Books": "کتابیں",
+    "HeaderFavoriteSongs": "پسندیدہ گانے",
+    "HeaderFavoriteShows": "پسندیدہ شوز",
+    "TaskDownloadMissingSubtitlesDescription": "میٹا ڈیٹا کی تشکیل پر مبنی ذیلی عنوانات کے غائب عنوانات انٹرنیٹ پے تلاش کرتا ہے۔",
+    "TaskDownloadMissingSubtitles": "غائب سب ٹائٹلز ڈاؤن لوڈ کریں",
+    "TaskRefreshChannelsDescription": "انٹرنیٹ چینل کی معلومات کو تازہ دم کرتا ہے۔",
+    "TaskRefreshChannels": "چینلز ریفریش کریں",
+    "TaskCleanTranscodeDescription": "ایک دن سے زیادہ پرانی ٹرانسکوڈ فائلوں کو حذف کرتا ہے۔",
+    "TaskCleanTranscode": "ٹرانس کوڈ ڈائرکٹری صاف کریں",
+    "TaskUpdatePluginsDescription": "پلگ انز کے لئے اپ ڈیٹس ڈاؤن لوڈ اور انسٹال کرتے ہیں جو خود بخود اپ ڈیٹ کرنے کیلئے تشکیل شدہ ہیں۔",
+    "TaskUpdatePlugins": "پلگ انز کو اپ ڈیٹ کریں",
+    "TaskRefreshPeopleDescription": "آپ کی میڈیا لائبریری میں اداکاروں اور ہدایت کاروں کے لئے میٹا ڈیٹا کی تازہ کاری۔",
+    "TaskRefreshPeople": "لوگوں کو تروتازہ کریں",
+    "TaskCleanLogsDescription": "لاگ فائلوں کو حذف کریں جو {0} دن سے زیادہ پرانی ہیں۔",
+    "TaskCleanLogs": "لاگ ڈائرکٹری کو صاف کریں",
+    "TaskRefreshLibraryDescription": "میڈیا لائبریری کو اسکین کرتا ھے ہر میٹا دیٹا کہ تازہ دم کرتا ھے.",
+    "TaskRefreshLibrary": "اسکین میڈیا لائبریری",
+    "TaskRefreshChapterImagesDescription": "بابوں والی ویڈیوز کے لئے تمبنیل بنایں۔",
+    "TaskRefreshChapterImages": "باب کی تصاویر نکالیں",
+    "TaskCleanCacheDescription": "فائلوں کو حذف کریں جنکی ضرورت نھیں ھے۔",
+    "TaskCleanCache": "کیش ڈائرکٹری کلیر کریں",
+    "TasksChannelsCategory": "انٹرنیٹ چینلز",
+    "TasksApplicationCategory": "پروگرام",
+    "TasksLibraryCategory": "لآیبریری",
+    "TasksMaintenanceCategory": "مرمت",
+    "VersionNumber": "ورژن {0}",
+    "ValueHasBeenAddedToLibrary": "{0} آپ کی میڈیا لائبریری میں شامل کر دیا گیا ہے",
+    "UserStoppedPlayingItemWithValues": "{0} نے {1} چلانا ختم کر دیا ھے {2} پے",
+    "UserStartedPlayingItemWithValues": "{0} چلا رہا ہے {1} {2} پے",
+    "UserPolicyUpdatedWithName": "صارف {0} کی پالیسی کیلئے تازہ کاری کی گئی ہے",
+    "UserPasswordChangedWithName": "صارف {0} کے لئے پاس ورڈ تبدیل کر دیا گیا ہے",
+    "UserOnlineFromDevice": "{0} آن لائن ہے {1} سے",
+    "UserOfflineFromDevice": "{0} سے منقطع ہوگیا ہے {1}",
+    "UserLockedOutWithName": "صارف {0} کو لاک آؤٹ کردیا گیا ہے",
+    "UserDownloadingItemWithValues": "{0} ڈاؤن لوڈ کر رھا ھے {1}",
+    "UserDeletedWithName": "صارف {0} کو ہٹا دیا گیا ہے",
+    "UserCreatedWithName": "صارف {0} تشکیل دیا گیا ہے",
+    "User": "صارف",
+    "TvShows": "ٹی وی کے پروگرام",
+    "System": "نظام",
+    "SubtitleDownloadFailureFromForItem": "ذیلی عنوانات {0} سے ڈاؤن لوڈ کرنے میں ناکام {1} کے لیے",
+    "StartupEmbyServerIsLoading": "جیلیفن سرور لوڈ ہورہا ہے۔ براہ کرم جلد ہی دوبارہ کوشش کریں۔",
+    "ServerNameNeedsToBeRestarted": "{0} دوبارہ چلانے کرنے کی ضرورت ہے",
+    "ScheduledTaskStartedWithName": "{0} شروع",
+    "ScheduledTaskFailedWithName": "{0} ناکام",
+    "ProviderValue": "فراہم کرنے والا: {0}",
+    "PluginUpdatedWithName": "{0} تازہ کاری کی گئی تھی",
+    "PluginUninstalledWithName": "[0} ہٹا دیا گیا تھا",
+    "PluginInstalledWithName": "{0} انسٹال کیا گیا تھا",
+    "Plugin": "پلگن",
+    "NotificationOptionVideoPlaybackStopped": "ویڈیو پلے بیک رک گیا",
+    "NotificationOptionVideoPlayback": "ویڈیو پلے بیک شروع ہوا",
+    "NotificationOptionUserLockedOut": "صارف کو لاک آؤٹ کیا گیا",
+    "NotificationOptionTaskFailed": "طے شدہ کام کی ناکامی",
+    "NotificationOptionServerRestartRequired": "سرور دوبارہ چلانے کرنے کی ضرورت ہے",
+    "NotificationOptionPluginUpdateInstalled": "پلگ ان اپ ڈیٹ انسٹال",
+    "NotificationOptionPluginUninstalled": "پلگ ان ہٹا دیا گیا",
+    "NotificationOptionPluginInstalled": "پلگ ان انسٹال ہوا",
+    "NotificationOptionPluginError": "پلگ ان کی ناکامی",
+    "NotificationOptionNewLibraryContent": "نیا مواد شامل کیا گیا",
+    "NotificationOptionInstallationFailed": "تنصیب کی ناکامی",
+    "NotificationOptionCameraImageUploaded": "کیمرے کی تصویر اپ لوڈ ہوگئی",
+    "NotificationOptionAudioPlaybackStopped": "آڈیو پلے بیک رک گیا",
+    "NotificationOptionAudioPlayback": "آڈیو پلے بیک شروع ہوا",
+    "NotificationOptionApplicationUpdateInstalled": "پروگرام اپ ڈیٹ انسٹال ہوچکا ھے",
+    "NotificationOptionApplicationUpdateAvailable": "پروگرام کی تازہ کاری دستیاب ہے",
+    "NewVersionIsAvailable": "جیلیفن سرور کا ایک نیا ورژن ڈاؤن لوڈ کے لئے دستیاب ہے۔",
+    "NameSeasonUnknown": "نامعلوم باب",
+    "NameSeasonNumber": "باب {0}",
+    "NameInstallFailed": "{0} تنصیب ناکام ہوگئی",
+    "MusicVideos": "موسیقی ویڈیو",
+    "Music": "موسیقی",
+    "MixedContent": "مخلوط مواد",
+    "MessageServerConfigurationUpdated": "سرور کو اپ ڈیٹ کر دیا گیا ہے",
+    "MessageNamedServerConfigurationUpdatedWithValue": "سرور ضمن {0} کو ترتیب دے دیا گیا ھے",
+    "MessageApplicationUpdatedTo": "جیلیفن سرور کو اپ ڈیٹ کیا ہے {0}",
+    "MessageApplicationUpdated": "جیلیفن سرور کو اپ ڈیٹ کر دیا گیا ہے",
+    "Latest": "تازہ ترین",
+    "LabelRunningTimeValue": "چلانے کی مدت",
+    "LabelIpAddressValue": "ای پی پتے {0}",
+    "ItemRemovedWithName": "لائبریری سے ہٹا دیا گیا ھے",
+    "ItemAddedWithName": "[0} لائبریری میں شامل کیا گیا ھے",
+    "Inherit": "وراثت میں",
+    "HomeVideos": "ہوم ویڈیو",
+    "HeaderRecordingGroups": "ریکارڈنگ گروپس",
+    "HeaderCameraUploads": "کیمرہ اپلوڈز",
+    "FailedLoginAttemptWithUserName": "لاگن کئ کوشش ناکام {0}",
+    "DeviceOnlineWithName": "{0} متصل ھو چکا ھے",
+    "DeviceOfflineWithName": "{0} منقطع ھو چکا ھے",
+    "ChapterNameValue": "باب",
+    "AuthenticationSucceededWithUserName": "{0} کامیابی کے ساتھ تصدیق ھوچکی ھے",
+    "CameraImageUploadedFrom": "ایک نئی کیمرہ تصویر اپ لوڈ کی گئی ہے {0}",
+    "Application": "پروگرام",
+    "AppDeviceValues": "پروگرام:{0}, آلہ:{1}"
+}

+ 8 - 21
MediaBrowser.Api/ApiEntryPoint.cs

@@ -86,12 +86,9 @@ namespace MediaBrowser.Api
                 return Array.Empty<string>();
                 return Array.Empty<string>();
             }
             }
 
 
-            if (removeEmpty)
-            {
-                return value.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries);
-            }
-
-            return value.Split(separator);
+            return removeEmpty
+                ? value.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries)
+                : value.Split(separator);
         }
         }
 
 
         public SemaphoreSlim GetTranscodingLock(string outputPath)
         public SemaphoreSlim GetTranscodingLock(string outputPath)
@@ -258,7 +255,7 @@ namespace MediaBrowser.Api
 
 
         public void ReportTranscodingProgress(TranscodingJob job, StreamState state, TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
         public void ReportTranscodingProgress(TranscodingJob job, StreamState state, TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
         {
         {
-            var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null;
+            var ticks = transcodingPosition?.Ticks;
 
 
             if (job != null)
             if (job != null)
             {
             {
@@ -487,16 +484,9 @@ namespace MediaBrowser.Api
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         internal Task KillTranscodingJobs(string deviceId, string playSessionId, Func<string, bool> deleteFiles)
         internal Task KillTranscodingJobs(string deviceId, string playSessionId, Func<string, bool> deleteFiles)
         {
         {
-            return KillTranscodingJobs(j =>
-            {
-                if (!string.IsNullOrWhiteSpace(playSessionId))
-                {
-                    return string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase);
-                }
-
-                return string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase);
-
-            }, deleteFiles);
+            return KillTranscodingJobs(j => string.IsNullOrWhiteSpace(playSessionId)
+                ? string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase)
+                : string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase), deleteFiles);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -561,10 +551,7 @@ namespace MediaBrowser.Api
 
 
             lock (job.ProcessLock)
             lock (job.ProcessLock)
             {
             {
-                if (job.TranscodingThrottler != null)
-                {
-                    job.TranscodingThrottler.Stop().GetAwaiter().GetResult();
-                }
+                job.TranscodingThrottler?.Stop().GetAwaiter().GetResult();
 
 
                 var process = job.Process;
                 var process = job.Process;
 
 

+ 20 - 38
MediaBrowser.Api/BaseApiService.cs

@@ -58,12 +58,9 @@ namespace MediaBrowser.Api
 
 
         public static string[] SplitValue(string value, char delim)
         public static string[] SplitValue(string value, char delim)
         {
         {
-            if (value == null)
-            {
-                return Array.Empty<string>();
-            }
-
-            return value.Split(new[] { delim }, StringSplitOptions.RemoveEmptyEntries);
+            return value == null
+                ? Array.Empty<string>()
+                : value.Split(new[] { delim }, StringSplitOptions.RemoveEmptyEntries);
         }
         }
 
 
         public static Guid[] GetGuids(string value)
         public static Guid[] GetGuids(string value)
@@ -97,19 +94,10 @@ namespace MediaBrowser.Api
             var authenticatedUser = auth.User;
             var authenticatedUser = auth.User;
 
 
             // If they're going to update the record of another user, they must be an administrator
             // If they're going to update the record of another user, they must be an administrator
-            if (!userId.Equals(auth.UserId))
-            {
-                if (!authenticatedUser.Policy.IsAdministrator)
-                {
-                    throw new SecurityException("Unauthorized access.");
-                }
-            }
-            else if (restrictUserPreferences)
+            if ((!userId.Equals(auth.UserId) && !authenticatedUser.Policy.IsAdministrator)
+                || (restrictUserPreferences && !authenticatedUser.Policy.EnableUserPreferenceAccess))
             {
             {
-                if (!authenticatedUser.Policy.EnableUserPreferenceAccess)
-                {
-                    throw new SecurityException("Unauthorized access.");
-                }
+                throw new SecurityException("Unauthorized access.");
             }
             }
         }
         }
 
 
@@ -138,8 +126,8 @@ namespace MediaBrowser.Api
                 options.Fields = hasFields.GetItemFields();
                 options.Fields = hasFields.GetItemFields();
             }
             }
 
 
-            if (!options.ContainsField(Model.Querying.ItemFields.RecursiveItemCount)
-                || !options.ContainsField(Model.Querying.ItemFields.ChildCount))
+            if (!options.ContainsField(ItemFields.RecursiveItemCount)
+                || !options.ContainsField(ItemFields.ChildCount))
             {
             {
                 var client = authContext.GetAuthorizationInfo(Request).Client ?? string.Empty;
                 var client = authContext.GetAuthorizationInfo(Request).Client ?? string.Empty;
                 if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 ||
                 if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 ||
@@ -150,7 +138,7 @@ namespace MediaBrowser.Api
                     int oldLen = options.Fields.Length;
                     int oldLen = options.Fields.Length;
                     var arr = new ItemFields[oldLen + 1];
                     var arr = new ItemFields[oldLen + 1];
                     options.Fields.CopyTo(arr, 0);
                     options.Fields.CopyTo(arr, 0);
-                    arr[oldLen] = Model.Querying.ItemFields.RecursiveItemCount;
+                    arr[oldLen] = ItemFields.RecursiveItemCount;
                     options.Fields = arr;
                     options.Fields = arr;
                 }
                 }
 
 
@@ -166,7 +154,7 @@ namespace MediaBrowser.Api
                     int oldLen = options.Fields.Length;
                     int oldLen = options.Fields.Length;
                     var arr = new ItemFields[oldLen + 1];
                     var arr = new ItemFields[oldLen + 1];
                     options.Fields.CopyTo(arr, 0);
                     options.Fields.CopyTo(arr, 0);
-                    arr[oldLen] = Model.Querying.ItemFields.ChildCount;
+                    arr[oldLen] = ItemFields.ChildCount;
                     options.Fields = arr;
                     options.Fields = arr;
                 }
                 }
             }
             }
@@ -282,27 +270,21 @@ namespace MediaBrowser.Api
 
 
             }).OfType<T>().FirstOrDefault();
             }).OfType<T>().FirstOrDefault();
 
 
-            if (result == null)
+            result ??= libraryManager.GetItemList(new InternalItemsQuery
             {
             {
-                result = libraryManager.GetItemList(new InternalItemsQuery
-                {
-                    Name = name.Replace(BaseItem.SlugChar, '/'),
-                    IncludeItemTypes = new[] { typeof(T).Name },
-                    DtoOptions = dtoOptions
+                Name = name.Replace(BaseItem.SlugChar, '/'),
+                IncludeItemTypes = new[] { typeof(T).Name },
+                DtoOptions = dtoOptions
 
 
-                }).OfType<T>().FirstOrDefault();
-            }
+            }).OfType<T>().FirstOrDefault();
 
 
-            if (result == null)
+            result ??= libraryManager.GetItemList(new InternalItemsQuery
             {
             {
-                result = libraryManager.GetItemList(new InternalItemsQuery
-                {
-                    Name = name.Replace(BaseItem.SlugChar, '?'),
-                    IncludeItemTypes = new[] { typeof(T).Name },
-                    DtoOptions = dtoOptions
+                Name = name.Replace(BaseItem.SlugChar, '?'),
+                IncludeItemTypes = new[] { typeof(T).Name },
+                DtoOptions = dtoOptions
 
 
-                }).OfType<T>().FirstOrDefault();
-            }
+            }).OfType<T>().FirstOrDefault();
 
 
             return result;
             return result;
         }
         }

+ 7 - 15
MediaBrowser.Api/ChannelService.cs

@@ -116,12 +116,9 @@ namespace MediaBrowser.Api
         {
         {
             var val = Filters;
             var val = Filters;
 
 
-            if (string.IsNullOrEmpty(val))
-            {
-                return new ItemFilter[] { };
-            }
-
-            return val.Split(',').Select(v => (ItemFilter)Enum.Parse(typeof(ItemFilter), v, true));
+            return string.IsNullOrEmpty(val)
+                ? Array.Empty<ItemFilter>()
+                : val.Split(',').Select(v => Enum.Parse<ItemFilter>(v, true));
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -173,14 +170,9 @@ namespace MediaBrowser.Api
         /// <returns>IEnumerable{ItemFilter}.</returns>
         /// <returns>IEnumerable{ItemFilter}.</returns>
         public IEnumerable<ItemFilter> GetFilters()
         public IEnumerable<ItemFilter> GetFilters()
         {
         {
-            var val = Filters;
-
-            if (string.IsNullOrEmpty(val))
-            {
-                return new ItemFilter[] { };
-            }
-
-            return val.Split(',').Select(v => (ItemFilter)Enum.Parse(typeof(ItemFilter), v, true));
+            return string.IsNullOrEmpty(Filters)
+                ? Array.Empty<ItemFilter>()
+                : Filters.Split(',').Select(v => Enum.Parse<ItemFilter>(v, true));
         }
         }
     }
     }
 
 
@@ -241,7 +233,7 @@ namespace MediaBrowser.Api
             {
             {
                 Limit = request.Limit,
                 Limit = request.Limit,
                 StartIndex = request.StartIndex,
                 StartIndex = request.StartIndex,
-                ChannelIds = new Guid[] { new Guid(request.Id) },
+                ChannelIds = new[] { new Guid(request.Id) },
                 ParentId = string.IsNullOrWhiteSpace(request.FolderId) ? Guid.Empty : new Guid(request.FolderId),
                 ParentId = string.IsNullOrWhiteSpace(request.FolderId) ? Guid.Empty : new Guid(request.FolderId),
                 OrderBy = request.GetOrderBy(),
                 OrderBy = request.GetOrderBy(),
                 DtoOptions = new Controller.Dto.DtoOptions
                 DtoOptions = new Controller.Dto.DtoOptions

+ 7 - 9
MediaBrowser.Api/Devices/DeviceService.cs

@@ -155,16 +155,14 @@ namespace MediaBrowser.Api.Devices
                     Id = id
                     Id = id
                 });
                 });
             }
             }
-            else
+
+            return _deviceManager.AcceptCameraUpload(deviceId, request.RequestStream, new LocalFileInfo
             {
             {
-                return _deviceManager.AcceptCameraUpload(deviceId, request.RequestStream, new LocalFileInfo
-                {
-                    MimeType = Request.ContentType,
-                    Album = album,
-                    Name = name,
-                    Id = id
-                });
-            }
+                MimeType = Request.ContentType,
+                Album = album,
+                Name = name,
+                Id = id
+            });
         }
         }
     }
     }
 }
 }

+ 1 - 6
MediaBrowser.Api/EnvironmentService.cs

@@ -258,12 +258,7 @@ namespace MediaBrowser.Api
                     return false;
                     return false;
                 }
                 }
 
 
-                if (!request.IncludeDirectories && isDirectory)
-                {
-                    return false;
-                }
-
-                return true;
+                return request.IncludeDirectories || !isDirectory;
             });
             });
 
 
             return entries.Select(f => new FileSystemEntryInfo
             return entries.Select(f => new FileSystemEntryInfo

+ 2 - 2
MediaBrowser.Api/FilterService.cs

@@ -133,7 +133,7 @@ namespace MediaBrowser.Api
             // Non recursive not yet supported for library folders
             // Non recursive not yet supported for library folders
             if ((request.Recursive ?? true) || parentItem is UserView || parentItem is ICollectionFolder)
             if ((request.Recursive ?? true) || parentItem is UserView || parentItem is ICollectionFolder)
             {
             {
-                genreQuery.AncestorIds = parentItem == null ? Array.Empty<Guid>() : new Guid[] { parentItem.Id };
+                genreQuery.AncestorIds = parentItem == null ? Array.Empty<Guid>() : new[] { parentItem.Id };
             }
             }
             else
             else
             {
             {
@@ -231,7 +231,7 @@ namespace MediaBrowser.Api
                 EnableTotalRecordCount = false,
                 EnableTotalRecordCount = false,
                 DtoOptions = new Controller.Dto.DtoOptions
                 DtoOptions = new Controller.Dto.DtoOptions
                 {
                 {
-                    Fields = new ItemFields[] { ItemFields.Genres, ItemFields.Tags },
+                    Fields = new[] { ItemFields.Genres, ItemFields.Tags },
                     EnableImages = false,
                     EnableImages = false,
                     EnableUserData = false
                     EnableUserData = false
                 }
                 }

+ 12 - 14
MediaBrowser.Api/Images/ImageService.cs

@@ -657,7 +657,7 @@ namespace MediaBrowser.Api.Images
             if (!string.IsNullOrWhiteSpace(request.Format)
             if (!string.IsNullOrWhiteSpace(request.Format)
                 && Enum.TryParse(request.Format, true, out ImageFormat format))
                 && Enum.TryParse(request.Format, true, out ImageFormat format))
             {
             {
-                return new ImageFormat[] { format };
+                return new[] { format };
             }
             }
 
 
             return GetClientSupportedFormats();
             return GetClientSupportedFormats();
@@ -750,24 +750,22 @@ namespace MediaBrowser.Api.Images
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         public async Task PostImage(BaseItem entity, Stream inputStream, ImageType imageType, string mimeType)
         public async Task PostImage(BaseItem entity, Stream inputStream, ImageType imageType, string mimeType)
         {
         {
-            using (var reader = new StreamReader(inputStream))
-            {
-                var text = await reader.ReadToEndAsync().ConfigureAwait(false);
+            using var reader = new StreamReader(inputStream);
+            var text = await reader.ReadToEndAsync().ConfigureAwait(false);
 
 
-                var bytes = Convert.FromBase64String(text);
+            var bytes = Convert.FromBase64String(text);
 
 
-                var memoryStream = new MemoryStream(bytes)
-                {
-                    Position = 0
-                };
+            var memoryStream = new MemoryStream(bytes)
+            {
+                Position = 0
+            };
 
 
-                // Handle image/png; charset=utf-8
-                mimeType = mimeType.Split(';').FirstOrDefault();
+            // Handle image/png; charset=utf-8
+            mimeType = mimeType.Split(';').FirstOrDefault();
 
 
-                await _providerManager.SaveImage(entity, memoryStream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false);
+            await _providerManager.SaveImage(entity, memoryStream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false);
 
 
-                entity.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
-            }
+            entity.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
         }
         }
     }
     }
 }
 }

+ 12 - 14
MediaBrowser.Api/Images/RemoteImageService.cs

@@ -261,27 +261,25 @@ namespace MediaBrowser.Api.Images
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath)
         private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath)
         {
         {
-            using (var result = await _httpClient.GetResponse(new HttpRequestOptions
+            using var result = await _httpClient.GetResponse(new HttpRequestOptions
             {
             {
                 Url = url,
                 Url = url,
                 BufferContent = false
                 BufferContent = false
 
 
-            }).ConfigureAwait(false))
-            {
-                var ext = result.ContentType.Split('/').Last();
-
-                var fullCachePath = GetFullCachePath(urlHash + "." + ext);
+            }).ConfigureAwait(false);
+            var ext = result.ContentType.Split('/').Last();
 
 
-                Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
-                using (var stream = result.Content)
-                using (var filestream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true))
-                {
-                    await stream.CopyToAsync(filestream).ConfigureAwait(false);
-                }
+            var fullCachePath = GetFullCachePath(urlHash + "." + ext);
 
 
-                Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
-                File.WriteAllText(pointerCachePath, fullCachePath);
+            Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
+            using (var stream = result.Content)
+            {
+                using var filestream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
+                await stream.CopyToAsync(filestream).ConfigureAwait(false);
             }
             }
+
+            Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
+            File.WriteAllText(pointerCachePath, fullCachePath);
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 9 - 2
MediaBrowser.Api/ItemLookupService.cs

@@ -305,9 +305,16 @@ namespace MediaBrowser.Api
 
 
             Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
             Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
             using (var stream = result.Content)
             using (var stream = result.Content)
-            using (var filestream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true))
             {
             {
-                await stream.CopyToAsync(filestream).ConfigureAwait(false);
+                using var fileStream = new FileStream(
+                    fullCachePath,
+                    FileMode.Create,
+                    FileAccess.Write,
+                    FileShare.Read,
+                    IODefaults.FileStreamBufferSize,
+                    true);
+
+                await stream.CopyToAsync(fileStream).ConfigureAwait(false);
             }
             }
 
 
             Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
             Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));

+ 9 - 19
MediaBrowser.Api/ItemUpdateService.cs

@@ -263,8 +263,7 @@ namespace MediaBrowser.Api
             item.Overview = request.Overview;
             item.Overview = request.Overview;
             item.Genres = request.Genres;
             item.Genres = request.Genres;
 
 
-            var episode = item as Episode;
-            if (episode != null)
+            if (item is Episode episode)
             {
             {
                 episode.AirsAfterSeasonNumber = request.AirsAfterSeasonNumber;
                 episode.AirsAfterSeasonNumber = request.AirsAfterSeasonNumber;
                 episode.AirsBeforeEpisodeNumber = request.AirsBeforeEpisodeNumber;
                 episode.AirsBeforeEpisodeNumber = request.AirsBeforeEpisodeNumber;
@@ -302,14 +301,12 @@ namespace MediaBrowser.Api
             item.PreferredMetadataCountryCode = request.PreferredMetadataCountryCode;
             item.PreferredMetadataCountryCode = request.PreferredMetadataCountryCode;
             item.PreferredMetadataLanguage = request.PreferredMetadataLanguage;
             item.PreferredMetadataLanguage = request.PreferredMetadataLanguage;
 
 
-            var hasDisplayOrder = item as IHasDisplayOrder;
-            if (hasDisplayOrder != null)
+            if (item is IHasDisplayOrder hasDisplayOrder)
             {
             {
                 hasDisplayOrder.DisplayOrder = request.DisplayOrder;
                 hasDisplayOrder.DisplayOrder = request.DisplayOrder;
             }
             }
 
 
-            var hasAspectRatio = item as IHasAspectRatio;
-            if (hasAspectRatio != null)
+            if (item is IHasAspectRatio hasAspectRatio)
             {
             {
                 hasAspectRatio.AspectRatio = request.AspectRatio;
                 hasAspectRatio.AspectRatio = request.AspectRatio;
             }
             }
@@ -337,16 +334,14 @@ namespace MediaBrowser.Api
 
 
             item.ProviderIds = request.ProviderIds;
             item.ProviderIds = request.ProviderIds;
 
 
-            var video = item as Video;
-            if (video != null)
+            if (item is Video video)
             {
             {
                 video.Video3DFormat = request.Video3DFormat;
                 video.Video3DFormat = request.Video3DFormat;
             }
             }
 
 
             if (request.AlbumArtists != null)
             if (request.AlbumArtists != null)
             {
             {
-                var hasAlbumArtists = item as IHasAlbumArtist;
-                if (hasAlbumArtists != null)
+                if (item is IHasAlbumArtist hasAlbumArtists)
                 {
                 {
                     hasAlbumArtists.AlbumArtists = request
                     hasAlbumArtists.AlbumArtists = request
                         .AlbumArtists
                         .AlbumArtists
@@ -357,8 +352,7 @@ namespace MediaBrowser.Api
 
 
             if (request.ArtistItems != null)
             if (request.ArtistItems != null)
             {
             {
-                var hasArtists = item as IHasArtist;
-                if (hasArtists != null)
+                if (item is IHasArtist hasArtists)
                 {
                 {
                     hasArtists.Artists = request
                     hasArtists.Artists = request
                         .ArtistItems
                         .ArtistItems
@@ -367,20 +361,17 @@ namespace MediaBrowser.Api
                 }
                 }
             }
             }
 
 
-            var song = item as Audio;
-            if (song != null)
+            if (item is Audio song)
             {
             {
                 song.Album = request.Album;
                 song.Album = request.Album;
             }
             }
 
 
-            var musicVideo = item as MusicVideo;
-            if (musicVideo != null)
+            if (item is MusicVideo musicVideo)
             {
             {
                 musicVideo.Album = request.Album;
                 musicVideo.Album = request.Album;
             }
             }
 
 
-            var series = item as Series;
-            if (series != null)
+            if (item is Series series)
             {
             {
                 series.Status = GetSeriesStatus(request);
                 series.Status = GetSeriesStatus(request);
 
 
@@ -400,7 +391,6 @@ namespace MediaBrowser.Api
             }
             }
 
 
             return (SeriesStatus)Enum.Parse(typeof(SeriesStatus), item.Status, true);
             return (SeriesStatus)Enum.Parse(typeof(SeriesStatus), item.Status, true);
-
         }
         }
     }
     }
 }
 }

+ 50 - 145
MediaBrowser.Api/Library/LibraryService.cs

@@ -348,28 +348,19 @@ namespace MediaBrowser.Api.Library
 
 
         private string[] GetRepresentativeItemTypes(string contentType)
         private string[] GetRepresentativeItemTypes(string contentType)
         {
         {
-            switch (contentType)
+            return contentType switch
             {
             {
-                case CollectionType.BoxSets:
-                    return new string[] { "BoxSet" };
-                case CollectionType.Playlists:
-                    return new string[] { "Playlist" };
-                case CollectionType.Movies:
-                    return new string[] { "Movie" };
-                case CollectionType.TvShows:
-                    return new string[] { "Series", "Season", "Episode" };
-                case CollectionType.Books:
-                    return new string[] { "Book" };
-                case CollectionType.Music:
-                    return new string[] { "MusicAlbum", "MusicArtist", "Audio", "MusicVideo" };
-                case CollectionType.HomeVideos:
-                case CollectionType.Photos:
-                    return new string[] { "Video", "Photo" };
-                case CollectionType.MusicVideos:
-                    return new string[] { "MusicVideo" };
-                default:
-                    return new string[] { "Series", "Season", "Episode", "Movie" };
-            }
+                CollectionType.BoxSets => new[] {"BoxSet"},
+                CollectionType.Playlists => new[] {"Playlist"},
+                CollectionType.Movies => new[] {"Movie"},
+                CollectionType.TvShows => new[] {"Series", "Season", "Episode"},
+                CollectionType.Books => new[] {"Book"},
+                CollectionType.Music => new[] {"MusicAlbum", "MusicArtist", "Audio", "MusicVideo"},
+                CollectionType.HomeVideos => new[] {"Video", "Photo"},
+                CollectionType.Photos => new[] {"Video", "Photo"},
+                CollectionType.MusicVideos => new[] {"MusicVideo"},
+                _ => new[] {"Series", "Season", "Episode", "Movie"}
+            };
         }
         }
 
 
         private bool IsSaverEnabledByDefault(string name, string[] itemTypes, bool isNewLibrary)
         private bool IsSaverEnabledByDefault(string name, string[] itemTypes, bool isNewLibrary)
@@ -397,54 +388,22 @@ namespace MediaBrowser.Api.Library
             {
             {
                 if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase))
                 if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase))
                 {
                 {
-                    if (string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase))
-                    {
-                        return true;
-                    }
-                    if (string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase))
-                    {
-                        return false;
-                    }
-                    if (string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase))
-                    {
-                        return false;
-                    }
-                    if (string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase))
-                    {
-                        return false;
-                    }
-                    return true;
-                }
-                else if (string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase))
-                {
-                    return true;
-                }
-                else if (string.Equals(name, "The Open Movie Database", StringComparison.OrdinalIgnoreCase))
-                {
-                    return false;
-                }
-                else if (string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase))
-                {
-                    return true;
-                }
-                else if (string.Equals(name, "MusicBrainz", StringComparison.OrdinalIgnoreCase))
-                {
-                    return true;
+                    return !(string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase)
+                         || string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase)
+                         || string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase));
                 }
                 }
 
 
-                return false;
+                return string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase)
+                   || string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase)
+                   || string.Equals(name, "MusicBrainz", StringComparison.OrdinalIgnoreCase);
             }
             }
 
 
             var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions
             var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions
                 .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
                 .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
                 .ToArray();
                 .ToArray();
 
 
-            if (metadataOptions.Length == 0)
-            {
-                return true;
-            }
-
-            return metadataOptions.Any(i => !i.DisabledMetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase));
+            return metadataOptions.Length == 0
+               || metadataOptions.Any(i => !i.DisabledMetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase));
         }
         }
 
 
         private bool IsImageFetcherEnabledByDefault(string name, string type, bool isNewLibrary)
         private bool IsImageFetcherEnabledByDefault(string name, string type, bool isNewLibrary)
@@ -453,50 +412,17 @@ namespace MediaBrowser.Api.Library
             {
             {
                 if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase))
                 if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase))
                 {
                 {
-                    if (string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase))
-                    {
-                        return false;
-                    }
-                    if (string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase))
-                    {
-                        return false;
-                    }
-                    if (string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase))
-                    {
-                        return false;
-                    }
-                    if (string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase))
-                    {
-                        return false;
-                    }
-                    return true;
-                }
-                else if (string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase))
-                {
-                    return true;
-                }
-                else if (string.Equals(name, "The Open Movie Database", StringComparison.OrdinalIgnoreCase))
-                {
-                    return false;
-                }
-                else if (string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase))
-                {
-                    return true;
-                }
-                else if (string.Equals(name, "Emby Designs", StringComparison.OrdinalIgnoreCase))
-                {
-                    return true;
-                }
-                else if (string.Equals(name, "Screen Grabber", StringComparison.OrdinalIgnoreCase))
-                {
-                    return true;
-                }
-                else if (string.Equals(name, "Image Extractor", StringComparison.OrdinalIgnoreCase))
-                {
-                    return true;
+                    return !string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase)
+                           && !string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase)
+                           && !string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase)
+                           && !string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase);
                 }
                 }
 
 
-                return false;
+                return string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase)
+                       || string.Equals(name, "Screen Grabber", StringComparison.OrdinalIgnoreCase)
+                       || string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase)
+                       || string.Equals(name, "Emby Designs", StringComparison.OrdinalIgnoreCase)
+                       || string.Equals(name, "Image Extractor", StringComparison.OrdinalIgnoreCase);
             }
             }
 
 
             var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions
             var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions
@@ -561,8 +487,7 @@ namespace MediaBrowser.Api.Library
 
 
             foreach (var type in types)
             foreach (var type in types)
             {
             {
-                ImageOption[] defaultImageOptions = null;
-                TypeOptions.DefaultImageOptions.TryGetValue(type, out defaultImageOptions);
+                TypeOptions.DefaultImageOptions.TryGetValue(type, out var defaultImageOptions);
 
 
                 typeOptions.Add(new LibraryTypeOptions
                 typeOptions.Add(new LibraryTypeOptions
                 {
                 {
@@ -609,8 +534,6 @@ namespace MediaBrowser.Api.Library
 
 
         public object Get(GetSimilarItems request)
         public object Get(GetSimilarItems request)
         {
         {
-            var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null;
-
             var item = string.IsNullOrEmpty(request.Id) ?
             var item = string.IsNullOrEmpty(request.Id) ?
                 (!request.UserId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() :
                 (!request.UserId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() :
                 _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
                 _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
@@ -668,7 +591,7 @@ namespace MediaBrowser.Api.Library
             // ExcludeArtistIds
             // ExcludeArtistIds
             if (!string.IsNullOrEmpty(request.ExcludeArtistIds))
             if (!string.IsNullOrEmpty(request.ExcludeArtistIds))
             {
             {
-                query.ExcludeArtistIds = BaseApiService.GetGuids(request.ExcludeArtistIds);
+                query.ExcludeArtistIds = GetGuids(request.ExcludeArtistIds);
             }
             }
 
 
             List<BaseItem> itemsResult;
             List<BaseItem> itemsResult;
@@ -689,7 +612,6 @@ namespace MediaBrowser.Api.Library
             var result = new QueryResult<BaseItemDto>
             var result = new QueryResult<BaseItemDto>
             {
             {
                 Items = returnList,
                 Items = returnList,
-
                 TotalRecordCount = itemsResult.Count
                 TotalRecordCount = itemsResult.Count
             };
             };
 
 
@@ -919,12 +841,10 @@ namespace MediaBrowser.Api.Library
 
 
         private BaseItem TranslateParentItem(BaseItem item, User user)
         private BaseItem TranslateParentItem(BaseItem item, User user)
         {
         {
-            if (item.GetParent() is AggregateFolder)
-            {
-                return _libraryManager.GetUserRootFolder().GetChildren(user, true).FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path));
-            }
-
-            return item;
+            return item.GetParent() is AggregateFolder
+                ? _libraryManager.GetUserRootFolder().GetChildren(user, true)
+                    .FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path))
+                : item;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -1086,7 +1006,7 @@ namespace MediaBrowser.Api.Library
             var item = string.IsNullOrEmpty(request.Id)
             var item = string.IsNullOrEmpty(request.Id)
                            ? (!request.UserId.Equals(Guid.Empty)
                            ? (!request.UserId.Equals(Guid.Empty)
                                   ? _libraryManager.GetUserRootFolder()
                                   ? _libraryManager.GetUserRootFolder()
-                                  : (Folder)_libraryManager.RootFolder)
+                                  : _libraryManager.RootFolder)
                            : _libraryManager.GetItemById(request.Id);
                            : _libraryManager.GetItemById(request.Id);
 
 
             if (item == null)
             if (item == null)
@@ -1094,18 +1014,13 @@ namespace MediaBrowser.Api.Library
                 throw new ResourceNotFoundException("Item not found.");
                 throw new ResourceNotFoundException("Item not found.");
             }
             }
 
 
-            BaseItem[] themeItems = Array.Empty<BaseItem>();
+            IEnumerable<BaseItem> themeItems;
 
 
             while (true)
             while (true)
             {
             {
-                themeItems = item.GetThemeSongs().ToArray();
-
-                if (themeItems.Length > 0)
-                {
-                    break;
-                }
+                themeItems = item.GetThemeSongs();
 
 
-                if (!request.InheritFromParent)
+                if (themeItems.Any() || !request.InheritFromParent)
                 {
                 {
                     break;
                     break;
                 }
                 }
@@ -1119,11 +1034,9 @@ namespace MediaBrowser.Api.Library
             }
             }
 
 
             var dtoOptions = GetDtoOptions(_authContext, request);
             var dtoOptions = GetDtoOptions(_authContext, request);
-
-            var dtos = themeItems
-                            .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
-
-            var items = dtos.ToArray();
+            var items = themeItems
+                .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
+                .ToArray();
 
 
             return new ThemeMediaResult
             return new ThemeMediaResult
             {
             {
@@ -1140,9 +1053,7 @@ namespace MediaBrowser.Api.Library
         /// <returns>System.Object.</returns>
         /// <returns>System.Object.</returns>
         public object Get(GetThemeVideos request)
         public object Get(GetThemeVideos request)
         {
         {
-            var result = GetThemeVideos(request);
-
-            return ToOptimizedResult(result);
+            return ToOptimizedResult(GetThemeVideos(request));
         }
         }
 
 
         public ThemeMediaResult GetThemeVideos(GetThemeVideos request)
         public ThemeMediaResult GetThemeVideos(GetThemeVideos request)
@@ -1152,7 +1063,7 @@ namespace MediaBrowser.Api.Library
             var item = string.IsNullOrEmpty(request.Id)
             var item = string.IsNullOrEmpty(request.Id)
                            ? (!request.UserId.Equals(Guid.Empty)
                            ? (!request.UserId.Equals(Guid.Empty)
                                   ? _libraryManager.GetUserRootFolder()
                                   ? _libraryManager.GetUserRootFolder()
-                                  : (Folder)_libraryManager.RootFolder)
+                                  : _libraryManager.RootFolder)
                            : _libraryManager.GetItemById(request.Id);
                            : _libraryManager.GetItemById(request.Id);
 
 
             if (item == null)
             if (item == null)
@@ -1160,18 +1071,13 @@ namespace MediaBrowser.Api.Library
                 throw new ResourceNotFoundException("Item not found.");
                 throw new ResourceNotFoundException("Item not found.");
             }
             }
 
 
-            BaseItem[] themeItems = Array.Empty<BaseItem>();
+            IEnumerable<BaseItem> themeItems;
 
 
             while (true)
             while (true)
             {
             {
-                themeItems = item.GetThemeVideos().ToArray();
-
-                if (themeItems.Length > 0)
-                {
-                    break;
-                }
+                themeItems = item.GetThemeVideos();
 
 
-                if (!request.InheritFromParent)
+                if (themeItems.Any() || !request.InheritFromParent)
                 {
                 {
                     break;
                     break;
                 }
                 }
@@ -1186,10 +1092,9 @@ namespace MediaBrowser.Api.Library
 
 
             var dtoOptions = GetDtoOptions(_authContext, request);
             var dtoOptions = GetDtoOptions(_authContext, request);
 
 
-            var dtos = themeItems
-                            .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
-
-            var items = dtos.ToArray();
+            var items = themeItems
+                .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
+                .ToArray();
 
 
             return new ThemeMediaResult
             return new ThemeMediaResult
             {
             {

+ 4 - 8
MediaBrowser.Api/Library/LibraryStructureService.cs

@@ -327,15 +327,11 @@ namespace MediaBrowser.Api.Library
 
 
             try
             try
             {
             {
-                var mediaPath = request.PathInfo;
-
-                if (mediaPath == null)
+                var mediaPath = request.PathInfo ?? new MediaPathInfo
                 {
                 {
-                    mediaPath = new MediaPathInfo
-                    {
-                        Path = request.Path
-                    };
-                }
+                    Path = request.Path
+                };
+
                 _libraryManager.AddMediaPath(request.Name, mediaPath);
                 _libraryManager.AddMediaPath(request.Name, mediaPath);
             }
             }
             finally
             finally

+ 5 - 7
MediaBrowser.Api/LiveTv/LiveTvService.cs

@@ -885,11 +885,10 @@ namespace MediaBrowser.Api.LiveTv
         {
         {
             // SchedulesDirect requires a SHA1 hash of the user's password
             // SchedulesDirect requires a SHA1 hash of the user's password
             // https://github.com/SchedulesDirect/JSON-Service/wiki/API-20141201#obtain-a-token
             // https://github.com/SchedulesDirect/JSON-Service/wiki/API-20141201#obtain-a-token
-            using (SHA1 sha = SHA1.Create())
-            {
-                return Hex.Encode(
-                    sha.ComputeHash(Encoding.UTF8.GetBytes(str)));
-            }
+            using SHA1 sha = SHA1.Create();
+
+            return Hex.Encode(
+                sha.ComputeHash(Encoding.UTF8.GetBytes(str)));
         }
         }
 
 
         public void Delete(DeleteListingProvider request)
         public void Delete(DeleteListingProvider request)
@@ -1050,8 +1049,7 @@ namespace MediaBrowser.Api.LiveTv
             {
             {
                 query.IsSeries = true;
                 query.IsSeries = true;
 
 
-                var series = _libraryManager.GetItemById(request.LibrarySeriesId) as Series;
-                if (series != null)
+                if (_libraryManager.GetItemById(request.LibrarySeriesId) is Series series)
                 {
                 {
                     query.Name = series.Name;
                     query.Name = series.Name;
                 }
                 }

+ 1 - 1
MediaBrowser.Api/Movies/MoviesService.cs

@@ -394,7 +394,7 @@ namespace MediaBrowser.Api.Movies
         {
         {
             var people = _libraryManager.GetPeople(new InternalPeopleQuery
             var people = _libraryManager.GetPeople(new InternalPeopleQuery
             {
             {
-                PersonTypes = new string[]
+                PersonTypes = new[]
                 {
                 {
                     PersonType.Director
                     PersonType.Director
                 }
                 }

+ 184 - 222
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -137,12 +137,9 @@ namespace MediaBrowser.Api.Playback
             var ext = outputFileExtension.ToLowerInvariant();
             var ext = outputFileExtension.ToLowerInvariant();
             var folder = ServerConfigurationManager.GetTranscodePath();
             var folder = ServerConfigurationManager.GetTranscodePath();
 
 
-            if (EnableOutputInSubFolder)
-            {
-                return Path.Combine(folder, filename, filename + ext);
-            }
-
-            return Path.Combine(folder, filename + ext);
+            return EnableOutputInSubFolder
+                ? Path.Combine(folder, filename, filename + ext)
+                : Path.Combine(folder, filename + ext);
         }
         }
 
 
         protected virtual string GetDefaultEncoderPreset()
         protected virtual string GetDefaultEncoderPreset()
@@ -248,14 +245,8 @@ namespace MediaBrowser.Api.Playback
             if (state.VideoRequest != null
             if (state.VideoRequest != null
                 && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
                 && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
             {
             {
-                if (string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
-                {
-                    logFilePrefix = "ffmpeg-remux";
-                }
-                else
-                {
-                    logFilePrefix = "ffmpeg-directstream";
-                }
+                logFilePrefix = string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)
+                    ? "ffmpeg-remux" : "ffmpeg-directstream";
             }
             }
 
 
             var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt");
             var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt");
@@ -389,195 +380,181 @@ namespace MediaBrowser.Api.Playback
                     continue;
                     continue;
                 }
                 }
 
 
-                if (i == 0)
-                {
-                    request.DeviceProfileId = val;
-                }
-                else if (i == 1)
-                {
-                    request.DeviceId = val;
-                }
-                else if (i == 2)
-                {
-                    request.MediaSourceId = val;
-                }
-                else if (i == 3)
-                {
-                    request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
-                }
-                else if (i == 4)
-                {
-                    if (videoRequest != null)
-                    {
-                        videoRequest.VideoCodec = val;
-                    }
-                }
-                else if (i == 5)
-                {
-                    request.AudioCodec = val;
-                }
-                else if (i == 6)
-                {
-                    if (videoRequest != null)
-                    {
-                        videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
-                    }
-                }
-                else if (i == 7)
-                {
-                    if (videoRequest != null)
-                    {
-                        videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
-                    }
-                }
-                else if (i == 8)
-                {
-                    if (videoRequest != null)
-                    {
-                        videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture);
-                    }
-                }
-                else if (i == 9)
-                {
-                    request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture);
-                }
-                else if (i == 10)
-                {
-                    request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
-                }
-                else if (i == 11)
-                {
-                    if (videoRequest != null)
-                    {
-                        videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture);
-                    }
-                }
-                else if (i == 12)
-                {
-                    if (videoRequest != null)
-                    {
-                        videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture);
-                    }
-                }
-                else if (i == 13)
-                {
-                    if (videoRequest != null)
-                    {
-                        videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture);
-                    }
-                }
-                else if (i == 14)
-                {
-                    request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture);
-                }
-                else if (i == 15)
-                {
-                    if (videoRequest != null)
-                    {
-                        videoRequest.Level = val;
-                    }
-                }
-                else if (i == 16)
-                {
-                    if (videoRequest != null)
-                    {
-                        videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture);
-                    }
-                }
-                else if (i == 17)
-                {
-                    if (videoRequest != null)
-                    {
-                        videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture);
-                    }
-                }
-                else if (i == 18)
-                {
-                    if (videoRequest != null)
-                    {
-                        videoRequest.Profile = val;
-                    }
-                }
-                else if (i == 19)
-                {
-                    // cabac no longer used
-                }
-                else if (i == 20)
-                {
-                    request.PlaySessionId = val;
-                }
-                else if (i == 21)
-                {
-                    // api_key
-                }
-                else if (i == 22)
-                {
-                    request.LiveStreamId = val;
-                }
-                else if (i == 23)
-                {
-                    // Duplicating ItemId because of MediaMonkey
-                }
-                else if (i == 24)
-                {
-                    if (videoRequest != null)
-                    {
-                        videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
-                    }
-                }
-                else if (i == 25)
-                {
-                    if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
-                    {
-                        if (Enum.TryParse(val, out SubtitleDeliveryMethod method))
+                switch (i)
+                {
+                    case 0:
+                        request.DeviceProfileId = val;
+                        break;
+                    case 1:
+                        request.DeviceId = val;
+                        break;
+                    case 2:
+                        request.MediaSourceId = val;
+                        break;
+                    case 3:
+                        request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+                        break;
+                    case 4:
+                        if (videoRequest != null)
                         {
                         {
-                            videoRequest.SubtitleMethod = method;
+                            videoRequest.VideoCodec = val;
                         }
                         }
-                    }
-                }
-                else if (i == 26)
-                {
-                    request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
-                }
-                else if (i == 27)
-                {
-                    if (videoRequest != null)
-                    {
-                        videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
-                    }
-                }
-                else if (i == 28)
-                {
-                    request.Tag = val;
-                }
-                else if (i == 29)
-                {
-                    if (videoRequest != null)
-                    {
-                        videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
-                    }
-                }
-                else if (i == 30)
-                {
-                    request.SubtitleCodec = val;
-                }
-                else if (i == 31)
-                {
-                    if (videoRequest != null)
-                    {
-                        videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
-                    }
-                }
-                else if (i == 32)
-                {
-                    if (videoRequest != null)
-                    {
-                        videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
-                    }
-                }
-                else if (i == 33)
-                {
-                    request.TranscodeReasons = val;
+
+                        break;
+                    case 5:
+                        request.AudioCodec = val;
+                        break;
+                    case 6:
+                        if (videoRequest != null)
+                        {
+                            videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
+                        }
+
+                        break;
+                    case 7:
+                        if (videoRequest != null)
+                        {
+                            videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
+                        }
+
+                        break;
+                    case 8:
+                        if (videoRequest != null)
+                        {
+                            videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture);
+                        }
+
+                        break;
+                    case 9:
+                        request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture);
+                        break;
+                    case 10:
+                        request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
+                        break;
+                    case 11:
+                        if (videoRequest != null)
+                        {
+                            videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture);
+                        }
+
+                        break;
+                    case 12:
+                        if (videoRequest != null)
+                        {
+                            videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture);
+                        }
+
+                        break;
+                    case 13:
+                        if (videoRequest != null)
+                        {
+                            videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture);
+                        }
+
+                        break;
+                    case 14:
+                        request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture);
+                        break;
+                    case 15:
+                        if (videoRequest != null)
+                        {
+                            videoRequest.Level = val;
+                        }
+
+                        break;
+                    case 16:
+                        if (videoRequest != null)
+                        {
+                            videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture);
+                        }
+
+                        break;
+                    case 17:
+                        if (videoRequest != null)
+                        {
+                            videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture);
+                        }
+
+                        break;
+                    case 18:
+                        if (videoRequest != null)
+                        {
+                            videoRequest.Profile = val;
+                        }
+
+                        break;
+                    case 19:
+                        // cabac no longer used
+                        break;
+                    case 20:
+                        request.PlaySessionId = val;
+                        break;
+                    case 21:
+                        // api_key
+                        break;
+                    case 22:
+                        request.LiveStreamId = val;
+                        break;
+                    case 23:
+                        // Duplicating ItemId because of MediaMonkey
+                        break;
+                    case 24:
+                        if (videoRequest != null)
+                        {
+                            videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+                        }
+
+                        break;
+                    case 25:
+                        if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
+                        {
+                            if (Enum.TryParse(val, out SubtitleDeliveryMethod method))
+                            {
+                                videoRequest.SubtitleMethod = method;
+                            }
+                        }
+
+                        break;
+                    case 26:
+                        request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
+                        break;
+                    case 27:
+                        if (videoRequest != null)
+                        {
+                            videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+                        }
+
+                        break;
+                    case 28:
+                        request.Tag = val;
+                        break;
+                    case 29:
+                        if (videoRequest != null)
+                        {
+                            videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+                        }
+
+                        break;
+                    case 30:
+                        request.SubtitleCodec = val;
+                        break;
+                    case 31:
+                        if (videoRequest != null)
+                        {
+                            videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+                        }
+
+                        break;
+                    case 32:
+                        if (videoRequest != null)
+                        {
+                            videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+                        }
+
+                        break;
+                    case 33:
+                        request.TranscodeReasons = val;
+                        break;
                 }
                 }
             }
             }
         }
         }
@@ -630,14 +607,9 @@ namespace MediaBrowser.Api.Playback
                 throw new ArgumentException("Invalid timeseek header");
                 throw new ArgumentException("Invalid timeseek header");
             }
             }
             int index = value.IndexOf('-');
             int index = value.IndexOf('-');
-            if (index == -1)
-            {
-                value = value.Substring(Npt.Length);
-            }
-            else
-            {
-                value = value.Substring(Npt.Length, index - Npt.Length);
-            }
+            value = index == -1
+                ? value.Substring(Npt.Length)
+                : value.Substring(Npt.Length, index - Npt.Length);
 
 
             if (value.IndexOf(':') == -1)
             if (value.IndexOf(':') == -1)
             {
             {
@@ -856,21 +828,11 @@ namespace MediaBrowser.Api.Playback
             {
             {
                 state.DeviceProfile = DlnaManager.GetProfile(state.Request.DeviceProfileId);
                 state.DeviceProfile = DlnaManager.GetProfile(state.Request.DeviceProfileId);
             }
             }
-            else
+            else if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
             {
             {
-                if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
-                {
-                    var caps = DeviceManager.GetCapabilities(state.Request.DeviceId);
+                var caps = DeviceManager.GetCapabilities(state.Request.DeviceId);
 
 
-                    if (caps != null)
-                    {
-                        state.DeviceProfile = caps.DeviceProfile;
-                    }
-                    else
-                    {
-                        state.DeviceProfile = DlnaManager.GetProfile(headers);
-                    }
-                }
+                state.DeviceProfile = caps == null ? DlnaManager.GetProfile(headers) : caps.DeviceProfile;
             }
             }
 
 
             var profile = state.DeviceProfile;
             var profile = state.DeviceProfile;

+ 31 - 42
MediaBrowser.Api/Playback/Hls/BaseHlsService.cs

@@ -140,7 +140,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
 
             if (isLive)
             if (isLive)
             {
             {
-                job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
+                job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
 
 
                 if (job != null)
                 if (job != null)
                 {
                 {
@@ -156,7 +156,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
 
             var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, baselineStreamBitrate);
             var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, baselineStreamBitrate);
 
 
-            job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
+            job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
 
 
             if (job != null)
             if (job != null)
             {
             {
@@ -168,22 +168,19 @@ namespace MediaBrowser.Api.Playback.Hls
 
 
         private string GetLivePlaylistText(string path, int segmentLength)
         private string GetLivePlaylistText(string path, int segmentLength)
         {
         {
-            using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
-            {
-                using (var reader = new StreamReader(stream))
-                {
-                    var text = reader.ReadToEnd();
+            using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+            using var reader = new StreamReader(stream);
 
 
-                    text = text.Replace("#EXTM3U", "#EXTM3U\n#EXT-X-PLAYLIST-TYPE:EVENT");
+            var text = reader.ReadToEnd();
 
 
-                    var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(CultureInfo.InvariantCulture);
+            text = text.Replace("#EXTM3U", "#EXTM3U\n#EXT-X-PLAYLIST-TYPE:EVENT");
 
 
-                    text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength - 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
-                    //text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
+            var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(CultureInfo.InvariantCulture);
 
 
-                    return text;
-                }
-            }
+            text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength - 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
+            //text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
+
+            return text;
         }
         }
 
 
         private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, int baselineStreamBitrate)
         private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, int baselineStreamBitrate)
@@ -212,29 +209,25 @@ namespace MediaBrowser.Api.Playback.Hls
                 try
                 try
                 {
                 {
                     // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
                     // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
-                    using (var fileStream = GetPlaylistFileStream(playlist))
+                    using var fileStream = GetPlaylistFileStream(playlist);
+                    using var reader = new StreamReader(fileStream);
+                    var count = 0;
+
+                    while (!reader.EndOfStream)
                     {
                     {
-                        using (var reader = new StreamReader(fileStream))
-                        {
-                            var count = 0;
+                        var line = reader.ReadLine();
 
 
-                            while (!reader.EndOfStream)
+                        if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1)
+                        {
+                            count++;
+                            if (count >= segmentCount)
                             {
                             {
-                                var line = reader.ReadLine();
-
-                                if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1)
-                                {
-                                    count++;
-                                    if (count >= segmentCount)
-                                    {
-                                        Logger.LogDebug("Finished waiting for {0} segments in {1}", segmentCount, playlist);
-                                        return;
-                                    }
-                                }
+                                Logger.LogDebug("Finished waiting for {0} segments in {1}", segmentCount, playlist);
+                                return;
                             }
                             }
-                            await Task.Delay(100, cancellationToken).ConfigureAwait(false);
                         }
                         }
                     }
                     }
+                    await Task.Delay(100, cancellationToken).ConfigureAwait(false);
                 }
                 }
                 catch (IOException)
                 catch (IOException)
                 {
                 {
@@ -247,17 +240,13 @@ namespace MediaBrowser.Api.Playback.Hls
 
 
         protected Stream GetPlaylistFileStream(string path)
         protected Stream GetPlaylistFileStream(string path)
         {
         {
-            var tmpPath = path + ".tmp";
-            tmpPath = path;
-
-            try
-            {
-                return new FileStream(tmpPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, FileOptions.SequentialScan);
-            }
-            catch (IOException)
-            {
-                return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, FileOptions.SequentialScan);
-            }
+            return new FileStream(
+                path,
+                FileMode.Open,
+                FileAccess.Read,
+                FileShare.ReadWrite,
+                IODefaults.FileStreamBufferSize,
+                FileOptions.SequentialScan);
         }
         }
 
 
         protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding)
         protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding)

+ 4 - 6
MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs

@@ -284,7 +284,7 @@ namespace MediaBrowser.Api.Playback.Hls
             //}
             //}
 
 
             Logger.LogDebug("returning {0} [general case]", segmentPath);
             Logger.LogDebug("returning {0} [general case]", segmentPath);
-            job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
+            job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
             return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false);
             return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false);
         }
         }
 
 
@@ -438,8 +438,7 @@ namespace MediaBrowser.Api.Playback.Hls
         {
         {
             var segmentId = "0";
             var segmentId = "0";
 
 
-            var segmentRequest = request as GetHlsVideoSegment;
-            if (segmentRequest != null)
+            if (request is GetHlsVideoSegment segmentRequest)
             {
             {
                 segmentId = segmentRequest.SegmentId;
                 segmentId = segmentRequest.SegmentId;
             }
             }
@@ -690,8 +689,7 @@ namespace MediaBrowser.Api.Playback.Hls
                 return false;
                 return false;
             }
             }
 
 
-            var request = state.Request as IMasterHlsRequest;
-            if (request != null && !request.EnableAdaptiveBitrateStreaming)
+            if (state.Request is IMasterHlsRequest request && !request.EnableAdaptiveBitrateStreaming)
             {
             {
                 return false;
                 return false;
             }
             }
@@ -936,7 +934,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
 
                 var framerate = state.VideoStream?.RealFrameRate;
                 var framerate = state.VideoStream?.RealFrameRate;
 
 
-                if (framerate != null && framerate.HasValue)
+                if (framerate.HasValue)
                 {
                 {
                     // This is to make sure keyframe interval is limited to our segment,
                     // This is to make sure keyframe interval is limited to our segment,
                     // as forcing keyframes is not enough.
                     // as forcing keyframes is not enough.

+ 7 - 16
MediaBrowser.Api/Playback/MediaInfoService.cs

@@ -234,7 +234,7 @@ namespace MediaBrowser.Api.Playback
                         OpenToken = mediaSource.OpenToken
                         OpenToken = mediaSource.OpenToken
                     }).ConfigureAwait(false);
                     }).ConfigureAwait(false);
 
 
-                    info.MediaSources = new MediaSourceInfo[] { openStreamResult.MediaSource };
+                    info.MediaSources = new[] { openStreamResult.MediaSource };
                 }
                 }
             }
             }
 
 
@@ -289,7 +289,7 @@ namespace MediaBrowser.Api.Playback
             {
             {
                 var mediaSource = await _mediaSourceManager.GetLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false);
                 var mediaSource = await _mediaSourceManager.GetLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false);
 
 
-                mediaSources = new MediaSourceInfo[] { mediaSource };
+                mediaSources = new[] { mediaSource };
             }
             }
 
 
             if (mediaSources.Length == 0)
             if (mediaSources.Length == 0)
@@ -366,7 +366,7 @@ namespace MediaBrowser.Api.Playback
 
 
             var options = new VideoOptions
             var options = new VideoOptions
             {
             {
-                MediaSources = new MediaSourceInfo[] { mediaSource },
+                MediaSources = new[] { mediaSource },
                 Context = EncodingContext.Streaming,
                 Context = EncodingContext.Streaming,
                 DeviceId = auth.DeviceId,
                 DeviceId = auth.DeviceId,
                 ItemId = item.Id,
                 ItemId = item.Id,
@@ -572,8 +572,7 @@ namespace MediaBrowser.Api.Playback
             {
             {
                 attachment.DeliveryUrl = string.Format(
                 attachment.DeliveryUrl = string.Format(
                     CultureInfo.InvariantCulture,
                     CultureInfo.InvariantCulture,
-                    "{0}/Videos/{1}/{2}/Attachments/{3}",
-                    ServerConfigurationManager.Configuration.BaseUrl,
+                    "/Videos/{0}/{1}/Attachments/{2}",
                     item.Id,
                     item.Id,
                     mediaSource.Id,
                     mediaSource.Id,
                     attachment.Index);
                     attachment.Index);
@@ -583,7 +582,7 @@ namespace MediaBrowser.Api.Playback
         private long? GetMaxBitrate(long? clientMaxBitrate, User user)
         private long? GetMaxBitrate(long? clientMaxBitrate, User user)
         {
         {
             var maxBitrate = clientMaxBitrate;
             var maxBitrate = clientMaxBitrate;
-            var remoteClientMaxBitrate = user == null ? 0 : user.Policy.RemoteClientBitrateLimit;
+            var remoteClientMaxBitrate = user?.Policy.RemoteClientBitrateLimit ?? 0;
 
 
             if (remoteClientMaxBitrate <= 0)
             if (remoteClientMaxBitrate <= 0)
             {
             {
@@ -662,17 +661,9 @@ namespace MediaBrowser.Api.Playback
                 };
                 };
             }).ThenBy(i =>
             }).ThenBy(i =>
             {
             {
-                if (maxBitrate.HasValue)
+                if (maxBitrate.HasValue && i.Bitrate.HasValue)
                 {
                 {
-                    if (i.Bitrate.HasValue)
-                    {
-                        if (i.Bitrate.Value <= maxBitrate.Value)
-                        {
-                            return 0;
-                        }
-
-                        return 2;
-                    }
+                    return i.Bitrate.Value <= maxBitrate.Value ? 0 : 2;
                 }
                 }
 
 
                 return 1;
                 return 1;

+ 2 - 2
MediaBrowser.Api/Playback/UniversalAudioService.cs

@@ -167,7 +167,7 @@ namespace MediaBrowser.Api.Playback
                     AudioCodec = request.AudioCodec,
                     AudioCodec = request.AudioCodec,
                     Protocol = request.TranscodingProtocol,
                     Protocol = request.TranscodingProtocol,
                     BreakOnNonKeyFrames = request.BreakOnNonKeyFrames,
                     BreakOnNonKeyFrames = request.BreakOnNonKeyFrames,
-                    MaxAudioChannels = request.TranscodingAudioChannels.HasValue ? request.TranscodingAudioChannels.Value.ToString(CultureInfo.InvariantCulture) : null
+                    MaxAudioChannels = request.TranscodingAudioChannels?.ToString(CultureInfo.InvariantCulture)
                 }
                 }
             };
             };
 
 
@@ -300,7 +300,7 @@ namespace MediaBrowser.Api.Playback
 
 
                 // hls segment container can only be mpegts or fmp4 per ffmpeg documentation
                 // hls segment container can only be mpegts or fmp4 per ffmpeg documentation
                 // TODO: remove this when we switch back to the segment muxer
                 // TODO: remove this when we switch back to the segment muxer
-                var supportedHLSContainers = new string[] { "mpegts", "fmp4" };
+                var supportedHLSContainers = new[] { "mpegts", "fmp4" };
 
 
                 var newRequest = new GetMasterHlsAudioPlaylist
                 var newRequest = new GetMasterHlsAudioPlaylist
                 {
                 {

+ 1 - 3
MediaBrowser.Api/PluginService.cs

@@ -243,9 +243,7 @@ namespace MediaBrowser.Api
             // https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs
             // https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs
             var id = Guid.Parse(GetPathValue(1));
             var id = Guid.Parse(GetPathValue(1));
 
 
-            var plugin = _appHost.Plugins.First(p => p.Id == id) as IHasPluginConfiguration;
-
-            if (plugin == null)
+            if (!(_appHost.Plugins.First(p => p.Id == id) is IHasPluginConfiguration plugin))
             {
             {
                 throw new FileNotFoundException();
                 throw new FileNotFoundException();
             }
             }

+ 2 - 6
MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs

@@ -123,9 +123,7 @@ namespace MediaBrowser.Api.ScheduledTasks
                 {
                 {
                     var isHidden = false;
                     var isHidden = false;
 
 
-                    var configurableTask = i.ScheduledTask as IConfigurableScheduledTask;
-
-                    if (configurableTask != null)
+                    if (i.ScheduledTask is IConfigurableScheduledTask configurableTask)
                     {
                     {
                         isHidden = configurableTask.IsHidden;
                         isHidden = configurableTask.IsHidden;
                     }
                     }
@@ -142,9 +140,7 @@ namespace MediaBrowser.Api.ScheduledTasks
                 {
                 {
                     var isEnabled = true;
                     var isEnabled = true;
 
 
-                    var configurableTask = i.ScheduledTask as IConfigurableScheduledTask;
-
-                    if (configurableTask != null)
+                    if (i.ScheduledTask is IConfigurableScheduledTask configurableTask)
                     {
                     {
                         isEnabled = configurableTask.IsEnabled;
                         isEnabled = configurableTask.IsEnabled;
                     }
                     }

+ 40 - 58
MediaBrowser.Api/SearchService.cs

@@ -234,59 +234,48 @@ namespace MediaBrowser.Api
             SetThumbImageInfo(result, item);
             SetThumbImageInfo(result, item);
             SetBackdropImageInfo(result, item);
             SetBackdropImageInfo(result, item);
 
 
-            var program = item as LiveTvProgram;
-            if (program != null)
+            switch (item)
             {
             {
-                result.StartDate = program.StartDate;
-            }
-
-            var hasSeries = item as IHasSeries;
-            if (hasSeries != null)
-            {
-                result.Series = hasSeries.SeriesName;
-            }
-
-            var series = item as Series;
-            if (series != null)
-            {
-                if (series.Status.HasValue)
-                {
-                    result.Status = series.Status.Value.ToString();
-                }
-            }
-
-            var album = item as MusicAlbum;
-
-            if (album != null)
-            {
-                result.Artists = album.Artists;
-                result.AlbumArtist = album.AlbumArtist;
-            }
-
-            var song = item as Audio;
-
-            if (song != null)
-            {
-                result.AlbumArtist = song.AlbumArtists.FirstOrDefault();
-                result.Artists = song.Artists;
-
-                album = song.AlbumEntity;
-
-                if (album != null)
-                {
-                    result.Album = album.Name;
-                    result.AlbumId = album.Id;
-                }
-                else
-                {
-                    result.Album = song.Album;
-                }
+                case IHasSeries hasSeries:
+                    result.Series = hasSeries.SeriesName;
+                    break;
+                case LiveTvProgram program:
+                    result.StartDate = program.StartDate;
+                    break;
+                case Series series:
+                    if (series.Status.HasValue)
+                    {
+                        result.Status = series.Status.Value.ToString();
+                    }
+
+                    break;
+                case MusicAlbum album:
+                    result.Artists = album.Artists;
+                    result.AlbumArtist = album.AlbumArtist;
+                    break;
+                case Audio song:
+                    result.AlbumArtist = song.AlbumArtists.FirstOrDefault();
+                    result.Artists = song.Artists;
+
+                    MusicAlbum musicAlbum = song.AlbumEntity;
+
+                    if (musicAlbum != null)
+                    {
+                        result.Album = musicAlbum.Name;
+                        result.AlbumId = musicAlbum.Id;
+                    }
+                    else
+                    {
+                        result.Album = song.Album;
+                    }
+
+                    break;
             }
             }
 
 
             if (!item.ChannelId.Equals(Guid.Empty))
             if (!item.ChannelId.Equals(Guid.Empty))
             {
             {
                 var channel = _libraryManager.GetItemById(item.ChannelId);
                 var channel = _libraryManager.GetItemById(item.ChannelId);
-                result.ChannelName = channel == null ? null : channel.Name;
+                result.ChannelName = channel?.Name;
             }
             }
 
 
             return result;
             return result;
@@ -296,12 +285,9 @@ namespace MediaBrowser.Api
         {
         {
             var itemWithImage = item.HasImage(ImageType.Thumb) ? item : null;
             var itemWithImage = item.HasImage(ImageType.Thumb) ? item : null;
 
 
-            if (itemWithImage == null)
+            if (itemWithImage == null && item is Episode)
             {
             {
-                if (item is Episode)
-                {
-                    itemWithImage = GetParentWithImage<Series>(item, ImageType.Thumb);
-                }
+                itemWithImage = GetParentWithImage<Series>(item, ImageType.Thumb);
             }
             }
 
 
             if (itemWithImage == null)
             if (itemWithImage == null)
@@ -323,12 +309,8 @@ namespace MediaBrowser.Api
 
 
         private void SetBackdropImageInfo(SearchHint hint, BaseItem item)
         private void SetBackdropImageInfo(SearchHint hint, BaseItem item)
         {
         {
-            var itemWithImage = item.HasImage(ImageType.Backdrop) ? item : null;
-
-            if (itemWithImage == null)
-            {
-                itemWithImage = GetParentWithImage<BaseItem>(item, ImageType.Backdrop);
-            }
+            var itemWithImage = (item.HasImage(ImageType.Backdrop) ? item : null)
+                ?? GetParentWithImage<BaseItem>(item, ImageType.Backdrop);
 
 
             if (itemWithImage != null)
             if (itemWithImage != null)
             {
             {

+ 6 - 9
MediaBrowser.Api/Subtitles/SubtitleService.cs

@@ -230,17 +230,14 @@ namespace MediaBrowser.Api.Subtitles
 
 
             if (string.Equals(request.Format, "vtt", StringComparison.OrdinalIgnoreCase) && request.AddVttTimeMap)
             if (string.Equals(request.Format, "vtt", StringComparison.OrdinalIgnoreCase) && request.AddVttTimeMap)
             {
             {
-                using (var stream = await GetSubtitles(request).ConfigureAwait(false))
-                {
-                    using (var reader = new StreamReader(stream))
-                    {
-                        var text = reader.ReadToEnd();
+                using var stream = await GetSubtitles(request).ConfigureAwait(false);
+                using var reader = new StreamReader(stream);
 
 
-                        text = text.Replace("WEBVTT", "WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000");
+                var text = reader.ReadToEnd();
 
 
-                        return ResultFactory.GetResult(Request, text, MimeTypes.GetMimeType("file." + request.Format));
-                    }
-                }
+                text = text.Replace("WEBVTT", "WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000");
+
+                return ResultFactory.GetResult(Request, text, MimeTypes.GetMimeType("file." + request.Format));
             }
             }
 
 
             return ResultFactory.GetResult(Request, await GetSubtitles(request).ConfigureAwait(false), MimeTypes.GetMimeType("file." + request.Format));
             return ResultFactory.GetResult(Request, await GetSubtitles(request).ConfigureAwait(false), MimeTypes.GetMimeType("file." + request.Format));

+ 2 - 5
MediaBrowser.Api/System/SystemService.cs

@@ -168,12 +168,9 @@ namespace MediaBrowser.Api.System
                 .First(i => string.Equals(i.Name, request.Name, StringComparison.OrdinalIgnoreCase));
                 .First(i => string.Equals(i.Name, request.Name, StringComparison.OrdinalIgnoreCase));
 
 
             // For older files, assume fully static
             // For older files, assume fully static
-            if (file.LastWriteTimeUtc < DateTime.UtcNow.AddHours(-1))
-            {
-                return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShare.Read);
-            }
+            var fileShare = file.LastWriteTimeUtc < DateTime.UtcNow.AddHours(-1) ? FileShare.Read : FileShare.ReadWrite;
 
 
-            return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShare.ReadWrite);
+            return ResultFactory.GetStaticFileResult(Request, file.FullName, fileShare);
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 1 - 4
MediaBrowser.Api/TranscodingJob.cs

@@ -92,10 +92,7 @@ namespace MediaBrowser.Api
         {
         {
             lock (_timerLock)
             lock (_timerLock)
             {
             {
-                if (KillTimer != null)
-                {
-                    KillTimer.Change(Timeout.Infinite, Timeout.Infinite);
-                }
+                KillTimer?.Change(Timeout.Infinite, Timeout.Infinite);
             }
             }
         }
         }
 
 

+ 2 - 11
MediaBrowser.Api/TvShowsService.cs

@@ -424,9 +424,7 @@ namespace MediaBrowser.Api
 
 
             if (!string.IsNullOrWhiteSpace(request.SeasonId))
             if (!string.IsNullOrWhiteSpace(request.SeasonId))
             {
             {
-                var season = _libraryManager.GetItemById(new Guid(request.SeasonId)) as Season;
-
-                if (season == null)
+                if (!(_libraryManager.GetItemById(new Guid(request.SeasonId)) is Season season))
                 {
                 {
                     throw new ResourceNotFoundException("No season exists with Id " + request.SeasonId);
                     throw new ResourceNotFoundException("No season exists with Id " + request.SeasonId);
                 }
                 }
@@ -444,14 +442,7 @@ namespace MediaBrowser.Api
 
 
                 var season = series.GetSeasons(user, dtoOptions).FirstOrDefault(i => i.IndexNumber == request.Season.Value);
                 var season = series.GetSeasons(user, dtoOptions).FirstOrDefault(i => i.IndexNumber == request.Season.Value);
 
 
-                if (season == null)
-                {
-                    episodes = new List<BaseItem>();
-                }
-                else
-                {
-                    episodes = ((Season)season).GetEpisodes(user, dtoOptions);
-                }
+                episodes = season == null ? new List<BaseItem>() : ((Season)season).GetEpisodes(user, dtoOptions);
             }
             }
             else
             else
             {
             {

+ 1 - 6
MediaBrowser.Api/UserLibrary/ArtistsService.cs

@@ -126,12 +126,7 @@ namespace MediaBrowser.Api.UserLibrary
 
 
         protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
         protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
         {
         {
-            if (request is GetAlbumArtists)
-            {
-                return LibraryManager.GetAlbumArtists(query);
-            }
-
-            return LibraryManager.GetArtists(query);
+            return request is GetAlbumArtists ? LibraryManager.GetAlbumArtists(query) : LibraryManager.GetArtists(query);
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 12 - 22
MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs

@@ -82,8 +82,7 @@ namespace MediaBrowser.Api.UserLibrary
         {
         {
             var parent = GetParentItem(request);
             var parent = GetParentItem(request);
 
 
-            var collectionFolder = parent as IHasCollectionType;
-            if (collectionFolder != null)
+            if (parent is IHasCollectionType collectionFolder)
             {
             {
                 return collectionFolder.CollectionType;
                 return collectionFolder.CollectionType;
             }
             }
@@ -274,7 +273,7 @@ namespace MediaBrowser.Api.UserLibrary
                 DtoOptions = dtoOptions
                 DtoOptions = dtoOptions
             };
             };
 
 
-            Func<BaseItem, bool> filter = i => FilterItem(request, i, excludeItemTypes, includeItemTypes, mediaTypes);
+            bool Filter(BaseItem i) => FilterItem(request, i, excludeItemTypes, includeItemTypes, mediaTypes);
 
 
             if (parentItem.IsFolder)
             if (parentItem.IsFolder)
             {
             {
@@ -284,18 +283,18 @@ namespace MediaBrowser.Api.UserLibrary
                 {
                 {
                     items = request.Recursive ?
                     items = request.Recursive ?
                         folder.GetRecursiveChildren(user, query).ToList() :
                         folder.GetRecursiveChildren(user, query).ToList() :
-                        folder.GetChildren(user, true).Where(filter).ToList();
+                        folder.GetChildren(user, true).Where(Filter).ToList();
                 }
                 }
                 else
                 else
                 {
                 {
                     items = request.Recursive ?
                     items = request.Recursive ?
-                        folder.GetRecursiveChildren(filter) :
-                        folder.Children.Where(filter).ToList();
+                        folder.GetRecursiveChildren(Filter) :
+                        folder.Children.Where(Filter).ToList();
                 }
                 }
             }
             }
             else
             else
             {
             {
-                items = new[] { parentItem }.Where(filter).ToList();
+                items = new[] { parentItem }.Where(Filter).ToList();
             }
             }
 
 
             var extractedItems = GetAllItems(request, items);
             var extractedItems = GetAllItems(request, items);
@@ -346,30 +345,21 @@ namespace MediaBrowser.Api.UserLibrary
         private bool FilterItem(GetItemsByName request, BaseItem f, string[] excludeItemTypes, string[] includeItemTypes, string[] mediaTypes)
         private bool FilterItem(GetItemsByName request, BaseItem f, string[] excludeItemTypes, string[] includeItemTypes, string[] mediaTypes)
         {
         {
             // Exclude item types
             // Exclude item types
-            if (excludeItemTypes.Length > 0)
+            if (excludeItemTypes.Length > 0 && excludeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
             {
             {
-                if (excludeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
-                {
-                    return false;
-                }
+                return false;
             }
             }
 
 
             // Include item types
             // Include item types
-            if (includeItemTypes.Length > 0)
+            if (includeItemTypes.Length > 0 && !includeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
             {
             {
-                if (!includeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
-                {
-                    return false;
-                }
+                return false;
             }
             }
 
 
             // Include MediaTypes
             // Include MediaTypes
-            if (mediaTypes.Length > 0)
+            if (mediaTypes.Length > 0 && !mediaTypes.Contains(f.MediaType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
             {
             {
-                if (!mediaTypes.Contains(f.MediaType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
-                {
-                    return false;
-                }
+                return false;
             }
             }
 
 
             return true;
             return true;

+ 14 - 19
MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs

@@ -396,12 +396,10 @@ namespace MediaBrowser.Api.UserLibrary
 
 
         public VideoType[] GetVideoTypes()
         public VideoType[] GetVideoTypes()
         {
         {
-            if (string.IsNullOrEmpty(VideoTypes))
-            {
-                return Array.Empty<VideoType>();
-            }
-
-            return VideoTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (VideoType)Enum.Parse(typeof(VideoType), v, true)).ToArray();
+            return string.IsNullOrEmpty(VideoTypes)
+                ? Array.Empty<VideoType>()
+                : VideoTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
+                    .Select(v => Enum.Parse<VideoType>(v, true)).ToArray();
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -412,12 +410,10 @@ namespace MediaBrowser.Api.UserLibrary
         {
         {
             var val = Filters;
             var val = Filters;
 
 
-            if (string.IsNullOrEmpty(val))
-            {
-                return new ItemFilter[] { };
-            }
-
-            return val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (ItemFilter)Enum.Parse(typeof(ItemFilter), v, true)).ToArray();
+            return string.IsNullOrEmpty(val)
+                ? Array.Empty<ItemFilter>()
+                : val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
+                    .Select(v => Enum.Parse<ItemFilter>(v, true)).ToArray();
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -428,12 +424,9 @@ namespace MediaBrowser.Api.UserLibrary
         {
         {
             var val = ImageTypes;
             var val = ImageTypes;
 
 
-            if (string.IsNullOrEmpty(val))
-            {
-                return new ImageType[] { };
-            }
-
-            return val.Split(',').Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)).ToArray();
+            return string.IsNullOrEmpty(val)
+                ? Array.Empty<ImageType>()
+                : val.Split(',').Select(v => Enum.Parse<ImageType>(v, true)).ToArray();
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -469,7 +462,9 @@ namespace MediaBrowser.Api.UserLibrary
                 var sortOrderIndex = sortOrders.Length > i ? i : 0;
                 var sortOrderIndex = sortOrders.Length > i ? i : 0;
 
 
                 var sortOrderValue = sortOrders.Length > sortOrderIndex ? sortOrders[sortOrderIndex] : null;
                 var sortOrderValue = sortOrders.Length > sortOrderIndex ? sortOrders[sortOrderIndex] : null;
-                var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase) ? MediaBrowser.Model.Entities.SortOrder.Descending : MediaBrowser.Model.Entities.SortOrder.Ascending;
+                var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase)
+                    ? MediaBrowser.Model.Entities.SortOrder.Descending
+                    : MediaBrowser.Model.Entities.SortOrder.Ascending;
 
 
                 result[i] = new ValueTuple<string, SortOrder>(vals[i], sortOrder);
                 result[i] = new ValueTuple<string, SortOrder>(vals[i], sortOrder);
             }
             }

+ 2 - 4
MediaBrowser.Api/UserLibrary/ItemsService.cs

@@ -199,14 +199,12 @@ namespace MediaBrowser.Api.UserLibrary
                 item = _libraryManager.GetUserRootFolder();
                 item = _libraryManager.GetUserRootFolder();
             }
             }
 
 
-            Folder folder = item as Folder;
-            if (folder == null)
+            if (!(item is Folder folder))
             {
             {
                 folder = _libraryManager.GetUserRootFolder();
                 folder = _libraryManager.GetUserRootFolder();
             }
             }
 
 
-            var hasCollectionType = folder as IHasCollectionType;
-            if (hasCollectionType != null
+            if (folder is IHasCollectionType hasCollectionType
                 && string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase))
                 && string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase))
             {
             {
                 request.Recursive = true;
                 request.Recursive = true;

+ 2 - 11
MediaBrowser.Api/VideosService.cs

@@ -139,17 +139,11 @@ namespace MediaBrowser.Api
                 .ToList();
                 .ToList();
 
 
             var primaryVersion = videosWithVersions.FirstOrDefault();
             var primaryVersion = videosWithVersions.FirstOrDefault();
-
             if (primaryVersion == null)
             if (primaryVersion == null)
             {
             {
                 primaryVersion = items.OrderBy(i =>
                 primaryVersion = items.OrderBy(i =>
                     {
                     {
-                        if (i.Video3DFormat.HasValue)
-                        {
-                            return 1;
-                        }
-
-                        if (i.VideoType != Model.Entities.VideoType.VideoFile)
+                        if (i.Video3DFormat.HasValue || i.VideoType != Model.Entities.VideoType.VideoFile)
                         {
                         {
                             return 1;
                             return 1;
                         }
                         }
@@ -158,10 +152,7 @@ namespace MediaBrowser.Api
                     })
                     })
                     .ThenByDescending(i =>
                     .ThenByDescending(i =>
                     {
                     {
-                        var stream = i.GetDefaultVideoStream();
-
-                        return stream == null || stream.Width == null ? 0 : stream.Width.Value;
-
+                        return i.GetDefaultVideoStream()?.Width ?? 0;
                     }).First();
                     }).First();
             }
             }
 
 

+ 80 - 0
MediaBrowser.Common/Extensions/ProcessExtensions.cs

@@ -0,0 +1,80 @@
+using System;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Common.Extensions
+{
+    /// <summary>
+    /// Extension methods for <see cref="Process"/>.
+    /// </summary>
+    public static class ProcessExtensions
+    {
+        /// <summary>
+        /// Asynchronously wait for the process to exit.
+        /// </summary>
+        /// <param name="process">The process to wait for.</param>
+        /// <param name="timeout">The duration to wait before cancelling waiting for the task.</param>
+        /// <returns>True if the task exited normally, false if the timeout elapsed before the process exited.</returns>
+        /// <exception cref="InvalidOperationException">If <see cref="Process.EnableRaisingEvents"/> is not set to true for the process.</exception>
+        public static async Task<bool> WaitForExitAsync(this Process process, TimeSpan timeout)
+        {
+            using (var cancelTokenSource = new CancellationTokenSource(timeout))
+            {
+                return await WaitForExitAsync(process, cancelTokenSource.Token).ConfigureAwait(false);
+            }
+        }
+
+        /// <summary>
+        /// Asynchronously wait for the process to exit.
+        /// </summary>
+        /// <param name="process">The process to wait for.</param>
+        /// <param name="cancelToken">A <see cref="CancellationToken"/> to observe while waiting for the process to exit.</param>
+        /// <returns>True if the task exited normally, false if cancelled before the process exited.</returns>
+        public static async Task<bool> WaitForExitAsync(this Process process, CancellationToken cancelToken)
+        {
+            if (!process.EnableRaisingEvents)
+            {
+                throw new InvalidOperationException("EnableRisingEvents must be enabled to async wait for a task to exit.");
+            }
+
+            // Add an event handler for the process exit event
+            var tcs = new TaskCompletionSource<bool>();
+            process.Exited += (sender, args) => tcs.TrySetResult(true);
+
+            // Return immediately if the process has already exited
+            if (process.HasExitedSafe())
+            {
+                return true;
+            }
+
+            // Register with the cancellation token then await
+            using (var cancelRegistration = cancelToken.Register(() => tcs.TrySetResult(process.HasExitedSafe())))
+            {
+                return await tcs.Task.ConfigureAwait(false);
+            }
+        }
+
+        /// <summary>
+        /// Gets a value indicating whether the associated process has been terminated using
+        /// <see cref="Process.HasExited"/>. This is safe to call even if there is no operating system process
+        /// associated with the <see cref="Process"/>.
+        /// </summary>
+        /// <param name="process">The process to check the exit status for.</param>
+        /// <returns>
+        /// True if the operating system process referenced by the <see cref="Process"/> component has
+        /// terminated, or if there is no associated operating system process; otherwise, false.
+        /// </returns>
+        private static bool HasExitedSafe(this Process process)
+        {
+            try
+            {
+                return process.HasExited;
+            }
+            catch (InvalidOperationException)
+            {
+                return true;
+            }
+        }
+    }
+}

+ 30 - 32
MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs

@@ -78,8 +78,7 @@ namespace MediaBrowser.Controller.MediaEncoding
 
 
                 if (!string.IsNullOrEmpty(hwType)
                 if (!string.IsNullOrEmpty(hwType)
                     && encodingOptions.EnableHardwareEncoding
                     && encodingOptions.EnableHardwareEncoding
-                    && codecMap.ContainsKey(hwType)
-                    && CheckVaapi(state, hwType, encodingOptions))
+                    && codecMap.ContainsKey(hwType))
                 {
                 {
                     var preferredEncoder = codecMap[hwType];
                     var preferredEncoder = codecMap[hwType];
 
 
@@ -93,23 +92,6 @@ namespace MediaBrowser.Controller.MediaEncoding
             return defaultEncoder;
             return defaultEncoder;
         }
         }
 
 
-        private bool CheckVaapi(EncodingJobInfo state, string hwType, EncodingOptions encodingOptions)
-        {
-            if (!string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase))
-            {
-                // No vaapi requested, return OK.
-                return true;
-            }
-
-            if (string.IsNullOrEmpty(encodingOptions.VaapiDevice))
-            {
-                // No device specified, return OK.
-                return true;
-            }
-
-            return IsVaapiSupported(state);
-        }
-
         private bool IsVaapiSupported(EncodingJobInfo state)
         private bool IsVaapiSupported(EncodingJobInfo state)
         {
         {
             var videoStream = state.VideoStream;
             var videoStream = state.VideoStream;
@@ -424,7 +406,13 @@ namespace MediaBrowser.Controller.MediaEncoding
 
 
             if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
             if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
             {
             {
-                return "aac -strict experimental";
+                // Use libfdk_aac for better audio quality if using custom build of FFmpeg which has fdk_aac support
+                if (_mediaEncoder.SupportsEncoder("libfdk_aac"))
+                {
+                    return "libfdk_aac";
+                }
+
+                return "aac";
             }
             }
 
 
             if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase))
             if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase))
@@ -1605,7 +1593,7 @@ namespace MediaBrowser.Controller.MediaEncoding
                 // For VAAPI and CUVID decoder
                 // For VAAPI and CUVID decoder
                 // these encoders cannot automatically adjust the size of graphical subtitles to fit the output video,
                 // these encoders cannot automatically adjust the size of graphical subtitles to fit the output video,
                 // thus needs to be manually adjusted.
                 // thus needs to be manually adjusted.
-                if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
+                if ((IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
                     || (videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1)
                     || (videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1)
                 {
                 {
                     var videoStream = state.VideoStream;
                     var videoStream = state.VideoStream;
@@ -1648,7 +1636,7 @@ namespace MediaBrowser.Controller.MediaEncoding
             }
             }
 
 
             // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
             // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
-            else if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
+            else if (IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
                 && string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
                 && string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
             {
             {
                 /*
                 /*
@@ -2014,19 +2002,29 @@ namespace MediaBrowser.Controller.MediaEncoding
             }
             }
 
 
             // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
             // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
-            else if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
+            else if (IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
                 && string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
                 && string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
             {
             {
                 var codec = videoStream.Codec.ToLowerInvariant();
                 var codec = videoStream.Codec.ToLowerInvariant();
-                var pixelFormat = videoStream.PixelFormat.ToLowerInvariant();
-
-                // Assert hardware VAAPI decodable (Except h264 10-bit and higher color depth)
-				// TODO: a propery way to detect hardware capabilities and falling back when transcoding is failed
-                if ((pixelFormat ?? string.Empty).IndexOf("p10", StringComparison.OrdinalIgnoreCase) == -1
-                    || ((pixelFormat ?? string.Empty).IndexOf("p10", StringComparison.OrdinalIgnoreCase) != -1
-                        && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
-                            || string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
-                            || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))))
+                var isColorDepth10 = !string.IsNullOrEmpty(videoStream.Profile) && (videoStream.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase)
+                    || videoStream.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase));
+
+                // Assert 10-bit hardware VAAPI decodable
+                if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
+                    || string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
+                    || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase)))
+                {
+                    /*
+                        Download data from GPU to CPU as p010le format.
+                        Colorspace conversion is unnecessary here as libx264 will handle it.
+                        If this step is missing, it will fail on AMD but not on intel.
+                    */
+                    filters.Add("hwdownload");
+                    filters.Add("format=p010le");
+                }
+
+                // Assert 8-bit hardware VAAPI decodable
+                else if (!isColorDepth10)
                 {
                 {
                     filters.Add("hwdownload");
                     filters.Add("hwdownload");
                     filters.Add("format=nv12");
                     filters.Add("format=nv12");

+ 31 - 34
MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs

@@ -155,47 +155,44 @@ namespace MediaBrowser.MediaEncoding.Attachments
                 inputPath,
                 inputPath,
                 attachmentStreamIndex,
                 attachmentStreamIndex,
                 outputPath);
                 outputPath);
-            var startInfo = new ProcessStartInfo
-            {
-                Arguments = processArgs,
-                FileName = _mediaEncoder.EncoderPath,
-                UseShellExecute = false,
-                CreateNoWindow = true,
-                WindowStyle = ProcessWindowStyle.Hidden,
-                ErrorDialog = false
-            };
-            var process = new Process
-            {
-                StartInfo = startInfo
-            };
 
 
-            _logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
+            int exitCode;
 
 
-            process.Start();
+            using (var process = new Process
+                {
+                    StartInfo = new ProcessStartInfo
+                    {
+                        Arguments = processArgs,
+                        FileName = _mediaEncoder.EncoderPath,
+                        UseShellExecute = false,
+                        CreateNoWindow = true,
+                        WindowStyle = ProcessWindowStyle.Hidden,
+                        ErrorDialog = false
+                    },
+                    EnableRaisingEvents = true
+                })
+            {
+                _logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
 
 
-            var processTcs = new TaskCompletionSource<bool>();
-            process.EnableRaisingEvents = true;
-            process.Exited += (sender, args) => processTcs.TrySetResult(true);
-            var unregister = cancellationToken.Register(() => processTcs.TrySetResult(process.HasExited));
-            var ranToCompletion = await processTcs.Task.ConfigureAwait(false);
-            unregister.Dispose();
+                process.Start();
 
 
-            if (!ranToCompletion)
-            {
-                try
-                {
-                    _logger.LogWarning("Killing ffmpeg attachment extraction process");
-                    process.Kill();
-                }
-                catch (Exception ex)
+                var ranToCompletion = await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
+
+                if (!ranToCompletion)
                 {
                 {
-                    _logger.LogError(ex, "Error killing attachment extraction process");
+                    try
+                    {
+                        _logger.LogWarning("Killing ffmpeg attachment extraction process");
+                        process.Kill();
+                    }
+                    catch (Exception ex)
+                    {
+                        _logger.LogError(ex, "Error killing attachment extraction process");
+                    }
                 }
                 }
-            }
 
 
-            var exitCode = ranToCompletion ? process.ExitCode : -1;
-
-            process.Dispose();
+                exitCode = ranToCompletion ? process.ExitCode : -1;
+            }
 
 
             var failed = false;
             var failed = false;
 
 

+ 1 - 0
MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs

@@ -42,6 +42,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             "libvpx",
             "libvpx",
             "libvpx-vp9",
             "libvpx-vp9",
             "aac",
             "aac",
+            "libfdk_aac",
             "libmp3lame",
             "libmp3lame",
             "libopus",
             "libopus",
             "libvorbis",
             "libvorbis",

+ 44 - 37
MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs

@@ -13,7 +13,6 @@ using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.MediaEncoding.Probing;
 using MediaBrowser.MediaEncoding.Probing;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Diagnostics;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Globalization;
@@ -22,6 +21,7 @@ using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.System;
 using MediaBrowser.Model.System;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
+using System.Diagnostics;
 
 
 namespace MediaBrowser.MediaEncoding.Encoder
 namespace MediaBrowser.MediaEncoding.Encoder
 {
 {
@@ -38,7 +38,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
         private readonly ILogger _logger;
         private readonly ILogger _logger;
         private readonly IServerConfigurationManager _configurationManager;
         private readonly IServerConfigurationManager _configurationManager;
         private readonly IFileSystem _fileSystem;
         private readonly IFileSystem _fileSystem;
-        private readonly IProcessFactory _processFactory;
         private readonly ILocalizationManager _localization;
         private readonly ILocalizationManager _localization;
         private readonly Lazy<EncodingHelper> _encodingHelperFactory;
         private readonly Lazy<EncodingHelper> _encodingHelperFactory;
         private readonly string _startupOptionFFmpegPath;
         private readonly string _startupOptionFFmpegPath;
@@ -55,7 +54,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
             ILogger<MediaEncoder> logger,
             ILogger<MediaEncoder> logger,
             IServerConfigurationManager configurationManager,
             IServerConfigurationManager configurationManager,
             IFileSystem fileSystem,
             IFileSystem fileSystem,
-            IProcessFactory processFactory,
             ILocalizationManager localization,
             ILocalizationManager localization,
             Lazy<EncodingHelper> encodingHelperFactory,
             Lazy<EncodingHelper> encodingHelperFactory,
             string startupOptionsFFmpegPath)
             string startupOptionsFFmpegPath)
@@ -63,7 +61,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
             _logger = logger;
             _logger = logger;
             _configurationManager = configurationManager;
             _configurationManager = configurationManager;
             _fileSystem = fileSystem;
             _fileSystem = fileSystem;
-            _processFactory = processFactory;
             _localization = localization;
             _localization = localization;
             _encodingHelperFactory = encodingHelperFactory;
             _encodingHelperFactory = encodingHelperFactory;
             _startupOptionFFmpegPath = startupOptionsFFmpegPath;
             _startupOptionFFmpegPath = startupOptionsFFmpegPath;
@@ -354,30 +351,33 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 : "{0} -i {1} -threads 0 -v warning -print_format json -show_streams -show_format";
                 : "{0} -i {1} -threads 0 -v warning -print_format json -show_streams -show_format";
             args = string.Format(args, probeSizeArgument, inputPath).Trim();
             args = string.Format(args, probeSizeArgument, inputPath).Trim();
 
 
-            var process = _processFactory.Create(new ProcessOptions
+            var process = new Process
             {
             {
-                CreateNoWindow = true,
-                UseShellExecute = false,
+                StartInfo = new ProcessStartInfo
+                {
+                    CreateNoWindow = true,
+                    UseShellExecute = false,
 
 
-                // Must consume both or ffmpeg may hang due to deadlocks. See comments below.
-                RedirectStandardOutput = true,
+                    // Must consume both or ffmpeg may hang due to deadlocks. See comments below.
+                    RedirectStandardOutput = true,
 
 
-                FileName = _ffprobePath,
-                Arguments = args,
+                    FileName = _ffprobePath,
+                    Arguments = args,
 
 
 
 
-                IsHidden = true,
-                ErrorDialog = false,
+                    WindowStyle = ProcessWindowStyle.Hidden,
+                    ErrorDialog = false,
+                },
                 EnableRaisingEvents = true
                 EnableRaisingEvents = true
-            });
+            };
 
 
             if (forceEnableLogging)
             if (forceEnableLogging)
             {
             {
-                _logger.LogInformation("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
+                _logger.LogInformation("{ProcessFileName} {ProcessArgs}", process.StartInfo.FileName, process.StartInfo.Arguments);
             }
             }
             else
             else
             {
             {
-                _logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
+                _logger.LogDebug("{ProcessFileName} {ProcessArgs}", process.StartInfo.FileName, process.StartInfo.Arguments);
             }
             }
 
 
             using (var processWrapper = new ProcessWrapper(process, this))
             using (var processWrapper = new ProcessWrapper(process, this))
@@ -563,18 +563,21 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 }
                 }
             }
             }
 
 
-            var process = _processFactory.Create(new ProcessOptions
+            var process = new Process
             {
             {
-                CreateNoWindow = true,
-                UseShellExecute = false,
-                FileName = _ffmpegPath,
-                Arguments = args,
-                IsHidden = true,
-                ErrorDialog = false,
+                StartInfo = new ProcessStartInfo
+                {
+                    CreateNoWindow = true,
+                    UseShellExecute = false,
+                    FileName = _ffmpegPath,
+                    Arguments = args,
+                    WindowStyle = ProcessWindowStyle.Hidden,
+                    ErrorDialog = false,
+                },
                 EnableRaisingEvents = true
                 EnableRaisingEvents = true
-            });
+            };
 
 
-            _logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
+            _logger.LogDebug("{ProcessFileName} {ProcessArguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
 
 
             using (var processWrapper = new ProcessWrapper(process, this))
             using (var processWrapper = new ProcessWrapper(process, this))
             {
             {
@@ -591,7 +594,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
                         timeoutMs = DefaultImageExtractionTimeout;
                         timeoutMs = DefaultImageExtractionTimeout;
                     }
                     }
 
 
-                    ranToCompletion = await process.WaitForExitAsync(timeoutMs).ConfigureAwait(false);
+                    ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMilliseconds(timeoutMs)).ConfigureAwait(false);
 
 
                     if (!ranToCompletion)
                     if (!ranToCompletion)
                     {
                     {
@@ -692,23 +695,27 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 }
                 }
             }
             }
 
 
-            var process = _processFactory.Create(new ProcessOptions
+            var processStartInfo = new ProcessStartInfo
             {
             {
                 CreateNoWindow = true,
                 CreateNoWindow = true,
                 UseShellExecute = false,
                 UseShellExecute = false,
                 FileName = _ffmpegPath,
                 FileName = _ffmpegPath,
                 Arguments = args,
                 Arguments = args,
-                IsHidden = true,
-                ErrorDialog = false,
-                EnableRaisingEvents = true
-            });
+                WindowStyle = ProcessWindowStyle.Hidden,
+                ErrorDialog = false
+            };
 
 
-            _logger.LogInformation(process.StartInfo.FileName + " " + process.StartInfo.Arguments);
+            _logger.LogInformation(processStartInfo.FileName + " " + processStartInfo.Arguments);
 
 
             await _thumbnailResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
             await _thumbnailResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
 
 
             bool ranToCompletion = false;
             bool ranToCompletion = false;
 
 
+            var process = new Process
+            {
+                StartInfo = processStartInfo,
+                EnableRaisingEvents = true
+            };
             using (var processWrapper = new ProcessWrapper(process, this))
             using (var processWrapper = new ProcessWrapper(process, this))
             {
             {
                 try
                 try
@@ -724,7 +731,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
 
                     while (isResponsive)
                     while (isResponsive)
                     {
                     {
-                        if (await process.WaitForExitAsync(30000).ConfigureAwait(false))
+                        if (await process.WaitForExitAsync(TimeSpan.FromSeconds(30)).ConfigureAwait(false))
                         {
                         {
                             ranToCompletion = true;
                             ranToCompletion = true;
                             break;
                             break;
@@ -941,14 +948,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
 
             private bool _disposed = false;
             private bool _disposed = false;
 
 
-            public ProcessWrapper(IProcess process, MediaEncoder mediaEncoder)
+            public ProcessWrapper(Process process, MediaEncoder mediaEncoder)
             {
             {
                 Process = process;
                 Process = process;
                 _mediaEncoder = mediaEncoder;
                 _mediaEncoder = mediaEncoder;
                 Process.Exited += OnProcessExited;
                 Process.Exited += OnProcessExited;
             }
             }
 
 
-            public IProcess Process { get; }
+            public Process Process { get; }
 
 
             public bool HasExited { get; private set; }
             public bool HasExited { get; private set; }
 
 
@@ -956,7 +963,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
 
             void OnProcessExited(object sender, EventArgs e)
             void OnProcessExited(object sender, EventArgs e)
             {
             {
-                var process = (IProcess)sender;
+                var process = (Process)sender;
 
 
                 HasExited = true;
                 HasExited = true;
 
 
@@ -971,7 +978,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 DisposeProcess(process);
                 DisposeProcess(process);
             }
             }
 
 
-            private void DisposeProcess(IProcess process)
+            private void DisposeProcess(Process process)
             {
             {
                 lock (_mediaEncoder._runningProcessesLock)
                 lock (_mediaEncoder._runningProcessesLock)
                 {
                 {

+ 76 - 71
MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Concurrent;
+using System.Diagnostics;
 using System.Globalization;
 using System.Globalization;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
@@ -12,7 +13,6 @@ using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.Diagnostics;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
@@ -31,7 +31,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
         private readonly IMediaEncoder _mediaEncoder;
         private readonly IMediaEncoder _mediaEncoder;
         private readonly IHttpClient _httpClient;
         private readonly IHttpClient _httpClient;
         private readonly IMediaSourceManager _mediaSourceManager;
         private readonly IMediaSourceManager _mediaSourceManager;
-        private readonly IProcessFactory _processFactory;
 
 
         public SubtitleEncoder(
         public SubtitleEncoder(
             ILibraryManager libraryManager,
             ILibraryManager libraryManager,
@@ -40,8 +39,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
             IFileSystem fileSystem,
             IFileSystem fileSystem,
             IMediaEncoder mediaEncoder,
             IMediaEncoder mediaEncoder,
             IHttpClient httpClient,
             IHttpClient httpClient,
-            IMediaSourceManager mediaSourceManager,
-            IProcessFactory processFactory)
+            IMediaSourceManager mediaSourceManager)
         {
         {
             _libraryManager = libraryManager;
             _libraryManager = libraryManager;
             _logger = logger;
             _logger = logger;
@@ -50,7 +48,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
             _mediaEncoder = mediaEncoder;
             _mediaEncoder = mediaEncoder;
             _httpClient = httpClient;
             _httpClient = httpClient;
             _mediaSourceManager = mediaSourceManager;
             _mediaSourceManager = mediaSourceManager;
-            _processFactory = processFactory;
         }
         }
 
 
         private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles");
         private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles");
@@ -429,49 +426,53 @@ namespace MediaBrowser.MediaEncoding.Subtitles
                 encodingParam = " -sub_charenc " + encodingParam;
                 encodingParam = " -sub_charenc " + encodingParam;
             }
             }
 
 
-            var process = _processFactory.Create(new ProcessOptions
-            {
-                CreateNoWindow = true,
-                UseShellExecute = false,
-                FileName = _mediaEncoder.EncoderPath,
-                Arguments = string.Format("{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
-                EnableRaisingEvents = true,
-                IsHidden = true,
-                ErrorDialog = false
-            });
-
-            _logger.LogInformation("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
-
-            try
-            {
-                process.Start();
-            }
-            catch (Exception ex)
-            {
-                _logger.LogError(ex, "Error starting ffmpeg");
+            int exitCode;
 
 
-                throw;
-            }
-
-            var ranToCompletion = await process.WaitForExitAsync(300000).ConfigureAwait(false);
+            using (var process = new Process
+                {
+                    StartInfo = new ProcessStartInfo
+                    {
+                        CreateNoWindow = true,
+                        UseShellExecute = false,
+                        FileName = _mediaEncoder.EncoderPath,
+                        Arguments = string.Format("{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
+                        WindowStyle = ProcessWindowStyle.Hidden,
+                        ErrorDialog = false
+                    },
+                    EnableRaisingEvents = true
+                })
+            {
+                _logger.LogInformation("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
 
 
-            if (!ranToCompletion)
-            {
                 try
                 try
                 {
                 {
-                    _logger.LogInformation("Killing ffmpeg subtitle conversion process");
-
-                    process.Kill();
+                    process.Start();
                 }
                 }
                 catch (Exception ex)
                 catch (Exception ex)
                 {
                 {
-                    _logger.LogError(ex, "Error killing subtitle conversion process");
+                    _logger.LogError(ex, "Error starting ffmpeg");
+
+                    throw;
                 }
                 }
-            }
 
 
-            var exitCode = ranToCompletion ? process.ExitCode : -1;
+                var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(5)).ConfigureAwait(false);
+
+                if (!ranToCompletion)
+                {
+                    try
+                    {
+                        _logger.LogInformation("Killing ffmpeg subtitle conversion process");
 
 
-            process.Dispose();
+                        process.Kill();
+                    }
+                    catch (Exception ex)
+                    {
+                        _logger.LogError(ex, "Error killing subtitle conversion process");
+                    }
+                }
+
+                exitCode = ranToCompletion ? process.ExitCode : -1;
+            }
 
 
             var failed = false;
             var failed = false;
 
 
@@ -578,49 +579,53 @@ namespace MediaBrowser.MediaEncoding.Subtitles
                 outputCodec,
                 outputCodec,
                 outputPath);
                 outputPath);
 
 
-            var process = _processFactory.Create(new ProcessOptions
-            {
-                CreateNoWindow = true,
-                UseShellExecute = false,
-                EnableRaisingEvents = true,
-                FileName = _mediaEncoder.EncoderPath,
-                Arguments = processArgs,
-                IsHidden = true,
-                ErrorDialog = false
-            });
-
-            _logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
-
-            try
-            {
-                process.Start();
-            }
-            catch (Exception ex)
-            {
-                _logger.LogError(ex, "Error starting ffmpeg");
+            int exitCode;
 
 
-                throw;
-            }
-
-            var ranToCompletion = await process.WaitForExitAsync(300000).ConfigureAwait(false);
+            using (var process = new Process
+                {
+                    StartInfo = new ProcessStartInfo
+                    {
+                        CreateNoWindow = true,
+                        UseShellExecute = false,
+                        FileName = _mediaEncoder.EncoderPath,
+                        Arguments = processArgs,
+                        WindowStyle = ProcessWindowStyle.Hidden,
+                        ErrorDialog = false
+                    },
+                    EnableRaisingEvents = true
+                })
+            {
+                _logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
 
 
-            if (!ranToCompletion)
-            {
                 try
                 try
                 {
                 {
-                    _logger.LogWarning("Killing ffmpeg subtitle extraction process");
-
-                    process.Kill();
+                    process.Start();
                 }
                 }
                 catch (Exception ex)
                 catch (Exception ex)
                 {
                 {
-                    _logger.LogError(ex, "Error killing subtitle extraction process");
+                    _logger.LogError(ex, "Error starting ffmpeg");
+
+                    throw;
                 }
                 }
-            }
 
 
-            var exitCode = ranToCompletion ? process.ExitCode : -1;
+                var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(5)).ConfigureAwait(false);
+
+                if (!ranToCompletion)
+                {
+                    try
+                    {
+                        _logger.LogWarning("Killing ffmpeg subtitle extraction process");
 
 
-            process.Dispose();
+                        process.Kill();
+                    }
+                    catch (Exception ex)
+                    {
+                        _logger.LogError(ex, "Error killing subtitle extraction process");
+                    }
+                }
+
+                exitCode = ranToCompletion ? process.ExitCode : -1;
+            }
 
 
             var failed = false;
             var failed = false;
 
 

+ 0 - 23
MediaBrowser.Model/Diagnostics/IProcess.cs

@@ -1,23 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.IO;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Model.Diagnostics
-{
-    public interface IProcess : IDisposable
-    {
-        event EventHandler Exited;
-
-        void Kill();
-        bool WaitForExit(int timeMs);
-        Task<bool> WaitForExitAsync(int timeMs);
-        int ExitCode { get; }
-        void Start();
-        StreamWriter StandardInput { get; }
-        StreamReader StandardError { get; }
-        StreamReader StandardOutput { get; }
-        ProcessOptions StartInfo { get; }
-    }
-}

+ 0 - 24
MediaBrowser.Model/Diagnostics/IProcessFactory.cs

@@ -1,24 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Model.Diagnostics
-{
-    public interface IProcessFactory
-    {
-        IProcess Create(ProcessOptions options);
-    }
-
-    public class ProcessOptions
-    {
-        public string FileName { get; set; }
-        public string Arguments { get; set; }
-        public string WorkingDirectory { get; set; }
-        public bool CreateNoWindow { get; set; }
-        public bool UseShellExecute { get; set; }
-        public bool EnableRaisingEvents { get; set; }
-        public bool ErrorDialog { get; set; }
-        public bool RedirectStandardError { get; set; }
-        public bool RedirectStandardInput { get; set; }
-        public bool RedirectStandardOutput { get; set; }
-        public bool IsHidden { get; set; }
-    }
-}

+ 6 - 0
nuget.config

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+    <packageSources>
+        <add key="NuGet official package source" value="https://api.nuget.org/v3/index.json" />
+    </packageSources>
+</configuration>

+ 45 - 21
tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs

@@ -7,6 +7,8 @@ namespace Jellyfin.Naming.Tests.Video
 {
 {
     public class ExtraTests : BaseVideoTest
     public class ExtraTests : BaseVideoTest
     {
     {
+        private readonly NamingOptions _videoOptions = new NamingOptions();
+
         // Requirements
         // Requirements
         // movie-deleted = ExtraType deletedscene
         // movie-deleted = ExtraType deletedscene
 
 
@@ -15,42 +17,64 @@ namespace Jellyfin.Naming.Tests.Video
         [Fact]
         [Fact]
         public void TestKodiExtras()
         public void TestKodiExtras()
         {
         {
-            var videoOptions = new NamingOptions();
-
-            Test("trailer.mp4", ExtraType.Trailer, videoOptions);
-            Test("300-trailer.mp4", ExtraType.Trailer, videoOptions);
+            Test("trailer.mp4", ExtraType.Trailer, _videoOptions);
+            Test("300-trailer.mp4", ExtraType.Trailer, _videoOptions);
 
 
-            Test("theme.mp3", ExtraType.ThemeSong, videoOptions);
+            Test("theme.mp3", ExtraType.ThemeSong, _videoOptions);
         }
         }
 
 
         [Fact]
         [Fact]
         public void TestExpandedExtras()
         public void TestExpandedExtras()
         {
         {
-            var videoOptions = new NamingOptions();
+            Test("trailer.mp4", ExtraType.Trailer, _videoOptions);
+            Test("trailer.mp3", null, _videoOptions);
+            Test("300-trailer.mp4", ExtraType.Trailer, _videoOptions);
 
 
-            Test("trailer.mp4", ExtraType.Trailer, videoOptions);
-            Test("trailer.mp3", null, videoOptions);
-            Test("300-trailer.mp4", ExtraType.Trailer, videoOptions);
+            Test("theme.mp3", ExtraType.ThemeSong, _videoOptions);
+            Test("theme.mkv", null, _videoOptions);
 
 
-            Test("theme.mp3", ExtraType.ThemeSong, videoOptions);
-            Test("theme.mkv", null, videoOptions);
+            Test("300-scene.mp4", ExtraType.Scene, _videoOptions);
+            Test("300-scene2.mp4", ExtraType.Scene, _videoOptions);
+            Test("300-clip.mp4", ExtraType.Clip, _videoOptions);
+
+            Test("300-deleted.mp4", ExtraType.DeletedScene, _videoOptions);
+            Test("300-deletedscene.mp4", ExtraType.DeletedScene, _videoOptions);
+            Test("300-interview.mp4", ExtraType.Interview, _videoOptions);
+            Test("300-behindthescenes.mp4", ExtraType.BehindTheScenes, _videoOptions);
+        }
 
 
-            Test("300-scene.mp4", ExtraType.Scene, videoOptions);
-            Test("300-scene2.mp4", ExtraType.Scene, videoOptions);
-            Test("300-clip.mp4", ExtraType.Clip, videoOptions);
+        [Theory]
+        [InlineData(ExtraType.BehindTheScenes, "behind the scenes" )]
+        [InlineData(ExtraType.DeletedScene, "deleted scenes" )]
+        [InlineData(ExtraType.Interview, "interviews" )]
+        [InlineData(ExtraType.Scene, "scenes" )]
+        [InlineData(ExtraType.Sample, "samples" )]
+        [InlineData(ExtraType.Clip, "shorts" )]
+        [InlineData(ExtraType.Clip, "featurettes" )]
+        [InlineData(ExtraType.Unknown, "extras" )]
+        public void TestDirectories(ExtraType type, string dirName)
+        {
+            Test(dirName + "/300.mp4", type, _videoOptions);
+            Test("300/" + dirName + "/something.mkv", type, _videoOptions);
+            Test("/data/something/Movies/300/" + dirName + "/whoknows.mp4", type, _videoOptions);
+        }
 
 
-            Test("300-deleted.mp4", ExtraType.DeletedScene, videoOptions);
-            Test("300-deletedscene.mp4", ExtraType.DeletedScene, videoOptions);
-            Test("300-interview.mp4", ExtraType.Interview, videoOptions);
-            Test("300-behindthescenes.mp4", ExtraType.BehindTheScenes, videoOptions);
+        [Theory]
+        [InlineData("gibberish")]
+        [InlineData("not a scene")]
+        [InlineData("The Big Short")]
+        public void TestNonExtraDirectories(string dirName)
+        {
+            Test(dirName + "/300.mp4", null, _videoOptions);
+            Test("300/" + dirName + "/something.mkv", null, _videoOptions);
+            Test("/data/something/Movies/300/" + dirName + "/whoknows.mp4", null, _videoOptions);
+            Test("/data/something/Movies/" + dirName + "/" + dirName + ".mp4", null, _videoOptions);
         }
         }
 
 
         [Fact]
         [Fact]
         public void TestSample()
         public void TestSample()
         {
         {
-            var videoOptions = new NamingOptions();
-
-            Test("300-sample.mp4", ExtraType.Sample, videoOptions);
+            Test("300-sample.mp4", ExtraType.Sample, _videoOptions);
         }
         }
 
 
         private void Test(string input, ExtraType? expectedType, NamingOptions videoOptions)
         private void Test(string input, ExtraType? expectedType, NamingOptions videoOptions)