Browse Source

Merge pull request #3 from jellyfin/master

nightly, big one
Artiume 5 years ago
parent
commit
6a6e02e1ec
37 changed files with 554 additions and 358 deletions
  1. 15 8
      Emby.Server.Implementations/ApplicationHost.cs
  2. 5 2
      Emby.Server.Implementations/ConfigurationOptions.cs
  3. 0 1
      Emby.Server.Implementations/Emby.Server.Implementations.csproj
  4. 37 37
      Emby.Server.Implementations/Localization/Core/ca.json
  5. 13 0
      Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
  6. 9 0
      Jellyfin.Drawing.Skia/PercentPlayedDrawer.cs
  7. 13 2
      Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs
  8. 8 8
      Jellyfin.Drawing.Skia/SkiaCodecException.cs
  9. 36 13
      Jellyfin.Drawing.Skia/SkiaEncoder.cs
  10. 16 3
      Jellyfin.Drawing.Skia/SkiaException.cs
  11. 27 0
      Jellyfin.Drawing.Skia/StripCollageBuilder.cs
  12. 17 0
      Jellyfin.Drawing.Skia/UnplayedCountIndicator.cs
  13. 6 1
      Jellyfin.Server/Program.cs
  14. 5 10
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  15. 17 17
      MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
  16. 4 4
      MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
  17. 33 33
      MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
  18. 4 4
      MediaBrowser.Api/Playback/Progressive/AudioService.cs
  19. 4 4
      MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
  20. 4 4
      MediaBrowser.Api/Playback/Progressive/VideoService.cs
  21. 10 9
      MediaBrowser.Api/Playback/UniversalAudioService.cs
  22. 11 5
      MediaBrowser.Controller/Drawing/IImageEncoder.cs
  23. 3 2
      MediaBrowser.Controller/Entities/Video.cs
  24. 36 0
      MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs
  25. 4 0
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  26. 23 9
      MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
  27. 3 1
      MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
  28. 93 98
      MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
  29. 1 1
      MediaBrowser.MediaEncoding/Subtitles/ISubtitleWriter.cs
  30. 24 12
      MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs
  31. 10 7
      MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs
  32. 51 52
      MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
  33. 0 7
      MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs
  34. 0 1
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  35. 5 2
      MediaBrowser.Model/MediaInfo/SubtitleTrackInfo.cs
  36. 5 1
      MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
  37. 2 0
      jellyfin.ruleset

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

@@ -841,16 +841,14 @@ namespace Emby.Server.Implementations
             serviceCollection.AddSingleton(ChapterManager);
             serviceCollection.AddSingleton(ChapterManager);
 
 
             MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder(
             MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder(
-                LoggerFactory,
-                JsonSerializer,
-                StartupOptions.FFmpegPath,
+                LoggerFactory.CreateLogger<MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(),
                 ServerConfigurationManager,
                 ServerConfigurationManager,
                 FileSystemManager,
                 FileSystemManager,
-                () => SubtitleEncoder,
-                () => MediaSourceManager,
                 ProcessFactory,
                 ProcessFactory,
-                5000,
-                LocalizationManager);
+                LocalizationManager,
+                () => SubtitleEncoder,
+                _configuration,
+                StartupOptions.FFmpegPath);
             serviceCollection.AddSingleton(MediaEncoder);
             serviceCollection.AddSingleton(MediaEncoder);
 
 
             EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager);
             EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager);
@@ -867,10 +865,19 @@ namespace Emby.Server.Implementations
             AuthService = new AuthService(LoggerFactory.CreateLogger<AuthService>(), authContext, ServerConfigurationManager, SessionManager, NetworkManager);
             AuthService = new AuthService(LoggerFactory.CreateLogger<AuthService>(), authContext, ServerConfigurationManager, SessionManager, NetworkManager);
             serviceCollection.AddSingleton(AuthService);
             serviceCollection.AddSingleton(AuthService);
 
 
-            SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory);
+            SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(
+                LibraryManager,
+                LoggerFactory.CreateLogger<MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder>(),
+                ApplicationPaths,
+                FileSystemManager,
+                MediaEncoder,
+                HttpClient,
+                MediaSourceManager,
+                ProcessFactory);
             serviceCollection.AddSingleton(SubtitleEncoder);
             serviceCollection.AddSingleton(SubtitleEncoder);
 
 
             serviceCollection.AddSingleton(typeof(IResourceFileManager), typeof(ResourceFileManager));
             serviceCollection.AddSingleton(typeof(IResourceFileManager), typeof(ResourceFileManager));
+            serviceCollection.AddSingleton<EncodingHelper>();
 
 
             _displayPreferencesRepository.Initialize();
             _displayPreferencesRepository.Initialize();
 
 

+ 5 - 2
Emby.Server.Implementations/ConfigurationOptions.cs

@@ -1,13 +1,16 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
+using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
 
 
 namespace Emby.Server.Implementations
 namespace Emby.Server.Implementations
 {
 {
     public static class ConfigurationOptions
     public static class ConfigurationOptions
     {
     {
-        public static readonly Dictionary<string, string> Configuration = new Dictionary<string, string>
+        public static Dictionary<string, string> Configuration => new Dictionary<string, string>
         {
         {
             { "HttpListenerHost:DefaultRedirectPath", "web/index.html" },
             { "HttpListenerHost:DefaultRedirectPath", "web/index.html" },
-            { "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" }
+            { "MusicBrainz:BaseUrl", "https://www.musicbrainz.org" },
+            { FfmpegProbeSizeKey, "1G" },
+            { FfmpegAnalyzeDurationKey, "200M" }
         };
         };
     }
     }
 }
 }

+ 0 - 1
Emby.Server.Implementations/Emby.Server.Implementations.csproj

@@ -29,7 +29,6 @@
     <PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
     <PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
     <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
     <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
     <PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
     <PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
-    <PackageReference Include="Microsoft.Extensions.Logging" Version="3.0.1" />
     <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.1" />
     <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.1" />
     <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.0.1" />
     <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.0.1" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.0.1" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.0.1" />

+ 37 - 37
Emby.Server.Implementations/Localization/Core/ca.json

@@ -1,11 +1,11 @@
 {
 {
     "Albums": "Àlbums",
     "Albums": "Àlbums",
-    "AppDeviceValues": "App: {0}, Dispositiu: {1}",
-    "Application": "Application",
+    "AppDeviceValues": "Aplicació: {0}, Dispositiu: {1}",
+    "Application": "Aplicació",
     "Artists": "Artistes",
     "Artists": "Artistes",
     "AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament",
     "AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament",
     "Books": "Llibres",
     "Books": "Llibres",
-    "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
+    "CameraImageUploadedFrom": "Una nova imatge de càmera ha sigut pujada des de {0}",
     "Channels": "Canals",
     "Channels": "Canals",
     "ChapterNameValue": "Episodi {0}",
     "ChapterNameValue": "Episodi {0}",
     "Collections": "Col·leccions",
     "Collections": "Col·leccions",
@@ -15,8 +15,8 @@
     "Favorites": "Preferits",
     "Favorites": "Preferits",
     "Folders": "Directoris",
     "Folders": "Directoris",
     "Genres": "Gèneres",
     "Genres": "Gèneres",
-    "HeaderAlbumArtists": "Album Artists",
-    "HeaderCameraUploads": "Camera Uploads",
+    "HeaderAlbumArtists": "Artistes dels Àlbums",
+    "HeaderCameraUploads": "Pujades de Càmera",
     "HeaderContinueWatching": "Continua Veient",
     "HeaderContinueWatching": "Continua Veient",
     "HeaderFavoriteAlbums": "Àlbums Preferits",
     "HeaderFavoriteAlbums": "Àlbums Preferits",
     "HeaderFavoriteArtists": "Artistes Preferits",
     "HeaderFavoriteArtists": "Artistes Preferits",
@@ -27,71 +27,71 @@
     "HeaderNextUp": "A continuació",
     "HeaderNextUp": "A continuació",
     "HeaderRecordingGroups": "Grups d'Enregistrament",
     "HeaderRecordingGroups": "Grups d'Enregistrament",
     "HomeVideos": "Vídeos domèstics",
     "HomeVideos": "Vídeos domèstics",
-    "Inherit": "Heretat",
-    "ItemAddedWithName": "{0} afegit a la biblioteca",
-    "ItemRemovedWithName": "{0} eliminat de la biblioteca",
+    "Inherit": "Hereta",
+    "ItemAddedWithName": "{0} ha estat afegit a la biblioteca",
+    "ItemRemovedWithName": "{0} ha estat eliminat de la biblioteca",
     "LabelIpAddressValue": "Adreça IP: {0}",
     "LabelIpAddressValue": "Adreça IP: {0}",
-    "LabelRunningTimeValue": "Temps en marxa: {0}",
+    "LabelRunningTimeValue": "Temps en funcionament: {0}",
     "Latest": "Darreres",
     "Latest": "Darreres",
-    "MessageApplicationUpdated": "El Servidor d'Jellyfin ha estat actualitzat",
-    "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
-    "MessageNamedServerConfigurationUpdatedWithValue": "La secció de configuració {0} ha estat actualitzada",
+    "MessageApplicationUpdated": "El Servidor de Jellyfin ha estat actualitzat",
+    "MessageApplicationUpdatedTo": "El Servidor de Jellyfin ha estat actualitzat a {0}",
+    "MessageNamedServerConfigurationUpdatedWithValue": "La secció {0} de la configuració del servidor ha estat actualitzada",
     "MessageServerConfigurationUpdated": "S'ha actualitzat la configuració del servidor",
     "MessageServerConfigurationUpdated": "S'ha actualitzat la configuració del servidor",
     "MixedContent": "Contingut mesclat",
     "MixedContent": "Contingut mesclat",
     "Movies": "Pel·lícules",
     "Movies": "Pel·lícules",
     "Music": "Música",
     "Music": "Música",
     "MusicVideos": "Vídeos musicals",
     "MusicVideos": "Vídeos musicals",
-    "NameInstallFailed": "{0} installation failed",
+    "NameInstallFailed": "Instalació de {0} fallida",
     "NameSeasonNumber": "Temporada {0}",
     "NameSeasonNumber": "Temporada {0}",
-    "NameSeasonUnknown": "Season Unknown",
-    "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
+    "NameSeasonUnknown": "Temporada Desconeguda",
+    "NewVersionIsAvailable": "Una nova versió del Servidor Jellyfin està disponible per descarregar.",
     "NotificationOptionApplicationUpdateAvailable": "Actualització d'aplicació disponible",
     "NotificationOptionApplicationUpdateAvailable": "Actualització d'aplicació disponible",
     "NotificationOptionApplicationUpdateInstalled": "Actualització d'aplicació instal·lada",
     "NotificationOptionApplicationUpdateInstalled": "Actualització d'aplicació instal·lada",
-    "NotificationOptionAudioPlayback": "Audio playback started",
-    "NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
-    "NotificationOptionCameraImageUploaded": "Camera image uploaded",
-    "NotificationOptionInstallationFailed": "Installation failure",
-    "NotificationOptionNewLibraryContent": "New content added",
-    "NotificationOptionPluginError": "Un component ha fallat",
-    "NotificationOptionPluginInstalled": "Complement instal·lat",
-    "NotificationOptionPluginUninstalled": "Complement desinstal·lat",
-    "NotificationOptionPluginUpdateInstalled": "Actualització de complement instal·lada",
-    "NotificationOptionServerRestartRequired": "Server restart required",
-    "NotificationOptionTaskFailed": "Scheduled task failure",
-    "NotificationOptionUserLockedOut": "User locked out",
-    "NotificationOptionVideoPlayback": "Video playback started",
-    "NotificationOptionVideoPlaybackStopped": "Video playback stopped",
+    "NotificationOptionAudioPlayback": "Reproducció d'audio iniciada",
+    "NotificationOptionAudioPlaybackStopped": "Reproducció d'audio aturada",
+    "NotificationOptionCameraImageUploaded": "Imatge de càmera pujada",
+    "NotificationOptionInstallationFailed": "Instalació fallida",
+    "NotificationOptionNewLibraryContent": "Nou contingut afegit",
+    "NotificationOptionPluginError": "Un connector ha fallat",
+    "NotificationOptionPluginInstalled": "Connector instal·lat",
+    "NotificationOptionPluginUninstalled": "Connector desinstal·lat",
+    "NotificationOptionPluginUpdateInstalled": "Actualització de connector instal·lada",
+    "NotificationOptionServerRestartRequired": "Reinici del servidor requerit",
+    "NotificationOptionTaskFailed": "Tasca programada fallida",
+    "NotificationOptionUserLockedOut": "Usuari tancat",
+    "NotificationOptionVideoPlayback": "Reproducció de video iniciada",
+    "NotificationOptionVideoPlaybackStopped": "Reproducció de video aturada",
     "Photos": "Fotos",
     "Photos": "Fotos",
     "Playlists": "Llistes de reproducció",
     "Playlists": "Llistes de reproducció",
-    "Plugin": "Plugin",
+    "Plugin": "Connector",
     "PluginInstalledWithName": "{0} ha estat instal·lat",
     "PluginInstalledWithName": "{0} ha estat instal·lat",
     "PluginUninstalledWithName": "{0} ha estat desinstal·lat",
     "PluginUninstalledWithName": "{0} ha estat desinstal·lat",
     "PluginUpdatedWithName": "{0} ha estat actualitzat",
     "PluginUpdatedWithName": "{0} ha estat actualitzat",
     "ProviderValue": "Proveïdor: {0}",
     "ProviderValue": "Proveïdor: {0}",
     "ScheduledTaskFailedWithName": "{0} ha fallat",
     "ScheduledTaskFailedWithName": "{0} ha fallat",
     "ScheduledTaskStartedWithName": "{0} iniciat",
     "ScheduledTaskStartedWithName": "{0} iniciat",
-    "ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
-    "Shows": "Espectacles",
+    "ServerNameNeedsToBeRestarted": "{0} necessita ser reiniciat",
+    "Shows": "Programes",
     "Songs": "Cançons",
     "Songs": "Cançons",
     "StartupEmbyServerIsLoading": "El Servidor d'Jellyfin est&agrave; carregant. Si et plau, prova de nou en breus.",
     "StartupEmbyServerIsLoading": "El Servidor d'Jellyfin est&agrave; carregant. Si et plau, prova de nou en breus.",
     "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
     "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
-    "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
+    "SubtitleDownloadFailureFromForItem": "Els subtítols no s'han pogut baixar de {0} per {1}",
     "SubtitlesDownloadedForItem": "Subtítols descarregats per a {0}",
     "SubtitlesDownloadedForItem": "Subtítols descarregats per a {0}",
-    "Sync": "Sync",
+    "Sync": "Sincronitzar",
     "System": "System",
     "System": "System",
     "TvShows": "Espectacles de TV",
     "TvShows": "Espectacles de TV",
     "User": "User",
     "User": "User",
     "UserCreatedWithName": "S'ha creat l'usuari {0}",
     "UserCreatedWithName": "S'ha creat l'usuari {0}",
     "UserDeletedWithName": "L'usuari {0} ha estat eliminat",
     "UserDeletedWithName": "L'usuari {0} ha estat eliminat",
     "UserDownloadingItemWithValues": "{0} està descarregant {1}",
     "UserDownloadingItemWithValues": "{0} està descarregant {1}",
-    "UserLockedOutWithName": "User {0} has been locked out",
+    "UserLockedOutWithName": "L'usuari {0} ha sigut tancat",
     "UserOfflineFromDevice": "{0} s'ha desconnectat de {1}",
     "UserOfflineFromDevice": "{0} s'ha desconnectat de {1}",
     "UserOnlineFromDevice": "{0} està connectat des de {1}",
     "UserOnlineFromDevice": "{0} està connectat des de {1}",
     "UserPasswordChangedWithName": "La contrasenya ha estat canviada per a l'usuari {0}",
     "UserPasswordChangedWithName": "La contrasenya ha estat canviada per a l'usuari {0}",
-    "UserPolicyUpdatedWithName": "User policy has been updated for {0}",
+    "UserPolicyUpdatedWithName": "La política d'usuari s'ha actualitzat per {0}",
     "UserStartedPlayingItemWithValues": "{0} ha començat a reproduir {1}",
     "UserStartedPlayingItemWithValues": "{0} ha començat a reproduir {1}",
     "UserStoppedPlayingItemWithValues": "{0} ha parat de reproduir {1}",
     "UserStoppedPlayingItemWithValues": "{0} ha parat de reproduir {1}",
-    "ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
+    "ValueHasBeenAddedToLibrary": "{0} ha sigut afegit a la teva llibreria",
     "ValueSpecialEpisodeName": "Especial - {0}",
     "ValueSpecialEpisodeName": "Especial - {0}",
     "VersionNumber": "Versió {0}"
     "VersionNumber": "Versió {0}"
 }
 }

