Selaa lähdekoodia

Implement code feedback

- Rewrite AudioResolver
- Use async & await instead of .Result
- Add support for audio containers with multiple audio streams (e.g.
  mka)
- Fix bug when using external subtitle and external audio streams at the
  same time
Jonas Resch 3 vuotta sitten
vanhempi
sitoutus
a68e58556c

+ 14 - 7
MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs

@@ -678,12 +678,6 @@ namespace MediaBrowser.Controller.MediaEncoding
             arg.Append("-i ")
                 .Append(GetInputPathArgument(state));
 
-            if (state.AudioStream.IsExternal)
-            {
-                arg.Append(" -i ")
-                    .Append(string.Format(CultureInfo.InvariantCulture, "file:\"{0}\"", state.AudioStream.Path));
-            }
-
             if (state.SubtitleStream != null
                 && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
                 && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
@@ -702,6 +696,12 @@ namespace MediaBrowser.Controller.MediaEncoding
                 arg.Append(" -i \"").Append(subtitlePath).Append('\"');
             }
 
+            if (state.AudioStream != null && state.AudioStream.IsExternal)
+            {
+                arg.Append(" -i ")
+                    .Append(string.Format(CultureInfo.InvariantCulture, "file:\"{0}\"", state.AudioStream.Path));
+            }
+
             return arg.ToString();
         }
 
@@ -2007,7 +2007,14 @@ namespace MediaBrowser.Controller.MediaEncoding
             {
                 if (state.AudioStream.IsExternal)
                 {
-                    args += " -map 1:a";
+                    int externalAudioMapIndex = state.SubtitleStream != null && state.SubtitleStream.IsExternal ? 2 : 1;
+                    int externalAudioStream = state.MediaSource.MediaStreams.Where(i => i.Path == state.AudioStream.Path).ToList().IndexOf(state.AudioStream);
+
+                    args += string.Format(
+                        CultureInfo.InvariantCulture,
+                        " -map {0}:{1}",
+                        externalAudioMapIndex,
+                        externalAudioStream);
                 }
                 else
                 {

+ 80 - 191
MediaBrowser.Providers/MediaInfo/AudioResolver.cs

@@ -1,13 +1,13 @@
-#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 Emby.Naming.Audio;
+using Emby.Naming.Common;
+using Jellyfin.Extensions;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.Providers;
@@ -21,24 +21,15 @@ 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<MediaStream> GetExternalAudioStreams(
+        public async Task<List<MediaStream>> GetExternalAudioStreams(
             Video video,
             int startIndex,
             IDirectoryService directoryService,
-            bool clearCache)
+            NamingOptions namingOptions,
+            bool clearCache,
+            ILocalizationManager localizationManager,
+            IMediaEncoder mediaEncoder,
+            CancellationToken cancellationToken)
         {
             var streams = new List<MediaStream>();
 
@@ -47,198 +38,117 @@ namespace MediaBrowser.Providers.MediaInfo
                 return streams;
             }
 
-            AddExternalAudioStreams(streams, video.ContainingFolderPath, video.Path, startIndex, directoryService, clearCache);
-
-            startIndex += streams.Count;
+            List<string> paths = GetExternalAudioFiles(video, directoryService, namingOptions, clearCache);
 
-            string folder = video.GetInternalMetadataPath();
-
-            if (!Directory.Exists(folder))
-            {
-                return streams;
-            }
-
-            try
-            {
-                AddExternalAudioStreams(streams, folder, video.Path, startIndex, directoryService, clearCache);
-            }
-            catch (IOException)
-            {
-            }
+            await AddExternalAudioStreams(streams, paths, startIndex, localizationManager, mediaEncoder, cancellationToken);
 
             return streams;
         }
 
-        public IEnumerable<string> GetExternalAudioFiles(
+        public List<string> GetExternalAudioFiles(
             Video video,
             IDirectoryService directoryService,
+            NamingOptions namingOptions,
             bool clearCache)
         {
+            List<string> paths = new List<string>();
+
             if (!video.IsFileProtocol)
             {
-                yield break;
+                return paths;
             }
 
-            var streams = GetExternalAudioStreams(video, 0, directoryService, clearCache);
+            paths.AddRange(GetAudioFilesFromFolder(video.ContainingFolderPath, video.Path, directoryService, namingOptions, clearCache));
+            paths.AddRange(GetAudioFilesFromFolder(video.GetInternalMetadataPath(), video.Path, directoryService, namingOptions, clearCache));
 
-            foreach (var stream in streams)
-            {
-                yield return stream.Path;
-            }
+            return paths;
         }
 
-        public void AddExternalAudioStreams(
-            List<MediaStream> streams,
-            string videoPath,
-            int startIndex,
-            IReadOnlyList<string> files)
+        private List<string> GetAudioFilesFromFolder(
+            string folder,
+            string videoFileName,
+            IDirectoryService directoryService,
+            NamingOptions namingOptions,
+            bool clearCache)
         {
-            var videoFileNameWithoutExtension = NormalizeFilenameForAudioComparison(videoPath);
+            List<string> paths = new List<string>();
+            string videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(videoFileName);
 
-            for (var i = 0; i < files.Count; i++)
+            if (!Directory.Exists(folder))
+            {
+                return paths;
+            }
+
+            var files = directoryService.GetFilePaths(folder, clearCache, true);
+            for (int i = 0; i < files.Count; i++)
             {
+                string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(files[i]);
 
-                var fullName = files[i];
-                var extension = Path.GetExtension(fullName.AsSpan());
-                if (!IsAudioExtension(extension))
+                if (!AudioFileParser.IsAudioFile(files[i], namingOptions))
                 {
                     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
+                if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase) ||
+                    (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length
                          && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.'
-                         && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
+                         && 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;
+                    paths.Add(files[i]);
                 }
-
-                mediaStream.Codec = extension.TrimStart('.').ToString().ToLowerInvariant();
-
-                streams.Add(mediaStream);
             }
+
+            return paths;
         }
 
-        private static bool IsAudioExtension(ReadOnlySpan<char> extension)
+        public async Task AddExternalAudioStreams(
+            List<MediaStream> streams,
+            List<string> paths,
+            int startIndex,
+            ILocalizationManager localizationManager,
+            IMediaEncoder mediaEncoder,
+            CancellationToken cancellationToken)
         {
-            String[] audioExtensions = new[]
+            foreach (string path in paths)
             {
-                ".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"
-            };
+                string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path);
+                Model.MediaInfo.MediaInfo mediaInfo = await GetMediaInfo(path, mediaEncoder, cancellationToken);
 
-            foreach (String audioExtension in audioExtensions)
-            {
-                if (extension.Equals(audioExtension, StringComparison.OrdinalIgnoreCase))
+                foreach (MediaStream mediaStream in mediaInfo.MediaStreams)
                 {
-                    return true;
+                    mediaStream.Index = startIndex++;
+                    mediaStream.Type = MediaStreamType.Audio;
+                    mediaStream.IsExternal = true;
+                    mediaStream.Path = path;
+                    mediaStream.IsDefault = false;
+                    mediaStream.Title = null;
+
+                    if (mediaStream.Language == null)
+                    {
+                        // 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;
+                        }
+                    }
+
+                    streams.Add(mediaStream);
                 }
             }
-
-            return false;
         }
 
