Quellcode durchsuchen

Merge branch 'master' into register-services-correctly

Mark Monteiro vor 5 Jahren
Ursprung
Commit
9728aa8b0a
58 geänderte Dateien mit 1060 neuen und 1262 gelöschten Zeilen
  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)
  - [XVicarious](https://github.com/XVicarious)
  - [YouKnowBlom](https://github.com/YouKnowBlom)
+ - [KristupasSavickas](https://github.com/KristupasSavickas)
 
 # Emby Contributors
 

+ 1 - 1
Dockerfile.arm

@@ -74,4 +74,4 @@ VOLUME /cache /config /media
 ENTRYPOINT ["./jellyfin/jellyfin", \
     "--datadir", "/config", \
     "--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.Data;
 using Emby.Server.Implementations.Devices;
-using Emby.Server.Implementations.Diagnostics;
 using Emby.Server.Implementations.Dto;
 using Emby.Server.Implementations.HttpServer;
 using Emby.Server.Implementations.HttpServer.Security;
@@ -86,7 +85,6 @@ using MediaBrowser.MediaEncoding.BdInfo;
 using MediaBrowser.Model.Activity;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Cryptography;
-using MediaBrowser.Model.Diagnostics;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.IO;
@@ -574,8 +572,6 @@ namespace Emby.Server.Implementations
 
             serviceCollection.AddSingleton(_xmlSerializer);
 
-            serviceCollection.AddSingleton<IProcessFactory, ProcessFactory>();
-
             serviceCollection.AddSingleton<IStreamHelper, StreamHelper>();
 
             serviceCollection.AddSingleton<ICryptoProvider, CryptographyProvider>();
@@ -1462,15 +1458,17 @@ namespace Emby.Server.Implementations
                 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
             {
@@ -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)
         {
         }

+ 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
             {
@@ -247,11 +247,11 @@ namespace Emby.Server.Implementations.HttpServer
 
                 if (logExceptionStackTrace)
                 {
-                    _logger.LogError(ex, "Error processing request");
+                    _logger.LogError(ex, "Error processing request. URL: {Url}", urlToLog);
                 }
                 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;
@@ -271,7 +271,7 @@ namespace Emby.Server.Implementations.HttpServer
             }
             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();
             stopWatch.Start();
             var httpRes = httpReq.Response;
-            string urlToLog = null;
+            string urlToLog = GetUrlToLog(urlString);
             string remoteIp = httpReq.RemoteIp;
 
             try
@@ -502,8 +502,6 @@ namespace Emby.Server.Implementations.HttpServer
                     return;
                 }
 
-                urlToLog = GetUrlToLog(urlString);
-
                 if (string.Equals(localPath, _baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase)
                     || string.Equals(localPath, _baseUrlPrefix, StringComparison.OrdinalIgnoreCase)
                     || string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)
@@ -553,7 +551,7 @@ namespace Emby.Server.Implementations.HttpServer
                     || ex is OperationCanceledException
                     || ex is SecurityException
                     || ex is FileNotFoundException;
-                await ErrorHandler(ex, httpReq, ignoreStackTrace).ConfigureAwait(false);
+                await ErrorHandler(ex, httpReq, !ignoreStackTrace, urlToLog).ConfigureAwait(false);
             }
             finally
             {

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

@@ -3,6 +3,7 @@
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Globalization;
 using System.IO;
 using System.Linq;
@@ -25,7 +26,6 @@ using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Diagnostics;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Events;
@@ -61,7 +61,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
         private readonly ILibraryManager _libraryManager;
         private readonly IProviderManager _providerManager;
         private readonly IMediaEncoder _mediaEncoder;
-        private readonly IProcessFactory _processFactory;
         private readonly IMediaSourceManager _mediaSourceManager;
         private readonly IStreamHelper _streamHelper;
 
@@ -88,8 +87,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             ILibraryManager libraryManager,
             ILibraryMonitor libraryMonitor,
             IProviderManager providerManager,
-            IMediaEncoder mediaEncoder,
-            IProcessFactory processFactory)
+            IMediaEncoder mediaEncoder)
         {
             Current = this;
 
@@ -102,7 +100,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             _libraryMonitor = libraryMonitor;
             _providerManager = providerManager;
             _mediaEncoder = mediaEncoder;
-            _processFactory = processFactory;
             _liveTvManager = (LiveTvManager)liveTvManager;
             _jsonSerializer = jsonSerializer;
             _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))
             {
-                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);
@@ -1683,16 +1680,19 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
             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);
 
