aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Providers/MediaInfo
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Providers/MediaInfo')
-rw-r--r--MediaBrowser.Providers/MediaInfo/AudioResolver.cs158
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs17
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs24
-rw-r--r--MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs231
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs235
5 files changed, 272 insertions, 393 deletions
diff --git a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs
index 425913501a..ff90eeffbc 100644
--- a/MediaBrowser.Providers/MediaInfo/AudioResolver.cs
+++ b/MediaBrowser.Providers/MediaInfo/AudioResolver.cs
@@ -1,176 +1,28 @@
-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.
+ /// Resolves external audio files for <see cref="Video"/>.
/// </summary>
- public class AudioResolver
+ public class AudioResolver : MediaInfoResolver
{
- private readonly ILocalizationManager _localizationManager;
- private readonly IMediaEncoder _mediaEncoder;
- private readonly NamingOptions _namingOptions;
-
/// <summary>
- /// Initializes a new instance of the <see cref="AudioResolver"/> class.
+ /// Initializes a new instance of the <see cref="AudioResolver"/> class for external audio file processing.
/// </summary>
/// <param name="localizationManager">The localization manager.</param>
/// <param name="mediaEncoder">The media encoder.</param>
- /// <param name="namingOptions">The naming options.</param>
+ /// <param name="namingOptions">The <see cref="NamingOptions"/> object containing FileExtensions, MediaDefaultFlags, MediaForcedFlags and MediaFlagDelimiters.</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)
+ : base(localizationManager, mediaEncoder, namingOptions, DlnaProfileType.Audio)
{
- 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 19a435196a..560e20dae7 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
@@ -19,6 +19,7 @@ using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Subtitles;
+using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.MediaInfo;
@@ -39,11 +40,10 @@ namespace MediaBrowser.Providers.MediaInfo
IHasItemChangeMonitor
{
private readonly ILogger<FFProbeProvider> _logger;
- private readonly SubtitleResolver _subtitleResolver;
private readonly AudioResolver _audioResolver;
+ private readonly SubtitleResolver _subtitleResolver;
private readonly FFProbeVideoInfo _videoProber;
private readonly FFProbeAudioInfo _audioProber;
-
private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.None);
public FFProbeProvider(
@@ -62,7 +62,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
_logger = logger;
_audioResolver = new AudioResolver(localization, mediaEncoder, namingOptions);
- _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager);
+ _subtitleResolver = new SubtitleResolver(localization, mediaEncoder, namingOptions);
_videoProber = new FFProbeVideoInfo(
_logger,
mediaSourceManager,
@@ -75,7 +75,8 @@ namespace MediaBrowser.Providers.MediaInfo
subtitleManager,
chapterManager,
libraryManager,
- _audioResolver);
+ _audioResolver,
+ _subtitleResolver);
_audioProber = new FFProbeAudioInfo(mediaSourceManager, mediaEncoder, itemRepo, libraryManager);
}
@@ -104,7 +105,9 @@ namespace MediaBrowser.Providers.MediaInfo
if (item.SupportsLocalMetadata && video != null && !video.IsPlaceHolder
&& !video.SubtitleFiles.SequenceEqual(
- _subtitleResolver.GetExternalSubtitleFiles(video, directoryService, false), StringComparer.Ordinal))
+ _subtitleResolver.GetExternalFiles(video, directoryService, false)
+ .Select(info => info.Path).ToList(),
+ StringComparer.Ordinal))
{
_logger.LogDebug("Refreshing {ItemPath} due to external subtitles change.", item.Path);
return true;
@@ -112,7 +115,9 @@ namespace MediaBrowser.Providers.MediaInfo
if (item.SupportsLocalMetadata && video != null && !video.IsPlaceHolder
&& !video.AudioFiles.SequenceEqual(
- _audioResolver.GetExternalAudioFiles(video, directoryService, false), StringComparer.Ordinal))
+ _audioResolver.GetExternalFiles(video, directoryService, false)
+ .Select(info => info.Path).ToList(),
+ 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 77a849d00b..26ff0412b2 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
@@ -45,6 +45,7 @@ namespace MediaBrowser.Providers.MediaInfo
private readonly IChapterManager _chapterManager;
private readonly ILibraryManager _libraryManager;
private readonly AudioResolver _audioResolver;
+ private readonly SubtitleResolver _subtitleResolver;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks;
@@ -61,9 +62,11 @@ namespace MediaBrowser.Providers.MediaInfo
ISubtitleManager subtitleManager,
IChapterManager chapterManager,
ILibraryManager libraryManager,
- AudioResolver audioResolver)
+ AudioResolver audioResolver,
+ SubtitleResolver subtitleResolver)
{
_logger = logger;
+ _mediaSourceManager = mediaSourceManager;
_mediaEncoder = mediaEncoder;
_itemRepo = itemRepo;
_blurayExaminer = blurayExaminer;
@@ -74,7 +77,7 @@ namespace MediaBrowser.Providers.MediaInfo
_chapterManager = chapterManager;
_libraryManager = libraryManager;
_audioResolver = audioResolver;
- _mediaSourceManager = mediaSourceManager;
+ _subtitleResolver = subtitleResolver;
}
public async Task<ItemUpdateType> ProbeVideo<T>(
@@ -215,7 +218,7 @@ namespace MediaBrowser.Providers.MediaInfo
chapters = Array.Empty<ChapterInfo>();
}
- await AddExternalSubtitles(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
+ await AddExternalSubtitlesAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
await AddExternalAudioAsync(video, mediaStreams, options, cancellationToken).ConfigureAwait(false);
@@ -526,16 +529,14 @@ namespace MediaBrowser.Providers.MediaInfo
/// <param name="options">The refreshOptions.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- private async Task AddExternalSubtitles(
+ private async Task AddExternalSubtitlesAsync(
Video video,
List<MediaStream> currentStreams,
MetadataRefreshOptions options,
CancellationToken cancellationToken)
{
- var subtitleResolver = new SubtitleResolver(_localization);
-
var startIndex = currentStreams.Count == 0 ? 0 : (currentStreams.Select(i => i.Index).Max() + 1);
- var externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, startIndex, options.DirectoryService, false);
+ var externalSubtitleStreams = await _subtitleResolver.GetExternalStreamsAsync(video, startIndex, options.DirectoryService, false, cancellationToken);
var enableSubtitleDownloading = options.MetadataRefreshMode == MetadataRefreshMode.Default ||
options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh;
@@ -589,7 +590,7 @@ namespace MediaBrowser.Providers.MediaInfo
// Rescan
if (downloadedLanguages.Count > 0)
{
- externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, startIndex, options.DirectoryService, true);
+ externalSubtitleStreams = await _subtitleResolver.GetExternalStreamsAsync(video, startIndex, options.DirectoryService, true, cancellationToken);
}
}
@@ -612,12 +613,9 @@ namespace MediaBrowser.Providers.MediaInfo
CancellationToken cancellationToken)
{
var startIndex = currentStreams.Count == 0 ? 0 : currentStreams.Max(i => i.Index) + 1;
- var externalAudioStreams = _audioResolver.GetExternalAudioStreams(video, startIndex, options.DirectoryService, false, cancellationToken);
+ var externalAudioStreams = await _audioResolver.GetExternalStreamsAsync(video, startIndex, options.DirectoryService, false, cancellationToken).ConfigureAwait(false);
- await foreach (MediaStream externalAudioStream in externalAudioStreams)
- {
- currentStreams.Add(externalAudioStream);
- }
+ currentStreams = currentStreams.Concat(externalAudioStreams).ToList();
// Select all external audio file paths
video.AudioFiles = currentStreams.Where(i => i.Type == MediaStreamType.Audio && i.IsExternal).Select(i => i.Path).Distinct().ToArray();
diff --git a/MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs b/MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs
new file mode 100644
index 0000000000..40b45faf52
--- /dev/null
+++ b/MediaBrowser.Providers/MediaInfo/MediaInfoResolver.cs
@@ -0,0 +1,231 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Emby.Naming.Common;
+using Emby.Naming.ExternalFiles;
+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 files for <see cref="Video"/>.
+ /// </summary>
+ public abstract class MediaInfoResolver
+ {
+ /// <summary>
+ /// The <see cref="CompareOptions"/> instance.
+ /// </summary>
+ private const CompareOptions CompareOptions = System.Globalization.CompareOptions.IgnoreCase | System.Globalization.CompareOptions.IgnoreNonSpace | System.Globalization.CompareOptions.IgnoreSymbols;
+
+ /// <summary>
+ /// The <see cref="CompareInfo"/> instance.
+ /// </summary>
+ private readonly CompareInfo _compareInfo = CultureInfo.InvariantCulture.CompareInfo;
+
+ /// <summary>
+ /// The <see cref="ExternalPathParser"/> instance.
+ /// </summary>
+ private readonly ExternalPathParser _externalPathParser;
+
+ /// <summary>
+ /// The <see cref="IMediaEncoder"/> instance.
+ /// </summary>
+ private readonly IMediaEncoder _mediaEncoder;
+
+ /// <summary>
+ /// The <see cref="NamingOptions"/> instance.
+ /// </summary>
+ private readonly NamingOptions _namingOptions;
+
+ /// <summary>
+ /// The <see cref="DlnaProfileType"/> of the files this resolver should resolve.
+ /// </summary>
+ private readonly DlnaProfileType _type;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MediaInfoResolver"/> class.
+ /// </summary>
+ /// <param name="localizationManager">The localization manager.</param>
+ /// <param name="mediaEncoder">The media encoder.</param>
+ /// <param name="namingOptions">The <see cref="NamingOptions"/> object containing FileExtensions, MediaDefaultFlags, MediaForcedFlags and MediaFlagDelimiters.</param>
+ /// <param name="type">The <see cref="DlnaProfileType"/> of the parsed file.</param>
+ protected MediaInfoResolver(
+ ILocalizationManager localizationManager,
+ IMediaEncoder mediaEncoder,
+ NamingOptions namingOptions,
+ DlnaProfileType type)
+ {
+ _mediaEncoder = mediaEncoder;
+ _namingOptions = namingOptions;
+ _type = type;
+ _externalPathParser = new ExternalPathParser(namingOptions, localizationManager, _type);
+ }
+
+ /// <summary>
+ /// Retrieves the external streams for the provided video.
+ /// </summary>
+ /// <param name="video">The <see cref="Video"/> object to search external streams for.</param>
+ /// <param name="startIndex">The stream index to start adding external 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.</param>
+ /// <returns>The external streams located.</returns>
+ public async Task<IReadOnlyList<MediaStream>> GetExternalStreamsAsync(
+ Video video,
+ int startIndex,
+ IDirectoryService directoryService,
+ bool clearCache,
+ CancellationToken cancellationToken)
+ {
+ if (!video.IsFileProtocol)
+ {
+ return Array.Empty<MediaStream>();
+ }
+
+ var pathInfos = GetExternalFiles(video, directoryService, clearCache);
+
+ if (!pathInfos.Any())
+ {
+ return Array.Empty<MediaStream>();
+ }
+
+ var mediaStreams = new List<MediaStream>();
+
+ foreach (var pathInfo in pathInfos)
+ {
+ var mediaInfo = await GetMediaInfo(pathInfo.Path, _type, cancellationToken).ConfigureAwait(false);
+
+ if (mediaInfo.MediaStreams.Count == 1)
+ {
+ MediaStream mediaStream = mediaInfo.MediaStreams[0];
+ mediaStream.Index = startIndex++;
+ mediaStream.IsDefault = pathInfo.IsDefault || mediaStream.IsDefault;
+ mediaStream.IsForced = pathInfo.IsForced || mediaStream.IsForced;
+
+ mediaStreams.Add(MergeMetadata(mediaStream, pathInfo));
+ }
+ else
+ {
+ foreach (MediaStream mediaStream in mediaInfo.MediaStreams)
+ {
+ mediaStream.Index = startIndex++;
+
+ mediaStreams.Add(MergeMetadata(mediaStream, pathInfo));
+ }
+ }
+ }
+
+ return mediaStreams.AsReadOnly();
+ }
+
+ /// <summary>
+ /// Returns the external file infos for the given video.
+ /// </summary>
+ /// <param name="video">The <see cref="Video"/> object to search external files for.</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>The external file paths located.</returns>
+ public IReadOnlyList<ExternalPathParserResult> GetExternalFiles(
+ Video video,
+ IDirectoryService directoryService,
+ bool clearCache)
+ {
+ if (!video.IsFileProtocol)
+ {
+ return Array.Empty<ExternalPathParserResult>();
+ }
+
+ // Check if video folder exists
+ string folder = video.ContainingFolderPath;
+ if (!Directory.Exists(folder))
+ {
+ return Array.Empty<ExternalPathParserResult>();
+ }
+
+ var externalPathInfos = new List<ExternalPathParserResult>();
+
+ var files = directoryService.GetFilePaths(folder, clearCache).ToList();
+ files.AddRange(directoryService.GetFilePaths(video.GetInternalMetadataPath(), clearCache));
+
+ if (!files.Any())
+ {
+ return Array.Empty<ExternalPathParserResult>();
+ }
+
+ foreach (var file in files)
+ {
+ var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file);
+ if (_compareInfo.IsPrefix(fileNameWithoutExtension, video.FileNameWithoutExtension, CompareOptions, out int matchLength)
+ && (fileNameWithoutExtension.Length == matchLength || _namingOptions.MediaFlagDelimiters.Contains(fileNameWithoutExtension[matchLength].ToString())))
+ {
+ var externalPathInfo = _externalPathParser.ParseFile(file, fileNameWithoutExtension[matchLength..]);
+
+ if (externalPathInfo != null)
+ {
+ externalPathInfos.Add(externalPathInfo);
+ }
+ }
+ }
+
+ return externalPathInfos;
+ }
+
+ /// <summary>
+ /// Returns the media info of the given file.
+ /// </summary>
+ /// <param name="path">The path to the file.</param>
+ /// <param name="type">The <see cref="DlnaProfileType"/>.</param>
+ /// <param name="cancellationToken">The cancellation token to cancel operation.</param>
+ /// <returns>The media info for the given file.</returns>
+ private Task<Model.MediaInfo.MediaInfo> GetMediaInfo(string path, DlnaProfileType type, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ return _mediaEncoder.GetMediaInfo(
+ new MediaInfoRequest
+ {
+ MediaType = type,
+ MediaSource = new MediaSourceInfo
+ {
+ Path = path,
+ Protocol = MediaProtocol.File
+ }
+ },
+ cancellationToken);
+ }
+
+ /// <summary>
+ /// Merges path metadata into stream metadata.
+ /// </summary>
+ /// <param name="mediaStream">The <see cref="MediaStream"/> object.</param>
+ /// <param name="pathInfo">The <see cref="ExternalPathParserResult"/> object.</param>
+ /// <returns>The modified mediaStream.</returns>
+ private MediaStream MergeMetadata(MediaStream mediaStream, ExternalPathParserResult pathInfo)
+ {
+ mediaStream.Path = pathInfo.Path;
+ mediaStream.IsExternal = true;
+ mediaStream.Title = string.IsNullOrEmpty(mediaStream.Title) ? (string.IsNullOrEmpty(pathInfo.Title) ? null : pathInfo.Title) : mediaStream.Title;
+ mediaStream.Language = string.IsNullOrEmpty(mediaStream.Language) ? (string.IsNullOrEmpty(pathInfo.Language) ? null : pathInfo.Language) : mediaStream.Language;
+
+ mediaStream.Type = _type switch
+ {
+ DlnaProfileType.Audio => MediaStreamType.Audio,
+ DlnaProfileType.Subtitle => MediaStreamType.Subtitle,
+ _ => mediaStream.Type
+ };
+
+ return mediaStream;
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
index ba284187ed..289036fdab 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
@@ -1,235 +1,28 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
+using Emby.Naming.Common;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Globalization;
namespace MediaBrowser.Providers.MediaInfo
{
/// <summary>
- /// Resolves external subtitles for videos.
+ /// Resolves external subtitle files for <see cref="Video"/>.
/// </summary>
- public class SubtitleResolver
+ public class SubtitleResolver : MediaInfoResolver
{
- private readonly ILocalizationManager _localization;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="SubtitleResolver"/> class.
- /// </summary>
- /// <param name="localization">The localization manager.</param>
- public SubtitleResolver(ILocalizationManager localization)
- {
- _localization = localization;
- }
-
- /// <summary>
- /// Retrieves the external subtitle streams for the provided video.
- /// </summary>
- /// <param name="video">The video to search from.</param>
- /// <param name="startIndex">The stream index to start adding subtitle 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>
- /// <returns>The external subtitle streams located.</returns>
- public List<MediaStream> GetExternalSubtitleStreams(
- Video video,
- int startIndex,
- IDirectoryService directoryService,
- bool clearCache)
- {
- var streams = new List<MediaStream>();
-
- if (!video.IsFileProtocol)
- {
- return streams;
- }
-
- AddExternalSubtitleStreams(streams, video.ContainingFolderPath, video.Path, startIndex, directoryService, clearCache);
-
- startIndex += streams.Count;
-
- string folder = video.GetInternalMetadataPath();
-
- if (!Directory.Exists(folder))
- {
- return streams;
- }
-
- try
- {
- AddExternalSubtitleStreams(streams, folder, video.Path, startIndex, directoryService, clearCache);
- }
- catch (IOException)
- {
- }
-
- return streams;
- }
-
- /// <summary>
- /// Locates the external subtitle files for the provided video.
- /// </summary>
- /// <param name="video">The video to search 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>The external subtitle file paths located.</returns>
- public IEnumerable<string> GetExternalSubtitleFiles(
- Video video,
- IDirectoryService directoryService,
- bool clearCache)
- {
- if (!video.IsFileProtocol)
- {
- yield break;
- }
-
- var streams = GetExternalSubtitleStreams(video, 0, directoryService, clearCache);
-
- foreach (var stream in streams)
- {
- yield return stream.Path;
- }
- }
-
/// <summary>
- /// Extracts the subtitle files from the provided list and adds them to the list of streams.
+ /// Initializes a new instance of the <see cref="SubtitleResolver"/> class for external subtitle file processing.
/// </summary>
- /// <param name="streams">The list of streams to add external subtitles to.</param>
- /// <param name="videoPath">The path to the video file.</param>
- /// <param name="startIndex">The stream index to start adding subtitle streams at.</param>
- /// <param name="files">The files to add if they are subtitles.</param>
- public void AddExternalSubtitleStreams(
- List<MediaStream> streams,
- string videoPath,
- int startIndex,
- IReadOnlyList<string> files)
- {
- var videoFileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(videoPath);
-
- for (var i = 0; i < files.Count; i++)
+ /// <param name="localizationManager">The localization manager.</param>
+ /// <param name="mediaEncoder">The media encoder.</param>
+ /// <param name="namingOptions">The <see cref="NamingOptions"/> object containing FileExtensions, MediaDefaultFlags, MediaForcedFlags and MediaFlagDelimiters.</param>
+ public SubtitleResolver(
+ ILocalizationManager localizationManager,
+ IMediaEncoder mediaEncoder,
+ NamingOptions namingOptions)
+ : base(localizationManager, mediaEncoder, namingOptions, DlnaProfileType.Subtitle)
{
- var fullName = files[i];
- var extension = Path.GetExtension(fullName.AsSpan());
- if (!IsSubtitleExtension(extension))
- {
- continue;
- }
-
- var fileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(fullName);
-
- MediaStream mediaStream;
-
- // The subtitle 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 = new MediaStream
- {
- Index = startIndex++,
- Type = MediaStreamType.Subtitle,
- IsExternal = true,
- Path = fullName
- };
- }
- else if (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length
- && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.'
- && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
- {
- var isForced = fullName.Contains(".forced.", StringComparison.OrdinalIgnoreCase)
- || fullName.Contains(".foreign.", StringComparison.OrdinalIgnoreCase);
-
- var isDefault = fullName.Contains(".default.", StringComparison.OrdinalIgnoreCase);
-
- // Support xbmc naming conventions - 300.spanish.srt
- var languageSpan = fileNameWithoutExtension;
- while (languageSpan.Length > 0)
- {
- var lastDot = languageSpan.LastIndexOf('.');
- if (lastDot < videoFileNameWithoutExtension.Length)
- {
- languageSpan = ReadOnlySpan<char>.Empty;
- break;
- }
-
- var currentSlice = languageSpan[lastDot..];
- if (currentSlice.Equals(".default", StringComparison.OrdinalIgnoreCase)
- || currentSlice.Equals(".forced", StringComparison.OrdinalIgnoreCase)
- || currentSlice.Equals(".foreign", StringComparison.OrdinalIgnoreCase))
- {
- languageSpan = languageSpan[..lastDot];
- continue;
- }
-
- languageSpan = languageSpan[(lastDot + 1)..];
- break;
- }
-
- var language = languageSpan.ToString();
- if (string.IsNullOrWhiteSpace(language))
- {
- language = null;
- }
- else
- {
- // Try to translate to three character code
- // Be flexible and check against both the full and three character versions
- var culture = _localization.FindLanguageInfo(language);
-
- language = culture == null ? language : culture.ThreeLetterISOLanguageName;
- }
-
- mediaStream = new MediaStream
- {
- Index = startIndex++,
- Type = MediaStreamType.Subtitle,
- IsExternal = true,
- Path = fullName,
- Language = language,
- IsForced = isForced,
- IsDefault = isDefault
- };
- }
- else
- {
- continue;
- }
-
- mediaStream.Codec = extension.TrimStart('.').ToString().ToLowerInvariant();
-
- streams.Add(mediaStream);
- }
- }
-
- private static bool IsSubtitleExtension(ReadOnlySpan<char> extension)
- {
- return extension.Equals(".srt", StringComparison.OrdinalIgnoreCase)
- || extension.Equals(".ssa", StringComparison.OrdinalIgnoreCase)
- || extension.Equals(".ass", StringComparison.OrdinalIgnoreCase)
- || extension.Equals(".sub", StringComparison.OrdinalIgnoreCase)
- || extension.Equals(".vtt", StringComparison.OrdinalIgnoreCase)
- || extension.Equals(".smi", StringComparison.OrdinalIgnoreCase)
- || extension.Equals(".sami", StringComparison.OrdinalIgnoreCase);
- }
-
- private static ReadOnlySpan<char> NormalizeFilenameForSubtitleComparison(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 AddExternalSubtitleStreams(
- List<MediaStream> streams,
- string folder,
- string videoPath,
- int startIndex,
- IDirectoryService directoryService,
- bool clearCache)
- {
- var files = directoryService.GetFilePaths(folder, clearCache, true);
-
- AddExternalSubtitleStreams(streams, videoPath, startIndex, files);
}
}
}