diff --git a/Emby.Naming/ExternalFiles/ExternalPathParser.cs b/Emby.Naming/ExternalFiles/ExternalPathParser.cs index 7b5767b67d..9d07dc2f9c 100644 --- a/Emby.Naming/ExternalFiles/ExternalPathParser.cs +++ b/Emby.Naming/ExternalFiles/ExternalPathParser.cs @@ -9,7 +9,7 @@ using MediaBrowser.Model.Globalization; namespace Emby.Naming.ExternalFiles { /// - /// External file parser class. + /// External media file parser class. /// public class ExternalPathParser { @@ -44,9 +44,8 @@ namespace Emby.Naming.ExternalFiles } var extension = Path.GetExtension(path); - if (!((_type == DlnaProfileType.Subtitle && _namingOptions.SubtitleFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) - || (_type == DlnaProfileType.Audio && _namingOptions.AudioFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) - || (_type == DlnaProfileType.Video && _namingOptions.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)))) + if (!(_type == DlnaProfileType.Subtitle && _namingOptions.SubtitleFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) + && !(_type == DlnaProfileType.Audio && _namingOptions.AudioFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))) { return null; } @@ -66,7 +65,7 @@ namespace Emby.Naming.ExternalFiles while (languageString.Length > 0) { - var lastSeparator = languageString.LastIndexOf(separator, StringComparison.OrdinalIgnoreCase); + int lastSeparator = languageString.LastIndexOf(separator, StringComparison.OrdinalIgnoreCase); if (lastSeparator == -1) { @@ -74,8 +73,9 @@ namespace Emby.Naming.ExternalFiles } string currentSlice = languageString[lastSeparator..]; + string currentSliceWithoutSeparator = currentSlice[separatorLength..]; - if (_namingOptions.MediaDefaultFlags.Any(s => currentSlice[separatorLength..].Contains(s, StringComparison.OrdinalIgnoreCase))) + if (_namingOptions.MediaDefaultFlags.Any(s => currentSliceWithoutSeparator.Contains(s, StringComparison.OrdinalIgnoreCase))) { pathInfo.IsDefault = true; extraString = extraString.Replace(currentSlice, string.Empty, StringComparison.OrdinalIgnoreCase); @@ -83,7 +83,7 @@ namespace Emby.Naming.ExternalFiles continue; } - if (_namingOptions.MediaForcedFlags.Any(s => currentSlice[separatorLength..].Contains(s, StringComparison.OrdinalIgnoreCase))) + if (_namingOptions.MediaForcedFlags.Any(s => currentSliceWithoutSeparator.Contains(s, StringComparison.OrdinalIgnoreCase))) { pathInfo.IsForced = true; extraString = extraString.Replace(currentSlice, string.Empty, StringComparison.OrdinalIgnoreCase); @@ -92,7 +92,7 @@ namespace Emby.Naming.ExternalFiles } // Try to translate to three character code - var culture = _localizationManager.FindLanguageInfo(currentSlice[separatorLength..]); + var culture = _localizationManager.FindLanguageInfo(currentSliceWithoutSeparator); if (culture != null && pathInfo.Language == null) { diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs new file mode 100644 index 0000000000..9329978a84 --- /dev/null +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -0,0 +1,28 @@ +using Emby.Naming.Common; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Globalization; + +namespace MediaBrowser.Providers.MediaInfo +{ + /// + /// Resolves external audio files for . + /// + public class AudioResolver : MediaInfoResolver + { + /// + /// Initializes a new instance of the class for external audio file processing. + /// + /// The localization manager. + /// The media encoder. + /// The object containing FileExtensions, MediaDefaultFlags, MediaForcedFlags and MediaFlagDelimiters. + public AudioResolver( + ILocalizationManager localizationManager, + IMediaEncoder mediaEncoder, + NamingOptions namingOptions) + : base(localizationManager, mediaEncoder, namingOptions, DlnaProfileType.Audio) + { + } + } +} diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index dab06cc089..560e20dae7 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -40,8 +40,8 @@ namespace MediaBrowser.Providers.MediaInfo IHasItemChangeMonitor { private readonly ILogger _logger; - private readonly MediaInfoResolver _subtitleResolver; - private readonly MediaInfoResolver _audioResolver; + private readonly AudioResolver _audioResolver; + private readonly SubtitleResolver _subtitleResolver; private readonly FFProbeVideoInfo _videoProber; private readonly FFProbeAudioInfo _audioProber; private readonly Task _cachedTask = Task.FromResult(ItemUpdateType.None); @@ -61,8 +61,8 @@ namespace MediaBrowser.Providers.MediaInfo NamingOptions namingOptions) { _logger = logger; - _audioResolver = new MediaInfoResolver(localization, mediaEncoder, namingOptions, DlnaProfileType.Audio); - _subtitleResolver = new MediaInfoResolver(localization, mediaEncoder, namingOptions, DlnaProfileType.Subtitle); + _audioResolver = new AudioResolver(localization, mediaEncoder, namingOptions); + _subtitleResolver = new SubtitleResolver(localization, mediaEncoder, namingOptions); _videoProber = new FFProbeVideoInfo( _logger, mediaSourceManager, @@ -75,8 +75,8 @@ namespace MediaBrowser.Providers.MediaInfo subtitleManager, chapterManager, libraryManager, - _subtitleResolver, - _audioResolver); + _audioResolver, + _subtitleResolver); _audioProber = new FFProbeAudioInfo(mediaSourceManager, mediaEncoder, itemRepo, libraryManager); } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index cce9b5aebf..26ff0412b2 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -44,8 +44,8 @@ namespace MediaBrowser.Providers.MediaInfo private readonly ISubtitleManager _subtitleManager; private readonly IChapterManager _chapterManager; private readonly ILibraryManager _libraryManager; - private readonly MediaInfoResolver _audioResolver; - private readonly MediaInfoResolver _subtitleResolver; + private readonly AudioResolver _audioResolver; + private readonly SubtitleResolver _subtitleResolver; private readonly IMediaSourceManager _mediaSourceManager; private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks; @@ -62,10 +62,11 @@ namespace MediaBrowser.Providers.MediaInfo ISubtitleManager subtitleManager, IChapterManager chapterManager, ILibraryManager libraryManager, - MediaInfoResolver subtitleResolver, - MediaInfoResolver audioResolver) + AudioResolver audioResolver, + SubtitleResolver subtitleResolver) { _logger = logger; + _mediaSourceManager = mediaSourceManager; _mediaEncoder = mediaEncoder; _itemRepo = itemRepo; _blurayExaminer = blurayExaminer; @@ -77,7 +78,6 @@ namespace MediaBrowser.Providers.MediaInfo _libraryManager = libraryManager; _audioResolver = audioResolver; _subtitleResolver = subtitleResolver; - _mediaSourceManager = mediaSourceManager; } public async Task ProbeVideo( @@ -536,14 +536,7 @@ namespace MediaBrowser.Providers.MediaInfo CancellationToken cancellationToken) { var startIndex = currentStreams.Count == 0 ? 0 : (currentStreams.Select(i => i.Index).Max() + 1); - var externalSubtitleStreamsAsync = _subtitleResolver.GetExternalStreamsAsync(video, startIndex, options.DirectoryService, false, cancellationToken); - - List externalSubtitleStreams = new List(); - - await foreach (MediaStream externalSubtitleStream in externalSubtitleStreamsAsync) - { - externalSubtitleStreams.Add(externalSubtitleStream); - } + var externalSubtitleStreams = await _subtitleResolver.GetExternalStreamsAsync(video, startIndex, options.DirectoryService, false, cancellationToken); var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.Default || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh; @@ -597,10 +590,7 @@ namespace MediaBrowser.Providers.MediaInfo // Rescan if (downloadedLanguages.Count > 0) { - await foreach (MediaStream externalSubtitleStream in _subtitleResolver.GetExternalStreamsAsync(video, startIndex, options.DirectoryService, true, cancellationToken)) - { - externalSubtitleStreams.Add(externalSubtitleStream); - } + externalSubtitleStreams = await _subtitleResolver.GetExternalStreamsAsync(video, startIndex, options.DirectoryService, true, cancellationToken); } } @@ -623,12 +613,9 @@ namespace MediaBrowser.Providers.MediaInfo CancellationToken cancellationToken) { var startIndex = currentStreams.Count == 0 ? 0 : currentStreams.Max(i => i.Index) + 1; - var externalAudioStreams = _audioResolver.GetExternalStreamsAsync(video, startIndex, options.DirectoryService, false, cancellationToken); + var externalAudioStreams = await _audioResolver.GetExternalStreamsAsync(video, startIndex, options.DirectoryService, false, cancellationToken).ConfigureAwait(false); - await foreach (MediaStream externalAudioStream in externalAudioStreams) - { - currentStreams.Add(externalAudioStream); - } + currentStreams = currentStreams.Concat(externalAudioStreams).ToList(); // Select all external audio file paths video.AudioFiles = currentStreams.Where(i => i.Type == MediaStreamType.Audio && i.IsExternal).Select(i => i.Path).Distinct().ToArray(); diff --git a/MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs b/MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs index 51cf6d1339..f4c9bfa90c 100644 --- a/MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs @@ -1,8 +1,8 @@ +using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Emby.Naming.Common; @@ -19,9 +19,9 @@ using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Providers.MediaInfo { /// - /// Resolves external files for videos. + /// Resolves external files for . /// - public class MediaInfoResolver + public abstract class MediaInfoResolver { /// /// The instance. @@ -55,7 +55,7 @@ namespace MediaBrowser.Providers.MediaInfo /// The media encoder. /// The object containing FileExtensions, MediaDefaultFlags, MediaForcedFlags and MediaFlagDelimiters. /// The of the parsed file. - public MediaInfoResolver( + protected MediaInfoResolver( ILocalizationManager localizationManager, IMediaEncoder mediaEncoder, NamingOptions namingOptions, @@ -73,27 +73,32 @@ namespace MediaBrowser.Providers.MediaInfo /// The stream index to start adding external streams at. /// The directory service to search for files. /// True if the directory service cache should be cleared before searching. - /// The cancellation token to cancel operation. + /// The cancellation token. /// The external streams located. - public async IAsyncEnumerable GetExternalStreamsAsync( + public async Task> GetExternalStreamsAsync( Video video, int startIndex, IDirectoryService directoryService, bool clearCache, - [EnumeratorCancellation] CancellationToken cancellationToken) + CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); - if (!video.IsFileProtocol) { - yield break; + return Array.Empty(); } var pathInfos = GetExternalFiles(video, directoryService, clearCache); + if (!pathInfos.Any()) + { + return Array.Empty(); + } + + var mediaStreams = new List(); + foreach (var pathInfo in pathInfos) { - Model.MediaInfo.MediaInfo mediaInfo = await GetMediaInfo(pathInfo.Path, _type, cancellationToken).ConfigureAwait(false); + var mediaInfo = await GetMediaInfo(pathInfo.Path, _type, cancellationToken).ConfigureAwait(false); if (mediaInfo.MediaStreams.Count == 1) { @@ -102,7 +107,7 @@ namespace MediaBrowser.Providers.MediaInfo mediaStream.IsDefault = pathInfo.IsDefault || mediaStream.IsDefault; mediaStream.IsForced = pathInfo.IsForced || mediaStream.IsForced; - yield return MergeMetadata(mediaStream, pathInfo); + mediaStreams.Add(MergeMetadata(mediaStream, pathInfo)); } else { @@ -110,10 +115,12 @@ namespace MediaBrowser.Providers.MediaInfo { mediaStream.Index = startIndex++; - yield return MergeMetadata(mediaStream, pathInfo); + mediaStreams.Add(MergeMetadata(mediaStream, pathInfo)); } } } + + return mediaStreams.AsReadOnly(); } /// @@ -123,26 +130,33 @@ namespace MediaBrowser.Providers.MediaInfo /// The directory service to search for files. /// True if the directory service cache should be cleared before searching. /// The external file paths located. - public IEnumerable GetExternalFiles( + public IReadOnlyList GetExternalFiles( Video video, IDirectoryService directoryService, bool clearCache) { if (!video.IsFileProtocol) { - yield break; + return Array.Empty(); } // Check if video folder exists string folder = video.ContainingFolderPath; if (!Directory.Exists(folder)) { - yield break; + return Array.Empty(); } + var externalPathInfos = new List(); + var files = directoryService.GetFilePaths(folder, clearCache).ToList(); files.AddRange(directoryService.GetFilePaths(video.GetInternalMetadataPath(), clearCache)); + if (!files.Any()) + { + return Array.Empty(); + } + foreach (var file in files) { if (_compareInfo.IsPrefix(Path.GetFileNameWithoutExtension(file), video.FileNameWithoutExtension, CompareOptions, out int matchLength)) @@ -151,10 +165,12 @@ namespace MediaBrowser.Providers.MediaInfo if (externalPathInfo != null) { - yield return externalPathInfo; + externalPathInfos.Add(externalPathInfo); } } } + + return externalPathInfos.AsReadOnly(); } /// @@ -198,7 +214,6 @@ namespace MediaBrowser.Providers.MediaInfo { DlnaProfileType.Audio => MediaStreamType.Audio, DlnaProfileType.Subtitle => MediaStreamType.Subtitle, - DlnaProfileType.Video => MediaStreamType.Video, _ => mediaStream.Type }; diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs new file mode 100644 index 0000000000..78b3836e78 --- /dev/null +++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs @@ -0,0 +1,28 @@ +using Emby.Naming.Common; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Globalization; + +namespace MediaBrowser.Providers.MediaInfo +{ + /// + /// Resolves external subtitle files for . + /// + public class SubtitleResolver : MediaInfoResolver + { + /// + /// Initializes a new instance of the class for external subtitle file processing. + /// + /// The localization manager. + /// The media encoder. + /// The object containing FileExtensions, MediaDefaultFlags, MediaForcedFlags and MediaFlagDelimiters. + public SubtitleResolver( + ILocalizationManager localizationManager, + IMediaEncoder mediaEncoder, + NamingOptions namingOptions) + : base(localizationManager, mediaEncoder, namingOptions, DlnaProfileType.Subtitle) + { + } + } +} diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/AudioResolverTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/AudioResolverTests.cs index d0f216eb00..69f10d6700 100644 --- a/tests/Jellyfin.Providers.Tests/MediaInfo/AudioResolverTests.cs +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/AudioResolverTests.cs @@ -8,7 +8,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Providers.MediaInfo; @@ -21,17 +20,11 @@ namespace Jellyfin.Providers.Tests.MediaInfo { private const string VideoDirectoryPath = "Test Data/Video"; private const string MetadataDirectoryPath = "Test Data/Metadata"; - private readonly MediaInfoResolver _audioResolver; + private readonly AudioResolver _audioResolver; public AudioResolverTests() { - var englishCultureDto = new CultureDto - { - Name = "English", - DisplayName = "English", - ThreeLetterISOLanguageNames = new[] { "eng" }, - TwoLetterISOLanguageName = "en" - }; + var englishCultureDto = new CultureDto("English", "English", "en", new[] { "eng" }); var localizationManager = new Mock(MockBehavior.Loose); localizationManager.Setup(lm => lm.FindLanguageInfo(It.IsRegex(@"en.*", RegexOptions.IgnoreCase))) @@ -47,11 +40,11 @@ namespace Jellyfin.Providers.Tests.MediaInfo } })); - _audioResolver = new MediaInfoResolver(localizationManager.Object, mediaEncoder.Object, new NamingOptions(), DlnaProfileType.Audio); + _audioResolver = new AudioResolver(localizationManager.Object, mediaEncoder.Object, new NamingOptions()); } [Fact] - public async void AddExternalStreams_GivenMixedFilenames_ReturnsValidSubtitles() + public async void AddExternalStreamsAsync_GivenMixedFilenames_ReturnsValidSubtitles() { var startIndex = 0; var index = startIndex; @@ -108,13 +101,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(@"Test Data[/\\]Metadata"), It.IsAny(), It.IsAny())) .Returns(metadataFiles); - var asyncStreams = _audioResolver.GetExternalStreamsAsync(video.Object, startIndex, directoryService.Object, false, CancellationToken.None).ConfigureAwait(false); - - var streams = new List(); - await foreach (var stream in asyncStreams) - { - streams.Add(stream); - } + var streams = await _audioResolver.GetExternalStreamsAsync(video.Object, startIndex, directoryService.Object, false, CancellationToken.None); Assert.Equal(expectedResult.Length, streams.Count); for (var i = 0; i < expectedResult.Length; i++) @@ -140,7 +127,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo [InlineData("My.Video.forced.English.mp3", "eng", null, true, false)] [InlineData("My.Video.default.English.mp3", "eng", null, false, true)] [InlineData("My.Video.English.forced.default.Title.mp3", "eng", "Title", true, true)] - public async void GetExternalAudioStreams_GivenSingleFile_ReturnsExpectedStream(string file, string? language, string? title, bool isForced, bool isDefault) + public async void AddExternalStreamsAsync_GivenSingleFile_ReturnsExpectedStream(string file, string? language, string? title, bool isForced, bool isDefault) { BaseItem.MediaSourceManager = Mock.Of(); @@ -155,13 +142,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(@"Test Data[/\\]Metadata"), It.IsAny(), It.IsAny())) .Returns(Array.Empty()); - var asyncStreams = _audioResolver.GetExternalStreamsAsync(video.Object, 0, directoryService.Object, false, CancellationToken.None).ConfigureAwait(false); - - var streams = new List(); - await foreach (var stream in asyncStreams) - { - streams.Add(stream); - } + var streams = await _audioResolver.GetExternalStreamsAsync(video.Object, 0, directoryService.Object, false, CancellationToken.None); Assert.Single(streams); diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs index fa261a548c..2532d8926a 100644 --- a/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs @@ -8,7 +8,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Providers.MediaInfo; @@ -21,24 +20,12 @@ namespace Jellyfin.Providers.Tests.MediaInfo { private const string VideoDirectoryPath = "Test Data/Video"; private const string MetadataDirectoryPath = "Test Data/Metadata"; - private readonly MediaInfoResolver _subtitleResolver; + private readonly SubtitleResolver _subtitleResolver; public SubtitleResolverTests() { - var englishCultureDto = new CultureDto - { - Name = "English", - DisplayName = "English", - ThreeLetterISOLanguageNames = new[] { "eng" }, - TwoLetterISOLanguageName = "en" - }; - var frenchCultureDto = new CultureDto - { - Name = "French", - DisplayName = "French", - ThreeLetterISOLanguageNames = new[] { "fre", "fra" }, - TwoLetterISOLanguageName = "fr" - }; + var englishCultureDto = new CultureDto("English", "English", "en", new[] { "eng" }); + var frenchCultureDto = new CultureDto("French", "French", "fr", new[] { "fre", "fra" }); var localizationManager = new Mock(MockBehavior.Loose); localizationManager.Setup(lm => lm.FindLanguageInfo(It.IsRegex(@"en.*", RegexOptions.IgnoreCase))) @@ -56,11 +43,11 @@ namespace Jellyfin.Providers.Tests.MediaInfo } })); - _subtitleResolver = new MediaInfoResolver(localizationManager.Object, mediaEncoder.Object, new NamingOptions(), DlnaProfileType.Subtitle); + _subtitleResolver = new SubtitleResolver(localizationManager.Object, mediaEncoder.Object, new NamingOptions()); } [Fact] - public async void AddExternalSubtitleStreams_GivenMixedFilenames_ReturnsValidSubtitles() + public async void AddExternalStreamsAsync_GivenMixedFilenames_ReturnsValidSubtitles() { var startIndex = 0; var index = startIndex; @@ -127,13 +114,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(@"Test Data[/\\]Metadata"), It.IsAny(), It.IsAny())) .Returns(metadataFiles); - var asyncStreams = _subtitleResolver.GetExternalStreamsAsync(video.Object, startIndex, directoryService.Object, false, CancellationToken.None).ConfigureAwait(false); - - var streams = new List(); - await foreach (var stream in asyncStreams) - { - streams.Add(stream); - } + var streams = await _subtitleResolver.GetExternalStreamsAsync(video.Object, startIndex, directoryService.Object, false, CancellationToken.None); Assert.Equal(expectedResult.Length, streams.Count); for (var i = 0; i < expectedResult.Length; i++) @@ -169,7 +150,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo [InlineData("My.Video.Track.Label.srt", "srt", null, "Track.Label", false, false)] [InlineData("My.Video.Track Label.en.default.forced.srt", "srt", "eng", "Track Label", true, true)] [InlineData("My.Video.en.default.forced.Track Label.srt", "srt", "eng", "Track Label", true, true)] - public async void AddExternalSubtitleStreams_GivenSingleFile_ReturnsExpectedSubtitle(string file, string codec, string? language, string? title, bool isForced, bool isDefault) + public async void AddExternalStreamsAsync_GivenSingleFile_ReturnsExpectedSubtitle(string file, string codec, string? language, string? title, bool isForced, bool isDefault) { BaseItem.MediaSourceManager = Mock.Of(); @@ -184,13 +165,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(@"Test Data[/\\]Metadata"), It.IsAny(), It.IsAny())) .Returns(Array.Empty()); - var asyncStreams = _subtitleResolver.GetExternalStreamsAsync(video.Object, 0, directoryService.Object, false, CancellationToken.None).ConfigureAwait(false); - - var streams = new List(); - await foreach (var stream in asyncStreams) - { - streams.Add(stream); - } + var streams = await _subtitleResolver.GetExternalStreamsAsync(video.Object, 0, directoryService.Object, false, CancellationToken.None); Assert.Single(streams); var actual = streams[0];