@@ -1712,11 +1712,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 
         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);
-
-                process.Dispose();
             }
         }
 

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

@@ -2,6 +2,7 @@
 
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Globalization;
 using System.IO;
 using System.Text;
@@ -13,7 +14,6 @@ using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Diagnostics;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Serialization;
@@ -29,8 +29,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
         private bool _hasExited;
         private Stream _logFileStream;
         private string _targetPath;
-        private IProcess _process;
-        private readonly IProcessFactory _processFactory;
+        private Process _process;
         private readonly IJsonSerializer _json;
         private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
         private readonly IServerConfigurationManager _config;
@@ -40,14 +39,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             IMediaEncoder mediaEncoder,
             IServerApplicationPaths appPaths,
             IJsonSerializer json,
-            IProcessFactory processFactory,
             IServerConfigurationManager config)
         {
             _logger = logger;
             _mediaEncoder = mediaEncoder;
             _appPaths = appPaths;
             _json = json;
-            _processFactory = processFactory;
             _config = config;
         }
 
@@ -79,7 +76,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
             _targetPath = targetFile;
             Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
 
-            var process = _processFactory.Create(new ProcessOptions
+            var processStartInfo = new ProcessStartInfo
             {
                 CreateNoWindow = true,
                 UseShellExecute = false,
@@ -90,14 +87,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
                 FileName = _mediaEncoder.EncoderPath,
                 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);
 
             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);
             _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);
 
             onStarted();
 
             // 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);
 
@@ -292,30 +291,33 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
         /// <summary>
         /// Processes the exited.
         /// </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}",
     "Application": "Applikation",
     "Artists": "Kunstnere",
@@ -106,5 +106,7 @@
     "TasksChannelsCategory": "Internet Kanaler",
     "TasksApplicationCategory": "Applikation",
     "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}",
     "ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
     "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",
     "Artists": "Artistas",
     "DeviceOnlineWithName": "{0} está conectado",
-    "DeviceOfflineWithName": "{0} ha desconectado",
+    "DeviceOfflineWithName": "{0} se ha desconectado",
     "ChapterNameValue": "Capítulo {0}",
     "CameraImageUploadedFrom": "Se ha subido una nueva imagen de cámara desde {0}",
     "AuthenticationSucceededWithUserName": "{0} autenticado con éxito",

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

@@ -23,7 +23,7 @@
     "HeaderFavoriteEpisodes": "قسمت‌های مورد علاقه",
     "HeaderFavoriteShows": "سریال‌های مورد علاقه",
     "HeaderFavoriteSongs": "آهنگ‌های مورد علاقه",
-    "HeaderLiveTV": "پخش زنده تلویزیون",
+    "HeaderLiveTV": "تلویزیون زنده",
     "HeaderNextUp": "قسمت بعدی",
     "HeaderRecordingGroups": "گروه‌های ضبط",
     "HomeVideos": "ویدیوهای خانگی",
@@ -92,5 +92,27 @@
     "UserStoppedPlayingItemWithValues": "{0} پخش {1} را بر روی {2} به پایان رساند",
     "ValueHasBeenAddedToLibrary": "{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.",
     "NameSeasonUnknown": "Tuntematon Kausi",
     "NameSeasonNumber": "Kausi {0}",
@@ -19,12 +19,12 @@
     "ItemAddedWithName": "{0} lisättiin kirjastoon",
     "Inherit": "Periytyä",
     "HomeVideos": "Kotivideot",
-    "HeaderRecordingGroups": "Nauhoitusryhmät",
+    "HeaderRecordingGroups": "Nauhoiteryhmät",
     "HeaderNextUp": "Seuraavaksi",
     "HeaderFavoriteSongs": "Lempikappaleet",
     "HeaderFavoriteShows": "Lempisarjat",
     "HeaderFavoriteEpisodes": "Lempijaksot",
