2
0
Эх сурвалжийг харах

Merge pull request #3825 from crobibero/api-merge-again

Merge master into api-migration
Joshua M. Boniface 4 жил өмнө
parent
commit
3e974c0351
31 өөрчлөгдсөн 316 нэмэгдсэн , 240 устгасан
  1. 16 12
      .ci/azure-pipelines-abi.yml
  2. 7 12
      .ci/azure-pipelines-package.yml
  3. 32 26
      Emby.Server.Implementations/Data/SqliteItemRepository.cs
  4. 1 1
      Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
  5. 4 4
      Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
  6. 1 1
      Jellyfin.Api/Controllers/DynamicHlsController.cs
  7. 1 2
      Jellyfin.Api/Controllers/SubtitleController.cs
  8. 3 2
      Jellyfin.Server.Implementations/Activity/ActivityManager.cs
  9. 2 0
      MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
  10. 14 5
      MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs
  11. 4 0
      MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs
  12. 11 8
      MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs
  13. 2 32
      MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs
  14. 38 0
      MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationStore.cs
  15. 17 15
      MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
  16. 2 0
      MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
  17. 47 30
      MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
  18. 13 0
      MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
  19. 3 0
      MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs
  20. 2 0
      MediaBrowser.MediaEncoding/Probing/MediaChapter.cs
  21. 4 0
      MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
  22. 32 35
      MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
  23. 11 10
      MediaBrowser.MediaEncoding/Subtitles/AssParser.cs
  24. 2 0
      MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs
  25. 9 5
      MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs
  26. 31 31
      MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
  27. 2 2
      MediaBrowser.Model/Entities/MediaStream.cs
  28. 2 2
      MediaBrowser.Providers/Manager/ImageSaver.cs
  29. 2 2
      MediaBrowser.Providers/Manager/ProviderManager.cs
  30. 1 1
      README.md
  31. 0 2
      RSSDP/RSSDP.csproj

+ 16 - 12
.ci/azure-pipelines-abi.yml

@@ -12,10 +12,12 @@ parameters:
 jobs:
 jobs:
   - job: CompatibilityCheck
   - job: CompatibilityCheck
     displayName: Compatibility Check
     displayName: Compatibility Check
+    dependsOn: Build
+    condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber'])
+
     pool:
     pool:
       vmImage: "${{ parameters.LinuxImage }}"
       vmImage: "${{ parameters.LinuxImage }}"
-    # only execute for pull requests
-    condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber'])
+
     strategy:
     strategy:
       matrix:
       matrix:
         ${{ each Package in parameters.Packages }}:
         ${{ each Package in parameters.Packages }}:
@@ -23,7 +25,7 @@ jobs:
             NugetPackageName: ${{ Package.value.NugetPackageName }}
             NugetPackageName: ${{ Package.value.NugetPackageName }}
             AssemblyFileName: ${{ Package.value.AssemblyFileName }}
             AssemblyFileName: ${{ Package.value.AssemblyFileName }}
       maxParallel: 2
       maxParallel: 2
-    dependsOn: Build
+
     steps:
     steps:
       - checkout: none
       - checkout: none
 
 
@@ -34,32 +36,33 @@ jobs:
           version: ${{ parameters.DotNetSdkVersion }}
           version: ${{ parameters.DotNetSdkVersion }}
 
 
       - task: DotNetCoreCLI@2
       - task: DotNetCoreCLI@2
-        displayName: 'Install ABI CompatibilityChecker tool'
+        displayName: 'Install ABI CompatibilityChecker Tool'
         inputs:
         inputs:
           command: custom
           command: custom
           custom: tool
           custom: tool
           arguments: 'update compatibilitychecker -g'
           arguments: 'update compatibilitychecker -g'
 
 
       - task: DownloadPipelineArtifact@2
       - task: DownloadPipelineArtifact@2
-        displayName: "Download New Assembly Build Artifact"
+        displayName: 'Download New Assembly Build Artifact'
         inputs:
         inputs:
-          source: "current"
+          source: 'current'
           artifact: "$(NugetPackageName)"
           artifact: "$(NugetPackageName)"
           path: "$(System.ArtifactsDirectory)/new-artifacts"
           path: "$(System.ArtifactsDirectory)/new-artifacts"
           runVersion: "latest"
           runVersion: "latest"
 
 
       - task: CopyFiles@2
       - task: CopyFiles@2
-        displayName: "Copy New Assembly Build Artifact"
+        displayName: 'Copy New Assembly Build Artifact'
         inputs:
         inputs:
           sourceFolder: $(System.ArtifactsDirectory)/new-artifacts
           sourceFolder: $(System.ArtifactsDirectory)/new-artifacts
-          contents: "**/*.dll"
+          contents: '**/*.dll'
           targetFolder: $(System.ArtifactsDirectory)/new-release
           targetFolder: $(System.ArtifactsDirectory)/new-release
           cleanTargetFolder: true
           cleanTargetFolder: true
           overWrite: true
           overWrite: true
           flattenFolders: true
           flattenFolders: true
 
 
       - task: DownloadPipelineArtifact@2
       - task: DownloadPipelineArtifact@2
-        displayName: "Download Reference Assembly Build Artifact"
+        displayName: 'Download Reference Assembly Build Artifact'
+        enabled: false
         inputs:
         inputs:
           source: "specific"
           source: "specific"
           artifact: "$(NugetPackageName)"
           artifact: "$(NugetPackageName)"
@@ -70,18 +73,19 @@ jobs:
           runBranch: "refs/heads/$(System.PullRequest.TargetBranch)"
           runBranch: "refs/heads/$(System.PullRequest.TargetBranch)"
 
 
       - task: CopyFiles@2
       - task: CopyFiles@2
-        displayName: "Copy Reference Assembly Build Artifact"
+        displayName: 'Copy Reference Assembly Build Artifact'
+        enabled: false
         inputs:
         inputs:
           sourceFolder: $(System.ArtifactsDirectory)/current-artifacts
           sourceFolder: $(System.ArtifactsDirectory)/current-artifacts
-          contents: "**/*.dll"
+          contents: '**/*.dll'
           targetFolder: $(System.ArtifactsDirectory)/current-release
           targetFolder: $(System.ArtifactsDirectory)/current-release
           cleanTargetFolder: true
           cleanTargetFolder: true
           overWrite: true
           overWrite: true
           flattenFolders: true
           flattenFolders: true
 
 
-      # The `--warnings-only` switch will swallow the return code and not emit any errors.
       - task: DotNetCoreCLI@2
       - task: DotNetCoreCLI@2
         displayName: 'Execute ABI Compatibility Check Tool'
         displayName: 'Execute ABI Compatibility Check Tool'
+        enabled: false
         inputs:
         inputs:
           command: custom
           command: custom
           custom: compat
           custom: compat

+ 7 - 12
.ci/azure-pipelines-package.yml

@@ -116,6 +116,7 @@ jobs:
         $(JellyfinVersion)-$(BuildConfiguration)
         $(JellyfinVersion)-$(BuildConfiguration)
 
 
 - job: CollectArtifacts
 - job: CollectArtifacts
+  timeoutInMinutes: 10
   displayName: 'Collect Artifacts'
   displayName: 'Collect Artifacts'
   dependsOn:
   dependsOn:
   - BuildPackage
   - BuildPackage
@@ -131,29 +132,23 @@ jobs:
     condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
     condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
     inputs:
     inputs:
       sshEndpoint: repository
       sshEndpoint: repository
-      runOptions: 'inline'
-      inline: |
-        sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable
-        rm $0
-        exit
+      runOptions: 'commands'
+      commands: sudo -n /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable
 
 
   - task: SSH@0
   - task: SSH@0
     displayName: 'Update Stable Repository'
     displayName: 'Update Stable Repository'
     condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
     condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
     inputs:
     inputs:
       sshEndpoint: repository
       sshEndpoint: repository
-      runOptions: 'inline'
-      inline: |
-        sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber)
-        rm $0
-        exit
-
+      runOptions: 'commands'
+      commands: sudo -n /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber)
+      
 - job: PublishNuget
 - job: PublishNuget
   displayName: 'Publish NuGet packages'
   displayName: 'Publish NuGet packages'
   dependsOn:
   dependsOn:
   - BuildPackage
   - BuildPackage
   condition: and(succeeded('BuildPackage'), startsWith(variables['Build.SourceBranch'], 'refs/tags'))
   condition: and(succeeded('BuildPackage'), startsWith(variables['Build.SourceBranch'], 'refs/tags'))
-  
+
   pool:
   pool:
     vmImage: 'ubuntu-latest'
     vmImage: 'ubuntu-latest'
 
 

+ 32 - 26
Emby.Server.Implementations/Data/SqliteItemRepository.cs

@@ -978,7 +978,10 @@ namespace Emby.Server.Implementations.Data
                     continue;
                     continue;
                 }
                 }
 
 
