فهرست منبع

Respect preferred language when selecting forced subtitles (#13098)

Rework subtitle selection logic
timminator 2 ماه پیش
والد
کامیت
c24d0c1240
1فایلهای تغییر یافته به همراه60 افزوده شده و 26 حذف شده
  1. 60 26
      Emby.Server.Implementations/Library/MediaStreamSelector.cs

+ 60 - 26
Emby.Server.Implementations/Library/MediaStreamSelector.cs

@@ -39,46 +39,48 @@ namespace Emby.Server.Implementations.Library
                 return null;
             }
 
+            // Sort in the following order: Default > No tag > Forced
             var sortedStreams = streams
                 .Where(i => i.Type == MediaStreamType.Subtitle)
                 .OrderByDescending(x => x.IsExternal)
-                .ThenByDescending(x => x.IsForced && string.Equals(x.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
-                .ThenByDescending(x => x.IsForced)
                 .ThenByDescending(x => x.IsDefault)
-                .ThenByDescending(x => preferredLanguages.Contains(x.Language, StringComparison.OrdinalIgnoreCase))
+                .ThenByDescending(x => !x.IsForced && MatchesPreferredLanguage(x.Language, preferredLanguages))
+                .ThenByDescending(x => x.IsForced && MatchesPreferredLanguage(x.Language, preferredLanguages))
+                .ThenByDescending(x => x.IsForced && IsLanguageUndefined(x.Language))
+                .ThenByDescending(x => x.IsForced)
                 .ToList();
 
             MediaStream? stream = null;
+
             if (mode == SubtitlePlaybackMode.Default)
             {
-                // Load subtitles according to external, forced and default flags.
-                stream = sortedStreams.FirstOrDefault(x => x.IsExternal || x.IsForced || x.IsDefault);
+                // Load subtitles according to external, default and forced flags.
+                stream = sortedStreams.FirstOrDefault(x => x.IsExternal || x.IsDefault || x.IsForced);
             }
             else if (mode == SubtitlePlaybackMode.Smart)
             {
                 // Only attempt to load subtitles if the audio language is not one of the user's preferred subtitle languages.
-                // If no subtitles of preferred language available, use default behaviour.
+                // If no subtitles of preferred language available, use none.
+                // If the audio language is one of the user's preferred subtitle languages behave like OnlyForced.
                 if (!preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
                 {
-                    stream = sortedStreams.FirstOrDefault(x => preferredLanguages.Contains(x.Language, StringComparison.OrdinalIgnoreCase)) ??
-                        sortedStreams.FirstOrDefault(x => x.IsExternal || x.IsForced || x.IsDefault);
+                    stream = sortedStreams.FirstOrDefault(x => MatchesPreferredLanguage(x.Language, preferredLanguages));
                 }
                 else
                 {
-                    // Respect forced flag.
-                    stream = sortedStreams.FirstOrDefault(x => x.IsForced);
+                    stream = BehaviorOnlyForced(sortedStreams, preferredLanguages).FirstOrDefault();
                 }
             }
             else if (mode == SubtitlePlaybackMode.Always)
             {
-                // Always load (full/non-forced) subtitles of the user's preferred subtitle language if possible, otherwise default behaviour.
-                stream = sortedStreams.FirstOrDefault(x => !x.IsForced && preferredLanguages.Contains(x.Language, StringComparison.OrdinalIgnoreCase)) ??
-                    sortedStreams.FirstOrDefault(x => x.IsExternal || x.IsForced || x.IsDefault);
+                // Always load (full/non-forced) subtitles of the user's preferred subtitle language if possible, otherwise OnlyForced behaviour.
+                stream = sortedStreams.FirstOrDefault(x => !x.IsForced && MatchesPreferredLanguage(x.Language, preferredLanguages)) ??
+                    BehaviorOnlyForced(sortedStreams, preferredLanguages).FirstOrDefault();
             }
             else if (mode == SubtitlePlaybackMode.OnlyForced)
             {
-                // Only load subtitles that are flagged forced.
-                stream = sortedStreams.FirstOrDefault(x => x.IsForced);
+                // Load subtitles that are flagged forced of the user's preferred subtitle language or with an undefined language
+                stream = BehaviorOnlyForced(sortedStreams, preferredLanguages).FirstOrDefault();
             }
 
             return stream?.Index;
@@ -110,40 +112,72 @@ namespace Emby.Server.Implementations.Library
             if (mode == SubtitlePlaybackMode.Default)
             {
                 // Prefer embedded metadata over smart logic
-                filteredStreams = sortedStreams.Where(s => s.IsForced || s.IsDefault)
+                // Load subtitles according to external, default, and forced flags.
+                filteredStreams = sortedStreams.Where(s => s.IsExternal || s.IsDefault || s.IsForced)
                     .ToList();
             }
             else if (mode == SubtitlePlaybackMode.Smart)
             {
                 // Prefer smart logic over embedded metadata
+                // Only attempt to load subtitles if the audio language is not one of the user's preferred subtitle languages, otherwise OnlyForced behavior.
                 if (!preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
                 {
-                    filteredStreams = sortedStreams.Where(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase))
+                    filteredStreams = sortedStreams.Where(s => MatchesPreferredLanguage(s.Language, preferredLanguages))
                         .ToList();
                 }
+                else
+                {
+                    filteredStreams = BehaviorOnlyForced(sortedStreams, preferredLanguages);
+                }
             }
             else if (mode == SubtitlePlaybackMode.Always)
             {
-                // Always load the most suitable full subtitles
-                filteredStreams = sortedStreams.Where(s => !s.IsForced).ToList();
+                // Always load (full/non-forced) subtitles of the user's preferred subtitle language if possible, otherwise OnlyForced behavior.
+                filteredStreams = sortedStreams.Where(s => !s.IsForced && MatchesPreferredLanguage(s.Language, preferredLanguages))
+                    .ToList() ?? BehaviorOnlyForced(sortedStreams, preferredLanguages);
             }
             else if (mode == SubtitlePlaybackMode.OnlyForced)
             {
-                // Always load the most suitable full subtitles
-                filteredStreams = sortedStreams.Where(s => s.IsForced).ToList();
+                // Load subtitles that are flagged forced of the user's preferred subtitle language or with an undefined language
+                filteredStreams = BehaviorOnlyForced(sortedStreams, preferredLanguages);
             }
 
-            // Load forced subs if we have found no suitable full subtitles
-            var iterStreams = filteredStreams is null || filteredStreams.Count == 0
-                ? sortedStreams.Where(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
-                : filteredStreams;
+            // If filteredStreams is null, initialize it as an empty list to avoid null reference errors
+            filteredStreams ??= new List<MediaStream>();
 
-            foreach (var stream in iterStreams)
+            foreach (var stream in filteredStreams)
             {
                 stream.Score = GetStreamScore(stream, preferredLanguages);
             }
         }
 
+        private static bool MatchesPreferredLanguage(string language, IReadOnlyList<string> preferredLanguages)
+        {
+            // If preferredLanguages is empty, treat it as "any language" (wildcard)
+            return preferredLanguages.Count == 0 ||
+                preferredLanguages.Contains(language, StringComparison.OrdinalIgnoreCase);
+        }
+
+        private static bool IsLanguageUndefined(string language)
+        {
+            // Check for null, empty, or known placeholders
+            return string.IsNullOrEmpty(language) ||
+                language.Equals("und", StringComparison.OrdinalIgnoreCase) ||
+                language.Equals("unknown", StringComparison.OrdinalIgnoreCase) ||
+                language.Equals("undetermined", StringComparison.OrdinalIgnoreCase) ||
+                language.Equals("mul", StringComparison.OrdinalIgnoreCase) ||
+                language.Equals("zxx", StringComparison.OrdinalIgnoreCase);
+        }
+
+        private static List<MediaStream> BehaviorOnlyForced(IEnumerable<MediaStream> sortedStreams, IReadOnlyList<string> preferredLanguages)
+        {
+            return sortedStreams
+                .Where(s => s.IsForced && (MatchesPreferredLanguage(s.Language, preferredLanguages) || IsLanguageUndefined(s.Language)))
+                .OrderByDescending(s => MatchesPreferredLanguage(s.Language, preferredLanguages))
+                .ThenByDescending(s => IsLanguageUndefined(s.Language))
+                .ToList();
+        }
+
         internal static int GetStreamScore(MediaStream stream, IReadOnlyList<string> languagePreferences)
         {
             var index = languagePreferences.FindIndex(x => string.Equals(x, stream.Language, StringComparison.OrdinalIgnoreCase));