diff options
| author | Cody Robibero <cody@robibe.ro> | 2021-12-11 12:05:34 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-12-11 12:05:34 -0700 |
| commit | 8b4a36d6f729d8f573e7b5c7e99950b7742b635e (patch) | |
| tree | 2228ce6aa020792a85e9bb765a51570f02fcb35f /MediaBrowser.Providers | |
| parent | 4d1b8246513b352d65d4db6afcb97ae1e984c760 (diff) | |
| parent | 03b3f08354f496d18444779977bfc1602bbb2c73 (diff) | |
Merge pull request #6898 from jonas-resch/support-external-audio-filesv10.8.0-alpha3
Add support for external audio files
Diffstat (limited to 'MediaBrowser.Providers')
| -rw-r--r-- | MediaBrowser.Providers/MediaInfo/AudioResolver.cs | 176 | ||||
| -rw-r--r-- | MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs | 22 | ||||
| -rw-r--r-- | MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs | 32 |
3 files changed, 224 insertions, 6 deletions
diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs new file mode 100644 index 000000000..425913501 --- /dev/null +++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Emby.Naming.Audio; +using Emby.Naming.Common; +using Jellyfin.Extensions; +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 +{ + /// <summary> + /// Resolves external audios for videos. + /// </summary> + public class AudioResolver + { + private readonly ILocalizationManager _localizationManager; + private readonly IMediaEncoder _mediaEncoder; + private readonly NamingOptions _namingOptions; + + /// <summary> + /// Initializes a new instance of the <see cref="AudioResolver"/> class. + /// </summary> + /// <param name="localizationManager">The localization manager.</param> + /// <param name="mediaEncoder">The media encoder.</param> + /// <param name="namingOptions">The naming options.</param> + public AudioResolver( + ILocalizationManager localizationManager, + IMediaEncoder mediaEncoder, + NamingOptions namingOptions) + { + _localizationManager = localizationManager; + _mediaEncoder = mediaEncoder; + _namingOptions = namingOptions; + } + + /// <summary> + /// Returns the audio streams found in the external audio files for the given video. + /// </summary> + /// <param name="video">The video to get the external audio streams from.</param> + /// <param name="startIndex">The stream index to start adding audio streams at.</param> + /// <param name="directoryService">The directory service to search for files.</param> + /// <param name="clearCache">True if the directory service cache should be cleared before searching.</param> + /// <param name="cancellationToken">The cancellation token to cancel operation.</param> + /// <returns>A list of external audio streams.</returns> + public async IAsyncEnumerable<MediaStream> GetExternalAudioStreams( + Video video, + int startIndex, + IDirectoryService directoryService, + bool clearCache, + [EnumeratorCancellation] CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (!video.IsFileProtocol) + { + yield break; + } + + IEnumerable<string> paths = GetExternalAudioFiles(video, directoryService, clearCache); + foreach (string path in paths) + { + string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path); + Model.MediaInfo.MediaInfo mediaInfo = await GetMediaInfo(path, cancellationToken).ConfigureAwait(false); + + foreach (MediaStream mediaStream in mediaInfo.MediaStreams) + { + mediaStream.Index = startIndex++; + mediaStream.Type = MediaStreamType.Audio; + mediaStream.IsExternal = true; + mediaStream.Path = path; + mediaStream.IsDefault = false; + mediaStream.Title = null; + + if (string.IsNullOrEmpty(mediaStream.Language)) + { + // Try to translate to three character code + // Be flexible and check against both the full and three character versions + var language = StringExtensions.RightPart(fileNameWithoutExtension, '.').ToString(); + + if (language != fileNameWithoutExtension) + { + var culture = _localizationManager.FindLanguageInfo(language); + + language = culture == null ? language : culture.ThreeLetterISOLanguageName; + mediaStream.Language = language; + } + } + + yield return mediaStream; + } + } + } + + /// <summary> + /// Returns the external audio file paths for the given video. + /// </summary> + /// <param name="video">The video to get the external audio file paths from.</param> + /// <param name="directoryService">The directory service to search for files.</param> + /// <param name="clearCache">True if the directory service cache should be cleared before searching.</param> + /// <returns>A list of external audio file paths.</returns> + public IEnumerable<string> GetExternalAudioFiles( + Video video, + IDirectoryService directoryService, + bool clearCache) + { + if (!video.IsFileProtocol) + { + yield break; + } + + // Check if video folder exists + string folder = video.ContainingFolderPath; + if (!Directory.Exists(folder)) + { + yield break; + } + + string videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path); + + var files = directoryService.GetFilePaths(folder, clearCache, true); + for (int i = 0; i < files.Count; i++) + { + string file = files[i]; + if (string.Equals(video.Path, file, StringComparison.OrdinalIgnoreCase) + || !AudioFileParser.IsAudioFile(file, _namingOptions) + || Path.GetExtension(file.AsSpan()).Equals(".strm", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file); + // 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) + || (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length + && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.' + && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))) + { + yield return file; + } + } + } + + /// <summary> + /// Returns the media info of the given audio file. + /// </summary> + /// <param name="path">The path to the audio file.</param> + /// <param name="cancellationToken">The cancellation token to cancel operation.</param> + /// <returns>The media info for the given audio file.</returns> + private Task<Model.MediaInfo.MediaInfo> GetMediaInfo(string path, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + return _mediaEncoder.GetMediaInfo( + new MediaInfoRequest + { + MediaType = DlnaProfileType.Audio, + MediaSource = new MediaSourceInfo + { + Path = path, + Protocol = MediaProtocol.File + } + }, + cancellationToken); + } + } +} diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index 887a7f80c..19a435196 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Emby.Naming.Common; using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -39,6 +40,7 @@ namespace MediaBrowser.Providers.MediaInfo { private readonly ILogger<FFProbeProvider> _logger; private readonly SubtitleResolver _subtitleResolver; + private readonly AudioResolver _audioResolver; private readonly FFProbeVideoInfo _videoProber; private readonly FFProbeAudioInfo _audioProber; @@ -55,10 +57,11 @@ namespace MediaBrowser.Providers.MediaInfo IServerConfigurationManager config, ISubtitleManager subtitleManager, IChapterManager chapterManager, - ILibraryManager libraryManager) + ILibraryManager libraryManager, + NamingOptions namingOptions) { _logger = logger; - + _audioResolver = new AudioResolver(localization, mediaEncoder, namingOptions); _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager); _videoProber = new FFProbeVideoInfo( _logger, @@ -71,7 +74,8 @@ namespace MediaBrowser.Providers.MediaInfo config, subtitleManager, chapterManager, - libraryManager); + libraryManager, + _audioResolver); _audioProber = new FFProbeAudioInfo(mediaSourceManager, mediaEncoder, itemRepo, libraryManager); } @@ -92,7 +96,7 @@ namespace MediaBrowser.Providers.MediaInfo var file = directoryService.GetFile(path); if (file != null && file.LastWriteTimeUtc != item.DateModified) { - _logger.LogDebug("Refreshing {0} due to date modified timestamp change.", path); + _logger.LogDebug("Refreshing {ItemPath} due to date modified timestamp change.", path); return true; } } @@ -102,7 +106,15 @@ namespace MediaBrowser.Providers.MediaInfo && !video.SubtitleFiles.SequenceEqual( _subtitleResolver.GetExternalSubtitleFiles(video, directoryService, false), StringComparer.Ordinal)) { - _logger.LogDebug("Refreshing {0} due to external subtitles change.", item.Path); + _logger.LogDebug("Refreshing {ItemPath} due to external subtitles change.", item.Path); + return true; + } + + if (item.SupportsLocalMetadata && video != null && !video.IsPlaceHolder + && !video.AudioFiles.SequenceEqual( + _audioResolver.GetExternalAudioFiles(video, directoryService, false), StringComparer.Ordinal)) + { + _logger.LogDebug("Refreshing {ItemPath} due to external audio change.", item.Path); return true; } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 4ab15f60e..77372e063 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -44,6 +44,7 @@ namespace MediaBrowser.Providers.MediaInfo private readonly ISubtitleManager _subtitleManager; private readonly IChapterManager _chapterManager; private readonly ILibraryManager _libraryManager; + private readonly AudioResolver _audioResolver; private readonly IMediaSourceManager _mediaSourceManager; private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks; @@ -59,7 +60,8 @@ namespace MediaBrowser.Providers.MediaInfo IServerConfigurationManager config, ISubtitleManager subtitleManager, IChapterManager chapterManager, - ILibraryManager libraryManager) + ILibraryManager libraryManager, + AudioResolver audioResolver) { _logger = logger; _mediaEncoder = mediaEncoder; @@ -71,6 +73,7 @@ namespace MediaBrowser.Providers.MediaInfo _subtitleManager = subtitleManager; _chapterManager = chapterManager; _libraryManager = libraryManager; + _audioResolver = audioResolver; _mediaSourceManager = mediaSourceManager; } @@ -214,6 +217,8 @@ namespace MediaBrowser.Providers.MediaInfo await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); + await AddExternalAudioAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false); + var libraryOptions = _libraryManager.GetLibraryOptions(video); if (mediaInfo != null) @@ -575,6 +580,31 @@ namespace MediaBrowser.Providers.MediaInfo } /// <summary> + /// Adds the external audio. + /// </summary> + /// <param name="video">The video.</param> + /// <param name="currentStreams">The current streams.</param> + /// <param name="options">The refreshOptions.</param> + /// <param name="cancellationToken">The cancellation token.</param> + private async Task AddExternalAudioAsync( + Video video, + List<MediaStream> currentStreams, + MetadataRefreshOptions options, + CancellationToken cancellationToken) + { + var startIndex = currentStreams.Count == 0 ? 0 : currentStreams.Max(i => i.Index) + 1; + var externalAudioStreams = _audioResolver.GetExternalAudioStreams(video, startIndex, options.DirectoryService, false, cancellationToken); + + await foreach (MediaStream externalAudioStream in externalAudioStreams) + { + currentStreams.Add(externalAudioStream); + } + + // Select all external audio file paths + video.AudioFiles = currentStreams.Where(i => i.Type == MediaStreamType.Audio && i.IsExternal).Select(i => i.Path).Distinct().ToArray(); + } + + /// <summary> /// Creates dummy chapters. /// </summary> /// <param name="video">The video.</param> |
