123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text.RegularExpressions;
- using System.Threading;
- using System.Threading.Tasks;
- using Emby.Naming.Common;
- using MediaBrowser.Controller;
- using MediaBrowser.Controller.Configuration;
- using MediaBrowser.Controller.Entities;
- using MediaBrowser.Controller.Entities.Movies;
- using MediaBrowser.Controller.Library;
- using MediaBrowser.Controller.LiveTv;
- using MediaBrowser.Controller.MediaEncoding;
- using MediaBrowser.Controller.Providers;
- using MediaBrowser.Model.Entities;
- using MediaBrowser.Model.Globalization;
- using MediaBrowser.Model.IO;
- using MediaBrowser.Model.MediaInfo;
- using MediaBrowser.Providers.MediaInfo;
- using Moq;
- using Xunit;
- namespace Jellyfin.Providers.Tests.MediaInfo;
- public class MediaInfoResolverTests
- {
- public const string VideoDirectoryPath = "Test Data/Video";
- public const string VideoDirectoryRegex = @"Test Data[/\\]Video";
- public const string MetadataDirectoryPath = "library/00/00000000000000000000000000000000";
- public const string MetadataDirectoryRegex = @"library.*";
- private readonly ILocalizationManager _localizationManager;
- private readonly MediaInfoResolver _subtitleResolver;
- public MediaInfoResolverTests()
- {
- // prep BaseItem and Video for calls made that expect managers
- Video.LiveTvManager = Mock.Of<ILiveTvManager>();
- var applicationPaths = new Mock<IServerApplicationPaths>().Object;
- var serverConfig = new Mock<IServerConfigurationManager>();
- serverConfig.Setup(c => c.ApplicationPaths)
- .Returns(applicationPaths);
- BaseItem.ConfigurationManager = serverConfig.Object;
- // build resolver to test with
- var englishCultureDto = new CultureDto("English", "English", "en", new[] { "eng" });
- var localizationManager = new Mock<ILocalizationManager>(MockBehavior.Loose);
- localizationManager.Setup(lm => lm.FindLanguageInfo(It.IsRegex(@"en.*", RegexOptions.IgnoreCase)))
- .Returns(englishCultureDto);
- _localizationManager = localizationManager.Object;
- var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict);
- mediaEncoder.Setup(me => me.GetMediaInfo(It.IsAny<MediaInfoRequest>(), It.IsAny<CancellationToken>()))
- .Returns<MediaInfoRequest, CancellationToken>((_, _) => Task.FromResult(new MediaBrowser.Model.MediaInfo.MediaInfo
- {
- MediaStreams = new List<MediaStream>
- {
- new()
- }
- }));
- var fileSystem = new Mock<IFileSystem>(MockBehavior.Strict);
- fileSystem.Setup(fs => fs.DirectoryExists(It.IsAny<string>()))
- .Returns(false);
- fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(VideoDirectoryRegex)))
- .Returns(true);
- fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MetadataDirectoryRegex)))
- .Returns(true);
- _subtitleResolver = new SubtitleResolver(_localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions());
- }
- [Fact]
- public void GetExternalFiles_BadProtocol_ReturnsNoSubtitles()
- {
- // need a media source manager capable of returning something other than file protocol
- var mediaSourceManager = new Mock<IMediaSourceManager>();
- mediaSourceManager.Setup(m => m.GetPathProtocol(It.IsRegex(@"http.*")))
- .Returns(MediaProtocol.Http);
- BaseItem.MediaSourceManager = mediaSourceManager.Object;
- var video = new Movie
- {
- Path = "https://url.com/My.Video.mkv"
- };
- Assert.Empty(_subtitleResolver.GetExternalFiles(video, Mock.Of<IDirectoryService>(), false));
- }
- [Theory]
- [InlineData(false)]
- [InlineData(true)]
- public void GetExternalFiles_MissingDirectory_DirectoryNotQueried(bool metadataDirectory)
- {
- BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>();
- string containingFolderPath, metadataPath;
- if (metadataDirectory)
- {
- containingFolderPath = VideoDirectoryPath;
- metadataPath = "invalid";
- }
- else
- {
- containingFolderPath = "invalid";
- metadataPath = MetadataDirectoryPath;
- }
- var video = new Mock<Movie>();
- video.Setup(m => m.Path)
- .Returns(VideoDirectoryPath + "/My.Video.mkv");
- video.Setup(m => m.ContainingFolderPath)
- .Returns(containingFolderPath);
- video.Setup(m => m.GetInternalMetadataPath())
- .Returns(metadataPath);
- string pathNotFoundRegex = metadataDirectory ? MetadataDirectoryRegex : VideoDirectoryRegex;
- var directoryService = new Mock<IDirectoryService>(MockBehavior.Strict);
- // any path other than test target exists and provides an empty listing
- directoryService.Setup(ds => ds.GetFilePaths(It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<bool>()))
- .Returns(Array.Empty<string>());
- _subtitleResolver.GetExternalFiles(video.Object, directoryService.Object, false);
- directoryService.Verify(
- ds => ds.GetFilePaths(It.IsRegex(pathNotFoundRegex), It.IsAny<bool>(), It.IsAny<bool>()),
- Times.Never);
- }
- [Theory]
- [InlineData("My.Video.srt", null)] // exact
- [InlineData("My.Video.en.srt", "eng")]
- [InlineData("MyVideo.en.srt", "eng")] // shorter title
- [InlineData("My _ Video.en.srt", "eng")] // longer title
- [InlineData("My.Video.en.srt", "eng", true)]
- public void GetExternalFiles_FuzzyMatching_MatchesAndParsesToken(string file, string? language, bool metadataDirectory = false)
- {
- BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>();
- var video = new Movie
- {
- Path = VideoDirectoryPath + "/My.Video.mkv"
- };
- var directoryService = GetDirectoryServiceForExternalFile(file, metadataDirectory);
- var streams = _subtitleResolver.GetExternalFiles(video, directoryService, false).ToList();
- Assert.Single(streams);
- var actual = streams[0];
- Assert.Equal(language, actual.Language);
- Assert.Null(actual.Title);
- }
- [Theory]
- [InlineData("My.Video.mp3")]
- [InlineData("My.Video.png")]
- [InlineData("My.Video.txt")]
- [InlineData("My.Video Sequel.srt")]
- [InlineData("Some.Other.Video.srt")]
- public void GetExternalFiles_FuzzyMatching_RejectsNonMatches(string file)
- {
- BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>();
- var video = new Movie
- {
- Path = VideoDirectoryPath + "/My.Video.mkv"
- };
- var directoryService = GetDirectoryServiceForExternalFile(file);
- var streams = _subtitleResolver.GetExternalFiles(video, directoryService, false).ToList();
- Assert.Empty(streams);
- }
- [Theory]
- [InlineData("https://url.com/My.Video.mkv")]
- [InlineData(VideoDirectoryPath)] // valid but no files found for this test
- public async void GetExternalStreams_BadPaths_ReturnsNoSubtitles(string path)
- {
- // need a media source manager capable of returning something other than file protocol
- var mediaSourceManager = new Mock<IMediaSourceManager>();
- mediaSourceManager.Setup(m => m.GetPathProtocol(It.IsRegex(@"http.*")))
- .Returns(MediaProtocol.Http);
- BaseItem.MediaSourceManager = mediaSourceManager.Object;
- var video = new Movie
- {
- Path = path
- };
- var directoryService = new Mock<IDirectoryService>(MockBehavior.Strict);
- directoryService.Setup(ds => ds.GetFilePaths(It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<bool>()))
- .Returns(Array.Empty<string>());
- var mediaEncoder = Mock.Of<IMediaEncoder>(MockBehavior.Strict);
- var fileSystem = Mock.Of<IFileSystem>();
- var subtitleResolver = new SubtitleResolver(_localizationManager, mediaEncoder, fileSystem, new NamingOptions());
- var streams = await subtitleResolver.GetExternalStreamsAsync(video, 0, directoryService.Object, false, CancellationToken.None);
- Assert.Empty(streams);
- }
- private static TheoryData<string, MediaStream[], MediaStream[]> GetExternalStreams_MergeMetadata_HandlesOverridesCorrectly_Data()
- {
- var data = new TheoryData<string, MediaStream[], MediaStream[]>();
- // filename and stream have no metadata set
- string file = "My.Video.srt";
- data.Add(
- file,
- new[]
- {
- CreateMediaStream(VideoDirectoryPath + "/" + file, null, null, 0)
- },
- new[]
- {
- CreateMediaStream(VideoDirectoryPath + "/" + file, null, null, 0)
- });
- // filename has metadata
- file = "My.Video.Title1.default.forced.en.srt";
- data.Add(
- file,
- new[]
- {
- CreateMediaStream(VideoDirectoryPath + "/" + file, null, null, 0)
- },
- new[]
- {
- CreateMediaStream(VideoDirectoryPath + "/" + file, "eng", "Title1", 0, true, true)
- });
- // single stream with metadata
- file = "My.Video.mks";
- data.Add(
- file,
- new[]
- {
- CreateMediaStream(VideoDirectoryPath + "/" + file, "eng", "Title", 0, true, true)
- },
- new[]
- {
- CreateMediaStream(VideoDirectoryPath + "/" + file, "eng", "Title", 0, true, true)
- });
- // stream wins for title/language, filename wins for flags when conflicting
- file = "My.Video.Title2.default.forced.en.srt";
- data.Add(
- file,
- new[]
- {
- CreateMediaStream(VideoDirectoryPath + "/" + file, "fra", "Metadata", 0)
- },
- new[]
- {
- CreateMediaStream(VideoDirectoryPath + "/" + file, "fra", "Metadata", 0, true, true)
- });
- // multiple stream with metadata - filename flags ignored but other data filled in when missing from stream
- file = "My.Video.Title3.default.forced.en.srt";
- data.Add(
- file,
- new[]
- {
- CreateMediaStream(VideoDirectoryPath + "/" + file, null, null, 0, true, true),
- CreateMediaStream(VideoDirectoryPath + "/" + file, "fra", "Metadata", 1)
- },
- new[]
- {
- CreateMediaStream(VideoDirectoryPath + "/" + file, "eng", "Title3", 0, true, true),
- CreateMediaStream(VideoDirectoryPath + "/" + file, "fra", "Metadata", 1)
- });
- return data;
- }
- [Theory]
- [MemberData(nameof(GetExternalStreams_MergeMetadata_HandlesOverridesCorrectly_Data))]
- public async void GetExternalStreams_MergeMetadata_HandlesOverridesCorrectly(string file, MediaStream[] inputStreams, MediaStream[] expectedStreams)
- {
- BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>();
- var video = new Movie
- {
- Path = VideoDirectoryPath + "/My.Video.mkv"
- };
- var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict);
- mediaEncoder.Setup(me => me.GetMediaInfo(It.IsAny<MediaInfoRequest>(), It.IsAny<CancellationToken>()))
- .Returns<MediaInfoRequest, CancellationToken>((_, _) => Task.FromResult(new MediaBrowser.Model.MediaInfo.MediaInfo
- {
- MediaStreams = inputStreams.ToList()
- }));
- var fileSystem = new Mock<IFileSystem>(MockBehavior.Strict);
- fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(VideoDirectoryRegex)))
- .Returns(true);
- fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MetadataDirectoryRegex)))
- .Returns(true);
- var subtitleResolver = new SubtitleResolver(_localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions());
- var directoryService = GetDirectoryServiceForExternalFile(file);
- var streams = await subtitleResolver.GetExternalStreamsAsync(video, 0, directoryService, false, CancellationToken.None);
- Assert.Equal(expectedStreams.Length, streams.Count);
- for (var i = 0; i < expectedStreams.Length; i++)
- {
- var expected = expectedStreams[i];
- var actual = streams[i];
- Assert.True(actual.IsExternal);
- Assert.Equal(expected.Index, actual.Index);
- Assert.Equal(expected.Type, actual.Type);
- Assert.Equal(expected.Path, actual.Path);
- Assert.Equal(expected.IsDefault, actual.IsDefault);
- Assert.Equal(expected.IsForced, actual.IsForced);
- Assert.Equal(expected.Language, actual.Language);
- Assert.Equal(expected.Title, actual.Title);
- }
- }
- [Theory]
- [InlineData(1, 1)]
- [InlineData(1, 2)]
- [InlineData(2, 1)]
- [InlineData(2, 2)]
- public async void GetExternalStreams_StreamIndex_HandlesFilesAndContainers(int fileCount, int streamCount)
- {
- BaseItem.MediaSourceManager = Mock.Of<IMediaSourceManager>();
- var video = new Movie
- {
- Path = VideoDirectoryPath + "/My.Video.mkv"
- };
- var files = new string[fileCount];
- for (int i = 0; i < fileCount; i++)
- {
- files[i] = $"{VideoDirectoryPath}/MyVideo.{i}.srt";
- }
- var directoryService = new Mock<IDirectoryService>(MockBehavior.Strict);
- directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(VideoDirectoryRegex), It.IsAny<bool>(), It.IsAny<bool>()))
- .Returns(files);
- directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(MetadataDirectoryRegex), It.IsAny<bool>(), It.IsAny<bool>()))
- .Returns(Array.Empty<string>());
- List<MediaStream> GenerateMediaStreams()
- {
- var mediaStreams = new List<MediaStream>();
- for (int i = 0; i < streamCount; i++)
- {
- mediaStreams.Add(new());
- }
- return mediaStreams;
- }
- var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict);
- mediaEncoder.Setup(me => me.GetMediaInfo(It.IsAny<MediaInfoRequest>(), It.IsAny<CancellationToken>()))
- .Returns<MediaInfoRequest, CancellationToken>((_, _) => Task.FromResult(new MediaBrowser.Model.MediaInfo.MediaInfo
- {
- MediaStreams = GenerateMediaStreams()
- }));
- var fileSystem = new Mock<IFileSystem>(MockBehavior.Strict);
- fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(VideoDirectoryRegex)))
- .Returns(true);
- fileSystem.Setup(fs => fs.DirectoryExists(It.IsRegex(MetadataDirectoryRegex)))
- .Returns(true);
- var subtitleResolver = new SubtitleResolver(_localizationManager, mediaEncoder.Object, fileSystem.Object, new NamingOptions());
- int startIndex = 1;
- var streams = await subtitleResolver.GetExternalStreamsAsync(video, startIndex, directoryService.Object, false, CancellationToken.None);
- Assert.Equal(fileCount * streamCount, streams.Count);
- for (var i = 0; i < streams.Count; i++)
- {
- Assert.Equal(startIndex + i, streams[i].Index);
- // intentional integer division to ensure correct number of streams come back from each file
- Assert.Matches(@$".*\.{i / streamCount}\.srt", streams[i].Path);
- }
- }
- private static MediaStream CreateMediaStream(string path, string? language, string? title, int index, bool isForced = false, bool isDefault = false)
- {
- return new MediaStream
- {
- Index = index,
- Type = MediaStreamType.Subtitle,
- Path = path,
- IsDefault = isDefault,
- IsForced = isForced,
- Language = language,
- Title = title
- };
- }
- /// <summary>
- /// Provides an <see cref="IDirectoryService"/> that when queried for the test video/metadata directory will return a path including the provided file name.
- /// </summary>
- /// <param name="file">The name of the file to locate.</param>
- /// <param name="useMetadataDirectory"><c>true</c> if the file belongs in the metadata directory.</param>
- /// <returns>A mocked <see cref="IDirectoryService"/>.</returns>
- public static IDirectoryService GetDirectoryServiceForExternalFile(string file, bool useMetadataDirectory = false)
- {
- var directoryService = new Mock<IDirectoryService>(MockBehavior.Strict);
- if (useMetadataDirectory)
- {
- directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(VideoDirectoryRegex), It.IsAny<bool>(), It.IsAny<bool>()))
- .Returns(Array.Empty<string>());
- directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(MetadataDirectoryRegex), It.IsAny<bool>(), It.IsAny<bool>()))
- .Returns(new[] { MetadataDirectoryPath + "/" + file });
- }
- else
- {
- directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(VideoDirectoryRegex), It.IsAny<bool>(), It.IsAny<bool>()))
- .Returns(new[] { VideoDirectoryPath + "/" + file });
- directoryService.Setup(ds => ds.GetFilePaths(It.IsRegex(MetadataDirectoryRegex), It.IsAny<bool>(), It.IsAny<bool>()))
- .Returns(Array.Empty<string>());
- }
- return directoryService.Object;
- }
- }
|