-                str.Append($"{i.Key}={i.Value}|");
+                str.Append(i.Key)
+                    .Append('=')
+                    .Append(i.Value)
+                    .Append('|');
             }
             }
 
 
             if (str.Length == 0)
             if (str.Length == 0)
@@ -1032,8 +1035,8 @@ namespace Emby.Server.Implementations.Data
                     continue;
                     continue;
                 }
                 }
 
 
-                str.Append(ToValueString(i))
-                    .Append('|');
+                AppendItemImageInfo(str, i);
+                str.Append('|');
             }
             }
 
 
             str.Length -= 1; // Remove last |
             str.Length -= 1; // Remove last |
@@ -1067,26 +1070,26 @@ namespace Emby.Server.Implementations.Data
             item.ImageInfos = list.ToArray();
             item.ImageInfos = list.ToArray();
         }
         }
 
 
-        public string ToValueString(ItemImageInfo image)
+        public void AppendItemImageInfo(StringBuilder bldr, ItemImageInfo image)
         {
         {
-            const string Delimeter = "*";
+            const char Delimiter = '*';
 
 
             var path = image.Path ?? string.Empty;
             var path = image.Path ?? string.Empty;
             var hash = image.BlurHash ?? string.Empty;
             var hash = image.BlurHash ?? string.Empty;
 
 
-            return GetPathToSave(path) +
-                   Delimeter +
-                   image.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) +
-                   Delimeter +
-                   image.Type +
-                   Delimeter +
-                   image.Width.ToString(CultureInfo.InvariantCulture) +
-                   Delimeter +
-                   image.Height.ToString(CultureInfo.InvariantCulture) +
-                   Delimeter +
-                   // Replace delimiters with other characters.
-                   // This can be removed when we migrate to a proper DB.
-                   hash.Replace('*', '/').Replace('|', '\\');
+            bldr.Append(GetPathToSave(path))
+                .Append(Delimiter)
+                .Append(image.DateModified.Ticks)
+                .Append(Delimiter)
+                .Append(image.Type)
+                .Append(Delimiter)
+                .Append(image.Width)
+                .Append(Delimiter)
+                .Append(image.Height)
+                .Append(Delimiter)
+                // Replace delimiters with other characters.
+                // This can be removed when we migrate to a proper DB.
+                .Append(hash.Replace('*', '/').Replace('|', '\\'));
         }
         }
 
 
         public ItemImageInfo ItemImageInfoFromValueString(string value)
         public ItemImageInfo ItemImageInfoFromValueString(string value)
@@ -5659,10 +5662,10 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
             const int Limit = 100;
             const int Limit = 100;
             var startIndex = 0;
             var startIndex = 0;
 
 
+            const string StartInsertText = "insert into ItemValues (ItemId, Type, Value, CleanValue) values ";
+            var insertText = new StringBuilder(StartInsertText);
             while (startIndex < values.Count)
             while (startIndex < values.Count)
             {
             {
-                var insertText = new StringBuilder("insert into ItemValues (ItemId, Type, Value, CleanValue) values ");
-
                 var endIndex = Math.Min(values.Count, startIndex + Limit);
                 var endIndex = Math.Min(values.Count, startIndex + Limit);
 
 
                 for (var i = startIndex; i < endIndex; i++)
                 for (var i = startIndex; i < endIndex; i++)
@@ -5704,6 +5707,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
                 }
                 }
 
 
                 startIndex += Limit;
                 startIndex += Limit;
+                insertText.Length = StartInsertText.Length;
             }
             }
         }
         }
 
 
@@ -5741,10 +5745,10 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
             var startIndex = 0;
             var startIndex = 0;
             var listIndex = 0;
             var listIndex = 0;
 
 
+            const string StartInsertText = "insert into People (ItemId, Name, Role, PersonType, SortOrder, ListOrder) values ";
+            var insertText = new StringBuilder(StartInsertText);
             while (startIndex < people.Count)
             while (startIndex < people.Count)
             {
             {
-                var insertText = new StringBuilder("insert into People (ItemId, Name, Role, PersonType, SortOrder, ListOrder) values ");
-
                 var endIndex = Math.Min(people.Count, startIndex + Limit);
                 var endIndex = Math.Min(people.Count, startIndex + Limit);
                 for (var i = startIndex; i < endIndex; i++)
                 for (var i = startIndex; i < endIndex; i++)
                 {
                 {
@@ -5778,6 +5782,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
                 }
                 }
 
 
                 startIndex += Limit;
                 startIndex += Limit;
+                insertText.Length = StartInsertText.Length;
             }
             }
         }
         }
 
 
@@ -5893,10 +5898,9 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
             const int Limit = 10;
             const int Limit = 10;
             var startIndex = 0;
             var startIndex = 0;
 
 
+            var insertText = new StringBuilder(_mediaStreamSaveColumnsInsertQuery);
             while (startIndex < streams.Count)
             while (startIndex < streams.Count)
             {
             {
-                var insertText = new StringBuilder(_mediaStreamSaveColumnsInsertQuery);
-
                 var endIndex = Math.Min(streams.Count, startIndex + Limit);
                 var endIndex = Math.Min(streams.Count, startIndex + Limit);
 
 
                 for (var i = startIndex; i < endIndex; i++)
                 for (var i = startIndex; i < endIndex; i++)
@@ -5979,6 +5983,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
                 }
                 }
 
 
                 startIndex += Limit;
                 startIndex += Limit;
+                insertText.Length = _mediaStreamSaveColumnsInsertQuery.Length;
             }
             }
         }
         }
 
 
@@ -6230,10 +6235,9 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
         {
         {
             const int InsertAtOnce = 10;
             const int InsertAtOnce = 10;
 
 
+            var insertText = new StringBuilder(_mediaAttachmentInsertPrefix);
             for (var startIndex = 0; startIndex < attachments.Count; startIndex += InsertAtOnce)
             for (var startIndex = 0; startIndex < attachments.Count; startIndex += InsertAtOnce)
             {
             {
-                var insertText = new StringBuilder(_mediaAttachmentInsertPrefix);
-
                 var endIndex = Math.Min(attachments.Count, startIndex + InsertAtOnce);
                 var endIndex = Math.Min(attachments.Count, startIndex + InsertAtOnce);
 
 
                 for (var i = startIndex; i < endIndex; i++)
                 for (var i = startIndex; i < endIndex; i++)
@@ -6279,6 +6283,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
                     statement.Reset();
                     statement.Reset();
                     statement.MoveNext();
                     statement.MoveNext();
                 }
                 }
+
+                insertText.Length = _mediaAttachmentInsertPrefix.Length;
             }
             }
         }
         }
 
 

+ 1 - 1
Emby.Server.Implementations/HttpServer/HttpListenerHost.cs

@@ -567,7 +567,7 @@ namespace Emby.Server.Implementations.HttpServer
 
 
                 WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false);
                 WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false);
 
 
