#nullable disable #pragma warning disable CA1002, CS1591 using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Providers.MediaInfo { public class AudioResolver { private readonly ILocalizationManager _localization; private readonly IMediaEncoder _mediaEncoder; private readonly CancellationToken _cancellationToken; public AudioResolver(ILocalizationManager localization, IMediaEncoder mediaEncoder, CancellationToken cancellationToken = default) { _localization = localization; _mediaEncoder = mediaEncoder; _cancellationToken = cancellationToken; } public List GetExternalAudioStreams( Video video, int startIndex, IDirectoryService directoryService, bool clearCache) { var streams = new List(); if (!video.IsFileProtocol) { return streams; } AddExternalAudioStreams(streams, video.ContainingFolderPath, video.Path, startIndex, directoryService, clearCache); startIndex += streams.Count; string folder = video.GetInternalMetadataPath(); if (!Directory.Exists(folder)) { return streams; } try { AddExternalAudioStreams(streams, folder, video.Path, startIndex, directoryService, clearCache); } catch (IOException) { } return streams; } public IEnumerable GetExternalAudioFiles( Video video, IDirectoryService directoryService, bool clearCache) { if (!video.IsFileProtocol) { yield break; } var streams = GetExternalAudioStreams(video, 0, directoryService, clearCache); foreach (var stream in streams) { yield return stream.Path; } } public void AddExternalAudioStreams( List streams, string videoPath, int startIndex, IReadOnlyList files) { var videoFileNameWithoutExtension = NormalizeFilenameForAudioComparison(videoPath); for (var i = 0; i < files.Count; i++) { var fullName = files[i]; var extension = Path.GetExtension(fullName.AsSpan()); if (!IsAudioExtension(extension)) { continue; } Model.MediaInfo.MediaInfo mediaInfo = GetMediaInfo(fullName).Result; MediaStream mediaStream = mediaInfo.MediaStreams.First(); mediaStream.Index = startIndex++; mediaStream.Type = MediaStreamType.Audio; mediaStream.IsExternal = true; mediaStream.Path = fullName; mediaStream.IsDefault = false; mediaStream.Title = null; var fileNameWithoutExtension = NormalizeFilenameForAudioComparison(fullName); // The audio filename must either be equal to the video filename or start with the video filename followed by a dot if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) { mediaStream.Path = fullName; } else if (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.' && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) { // Support xbmc naming conventions - 300.spanish.m4a var languageSpan = fileNameWithoutExtension; while (languageSpan.Length > 0) { var lastDot = languageSpan.LastIndexOf('.'); var currentSlice = languageSpan[lastDot..]; languageSpan = languageSpan[(lastDot + 1)..]; break; } // Try to translate to three character code // Be flexible and check against both the full and three character versions var language = languageSpan.ToString(); var culture = _localization.FindLanguageInfo(language); language = culture == null ? language : culture.ThreeLetterISOLanguageName; mediaStream.Language = language; } else { continue; } mediaStream.Codec = extension.TrimStart('.').ToString().ToLowerInvariant(); streams.Add(mediaStream); } } private static bool IsAudioExtension(ReadOnlySpan extension) { String[] audioExtensions = new[] { ".nsv", ".m4a", ".flac", ".aac", ".strm", ".pls", ".rm", ".mpa", ".wav", ".wma", ".ogg", ".opus", ".mp3", ".mp2", ".mod", ".amf", ".669", ".dmf", ".dsm", ".far", ".gdm", ".imf", ".it", ".m15", ".med", ".okt", ".s3m", ".stm", ".sfx", ".ult", ".uni", ".xm", ".sid", ".ac3", ".dts", ".cue", ".aif", ".aiff", ".ape", ".mac", ".mpc", ".mp+", ".mpp", ".shn", ".wv", ".nsf", ".spc", ".gym", ".adplug", ".adx", ".dsp", ".adp", ".ymf", ".ast", ".afc", ".hps", ".xsp", ".acc", ".m4b", ".oga", ".dsf", ".mka" }; foreach (String audioExtension in audioExtensions) { if (extension.Equals(audioExtension, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private Task GetMediaInfo(string path) { _cancellationToken.ThrowIfCancellationRequested(); return _mediaEncoder.GetMediaInfo( new MediaInfoRequest { MediaType = DlnaProfileType.Audio, MediaSource = new MediaSourceInfo { Path = path, Protocol = MediaProtocol.File } }, _cancellationToken); } private static ReadOnlySpan NormalizeFilenameForAudioComparison(string filename) { // Try to account for sloppy file naming filename = filename.Replace("_", string.Empty, StringComparison.Ordinal); filename = filename.Replace(" ", string.Empty, StringComparison.Ordinal); return Path.GetFileNameWithoutExtension(filename.AsSpan()); } private void AddExternalAudioStreams( List streams, string folder, string videoPath, int startIndex, IDirectoryService directoryService, bool clearCache) { var files = directoryService.GetFilePaths(folder, clearCache, true); AddExternalAudioStreams(streams, videoPath, startIndex, files); } } }