SubtitleResolver.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. #pragma warning disable CS1591
  2. using System;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using MediaBrowser.Controller.Entities;
  6. using MediaBrowser.Controller.Providers;
  7. using MediaBrowser.Model.Entities;
  8. using MediaBrowser.Model.Globalization;
  9. namespace MediaBrowser.Providers.MediaInfo
  10. {
  11. public class SubtitleResolver
  12. {
  13. private readonly ILocalizationManager _localization;
  14. public SubtitleResolver(ILocalizationManager localization)
  15. {
  16. _localization = localization;
  17. }
  18. public List<MediaStream> GetExternalSubtitleStreams(
  19. Video video,
  20. int startIndex,
  21. IDirectoryService directoryService,
  22. bool clearCache)
  23. {
  24. var streams = new List<MediaStream>();
  25. if (!video.IsFileProtocol)
  26. {
  27. return streams;
  28. }
  29. AddExternalSubtitleStreams(streams, video.ContainingFolderPath, video.Path, startIndex, directoryService, clearCache);
  30. startIndex += streams.Count;
  31. string folder = video.GetInternalMetadataPath();
  32. if (!Directory.Exists(folder))
  33. {
  34. return streams;
  35. }
  36. try
  37. {
  38. AddExternalSubtitleStreams(streams, folder, video.Path, startIndex, directoryService, clearCache);
  39. }
  40. catch (IOException)
  41. {
  42. }
  43. return streams;
  44. }
  45. public IEnumerable<string> GetExternalSubtitleFiles(
  46. Video video,
  47. IDirectoryService directoryService,
  48. bool clearCache)
  49. {
  50. if (!video.IsFileProtocol)
  51. {
  52. yield break;
  53. }
  54. var streams = GetExternalSubtitleStreams(video, 0, directoryService, clearCache);
  55. foreach (var stream in streams)
  56. {
  57. yield return stream.Path;
  58. }
  59. }
  60. public void AddExternalSubtitleStreams(
  61. List<MediaStream> streams,
  62. string videoPath,
  63. int startIndex,
  64. IReadOnlyList<string> files)
  65. {
  66. var videoFileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(videoPath);
  67. for (var i = 0; i < files.Count; i++)
  68. {
  69. var fullName = files[i];
  70. var extension = Path.GetExtension(fullName.AsSpan());
  71. if (!IsSubtitleExtension(extension))
  72. {
  73. continue;
  74. }
  75. var fileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(fullName);
  76. MediaStream mediaStream;
  77. // The subtitle filename must either be equal to the video filename or start with the video filename followed by a dot
  78. if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
  79. {
  80. mediaStream = new MediaStream
  81. {
  82. Index = startIndex++,
  83. Type = MediaStreamType.Subtitle,
  84. IsExternal = true,
  85. Path = fullName
  86. };
  87. }
  88. else if (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length
  89. && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.'
  90. && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
  91. {
  92. var isForced = fullName.Contains(".forced.", StringComparison.OrdinalIgnoreCase)
  93. || fullName.Contains(".foreign.", StringComparison.OrdinalIgnoreCase);
  94. var isDefault = fullName.Contains(".default.", StringComparison.OrdinalIgnoreCase);
  95. // Support xbmc naming conventions - 300.spanish.srt
  96. var languageSpan = fileNameWithoutExtension;
  97. while (languageSpan.Length > 0)
  98. {
  99. var lastDot = languageSpan.LastIndexOf('.');
  100. var currentSlice = languageSpan[lastDot..];
  101. if (currentSlice.Equals(".default", StringComparison.OrdinalIgnoreCase)
  102. || currentSlice.Equals(".forced", StringComparison.OrdinalIgnoreCase)
  103. || currentSlice.Equals(".foreign", StringComparison.OrdinalIgnoreCase))
  104. {
  105. languageSpan = languageSpan[..lastDot];
  106. continue;
  107. }
  108. languageSpan = languageSpan[(lastDot + 1)..];
  109. break;
  110. }
  111. // Try to translate to three character code
  112. // Be flexible and check against both the full and three character versions
  113. var language = languageSpan.ToString();
  114. var culture = _localization.FindLanguageInfo(language);
  115. language = culture == null ? language : culture.ThreeLetterISOLanguageName;
  116. mediaStream = new MediaStream
  117. {
  118. Index = startIndex++,
  119. Type = MediaStreamType.Subtitle,
  120. IsExternal = true,
  121. Path = fullName,
  122. Language = language,
  123. IsForced = isForced,
  124. IsDefault = isDefault
  125. };
  126. }
  127. else
  128. {
  129. continue;
  130. }
  131. mediaStream.Codec = extension.TrimStart('.').ToString().ToLowerInvariant();
  132. streams.Add(mediaStream);
  133. }
  134. }
  135. private static bool IsSubtitleExtension(ReadOnlySpan<char> extension)
  136. {
  137. return extension.Equals(".srt", StringComparison.OrdinalIgnoreCase)
  138. || extension.Equals(".ssa", StringComparison.OrdinalIgnoreCase)
  139. || extension.Equals(".ass", StringComparison.OrdinalIgnoreCase)
  140. || extension.Equals(".sub", StringComparison.OrdinalIgnoreCase)
  141. || extension.Equals(".vtt", StringComparison.OrdinalIgnoreCase)
  142. || extension.Equals(".smi", StringComparison.OrdinalIgnoreCase)
  143. || extension.Equals(".sami", StringComparison.OrdinalIgnoreCase);
  144. }
  145. private static ReadOnlySpan<char> NormalizeFilenameForSubtitleComparison(string filename)
  146. {
  147. // Try to account for sloppy file naming
  148. filename = filename.Replace("_", string.Empty, StringComparison.Ordinal);
  149. filename = filename.Replace(" ", string.Empty, StringComparison.Ordinal);
  150. return Path.GetFileNameWithoutExtension(filename.AsSpan());
  151. }
  152. private void AddExternalSubtitleStreams(
  153. List<MediaStream> streams,
  154. string folder,
  155. string videoPath,
  156. int startIndex,
  157. IDirectoryService directoryService,
  158. bool clearCache)
  159. {
  160. var files = directoryService.GetSortedFilePaths(folder, clearCache);
  161. AddExternalSubtitleStreams(streams, videoPath, startIndex, files);
  162. }
  163. }
  164. }