-                var connection = new WebSocketConnection(
+                using var connection = new WebSocketConnection(
                     _loggerFactory.CreateLogger<WebSocketConnection>(),
                     _loggerFactory.CreateLogger<WebSocketConnection>(),
                     webSocket,
                     webSocket,
                     context.Connection.RemoteIpAddress,
                     context.Connection.RemoteIpAddress,

+ 4 - 4
Emby.Server.Implementations/HttpServer/WebSocketConnection.cs

@@ -19,7 +19,7 @@ namespace Emby.Server.Implementations.HttpServer
     /// <summary>
     /// <summary>
     /// Class WebSocketConnection.
     /// Class WebSocketConnection.
     /// </summary>
     /// </summary>
-    public class WebSocketConnection : IWebSocketConnection
+    public class WebSocketConnection : IWebSocketConnection, IDisposable
     {
     {
         /// <summary>
         /// <summary>
         /// The logger.
         /// The logger.
@@ -119,7 +119,7 @@ namespace Emby.Server.Implementations.HttpServer
                 Memory<byte> memory = writer.GetMemory(512);
                 Memory<byte> memory = writer.GetMemory(512);
                 try
                 try
                 {
                 {
-                    receiveresult = await _socket.ReceiveAsync(memory, cancellationToken);
+                    receiveresult = await _socket.ReceiveAsync(memory, cancellationToken).ConfigureAwait(false);
                 }
                 }
                 catch (WebSocketException ex)
                 catch (WebSocketException ex)
                 {
                 {
@@ -137,7 +137,7 @@ namespace Emby.Server.Implementations.HttpServer
                 writer.Advance(bytesRead);
                 writer.Advance(bytesRead);
 
 
                 // Make the data available to the PipeReader
                 // Make the data available to the PipeReader
-                FlushResult flushResult = await writer.FlushAsync();
+                FlushResult flushResult = await writer.FlushAsync().ConfigureAwait(false);
                 if (flushResult.IsCompleted)
                 if (flushResult.IsCompleted)
                 {
                 {
                     // The PipeReader stopped reading
                     // The PipeReader stopped reading
@@ -223,7 +223,7 @@ namespace Emby.Server.Implementations.HttpServer
 
 
             if (info.MessageType.Equals("KeepAlive", StringComparison.Ordinal))
             if (info.MessageType.Equals("KeepAlive", StringComparison.Ordinal))
             {
             {
-                await SendKeepAliveResponse();
+                await SendKeepAliveResponse().ConfigureAwait(false);
             }
             }
             else
             else
             {
             {

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

@@ -1828,7 +1828,7 @@ namespace Jellyfin.Api.Controllers
                 }
                 }
 
 
                 audioTranscodeParams.Add("-vn");
                 audioTranscodeParams.Add("-vn");
-                return string.Join(" ", audioTranscodeParams.ToArray());
+                return string.Join(' ', audioTranscodeParams);
             }
             }
 
 
             if (EncodingHelper.IsCopyCodec(audioCodec))
             if (EncodingHelper.IsCopyCodec(audioCodec))

+ 1 - 2
Jellyfin.Api/Controllers/SubtitleController.cs

@@ -262,8 +262,6 @@ namespace Jellyfin.Api.Controllers
 
 
             var mediaSource = await _mediaSourceManager.GetMediaSource(item, mediaSourceId, null, false, CancellationToken.None).ConfigureAwait(false);
             var mediaSource = await _mediaSourceManager.GetMediaSource(item, mediaSourceId, null, false, CancellationToken.None).ConfigureAwait(false);
 
 
-            var builder = new StringBuilder();
-
             var runtime = mediaSource.RunTimeTicks ?? -1;
             var runtime = mediaSource.RunTimeTicks ?? -1;
 
 
             if (runtime <= 0)
             if (runtime <= 0)
@@ -277,6 +275,7 @@ namespace Jellyfin.Api.Controllers
                 throw new ArgumentException("segmentLength was not given, or it was given incorrectly. (It should be bigger than 0)");
                 throw new ArgumentException("segmentLength was not given, or it was given incorrectly. (It should be bigger than 0)");
             }
             }
 
 
+            var builder = new StringBuilder();
             builder.AppendLine("#EXTM3U")
             builder.AppendLine("#EXTM3U")
                 .Append("#EXT-X-TARGETDURATION:")
                 .Append("#EXT-X-TARGETDURATION:")
                 .AppendLine(segmentLength.ToString(CultureInfo.InvariantCulture))
                 .AppendLine(segmentLength.ToString(CultureInfo.InvariantCulture))

+ 3 - 2
Jellyfin.Server.Implementations/Activity/ActivityManager.cs

@@ -40,8 +40,9 @@ namespace Jellyfin.Server.Implementations.Activity
         /// <inheritdoc/>
         /// <inheritdoc/>
         public async Task CreateAsync(ActivityLog entry)
         public async Task CreateAsync(ActivityLog entry)
         {
         {
-            using var dbContext = _provider.CreateContext();
-            await dbContext.ActivityLogs.AddAsync(entry);
+            await using var dbContext = _provider.CreateContext();
+
+            dbContext.ActivityLogs.Add(entry);
             await dbContext.SaveChangesAsync().ConfigureAwait(false);
             await dbContext.SaveChangesAsync().ConfigureAwait(false);
 
 
             EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(ConvertToOldModel(entry)));
             EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(ConvertToOldModel(entry)));

+ 2 - 0
MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
 using System;
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Concurrent;
 using System.Diagnostics;
 using System.Diagnostics;

+ 14 - 5
MediaBrowser.MediaEncoding/BdInfo/BdInfoDirectoryInfo.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
 using System;
 using System;
 using System.Linq;
 using System.Linq;
 using BDInfo.IO;
 using BDInfo.IO;
@@ -5,7 +7,7 @@ using MediaBrowser.Model.IO;
 
 
 namespace MediaBrowser.MediaEncoding.BdInfo
 namespace MediaBrowser.MediaEncoding.BdInfo
 {
 {
-    class BdInfoDirectoryInfo : IDirectoryInfo
+    public class BdInfoDirectoryInfo : IDirectoryInfo
     {
     {
         private readonly IFileSystem _fileSystem = null;
         private readonly IFileSystem _fileSystem = null;
 
 
@@ -43,25 +45,32 @@ namespace MediaBrowser.MediaEncoding.BdInfo
 
 
         public IDirectoryInfo[] GetDirectories()
         public IDirectoryInfo[] GetDirectories()
         {
         {
-            return Array.ConvertAll(_fileSystem.GetDirectories(_impl.FullName).ToArray(),
+            return Array.ConvertAll(
+                _fileSystem.GetDirectories(_impl.FullName).ToArray(),
                 x => new BdInfoDirectoryInfo(_fileSystem, x));
                 x => new BdInfoDirectoryInfo(_fileSystem, x));
         }
         }
 
 
         public IFileInfo[] GetFiles()
         public IFileInfo[] GetFiles()
         {
         {
-            return Array.ConvertAll(_fileSystem.GetFiles(_impl.FullName).ToArray(),
+            return Array.ConvertAll(
+                _fileSystem.GetFiles(_impl.FullName).ToArray(),
                 x => new BdInfoFileInfo(x));
                 x => new BdInfoFileInfo(x));
         }
         }
 
 
         public IFileInfo[] GetFiles(string searchPattern)
         public IFileInfo[] GetFiles(string searchPattern)
         {
         {
-            return Array.ConvertAll(_fileSystem.GetFiles(_impl.FullName, new[] { searchPattern }, false, false).ToArray(),
+            return Array.ConvertAll(
+                _fileSystem.GetFiles(_impl.FullName, new[] { searchPattern }, false, false).ToArray(),
                 x => new BdInfoFileInfo(x));
                 x => new BdInfoFileInfo(x));
         }
         }
 
 
         public IFileInfo[] GetFiles(string searchPattern, System.IO.SearchOption searchOption)
         public IFileInfo[] GetFiles(string searchPattern, System.IO.SearchOption searchOption)
         {
         {
-            return Array.ConvertAll(_fileSystem.GetFiles(_impl.FullName, new[] { searchPattern }, false,
+            return Array.ConvertAll(
+                _fileSystem.GetFiles(
+                    _impl.FullName,
+                    new[] { searchPattern },
+                    false,
                     searchOption.HasFlag(System.IO.SearchOption.AllDirectories)).ToArray(),
                     searchOption.HasFlag(System.IO.SearchOption.AllDirectories)).ToArray(),
                 x => new BdInfoFileInfo(x));
                 x => new BdInfoFileInfo(x));
         }
         }

+ 4 - 0
MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs

@@ -15,6 +15,10 @@ namespace MediaBrowser.MediaEncoding.BdInfo
     {
     {
         private readonly IFileSystem _fileSystem;
         private readonly IFileSystem _fileSystem;
 
 
+        /// <summary>
+        /// Initializes a new instance of the <see cref="BdInfoExaminer" /> class.
+        /// </summary>
+        /// <param name="fileSystem">The filesystem.</param>
         public BdInfoExaminer(IFileSystem fileSystem)
         public BdInfoExaminer(IFileSystem fileSystem)
         {
         {
             _fileSystem = fileSystem;
             _fileSystem = fileSystem;

+ 11 - 8
MediaBrowser.MediaEncoding/BdInfo/BdInfoFileInfo.cs

@@ -1,11 +1,18 @@
+#pragma warning disable CS1591
+
 using System.IO;
 using System.IO;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
 
 
 namespace MediaBrowser.MediaEncoding.BdInfo
 namespace MediaBrowser.MediaEncoding.BdInfo
 {
 {
-    class BdInfoFileInfo : BDInfo.IO.IFileInfo
+    public class BdInfoFileInfo : BDInfo.IO.IFileInfo
     {
     {
-        FileSystemMetadata _impl = null;
+        private FileSystemMetadata _impl = null;
+
+        public BdInfoFileInfo(FileSystemMetadata impl)
+        {
+            _impl = impl;
+        }
 
 
         public string Name => _impl.Name;
         public string Name => _impl.Name;
 
 
@@ -17,14 +24,10 @@ namespace MediaBrowser.MediaEncoding.BdInfo
 
 
         public bool IsDir => _impl.IsDirectory;
         public bool IsDir => _impl.IsDirectory;
 
 
-        public BdInfoFileInfo(FileSystemMetadata impl)
-        {
-            _impl = impl;
-        }
-
         public System.IO.Stream OpenRead()
         public System.IO.Stream OpenRead()
         {
         {
-            return new FileStream(FullName,
+            return new FileStream(
+                FullName,
                 FileMode.Open,
                 FileMode.Open,
                 FileAccess.Read,
                 FileAccess.Read,
                 FileShare.Read);
                 FileShare.Read);

+ 2 - 32
MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationFactory.cs

@@ -1,9 +1,7 @@
-using System;
+#pragma warning disable CS1591
+
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Configuration;
-using MediaBrowser.Model.Configuration;
 
 
 namespace MediaBrowser.MediaEncoding.Configuration
 namespace MediaBrowser.MediaEncoding.Configuration
 {
 {
@@ -17,32 +15,4 @@ namespace MediaBrowser.MediaEncoding.Configuration
             };
             };
         }
         }
     }
     }
-
-    public class EncodingConfigurationStore : ConfigurationStore, IValidatingConfiguration
-    {
-        public EncodingConfigurationStore()
-        {
-            ConfigurationType = typeof(EncodingOptions);
-            Key = "encoding";
-        }
-
-        public void Validate(object oldConfig, object newConfig)
-        {
-            var newPath = ((EncodingOptions)newConfig).TranscodingTempPath;
-
-            if (!string.IsNullOrWhiteSpace(newPath)
-                && !string.Equals(((EncodingOptions)oldConfig).TranscodingTempPath, newPath, StringComparison.Ordinal))
-            {
-                // Validate
-                if (!Directory.Exists(newPath))
-                {
-                    throw new DirectoryNotFoundException(
-                        string.Format(
-                            CultureInfo.InvariantCulture,
-                            "{0} does not exist.",
-                            newPath));
-                }
-            }
-        }
-    }
 }
 }

+ 38 - 0
MediaBrowser.MediaEncoding/Configuration/EncodingConfigurationStore.cs

@@ -0,0 +1,38 @@
+#pragma warning disable CS1591
+
+using System;
+using System.Globalization;
+using System.IO;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Model.Configuration;
+
+namespace MediaBrowser.MediaEncoding.Configuration
+{
+    public class EncodingConfigurationStore : ConfigurationStore, IValidatingConfiguration
+    {
+        public EncodingConfigurationStore()
+        {
+            ConfigurationType = typeof(EncodingOptions);
+            Key = "encoding";
+        }
+
+        public void Validate(object oldConfig, object newConfig)
+        {
+            var newPath = ((EncodingOptions)newConfig).TranscodingTempPath;
+
+            if (!string.IsNullOrWhiteSpace(newPath)
+                && !string.Equals(((EncodingOptions)oldConfig).TranscodingTempPath, newPath, StringComparison.Ordinal))
+            {
+                // Validate
+                if (!Directory.Exists(newPath))
+                {
+                    throw new DirectoryNotFoundException(
+                        string.Format(
+                            CultureInfo.InvariantCulture,
+                            "{0} does not exist.",
+                            newPath));
+                }
+            }
+        }
+    }
+}

+ 17 - 15
MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Diagnostics;
@@ -12,7 +14,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
     {
     {
         private const string DefaultEncoderPath = "ffmpeg";
         private const string DefaultEncoderPath = "ffmpeg";
 
 
-        private static readonly string[] requiredDecoders = new[]
+        private static readonly string[] _requiredDecoders = new[]
         {
         {
             "h264",
             "h264",
             "hevc",
             "hevc",
@@ -55,7 +57,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             "vc1_opencl"
             "vc1_opencl"
         };
         };
 
 
-        private static readonly string[] requiredEncoders = new[]
+        private static readonly string[] _requiredEncoders = new[]
         {
         {
             "libx264",
             "libx264",
             "libx265",
             "libx265",
@@ -110,6 +112,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
             _encoderPath = encoderPath;
             _encoderPath = encoderPath;
         }
         }
 
 
+        private enum Codec
+        {
+            Encoder,
+            Decoder
+        }
+
         public static Version MinVersion { get; } = new Version(4, 0);
         public static Version MinVersion { get; } = new Version(4, 0);
 
 
         public static Version MaxVersion { get; } = null;
         public static Version MaxVersion { get; } = null;
@@ -193,8 +201,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// If that fails then we use one of the main libraries to determine if it's new/older than the latest
         /// If that fails then we use one of the main libraries to determine if it's new/older than the latest
         /// we have stored.
         /// we have stored.
         /// </summary>
         /// </summary>
-        /// <param name="output"></param>
-        /// <returns></returns>
+        /// <param name="output">The output from "ffmpeg -version".</param>
+        /// <returns>The FFmpeg version.</returns>
         internal static Version GetFFmpegVersion(string output)
         internal static Version GetFFmpegVersion(string output)
         {
         {
             // For pre-built binaries the FFmpeg version should be mentioned at the very start of the output
             // For pre-built binaries the FFmpeg version should be mentioned at the very start of the output
@@ -216,10 +224,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
 
         /// <summary>
         /// <summary>
         /// Grabs the library names and major.minor version numbers from the 'ffmpeg -version' output
         /// Grabs the library names and major.minor version numbers from the 'ffmpeg -version' output
-        /// and condenses them on to one line.  Output format is "name1=major.minor,name2=major.minor,etc."
+        /// and condenses them on to one line.  Output format is "name1=major.minor,name2=major.minor,etc.".
         /// </summary>
         /// </summary>
-        /// <param name="output"></param>
-        /// <returns></returns>
+        /// <param name="output">The 'ffmpeg -version' output.</param>
+        /// <returns>The library names and major.minor version numbers.</returns>
         private static string GetLibrariesVersionString(string output)
         private static string GetLibrariesVersionString(string output)
         {
         {
             var rc = new StringBuilder(144);
             var rc = new StringBuilder(144);
@@ -239,12 +247,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
             return rc.Length == 0 ? null : rc.ToString();
             return rc.Length == 0 ? null : rc.ToString();
         }
         }
 
 
-        private enum Codec
-        {
-            Encoder,
-            Decoder
-        }
-
         private IEnumerable<string> GetHwaccelTypes()
         private IEnumerable<string> GetHwaccelTypes()
         {
         {
             string output = null;
             string output = null;
@@ -262,7 +264,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 return Enumerable.Empty<string>();
                 return Enumerable.Empty<string>();
             }
             }
 
 
-            var found = output.Split(new char[] {'\r','\n'}, StringSplitOptions.RemoveEmptyEntries).Skip(1).Distinct().ToList();
+            var found = output.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Skip(1).Distinct().ToList();
             _logger.LogInformation("Available hwaccel types: {Types}", found);
             _logger.LogInformation("Available hwaccel types: {Types}", found);
 
 
             return found;
             return found;
@@ -286,7 +288,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 return Enumerable.Empty<string>();
                 return Enumerable.Empty<string>();
             }
             }
 
 
-            var required = codec == Codec.Encoder ? requiredEncoders : requiredDecoders;
+            var required = codec == Codec.Encoder ? _requiredEncoders : _requiredDecoders;
 
 
             var found = Regex
             var found = Regex
                 .Matches(output, @"^\s\S{6}\s(?<codec>[\w|-]+)\s+.+$", RegexOptions.Multiline)
                 .Matches(output, @"^\s\S{6}\s(?<codec>[\w|-]+)\s+.+$", RegexOptions.Multiline)

+ 2 - 0
MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.MediaInfo;

+ 47 - 30
MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs

@@ -1,5 +1,8 @@
+#pragma warning disable CS1591
+
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Globalization;
 using System.Globalization;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
@@ -19,9 +22,8 @@ using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.System;
 using MediaBrowser.Model.System;
-using Microsoft.Extensions.Logging;
-using System.Diagnostics;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
 
 
 namespace MediaBrowser.MediaEncoding.Encoder
 namespace MediaBrowser.MediaEncoding.Encoder
 {
 {
@@ -35,6 +37,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// </summary>
         /// </summary>
         internal const int DefaultImageExtractionTimeout = 5000;
         internal const int DefaultImageExtractionTimeout = 5000;
 
 
+        /// <summary>
+        /// The us culture.
+        /// </summary>
+        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+
         private readonly ILogger<MediaEncoder> _logger;
         private readonly ILogger<MediaEncoder> _logger;
         private readonly IServerConfigurationManager _configurationManager;
         private readonly IServerConfigurationManager _configurationManager;
         private readonly IFileSystem _fileSystem;
         private readonly IFileSystem _fileSystem;
@@ -47,6 +54,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
         private readonly object _runningProcessesLock = new object();
         private readonly object _runningProcessesLock = new object();
         private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
         private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
 
 
+        private List<string> _encoders = new List<string>();
+        private List<string> _decoders = new List<string>();
+        private List<string> _hwaccels = new List<string>();
+
         private string _ffmpegPath = string.Empty;
         private string _ffmpegPath = string.Empty;
         private string _ffprobePath;
         private string _ffprobePath;
 
 
@@ -77,7 +88,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// <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.
-        /// Precedence is: Config > CLI > $PATH
+        /// Precedence is: Config > CLI > $PATH.
         /// </summary>
         /// </summary>
         public void SetFFmpegPath()
         public void SetFFmpegPath()
         {
         {
@@ -122,8 +133,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// Triggered from the Settings > Transcoding UI page when users submits Custom FFmpeg path to use.
         /// Triggered from the Settings > Transcoding UI page when users submits Custom FFmpeg path to use.
         /// Only write the new path to xml if it exists.  Do not perform validation checks on ffmpeg here.
         /// Only write the new path to xml if it exists.  Do not perform validation checks on ffmpeg here.
         /// </summary>
         /// </summary>
-        /// <param name="path"></param>
-        /// <param name="pathType"></param>
+        /// <param name="path">The path.</param>
+        /// <param name="pathType">The path type.</param>
         public void UpdateEncoderPath(string path, string pathType)
         public void UpdateEncoderPath(string path, string pathType)
         {
         {
             string newPath;
             string newPath;
@@ -168,8 +179,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// If checks pass, global variable FFmpegPath and EncoderLocation are updated.
         /// If checks pass, global variable FFmpegPath and EncoderLocation are updated.
         /// </summary>
         /// </summary>
         /// <param name="path">FQPN to test.</param>
         /// <param name="path">FQPN to test.</param>
-        /// <param name="location">Location (External, Custom, System) of tool</param>
-        /// <returns></returns>
+        /// <param name="location">Location (External, Custom, System) of tool.</param>
+        /// <returns><c>true</c> if the version validation succeeded; otherwise, <c>false</c>.</returns>
         private bool ValidatePath(string path, FFmpegLocation location)
         private bool ValidatePath(string path, FFmpegLocation location)
         {
         {
             bool rc = false;
             bool rc = false;
@@ -221,8 +232,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// <summary>
         /// <summary>
         /// Search the system $PATH environment variable looking for given filename.
         /// Search the system $PATH environment variable looking for given filename.
         /// </summary>
         /// </summary>
-        /// <param name="fileName"></param>
-        /// <returns></returns>
+        /// <param name="fileName">The filename.</param>
+        /// <returns>The full path to the file.</returns>
         private string ExistsOnSystemPath(string fileName)
         private string ExistsOnSystemPath(string fileName)
         {
         {
             var inJellyfinPath = GetEncoderPathFromDirectory(AppContext.BaseDirectory, fileName, recursive: true);
             var inJellyfinPath = GetEncoderPathFromDirectory(AppContext.BaseDirectory, fileName, recursive: true);
@@ -246,25 +257,19 @@ namespace MediaBrowser.MediaEncoding.Encoder
             return null;
             return null;
         }
         }
 
 
-        private List<string> _encoders = new List<string>();
         public void SetAvailableEncoders(IEnumerable<string> list)
         public void SetAvailableEncoders(IEnumerable<string> list)
         {
         {
             _encoders = list.ToList();
             _encoders = list.ToList();
-            // _logger.Info("Supported encoders: {0}", string.Join(",", list.ToArray()));
         }
         }
 
 
-        private List<string> _decoders = new List<string>();
         public void SetAvailableDecoders(IEnumerable<string> list)
         public void SetAvailableDecoders(IEnumerable<string> list)
         {
         {
             _decoders = list.ToList();
             _decoders = list.ToList();
-            // _logger.Info("Supported decoders: {0}", string.Join(",", list.ToArray()));
         }
         }
 
 
-        private List<string> _hwaccels = new List<string>();
         public void SetAvailableHwaccels(IEnumerable<string> list)
         public void SetAvailableHwaccels(IEnumerable<string> list)
         {
         {
             _hwaccels = list.ToList();
             _hwaccels = list.ToList();
-            //_logger.Info("Supported hwaccels: {0}", string.Join(",", list.ToArray()));
         }
         }
 
 
         public bool SupportsEncoder(string encoder)
         public bool SupportsEncoder(string encoder)
@@ -332,8 +337,16 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
 
             var forceEnableLogging = request.MediaSource.Protocol != MediaProtocol.File;
             var forceEnableLogging = request.MediaSource.Protocol != MediaProtocol.File;
 
 
-            return GetMediaInfoInternal(GetInputArgument(inputFiles, request.MediaSource.Protocol), request.MediaSource.Path, request.MediaSource.Protocol, extractChapters,
-                probeSize, request.MediaType == DlnaProfileType.Audio, request.MediaSource.VideoType, forceEnableLogging, cancellationToken);
+            return GetMediaInfoInternal(
+                GetInputArgument(inputFiles, request.MediaSource.Protocol),
+                request.MediaSource.Path,
+                request.MediaSource.Protocol,
+                extractChapters,
+                probeSize,
+                request.MediaType == DlnaProfileType.Audio,
+                request.MediaSource.VideoType,
+                forceEnableLogging,
+                cancellationToken);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -342,7 +355,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// <param name="inputFiles">The input files.</param>
         /// <param name="inputFiles">The input files.</param>
         /// <param name="protocol">The protocol.</param>
         /// <param name="protocol">The protocol.</param>
         /// <returns>System.String.</returns>
         /// <returns>System.String.</returns>
-        /// <exception cref="ArgumentException">Unrecognized InputType</exception>
+        /// <exception cref="ArgumentException">Unrecognized InputType.</exception>
         public string GetInputArgument(IReadOnlyList<string> inputFiles, MediaProtocol protocol)
         public string GetInputArgument(IReadOnlyList<string> inputFiles, MediaProtocol protocol)
             => EncodingUtils.GetInputArgument(inputFiles, protocol);
             => EncodingUtils.GetInputArgument(inputFiles, protocol);
 
 
@@ -350,7 +363,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// Gets the media info internal.
         /// Gets the media info internal.
         /// </summary>
         /// </summary>
         /// <returns>Task{MediaInfoResult}.</returns>
         /// <returns>Task{MediaInfoResult}.</returns>
-        private async Task<MediaInfo> GetMediaInfoInternal(string inputPath,
+        private async Task<MediaInfo> GetMediaInfoInternal(
+            string inputPath,
             string primaryPath,
             string primaryPath,
             MediaProtocol protocol,
             MediaProtocol protocol,
             bool extractChapters,
             bool extractChapters,
@@ -378,7 +392,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
                     FileName = _ffprobePath,
                     FileName = _ffprobePath,
                     Arguments = args,
                     Arguments = args,
 
 
-
                     WindowStyle = ProcessWindowStyle.Hidden,
                     WindowStyle = ProcessWindowStyle.Hidden,
                     ErrorDialog = false,
                     ErrorDialog = false,
                 },
                 },
@@ -439,11 +452,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
             }
             }
         }
         }
 
 
-        /// <summary>
-        /// The us culture.
-        /// </summary>
-        protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
         public Task<string> ExtractAudioImage(string path, int? imageStreamIndex, CancellationToken cancellationToken)
         public Task<string> ExtractAudioImage(string path, int? imageStreamIndex, CancellationToken cancellationToken)
         {
         {
             return ExtractImage(new[] { path }, null, null, imageStreamIndex, MediaProtocol.File, true, null, null, cancellationToken);
             return ExtractImage(new[] { path }, null, null, imageStreamIndex, MediaProtocol.File, true, null, null, cancellationToken);
@@ -459,8 +467,16 @@ namespace MediaBrowser.MediaEncoding.Encoder
             return ExtractImage(inputFiles, container, imageStream, imageStreamIndex, protocol, false, null, null, cancellationToken);
             return ExtractImage(inputFiles, container, imageStream, imageStreamIndex, protocol, false, null, null, cancellationToken);
         }
         }
 
 
-        private async Task<string> ExtractImage(string[] inputFiles, string container, MediaStream videoStream, int? imageStreamIndex, MediaProtocol protocol, bool isAudio,
-            Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken)
+        private async Task<string> ExtractImage(
+            string[] inputFiles,
+            string container,
+            MediaStream videoStream,
+            int? imageStreamIndex,
+            MediaProtocol protocol,
+            bool isAudio,
+            Video3DFormat? threedFormat,
+            TimeSpan? offset,
+            CancellationToken cancellationToken)
         {
         {
             var inputArgument = GetInputArgument(inputFiles, protocol);
             var inputArgument = GetInputArgument(inputFiles, protocol);
 
 
@@ -645,7 +661,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
 
         public string GetTimeParameter(TimeSpan time)
         public string GetTimeParameter(TimeSpan time)
         {
         {
-            return time.ToString(@"hh\:mm\:ss\.fff", UsCulture);
+            return time.ToString(@"hh\:mm\:ss\.fff", _usCulture);
         }
         }
 
 
         public async Task ExtractVideoImagesOnInterval(
         public async Task ExtractVideoImagesOnInterval(
@@ -662,11 +678,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
         {
         {
             var inputArgument = GetInputArgument(inputFiles, protocol);
             var inputArgument = GetInputArgument(inputFiles, protocol);
 
 
-            var vf = "fps=fps=1/" + interval.TotalSeconds.ToString(UsCulture);
+            var vf = "fps=fps=1/" + interval.TotalSeconds.ToString(_usCulture);
 
 
             if (maxWidth.HasValue)
             if (maxWidth.HasValue)
             {
             {
-                var maxWidthParam = maxWidth.Value.ToString(UsCulture);
+                var maxWidthParam = maxWidth.Value.ToString(_usCulture);
 
 
                 vf += string.Format(",scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam);
                 vf += string.Format(",scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam);
             }
             }
@@ -859,6 +875,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             if (dispose)
             if (dispose)
             {
             {
                 StopProcesses();
                 StopProcesses();
+                _thumbnailResourcePool.Dispose();
             }
             }
         }
         }
 
 

+ 13 - 0
MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj

@@ -9,6 +9,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>
@@ -27,4 +28,16 @@
     <PackageReference Include="UTF.Unknown" Version="2.3.0" />
     <PackageReference Include="UTF.Unknown" Version="2.3.0" />
   </ItemGroup>
   </ItemGroup>
 
 
+  <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+    <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
+  </PropertyGroup>
+
+  <!-- Code Analyzers-->
+  <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
+    <!-- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" 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>
+
 </Project>
 </Project>

+ 3 - 0
MediaBrowser.MediaEncoding/Probing/FFProbeHelpers.cs

@@ -3,6 +3,9 @@ using System.Collections.Generic;
 
 
 namespace MediaBrowser.MediaEncoding.Probing
 namespace MediaBrowser.MediaEncoding.Probing
 {
 {
+    /// <summary>
+    /// Class containing helper methods for working with FFprobe output.
+    /// </summary>
     public static class FFProbeHelpers
     public static class FFProbeHelpers
     {
     {
         /// <summary>
         /// <summary>

+ 2 - 0
MediaBrowser.MediaEncoding/Probing/MediaChapter.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Text.Json.Serialization;
 using System.Text.Json.Serialization;
 
 

+ 4 - 0
MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs

@@ -269,6 +269,10 @@ namespace MediaBrowser.MediaEncoding.Probing
         [JsonPropertyName("loro_surmixlev")]
         [JsonPropertyName("loro_surmixlev")]
         public string LoroSurmixlev { get; set; }
         public string LoroSurmixlev { get; set; }
 
 
+        /// <summary>
+        /// Gets or sets the field_order.
+        /// </summary>
+        /// <value>The field_order.</value>
         [JsonPropertyName("field_order")]
         [JsonPropertyName("field_order")]
         public string FieldOrder { get; set; }
         public string FieldOrder { get; set; }
 
 

+ 32 - 35
MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Globalization;
@@ -16,10 +18,19 @@ namespace MediaBrowser.MediaEncoding.Probing
 {
 {
     public class ProbeResultNormalizer
     public class ProbeResultNormalizer
     {
     {
+        // When extracting subtitles, the maximum length to consider (to avoid invalid filenames)
+        private const int MaxSubtitleDescriptionExtractionLength = 100;
+
+        private const string ArtistReplaceValue = " | ";
+
+        private readonly char[] _nameDelimiters = { '/', '|', ';', '\\' };
+
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
         private readonly ILogger _logger;
         private readonly ILogger _logger;
         private readonly ILocalizationManager _localization;
         private readonly ILocalizationManager _localization;
 
 
+        private List<string> _splitWhiteList = null;
+
         public ProbeResultNormalizer(ILogger logger, ILocalizationManager localization)
         public ProbeResultNormalizer(ILogger logger, ILocalizationManager localization)
         {
         {
             _logger = logger;
             _logger = logger;
@@ -368,7 +379,6 @@ namespace MediaBrowser.MediaEncoding.Probing
 
 
         private List<NameValuePair> ReadValueArray(XmlReader reader)
         private List<NameValuePair> ReadValueArray(XmlReader reader)
         {
         {
-
             var pairs = new List<NameValuePair>();
             var pairs = new List<NameValuePair>();
 
 
             reader.MoveToContent();
             reader.MoveToContent();
@@ -949,50 +959,46 @@ namespace MediaBrowser.MediaEncoding.Probing
 
 
         private void SetAudioInfoFromTags(MediaInfo audio, Dictionary<string, string> tags)
         private void SetAudioInfoFromTags(MediaInfo audio, Dictionary<string, string> tags)
         {
         {
+            var peoples = new List<BaseItemPerson>();
             var composer = FFProbeHelpers.GetDictionaryValue(tags, "composer");
             var composer = FFProbeHelpers.GetDictionaryValue(tags, "composer");
             if (!string.IsNullOrWhiteSpace(composer))
             if (!string.IsNullOrWhiteSpace(composer))
             {
             {
-                var peoples = new List<BaseItemPerson>();
                 foreach (var person in Split(composer, false))
                 foreach (var person in Split(composer, false))
                 {
                 {
                     peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Composer });
                     peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Composer });
                 }
                 }
-
-                audio.People = peoples.ToArray();
             }
             }
 
 
-            // var conductor = FFProbeHelpers.GetDictionaryValue(tags, "conductor");
-            // if (!string.IsNullOrWhiteSpace(conductor))
-            //{
-            //    foreach (var person in Split(conductor, false))
-            //    {
-            //        audio.People.Add(new BaseItemPerson { Name = person, Type = PersonType.Conductor });
-            //    }
-            //}
+            var conductor = FFProbeHelpers.GetDictionaryValue(tags, "conductor");
+            if (!string.IsNullOrWhiteSpace(conductor))
+            {
+                foreach (var person in Split(conductor, false))
+                {
+                    peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Conductor });
+                }
+            }
 
 
-            // var lyricist = FFProbeHelpers.GetDictionaryValue(tags, "lyricist");
-            // if (!string.IsNullOrWhiteSpace(lyricist))
-            //{
-            //    foreach (var person in Split(lyricist, false))
-            //    {
-            //        audio.People.Add(new BaseItemPerson { Name = person, Type = PersonType.Lyricist });
-            //    }
-            //}
+            var lyricist = FFProbeHelpers.GetDictionaryValue(tags, "lyricist");
+            if (!string.IsNullOrWhiteSpace(lyricist))
+            {
+                foreach (var person in Split(lyricist, false))
+                {
+                    peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Lyricist });
+                }
+            }
 
 
             // Check for writer some music is tagged that way as alternative to composer/lyricist
             // Check for writer some music is tagged that way as alternative to composer/lyricist
             var writer = FFProbeHelpers.GetDictionaryValue(tags, "writer");
             var writer = FFProbeHelpers.GetDictionaryValue(tags, "writer");
 
 
             if (!string.IsNullOrWhiteSpace(writer))
             if (!string.IsNullOrWhiteSpace(writer))
             {
             {
-                var peoples = new List<BaseItemPerson>();
                 foreach (var person in Split(writer, false))
                 foreach (var person in Split(writer, false))
                 {
                 {
                     peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Writer });
                     peoples.Add(new BaseItemPerson { Name = person, Type = PersonType.Writer });
                 }
                 }
-
-                audio.People = peoples.ToArray();
             }
             }
 
 
+            audio.People = peoples.ToArray();
             audio.Album = FFProbeHelpers.GetDictionaryValue(tags, "album");
             audio.Album = FFProbeHelpers.GetDictionaryValue(tags, "album");
 
 
             var artists = FFProbeHelpers.GetDictionaryValue(tags, "artists");
             var artists = FFProbeHelpers.GetDictionaryValue(tags, "artists");
@@ -1117,8 +1123,6 @@ namespace MediaBrowser.MediaEncoding.Probing
                 .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i));
                 .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i));
         }
         }
 
 
-        private readonly char[] _nameDelimiters = { '/', '|', ';', '\\' };
-
         /// <summary>
         /// <summary>
         /// Splits the specified val.
         /// Splits the specified val.
         /// </summary>
         /// </summary>
@@ -1138,8 +1142,6 @@ namespace MediaBrowser.MediaEncoding.Probing
                 .Select(i => i.Trim());
                 .Select(i => i.Trim());
         }
         }
 
 
-        private const string ArtistReplaceValue = " | ";
-
         private IEnumerable<string> SplitArtists(string val, char[] delimiters, bool splitFeaturing)
         private IEnumerable<string> SplitArtists(string val, char[] delimiters, bool splitFeaturing)
         {
         {
             if (splitFeaturing)
             if (splitFeaturing)
@@ -1169,9 +1171,6 @@ namespace MediaBrowser.MediaEncoding.Probing
             return artistsFound;
             return artistsFound;
         }
         }
 
 
-
-        private List<string> _splitWhiteList = null;
-
         private IEnumerable<string> GetSplitWhitelist()
         private IEnumerable<string> GetSplitWhitelist()
         {
         {
             if (_splitWhiteList == null)
             if (_splitWhiteList == null)
@@ -1248,7 +1247,7 @@ namespace MediaBrowser.MediaEncoding.Probing
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Gets the disc number, which is sometimes can be in the form of '1', or '1/3'
+        /// Gets the disc number, which is sometimes can be in the form of '1', or '1/3'.
         /// </summary>
         /// </summary>
         /// <param name="tags">The tags.</param>
         /// <param name="tags">The tags.</param>
         /// <param name="tagName">Name of the tag.</param>
         /// <param name="tagName">Name of the tag.</param>
@@ -1294,8 +1293,6 @@ namespace MediaBrowser.MediaEncoding.Probing
             return info;
             return info;
         }
         }
 
 
-        private const int MaxSubtitleDescriptionExtractionLength = 100; // When extracting subtitles, the maximum length to consider (to avoid invalid filenames)
-
         private void FetchWtvInfo(MediaInfo video, InternalMediaInfoResult data)
         private void FetchWtvInfo(MediaInfo video, InternalMediaInfoResult data)
         {
         {
             if (data.Format == null || data.Format.Tags == null)
             if (data.Format == null || data.Format.Tags == null)
@@ -1380,8 +1377,8 @@ namespace MediaBrowser.MediaEncoding.Probing
                         if (subtitle.Contains('/', StringComparison.Ordinal)) // It contains a episode number and season number
                         if (subtitle.Contains('/', StringComparison.Ordinal)) // It contains a episode number and season number
                         {
                         {
                             string[] numbers = subtitle.Split(' ');
                             string[] numbers = subtitle.Split(' ');
-                            video.IndexNumber = int.Parse(numbers[0].Replace(".", "").Split('/')[0]);
-                            int totalEpisodesInSeason = int.Parse(numbers[0].Replace(".", "").Split('/')[1]);
+                            video.IndexNumber = int.Parse(numbers[0].Replace(".", string.Empty, StringComparison.Ordinal).Split('/')[0]);
+                            int totalEpisodesInSeason = int.Parse(numbers[0].Replace(".", string.Empty, StringComparison.Ordinal).Split('/')[1]);
 
 
                             description = string.Join(" ", numbers, 1, numbers.Length - 1).Trim(); // Skip the first, concatenate the rest, clean up spaces and save it
                             description = string.Join(" ", numbers, 1, numbers.Length - 1).Trim(); // Skip the first, concatenate the rest, clean up spaces and save it
                         }
                         }

+ 11 - 10
MediaBrowser.MediaEncoding/Subtitles/AssParser.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Globalization;
@@ -13,6 +15,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
     {
     {
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
 
 
+        /// <inheritdoc />
         public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
         public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
         {
         {
             var trackInfo = new SubtitleTrackInfo();
             var trackInfo = new SubtitleTrackInfo();
@@ -22,7 +25,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
             {
             {
                 string line;
                 string line;
                 while (reader.ReadLine() != "[Events]")
                 while (reader.ReadLine() != "[Events]")
-                { }
+                {
+                }
 
 
                 var headers = ParseFieldHeaders(reader.ReadLine());
                 var headers = ParseFieldHeaders(reader.ReadLine());
 
 
@@ -72,17 +76,14 @@ namespace MediaBrowser.MediaEncoding.Subtitles
         {
         {
             var fields = line.Substring(8).Split(',').Select(x => x.Trim()).ToList();
             var fields = line.Substring(8).Split(',').Select(x => x.Trim()).ToList();
 
 
-            var result = new Dictionary<string, int> {
-                                                         {"Start", fields.IndexOf("Start")},
-                                                         {"End", fields.IndexOf("End")},
-                                                         {"Text", fields.IndexOf("Text")}
-                                                     };
-            return result;
+            return new Dictionary<string, int>
+            {
+                { "Start", fields.IndexOf("Start") },
+                { "End", fields.IndexOf("End") },
+                { "Text", fields.IndexOf("Text") }
+            };
         }
         }
 
 
-        /// <summary>
-        /// Credit: https://github.com/SubtitleEdit/subtitleedit/blob/master/src/Logic/SubtitleFormats/AdvancedSubStationAlpha.cs
-        /// </summary>
         private void RemoteNativeFormatting(SubtitleTrackEvent p)
         private void RemoteNativeFormatting(SubtitleTrackEvent p)
         {
         {
             int indexOfBegin = p.Text.IndexOf('{');
             int indexOfBegin = p.Text.IndexOf('{');

+ 2 - 0
MediaBrowser.MediaEncoding/Subtitles/ISubtitleParser.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
 using System.IO;
 using System.IO;
 using System.Threading;
 using System.Threading;
 using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.MediaInfo;

+ 9 - 5
MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs

@@ -8,7 +8,7 @@ using MediaBrowser.Model.MediaInfo;
 namespace MediaBrowser.MediaEncoding.Subtitles
 namespace MediaBrowser.MediaEncoding.Subtitles
 {
 {
     /// <summary>
     /// <summary>
-    /// Credit to https://github.com/SubtitleEdit/subtitleedit/blob/a299dc4407a31796364cc6ad83f0d3786194ba22/src/Logic/SubtitleFormats/SubStationAlpha.cs
+    /// <see href="https://github.com/SubtitleEdit/subtitleedit/blob/a299dc4407a31796364cc6ad83f0d3786194ba22/src/Logic/SubtitleFormats/SubStationAlpha.cs">Credit</see>.
     /// </summary>
     /// </summary>
     public class SsaParser : ISubtitleParser
     public class SsaParser : ISubtitleParser
     {
     {
@@ -179,10 +179,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
         {
         {
             // h:mm:ss.cc
             // h:mm:ss.cc
             string[] timeCode = time.Split(':', '.');
             string[] timeCode = time.Split(':', '.');
-            return new TimeSpan(0, int.Parse(timeCode[0]),
-                                int.Parse(timeCode[1]),
-                                int.Parse(timeCode[2]),
-                                int.Parse(timeCode[3]) * 10).Ticks;
+            return new TimeSpan(
+                0,
+                int.Parse(timeCode[0]),
+                int.Parse(timeCode[1]),
+                int.Parse(timeCode[2]),
+                int.Parse(timeCode[3]) * 10).Ticks;
         }
         }
 
 
         private static string GetFormattedText(string text)
         private static string GetFormattedText(string text)
@@ -282,6 +284,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
                         {
                         {
                             text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">");
                             text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">");
                         }
                         }
+
                         int indexOfEndTag = text.IndexOf("{\\c}", start);
                         int indexOfEndTag = text.IndexOf("{\\c}", start);
                         if (indexOfEndTag > 0)
                         if (indexOfEndTag > 0)
                         {
                         {
@@ -320,6 +323,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
                         {
                         {
                             text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">");
                             text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">");
                         }
                         }
+
                         text += "</font>";
                         text += "</font>";
                     }
                     }
                 }
                 }

+ 31 - 31
MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs

@@ -34,6 +34,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
         private readonly IHttpClient _httpClient;
         private readonly IHttpClient _httpClient;
         private readonly IMediaSourceManager _mediaSourceManager;
         private readonly IMediaSourceManager _mediaSourceManager;
 
 
+        /// <summary>
+        /// The _semaphoreLocks.
+        /// </summary>
+        private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphoreLocks =
+            new ConcurrentDictionary<string, SemaphoreSlim>();
+
         public SubtitleEncoder(
         public SubtitleEncoder(
             ILibraryManager libraryManager,
             ILibraryManager libraryManager,
             ILogger<SubtitleEncoder> logger,
             ILogger<SubtitleEncoder> logger,
@@ -269,25 +275,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
             return new SubtitleInfo(subtitleStream.Path, protocol, currentFormat, true);
             return new SubtitleInfo(subtitleStream.Path, protocol, currentFormat, true);
         }
         }
 
 
-        private struct SubtitleInfo
-        {
-            public SubtitleInfo(string path, MediaProtocol protocol, string format, bool isExternal)
-            {
-                Path = path;
-                Protocol = protocol;
-                Format = format;
-                IsExternal = isExternal;
-            }
-
-            public string Path { get; set; }
-
-            public MediaProtocol Protocol { get; set; }
-
-            public string Format { get; set; }
-
-            public bool IsExternal { get; set; }
-        }
-
         private ISubtitleParser GetReader(string format, bool throwIfMissing)
         private ISubtitleParser GetReader(string format, bool throwIfMissing)
         {
         {
             if (string.IsNullOrEmpty(format))
             if (string.IsNullOrEmpty(format))
@@ -360,12 +347,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
             throw new ArgumentException("Unsupported format: " + format);
             throw new ArgumentException("Unsupported format: " + format);
         }
         }
 
 
-        /// <summary>
-        /// The _semaphoreLocks.
-        /// </summary>
-        private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphoreLocks =
-            new ConcurrentDictionary<string, SemaphoreSlim>();
-
         /// <summary>
         /// <summary>
         /// Gets the lock.
         /// Gets the lock.
         /// </summary>
         /// </summary>
@@ -380,6 +361,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
         /// Converts the text subtitle to SRT.
         /// Converts the text subtitle to SRT.
         /// </summary>
         /// </summary>
         /// <param name="inputPath">The input path.</param>
         /// <param name="inputPath">The input path.</param>
+        /// <param name="language">The language.</param>
         /// <param name="inputProtocol">The input protocol.</param>
         /// <param name="inputProtocol">The input protocol.</param>
         /// <param name="outputPath">The output path.</param>
         /// <param name="outputPath">The output path.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
@@ -407,14 +389,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles
         /// Converts the text subtitle to SRT internal.
         /// Converts the text subtitle to SRT internal.
         /// </summary>
         /// </summary>
         /// <param name="inputPath">The input path.</param>
         /// <param name="inputPath">The input path.</param>
+        /// <param name="language">The language.</param>
         /// <param name="inputProtocol">The input protocol.</param>
         /// <param name="inputProtocol">The input protocol.</param>
         /// <param name="outputPath">The output path.</param>
         /// <param name="outputPath">The output path.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         /// <exception cref="ArgumentNullException">
         /// <exception cref="ArgumentNullException">
-        /// inputPath
-        /// or
-        /// outputPath
+        /// The <c>inputPath</c> or <c>outputPath</c> is <c>null</c>.
         /// </exception>
         /// </exception>
         private async Task ConvertTextSubtitleToSrtInternal(string inputPath, string language, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken)
         private async Task ConvertTextSubtitleToSrtInternal(string inputPath, string language, MediaProtocol inputProtocol, string outputPath, CancellationToken cancellationToken)
         {
         {
@@ -438,7 +419,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
                 (encodingParam.Equals("UTF-16BE", StringComparison.OrdinalIgnoreCase) ||
                 (encodingParam.Equals("UTF-16BE", StringComparison.OrdinalIgnoreCase) ||
                  encodingParam.Equals("UTF-16LE", StringComparison.OrdinalIgnoreCase)))
                  encodingParam.Equals("UTF-16LE", StringComparison.OrdinalIgnoreCase)))
             {
             {
-                encodingParam = "";
+                encodingParam = string.Empty;
             }
             }
             else if (!string.IsNullOrEmpty(encodingParam))
             else if (!string.IsNullOrEmpty(encodingParam))
             {
             {
@@ -540,7 +521,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
         /// <param name="outputPath">The output path.</param>
         /// <param name="outputPath">The output path.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        /// <exception cref="ArgumentException">Must use inputPath list overload</exception>
+        /// <exception cref="ArgumentException">Must use inputPath list overload.</exception>
         private async Task ExtractTextSubtitle(
         private async Task ExtractTextSubtitle(
             string[] inputFiles,
             string[] inputFiles,
             MediaProtocol protocol,
             MediaProtocol protocol,
@@ -759,7 +740,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
                     && (string.Equals(charset, "utf-16le", StringComparison.OrdinalIgnoreCase)
                     && (string.Equals(charset, "utf-16le", StringComparison.OrdinalIgnoreCase)
                         || string.Equals(charset, "utf-16be", StringComparison.OrdinalIgnoreCase)))
                         || string.Equals(charset, "utf-16be", StringComparison.OrdinalIgnoreCase)))
                 {
                 {
-                    charset = "";
+                    charset = string.Empty;
                 }
                 }
 
 
                 _logger.LogDebug("charset {0} detected for {Path}", charset ?? "null", path);
                 _logger.LogDebug("charset {0} detected for {Path}", charset ?? "null", path);
@@ -790,5 +771,24 @@ namespace MediaBrowser.MediaEncoding.Subtitles
                     throw new ArgumentOutOfRangeException(nameof(protocol));
                     throw new ArgumentOutOfRangeException(nameof(protocol));
             }
             }
         }
         }
+
+        private struct SubtitleInfo
+        {
+            public SubtitleInfo(string path, MediaProtocol protocol, string format, bool isExternal)
+            {
+                Path = path;
+                Protocol = protocol;
+                Format = format;
+                IsExternal = isExternal;
+            }
+
+            public string Path { get; set; }
+
+            public MediaProtocol Protocol { get; set; }
+
+            public string Format { get; set; }
+
+            public bool IsExternal { get; set; }
+        }
     }
     }
 }
 }

+ 2 - 2
MediaBrowser.Model/Entities/MediaStream.cs

@@ -233,7 +233,7 @@ namespace MediaBrowser.Model.Entities
 
 
                         if (!string.IsNullOrEmpty(Title))
                         if (!string.IsNullOrEmpty(Title))
                         {
                         {
-                           var result = new StringBuilder(Title);
+                            var result = new StringBuilder(Title);
                             foreach (var tag in attributes)
                             foreach (var tag in attributes)
                             {
                             {
                                 // Keep Tags that are not already in Title.
                                 // Keep Tags that are not already in Title.
@@ -246,7 +246,7 @@ namespace MediaBrowser.Model.Entities
                             return result.ToString();
                             return result.ToString();
                         }
                         }
 
 
-                        return string.Join(" - ", attributes.ToArray());
+                        return string.Join(" - ", attributes);
                     }
                     }
 
 
                     default:
                     default:

+ 2 - 2
MediaBrowser.Providers/Manager/ImageSaver.cs

@@ -125,7 +125,7 @@ namespace MediaBrowser.Providers.Manager
 
 
             // If there are more than one output paths, the stream will need to be seekable
             // If there are more than one output paths, the stream will need to be seekable
             var memoryStream = new MemoryStream();
             var memoryStream = new MemoryStream();
-            using (source)
+            await using (source.ConfigureAwait(false))
             {
             {
                 await source.CopyToAsync(memoryStream).ConfigureAwait(false);
                 await source.CopyToAsync(memoryStream).ConfigureAwait(false);
             }
             }
@@ -138,7 +138,7 @@ namespace MediaBrowser.Providers.Manager
 
 
             var savedPaths = new List<string>();
             var savedPaths = new List<string>();
 
 
-            await using (source)
+            await using (source.ConfigureAwait(false))
             {
             {
                 var currentPathIndex = 0;
                 var currentPathIndex = 0;
 
 

+ 2 - 2
MediaBrowser.Providers/Manager/ProviderManager.cs

@@ -966,7 +966,7 @@ namespace MediaBrowser.Providers.Manager
         /// <inheritdoc/>
         /// <inheritdoc/>
         public void OnRefreshStart(BaseItem item)
         public void OnRefreshStart(BaseItem item)
         {
         {
-            _logger.LogInformation("OnRefreshStart {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
+            _logger.LogDebug("OnRefreshStart {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
             _activeRefreshes[item.Id] = 0;
             _activeRefreshes[item.Id] = 0;
             RefreshStarted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
             RefreshStarted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
         }
         }
@@ -974,7 +974,7 @@ namespace MediaBrowser.Providers.Manager
         /// <inheritdoc/>
         /// <inheritdoc/>
         public void OnRefreshComplete(BaseItem item)
         public void OnRefreshComplete(BaseItem item)
         {
         {
-            _logger.LogInformation("OnRefreshComplete {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
+            _logger.LogDebug("OnRefreshComplete {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
 
 
             _activeRefreshes.Remove(item.Id, out _);
             _activeRefreshes.Remove(item.Id, out _);
 
 

+ 1 - 1
README.md

@@ -100,7 +100,7 @@ Note that it is also possible to [host the web client separately](#hosting-the-w
 
 
 There are three options to get the files for the web client.
 There are three options to get the files for the web client.
 
 
-1. Download one of the finished builds from the [Azure DevOps pipeline](https://dev.azure.com/jellyfin-project/jellyfin/_build?definitionId=11). You can download the build for a specific release by looking at the [branches tab](https://dev.azure.com/jellyfin-project/jellyfin/_build?definitionId=11&_a=summary&repositoryFilter=6&view=branches) of the pipelines page.
+1. Download one of the finished builds from the [Azure DevOps pipeline](https://dev.azure.com/jellyfin-project/jellyfin/_build?definitionId=27). You can download the build for a specific release by looking at the [branches tab](https://dev.azure.com/jellyfin-project/jellyfin/_build?definitionId=27&_a=summary&repositoryFilter=6&view=branches) of the pipelines page.
 2. Build them from source following the instructions on the [jellyfin-web repository](https://github.com/jellyfin/jellyfin-web)
 2. Build them from source following the instructions on the [jellyfin-web repository](https://github.com/jellyfin/jellyfin-web)
 3. Get the pre-built files from an existing installation of the server. For example, with a Windows server installation the client files are located at `C:\Program Files\Jellyfin\Server\jellyfin-web`
 3. Get the pre-built files from an existing installation of the server. For example, with a Windows server installation the client files are located at `C:\Program Files\Jellyfin\Server\jellyfin-web`
 
 

+ 0 - 2
RSSDP/RSSDP.csproj

@@ -6,9 +6,7 @@
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>
   <ItemGroup>
-    <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
     <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
     <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
-    <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
   </ItemGroup>
   </ItemGroup>
 
 
   <PropertyGroup>
   <PropertyGroup>