MediaInfoResolver.cs 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. using Emby.Naming.Common;
  9. using Emby.Naming.ExternalFiles;
  10. using MediaBrowser.Controller.Entities;
  11. using MediaBrowser.Controller.MediaEncoding;
  12. using MediaBrowser.Controller.Providers;
  13. using MediaBrowser.Model.Dlna;
  14. using MediaBrowser.Model.Dto;
  15. using MediaBrowser.Model.Entities;
  16. using MediaBrowser.Model.Globalization;
  17. using MediaBrowser.Model.MediaInfo;
  18. namespace MediaBrowser.Providers.MediaInfo
  19. {
  20. /// <summary>
  21. /// Resolves external files for <see cref="Video"/>.
  22. /// </summary>
  23. public abstract class MediaInfoResolver
  24. {
  25. /// <summary>
  26. /// The <see cref="CompareOptions"/> instance.
  27. /// </summary>
  28. private const CompareOptions CompareOptions = System.Globalization.CompareOptions.IgnoreCase | System.Globalization.CompareOptions.IgnoreNonSpace | System.Globalization.CompareOptions.IgnoreSymbols;
  29. /// <summary>
  30. /// The <see cref="CompareInfo"/> instance.
  31. /// </summary>
  32. private readonly CompareInfo _compareInfo = CultureInfo.InvariantCulture.CompareInfo;
  33. /// <summary>
  34. /// The <see cref="ExternalPathParser"/> instance.
  35. /// </summary>
  36. private readonly ExternalPathParser _externalPathParser;
  37. /// <summary>
  38. /// The <see cref="IMediaEncoder"/> instance.
  39. /// </summary>
  40. private readonly IMediaEncoder _mediaEncoder;
  41. /// <summary>
  42. /// The <see cref="NamingOptions"/> instance.
  43. /// </summary>
  44. private readonly NamingOptions _namingOptions;
  45. /// <summary>
  46. /// The <see cref="DlnaProfileType"/> of the files this resolver should resolve.
  47. /// </summary>
  48. private readonly DlnaProfileType _type;
  49. /// <summary>
  50. /// Initializes a new instance of the <see cref="MediaInfoResolver"/> class.
  51. /// </summary>
  52. /// <param name="localizationManager">The localization manager.</param>
  53. /// <param name="mediaEncoder">The media encoder.</param>
  54. /// <param name="namingOptions">The <see cref="NamingOptions"/> object containing FileExtensions, MediaDefaultFlags, MediaForcedFlags and MediaFlagDelimiters.</param>
  55. /// <param name="type">The <see cref="DlnaProfileType"/> of the parsed file.</param>
  56. protected MediaInfoResolver(
  57. ILocalizationManager localizationManager,
  58. IMediaEncoder mediaEncoder,
  59. NamingOptions namingOptions,
  60. DlnaProfileType type)
  61. {
  62. _mediaEncoder = mediaEncoder;
  63. _namingOptions = namingOptions;
  64. _type = type;
  65. _externalPathParser = new ExternalPathParser(namingOptions, localizationManager, _type);
  66. }
  67. /// <summary>
  68. /// Retrieves the external streams for the provided video.
  69. /// </summary>
  70. /// <param name="video">The <see cref="Video"/> object to search external streams for.</param>
  71. /// <param name="startIndex">The stream index to start adding external streams at.</param>
  72. /// <param name="directoryService">The directory service to search for files.</param>
  73. /// <param name="clearCache">True if the directory service cache should be cleared before searching.</param>
  74. /// <param name="cancellationToken">The cancellation token.</param>
  75. /// <returns>The external streams located.</returns>
  76. public async Task<IReadOnlyList<MediaStream>> GetExternalStreamsAsync(
  77. Video video,
  78. int startIndex,
  79. IDirectoryService directoryService,
  80. bool clearCache,
  81. CancellationToken cancellationToken)
  82. {
  83. if (!video.IsFileProtocol)
  84. {
  85. return Array.Empty<MediaStream>();
  86. }
  87. var pathInfos = GetExternalFiles(video, directoryService, clearCache);
  88. if (!pathInfos.Any())
  89. {
  90. return Array.Empty<MediaStream>();
  91. }
  92. var mediaStreams = new List<MediaStream>();
  93. foreach (var pathInfo in pathInfos)
  94. {
  95. var mediaInfo = await GetMediaInfo(pathInfo.Path, _type, cancellationToken).ConfigureAwait(false);
  96. if (mediaInfo.MediaStreams.Count == 1)
  97. {
  98. MediaStream mediaStream = mediaInfo.MediaStreams[0];
  99. mediaStream.Index = startIndex++;
  100. mediaStream.IsDefault = pathInfo.IsDefault || mediaStream.IsDefault;
  101. mediaStream.IsForced = pathInfo.IsForced || mediaStream.IsForced;
  102. mediaStreams.Add(MergeMetadata(mediaStream, pathInfo));
  103. }
  104. else
  105. {
  106. foreach (MediaStream mediaStream in mediaInfo.MediaStreams)
  107. {
  108. mediaStream.Index = startIndex++;
  109. mediaStreams.Add(MergeMetadata(mediaStream, pathInfo));
  110. }
  111. }
  112. }
  113. return mediaStreams.AsReadOnly();
  114. }
  115. /// <summary>
  116. /// Returns the external file infos for the given video.
  117. /// </summary>
  118. /// <param name="video">The <see cref="Video"/> object to search external files for.</param>
  119. /// <param name="directoryService">The directory service to search for files.</param>
  120. /// <param name="clearCache">True if the directory service cache should be cleared before searching.</param>
  121. /// <returns>The external file paths located.</returns>
  122. public IReadOnlyList<ExternalPathParserResult> GetExternalFiles(
  123. Video video,
  124. IDirectoryService directoryService,
  125. bool clearCache)
  126. {
  127. if (!video.IsFileProtocol)
  128. {
  129. return Array.Empty<ExternalPathParserResult>();
  130. }
  131. // Check if video folder exists
  132. string folder = video.ContainingFolderPath;
  133. if (!Directory.Exists(folder))
  134. {
  135. return Array.Empty<ExternalPathParserResult>();
  136. }
  137. var externalPathInfos = new List<ExternalPathParserResult>();
  138. var files = directoryService.GetFilePaths(folder, clearCache).ToList();
  139. files.AddRange(directoryService.GetFilePaths(video.GetInternalMetadataPath(), clearCache));
  140. if (!files.Any())
  141. {
  142. return Array.Empty<ExternalPathParserResult>();
  143. }
  144. foreach (var file in files)
  145. {
  146. var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file);
  147. if (_compareInfo.IsPrefix(fileNameWithoutExtension, video.FileNameWithoutExtension, CompareOptions, out int matchLength)
  148. && (fileNameWithoutExtension.Length == matchLength || _namingOptions.MediaFlagDelimiters.Contains(fileNameWithoutExtension[matchLength].ToString())))
  149. {
  150. var externalPathInfo = _externalPathParser.ParseFile(file, fileNameWithoutExtension[matchLength..]);
  151. if (externalPathInfo != null)
  152. {
  153. externalPathInfos.Add(externalPathInfo);
  154. }
  155. }
  156. }
  157. return externalPathInfos;
  158. }
  159. /// <summary>
  160. /// Returns the media info of the given file.
  161. /// </summary>
  162. /// <param name="path">The path to the file.</param>
  163. /// <param name="type">The <see cref="DlnaProfileType"/>.</param>
  164. /// <param name="cancellationToken">The cancellation token to cancel operation.</param>
  165. /// <returns>The media info for the given file.</returns>
  166. private Task<Model.MediaInfo.MediaInfo> GetMediaInfo(string path, DlnaProfileType type, CancellationToken cancellationToken)
  167. {
  168. cancellationToken.ThrowIfCancellationRequested();
  169. return _mediaEncoder.GetMediaInfo(
  170. new MediaInfoRequest
  171. {
  172. MediaType = type,
  173. MediaSource = new MediaSourceInfo
  174. {
  175. Path = path,
  176. Protocol = MediaProtocol.File
  177. }
  178. },
  179. cancellationToken);
  180. }
  181. /// <summary>
  182. /// Merges path metadata into stream metadata.
  183. /// </summary>
  184. /// <param name="mediaStream">The <see cref="MediaStream"/> object.</param>
  185. /// <param name="pathInfo">The <see cref="ExternalPathParserResult"/> object.</param>
  186. /// <returns>The modified mediaStream.</returns>
  187. private MediaStream MergeMetadata(MediaStream mediaStream, ExternalPathParserResult pathInfo)
  188. {
  189. mediaStream.Path = pathInfo.Path;
  190. mediaStream.IsExternal = true;
  191. mediaStream.Title = string.IsNullOrEmpty(mediaStream.Title) ? (string.IsNullOrEmpty(pathInfo.Title) ? null : pathInfo.Title) : mediaStream.Title;
  192. mediaStream.Language = string.IsNullOrEmpty(mediaStream.Language) ? (string.IsNullOrEmpty(pathInfo.Language) ? null : pathInfo.Language) : mediaStream.Language;
  193. mediaStream.Type = _type switch
  194. {
  195. DlnaProfileType.Audio => MediaStreamType.Audio,
  196. DlnaProfileType.Subtitle => MediaStreamType.Subtitle,
  197. _ => mediaStream.Type
  198. };
  199. return mediaStream;
  200. }
  201. }
  202. }