-    "HeaderCameraUploads": "Kameralataukset",
+    "HeaderCameraUploads": "Kamerasta Lähetetyt",
     "HeaderFavoriteArtists": "Lempiartistit",
     "HeaderFavoriteAlbums": "Lempialbumit",
     "HeaderContinueWatching": "Jatka katsomista",
@@ -63,10 +63,10 @@
     "UserPasswordChangedWithName": "Salasana vaihdettu käyttäjälle {0}",
     "UserOnlineFromDevice": "{0} on paikalla osoitteesta {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",
     "Sync": "Synkronoi",
     "SubtitleDownloadFailureFromForItem": "Tekstityksen lataaminen epäonnistui {0} - {1}",
@@ -74,22 +74,44 @@
     "Songs": "Kappaleet",
     "Shows": "Ohjelmat",
     "ServerNameNeedsToBeRestarted": "{0} vaatii uudelleenkäynnistyksen",
-    "ProviderValue": "Palveluntarjoaja: {0}",
+    "ProviderValue": "Tarjoaja: {0}",
     "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",
-    "NotificationOptionPluginUpdateInstalled": "Liitännäinen päivitetty",
+    "NotificationOptionPluginUpdateInstalled": "Lisäosan päivitys asennettu",
     "NotificationOptionPluginUninstalled": "Liitännäinen poistettu",
     "NotificationOptionPluginInstalled": "Liitännäinen asennettu",
     "NotificationOptionPluginError": "Ongelma liitännäisessä",
     "NotificationOptionNewLibraryContent": "Uutta sisältöä lisätty",
     "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",
     "AuthenticationSucceededWithUserName": "{0} authentifié avec succès",
     "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",
     "ChapterNameValue": "Chapitre {0}",
     "Collections": "Collections",

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

@@ -71,7 +71,7 @@
     "ScheduledTaskFailedWithName": "{0} sikertelen",
     "ScheduledTaskStartedWithName": "{0} elkezdve",
     "ServerNameNeedsToBeRestarted": "{0}-t újra kell indítani",
-    "Shows": "Műsorok",
+    "Shows": "Sorozatok",
     "Songs": "Dalok",
     "StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek, próbáld újra hamarosan.",
     "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}",
     "AuthenticationSucceededWithUserName": "{0} autenticado com sucesso",
     "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>();
             }
 
-            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)
@@ -258,7 +255,7 @@ namespace MediaBrowser.Api
 
         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)
             {
@@ -487,16 +484,9 @@ namespace MediaBrowser.Api
         /// <returns>Task.</returns>
         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>
@@ -561,10 +551,7 @@ namespace MediaBrowser.Api
 
             lock (job.ProcessLock)
             {
-                if (job.TranscodingThrottler != null)
-                {
-                    job.TranscodingThrottler.Stop().GetAwaiter().GetResult();
-                }
+                job.TranscodingThrottler?.Stop().GetAwaiter().GetResult();
 
                 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)
         {
-            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)
@@ -97,19 +94,10 @@ namespace MediaBrowser.Api
             var authenticatedUser = auth.User;
 
             // 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();
             }
 
-            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;
                 if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 ||
@@ -150,7 +138,7 @@ namespace MediaBrowser.Api
                     int oldLen = options.Fields.Length;
                     var arr = new ItemFields[oldLen + 1];
                     options.Fields.CopyTo(arr, 0);
-                    arr[oldLen] = Model.Querying.ItemFields.RecursiveItemCount;
+                    arr[oldLen] = ItemFields.RecursiveItemCount;
                     options.Fields = arr;
                 }
 
@@ -166,7 +154,7 @@ namespace MediaBrowser.Api
                     int oldLen = options.Fields.Length;
                     var arr = new ItemFields[oldLen + 1];
                     options.Fields.CopyTo(arr, 0);
-                    arr[oldLen] = Model.Querying.ItemFields.ChildCount;
+                    arr[oldLen] = ItemFields.ChildCount;
                     options.Fields = arr;
                 }
             }
