using ErsatzTV.Core.Domain; using ErsatzTV.Core.FFmpeg; using ErsatzTV.Core.Interfaces.FFmpeg; using ErsatzTV.Core.Tests.Fakes; using Microsoft.Extensions.Logging.Abstractions; using NUnit.Framework; using Shouldly; namespace ErsatzTV.Core.Tests.FFmpeg; [TestFixture] public class CustomStreamSelectorTests { [TestFixture] public class SelectStreams { private static readonly string TestFileName = Path.Combine(FileSystemLayout.ChannelStreamSelectorsFolder, "test.yml"); private Channel _channel; private MediaItemAudioVersion _audioVersion; private List _subtitles; [SetUp] public void SetUp() { _channel = new Channel(Guid.Empty) { StreamSelectorMode = ChannelStreamSelectorMode.Custom, StreamSelector = TestFileName }; _audioVersion = GetTestAudioVersion("eng"); _subtitles = [ new Subtitle { Id = 1, Language = "eng", Title = "Words", SubtitleKind = SubtitleKind.Embedded }, new Subtitle { Id = 2, Language = "en", Title = "Signs" }, new Subtitle { Id = 3, Language = "en", Title = "Songs" }, new Subtitle { Id = 4, Language = "en", Forced = true, SubtitleKind = SubtitleKind.Sidecar }, new Subtitle { Id = 5, Language = "jp" } ]; } [Test] public async Task Should_Select_eng_Audio_Exact_Match() { const string YAML = """ --- items: - audio_language: - "eng" """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), new NullLogger()); StreamSelectorResult result = await streamSelector.SelectStreams(_channel, _audioVersion, _subtitles); result.AudioStream.IsSome.ShouldBeTrue(); foreach (MediaStream audioStream in result.AudioStream) { audioStream.Index.ShouldBe(1); audioStream.Language.ShouldBe("eng"); } } [Test] public async Task Should_Select_eng_Audio_Exact_Match_Multiple_Audio_Languages() { const string YAML = """ --- items: - audio_language: ["en", "eng"] """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), new NullLogger()); StreamSelectorResult result = await streamSelector.SelectStreams(_channel, _audioVersion, _subtitles); result.AudioStream.IsSome.ShouldBeTrue(); foreach (MediaStream audioStream in result.AudioStream) { audioStream.Index.ShouldBe(1); audioStream.Language.ShouldBe("eng"); } } [Test] public async Task Should_Select_eng_Audio_Exact_Match_Multiple_Items() { const string YAML = """ --- items: - audio_language: - "de" subtitle_language: - "eng" - audio_language: - "eng" disable_subtitles: true """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), new NullLogger()); StreamSelectorResult result = await streamSelector.SelectStreams(_channel, _audioVersion, _subtitles); result.AudioStream.IsSome.ShouldBeTrue(); foreach (MediaStream audioStream in result.AudioStream) { audioStream.Index.ShouldBe(1); audioStream.Language.ShouldBe("eng"); } } [Test] public async Task Should_Select_eng_Audio_Pattern_Match() { const string YAML = """ --- items: - audio_language: - "en*" """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), new NullLogger()); StreamSelectorResult result = await streamSelector.SelectStreams(_channel, _audioVersion, _subtitles); result.AudioStream.IsSome.ShouldBeTrue(); foreach (MediaStream audioStream in result.AudioStream) { audioStream.Index.ShouldBe(1); audioStream.Language.ShouldBe("eng"); } } [Test] public async Task Should_Select_en_Audio_Pattern_Match() { const string YAML = """ --- items: - audio_language: - "en*" """; _audioVersion = GetTestAudioVersion("en"); var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), new NullLogger()); StreamSelectorResult result = await streamSelector.SelectStreams(_channel, _audioVersion, _subtitles); result.AudioStream.IsSome.ShouldBeTrue(); foreach (MediaStream audioStream in result.AudioStream) { audioStream.Index.ShouldBe(1); audioStream.Language.ShouldBe("en"); } } [Test] public async Task disable_subtitles_Should_Select_No_Subtitles() { const string YAML = """ --- items: - audio_language: - "eng" disable_subtitles: true """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), new NullLogger()); StreamSelectorResult result = await streamSelector.SelectStreams(_channel, _audioVersion, _subtitles); result.Subtitle.IsSome.ShouldBeFalse(); } [Test] public async Task Should_Select_eng_Subtitle_Exact_Match() { const string YAML = """ --- items: - audio_language: - "ja" subtitle_language: - "eng" """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), new NullLogger()); StreamSelectorResult result = await streamSelector.SelectStreams(_channel, _audioVersion, _subtitles); result.Subtitle.IsSome.ShouldBeTrue(); foreach (Subtitle subtitle in result.Subtitle) { subtitle.Id.ShouldBe(1); subtitle.Language.ShouldBe("eng"); } } [Test] public async Task Should_Select_eng_Subtitle_Pattern_Match() { const string YAML = """ --- items: - audio_language: - "ja" subtitle_language: - "en*" """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), new NullLogger()); StreamSelectorResult result = await streamSelector.SelectStreams(_channel, _audioVersion, _subtitles); result.Subtitle.IsSome.ShouldBeTrue(); foreach (Subtitle subtitle in result.Subtitle) { subtitle.Id.ShouldBe(1); subtitle.Language.ShouldBe("eng"); } } [Test] public async Task Should_Select_en_Subtitle_Pattern_Match() { const string YAML = """ --- items: - audio_language: - "ja" subtitle_language: - "en*" """; _audioVersion = GetTestAudioVersion("en"); _subtitles = [ new Subtitle { Id = 1, Language = "en", Title = "Words" } ]; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), new NullLogger()); StreamSelectorResult result = await streamSelector.SelectStreams(_channel, _audioVersion, _subtitles); result.Subtitle.IsSome.ShouldBeTrue(); foreach (Subtitle subtitle in result.Subtitle) { subtitle.Id.ShouldBe(1); subtitle.Language.ShouldBe("en"); } } [Test] public async Task Should_Select_No_Subtitle_Exact_Match_Multiple_Items() { const string YAML = """ --- items: - audio_language: - "de" subtitle_language: - "eng" - audio_language: - "eng" disable_subtitles: true """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), new NullLogger()); StreamSelectorResult result = await streamSelector.SelectStreams(_channel, _audioVersion, _subtitles); result.AudioStream.IsSome.ShouldBeTrue(); foreach (MediaStream audioStream in result.AudioStream) { audioStream.Index.ShouldBe(1); audioStream.Language.ShouldBe("eng"); } result.Subtitle.IsSome.ShouldBeFalse(); } [Test] public async Task Should_Select_Foreign_Audio_And_English_Subtitle_Multiple_Items() { const string YAML = """ --- items: - audio_language: - "ja" subtitle_language: - "eng" - audio_language: - "eng" disable_subtitles: true """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), new NullLogger()); StreamSelectorResult result = await streamSelector.SelectStreams(_channel, _audioVersion, _subtitles); result.AudioStream.IsSome.ShouldBeTrue(); foreach (MediaStream audioStream in result.AudioStream) { audioStream.Index.ShouldBe(0); audioStream.Language.ShouldBe("ja"); } result.Subtitle.IsSome.ShouldBeTrue(); foreach (Subtitle subtitle in result.Subtitle) { subtitle.Id.ShouldBe(1); subtitle.Language.ShouldBe("eng"); } } [Test] public async Task Should_Ignore_Blocked_Audio_Title() { const string YAML = """ --- items: - audio_language: - "en*" audio_title_blocklist: - "riff" """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), new NullLogger()); StreamSelectorResult result = await streamSelector.SelectStreams(_channel, _audioVersion, _subtitles); result.AudioStream.IsSome.ShouldBeTrue(); foreach (MediaStream audioStream in result.AudioStream) { audioStream.Index.ShouldBe(2); audioStream.Language.ShouldBe("eng"); } } [Test] public async Task Should_Select_Allowed_Audio_Title() { const string YAML = """ --- items: - audio_language: - "en*" audio_title_allowlist: - "movie" """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), new NullLogger()); StreamSelectorResult result = await streamSelector.SelectStreams(_channel, _audioVersion, _subtitles); result.AudioStream.IsSome.ShouldBeTrue(); foreach (MediaStream audioStream in result.AudioStream) { audioStream.Index.ShouldBe(2); audioStream.Language.ShouldBe("eng"); } } [Test] public async Task Should_Ignore_Blocked_Subtitle_Title() { const string YAML = """ --- items: - audio_language: - "*" subtitle_language: - "en" subtitle_title_blocklist: - "signs" """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), new NullLogger()); StreamSelectorResult result = await streamSelector.SelectStreams(_channel, _audioVersion, _subtitles); result.Subtitle.IsSome.ShouldBeTrue(); foreach (Subtitle subtitle in result.Subtitle) { subtitle.Id.ShouldBe(3); subtitle.Language.ShouldBe("en"); } } [Test] public async Task Should_Select_Allowed_Subtitle_Title() { const string YAML = """ --- items: - audio_language: - "*" subtitle_language: - "en" subtitle_title_allowlist: - "songs" """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), new NullLogger()); StreamSelectorResult result = await streamSelector.SelectStreams(_channel, _audioVersion, _subtitles); result.Subtitle.IsSome.ShouldBeTrue(); foreach (Subtitle subtitle in result.Subtitle) { subtitle.Id.ShouldBe(3); subtitle.Language.ShouldBe("en"); } } [Test] public async Task Should_Select_Condition_Forced_Subtitle() { const string YAML = """ --- items: - audio_language: - "*" subtitle_condition: "forced" """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), new NullLogger()); StreamSelectorResult result = await streamSelector.SelectStreams(_channel, _audioVersion, _subtitles); result.Subtitle.IsSome.ShouldBeTrue(); foreach (Subtitle subtitle in result.Subtitle) { subtitle.Id.ShouldBe(4); subtitle.Language.ShouldBe("en"); } } [Test] public async Task Should_Select_Condition_External_Subtitle() { const string YAML = """ --- items: - audio_language: - "*" subtitle_condition: "lang like 'en%' and external" """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), new NullLogger()); StreamSelectorResult result = await streamSelector.SelectStreams(_channel, _audioVersion, _subtitles); result.Subtitle.IsSome.ShouldBeTrue(); foreach (Subtitle subtitle in result.Subtitle) { subtitle.Id.ShouldBe(4); subtitle.Language.ShouldBe("en"); } } [Test] public async Task Should_Select_Condition_Audio_Title() { const string YAML = """ --- items: - audio_language: - "en*" audio_condition: "title like '%movie%'" """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), new NullLogger()); StreamSelectorResult result = await streamSelector.SelectStreams(_channel, _audioVersion, _subtitles); result.AudioStream.IsSome.ShouldBeTrue(); foreach (MediaStream audioStream in result.AudioStream) { audioStream.Index.ShouldBe(2); audioStream.Language.ShouldBe("eng"); } } [Test] public async Task Should_Select_Condition_Audio_Channels() { const string YAML = """ --- items: - audio_language: - "en*" audio_condition: "channels > 2" """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), new NullLogger()); StreamSelectorResult result = await streamSelector.SelectStreams(_channel, _audioVersion, _subtitles); result.AudioStream.IsSome.ShouldBeTrue(); foreach (MediaStream audioStream in result.AudioStream) { audioStream.Index.ShouldBe(2); audioStream.Language.ShouldBe("eng"); } } [Test] public async Task Should_Select_Prioritized_Audio_Language() { const string YAML = """ --- items: - audio_language: ["en*","ja"] audio_title_blocklist: ["riff"] """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), new NullLogger()); StreamSelectorResult result = await streamSelector.SelectStreams(_channel, _audioVersion, _subtitles); result.AudioStream.IsSome.ShouldBeTrue(); foreach (MediaStream audioStream in result.AudioStream) { audioStream.Index.ShouldBe(2); audioStream.Language.ShouldBe("eng"); } } [Test] public async Task Should_Select_Prioritized_Subtitle_Language() { const string YAML = """ --- items: - audio_language: - "*" subtitle_language: ["jp","en*"] """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), new NullLogger()); StreamSelectorResult result = await streamSelector.SelectStreams(_channel, _audioVersion, _subtitles); result.Subtitle.IsSome.ShouldBeTrue(); foreach (Subtitle subtitle in result.Subtitle) { subtitle.Id.ShouldBe(5); subtitle.Language.ShouldBe("jp"); } } private static MediaItemAudioVersion GetTestAudioVersion(string englishLanguage) { var mediaItem = new OtherVideo(); var mediaVersion = new MediaVersion { Streams = [ new MediaStream { Index = 0, MediaStreamKind = MediaStreamKind.Audio, Channels = 2, Language = "ja", Title = "Some Title", }, new MediaStream { Index = 1, MediaStreamKind = MediaStreamKind.Audio, Channels = 2, Language = englishLanguage, Title = "Riff Title", Default = true }, new MediaStream { Index = 2, MediaStreamKind = MediaStreamKind.Audio, Channels = 6, Language = englishLanguage, Title = "Movie Title", Default = true } ] }; return new MediaItemAudioVersion(mediaItem, mediaVersion); } } }