SubtitleResolver.cs 7.3 KB

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