@@ -282,27 +270,21 @@ namespace MediaBrowser.Api
 
             }).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;
         }

+ 7 - 15
MediaBrowser.Api/ChannelService.cs

@@ -116,12 +116,9 @@ namespace MediaBrowser.Api
         {
             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>
@@ -173,14 +170,9 @@ namespace MediaBrowser.Api
         /// <returns>IEnumerable{ItemFilter}.</returns>
         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,
                 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),
                 OrderBy = request.GetOrderBy(),
                 DtoOptions = new Controller.Dto.DtoOptions

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

@@ -155,16 +155,14 @@ namespace MediaBrowser.Api.Devices
                     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;
                 }
 
-                if (!request.IncludeDirectories && isDirectory)
-                {
-                    return false;
-                }
-
-                return true;
+                return request.IncludeDirectories || !isDirectory;
             });
 
             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
             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
             {
@@ -231,7 +231,7 @@ namespace MediaBrowser.Api
                 EnableTotalRecordCount = false,
                 DtoOptions = new Controller.Dto.DtoOptions
                 {
-                    Fields = new ItemFields[] { ItemFields.Genres, ItemFields.Tags },
+                    Fields = new[] { ItemFields.Genres, ItemFields.Tags },
                     EnableImages = false,
                     EnableUserData = false
                 }

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

@@ -657,7 +657,7 @@ namespace MediaBrowser.Api.Images
             if (!string.IsNullOrWhiteSpace(request.Format)
                 && Enum.TryParse(request.Format, true, out ImageFormat format))
             {
-                return new ImageFormat[] { format };
+                return new[] { format };
             }
 
             return GetClientSupportedFormats();
@@ -750,24 +750,22 @@ namespace MediaBrowser.Api.Images
         /// <returns>Task.</returns>
         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>
         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,
                 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>

+ 9 - 2
MediaBrowser.Api/ItemLookupService.cs

@@ -305,9 +305,16 @@ namespace MediaBrowser.Api
 
             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);
+                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));

+ 9 - 19
MediaBrowser.Api/ItemUpdateService.cs

@@ -263,8 +263,7 @@ namespace MediaBrowser.Api
             item.Overview = request.Overview;
             item.Genres = request.Genres;
 
