ProbeProvider.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. #nullable disable
  2. using System;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. using Emby.Naming.Common;
  9. using MediaBrowser.Controller.Chapters;
  10. using MediaBrowser.Controller.Configuration;
  11. using MediaBrowser.Controller.Entities;
  12. using MediaBrowser.Controller.Entities.Audio;
  13. using MediaBrowser.Controller.Entities.Movies;
  14. using MediaBrowser.Controller.Entities.TV;
  15. using MediaBrowser.Controller.Library;
  16. using MediaBrowser.Controller.Lyrics;
  17. using MediaBrowser.Controller.MediaEncoding;
  18. using MediaBrowser.Controller.Persistence;
  19. using MediaBrowser.Controller.Providers;
  20. using MediaBrowser.Controller.Subtitles;
  21. using MediaBrowser.Model.Entities;
  22. using MediaBrowser.Model.Globalization;
  23. using MediaBrowser.Model.IO;
  24. using MediaBrowser.Model.MediaInfo;
  25. using Microsoft.Extensions.Logging;
  26. namespace MediaBrowser.Providers.MediaInfo
  27. {
  28. /// <summary>
  29. /// The probe provider.
  30. /// </summary>
  31. public class ProbeProvider : ICustomMetadataProvider<Episode>,
  32. ICustomMetadataProvider<MusicVideo>,
  33. ICustomMetadataProvider<Movie>,
  34. ICustomMetadataProvider<Trailer>,
  35. ICustomMetadataProvider<Video>,
  36. ICustomMetadataProvider<Audio>,
  37. ICustomMetadataProvider<AudioBook>,
  38. IHasOrder,
  39. IForcedProvider,
  40. IPreRefreshProvider,
  41. IHasItemChangeMonitor
  42. {
  43. private readonly ILogger<ProbeProvider> _logger;
  44. private readonly AudioResolver _audioResolver;
  45. private readonly SubtitleResolver _subtitleResolver;
  46. private readonly LyricResolver _lyricResolver;
  47. private readonly FFProbeVideoInfo _videoProber;
  48. private readonly AudioFileProber _audioProber;
  49. private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.None);
  50. /// <summary>
  51. /// Initializes a new instance of the <see cref="ProbeProvider"/> class.
  52. /// </summary>
  53. /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
  54. /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
  55. /// <param name="blurayExaminer">Instance of the <see cref="IBlurayExaminer"/> interface.</param>
  56. /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
  57. /// <param name="chapterManager">Instance of the <see cref="IChapterManager"/> interface.</param>
  58. /// <param name="config">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
  59. /// <param name="subtitleManager">Instance of the <see cref="ISubtitleManager"/> interface.</param>
  60. /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
  61. /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/>.</param>
  62. /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
  63. /// <param name="namingOptions">The <see cref="NamingOptions"/>.</param>
  64. /// <param name="lyricManager">Instance of the <see cref="ILyricManager"/> interface.</param>
  65. /// <param name="mediaAttachmentRepository">Instance of the <see cref="IMediaAttachmentRepository"/> interface.</param>
  66. /// <param name="mediaStreamRepository">Instance of the <see cref="IMediaStreamRepository"/> interface.</param>
  67. public ProbeProvider(
  68. IMediaSourceManager mediaSourceManager,
  69. IMediaEncoder mediaEncoder,
  70. IBlurayExaminer blurayExaminer,
  71. ILocalizationManager localization,
  72. IChapterManager chapterManager,
  73. IServerConfigurationManager config,
  74. ISubtitleManager subtitleManager,
  75. ILibraryManager libraryManager,
  76. IFileSystem fileSystem,
  77. ILoggerFactory loggerFactory,
  78. NamingOptions namingOptions,
  79. ILyricManager lyricManager,
  80. IMediaAttachmentRepository mediaAttachmentRepository,
  81. IMediaStreamRepository mediaStreamRepository)
  82. {
  83. _logger = loggerFactory.CreateLogger<ProbeProvider>();
  84. _audioResolver = new AudioResolver(loggerFactory.CreateLogger<AudioResolver>(), localization, mediaEncoder, fileSystem, namingOptions);
  85. _subtitleResolver = new SubtitleResolver(loggerFactory.CreateLogger<SubtitleResolver>(), localization, mediaEncoder, fileSystem, namingOptions);
  86. _lyricResolver = new LyricResolver(loggerFactory.CreateLogger<LyricResolver>(), localization, mediaEncoder, fileSystem, namingOptions);
  87. _videoProber = new FFProbeVideoInfo(
  88. loggerFactory.CreateLogger<FFProbeVideoInfo>(),
  89. mediaSourceManager,
  90. mediaEncoder,
  91. blurayExaminer,
  92. localization,
  93. chapterManager,
  94. config,
  95. subtitleManager,
  96. libraryManager,
  97. _audioResolver,
  98. _subtitleResolver,
  99. mediaAttachmentRepository,
  100. mediaStreamRepository);
  101. _audioProber = new AudioFileProber(
  102. loggerFactory.CreateLogger<AudioFileProber>(),
  103. mediaSourceManager,
  104. mediaEncoder,
  105. libraryManager,
  106. _lyricResolver,
  107. lyricManager,
  108. mediaStreamRepository);
  109. }
  110. /// <inheritdoc />
  111. public string Name => "Probe Provider";
  112. /// <inheritdoc />
  113. public int Order => 100;
  114. /// <inheritdoc />
  115. public bool HasChanged(BaseItem item, IDirectoryService directoryService)
  116. {
  117. var video = item as Video;
  118. if (video is null || video.VideoType == VideoType.VideoFile || video.VideoType == VideoType.Iso)
  119. {
  120. var path = item.Path;
  121. if (!string.IsNullOrWhiteSpace(path) && item.IsFileProtocol)
  122. {
  123. var file = directoryService.GetFile(path);
  124. if (file is not null && file.LastWriteTimeUtc != item.DateModified && file.Length != item.Size)
  125. {
  126. _logger.LogDebug("Refreshing {ItemPath} due to file system modification.", path);
  127. return true;
  128. }
  129. }
  130. }
  131. if (video is not null
  132. && item.SupportsLocalMetadata
  133. && !video.IsPlaceHolder)
  134. {
  135. var externalFiles = new HashSet<string>(_subtitleResolver.GetExternalFiles(video, directoryService, false).Select(info => info.Path), StringComparer.OrdinalIgnoreCase);
  136. if (!new HashSet<string>(video.SubtitleFiles, StringComparer.Ordinal).SetEquals(externalFiles))
  137. {
  138. _logger.LogDebug("Refreshing {ItemPath} due to external subtitles change.", item.Path);
  139. return true;
  140. }
  141. externalFiles = new HashSet<string>(_audioResolver.GetExternalFiles(video, directoryService, false).Select(info => info.Path), StringComparer.OrdinalIgnoreCase);
  142. if (!new HashSet<string>(video.AudioFiles, StringComparer.Ordinal).SetEquals(externalFiles))
  143. {
  144. _logger.LogDebug("Refreshing {ItemPath} due to external audio change.", item.Path);
  145. return true;
  146. }
  147. }
  148. if (item is Audio audio
  149. && item.SupportsLocalMetadata)
  150. {
  151. var externalFiles = new HashSet<string>(_lyricResolver.GetExternalFiles(audio, directoryService, false).Select(info => info.Path), StringComparer.OrdinalIgnoreCase);
  152. if (!new HashSet<string>(audio.LyricFiles, StringComparer.Ordinal).SetEquals(externalFiles))
  153. {
  154. _logger.LogDebug("Refreshing {ItemPath} due to external lyrics change.", item.Path);
  155. return true;
  156. }
  157. }
  158. return false;
  159. }
  160. /// <inheritdoc />
  161. public Task<ItemUpdateType> FetchAsync(Episode item, MetadataRefreshOptions options, CancellationToken cancellationToken)
  162. {
  163. return FetchVideoInfo(item, options, cancellationToken);
  164. }
  165. /// <inheritdoc />
  166. public Task<ItemUpdateType> FetchAsync(MusicVideo item, MetadataRefreshOptions options, CancellationToken cancellationToken)
  167. {
  168. return FetchVideoInfo(item, options, cancellationToken);
  169. }
  170. /// <inheritdoc />
  171. public Task<ItemUpdateType> FetchAsync(Movie item, MetadataRefreshOptions options, CancellationToken cancellationToken)
  172. {
  173. return FetchVideoInfo(item, options, cancellationToken);
  174. }
  175. /// <inheritdoc />
  176. public Task<ItemUpdateType> FetchAsync(Trailer item, MetadataRefreshOptions options, CancellationToken cancellationToken)
  177. {
  178. return FetchVideoInfo(item, options, cancellationToken);
  179. }
  180. /// <inheritdoc />
  181. public Task<ItemUpdateType> FetchAsync(Video item, MetadataRefreshOptions options, CancellationToken cancellationToken)
  182. {
  183. return FetchVideoInfo(item, options, cancellationToken);
  184. }
  185. /// <inheritdoc />
  186. public Task<ItemUpdateType> FetchAsync(Audio item, MetadataRefreshOptions options, CancellationToken cancellationToken)
  187. {
  188. return FetchAudioInfo(item, options, cancellationToken);
  189. }
  190. /// <inheritdoc />
  191. public Task<ItemUpdateType> FetchAsync(AudioBook item, MetadataRefreshOptions options, CancellationToken cancellationToken)
  192. {
  193. return FetchAudioInfo(item, options, cancellationToken);
  194. }
  195. /// <summary>
  196. /// Fetches video information for an item.
  197. /// </summary>
  198. /// <param name="item">The item.</param>
  199. /// <param name="options">The <see cref="MetadataRefreshOptions"/>.</param>
  200. /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
  201. /// <typeparam name="T">The type of item to resolve.</typeparam>
  202. /// <returns>A <see cref="Task"/> fetching the <see cref="ItemUpdateType"/> for an item.</returns>
  203. public Task<ItemUpdateType> FetchVideoInfo<T>(T item, MetadataRefreshOptions options, CancellationToken cancellationToken)
  204. where T : Video
  205. {
  206. if (item.IsPlaceHolder)
  207. {
  208. return _cachedTask;
  209. }
  210. if (!item.IsCompleteMedia)
  211. {
  212. return _cachedTask;
  213. }
  214. if (item.IsVirtualItem)
  215. {
  216. return _cachedTask;
  217. }
  218. if (!options.EnableRemoteContentProbe && !item.IsFileProtocol)
  219. {
  220. return _cachedTask;
  221. }
  222. if (item.IsShortcut)
  223. {
  224. FetchShortcutInfo(item);
  225. }
  226. return _videoProber.ProbeVideo(item, options, cancellationToken);
  227. }
  228. private string NormalizeStrmLine(string line)
  229. {
  230. return line.Replace("\t", string.Empty, StringComparison.Ordinal)
  231. .Replace("\r", string.Empty, StringComparison.Ordinal)
  232. .Replace("\n", string.Empty, StringComparison.Ordinal)
  233. .Trim();
  234. }
  235. private void FetchShortcutInfo(BaseItem item)
  236. {
  237. item.ShortcutPath = File.ReadAllLines(item.Path)
  238. .Select(NormalizeStrmLine)
  239. .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i) && !i.StartsWith('#'));
  240. }
  241. /// <summary>
  242. /// Fetches audio information for an item.
  243. /// </summary>
  244. /// <param name="item">The item.</param>
  245. /// <param name="options">The <see cref="MetadataRefreshOptions"/>.</param>
  246. /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
  247. /// <typeparam name="T">The type of item to resolve.</typeparam>
  248. /// <returns>A <see cref="Task"/> fetching the <see cref="ItemUpdateType"/> for an item.</returns>
  249. public Task<ItemUpdateType> FetchAudioInfo<T>(T item, MetadataRefreshOptions options, CancellationToken cancellationToken)
  250. where T : Audio
  251. {
  252. if (item.IsVirtualItem)
  253. {
  254. return _cachedTask;
  255. }
  256. if (!options.EnableRemoteContentProbe && !item.IsFileProtocol)
  257. {
  258. return _cachedTask;
  259. }
  260. if (item.IsShortcut)
  261. {
  262. FetchShortcutInfo(item);
  263. }
  264. return _audioProber.Probe(item, options, cancellationToken);
  265. }
  266. }
  267. }