AudioResolver.cs 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Runtime.CompilerServices;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7. using Emby.Naming.Audio;
  8. using Emby.Naming.Common;
  9. using Jellyfin.Extensions;
  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 audios for videos.
  22. /// </summary>
  23. public class AudioResolver
  24. {
  25. private readonly ILocalizationManager _localizationManager;
  26. private readonly IMediaEncoder _mediaEncoder;
  27. private readonly NamingOptions _namingOptions;
  28. /// <summary>
  29. /// Initializes a new instance of the <see cref="AudioResolver"/> class.
  30. /// </summary>
  31. /// <param name="localizationManager">The localization manager.</param>
  32. /// <param name="mediaEncoder">The media encoder.</param>
  33. /// <param name="namingOptions">The naming options.</param>
  34. public AudioResolver(
  35. ILocalizationManager localizationManager,
  36. IMediaEncoder mediaEncoder,
  37. NamingOptions namingOptions)
  38. {
  39. _localizationManager = localizationManager;
  40. _mediaEncoder = mediaEncoder;
  41. _namingOptions = namingOptions;
  42. }
  43. /// <summary>
  44. /// Returns the audio streams found in the external audio files for the given video.
  45. /// </summary>
  46. /// <param name="video">The video to get the external audio streams from.</param>
  47. /// <param name="startIndex">The stream index to start adding audio streams at.</param>
  48. /// <param name="directoryService">The directory service to search for files.</param>
  49. /// <param name="clearCache">True if the directory service cache should be cleared before searching.</param>
  50. /// <param name="cancellationToken">The cancellation token to cancel operation.</param>
  51. /// <returns>A list of external audio streams.</returns>
  52. public async IAsyncEnumerable<MediaStream> GetExternalAudioStreams(
  53. Video video,
  54. int startIndex,
  55. IDirectoryService directoryService,
  56. bool clearCache,
  57. [EnumeratorCancellation] CancellationToken cancellationToken)
  58. {
  59. cancellationToken.ThrowIfCancellationRequested();
  60. if (!video.IsFileProtocol)
  61. {
  62. yield break;
  63. }
  64. IEnumerable<string> paths = GetExternalAudioFiles(video, directoryService, clearCache);
  65. foreach (string path in paths)
  66. {
  67. string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path);
  68. Model.MediaInfo.MediaInfo mediaInfo = await GetMediaInfo(path, cancellationToken).ConfigureAwait(false);
  69. foreach (MediaStream mediaStream in mediaInfo.MediaStreams)
  70. {
  71. mediaStream.Index = startIndex++;
  72. mediaStream.Type = MediaStreamType.Audio;
  73. mediaStream.IsExternal = true;
  74. mediaStream.Path = path;
  75. mediaStream.IsDefault = false;
  76. mediaStream.Title = null;
  77. if (string.IsNullOrEmpty(mediaStream.Language))
  78. {
  79. // Try to translate to three character code
  80. // Be flexible and check against both the full and three character versions
  81. var language = StringExtensions.RightPart(fileNameWithoutExtension, '.').ToString();
  82. if (language != fileNameWithoutExtension)
  83. {
  84. var culture = _localizationManager.FindLanguageInfo(language);
  85. language = culture == null ? language : culture.ThreeLetterISOLanguageName;
  86. mediaStream.Language = language;
  87. }
  88. }
  89. yield return mediaStream;
  90. }
  91. }
  92. }
  93. /// <summary>
  94. /// Returns the external audio file paths for the given video.
  95. /// </summary>
  96. /// <param name="video">The video to get the external audio file paths from.</param>
  97. /// <param name="directoryService">The directory service to search for files.</param>
  98. /// <param name="clearCache">True if the directory service cache should be cleared before searching.</param>
  99. /// <returns>A list of external audio file paths.</returns>
  100. public IEnumerable<string> GetExternalAudioFiles(
  101. Video video,
  102. IDirectoryService directoryService,
  103. bool clearCache)
  104. {
  105. if (!video.IsFileProtocol)
  106. {
  107. yield break;
  108. }
  109. // Check if video folder exists
  110. string folder = video.ContainingFolderPath;
  111. if (!Directory.Exists(folder))
  112. {
  113. yield break;
  114. }
  115. string videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path);
  116. var files = directoryService.GetFilePaths(folder, clearCache, true);
  117. for (int i = 0; i < files.Count; i++)
  118. {
  119. string file = files[i];
  120. if (!AudioFileParser.IsAudioFile(file, _namingOptions))
  121. {
  122. continue;
  123. }
  124. string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file);
  125. // The audio filename must either be equal to the video filename or start with the video filename followed by a dot
  126. if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)
  127. || (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length
  128. && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.'
  129. && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)))
  130. {
  131. yield return file;
  132. }
  133. }
  134. }
  135. /// <summary>
  136. /// Returns the media info of the given audio file.
  137. /// </summary>
  138. /// <param name="path">The path to the audio file.</param>
  139. /// <param name="cancellationToken">The cancellation token to cancel operation.</param>
  140. /// <returns>The media info for the given audio file.</returns>
  141. private Task<Model.MediaInfo.MediaInfo> GetMediaInfo(string path, CancellationToken cancellationToken)
  142. {
  143. cancellationToken.ThrowIfCancellationRequested();
  144. return _mediaEncoder.GetMediaInfo(
  145. new MediaInfoRequest
  146. {
  147. MediaType = DlnaProfileType.Audio,
  148. MediaSource = new MediaSourceInfo
  149. {
  150. Path = path,
  151. Protocol = MediaProtocol.File
  152. }
  153. },
  154. cancellationToken);
  155. }
  156. }
  157. }