-            var episode = item as Episode;
-            if (episode != null)
+            if (item is Episode episode)
             {
                 episode.AirsAfterSeasonNumber = request.AirsAfterSeasonNumber;
                 episode.AirsBeforeEpisodeNumber = request.AirsBeforeEpisodeNumber;
@@ -302,14 +301,12 @@ namespace MediaBrowser.Api
             item.PreferredMetadataCountryCode = request.PreferredMetadataCountryCode;
             item.PreferredMetadataLanguage = request.PreferredMetadataLanguage;
 
-            var hasDisplayOrder = item as IHasDisplayOrder;
-            if (hasDisplayOrder != null)
+            if (item is IHasDisplayOrder hasDisplayOrder)
             {
                 hasDisplayOrder.DisplayOrder = request.DisplayOrder;
             }
 
-            var hasAspectRatio = item as IHasAspectRatio;
-            if (hasAspectRatio != null)
+            if (item is IHasAspectRatio hasAspectRatio)
             {
                 hasAspectRatio.AspectRatio = request.AspectRatio;
             }
@@ -337,16 +334,14 @@ namespace MediaBrowser.Api
 
             item.ProviderIds = request.ProviderIds;
 
-            var video = item as Video;
-            if (video != null)
+            if (item is Video video)
             {
                 video.Video3DFormat = request.Video3DFormat;
             }
 
             if (request.AlbumArtists != null)
             {
-                var hasAlbumArtists = item as IHasAlbumArtist;
-                if (hasAlbumArtists != null)
+                if (item is IHasAlbumArtist hasAlbumArtists)
                 {
                     hasAlbumArtists.AlbumArtists = request
                         .AlbumArtists
@@ -357,8 +352,7 @@ namespace MediaBrowser.Api
 
             if (request.ArtistItems != null)
             {
-                var hasArtists = item as IHasArtist;
-                if (hasArtists != null)
+                if (item is IHasArtist hasArtists)
                 {
                     hasArtists.Artists = request
                         .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;
             }
 
-            var musicVideo = item as MusicVideo;
-            if (musicVideo != null)
+            if (item is MusicVideo musicVideo)
             {
                 musicVideo.Album = request.Album;
             }
 
-            var series = item as Series;
-            if (series != null)
+            if (item is Series series)
             {
                 series.Status = GetSeriesStatus(request);
 
@@ -400,7 +391,6 @@ namespace MediaBrowser.Api
             }
 
             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)
         {
-            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)
@@ -397,54 +388,22 @@ namespace MediaBrowser.Api.Library
             {
                 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
                 .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
                 .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)
@@ -453,50 +412,17 @@ namespace MediaBrowser.Api.Library
             {
                 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
@@ -561,8 +487,7 @@ namespace MediaBrowser.Api.Library
 
             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
                 {
@@ -609,8 +534,6 @@ namespace MediaBrowser.Api.Library
 
         public object Get(GetSimilarItems request)
         {
-            var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null;
-
             var item = string.IsNullOrEmpty(request.Id) ?
                 (!request.UserId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() :
                 _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
@@ -668,7 +591,7 @@ namespace MediaBrowser.Api.Library
             // ExcludeArtistIds
             if (!string.IsNullOrEmpty(request.ExcludeArtistIds))
             {
-                query.ExcludeArtistIds = BaseApiService.GetGuids(request.ExcludeArtistIds);
+                query.ExcludeArtistIds = GetGuids(request.ExcludeArtistIds);
             }
 
             List<BaseItem> itemsResult;
@@ -689,7 +612,6 @@ namespace MediaBrowser.Api.Library
             var result = new QueryResult<BaseItemDto>
             {
                 Items = returnList,
-
                 TotalRecordCount = itemsResult.Count
             };
 
@@ -919,12 +841,10 @@ namespace MediaBrowser.Api.Library
 
         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>
@@ -1086,7 +1006,7 @@ namespace MediaBrowser.Api.Library
             var item = string.IsNullOrEmpty(request.Id)
                            ? (!request.UserId.Equals(Guid.Empty)
                                   ? _libraryManager.GetUserRootFolder()
-                                  : (Folder)_libraryManager.RootFolder)
+                                  : _libraryManager.RootFolder)
                            : _libraryManager.GetItemById(request.Id);
 
             if (item == null)
@@ -1094,18 +1014,13 @@ namespace MediaBrowser.Api.Library
                 throw new ResourceNotFoundException("Item not found.");
             }
 
-            BaseItem[] themeItems = Array.Empty<BaseItem>();
+            IEnumerable<BaseItem> themeItems;
 
             while (true)
             {
-                themeItems = item.GetThemeSongs().ToArray();
-
-                if (themeItems.Length > 0)
-                {
-                    break;
-                }
+                themeItems = item.GetThemeSongs();
 
-                if (!request.InheritFromParent)
+                if (themeItems.Any() || !request.InheritFromParent)
                 {
                     break;
                 }
@@ -1119,11 +1034,9 @@ namespace MediaBrowser.Api.Library
             }
 
             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
             {
@@ -1140,9 +1053,7 @@ namespace MediaBrowser.Api.Library
         /// <returns>System.Object.</returns>
         public object Get(GetThemeVideos request)
         {
-            var result = GetThemeVideos(request);
-
-            return ToOptimizedResult(result);
+            return ToOptimizedResult(GetThemeVideos(request));
         }
 
         public ThemeMediaResult GetThemeVideos(GetThemeVideos request)
@@ -1152,7 +1063,7 @@ namespace MediaBrowser.Api.Library
             var item = string.IsNullOrEmpty(request.Id)
                            ? (!request.UserId.Equals(Guid.Empty)
                                   ? _libraryManager.GetUserRootFolder()
-                                  : (Folder)_libraryManager.RootFolder)
+                                  : _libraryManager.RootFolder)
                            : _libraryManager.GetItemById(request.Id);
 
             if (item == null)
@@ -1160,18 +1071,13 @@ namespace MediaBrowser.Api.Library
                 throw new ResourceNotFoundException("Item not found.");
             }
 
-            BaseItem[] themeItems = Array.Empty<BaseItem>();
+            IEnumerable<BaseItem> themeItems;
 
             while (true)
             {
-                themeItems = item.GetThemeVideos().ToArray();
-
-                if (themeItems.Length > 0)
-                {
-                    break;
-                }
+                themeItems = item.GetThemeVideos();
 
-                if (!request.InheritFromParent)
+                if (themeItems.Any() || !request.InheritFromParent)
                 {
                     break;
                 }
@@ -1186,10 +1092,9 @@ namespace MediaBrowser.Api.Library
 
             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
             {

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

@@ -327,15 +327,11 @@ namespace MediaBrowser.Api.Library
 
             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);
             }
             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
             // 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)
@@ -1050,8 +1049,7 @@ namespace MediaBrowser.Api.LiveTv
             {
                 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;
                 }

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

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

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

@@ -137,12 +137,9 @@ namespace MediaBrowser.Api.Playback
             var ext = outputFileExtension.ToLowerInvariant();
             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()
@@ -248,14 +245,8 @@ namespace MediaBrowser.Api.Playback
             if (state.VideoRequest != null
                 && 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");
@@ -389,195 +380,181 @@ namespace MediaBrowser.Api.Playback
                     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");
             }
             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)
             {
@@ -856,21 +828,11 @@ namespace MediaBrowser.Api.Playback
             {
                 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;

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

@@ -140,7 +140,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
             if (isLive)
             {
-                job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
+                job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
 
                 if (job != null)
                 {
@@ -156,7 +156,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
             var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, baselineStreamBitrate);
 
-            job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
+            job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
 
             if (job != null)
             {
@@ -168,22 +168,19 @@ namespace MediaBrowser.Api.Playback.Hls
 
         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)
@@ -212,29 +209,25 @@ namespace MediaBrowser.Api.Playback.Hls
                 try
                 {
                     // 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)
                 {
@@ -247,17 +240,13 @@ namespace MediaBrowser.Api.Playback.Hls
 
         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)

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

@@ -284,7 +284,7 @@ namespace MediaBrowser.Api.Playback.Hls
             //}
 
             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);
         }
 
@@ -438,8 +438,7 @@ namespace MediaBrowser.Api.Playback.Hls
         {
             var segmentId = "0";
 
-            var segmentRequest = request as GetHlsVideoSegment;
-            if (segmentRequest != null)
+            if (request is GetHlsVideoSegment segmentRequest)
             {
                 segmentId = segmentRequest.SegmentId;
             }
@@ -690,8 +689,7 @@ namespace MediaBrowser.Api.Playback.Hls
                 return false;
             }
 
-            var request = state.Request as IMasterHlsRequest;
-            if (request != null && !request.EnableAdaptiveBitrateStreaming)
+            if (state.Request is IMasterHlsRequest request && !request.EnableAdaptiveBitrateStreaming)
             {
                 return false;
             }
@@ -936,7 +934,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
                 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,
                     // as forcing keyframes is not enough.

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

@@ -234,7 +234,7 @@ namespace MediaBrowser.Api.Playback
                         OpenToken = mediaSource.OpenToken
                     }).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);
 
-                mediaSources = new MediaSourceInfo[] { mediaSource };
+                mediaSources = new[] { mediaSource };
             }
 
             if (mediaSources.Length == 0)
@@ -366,7 +366,7 @@ namespace MediaBrowser.Api.Playback
 
             var options = new VideoOptions
             {
-                MediaSources = new MediaSourceInfo[] { mediaSource },
+                MediaSources = new[] { mediaSource },
                 Context = EncodingContext.Streaming,
                 DeviceId = auth.DeviceId,
                 ItemId = item.Id,
@@ -572,8 +572,7 @@ namespace MediaBrowser.Api.Playback
             {
                 attachment.DeliveryUrl = string.Format(
                     CultureInfo.InvariantCulture,
-                    "{0}/Videos/{1}/{2}/Attachments/{3}",
-                    ServerConfigurationManager.Configuration.BaseUrl,
+                    "/Videos/{0}/{1}/Attachments/{2}",
                     item.Id,
                     mediaSource.Id,
                     attachment.Index);
@@ -583,7 +582,7 @@ namespace MediaBrowser.Api.Playback
         private long? GetMaxBitrate(long? clientMaxBitrate, User user)
         {
             var maxBitrate = clientMaxBitrate;
-            var remoteClientMaxBitrate = user == null ? 0 : user.Policy.RemoteClientBitrateLimit;
+            var remoteClientMaxBitrate = user?.Policy.RemoteClientBitrateLimit ?? 0;
 
             if (remoteClientMaxBitrate <= 0)
             {
@@ -662,17 +661,9 @@ namespace MediaBrowser.Api.Playback
                 };
             }).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;

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

@@ -167,7 +167,7 @@ namespace MediaBrowser.Api.Playback
                     AudioCodec = request.AudioCodec,
                     Protocol = request.TranscodingProtocol,
                     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
                 // 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
                 {

+ 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
             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();
             }

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

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

+ 40 - 58
MediaBrowser.Api/SearchService.cs

@@ -234,59 +234,48 @@ namespace MediaBrowser.Api
             SetThumbImageInfo(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))
             {
                 var channel = _libraryManager.GetItemById(item.ChannelId);
-                result.ChannelName = channel == null ? null : channel.Name;
+                result.ChannelName = channel?.Name;
             }
 
             return result;
@@ -296,12 +285,9 @@ namespace MediaBrowser.Api
         {
             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)
@@ -323,12 +309,8 @@ namespace MediaBrowser.Api
 
         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)
             {

+ 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)
             {
-                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));