+ 13 - 0
Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj

@@ -4,6 +4,7 @@
     <TargetFramework>netstandard2.1</TargetFramework>
     <TargetFramework>netstandard2.1</TargetFramework>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>
   <ItemGroup>
@@ -22,4 +23,16 @@
     <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
     <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
   </ItemGroup>
   </ItemGroup>
 
 
+  <!-- Code analysers-->
+  <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
+    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.7" PrivateAssets="All" />
+    <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+    <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+    <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
+  </ItemGroup>
+
+  <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+    <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+
 </Project>
 </Project>

+ 9 - 0
Jellyfin.Drawing.Skia/PercentPlayedDrawer.cs

@@ -4,10 +4,19 @@ using SkiaSharp;
 
 
 namespace Jellyfin.Drawing.Skia
 namespace Jellyfin.Drawing.Skia
 {
 {
+    /// <summary>
+    /// Static helper class used to draw percentage-played indicators on images.
+    /// </summary>
     public static class PercentPlayedDrawer
     public static class PercentPlayedDrawer
     {
     {
         private const int IndicatorHeight = 8;
         private const int IndicatorHeight = 8;
 
 
+        /// <summary>
+        /// Draw a percentage played indicator on a canvas.
+        /// </summary>
+        /// <param name="canvas">The canvas to draw the indicator on.</param>
+        /// <param name="imageSize">The size of the image being drawn on.</param>
+        /// <param name="percent">The percentage played to display with the indicator.</param>
         public static void Process(SKCanvas canvas, ImageDimensions imageSize, double percent)
         public static void Process(SKCanvas canvas, ImageDimensions imageSize, double percent)
         {
         {
             using (var paint = new SKPaint())
             using (var paint = new SKPaint())

+ 13 - 2
Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs

@@ -3,10 +3,21 @@ using SkiaSharp;
 
 
 namespace Jellyfin.Drawing.Skia
 namespace Jellyfin.Drawing.Skia
 {
 {
+    /// <summary>
+    /// Static helper class for drawing 'played' indicators.
+    /// </summary>
     public static class PlayedIndicatorDrawer
     public static class PlayedIndicatorDrawer
     {
     {
         private const int OffsetFromTopRightCorner = 38;
         private const int OffsetFromTopRightCorner = 38;
 
 
+        /// <summary>
+        /// Draw a 'played' indicator in the top right corner of a canvas.
+        /// </summary>
+        /// <param name="canvas">The canvas to draw the indicator on.</param>
+        /// <param name="imageSize">
+        /// The dimensions of the image to draw the indicator on. The width is used to determine the x-position of the
+        /// indicator.
+        /// </param>
         public static void DrawPlayedIndicator(SKCanvas canvas, ImageDimensions imageSize)
         public static void DrawPlayedIndicator(SKCanvas canvas, ImageDimensions imageSize)
         {
         {
             var x = imageSize.Width - OffsetFromTopRightCorner;
             var x = imageSize.Width - OffsetFromTopRightCorner;
@@ -26,10 +37,10 @@ namespace Jellyfin.Drawing.Skia
                 paint.TextSize = 30;
                 paint.TextSize = 30;
                 paint.IsAntialias = true;
                 paint.IsAntialias = true;
 
 
+                // or:
+                // var emojiChar = 0x1F680;
                 var text = "✔️";
                 var text = "✔️";
                 var emojiChar = StringUtilities.GetUnicodeCharacterCode(text, SKTextEncoding.Utf32);
                 var emojiChar = StringUtilities.GetUnicodeCharacterCode(text, SKTextEncoding.Utf32);
-                // or:
-                //var emojiChar = 0x1F680;
 
 
                 // ask the font manager for a font with that character
                 // ask the font manager for a font with that character
                 var fontManager = SKFontManager.Default;
                 var fontManager = SKFontManager.Default;

+ 8 - 8
Jellyfin.Drawing.Skia/SkiaCodecException.cs

@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
 using System.Globalization;
 using SkiaSharp;
 using SkiaSharp;
 
 
@@ -8,16 +9,10 @@ namespace Jellyfin.Drawing.Skia
     /// </summary>
     /// </summary>
     public class SkiaCodecException : SkiaException
     public class SkiaCodecException : SkiaException
     {
     {
-        /// <summary>
-        /// Returns the non-successfull codec result returned by Skia.
-        /// </summary>
-        /// <value>The non-successfull codec result returned by Skia.</value>
-        public SKCodecResult CodecResult { get; }
-
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="SkiaCodecException" /> class.
         /// Initializes a new instance of the <see cref="SkiaCodecException" /> class.
         /// </summary>
         /// </summary>
-        /// <param name="result">The non-successfull codec result returned by Skia.</param>
+        /// <param name="result">The non-successful codec result returned by Skia.</param>
         public SkiaCodecException(SKCodecResult result) : base()
         public SkiaCodecException(SKCodecResult result) : base()
         {
         {
             CodecResult = result;
             CodecResult = result;
@@ -27,7 +22,7 @@ namespace Jellyfin.Drawing.Skia
         /// Initializes a new instance of the <see cref="SkiaCodecException" /> class
         /// Initializes a new instance of the <see cref="SkiaCodecException" /> class
         /// with a specified error message.
         /// with a specified error message.
         /// </summary>
         /// </summary>
-        /// <param name="result">The non-successfull codec result returned by Skia.</param>
+        /// <param name="result">The non-successful codec result returned by Skia.</param>
         /// <param name="message">The message that describes the error.</param>
         /// <param name="message">The message that describes the error.</param>
         public SkiaCodecException(SKCodecResult result, string message)
         public SkiaCodecException(SKCodecResult result, string message)
             : base(message)
             : base(message)
@@ -35,6 +30,11 @@ namespace Jellyfin.Drawing.Skia
             CodecResult = result;
             CodecResult = result;
         }
         }
 
 
+        /// <summary>
+        /// Gets the non-successful codec result returned by Skia.
+        /// </summary>
+        public SKCodecResult CodecResult { get; }
+
         /// <inheritdoc />
         /// <inheritdoc />
         public override string ToString()
         public override string ToString()
             => string.Format(
             => string.Format(

+ 36 - 13
Jellyfin.Drawing.Skia/SkiaEncoder.cs

@@ -13,6 +13,9 @@ using static Jellyfin.Drawing.Skia.SkiaHelper;
 
 
 namespace Jellyfin.Drawing.Skia
 namespace Jellyfin.Drawing.Skia
 {
 {
+    /// <summary>
+    /// Image encoder that uses <see cref="SkiaSharp"/> to manipulate images.
+    /// </summary>
     public class SkiaEncoder : IImageEncoder
     public class SkiaEncoder : IImageEncoder
     {
     {
         private readonly ILogger _logger;
         private readonly ILogger _logger;
@@ -22,6 +25,12 @@ namespace Jellyfin.Drawing.Skia
         private static readonly HashSet<string> _transparentImageTypes
         private static readonly HashSet<string> _transparentImageTypes
             = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
             = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" };
 
 
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SkiaEncoder"/> class.
+        /// </summary>
+        /// <param name="logger">The application logger.</param>
+        /// <param name="appPaths">The application paths.</param>
+        /// <param name="localizationManager">The application localization manager.</param>
         public SkiaEncoder(
         public SkiaEncoder(
             ILogger<SkiaEncoder> logger,
             ILogger<SkiaEncoder> logger,
             IApplicationPaths appPaths,
             IApplicationPaths appPaths,
@@ -32,12 +41,16 @@ namespace Jellyfin.Drawing.Skia
             _localizationManager = localizationManager;
             _localizationManager = localizationManager;
         }
         }
 
 
+        /// <inheritdoc/>
         public string Name => "Skia";
         public string Name => "Skia";
 
 
+        /// <inheritdoc/>
         public bool SupportsImageCollageCreation => true;
         public bool SupportsImageCollageCreation => true;
 
 
+        /// <inheritdoc/>
         public bool SupportsImageEncoding => true;
         public bool SupportsImageEncoding => true;
 
 
+        /// <inheritdoc/>
         public IReadOnlyCollection<string> SupportedInputFormats =>
         public IReadOnlyCollection<string> SupportedInputFormats =>
             new HashSet<string>(StringComparer.OrdinalIgnoreCase)
             new HashSet<string>(StringComparer.OrdinalIgnoreCase)
             {
             {
@@ -65,11 +78,12 @@ namespace Jellyfin.Drawing.Skia
                 "arw"
                 "arw"
             };
             };
 
 
+        /// <inheritdoc/>
         public IReadOnlyCollection<ImageFormat> SupportedOutputFormats
         public IReadOnlyCollection<ImageFormat> SupportedOutputFormats
             => new HashSet<ImageFormat>() { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png };
             => new HashSet<ImageFormat>() { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png };
 
 
         /// <summary>
         /// <summary>
-        /// Test to determine if the native lib is available
+        /// Test to determine if the native lib is available.
         /// </summary>
         /// </summary>
         public static void TestSkia()
         public static void TestSkia()
         {
         {
@@ -80,6 +94,11 @@ namespace Jellyfin.Drawing.Skia
         private static bool IsTransparent(SKColor color)
         private static bool IsTransparent(SKColor color)
             => (color.Red == 255 && color.Green == 255 && color.Blue == 255) || color.Alpha == 0;
             => (color.Red == 255 && color.Green == 255 && color.Blue == 255) || color.Alpha == 0;
 
 
+        /// <summary>
+        /// Convert a <see cref="ImageFormat"/> to a <see cref="SKEncodedImageFormat"/>.
+        /// </summary>
+        /// <param name="selectedFormat">The format to convert.</param>
+        /// <returns>The converted format.</returns>
         public static SKEncodedImageFormat GetImageFormat(ImageFormat selectedFormat)
         public static SKEncodedImageFormat GetImageFormat(ImageFormat selectedFormat)
         {
         {
             switch (selectedFormat)
             switch (selectedFormat)
@@ -186,6 +205,9 @@ namespace Jellyfin.Drawing.Skia
         }
         }
 
 
         /// <inheritdoc />
         /// <inheritdoc />
+        /// <exception cref="ArgumentNullException">The path is null.</exception>
+        /// <exception cref="FileNotFoundException">The path is not valid.</exception>
+        /// <exception cref="SkiaCodecException">The file at the specified path could not be used to generate a codec.</exception>
         public ImageDimensions GetImageSize(string path)
         public ImageDimensions GetImageSize(string path)
         {
         {
             if (path == null)
             if (path == null)
@@ -269,6 +291,14 @@ namespace Jellyfin.Drawing.Skia
             }
             }
         }
         }
 
 
+        /// <summary>
+        /// Decode an image.
+        /// </summary>
+        /// <param name="path">The filepath of the image to decode.</param>
+        /// <param name="forceCleanBitmap">Whether to force clean the bitmap.</param>
+        /// <param name="orientation">The orientation of the image.</param>
+        /// <param name="origin">The detected origin of the image.</param>
+        /// <returns>The resulting bitmap of the image.</returns>
         internal SKBitmap Decode(string path, bool forceCleanBitmap, ImageOrientation? orientation, out SKEncodedOrigin origin)
         internal SKBitmap Decode(string path, bool forceCleanBitmap, ImageOrientation? orientation, out SKEncodedOrigin origin)
         {
         {
             if (!File.Exists(path))
             if (!File.Exists(path))
@@ -358,16 +388,6 @@ namespace Jellyfin.Drawing.Skia
 
 
         private SKBitmap OrientImage(SKBitmap bitmap, SKEncodedOrigin origin)
         private SKBitmap OrientImage(SKBitmap bitmap, SKEncodedOrigin origin)
         {
         {
-            //var transformations = {
-            //    2: { rotate: 0, flip: true},
-            //    3: { rotate: 180, flip: false},
-            //    4: { rotate: 180, flip: true},
-            //    5: { rotate: 90, flip: true},
-            //    6: { rotate: 90, flip: false},
-            //    7: { rotate: 270, flip: true},
-            //    8: { rotate: 270, flip: false},
-            //}
-
             switch (origin)
             switch (origin)
             {
             {
                 case SKEncodedOrigin.TopRight:
                 case SKEncodedOrigin.TopRight:
@@ -497,6 +517,7 @@ namespace Jellyfin.Drawing.Skia
             }
             }
         }
         }
 
 
+        /// <inheritdoc/>
         public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat)
         public string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat selectedOutputFormat)
         {
         {
             if (string.IsNullOrWhiteSpace(inputPath))
             if (string.IsNullOrWhiteSpace(inputPath))
@@ -520,7 +541,7 @@ namespace Jellyfin.Drawing.Skia
             {
             {
                 if (bitmap == null)
                 if (bitmap == null)
                 {
                 {
-                    throw new ArgumentOutOfRangeException(string.Format("Skia unable to read image {0}", inputPath));
+                    throw new ArgumentOutOfRangeException($"Skia unable to read image {inputPath}");
                 }
                 }
 
 
                 var originalImageSize = new ImageDimensions(bitmap.Width, bitmap.Height);
                 var originalImageSize = new ImageDimensions(bitmap.Width, bitmap.Height);
@@ -556,7 +577,7 @@ namespace Jellyfin.Drawing.Skia
                     }
                     }
 
 
                     // create bitmap to use for canvas drawing used to draw into bitmap
                     // create bitmap to use for canvas drawing used to draw into bitmap
-                    using (var saveBitmap = new SKBitmap(width, height))//, bitmap.ColorType, bitmap.AlphaType))
+                    using (var saveBitmap = new SKBitmap(width, height)) // , bitmap.ColorType, bitmap.AlphaType))
                     using (var canvas = new SKCanvas(saveBitmap))
                     using (var canvas = new SKCanvas(saveBitmap))
                     {
                     {
                         // set background color if present
                         // set background color if present
@@ -609,9 +630,11 @@ namespace Jellyfin.Drawing.Skia
                     }
                     }
                 }
                 }
             }
             }
+
             return outputPath;
             return outputPath;
         }
         }
 
 
+        /// <inheritdoc/>
         public void CreateImageCollage(ImageCollageOptions options)
         public void CreateImageCollage(ImageCollageOptions options)
         {
         {
             double ratio = (double)options.Width / options.Height;
             double ratio = (double)options.Width / options.Height;

+ 16 - 3
Jellyfin.Drawing.Skia/SkiaException.cs

@@ -7,17 +7,30 @@ namespace Jellyfin.Drawing.Skia
     /// </summary>
     /// </summary>
     public class SkiaException : Exception
     public class SkiaException : Exception
     {
     {
-        /// <inheritdoc />
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SkiaException"/> class.
+        /// </summary>
         public SkiaException() : base()
         public SkiaException() : base()
         {
         {
         }
         }
 
 
-        /// <inheritdoc />
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SkiaException"/> class with a specified error message.
+        /// </summary>
+        /// <param name="message">The message that describes the error.</param>
         public SkiaException(string message) : base(message)
         public SkiaException(string message) : base(message)
         {
         {
         }
         }
 
 
-        /// <inheritdoc />
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SkiaException"/> class with a specified error message and a
+        /// reference to the inner exception that is the cause of this exception.
+        /// </summary>
+        /// <param name="message">The error message that explains the reason for the exception.</param>
+        /// <param name="innerException">
+        /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if
+        /// no inner exception is specified.
+        /// </param>
         public SkiaException(string message, Exception innerException)
         public SkiaException(string message, Exception innerException)
             : base(message, innerException)
             : base(message, innerException)
         {
         {

+ 27 - 0
Jellyfin.Drawing.Skia/StripCollageBuilder.cs

@@ -5,15 +5,27 @@ using SkiaSharp;
 
 
 namespace Jellyfin.Drawing.Skia
 namespace Jellyfin.Drawing.Skia
 {
 {
+    /// <summary>
+    /// Used to build collages of multiple images arranged in vertical strips.
+    /// </summary>
     public class StripCollageBuilder
     public class StripCollageBuilder
     {
     {
         private readonly SkiaEncoder _skiaEncoder;
         private readonly SkiaEncoder _skiaEncoder;
 
 
+        /// <summary>
+        /// Initializes a new instance of the <see cref="StripCollageBuilder"/> class.
+        /// </summary>
+        /// <param name="skiaEncoder">The encoder to use for building collages.</param>
         public StripCollageBuilder(SkiaEncoder skiaEncoder)
         public StripCollageBuilder(SkiaEncoder skiaEncoder)
         {
         {
             _skiaEncoder = skiaEncoder;
             _skiaEncoder = skiaEncoder;
         }
         }
 
 
+        /// <summary>
+        /// Check which format an image has been encoded with using its filename extension.
+        /// </summary>
+        /// <param name="outputPath">The path to the image to get the format for.</param>
+        /// <returns>The image format.</returns>
         public static SKEncodedImageFormat GetEncodedFormat(string outputPath)
         public static SKEncodedImageFormat GetEncodedFormat(string outputPath)
         {
         {
             if (outputPath == null)
             if (outputPath == null)
@@ -48,6 +60,13 @@ namespace Jellyfin.Drawing.Skia
             return SKEncodedImageFormat.Png;
             return SKEncodedImageFormat.Png;
         }
         }
 
 
+        /// <summary>
+        /// Create a square collage.
+        /// </summary>
+        /// <param name="paths">The paths of the images to use in the collage.</param>
+        /// <param name="outputPath">The path at which to place the resulting collage image.</param>
+        /// <param name="width">The desired width of the collage.</param>
+        /// <param name="height">The desired height of the collage.</param>
         public void BuildSquareCollage(string[] paths, string outputPath, int width, int height)
         public void BuildSquareCollage(string[] paths, string outputPath, int width, int height)
         {
         {
             using (var bitmap = BuildSquareCollageBitmap(paths, width, height))
             using (var bitmap = BuildSquareCollageBitmap(paths, width, height))
@@ -58,6 +77,13 @@ namespace Jellyfin.Drawing.Skia
             }
             }
         }
         }
 
 
+        /// <summary>
+        /// Create a thumb collage.
+        /// </summary>
+        /// <param name="paths">The paths of the images to use in the collage.</param>
+        /// <param name="outputPath">The path at which to place the resulting image.</param>
+        /// <param name="width">The desired width of the collage.</param>
+        /// <param name="height">The desired height of the collage.</param>
         public void BuildThumbCollage(string[] paths, string outputPath, int width, int height)
         public void BuildThumbCollage(string[] paths, string outputPath, int width, int height)
         {
         {
             using (var bitmap = BuildThumbCollageBitmap(paths, width, height))
             using (var bitmap = BuildThumbCollageBitmap(paths, width, height))
@@ -98,6 +124,7 @@ namespace Jellyfin.Drawing.Skia
                         using (var resizeBitmap = new SKBitmap(iWidth, iHeight, currentBitmap.ColorType, currentBitmap.AlphaType))
                         using (var resizeBitmap = new SKBitmap(iWidth, iHeight, currentBitmap.ColorType, currentBitmap.AlphaType))
                         {
                         {
                             currentBitmap.ScalePixels(resizeBitmap, SKFilterQuality.High);
                             currentBitmap.ScalePixels(resizeBitmap, SKFilterQuality.High);
+
                             // crop image
                             // crop image
                             int ix = (int)Math.Abs((iWidth - iSlice) / 2);
                             int ix = (int)Math.Abs((iWidth - iSlice) / 2);
                             using (var image = SKImage.FromBitmap(resizeBitmap))
                             using (var image = SKImage.FromBitmap(resizeBitmap))

+ 17 - 0
Jellyfin.Drawing.Skia/UnplayedCountIndicator.cs

@@ -4,10 +4,25 @@ using SkiaSharp;
 
 
 namespace Jellyfin.Drawing.Skia
 namespace Jellyfin.Drawing.Skia
 {
 {
+    /// <summary>
+    /// Static helper class for drawing unplayed count indicators.
+    /// </summary>
     public static class UnplayedCountIndicator
     public static class UnplayedCountIndicator
     {
     {
+        /// <summary>
+        /// The x-offset used when drawing an unplayed count indicator.
+        /// </summary>
         private const int OffsetFromTopRightCorner = 38;
         private const int OffsetFromTopRightCorner = 38;
 
 
+        /// <summary>
+        /// Draw an unplayed count indicator in the top right corner of a canvas.
+        /// </summary>
+        /// <param name="canvas">The canvas to draw the indicator on.</param>
+        /// <param name="imageSize">
+        /// The dimensions of the image to draw the indicator on. The width is used to determine the x-position of the
+        /// indicator.
+        /// </param>
+        /// <param name="count">The number to draw in the indicator.</param>
         public static void DrawUnplayedCountIndicator(SKCanvas canvas, ImageDimensions imageSize, int count)
         public static void DrawUnplayedCountIndicator(SKCanvas canvas, ImageDimensions imageSize, int count)
         {
         {
             var x = imageSize.Width - OffsetFromTopRightCorner;
             var x = imageSize.Width - OffsetFromTopRightCorner;
@@ -19,6 +34,7 @@ namespace Jellyfin.Drawing.Skia
                 paint.Style = SKPaintStyle.Fill;
                 paint.Style = SKPaintStyle.Fill;
                 canvas.DrawCircle((float)x, OffsetFromTopRightCorner, 20, paint);
                 canvas.DrawCircle((float)x, OffsetFromTopRightCorner, 20, paint);
             }
             }
+
             using (var paint = new SKPaint())
             using (var paint = new SKPaint())
             {
             {
                 paint.Color = new SKColor(255, 255, 255, 255);
                 paint.Color = new SKColor(255, 255, 255, 255);
@@ -33,6 +49,7 @@ namespace Jellyfin.Drawing.Skia
                 {
                 {
                     x -= 7;
                     x -= 7;
                 }
                 }
+
                 if (text.Length == 2)
                 if (text.Length == 2)
                 {
                 {
                     x -= 13;
                     x -= 13;

+ 6 - 1
Jellyfin.Server/Program.cs

@@ -7,6 +7,7 @@ using System.Net;
 using System.Net.Security;
 using System.Net.Security;
 using System.Reflection;
 using System.Reflection;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
+using System.Text;
 using System.Text.RegularExpressions;
 using System.Text.RegularExpressions;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
@@ -145,6 +146,10 @@ namespace Jellyfin.Server
 
 
             ApplicationHost.LogEnvironmentInfo(_logger, appPaths);
             ApplicationHost.LogEnvironmentInfo(_logger, appPaths);
 
 
+            // Make sure we have all the code pages we can get
+            // Ref: https://docs.microsoft.com/en-us/dotnet/api/system.text.codepagesencodingprovider.instance?view=netcore-3.0#remarks
+            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
+
             // Increase the max http request limit
             // Increase the max http request limit
             // The default connection limit is 10 for ASP.NET hosted applications and 2 for all others.
             // The default connection limit is 10 for ASP.NET hosted applications and 2 for all others.
             ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit);
             ServicePointManager.DefaultConnectionLimit = Math.Max(96, ServicePointManager.DefaultConnectionLimit);
@@ -456,9 +461,9 @@ namespace Jellyfin.Server
 
 
             return new ConfigurationBuilder()
             return new ConfigurationBuilder()
                 .SetBasePath(appPaths.ConfigurationDirectoryPath)
                 .SetBasePath(appPaths.ConfigurationDirectoryPath)
+                .AddInMemoryCollection(ConfigurationOptions.Configuration)
                 .AddJsonFile("logging.json", false, true)
                 .AddJsonFile("logging.json", false, true)
                 .AddEnvironmentVariables("JELLYFIN_")
                 .AddEnvironmentVariables("JELLYFIN_")
-                .AddInMemoryCollection(ConfigurationOptions.Configuration)
                 .Build();
                 .Build();
         }
         }
 
 

+ 5 - 10
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -63,8 +63,6 @@ namespace MediaBrowser.Api.Playback
 
 
         protected IDeviceManager DeviceManager { get; private set; }
         protected IDeviceManager DeviceManager { get; private set; }
 
 
-        protected ISubtitleEncoder SubtitleEncoder { get; private set; }
-
         protected IMediaSourceManager MediaSourceManager { get; private set; }
         protected IMediaSourceManager MediaSourceManager { get; private set; }
 
 
         protected IJsonSerializer JsonSerializer { get; private set; }
         protected IJsonSerializer JsonSerializer { get; private set; }
@@ -92,11 +90,11 @@ namespace MediaBrowser.Api.Playback
             IMediaEncoder mediaEncoder,
             IMediaEncoder mediaEncoder,
             IFileSystem fileSystem,
             IFileSystem fileSystem,
             IDlnaManager dlnaManager,
             IDlnaManager dlnaManager,
-            ISubtitleEncoder subtitleEncoder,
             IDeviceManager deviceManager,
             IDeviceManager deviceManager,
             IMediaSourceManager mediaSourceManager,
             IMediaSourceManager mediaSourceManager,
             IJsonSerializer jsonSerializer,
             IJsonSerializer jsonSerializer,
-            IAuthorizationContext authorizationContext)
+            IAuthorizationContext authorizationContext,
+            EncodingHelper encodingHelper)
             : base(logger, serverConfigurationManager, httpResultFactory)
             : base(logger, serverConfigurationManager, httpResultFactory)
         {
         {
             UserManager = userManager;
             UserManager = userManager;
@@ -105,13 +103,12 @@ namespace MediaBrowser.Api.Playback
             MediaEncoder = mediaEncoder;
             MediaEncoder = mediaEncoder;
             FileSystem = fileSystem;
             FileSystem = fileSystem;
             DlnaManager = dlnaManager;
             DlnaManager = dlnaManager;
-            SubtitleEncoder = subtitleEncoder;
             DeviceManager = deviceManager;
             DeviceManager = deviceManager;
             MediaSourceManager = mediaSourceManager;
             MediaSourceManager = mediaSourceManager;
             JsonSerializer = jsonSerializer;
             JsonSerializer = jsonSerializer;
             AuthorizationContext = authorizationContext;
             AuthorizationContext = authorizationContext;
 
 
-            EncodingHelper = new EncodingHelper(MediaEncoder, FileSystem, SubtitleEncoder);
+            EncodingHelper = encodingHelper;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -148,8 +145,6 @@ namespace MediaBrowser.Api.Playback
             return Path.Combine(folder, filename + ext);
             return Path.Combine(folder, filename + ext);
         }
         }
 
 
-        protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
         protected virtual string GetDefaultEncoderPreset()
         protected virtual string GetDefaultEncoderPreset()
         {
         {
             return "superfast";
             return "superfast";
@@ -764,13 +759,13 @@ namespace MediaBrowser.Api.Playback
 
 
                 if (mediaSource == null)
                 if (mediaSource == null)
                 {
                 {
-                    var mediaSources = (await MediaSourceManager.GetPlayackMediaSources(LibraryManager.GetItemById(request.Id), null, false, false, cancellationToken).ConfigureAwait(false)).ToList();
+                    var mediaSources = await MediaSourceManager.GetPlayackMediaSources(LibraryManager.GetItemById(request.Id), null, false, false, cancellationToken).ConfigureAwait(false);
 
 
                     mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
                     mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
                        ? mediaSources[0]
                        ? mediaSources[0]
                        : mediaSources.Find(i => string.Equals(i.Id, request.MediaSourceId));
                        : mediaSources.Find(i => string.Equals(i.Id, request.MediaSourceId));
 
 
-                    if (mediaSource == null && request.MediaSourceId.Equals(request.Id))
+                    if (mediaSource == null && Guid.Parse(request.MediaSourceId) == request.Id)
                     {
                     {
                         mediaSource = mediaSources[0];
                         mediaSource = mediaSources[0];
                     }
                     }

+ 17 - 17
MediaBrowser.Api/Playback/Hls/BaseHlsService.cs

@@ -34,26 +34,26 @@ namespace MediaBrowser.Api.Playback.Hls
             IMediaEncoder mediaEncoder,
             IMediaEncoder mediaEncoder,
             IFileSystem fileSystem,
             IFileSystem fileSystem,
             IDlnaManager dlnaManager,
             IDlnaManager dlnaManager,
-            ISubtitleEncoder subtitleEncoder,
             IDeviceManager deviceManager,
             IDeviceManager deviceManager,
             IMediaSourceManager mediaSourceManager,
             IMediaSourceManager mediaSourceManager,
             IJsonSerializer jsonSerializer,
             IJsonSerializer jsonSerializer,
-            IAuthorizationContext authorizationContext)
-                : base(
-                    logger,
-                    serverConfigurationManager,
-                    httpResultFactory,
-                    userManager,
-                    libraryManager,
-                    isoManager,
-                    mediaEncoder,
-                    fileSystem,
-                    dlnaManager,
-                    subtitleEncoder,
-                    deviceManager,
-                    mediaSourceManager,
-                    jsonSerializer,
-                    authorizationContext)
+            IAuthorizationContext authorizationContext,
+            EncodingHelper encodingHelper)
+            : base(
+                logger,
+                serverConfigurationManager,
+                httpResultFactory,
+                userManager,
+                libraryManager,
+                isoManager,
+                mediaEncoder,
+                fileSystem,
+                dlnaManager,
+                deviceManager,
+                mediaSourceManager,
+                jsonSerializer,
+                authorizationContext,
+                encodingHelper)
         {
         {
         }
         }
 
 

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

@@ -104,12 +104,12 @@ namespace MediaBrowser.Api.Playback.Hls
             IMediaEncoder mediaEncoder,
             IMediaEncoder mediaEncoder,
             IFileSystem fileSystem,
             IFileSystem fileSystem,
             IDlnaManager dlnaManager,
             IDlnaManager dlnaManager,
-            ISubtitleEncoder subtitleEncoder,
             IDeviceManager deviceManager,
             IDeviceManager deviceManager,
             IMediaSourceManager mediaSourceManager,
             IMediaSourceManager mediaSourceManager,
             IJsonSerializer jsonSerializer,
             IJsonSerializer jsonSerializer,
             IAuthorizationContext authorizationContext,
             IAuthorizationContext authorizationContext,
-            INetworkManager networkManager)
+            INetworkManager networkManager,
+            EncodingHelper encodingHelper)
             : base(
             : base(
                 logger,
                 logger,
                 serverConfigurationManager,
                 serverConfigurationManager,
@@ -120,11 +120,11 @@ namespace MediaBrowser.Api.Playback.Hls
                 mediaEncoder,
                 mediaEncoder,
                 fileSystem,
                 fileSystem,
                 dlnaManager,
                 dlnaManager,
-                subtitleEncoder,
                 deviceManager,
                 deviceManager,
                 mediaSourceManager,
                 mediaSourceManager,
                 jsonSerializer,
                 jsonSerializer,
-                authorizationContext)
+                authorizationContext,
+                encodingHelper)
         {
         {
             NetworkManager = networkManager;
             NetworkManager = networkManager;
         }
         }

+ 33 - 33
MediaBrowser.Api/Playback/Hls/VideoHlsService.cs

@@ -27,6 +27,39 @@ namespace MediaBrowser.Api.Playback.Hls
     [Authenticated]
     [Authenticated]
     public class VideoHlsService : BaseHlsService
     public class VideoHlsService : BaseHlsService
     {
     {
+        public VideoHlsService(
+            ILogger<VideoHlsService> logger,
+            IServerConfigurationManager serverConfigurationManager,
+            IHttpResultFactory httpResultFactory,
+            IUserManager userManager,
+            ILibraryManager libraryManager,
+            IIsoManager isoManager,
+            IMediaEncoder mediaEncoder,
+            IFileSystem fileSystem,
+            IDlnaManager dlnaManager,
+            IDeviceManager deviceManager,
+            IMediaSourceManager mediaSourceManager,
+            IJsonSerializer jsonSerializer,
+            IAuthorizationContext authorizationContext,
+            EncodingHelper encodingHelper)
+            : base(
+                logger,
+                serverConfigurationManager,
+                httpResultFactory,
+                userManager,
+                libraryManager,
+                isoManager,
+                mediaEncoder,
+                fileSystem,
+                dlnaManager,
+                deviceManager,
+                mediaSourceManager,
+                jsonSerializer,
+                authorizationContext,
+                encodingHelper)
+        {
+        }
+
         public Task<object> Get(GetLiveHlsStream request)
         public Task<object> Get(GetLiveHlsStream request)
         {
         {
             return ProcessRequestAsync(request, true);
             return ProcessRequestAsync(request, true);
@@ -136,38 +169,5 @@ namespace MediaBrowser.Api.Playback.Hls
 
 
             return args;
             return args;
         }
         }
-
-        public VideoHlsService(
-            ILogger<VideoHlsService> logger,
-            IServerConfigurationManager serverConfigurationManager,
-            IHttpResultFactory httpResultFactory,
-            IUserManager userManager,
-            ILibraryManager libraryManager,
-            IIsoManager isoManager,
-            IMediaEncoder mediaEncoder,
-            IFileSystem fileSystem,
-            IDlnaManager dlnaManager,
-            ISubtitleEncoder subtitleEncoder,
-            IDeviceManager deviceManager,
-            IMediaSourceManager mediaSourceManager,
-            IJsonSerializer jsonSerializer,
-            IAuthorizationContext authorizationContext)
-                : base(
-                    logger,
-                    serverConfigurationManager,
-                    httpResultFactory,
-                    userManager,
-                    libraryManager,
-                    isoManager,
-                    mediaEncoder,
-                    fileSystem,
-                    dlnaManager,
-                    subtitleEncoder,
-                    deviceManager,
-                    mediaSourceManager,
-                    jsonSerializer,
-                    authorizationContext)
-        {
-        }
     }
     }
 }
 }

+ 4 - 4
MediaBrowser.Api/Playback/Progressive/AudioService.cs

@@ -43,11 +43,11 @@ namespace MediaBrowser.Api.Playback.Progressive
             IMediaEncoder mediaEncoder,
             IMediaEncoder mediaEncoder,
             IFileSystem fileSystem,
             IFileSystem fileSystem,
             IDlnaManager dlnaManager,
             IDlnaManager dlnaManager,
-            ISubtitleEncoder subtitleEncoder,
             IDeviceManager deviceManager,
             IDeviceManager deviceManager,
             IMediaSourceManager mediaSourceManager,
             IMediaSourceManager mediaSourceManager,
             IJsonSerializer jsonSerializer,
             IJsonSerializer jsonSerializer,
-            IAuthorizationContext authorizationContext)
+            IAuthorizationContext authorizationContext,
+            EncodingHelper encodingHelper)
             : base(
             : base(
                 logger,
                 logger,
                 serverConfigurationManager,
                 serverConfigurationManager,
@@ -59,11 +59,11 @@ namespace MediaBrowser.Api.Playback.Progressive
                 mediaEncoder,
                 mediaEncoder,
                 fileSystem,
                 fileSystem,
                 dlnaManager,
                 dlnaManager,
-                subtitleEncoder,
                 deviceManager,
                 deviceManager,
                 mediaSourceManager,
                 mediaSourceManager,
                 jsonSerializer,
                 jsonSerializer,
-                authorizationContext)
+                authorizationContext,
+                encodingHelper)
         {
         {
         }
         }
 
 

+ 4 - 4
MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs

@@ -38,11 +38,11 @@ namespace MediaBrowser.Api.Playback.Progressive
             IMediaEncoder mediaEncoder,
             IMediaEncoder mediaEncoder,
             IFileSystem fileSystem,
             IFileSystem fileSystem,
             IDlnaManager dlnaManager,
             IDlnaManager dlnaManager,
-            ISubtitleEncoder subtitleEncoder,
             IDeviceManager deviceManager,
             IDeviceManager deviceManager,
             IMediaSourceManager mediaSourceManager,
             IMediaSourceManager mediaSourceManager,
             IJsonSerializer jsonSerializer,
             IJsonSerializer jsonSerializer,
-            IAuthorizationContext authorizationContext)
+            IAuthorizationContext authorizationContext,
+            EncodingHelper encodingHelper)
             : base(
             : base(
                 logger,
                 logger,
                 serverConfigurationManager,
                 serverConfigurationManager,
@@ -53,11 +53,11 @@ namespace MediaBrowser.Api.Playback.Progressive
                 mediaEncoder,
                 mediaEncoder,
                 fileSystem,
                 fileSystem,
                 dlnaManager,
                 dlnaManager,
-                subtitleEncoder,
                 deviceManager,
                 deviceManager,
                 mediaSourceManager,
                 mediaSourceManager,
                 jsonSerializer,
                 jsonSerializer,
-                authorizationContext)
+                authorizationContext,
+                encodingHelper)
         {
         {
             HttpClient = httpClient;
             HttpClient = httpClient;
         }
         }

+ 4 - 4
MediaBrowser.Api/Playback/Progressive/VideoService.cs

@@ -80,11 +80,11 @@ namespace MediaBrowser.Api.Playback.Progressive
             IMediaEncoder mediaEncoder,
             IMediaEncoder mediaEncoder,
             IFileSystem fileSystem,
             IFileSystem fileSystem,
             IDlnaManager dlnaManager,
             IDlnaManager dlnaManager,
-            ISubtitleEncoder subtitleEncoder,
             IDeviceManager deviceManager,
             IDeviceManager deviceManager,
             IMediaSourceManager mediaSourceManager,
             IMediaSourceManager mediaSourceManager,
             IJsonSerializer jsonSerializer,
             IJsonSerializer jsonSerializer,
-            IAuthorizationContext authorizationContext)
+            IAuthorizationContext authorizationContext,
+            EncodingHelper encodingHelper)
             : base(
             : base(
                 logger,
                 logger,
                 serverConfigurationManager,
                 serverConfigurationManager,
@@ -96,11 +96,11 @@ namespace MediaBrowser.Api.Playback.Progressive
                 mediaEncoder,
                 mediaEncoder,
                 fileSystem,
                 fileSystem,
                 dlnaManager,
                 dlnaManager,
-                subtitleEncoder,
                 deviceManager,
                 deviceManager,
                 mediaSourceManager,
                 mediaSourceManager,
                 jsonSerializer,
                 jsonSerializer,
-                authorizationContext)
+                authorizationContext,
+                encodingHelper)
         {
         {
         }
         }
 
 

+ 10 - 9
MediaBrowser.Api/Playback/UniversalAudioService.cs

@@ -9,7 +9,6 @@ using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Net;
@@ -75,6 +74,9 @@ namespace MediaBrowser.Api.Playback
     [Authenticated]
     [Authenticated]
     public class UniversalAudioService : BaseApiService
     public class UniversalAudioService : BaseApiService
     {
     {
+        private readonly ILoggerFactory _loggerFactory;
+        private readonly EncodingHelper _encodingHelper;
+
         public UniversalAudioService(
         public UniversalAudioService(
             ILogger<UniversalAudioService> logger,
             ILogger<UniversalAudioService> logger,
             IServerConfigurationManager serverConfigurationManager,
             IServerConfigurationManager serverConfigurationManager,
@@ -87,11 +89,11 @@ namespace MediaBrowser.Api.Playback
             IFileSystem fileSystem,
             IFileSystem fileSystem,
             IDlnaManager dlnaManager,
             IDlnaManager dlnaManager,
             IDeviceManager deviceManager,
             IDeviceManager deviceManager,
-            ISubtitleEncoder subtitleEncoder,
             IMediaSourceManager mediaSourceManager,
             IMediaSourceManager mediaSourceManager,
             IJsonSerializer jsonSerializer,
             IJsonSerializer jsonSerializer,
             IAuthorizationContext authorizationContext,
             IAuthorizationContext authorizationContext,
-            INetworkManager networkManager)
+            INetworkManager networkManager,
+            EncodingHelper encodingHelper)
             : base(logger, serverConfigurationManager, httpResultFactory)
             : base(logger, serverConfigurationManager, httpResultFactory)
         {
         {
             HttpClient = httpClient;
             HttpClient = httpClient;
@@ -102,11 +104,11 @@ namespace MediaBrowser.Api.Playback
             FileSystem = fileSystem;
             FileSystem = fileSystem;
             DlnaManager = dlnaManager;
             DlnaManager = dlnaManager;
             DeviceManager = deviceManager;
             DeviceManager = deviceManager;
-            SubtitleEncoder = subtitleEncoder;
             MediaSourceManager = mediaSourceManager;
             MediaSourceManager = mediaSourceManager;
             JsonSerializer = jsonSerializer;
             JsonSerializer = jsonSerializer;
             AuthorizationContext = authorizationContext;
             AuthorizationContext = authorizationContext;
             NetworkManager = networkManager;
             NetworkManager = networkManager;
+            _encodingHelper = encodingHelper;
         }
         }
 
 
         protected IHttpClient HttpClient { get; private set; }
         protected IHttpClient HttpClient { get; private set; }
@@ -117,7 +119,6 @@ namespace MediaBrowser.Api.Playback
         protected IFileSystem FileSystem { get; private set; }
         protected IFileSystem FileSystem { get; private set; }
         protected IDlnaManager DlnaManager { get; private set; }
         protected IDlnaManager DlnaManager { get; private set; }
         protected IDeviceManager DeviceManager { get; private set; }
         protected IDeviceManager DeviceManager { get; private set; }
-        protected ISubtitleEncoder SubtitleEncoder { get; private set; }
         protected IMediaSourceManager MediaSourceManager { get; private set; }
         protected IMediaSourceManager MediaSourceManager { get; private set; }
         protected IJsonSerializer JsonSerializer { get; private set; }
         protected IJsonSerializer JsonSerializer { get; private set; }
         protected IAuthorizationContext AuthorizationContext { get; private set; }
         protected IAuthorizationContext AuthorizationContext { get; private set; }
@@ -287,12 +288,12 @@ namespace MediaBrowser.Api.Playback
                     MediaEncoder,
                     MediaEncoder,
                     FileSystem,
                     FileSystem,
                     DlnaManager,
                     DlnaManager,
-                    SubtitleEncoder,
                     DeviceManager,
                     DeviceManager,
                     MediaSourceManager,
                     MediaSourceManager,
                     JsonSerializer,
                     JsonSerializer,
                     AuthorizationContext,
                     AuthorizationContext,
-                    NetworkManager)
+                    NetworkManager,
+                    _encodingHelper)
                 {
                 {
                     Request = Request
                     Request = Request
                 };
                 };
@@ -337,11 +338,11 @@ namespace MediaBrowser.Api.Playback
                     MediaEncoder,
                     MediaEncoder,
                     FileSystem,
                     FileSystem,
                     DlnaManager,
                     DlnaManager,
-                    SubtitleEncoder,
                     DeviceManager,
                     DeviceManager,
                     MediaSourceManager,
                     MediaSourceManager,
                     JsonSerializer,
                     JsonSerializer,
-                    AuthorizationContext)
+                    AuthorizationContext,
+                    _encodingHelper)
                 {
                 {
                     Request = Request
                     Request = Request
                 };
                 };

+ 11 - 5
MediaBrowser.Controller/Drawing/IImageEncoder.cs

@@ -11,6 +11,7 @@ namespace MediaBrowser.Controller.Drawing
         /// </summary>
         /// </summary>
         /// <value>The supported input formats.</value>
         /// <value>The supported input formats.</value>
         IReadOnlyCollection<string> SupportedInputFormats { get; }
         IReadOnlyCollection<string> SupportedInputFormats { get; }
+
         /// <summary>
         /// <summary>
         /// Gets the supported output formats.
         /// Gets the supported output formats.
         /// </summary>
         /// </summary>
@@ -18,9 +19,9 @@ namespace MediaBrowser.Controller.Drawing
         IReadOnlyCollection<ImageFormat> SupportedOutputFormats { get; }
         IReadOnlyCollection<ImageFormat> SupportedOutputFormats { get; }
 
 
         /// <summary>
         /// <summary>
-        /// Gets the name.
+        /// Gets the display name for the encoder.
         /// </summary>
         /// </summary>
-        /// <value>The name.</value>
+        /// <value>The display name.</value>
         string Name { get; }
         string Name { get; }
 
 
         /// <summary>
         /// <summary>
@@ -35,17 +36,22 @@ namespace MediaBrowser.Controller.Drawing
         /// <value><c>true</c> if [supports image encoding]; otherwise, <c>false</c>.</value>
         /// <value><c>true</c> if [supports image encoding]; otherwise, <c>false</c>.</value>
         bool SupportsImageEncoding { get; }
         bool SupportsImageEncoding { get; }
 
 
+        /// <summary>
+        /// Get the dimensions of an image from the filesystem.
+        /// </summary>
+        /// <param name="path">The filepath of the image.</param>
+        /// <returns>The image dimensions.</returns>
         ImageDimensions GetImageSize(string path);
         ImageDimensions GetImageSize(string path);
 
 
         /// <summary>
         /// <summary>
-        /// Encodes the image.
+        /// Encode an image.
         /// </summary>
         /// </summary>
         string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat);
         string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat);
 
 
         /// <summary>
         /// <summary>
-        /// Creates the image collage.
+        /// Create an image collage.
         /// </summary>
         /// </summary>
-        /// <param name="options">The options.</param>
+        /// <param name="options">The options to use when creating the collage.</param>
         void CreateImageCollage(ImageCollageOptions options);
         void CreateImageCollage(ImageCollageOptions options);
     }
     }
 }
 }

+ 3 - 2
MediaBrowser.Controller/Entities/Video.cs

@@ -137,7 +137,7 @@ namespace MediaBrowser.Controller.Entities
         /// <value>The video3 D format.</value>
         /// <value>The video3 D format.</value>
         public Video3DFormat? Video3DFormat { get; set; }
         public Video3DFormat? Video3DFormat { get; set; }
 
 
-        public string[] GetPlayableStreamFileNames(IMediaEncoder mediaEncoder)
+        public string[] GetPlayableStreamFileNames()
         {
         {
             var videoType = VideoType;
             var videoType = VideoType;
 
 
@@ -153,7 +153,8 @@ namespace MediaBrowser.Controller.Entities
             {
             {
                 return Array.Empty<string>();
                 return Array.Empty<string>();
             }
             }
-            return mediaEncoder.GetPlayableStreamFileNames(Path, videoType);
+
+            throw new NotImplementedException();
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 36 - 0
MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs

@@ -0,0 +1,36 @@
+using Microsoft.Extensions.Configuration;
+
+namespace MediaBrowser.Controller.Extensions
+{
+    /// <summary>
+    /// Configuration extensions for <c>MediaBrowser.Controller</c>.
+    /// </summary>
+    public static class ConfigurationExtensions
+    {
+        /// <summary>
+        /// The key for the FFmpeg probe size option.
+        /// </summary>
+        public const string FfmpegProbeSizeKey = "FFmpeg:probesize";
+
+        /// <summary>
+        /// The key for the FFmpeg analyse duration option.
+        /// </summary>
+        public const string FfmpegAnalyzeDurationKey = "FFmpeg:analyzeduration";
+
+        /// <summary>
+        /// Retrieves the FFmpeg probe size from the <see cref="IConfiguration" />.
+        /// </summary>
+        /// <param name="configuration">This configuration.</param>
+        /// <returns>The FFmpeg probe size option.</returns>
+        public static string GetFFmpegProbeSize(this IConfiguration configuration)
+            => configuration[FfmpegProbeSizeKey];
+
+        /// <summary>
+        /// Retrieves the FFmpeg analyse duration from the <see cref="IConfiguration" />.
+        /// </summary>
+        /// <param name="configuration">This configuration.</param>
+        /// <returns>The FFmpeg analyse duration option.</returns>
+        public static string GetFFmpegAnalyzeDuration(this IConfiguration configuration)
+            => configuration[FfmpegAnalyzeDurationKey];
+    }
+}

+ 4 - 0
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -7,6 +7,10 @@
     <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
     <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
   </PropertyGroup>
   </PropertyGroup>
 
 
+  <ItemGroup>
+    <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.0.0" />
+  </ItemGroup>
+
   <ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
     <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
     <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
     <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />

+ 23 - 9
MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs

@@ -6,12 +6,14 @@ using System.Linq;
 using System.Text;
 using System.Text;
 using System.Threading;
 using System.Threading;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Extensions;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.MediaInfo;
+using Microsoft.Extensions.Configuration;
 
 
 namespace MediaBrowser.Controller.MediaEncoding
 namespace MediaBrowser.Controller.MediaEncoding
 {
 {
@@ -22,6 +24,7 @@ namespace MediaBrowser.Controller.MediaEncoding
         private readonly IMediaEncoder _mediaEncoder;
         private readonly IMediaEncoder _mediaEncoder;
         private readonly IFileSystem _fileSystem;
         private readonly IFileSystem _fileSystem;
         private readonly ISubtitleEncoder _subtitleEncoder;
         private readonly ISubtitleEncoder _subtitleEncoder;
+        private readonly IConfiguration _configuration;
 
 
         private static readonly string[] _videoProfiles = new[]
         private static readonly string[] _videoProfiles = new[]
         {
         {
@@ -34,11 +37,16 @@ namespace MediaBrowser.Controller.MediaEncoding
             "ConstrainedHigh"
             "ConstrainedHigh"
         };
         };
 
 
-        public EncodingHelper(IMediaEncoder mediaEncoder, IFileSystem fileSystem, ISubtitleEncoder subtitleEncoder)
+        public EncodingHelper(
+            IMediaEncoder mediaEncoder,
+            IFileSystem fileSystem,
+            ISubtitleEncoder subtitleEncoder,
+            IConfiguration configuration)
         {
         {
             _mediaEncoder = mediaEncoder;
             _mediaEncoder = mediaEncoder;
             _fileSystem = fileSystem;
             _fileSystem = fileSystem;
             _subtitleEncoder = subtitleEncoder;
             _subtitleEncoder = subtitleEncoder;
+            _configuration = configuration;
         }
         }
 
 
         public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
         public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
@@ -172,7 +180,7 @@ namespace MediaBrowser.Controller.MediaEncoding
             return string.Empty;
             return string.Empty;
         }
         }
 
 
-        public string GetInputFormat(string container)
+        public static string GetInputFormat(string container)
         {
         {
             if (string.IsNullOrEmpty(container))
             if (string.IsNullOrEmpty(container))
             {
             {
@@ -662,7 +670,11 @@ namespace MediaBrowser.Controller.MediaEncoding
 
 
                 if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
                 if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
                 {
                 {
-                    var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.SubtitleStream.Language, state.MediaSource.Protocol, CancellationToken.None).Result;
+                    var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(
+                        subtitlePath,
+                        state.SubtitleStream.Language,
+                        state.MediaSource.Protocol,
+                        CancellationToken.None).GetAwaiter().GetResult();
 
 
                     if (!string.IsNullOrEmpty(charenc))
                     if (!string.IsNullOrEmpty(charenc))
                     {
                     {
@@ -1948,7 +1960,7 @@ namespace MediaBrowser.Controller.MediaEncoding
                 // If transcoding from 10 bit, transform colour spaces too
                 // If transcoding from 10 bit, transform colour spaces too
                 if (!string.IsNullOrEmpty(videoStream.PixelFormat)
                 if (!string.IsNullOrEmpty(videoStream.PixelFormat)
                     && videoStream.PixelFormat.IndexOf("p10", StringComparison.OrdinalIgnoreCase) != -1
                     && videoStream.PixelFormat.IndexOf("p10", StringComparison.OrdinalIgnoreCase) != -1
-                    && string.Equals(outputVideoCodec,"libx264", StringComparison.OrdinalIgnoreCase))
+                    && string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
                 {
                 {
                     filters.Add("format=p010le");
                     filters.Add("format=p010le");
                     filters.Add("format=nv12");
                     filters.Add("format=nv12");
@@ -2011,7 +2023,9 @@ namespace MediaBrowser.Controller.MediaEncoding
 
 
             var output = string.Empty;
             var output = string.Empty;
 
 
-            if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
+            if (state.SubtitleStream != null
+                && state.SubtitleStream.IsTextSubtitleStream
+                && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
             {
             {
                 var subParam = GetTextSubtitleParam(state);
                 var subParam = GetTextSubtitleParam(state);
 
 
@@ -2100,11 +2114,11 @@ namespace MediaBrowser.Controller.MediaEncoding
             }
             }
         }
         }
 
 
-        public static string GetProbeSizeArgument(int numInputFiles)
-            => numInputFiles > 1 ? "-probesize 1G" : "";
+        public string GetProbeSizeArgument(int numInputFiles)
+            => numInputFiles > 1 ? "-probesize " + _configuration.GetFFmpegProbeSize() : string.Empty;
 
 
-        public static string GetAnalyzeDurationArgument(int numInputFiles)
-            => numInputFiles > 1 ? "-analyzeduration 200M" : "";
+        public string GetAnalyzeDurationArgument(int numInputFiles)
+            => numInputFiles > 1 ? "-analyzeduration " + _configuration.GetFFmpegAnalyzeDuration() : string.Empty;
 
 
         public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions)
         public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions)
         {
         {

+ 3 - 1
MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs

@@ -15,6 +15,9 @@ namespace MediaBrowser.Controller.MediaEncoding
     /// </summary>
     /// </summary>
     public interface IMediaEncoder : ITranscoderSupport
     public interface IMediaEncoder : ITranscoderSupport
     {
     {
+        /// <summary>
+        /// The location of the discovered FFmpeg tool.
+        /// </summary>
         FFmpegLocation EncoderLocation { get; }
         FFmpegLocation EncoderLocation { get; }
 
 
         /// <summary>
         /// <summary>
@@ -97,7 +100,6 @@ namespace MediaBrowser.Controller.MediaEncoding
         void UpdateEncoderPath(string path, string pathType);
         void UpdateEncoderPath(string path, string pathType);
         bool SupportsEncoder(string encoder);
         bool SupportsEncoder(string encoder);
 
 
-        string[] GetPlayableStreamFileNames(string path, VideoType videoType);
         IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, IIsoMount isoMount, uint? titleNumber);
         IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, IIsoMount isoMount, uint? titleNumber);
     }
     }
 }
 }

+ 93 - 98
MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs

@@ -3,13 +3,13 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.Globalization;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
+using System.Text.Json;
 using System.Text.RegularExpressions;
 using System.Text.RegularExpressions;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.MediaEncoding.Probing;
 using MediaBrowser.MediaEncoding.Probing;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Configuration;
@@ -19,9 +19,9 @@ using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.System;
 using MediaBrowser.Model.System;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Configuration;
 
 
 namespace MediaBrowser.MediaEncoding.Encoder
 namespace MediaBrowser.MediaEncoding.Encoder
 {
 {
@@ -31,55 +31,60 @@ namespace MediaBrowser.MediaEncoding.Encoder
     public class MediaEncoder : IMediaEncoder, IDisposable
     public class MediaEncoder : IMediaEncoder, IDisposable
     {
     {
         /// <summary>
         /// <summary>
-        /// Gets the encoder path.
+        /// The default image extraction timeout in milliseconds.
         /// </summary>
         /// </summary>
-        /// <value>The encoder path.</value>
-        public string EncoderPath => FFmpegPath;
-
-        /// <summary>
-        /// The location of the discovered FFmpeg tool.
-        /// </summary>
-        public FFmpegLocation EncoderLocation { get; private set; }
+        internal const int DefaultImageExtractionTimeout = 5000;
 
 
         private readonly ILogger _logger;
         private readonly ILogger _logger;
-        private readonly IJsonSerializer _jsonSerializer;
-        private string FFmpegPath;
-        private string FFprobePath;
-        protected readonly IServerConfigurationManager ConfigurationManager;
-        protected readonly IFileSystem FileSystem;
-        protected readonly Func<ISubtitleEncoder> SubtitleEncoder;
-        protected readonly Func<IMediaSourceManager> MediaSourceManager;
+        private readonly IServerConfigurationManager _configurationManager;
+        private readonly IFileSystem _fileSystem;
         private readonly IProcessFactory _processFactory;
         private readonly IProcessFactory _processFactory;
-        private readonly int DefaultImageExtractionTimeoutMs;
-        private readonly string StartupOptionFFmpegPath;
+        private readonly ILocalizationManager _localization;
+        private readonly Func<ISubtitleEncoder> _subtitleEncoder;
+        private readonly IConfiguration _configuration;
+        private readonly string _startupOptionFFmpegPath;
 
 
         private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(2, 2);
         private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(2, 2);
+
+        private readonly object _runningProcessesLock = new object();
         private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
         private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
-        private readonly ILocalizationManager _localization;
+
+        private EncodingHelper _encodingHelper;
+
+        private string _ffmpegPath;
+        private string _ffprobePath;
 
 
         public MediaEncoder(
         public MediaEncoder(
-            ILoggerFactory loggerFactory,
-            IJsonSerializer jsonSerializer,
-            string startupOptionsFFmpegPath,
+            ILogger<MediaEncoder> logger,
             IServerConfigurationManager configurationManager,
             IServerConfigurationManager configurationManager,
             IFileSystem fileSystem,
             IFileSystem fileSystem,
-            Func<ISubtitleEncoder> subtitleEncoder,
-            Func<IMediaSourceManager> mediaSourceManager,
             IProcessFactory processFactory,
             IProcessFactory processFactory,
-            int defaultImageExtractionTimeoutMs,
-            ILocalizationManager localization)
-        {
-            _logger = loggerFactory.CreateLogger(nameof(MediaEncoder));
-            _jsonSerializer = jsonSerializer;
-            StartupOptionFFmpegPath = startupOptionsFFmpegPath;
-            ConfigurationManager = configurationManager;
-            FileSystem = fileSystem;
-            SubtitleEncoder = subtitleEncoder;
+            ILocalizationManager localization,
+            Func<ISubtitleEncoder> subtitleEncoder,
+            IConfiguration configuration,
+            string startupOptionsFFmpegPath)
+        {
+            _logger = logger;
+            _configurationManager = configurationManager;
+            _fileSystem = fileSystem;
             _processFactory = processFactory;
             _processFactory = processFactory;
-            DefaultImageExtractionTimeoutMs = defaultImageExtractionTimeoutMs;
             _localization = localization;
             _localization = localization;
+            _startupOptionFFmpegPath = startupOptionsFFmpegPath;
+            _subtitleEncoder = subtitleEncoder;
+            _configuration = configuration;
         }
         }
 
 
+        private EncodingHelper EncodingHelper
+            => LazyInitializer.EnsureInitialized(
+                ref _encodingHelper,
+                () => new EncodingHelper(this, _fileSystem, _subtitleEncoder(), _configuration));
+
+        /// <inheritdoc />
+        public string EncoderPath => _ffmpegPath;
+
+        /// <inheritdoc />
+        public FFmpegLocation EncoderLocation { get; private set; }
+
         /// <summary>
         /// <summary>
         /// Run at startup or if the user removes a Custom path from transcode page.
         /// Run at startup or if the user removes a Custom path from transcode page.
         /// Sets global variables FFmpegPath.
         /// Sets global variables FFmpegPath.
@@ -88,39 +93,39 @@ namespace MediaBrowser.MediaEncoding.Encoder
         public void SetFFmpegPath()
         public void SetFFmpegPath()
         {
         {
             // 1) Custom path stored in config/encoding xml file under tag <EncoderAppPath> takes precedence
             // 1) Custom path stored in config/encoding xml file under tag <EncoderAppPath> takes precedence
-            if (!ValidatePath(ConfigurationManager.GetConfiguration<EncodingOptions>("encoding").EncoderAppPath, FFmpegLocation.Custom))
+            if (!ValidatePath(_configurationManager.GetConfiguration<EncodingOptions>("encoding").EncoderAppPath, FFmpegLocation.Custom))
             {
             {
                 // 2) Check if the --ffmpeg CLI switch has been given
                 // 2) Check if the --ffmpeg CLI switch has been given
-                if (!ValidatePath(StartupOptionFFmpegPath, FFmpegLocation.SetByArgument))
+                if (!ValidatePath(_startupOptionFFmpegPath, FFmpegLocation.SetByArgument))
                 {
                 {
                     // 3) Search system $PATH environment variable for valid FFmpeg
                     // 3) Search system $PATH environment variable for valid FFmpeg
                     if (!ValidatePath(ExistsOnSystemPath("ffmpeg"), FFmpegLocation.System))
                     if (!ValidatePath(ExistsOnSystemPath("ffmpeg"), FFmpegLocation.System))
                     {
                     {
                         EncoderLocation = FFmpegLocation.NotFound;
                         EncoderLocation = FFmpegLocation.NotFound;
-                        FFmpegPath = null;
+                        _ffmpegPath = null;
                     }
                     }
                 }
                 }
             }
             }
 
 
             // Write the FFmpeg path to the config/encoding.xml file as <EncoderAppPathDisplay> so it appears in UI
             // Write the FFmpeg path to the config/encoding.xml file as <EncoderAppPathDisplay> so it appears in UI
-            var config = ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
-            config.EncoderAppPathDisplay = FFmpegPath ?? string.Empty;
-            ConfigurationManager.SaveConfiguration("encoding", config);
+            var config = _configurationManager.GetConfiguration<EncodingOptions>("encoding");
+            config.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty;
+            _configurationManager.SaveConfiguration("encoding", config);
 
 
             // Only if mpeg path is set, try and set path to probe
             // Only if mpeg path is set, try and set path to probe
-            if (FFmpegPath != null)
+            if (_ffmpegPath != null)
             {
             {
                 // Determine a probe path from the mpeg path
                 // Determine a probe path from the mpeg path
-                FFprobePath = Regex.Replace(FFmpegPath, @"[^\/\\]+?(\.[^\/\\\n.]+)?$", @"ffprobe$1");
+                _ffprobePath = Regex.Replace(_ffmpegPath, @"[^\/\\]+?(\.[^\/\\\n.]+)?$", @"ffprobe$1");
 
 
                 // Interrogate to understand what coders are supported
                 // Interrogate to understand what coders are supported
-                var validator = new EncoderValidator(_logger, FFmpegPath);
+                var validator = new EncoderValidator(_logger, _ffmpegPath);
 
 
                 SetAvailableDecoders(validator.GetDecoders());
                 SetAvailableDecoders(validator.GetDecoders());
                 SetAvailableEncoders(validator.GetEncoders());
                 SetAvailableEncoders(validator.GetEncoders());
             }
             }
 
 
-            _logger.LogInformation("FFmpeg: {0}: {1}", EncoderLocation, FFmpegPath ?? string.Empty);
+            _logger.LogInformation("FFmpeg: {0}: {1}", EncoderLocation, _ffmpegPath ?? string.Empty);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -160,9 +165,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
 
             // Write the new ffmpeg path to the xml as <EncoderAppPath>
             // Write the new ffmpeg path to the xml as <EncoderAppPath>
             // This ensures its not lost on next startup
             // This ensures its not lost on next startup
-            var config = ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
+            var config = _configurationManager.GetConfiguration<EncodingOptions>("encoding");
             config.EncoderAppPath = newPath;
             config.EncoderAppPath = newPath;
-            ConfigurationManager.SaveConfiguration("encoding", config);
+            _configurationManager.SaveConfiguration("encoding", config);
 
 
             // Trigger SetFFmpegPath so we validate the new path and setup probe path
             // Trigger SetFFmpegPath so we validate the new path and setup probe path
             SetFFmpegPath();
             SetFFmpegPath();
@@ -193,7 +198,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
                     // ToDo - Enable the ffmpeg validator.  At the moment any version can be used.
                     // ToDo - Enable the ffmpeg validator.  At the moment any version can be used.
                     rc = true;
                     rc = true;
 
 
-                    FFmpegPath = path;
+                    _ffmpegPath = path;
                     EncoderLocation = location;
                     EncoderLocation = location;
                 }
                 }
                 else
                 else
@@ -209,7 +214,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
         {
         {
             try
             try
             {
             {
-                var files = FileSystem.GetFilePaths(path);
+                var files = _fileSystem.GetFilePaths(path);
 
 
                 var excludeExtensions = new[] { ".c" };
                 var excludeExtensions = new[] { ".c" };
 
 
@@ -304,7 +309,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
         {
         {
             var extractChapters = request.MediaType == DlnaProfileType.Video && request.ExtractChapters;
             var extractChapters = request.MediaType == DlnaProfileType.Video && request.ExtractChapters;
 
 
-            var inputFiles = MediaEncoderHelpers.GetInputArgument(FileSystem, request.MediaSource.Path, request.MountedIso, request.PlayableStreamFileNames);
+            var inputFiles = MediaEncoderHelpers.GetInputArgument(_fileSystem, request.MediaSource.Path, request.MountedIso, request.PlayableStreamFileNames);
 
 
             var probeSize = EncodingHelper.GetProbeSizeArgument(inputFiles.Length);
             var probeSize = EncodingHelper.GetProbeSizeArgument(inputFiles.Length);
             string analyzeDuration;
             string analyzeDuration;
@@ -365,7 +370,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 // Must consume both or ffmpeg may hang due to deadlocks. See comments below.
                 // Must consume both or ffmpeg may hang due to deadlocks. See comments below.
                 RedirectStandardOutput = true,
                 RedirectStandardOutput = true,
 
 
-                FileName = FFprobePath,
+                FileName = _ffprobePath,
                 Arguments = args,
                 Arguments = args,
 
 
 
 
@@ -383,7 +388,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 _logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
                 _logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
             }
             }
 
 
-            using (var processWrapper = new ProcessWrapper(process, this, _logger))
+            using (var processWrapper = new ProcessWrapper(process, this))
             {
             {
                 _logger.LogDebug("Starting ffprobe with args {Args}", args);
                 _logger.LogDebug("Starting ffprobe with args {Args}", args);
                 StartProcess(processWrapper);
                 StartProcess(processWrapper);
@@ -391,7 +396,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 InternalMediaInfoResult result;
                 InternalMediaInfoResult result;
                 try
                 try
                 {
                 {
-                    result = await _jsonSerializer.DeserializeFromStreamAsync<InternalMediaInfoResult>(
+                    result = await JsonSerializer.DeserializeAsync<InternalMediaInfoResult>(
                                         process.StandardOutput.BaseStream).ConfigureAwait(false);
                                         process.StandardOutput.BaseStream).ConfigureAwait(false);
                 }
                 }
                 catch
                 catch
@@ -423,7 +428,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
                     }
                     }
                 }
                 }
 
 
-                return new ProbeResultNormalizer(_logger, FileSystem, _localization).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol);
+                return new ProbeResultNormalizer(_logger, _fileSystem, _localization).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol);
             }
             }
         }
         }
 
 
@@ -486,7 +491,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 throw new ArgumentNullException(nameof(inputPath));
                 throw new ArgumentNullException(nameof(inputPath));
             }
             }
 
 
-            var tempExtractPath = Path.Combine(ConfigurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + ".jpg");
+            var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + ".jpg");
             Directory.CreateDirectory(Path.GetDirectoryName(tempExtractPath));
             Directory.CreateDirectory(Path.GetDirectoryName(tempExtractPath));
 
 
             // apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar then scale to width 600.
             // apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar then scale to width 600.
@@ -545,7 +550,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 args = string.Format("-ss {0} ", GetTimeParameter(offset.Value)) + args;
                 args = string.Format("-ss {0} ", GetTimeParameter(offset.Value)) + args;
             }
             }
 
 
-            var encodinghelper = new EncodingHelper(this, FileSystem, SubtitleEncoder());
             if (videoStream != null)
             if (videoStream != null)
             {
             {
                 /* fix
                 /* fix
@@ -559,7 +563,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
 
             if (!string.IsNullOrWhiteSpace(container))
             if (!string.IsNullOrWhiteSpace(container))
             {
             {
-                var inputFormat = encodinghelper.GetInputFormat(container);
+                var inputFormat = EncodingHelper.GetInputFormat(container);
                 if (!string.IsNullOrWhiteSpace(inputFormat))
                 if (!string.IsNullOrWhiteSpace(inputFormat))
                 {
                 {
                     args = "-f " + inputFormat + " " + args;
                     args = "-f " + inputFormat + " " + args;
@@ -570,7 +574,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             {
             {
                 CreateNoWindow = true,
                 CreateNoWindow = true,
                 UseShellExecute = false,
                 UseShellExecute = false,
-                FileName = FFmpegPath,
+                FileName = _ffmpegPath,
                 Arguments = args,
                 Arguments = args,
                 IsHidden = true,
                 IsHidden = true,
                 ErrorDialog = false,
                 ErrorDialog = false,
@@ -579,7 +583,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
 
             _logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
             _logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
 
 
-            using (var processWrapper = new ProcessWrapper(process, this, _logger))
+            using (var processWrapper = new ProcessWrapper(process, this))
             {
             {
                 bool ranToCompletion;
                 bool ranToCompletion;
 
 
@@ -588,10 +592,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 {
                 {
                     StartProcess(processWrapper);
                     StartProcess(processWrapper);
 
 
-                    var timeoutMs = ConfigurationManager.Configuration.ImageExtractionTimeoutMs;
+                    var timeoutMs = _configurationManager.Configuration.ImageExtractionTimeoutMs;
                     if (timeoutMs <= 0)
                     if (timeoutMs <= 0)
                     {
                     {
-                        timeoutMs = DefaultImageExtractionTimeoutMs;
+                        timeoutMs = DefaultImageExtractionTimeout;
                     }
                     }
 
 
                     ranToCompletion = await process.WaitForExitAsync(timeoutMs).ConfigureAwait(false);
                     ranToCompletion = await process.WaitForExitAsync(timeoutMs).ConfigureAwait(false);
@@ -607,7 +611,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 }
                 }
 
 
                 var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1;
                 var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1;
-                var file = FileSystem.GetFileInfo(tempExtractPath);
+                var file = _fileSystem.GetFileInfo(tempExtractPath);
 
 
                 if (exitCode == -1 || !file.Exists || file.Length == 0)
                 if (exitCode == -1 || !file.Exists || file.Length == 0)
                 {
                 {
@@ -675,7 +679,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 args = analyzeDurationArgument + " " + args;
                 args = analyzeDurationArgument + " " + args;
             }
             }
 
 
-            var encodinghelper = new EncodingHelper(this, FileSystem, SubtitleEncoder());
             if (videoStream != null)
             if (videoStream != null)
             {
             {
                 /* fix
                 /* fix
@@ -689,7 +692,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
 
             if (!string.IsNullOrWhiteSpace(container))
             if (!string.IsNullOrWhiteSpace(container))
             {
             {
-                var inputFormat = encodinghelper.GetInputFormat(container);
+                var inputFormat = EncodingHelper.GetInputFormat(container);
                 if (!string.IsNullOrWhiteSpace(inputFormat))
                 if (!string.IsNullOrWhiteSpace(inputFormat))
                 {
                 {
                     args = "-f " + inputFormat + " " + args;
                     args = "-f " + inputFormat + " " + args;
@@ -700,7 +703,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             {
             {
                 CreateNoWindow = true,
                 CreateNoWindow = true,
                 UseShellExecute = false,
                 UseShellExecute = false,
-                FileName = FFmpegPath,
+                FileName = _ffmpegPath,
                 Arguments = args,
                 Arguments = args,
                 IsHidden = true,
                 IsHidden = true,
                 ErrorDialog = false,
                 ErrorDialog = false,
@@ -713,7 +716,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
 
             bool ranToCompletion = false;
             bool ranToCompletion = false;
 
 
-            using (var processWrapper = new ProcessWrapper(process, this, _logger))
+            using (var processWrapper = new ProcessWrapper(process, this))
             {
             {
                 try
                 try
                 {
                 {
@@ -736,10 +739,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
 
                         cancellationToken.ThrowIfCancellationRequested();
                         cancellationToken.ThrowIfCancellationRequested();
 
 
-                        var jpegCount = FileSystem.GetFilePaths(targetDirectory)
+                        var jpegCount = _fileSystem.GetFilePaths(targetDirectory)
                             .Count(i => string.Equals(Path.GetExtension(i), ".jpg", StringComparison.OrdinalIgnoreCase));
                             .Count(i => string.Equals(Path.GetExtension(i), ".jpg", StringComparison.OrdinalIgnoreCase));
 
 
-                        isResponsive = (jpegCount > lastCount);
+                        isResponsive = jpegCount > lastCount;
                         lastCount = jpegCount;
                         lastCount = jpegCount;
                     }
                     }
 
 
@@ -770,7 +773,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
         {
         {
             process.Process.Start();
             process.Process.Start();
 
 
-            lock (_runningProcesses)
+            lock (_runningProcessesLock)
             {
             {
                 _runningProcesses.Add(process);
                 _runningProcesses.Add(process);
             }
             }
@@ -804,7 +807,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
         private void StopProcesses()
         private void StopProcesses()
         {
         {
             List<ProcessWrapper> proceses;
             List<ProcessWrapper> proceses;
-            lock (_runningProcesses)
+            lock (_runningProcessesLock)
             {
             {
                 proceses = _runningProcesses.ToList();
                 proceses = _runningProcesses.ToList();
                 _runningProcesses.Clear();
                 _runningProcesses.Clear();
@@ -827,12 +830,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
             return path.Replace('\\', '/').Replace(":", "\\:").Replace("'", "'\\\\\\''");
             return path.Replace('\\', '/').Replace(":", "\\:").Replace("'", "'\\\\\\''");
         }
         }
 
 
-        /// <summary>
-        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
-        /// </summary>
+        /// <inheritdoc />
         public void Dispose()
         public void Dispose()
         {
         {
             Dispose(true);
             Dispose(true);
+            GC.SuppressFinalize(this);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -852,11 +854,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
             throw new NotImplementedException();
             throw new NotImplementedException();
         }
         }
 
 
-        public string[] GetPlayableStreamFileNames(string path, VideoType videoType)
-        {
-            throw new NotImplementedException();
-        }
-
         public IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, IIsoMount isoMount, uint? titleNumber)
         public IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, IIsoMount isoMount, uint? titleNumber)
         {
         {
             throw new NotImplementedException();
             throw new NotImplementedException();
@@ -870,21 +867,24 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
 
         private class ProcessWrapper : IDisposable
         private class ProcessWrapper : IDisposable
         {
         {
-            public readonly IProcess Process;
-            public bool HasExited;
-            public int? ExitCode;
             private readonly MediaEncoder _mediaEncoder;
             private readonly MediaEncoder _mediaEncoder;
-            private readonly ILogger _logger;
 
 
-            public ProcessWrapper(IProcess process, MediaEncoder mediaEncoder, ILogger logger)
+            private bool _disposed = false;
+
+            public ProcessWrapper(IProcess process, MediaEncoder mediaEncoder)
             {
             {
                 Process = process;
                 Process = process;
                 _mediaEncoder = mediaEncoder;
                 _mediaEncoder = mediaEncoder;
-                _logger = logger;
-                Process.Exited += Process_Exited;
+                Process.Exited += OnProcessExited;
             }
             }
 
 
-            void Process_Exited(object sender, EventArgs e)
+            public IProcess Process { get; }
+
+            public bool HasExited { get; private set; }
+
+            public int? ExitCode { get; private set; }
+
+            void OnProcessExited(object sender, EventArgs e)
             {
             {
                 var process = (IProcess)sender;
                 var process = (IProcess)sender;
 
 
@@ -903,7 +903,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
 
             private void DisposeProcess(IProcess process)
             private void DisposeProcess(IProcess process)
             {
             {
-                lock (_mediaEncoder._runningProcesses)
+                lock (_mediaEncoder._runningProcessesLock)
                 {
                 {
                     _mediaEncoder._runningProcesses.Remove(this);
                     _mediaEncoder._runningProcesses.Remove(this);
                 }
                 }
@@ -917,23 +917,18 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 }
                 }
             }
             }
 
 
-            private bool _disposed;
-            private readonly object _syncLock = new object();
             public void Dispose()
             public void Dispose()
             {
             {
-                lock (_syncLock)
+                if (!_disposed)
                 {
                 {
-                    if (!_disposed)
+                    if (Process != null)
                     {
                     {
-                        if (Process != null)
-                        {
-                            Process.Exited -= Process_Exited;
-                            DisposeProcess(Process);
-                        }
+                        Process.Exited -= OnProcessExited;
+                        DisposeProcess(Process);
                     }
                     }
-
-                    _disposed = true;
                 }
                 }
+
+                _disposed = true;
             }
             }
         }
         }
     }
     }

+ 1 - 1
MediaBrowser.MediaEncoding/Subtitles/ISubtitleWriter.cs

@@ -5,7 +5,7 @@ using MediaBrowser.Model.MediaInfo;
 namespace MediaBrowser.MediaEncoding.Subtitles
 namespace MediaBrowser.MediaEncoding.Subtitles
 {
 {
     /// <summary>
     /// <summary>
-    /// Interface ISubtitleWriter
+    /// Interface ISubtitleWriter.
     /// </summary>
     /// </summary>
     public interface ISubtitleWriter
     public interface ISubtitleWriter
     {
     {

+ 24 - 12
MediaBrowser.MediaEncoding/Subtitles/JsonWriter.cs

@@ -1,27 +1,39 @@
 using System.IO;
 using System.IO;
-using System.Text;
+using System.Text.Json;
 using System.Threading;
 using System.Threading;
 using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Serialization;
 
 
 namespace MediaBrowser.MediaEncoding.Subtitles
 namespace MediaBrowser.MediaEncoding.Subtitles
 {
 {
+    /// <summary>
+    /// JSON subtitle writer.
+    /// </summary>
     public class JsonWriter : ISubtitleWriter
     public class JsonWriter : ISubtitleWriter
     {
     {
-        private readonly IJsonSerializer _json;
-
-        public JsonWriter(IJsonSerializer json)
-        {
-            _json = json;
-        }
-
+        /// <inheritdoc />
         public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
         public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
         {
         {
-            using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
+            using (var writer = new Utf8JsonWriter(stream))
             {
             {
-                var json = _json.SerializeToString(info);
+                var trackevents = info.TrackEvents;
+                writer.WriteStartArray("TrackEvents");
+
+                for (int i = 0; i < trackevents.Count; i++)
+                {
+                    cancellationToken.ThrowIfCancellationRequested();
+
+                    var current = trackevents[i];
+                    writer.WriteStartObject();
+
+                    writer.WriteString("Id", current.Id);
+                    writer.WriteString("Text", current.Text);
+                    writer.WriteNumber("StartPositionTicks", current.StartPositionTicks);
+                    writer.WriteNumber("EndPositionTicks", current.EndPositionTicks);
+
+                    writer.WriteEndObject();
+                }
 
 
-                writer.Write(json);
+                writer.WriteEndObject();
             }
             }
         }
         }
     }
     }

+ 10 - 7
MediaBrowser.MediaEncoding/Subtitles/SrtWriter.cs

@@ -14,14 +14,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles
         {
         {
             using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
             using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
             {
             {
-                var index = 1;
+                var trackEvents = info.TrackEvents;
 
 
-                foreach (var trackEvent in info.TrackEvents)
+                for (int i = 0; i < trackEvents.Count; i++)
                 {
                 {
                     cancellationToken.ThrowIfCancellationRequested();
                     cancellationToken.ThrowIfCancellationRequested();
 
 
-                    writer.WriteLine(index.ToString(CultureInfo.InvariantCulture));
-                    writer.WriteLine(@"{0:hh\:mm\:ss\,fff} --> {1:hh\:mm\:ss\,fff}", TimeSpan.FromTicks(trackEvent.StartPositionTicks), TimeSpan.FromTicks(trackEvent.EndPositionTicks));
+                    var trackEvent = trackEvents[i];
+
+                    writer.WriteLine((i + 1).ToString(CultureInfo.InvariantCulture));
+                    writer.WriteLine(
+                        @"{0:hh\:mm\:ss\,fff} --> {1:hh\:mm\:ss\,fff}",
+                        TimeSpan.FromTicks(trackEvent.StartPositionTicks),
+                        TimeSpan.FromTicks(trackEvent.EndPositionTicks));
 
 
                     var text = trackEvent.Text;
                     var text = trackEvent.Text;
 
 
@@ -29,9 +34,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
                     text = Regex.Replace(text, @"\\n", " ", RegexOptions.IgnoreCase);
                     text = Regex.Replace(text, @"\\n", " ", RegexOptions.IgnoreCase);
 
 
                     writer.WriteLine(text);
                     writer.WriteLine(text);
-                    writer.WriteLine(string.Empty);
-
-                    index++;
+                    writer.WriteLine();
                 }
                 }
             }
             }
         }
         }

+ 51 - 52
MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs

@@ -17,7 +17,6 @@ using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Serialization;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
 using UtfUnknown;
 using UtfUnknown;
 
 
@@ -30,28 +29,25 @@ namespace MediaBrowser.MediaEncoding.Subtitles
         private readonly IApplicationPaths _appPaths;
         private readonly IApplicationPaths _appPaths;
         private readonly IFileSystem _fileSystem;
         private readonly IFileSystem _fileSystem;
         private readonly IMediaEncoder _mediaEncoder;
         private readonly IMediaEncoder _mediaEncoder;
-        private readonly IJsonSerializer _json;
         private readonly IHttpClient _httpClient;
         private readonly IHttpClient _httpClient;
         private readonly IMediaSourceManager _mediaSourceManager;
         private readonly IMediaSourceManager _mediaSourceManager;
         private readonly IProcessFactory _processFactory;
         private readonly IProcessFactory _processFactory;
 
 
         public SubtitleEncoder(
         public SubtitleEncoder(
             ILibraryManager libraryManager,
             ILibraryManager libraryManager,
-            ILoggerFactory loggerFactory,
+            ILogger<SubtitleEncoder> logger,
             IApplicationPaths appPaths,
             IApplicationPaths appPaths,
             IFileSystem fileSystem,
             IFileSystem fileSystem,
             IMediaEncoder mediaEncoder,
             IMediaEncoder mediaEncoder,
-            IJsonSerializer json,
             IHttpClient httpClient,
             IHttpClient httpClient,
             IMediaSourceManager mediaSourceManager,
             IMediaSourceManager mediaSourceManager,
             IProcessFactory processFactory)
             IProcessFactory processFactory)
         {
         {
             _libraryManager = libraryManager;
             _libraryManager = libraryManager;
-            _logger = loggerFactory.CreateLogger(nameof(SubtitleEncoder));
+            _logger = logger;
             _appPaths = appPaths;
             _appPaths = appPaths;
             _fileSystem = fileSystem;
             _fileSystem = fileSystem;
             _mediaEncoder = mediaEncoder;
             _mediaEncoder = mediaEncoder;
-            _json = json;
             _httpClient = httpClient;
             _httpClient = httpClient;
             _mediaSourceManager = mediaSourceManager;
             _mediaSourceManager = mediaSourceManager;
             _processFactory = processFactory;
             _processFactory = processFactory;
@@ -59,7 +55,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
 
 
         private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles");
         private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles");
 
 
-        private Stream ConvertSubtitles(Stream stream,
+        private Stream ConvertSubtitles(
+            Stream stream,
             string inputFormat,
             string inputFormat,
             string outputFormat,
             string outputFormat,
             long startTimeTicks,
             long startTimeTicks,
@@ -170,7 +167,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
                 && (mediaSource.VideoType.Value == VideoType.BluRay || mediaSource.VideoType.Value == VideoType.Dvd))
                 && (mediaSource.VideoType.Value == VideoType.BluRay || mediaSource.VideoType.Value == VideoType.Dvd))
             {
             {
                 var mediaSourceItem = (Video)_libraryManager.GetItemById(new Guid(mediaSource.Id));
                 var mediaSourceItem = (Video)_libraryManager.GetItemById(new Guid(mediaSource.Id));
-                inputFiles = mediaSourceItem.GetPlayableStreamFileNames(_mediaEncoder);
+                inputFiles = mediaSourceItem.GetPlayableStreamFileNames();
             }
             }
             else
             else
             {
             {
@@ -179,32 +176,27 @@ namespace MediaBrowser.MediaEncoding.Subtitles
 
 
             var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, mediaSource.Protocol, subtitleStream, cancellationToken).ConfigureAwait(false);
             var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, mediaSource.Protocol, subtitleStream, cancellationToken).ConfigureAwait(false);
 
 
-            var stream = await GetSubtitleStream(fileInfo.Path, subtitleStream.Language, fileInfo.Protocol, fileInfo.IsExternal, cancellationToken).ConfigureAwait(false);
+            var stream = await GetSubtitleStream(fileInfo.Path, fileInfo.Protocol, fileInfo.IsExternal, cancellationToken).ConfigureAwait(false);
 
 
             return (stream, fileInfo.Format);
             return (stream, fileInfo.Format);
         }
         }
 
 
-        private async Task<Stream> GetSubtitleStream(string path, string language, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken)
+        private async Task<Stream> GetSubtitleStream(string path, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken)
         {
         {
             if (requiresCharset)
             if (requiresCharset)
             {
             {
-                var bytes = await GetBytes(path, protocol, cancellationToken).ConfigureAwait(false);
-
-                var charset = CharsetDetector.DetectFromBytes(bytes).Detected?.EncodingName;
-                _logger.LogDebug("charset {CharSet} detected for {Path}", charset ?? "null", path);
-
-                if (!string.IsNullOrEmpty(charset))
+                using (var stream = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false))
                 {
                 {
-                    // Make sure we have all the code pages we can get
-                    Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
-                    using (var inputStream = new MemoryStream(bytes))
-                    using (var reader = new StreamReader(inputStream, Encoding.GetEncoding(charset)))
+                    var result = CharsetDetector.DetectFromStream(stream).Detected;
+
+                    if (result != null)
                     {
                     {
-                        var text = await reader.ReadToEndAsync().ConfigureAwait(false);
+                        _logger.LogDebug("charset {CharSet} detected for {Path}", result.EncodingName, path);
 
 
-                        bytes = Encoding.UTF8.GetBytes(text);
+                        using var reader = new StreamReader(stream, result.Encoding);
+                        var text = await reader.ReadToEndAsync().ConfigureAwait(false);
 
 
-                        return new MemoryStream(bytes);
+                        return new MemoryStream(Encoding.UTF8.GetBytes(text));
                     }
                     }
                 }
                 }
             }
             }
@@ -323,7 +315,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
 
 
             if (string.Equals(format, "json", StringComparison.OrdinalIgnoreCase))
             if (string.Equals(format, "json", StringComparison.OrdinalIgnoreCase))
             {
             {
-                return new JsonWriter(_json);
+                return new JsonWriter();
             }
             }
             if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase))
             if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase))
             {
             {
@@ -544,7 +536,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
             {
             {
                 if (!File.Exists(outputPath))
                 if (!File.Exists(outputPath))
                 {
                 {
-                    await ExtractTextSubtitleInternal(_mediaEncoder.GetInputArgument(inputFiles, protocol), subtitleStreamIndex, outputCodec, outputPath, cancellationToken).ConfigureAwait(false);
+                    await ExtractTextSubtitleInternal(
+                        _mediaEncoder.GetInputArgument(inputFiles, protocol),
+                        subtitleStreamIndex,
+                        outputCodec,
+                        outputPath,
+                        cancellationToken).ConfigureAwait(false);
                 }
                 }
             }
             }
             finally
             finally
@@ -572,8 +569,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles
 
 
             Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
             Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
 
 
-            var processArgs = string.Format("-i {0} -map 0:{1} -an -vn -c:s {2} \"{3}\"", inputPath,
-                subtitleStreamIndex, outputCodec, outputPath);
+            var processArgs = string.Format(
+                CultureInfo.InvariantCulture,
+                "-i {0} -map 0:{1} -an -vn -c:s {2} \"{3}\"",
+                inputPath,
+                subtitleStreamIndex,
+                outputCodec,
+                outputPath);
 
 
             var process = _processFactory.Create(new ProcessOptions
             var process = _processFactory.Create(new ProcessOptions
             {
             {
@@ -721,41 +723,38 @@ namespace MediaBrowser.MediaEncoding.Subtitles
             }
             }
         }
         }
 
 
+        /// <inheritdoc />
         public async Task<string> GetSubtitleFileCharacterSet(string path, string language, MediaProtocol protocol, CancellationToken cancellationToken)
         public async Task<string> GetSubtitleFileCharacterSet(string path, string language, MediaProtocol protocol, CancellationToken cancellationToken)
         {
         {
-            var bytes = await GetBytes(path, protocol, cancellationToken).ConfigureAwait(false);
-
-            var charset = CharsetDetector.DetectFromBytes(bytes).Detected?.EncodingName;
+            using (var stream = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false))
+            {
+                var charset = CharsetDetector.DetectFromStream(stream).Detected?.EncodingName;
 
 
-            _logger.LogDebug("charset {0} detected for {Path}", charset ?? "null", path);
+                _logger.LogDebug("charset {0} detected for {Path}", charset ?? "null", path);
 
 
-            return charset;
+                return charset;
+            }
         }
         }
 
 
-        private async Task<byte[]> GetBytes(string path, MediaProtocol protocol, CancellationToken cancellationToken)
+        private Task<Stream> GetStream(string path, MediaProtocol protocol, CancellationToken cancellationToken)
         {
         {
-            if (protocol == MediaProtocol.Http)
+            switch (protocol)
             {
             {
-                var opts = new HttpRequestOptions()
-                {
-                    Url = path,
-                    CancellationToken = cancellationToken
-                };
-                using (var file = await _httpClient.Get(opts).ConfigureAwait(false))
-                using (var memoryStream = new MemoryStream())
-                {
-                    await file.CopyToAsync(memoryStream).ConfigureAwait(false);
-                    memoryStream.Position = 0;
+                case MediaProtocol.Http:
+                    var opts = new HttpRequestOptions()
+                    {
+                        Url = path,
+                        CancellationToken = cancellationToken,
+                        BufferContent = true
+                    };
 
 
-                    return memoryStream.ToArray();
-                }
-            }
-            if (protocol == MediaProtocol.File)
-            {
-                return File.ReadAllBytes(path);
-            }
+                    return _httpClient.Get(opts);
 
 
-            throw new ArgumentOutOfRangeException(nameof(protocol));
+            case MediaProtocol.File:
+                return Task.FromResult<Stream>(File.OpenRead(path));
+            default:
+                throw new ArgumentOutOfRangeException(nameof(protocol));
+            }
         }
         }
     }
     }
 }
 }

+ 0 - 7
MediaBrowser.MediaEncoding/Subtitles/TtmlWriter.cs

@@ -49,12 +49,5 @@ namespace MediaBrowser.MediaEncoding.Subtitles
                 writer.WriteLine("</tt>");
                 writer.WriteLine("</tt>");
             }
             }
         }
         }
-
-        private string FormatTime(long ticks)
-        {
-            var time = TimeSpan.FromTicks(ticks);
-
-            return string.Format(@"{0:hh\:mm\:ss\,fff}", time);
-        }
     }
     }
 }
 }

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

@@ -233,7 +233,6 @@ namespace MediaBrowser.Model.Configuration
             LocalNetworkSubnets = Array.Empty<string>();
             LocalNetworkSubnets = Array.Empty<string>();
             LocalNetworkAddresses = Array.Empty<string>();
             LocalNetworkAddresses = Array.Empty<string>();
             CodecsUsed = Array.Empty<string>();
             CodecsUsed = Array.Empty<string>();
-            ImageExtractionTimeoutMs = 0;
             PathSubstitutions = Array.Empty<PathSubstitution>();
             PathSubstitutions = Array.Empty<PathSubstitution>();
             IgnoreVirtualInterfaces = false;
             IgnoreVirtualInterfaces = false;
             EnableSimpleArtistDetection = true;
             EnableSimpleArtistDetection = true;

+ 5 - 2
MediaBrowser.Model/MediaInfo/SubtitleTrackInfo.cs

@@ -1,12 +1,15 @@
+using System;
+using System.Collections.Generic;
+
 namespace MediaBrowser.Model.MediaInfo
 namespace MediaBrowser.Model.MediaInfo
 {
 {
     public class SubtitleTrackInfo
     public class SubtitleTrackInfo
     {
     {
-        public SubtitleTrackEvent[] TrackEvents { get; set; }
+        public IReadOnlyList<SubtitleTrackEvent> TrackEvents { get; set; }
 
 
         public SubtitleTrackInfo()
         public SubtitleTrackInfo()
         {
         {
-            TrackEvents = new SubtitleTrackEvent[] { };
+            TrackEvents = Array.Empty<SubtitleTrackEvent>();
         }
         }
     }
     }
 }
 }

+ 5 - 1
MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs

@@ -62,7 +62,11 @@ namespace MediaBrowser.Providers.MediaInfo
         {
         {
             var protocol = item.PathProtocol ?? MediaProtocol.File;
             var protocol = item.PathProtocol ?? MediaProtocol.File;
 
 
-            var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, item.Path, null, item.GetPlayableStreamFileNames(_mediaEncoder));
+            var inputPath = MediaEncoderHelpers.GetInputArgument(
+                _fileSystem,
+                item.Path,
+                null,
+                item.GetPlayableStreamFileNames());
 
 
             var mediaStreams =
             var mediaStreams =
                 item.GetMediaStreams();
                 item.GetMediaStreams();

+ 2 - 0
jellyfin.ruleset

@@ -31,6 +31,8 @@
   <Rules AnalyzerId="Microsoft.CodeAnalysis.FxCopAnalyzers" RuleNamespace="Microsoft.Design">
   <Rules AnalyzerId="Microsoft.CodeAnalysis.FxCopAnalyzers" RuleNamespace="Microsoft.Design">
     <!-- disable warning CA1031: Do not catch general exception types -->
     <!-- disable warning CA1031: Do not catch general exception types -->
     <Rule Id="CA1031" Action="Info" />
     <Rule Id="CA1031" Action="Info" />
+    <!-- disable warning CA1032: Implement standard exception constructors -->
+    <Rule Id="CA1032" Action="Info" />
     <!-- disable warning CA1062: Validate arguments of public methods -->
     <!-- disable warning CA1062: Validate arguments of public methods -->
     <Rule Id="CA1062" Action="Info" />
     <Rule Id="CA1062" Action="Info" />
     <!-- disable warning CA1720: Identifiers should not contain type names -->
     <!-- disable warning CA1720: Identifiers should not contain type names -->