AudioResolver.cs 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. #nullable disable
  2. #pragma warning disable CA1002, CS1591
  3. using System;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  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.MediaInfo;
  17. namespace MediaBrowser.Providers.MediaInfo
  18. {
  19. public class AudioResolver
  20. {
  21. private readonly ILocalizationManager _localization;
  22. private readonly IMediaEncoder _mediaEncoder;
  23. private readonly CancellationToken _cancellationToken;
  24. public AudioResolver(ILocalizationManager localization, IMediaEncoder mediaEncoder, CancellationToken cancellationToken = default)
  25. {
  26. _localization = localization;
  27. _mediaEncoder = mediaEncoder;
  28. _cancellationToken = cancellationToken;
  29. }
  30. public List<MediaStream> GetExternalAudioStreams(
  31. Video video,
  32. int startIndex,
  33. IDirectoryService directoryService,
  34. bool clearCache)
  35. {
  36. var streams = new List<MediaStream>();
  37. if (!video.IsFileProtocol)
  38. {
  39. return streams;
  40. }
  41. AddExternalAudioStreams(streams, video.ContainingFolderPath, video.Path, startIndex, directoryService, clearCache);
  42. startIndex += streams.Count;
  43. string folder = video.GetInternalMetadataPath();
  44. if (!Directory.Exists(folder))
  45. {
  46. return streams;
  47. }
  48. try
  49. {
  50. AddExternalAudioStreams(streams, folder, video.Path, startIndex, directoryService, clearCache);
  51. }
  52. catch (IOException)
  53. {
  54. }
  55. return streams;
  56. }
  57. public IEnumerable<string> GetExternalAudioFiles(
  58. Video video,
  59. IDirectoryService directoryService,
  60. bool clearCache)
  61. {
  62. if (!video.IsFileProtocol)
  63. {
  64. yield break;
  65. }
  66. var streams = GetExternalAudioStreams(video, 0, directoryService, clearCache);
  67. foreach (var stream in streams)
  68. {
  69. yield return stream.Path;
  70. }
  71. }
  72. public void AddExternalAudioStreams(
  73. List<MediaStream> streams,
  74. string videoPath,
  75. int startIndex,
  76. IReadOnlyList<string> files)
  77. {
  78. var videoFileNameWithoutExtension = NormalizeFilenameForAudioComparison(videoPath);
  79. for (var i = 0; i < files.Count; i++)
  80. {
  81. var fullName = files[i];
  82. var extension = Path.GetExtension(fullName.AsSpan());
  83. if (!IsAudioExtension(extension))
  84. {
  85. continue;
  86. }
  87. Model.MediaInfo.MediaInfo mediaInfo = GetMediaInfo(fullName).Result;
  88. MediaStream mediaStream = mediaInfo.MediaStreams.First();
  89. mediaStream.Index = startIndex++;
  90. mediaStream.Type = MediaStreamType.Audio;
  91. mediaStream.IsExternal = true;
  92. mediaStream.Path = fullName;
  93. mediaStream.IsDefault = false;
  94. mediaStream.Title = null;
  95. var fileNameWithoutExtension = NormalizeFilenameForAudioComparison(fullName);
  96. // The audio filename must either be equal to the video filename or start with the video filename followed by a dot
  97. if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
  98. {
  99. mediaStream.Path = fullName;
  100. }
  101. else if (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length
  102. && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.'
  103. && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
  104. {
  105. // Support xbmc naming conventions - 300.spanish.m4a
  106. var languageSpan = fileNameWithoutExtension;
  107. while (languageSpan.Length > 0)
  108. {
  109. var lastDot = languageSpan.LastIndexOf('.');
  110. var currentSlice = languageSpan[lastDot..];
  111. languageSpan = languageSpan[(lastDot + 1)..];
  112. break;
  113. }
  114. // Try to translate to three character code
  115. // Be flexible and check against both the full and three character versions
  116. var language = languageSpan.ToString();
  117. var culture = _localization.FindLanguageInfo(language);
  118. language = culture == null ? language : culture.ThreeLetterISOLanguageName;
  119. mediaStream.Language = language;
  120. }
  121. else
  122. {
  123. continue;
  124. }
  125. mediaStream.Codec = extension.TrimStart('.').ToString().ToLowerInvariant();
  126. streams.Add(mediaStream);
  127. }
  128. }
  129. private static bool IsAudioExtension(ReadOnlySpan<char> extension)
  130. {
  131. String[] audioExtensions = new[]
  132. {
  133. ".nsv",
  134. ".m4a",
  135. ".flac",
  136. ".aac",
  137. ".strm",
  138. ".pls",
  139. ".rm",
  140. ".mpa",
  141. ".wav",
  142. ".wma",
  143. ".ogg",
  144. ".opus",
  145. ".mp3",
  146. ".mp2",
  147. ".mod",
  148. ".amf",
  149. ".669",
  150. ".dmf",
  151. ".dsm",
  152. ".far",
  153. ".gdm",
  154. ".imf",
  155. ".it",
  156. ".m15",
  157. ".med",
  158. ".okt",
  159. ".s3m",
  160. ".stm",
  161. ".sfx",
  162. ".ult",
  163. ".uni",
  164. ".xm",
  165. ".sid",
  166. ".ac3",
  167. ".dts",
  168. ".cue",
  169. ".aif",
  170. ".aiff",
  171. ".ape",
  172. ".mac",
  173. ".mpc",
  174. ".mp+",
  175. ".mpp",
  176. ".shn",
  177. ".wv",
  178. ".nsf",
  179. ".spc",
  180. ".gym",
  181. ".adplug",
  182. ".adx",
  183. ".dsp",
  184. ".adp",
  185. ".ymf",
  186. ".ast",
  187. ".afc",
  188. ".hps",
  189. ".xsp",
  190. ".acc",
  191. ".m4b",
  192. ".oga",
  193. ".dsf",
  194. ".mka"
  195. };
  196. foreach (String audioExtension in audioExtensions)
  197. {
  198. if (extension.Equals(audioExtension, StringComparison.OrdinalIgnoreCase))
  199. {
  200. return true;
  201. }
  202. }
  203. return false;
  204. }
  205. private Task<Model.MediaInfo.MediaInfo> GetMediaInfo(string path)
  206. {
  207. _cancellationToken.ThrowIfCancellationRequested();
  208. return _mediaEncoder.GetMediaInfo(
  209. new MediaInfoRequest
  210. {
  211. MediaType = DlnaProfileType.Audio,
  212. MediaSource = new MediaSourceInfo
  213. {
  214. Path = path,
  215. Protocol = MediaProtocol.File
  216. }
  217. },
  218. _cancellationToken);
  219. }
  220. private static ReadOnlySpan<char> NormalizeFilenameForAudioComparison(string filename)
  221. {
  222. // Try to account for sloppy file naming
  223. filename = filename.Replace("_", string.Empty, StringComparison.Ordinal);
  224. filename = filename.Replace(" ", string.Empty, StringComparison.Ordinal);
  225. return Path.GetFileNameWithoutExtension(filename.AsSpan());
  226. }
  227. private void AddExternalAudioStreams(
  228. List<MediaStream> streams,
  229. string folder,
  230. string videoPath,
  231. int startIndex,
  232. IDirectoryService directoryService,
  233. bool clearCache)
  234. {
  235. var files = directoryService.GetFilePaths(folder, clearCache, true);
  236. AddExternalAudioStreams(streams, videoPath, startIndex, files);
  237. }
  238. }
  239. }