+ 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));
 
             // 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>

+ 1 - 4
MediaBrowser.Api/TranscodingJob.cs

@@ -92,10 +92,7 @@ namespace MediaBrowser.Api
         {
             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))
             {
-                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);
                 }
@@ -444,14 +442,7 @@ namespace MediaBrowser.Api
 
                 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
             {

+ 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)
         {
-            if (request is GetAlbumArtists)
-            {
-                return LibraryManager.GetAlbumArtists(query);
-            }
-
-            return LibraryManager.GetArtists(query);
+            return request is GetAlbumArtists ? LibraryManager.GetAlbumArtists(query) : LibraryManager.GetArtists(query);
         }
 
         /// <summary>

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

@@ -82,8 +82,7 @@ namespace MediaBrowser.Api.UserLibrary
         {
             var parent = GetParentItem(request);
 
-            var collectionFolder = parent as IHasCollectionType;
-            if (collectionFolder != null)
+            if (parent is IHasCollectionType collectionFolder)
             {
                 return collectionFolder.CollectionType;
             }
@@ -274,7 +273,7 @@ namespace MediaBrowser.Api.UserLibrary
                 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)
             {
@@ -284,18 +283,18 @@ namespace MediaBrowser.Api.UserLibrary
                 {
                     items = request.Recursive ?
                         folder.GetRecursiveChildren(user, query).ToList() :
-                        folder.GetChildren(user, true).Where(filter).ToList();
+                        folder.GetChildren(user, true).Where(Filter).ToList();
                 }
                 else
                 {
                     items = request.Recursive ?
-                        folder.GetRecursiveChildren(filter) :
-                        folder.Children.Where(filter).ToList();
+                        folder.GetRecursiveChildren(Filter) :
+                        folder.Children.Where(Filter).ToList();
                 }
             }
             else
             {
-                items = new[] { parentItem }.Where(filter).ToList();
+                items = new[] { parentItem }.Where(Filter).ToList();
             }
 
             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)
         {
             // 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
-            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
-            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;

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

@@ -396,12 +396,10 @@ namespace MediaBrowser.Api.UserLibrary
 
         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>
@@ -412,12 +410,10 @@ namespace MediaBrowser.Api.UserLibrary
         {
             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>
@@ -428,12 +424,9 @@ namespace MediaBrowser.Api.UserLibrary
         {
             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>
@@ -469,7 +462,9 @@ namespace MediaBrowser.Api.UserLibrary
                 var sortOrderIndex = sortOrders.Length > i ? i : 0;
 
                 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);
             }

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

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

+ 2 - 11
MediaBrowser.Api/VideosService.cs

@@ -139,17 +139,11 @@ namespace MediaBrowser.Api
                 .ToList();
 
             var primaryVersion = videosWithVersions.FirstOrDefault();
-
             if (primaryVersion == null)
             {
                 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;
                         }
@@ -158,10 +152,7 @@ namespace MediaBrowser.Api
                     })
                     .ThenByDescending(i =>
                     {
-                        var stream = i.GetDefaultVideoStream();
-
-                        return stream == null || stream.Width == null ? 0 : stream.Width.Value;
-
+                        return i.GetDefaultVideoStream()?.Width ?? 0;
                     }).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)
                     && encodingOptions.EnableHardwareEncoding
