SubtitleResolver.cs 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Runtime.CompilerServices;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. using Emby.Naming.Common;
  10. using Emby.Naming.Subtitles;
  11. using MediaBrowser.Controller.Entities;
  12. using MediaBrowser.Controller.MediaEncoding;
  13. using MediaBrowser.Controller.Providers;
  14. using MediaBrowser.Model.Dlna;
  15. using MediaBrowser.Model.Dto;
  16. using MediaBrowser.Model.Entities;
  17. using MediaBrowser.Model.Globalization;
  18. using MediaBrowser.Model.MediaInfo;
  19. namespace MediaBrowser.Providers.MediaInfo
  20. {
  21. /// <summary>
  22. /// Resolves external subtitles for videos.
  23. /// </summary>
  24. public class SubtitleResolver
  25. {
  26. private readonly ILocalizationManager _localizationManager;
  27. private readonly IMediaEncoder _mediaEncoder;
  28. private readonly NamingOptions _namingOptions;
  29. private readonly SubtitleFilePathParser _subtitleFilePathParser;
  30. private readonly CompareInfo _compareInfo = CultureInfo.InvariantCulture.CompareInfo;
  31. private const CompareOptions CompareOptions = System.Globalization.CompareOptions.IgnoreCase | System.Globalization.CompareOptions.IgnoreNonSpace | System.Globalization.CompareOptions.IgnoreSymbols;
  32. /// <summary>
  33. /// Initializes a new instance of the <see cref="SubtitleResolver"/> class.
  34. /// </summary>
  35. /// <param name="localization">The localization manager.</param>
  36. /// <param name="mediaEncoder">The media encoder.</param>
  37. /// <param name="namingOptions">The naming Options.</param>
  38. public SubtitleResolver(
  39. ILocalizationManager localization,
  40. IMediaEncoder mediaEncoder,
  41. NamingOptions namingOptions)
  42. {
  43. _localizationManager = localization;
  44. _mediaEncoder = mediaEncoder;
  45. _namingOptions = namingOptions;
  46. _subtitleFilePathParser = new SubtitleFilePathParser(_namingOptions);
  47. }
  48. /// <summary>
  49. /// Retrieves the external subtitle streams for the provided video.
  50. /// </summary>
  51. /// <param name="video">The video to search from.</param>
  52. /// <param name="startIndex">The stream index to start adding subtitle streams at.</param>
  53. /// <param name="directoryService">The directory service to search for files.</param>
  54. /// <param name="clearCache">True if the directory service cache should be cleared before searching.</param>
  55. /// <param name="cancellationToken">The cancellation token to cancel operation.</param>
  56. /// <returns>The external subtitle streams located.</returns>
  57. public async IAsyncEnumerable<MediaStream> GetExternalSubtitleStreams(
  58. Video video,
  59. int startIndex,
  60. IDirectoryService directoryService,
  61. bool clearCache,
  62. [EnumeratorCancellation] CancellationToken cancellationToken)
  63. {
  64. cancellationToken.ThrowIfCancellationRequested();
  65. if (!video.IsFileProtocol)
  66. {
  67. yield break;
  68. }
  69. var subtitleFileInfos = GetExternalSubtitleFiles(video, directoryService, clearCache);
  70. var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path);
  71. foreach (var subtitleFileInfo in subtitleFileInfos)
  72. {
  73. string fileName = Path.GetFileName(subtitleFileInfo.Path);
  74. string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(subtitleFileInfo.Path);
  75. Model.MediaInfo.MediaInfo mediaInfo = await GetMediaInfo(subtitleFileInfo.Path, cancellationToken).ConfigureAwait(false);
  76. if (mediaInfo.MediaStreams.Count == 1)
  77. {
  78. MediaStream mediaStream = mediaInfo.MediaStreams.First();
  79. mediaStream.Index = startIndex++;
  80. mediaStream.Type = MediaStreamType.Subtitle;
  81. mediaStream.IsExternal = true;
  82. mediaStream.Path = subtitleFileInfo.Path;
  83. mediaStream.IsDefault = subtitleFileInfo.IsDefault || mediaStream.IsDefault;
  84. mediaStream.IsForced = subtitleFileInfo.IsForced || mediaStream.IsForced;
  85. yield return DetectLanguage(mediaStream, fileNameWithoutExtension, videoFileNameWithoutExtension);
  86. }
  87. else
  88. {
  89. foreach (MediaStream mediaStream in mediaInfo.MediaStreams)
  90. {
  91. mediaStream.Index = startIndex++;
  92. mediaStream.Type = MediaStreamType.Subtitle;
  93. mediaStream.IsExternal = true;
  94. mediaStream.Path = subtitleFileInfo.Path;
  95. yield return DetectLanguage(mediaStream, fileNameWithoutExtension, videoFileNameWithoutExtension);
  96. }
  97. }
  98. }
  99. }
  100. /// <summary>
  101. /// Locates the external subtitle files for the provided video.
  102. /// </summary>
  103. /// <param name="video">The video to search from.</param>
  104. /// <param name="directoryService">The directory service to search for files.</param>
  105. /// <param name="clearCache">True if the directory service cache should be cleared before searching.</param>
  106. /// <returns>The external subtitle file paths located.</returns>
  107. public IEnumerable<SubtitleFileInfo> GetExternalSubtitleFiles(
  108. Video video,
  109. IDirectoryService directoryService,
  110. bool clearCache)
  111. {
  112. if (!video.IsFileProtocol)
  113. {
  114. yield break;
  115. }
  116. // Check if video folder exists
  117. string folder = video.ContainingFolderPath;
  118. if (!Directory.Exists(folder))
  119. {
  120. yield break;
  121. }
  122. var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path);
  123. var files = directoryService.GetFilePaths(folder, clearCache, true);
  124. for (int i = 0; i < files.Count; i++)
  125. {
  126. var subtitleFileInfo = _subtitleFilePathParser.ParseFile(files[i]);
  127. if (subtitleFileInfo == null)
  128. {
  129. continue;
  130. }
  131. yield return subtitleFileInfo;
  132. }
  133. }
  134. /// <summary>
  135. /// Returns the media info of the given subtitle file.
  136. /// </summary>
  137. /// <param name="path">The path to the subtitle file.</param>
  138. /// <param name="cancellationToken">The cancellation token to cancel operation.</param>
  139. /// <returns>The media info for the given subtitle file.</returns>
  140. private Task<Model.MediaInfo.MediaInfo> GetMediaInfo(string path, CancellationToken cancellationToken)
  141. {
  142. cancellationToken.ThrowIfCancellationRequested();
  143. return _mediaEncoder.GetMediaInfo(
  144. new MediaInfoRequest
  145. {
  146. MediaType = DlnaProfileType.Subtitle,
  147. MediaSource = new MediaSourceInfo
  148. {
  149. Path = path,
  150. Protocol = MediaProtocol.File
  151. }
  152. },
  153. cancellationToken);
  154. }
  155. private MediaStream DetectLanguage(MediaStream mediaStream, string fileNameWithoutExtension, string videoFileNameWithoutExtension)
  156. {
  157. // Support xbmc naming conventions - 300.spanish.srt
  158. var languageString = fileNameWithoutExtension;
  159. while (languageString.Length > 0)
  160. {
  161. var lastDot = languageString.LastIndexOf('.');
  162. if (lastDot < videoFileNameWithoutExtension.Length)
  163. {
  164. break;
  165. }
  166. var currentSlice = languageString[lastDot..];
  167. languageString = languageString[..lastDot];
  168. if (currentSlice.Equals(".default", StringComparison.OrdinalIgnoreCase)
  169. || currentSlice.Equals(".forced", StringComparison.OrdinalIgnoreCase)
  170. || currentSlice.Equals(".foreign", StringComparison.OrdinalIgnoreCase))
  171. {
  172. continue;
  173. }
  174. var currentSliceString = currentSlice[1..];
  175. // Try to translate to three character code
  176. var culture = _localizationManager.FindLanguageInfo(currentSliceString);
  177. if (culture == null || mediaStream.Language != null)
  178. {
  179. if (mediaStream.Title == null)
  180. {
  181. mediaStream.Title = currentSliceString;
  182. }
  183. }
  184. else
  185. {
  186. mediaStream.Language = culture.ThreeLetterISOLanguageName;
  187. }
  188. }
  189. return mediaStream;
  190. }
  191. }
  192. }