MediaInfoResolver.cs 11 KB

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