using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
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
{
///
/// Resolves external files for videos.
///
public class MediaInfoResolver
{
///
/// The instance.
///
private const CompareOptions CompareOptions = System.Globalization.CompareOptions.IgnoreCase | System.Globalization.CompareOptions.IgnoreNonSpace | System.Globalization.CompareOptions.IgnoreSymbols;
///
/// The instance.
///
private readonly CompareInfo _compareInfo = CultureInfo.InvariantCulture.CompareInfo;
///
/// The instance.
///
private readonly ExternalPathParser _externalPathParser;
///
/// The instance.
///
private readonly IMediaEncoder _mediaEncoder;
///
/// The of the files this resolver should resolve.
///
private readonly DlnaProfileType _type;
///
/// Initializes a new instance of the class.
///
/// The localization manager.
/// The media encoder.
/// The object containing FileExtensions, MediaDefaultFlags, MediaForcedFlags and MediaFlagDelimiters.
/// The of the parsed file.
public MediaInfoResolver(
ILocalizationManager localizationManager,
IMediaEncoder mediaEncoder,
NamingOptions namingOptions,
DlnaProfileType type)
{
_mediaEncoder = mediaEncoder;
_type = type;
_externalPathParser = new ExternalPathParser(namingOptions, localizationManager, _type);
}
///
/// Retrieves the external streams for the provided video.
///
/// The object to search external streams for.
/// The stream index to start adding external streams at.
/// The directory service to search for files.
/// True if the directory service cache should be cleared before searching.
/// The cancellation token to cancel operation.
/// The external streams located.
public async IAsyncEnumerable GetExternalStreamsAsync(
Video video,
int startIndex,
IDirectoryService directoryService,
bool clearCache,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (!video.IsFileProtocol)
{
yield break;
}
var pathInfos = GetExternalFiles(video, directoryService, clearCache);
foreach (var pathInfo in pathInfos)
{
Model.MediaInfo.MediaInfo mediaInfo = await GetMediaInfo(pathInfo.Path, _type, cancellationToken).ConfigureAwait(false);
if (mediaInfo.MediaStreams.Count == 1)
{
MediaStream mediaStream = mediaInfo.MediaStreams.First();
mediaStream.Index = startIndex++;
mediaStream.IsDefault = pathInfo.IsDefault || mediaStream.IsDefault;
mediaStream.IsForced = pathInfo.IsForced || mediaStream.IsForced;
yield return MergeMetadata(mediaStream, pathInfo);
}
else
{
foreach (MediaStream mediaStream in mediaInfo.MediaStreams)
{
mediaStream.Index = startIndex++;
yield return MergeMetadata(mediaStream, pathInfo);
}
}
}
}
///
/// Returns the external file infos for the given video.
///
/// The object to search external files for.
/// The directory service to search for files.
/// True if the directory service cache should be cleared before searching.
/// The external file paths located.
public IEnumerable GetExternalFiles(
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;
}
var files = directoryService.GetFilePaths(folder, clearCache).ToList();
files.AddRange(directoryService.GetFilePaths(video.GetInternalMetadataPath(), clearCache));
foreach (var file in files)
{
if (_compareInfo.IsPrefix(Path.GetFileNameWithoutExtension(file), video.FileNameWithoutExtension, CompareOptions, out int matchLength))
{
var externalPathInfo = _externalPathParser.ParseFile(file, Path.GetFileNameWithoutExtension(file)[matchLength..]);
if (externalPathInfo != null)
{
yield return externalPathInfo;
}
}
}
}
///
/// Returns the media info of the given file.
///
/// The path to the file.
/// The .
/// The cancellation token to cancel operation.
/// The media info for the given file.
private Task 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);
}
///
/// Merges path metadata into stream metadata.
///
/// The object.
/// The object.
/// The modified mediaStream.
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,
DlnaProfileType.Video => MediaStreamType.Video,
_ => mediaStream.Type
};
return mediaStream;
}
}
}