MediaStreamSelector.cs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. #pragma warning disable CS1591
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using Jellyfin.Data.Enums;
  6. using Jellyfin.Extensions;
  7. using MediaBrowser.Model.Entities;
  8. namespace Emby.Server.Implementations.Library
  9. {
  10. public static class MediaStreamSelector
  11. {
  12. public static int? GetDefaultAudioStreamIndex(IReadOnlyList<MediaStream> streams, IReadOnlyList<string> preferredLanguages, bool preferDefaultTrack)
  13. {
  14. var sortedStreams = GetSortedStreams(streams, MediaStreamType.Audio, preferredLanguages);
  15. if (preferDefaultTrack)
  16. {
  17. return sortedStreams.FirstOrDefault(i => i.IsDefault)?.Index;
  18. }
  19. return sortedStreams.FirstOrDefault()?.Index;
  20. }
  21. public static int? GetDefaultSubtitleStreamIndex(
  22. IEnumerable<MediaStream> streams,
  23. IReadOnlyList<string> preferredLanguages,
  24. SubtitlePlaybackMode mode,
  25. string audioTrackLanguage)
  26. {
  27. if (mode == SubtitlePlaybackMode.None)
  28. {
  29. return null;
  30. }
  31. var sortedStreams = streams
  32. .Where(i => i.Type == MediaStreamType.Subtitle)
  33. .OrderByDescending(x => x.IsExternal)
  34. .ThenByDescending(x => x.IsForced && string.Equals(x.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
  35. .ThenByDescending(x => x.IsForced)
  36. .ThenByDescending(x => x.IsDefault)
  37. .ToList();
  38. MediaStream? stream = null;
  39. if (mode == SubtitlePlaybackMode.Default)
  40. {
  41. // Prefer embedded metadata over smart logic
  42. stream = sortedStreams.FirstOrDefault(s => s.IsExternal || s.IsForced || s.IsDefault);
  43. // if the audio language is not understood by the user, load their preferred subs, if there are any
  44. if (stream == null && !preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
  45. {
  46. stream = sortedStreams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase));
  47. }
  48. }
  49. else if (mode == SubtitlePlaybackMode.Smart)
  50. {
  51. // if the audio language is not understood by the user, load their preferred subs, if there are any
  52. if (!preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
  53. {
  54. stream = streams.FirstOrDefault(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase)) ??
  55. streams.FirstOrDefault(s => preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase));
  56. }
  57. }
  58. else if (mode == SubtitlePlaybackMode.Always)
  59. {
  60. // always load the most suitable full subtitles
  61. stream = sortedStreams.FirstOrDefault(s => !s.IsForced);
  62. }
  63. else if (mode == SubtitlePlaybackMode.OnlyForced)
  64. {
  65. // always load the most suitable full subtitles
  66. stream = sortedStreams.FirstOrDefault(x => x.IsForced);
  67. }
  68. // load forced subs if we have found no suitable full subtitles
  69. stream ??= sortedStreams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase));
  70. return stream?.Index;
  71. }
  72. private static IEnumerable<MediaStream> GetSortedStreams(IEnumerable<MediaStream> streams, MediaStreamType type, IReadOnlyList<string> languagePreferences)
  73. {
  74. // Give some preference to external text subs for better performance
  75. return streams
  76. .Where(i => i.Type == type)
  77. .OrderBy(i =>
  78. {
  79. var index = languagePreferences.FindIndex(x => string.Equals(x, i.Language, StringComparison.OrdinalIgnoreCase));
  80. return index == -1 ? 100 : index;
  81. })
  82. .ThenBy(i => GetBooleanOrderBy(i.IsDefault))
  83. .ThenBy(i => GetBooleanOrderBy(i.SupportsExternalStream))
  84. .ThenBy(i => GetBooleanOrderBy(i.IsTextSubtitleStream))
  85. .ThenBy(i => GetBooleanOrderBy(i.IsExternal))
  86. .ThenBy(i => i.Index);
  87. }
  88. public static void SetSubtitleStreamScores(
  89. IReadOnlyList<MediaStream> streams,
  90. IReadOnlyList<string> preferredLanguages,
  91. SubtitlePlaybackMode mode,
  92. string audioTrackLanguage)
  93. {
  94. if (mode == SubtitlePlaybackMode.None)
  95. {
  96. return;
  97. }
  98. var sortedStreams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages);
  99. var filteredStreams = new List<MediaStream>();
  100. if (mode == SubtitlePlaybackMode.Default)
  101. {
  102. // Prefer embedded metadata over smart logic
  103. filteredStreams = sortedStreams.Where(s => s.IsForced || s.IsDefault)
  104. .ToList();
  105. }
  106. else if (mode == SubtitlePlaybackMode.Smart)
  107. {
  108. // Prefer smart logic over embedded metadata
  109. if (!preferredLanguages.Contains(audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
  110. {
  111. filteredStreams = sortedStreams.Where(s => !s.IsForced && preferredLanguages.Contains(s.Language, StringComparison.OrdinalIgnoreCase))
  112. .ToList();
  113. }
  114. }
  115. else if (mode == SubtitlePlaybackMode.Always)
  116. {
  117. // always load the most suitable full subtitles
  118. filteredStreams = sortedStreams.Where(s => !s.IsForced).ToList();
  119. }
  120. else if (mode == SubtitlePlaybackMode.OnlyForced)
  121. {
  122. // always load the most suitable full subtitles
  123. filteredStreams = sortedStreams.Where(s => s.IsForced).ToList();
  124. }
  125. // load forced subs if we have found no suitable full subtitles
  126. var iterStreams = filteredStreams.Count == 0
  127. ? sortedStreams.Where(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
  128. : filteredStreams;
  129. foreach (var stream in iterStreams)
  130. {
  131. stream.Score = GetSubtitleScore(stream, preferredLanguages);
  132. }
  133. }
  134. private static int GetSubtitleScore(MediaStream stream, IReadOnlyList<string> languagePreferences)
  135. {
  136. var values = new List<int>();
  137. var index = languagePreferences.FindIndex(x => string.Equals(x, stream.Language, StringComparison.OrdinalIgnoreCase));
  138. values.Add(index == -1 ? 0 : 100 - index);
  139. values.Add(stream.IsForced ? 1 : 0);
  140. values.Add(stream.IsDefault ? 1 : 0);
  141. values.Add(stream.SupportsExternalStream ? 1 : 0);
  142. values.Add(stream.IsTextSubtitleStream ? 1 : 0);
  143. values.Add(stream.IsExternal ? 1 : 0);
  144. values.Reverse();
  145. var scale = 1;
  146. var score = 0;
  147. foreach (var value in values)
  148. {
  149. score += scale * (value + 1);
  150. scale *= 10;
  151. }
  152. return score;
  153. }
  154. private static int GetBooleanOrderBy(bool value)
  155. {
  156. return value ? 0 : 1;
  157. }
  158. }
  159. }