-        private Task<Model.MediaInfo.MediaInfo> GetMediaInfo(string path)
+        private Task<Model.MediaInfo.MediaInfo> GetMediaInfo(string path, IMediaEncoder mediaEncoder, CancellationToken cancellationToken)
         {
-            _cancellationToken.ThrowIfCancellationRequested();
+            cancellationToken.ThrowIfCancellationRequested();
 
-            return _mediaEncoder.GetMediaInfo(
+            return mediaEncoder.GetMediaInfo(
                 new MediaInfoRequest
                 {
                     MediaType = DlnaProfileType.Audio,
@@ -248,28 +158,7 @@ namespace MediaBrowser.Providers.MediaInfo
                         Protocol = MediaProtocol.File
                     }
                 },
-                _cancellationToken);
-        }
-
-        private static ReadOnlySpan<char> 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<MediaStream> streams,
-            string folder,
-            string videoPath,
-            int startIndex,
-            IDirectoryService directoryService,
-            bool clearCache)
-        {
-            var files = directoryService.GetFilePaths(folder, clearCache, true);
-
-            AddExternalAudioStreams(streams, videoPath, startIndex, files);
+                cancellationToken);
         }
     }
 }

+ 10 - 6
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;
@@ -50,10 +51,10 @@ namespace MediaBrowser.Providers.MediaInfo
         private readonly IMediaSourceManager _mediaSourceManager;
         private readonly SubtitleResolver _subtitleResolver;
 
-        private readonly AudioResolver _audioResolver;
-
         private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.None);
 
