From 462057a4b15d826633224752da288b7726a95a87 Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Fri, 27 Jun 2025 08:31:50 -0500 Subject: [PATCH] prioritize stream selection by language (#2079) --- .../FFmpeg/CustomStreamSelectorTests.cs | 60 +++++++++++++++++-- ErsatzTV.Core/FFmpeg/CustomStreamSelector.cs | 35 +++++++++-- 2 files changed, 85 insertions(+), 10 deletions(-) diff --git a/ErsatzTV.Core.Tests/FFmpeg/CustomStreamSelectorTests.cs b/ErsatzTV.Core.Tests/FFmpeg/CustomStreamSelectorTests.cs index dd19ab28..0e1564a3 100644 --- a/ErsatzTV.Core.Tests/FFmpeg/CustomStreamSelectorTests.cs +++ b/ErsatzTV.Core.Tests/FFmpeg/CustomStreamSelectorTests.cs @@ -36,7 +36,8 @@ public class CustomStreamSelectorTests 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 = 4, Language = "en", Forced = true, SubtitleKind = SubtitleKind.Sidecar }, + new Subtitle { Id = 5, Language = "jp" } ]; } @@ -73,9 +74,7 @@ items: """ --- items: - - audio_language: - - "en" - - "eng" + - audio_language: ["en", "eng"] """; var streamSelector = new CustomStreamSelector( @@ -584,6 +583,59 @@ items: } } + [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(); diff --git a/ErsatzTV.Core/FFmpeg/CustomStreamSelector.cs b/ErsatzTV.Core/FFmpeg/CustomStreamSelector.cs index 63570320..f9f60cd8 100644 --- a/ErsatzTV.Core/FFmpeg/CustomStreamSelector.cs +++ b/ErsatzTV.Core/FFmpeg/CustomStreamSelector.cs @@ -33,8 +33,8 @@ public class CustomStreamSelector(ILocalFileSystem localFileSystem, ILogger a, _ => int.MaxValue); + var candidateSubtitles = allSubtitles.ToDictionary(s => s, _ => int.MaxValue); // try to find matching audio stream foreach (MediaStream audioStream in audioStreams.ToList()) @@ -45,8 +45,10 @@ public class CustomStreamSelector(ILocalFileSystem localFileSystem, ILogger 0) { // match any of the listed languages - foreach (string audioLanguage in streamSelectorItem.AudioLanguages) + for (var langIndex = 0; langIndex < streamSelectorItem.AudioLanguages.Count; langIndex++) { + string audioLanguage = streamSelectorItem.AudioLanguages[langIndex]; + // special case if (audioLanguage == "*") { @@ -56,6 +58,12 @@ public class CustomStreamSelector(ILocalFileSystem localFileSystem, ILogger 0) { // match any of the listed languages - foreach (string subtitleLanguage in streamSelectorItem.SubtitleLanguages) + for (var langIndex = 0; langIndex < streamSelectorItem.SubtitleLanguages.Count; langIndex++) { + string subtitleLanguage = streamSelectorItem.SubtitleLanguages[langIndex]; + // special case if (subtitleLanguage == "*") { @@ -132,6 +142,12 @@ public class CustomStreamSelector(ILocalFileSystem localFileSystem, ILogger maybeAudioStream = candidateAudioStreams.HeadOrNone(); - Option maybeSubtitle = candidateSubtitles.HeadOrNone(); + Option maybeAudioStream = candidateAudioStreams + .OrderBy(a => a.Value) + .Select(a => a.Key) + .HeadOrNone(); + + Option maybeSubtitle = candidateSubtitles + .OrderBy(s => s.Value) + .Select(s => s.Key) + .HeadOrNone(); if (maybeAudioStream.IsSome) {