-                    && codecMap.ContainsKey(hwType)
-                    && CheckVaapi(state, hwType, encodingOptions))
+                    && codecMap.ContainsKey(hwType))
                 {
                     var preferredEncoder = codecMap[hwType];
 
@@ -93,23 +92,6 @@ namespace MediaBrowser.Controller.MediaEncoding
             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)
         {
             var videoStream = state.VideoStream;
@@ -424,7 +406,13 @@ namespace MediaBrowser.Controller.MediaEncoding
 
             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))
@@ -1605,7 +1593,7 @@ namespace MediaBrowser.Controller.MediaEncoding
                 // For VAAPI and CUVID decoder
                 // these encoders cannot automatically adjust the size of graphical subtitles to fit the output video,
                 // 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)
                 {
                     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
-            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))
             {
                 /*
@@ -2014,19 +2002,29 @@ namespace MediaBrowser.Controller.MediaEncoding
             }
 
             // 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))
             {
                 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("format=nv12");

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

@@ -155,47 +155,44 @@ namespace MediaBrowser.MediaEncoding.Attachments
                 inputPath,
                 attachmentStreamIndex,
                 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;
 

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

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

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

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

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

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Concurrent;
+using System.Diagnostics;
 using System.Globalization;
 using System.IO;
 using System.Linq;
@@ -12,7 +13,6 @@ using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.Diagnostics;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.IO;
@@ -31,7 +31,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
         private readonly IMediaEncoder _mediaEncoder;
         private readonly IHttpClient _httpClient;
         private readonly IMediaSourceManager _mediaSourceManager;
-        private readonly IProcessFactory _processFactory;
 
         public SubtitleEncoder(
             ILibraryManager libraryManager,
@@ -40,8 +39,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
             IFileSystem fileSystem,
             IMediaEncoder mediaEncoder,
             IHttpClient httpClient,
-            IMediaSourceManager mediaSourceManager,
-            IProcessFactory processFactory)
+            IMediaSourceManager mediaSourceManager)
         {
             _libraryManager = libraryManager;
             _logger = logger;
@@ -50,7 +48,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
             _mediaEncoder = mediaEncoder;
             _httpClient = httpClient;
             _mediaSourceManager = mediaSourceManager;
-            _processFactory = processFactory;
         }
 
         private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles");
@@ -429,49 +426,53 @@ namespace MediaBrowser.MediaEncoding.Subtitles
                 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
                 {
-                    _logger.LogInformation("Killing ffmpeg subtitle conversion process");
-
-                    process.Kill();
+                    process.Start();
                 }
                 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;
 
@@ -578,49 +579,53 @@ namespace MediaBrowser.MediaEncoding.Subtitles
                 outputCodec,
                 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
                 {
-                    _logger.LogWarning("Killing ffmpeg subtitle extraction process");
-
-                    process.Kill();
+                    process.Start();
                 }
                 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;
 

+ 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
     {
+        private readonly NamingOptions _videoOptions = new NamingOptions();
+
         // Requirements
         // movie-deleted = ExtraType deletedscene
 
@@ -15,42 +17,64 @@ namespace Jellyfin.Naming.Tests.Video
         [Fact]
         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]
         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]
         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)