MediaInfoResolver.cs 9.4 KB

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