+        private readonly NamingOptions _namingOptions;
+
         public FFProbeProvider(
             ILogger<FFProbeProvider> logger,
             IMediaSourceManager mediaSourceManager,
@@ -65,7 +66,8 @@ namespace MediaBrowser.Providers.MediaInfo
             IServerConfigurationManager config,
             ISubtitleManager subtitleManager,
             IChapterManager chapterManager,
-            ILibraryManager libraryManager)
+            ILibraryManager libraryManager,
+            NamingOptions namingOptions)
         {
             _logger = logger;
             _mediaEncoder = mediaEncoder;
@@ -78,9 +80,9 @@ namespace MediaBrowser.Providers.MediaInfo
             _chapterManager = chapterManager;
             _libraryManager = libraryManager;
             _mediaSourceManager = mediaSourceManager;
+            _namingOptions = namingOptions;
 
             _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager);
-            _audioResolver = new AudioResolver(BaseItem.LocalizationManager, mediaEncoder);
         }
 
         public string Name => "ffprobe";
@@ -114,9 +116,10 @@ namespace MediaBrowser.Providers.MediaInfo
                 return true;
             }
 
+            AudioResolver audioResolver = new AudioResolver();
             if (item.SupportsLocalMetadata && video != null && !video.IsPlaceHolder
                 && !video.AudioFiles.SequenceEqual(
-                        _audioResolver.GetExternalAudioFiles(video, directoryService, false), StringComparer.Ordinal))
+                        audioResolver.GetExternalAudioFiles(video, directoryService, _namingOptions, false), StringComparer.Ordinal))
             {
                 _logger.LogDebug("Refreshing {0} due to external audio change.", item.Path);
                 return true;
@@ -199,7 +202,8 @@ namespace MediaBrowser.Providers.MediaInfo
                 _config,
                 _subtitleManager,
                 _chapterManager,
-                _libraryManager);
+                _libraryManager,
+                _namingOptions);
 
             return prober.ProbeVideo(item, options, cancellationToken);
         }

+ 10 - 6
MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs

@@ -10,6 +10,7 @@ using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 using DvdLib.Ifo;
+using Emby.Naming.Common;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Controller.Chapters;
 using MediaBrowser.Controller.Configuration;
@@ -44,6 +45,7 @@ namespace MediaBrowser.Providers.MediaInfo
         private readonly ISubtitleManager _subtitleManager;
         private readonly IChapterManager _chapterManager;
         private readonly ILibraryManager _libraryManager;
+        private readonly NamingOptions _namingOptions;
         private readonly IMediaSourceManager _mediaSourceManager;
 
         private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks;
@@ -59,7 +61,8 @@ namespace MediaBrowser.Providers.MediaInfo
             IServerConfigurationManager config,
             ISubtitleManager subtitleManager,
             IChapterManager chapterManager,
-            ILibraryManager libraryManager)
+            ILibraryManager libraryManager,
+            NamingOptions namingOptions)
         {
             _logger = logger;
             _mediaEncoder = mediaEncoder;
@@ -71,6 +74,7 @@ namespace MediaBrowser.Providers.MediaInfo
             _subtitleManager = subtitleManager;
             _chapterManager = chapterManager;
             _libraryManager = libraryManager;
+            _namingOptions = namingOptions;
             _mediaSourceManager = mediaSourceManager;
         }
 
@@ -214,7 +218,7 @@ namespace MediaBrowser.Providers.MediaInfo
 
             await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
 
-            AddExternalAudio(video, mediaStreams, options, cancellationToken);
+            await AddExternalAudio(video, mediaStreams, options, cancellationToken);
 
             var libraryOptions = _libraryManager.GetLibraryOptions(video);
 
@@ -583,18 +587,18 @@ namespace MediaBrowser.Providers.MediaInfo
         /// <param name="currentStreams">The current streams.</param>
         /// <param name="options">The refreshOptions.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        private void AddExternalAudio(
+        private async Task AddExternalAudio(
             Video video,
             List<MediaStream> currentStreams,
             MetadataRefreshOptions options,
             CancellationToken cancellationToken)
         {
-            var audioResolver = new AudioResolver(_localization, _mediaEncoder, cancellationToken);
+            var audioResolver = new AudioResolver();
 
             var startIndex = currentStreams.Count == 0 ? 0 : currentStreams.Max(i => i.Index) + 1;
-            var externalAudioStreams = audioResolver.GetExternalAudioStreams(video, startIndex, options.DirectoryService, false);
+            var externalAudioStreams = await audioResolver.GetExternalAudioStreams(video, startIndex, options.DirectoryService, _namingOptions, false, _localization, _mediaEncoder, cancellationToken);
 
-            video.AudioFiles = externalAudioStreams.Select(i => i.Path).ToArray();
+            video.AudioFiles = externalAudioStreams.Select(i => i.Path).Distinct().ToArray();
 
             currentStreams.AddRange(externalAudioStreams);
         }