2022-02-17 08:03:08 +00:00
|
|
|
using System;
|
2022-02-07 17:36:07 +00:00
|
|
|
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>
|
2022-02-17 08:03:08 +00:00
|
|
|
/// Resolves external files for <see cref="Video"/>.
|
2022-02-07 17:36:07 +00:00
|
|
|
/// </summary>
|
2022-02-17 08:03:08 +00:00
|
|
|
public abstract class MediaInfoResolver
|
2022-02-07 17:36:07 +00:00
|
|
|
{
|
|
|
|
/// <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="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>
|
2022-02-17 08:03:08 +00:00
|
|
|
protected MediaInfoResolver(
|
2022-02-07 17:36:07 +00:00
|
|
|
ILocalizationManager localizationManager,
|
|
|
|
IMediaEncoder mediaEncoder,
|
|
|
|
NamingOptions namingOptions,
|
|
|
|
DlnaProfileType type)
|
|
|
|
{
|
|
|
|
_mediaEncoder = mediaEncoder;
|
|
|
|
_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>
|
2022-02-17 08:03:08 +00:00
|
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
2022-02-07 17:36:07 +00:00
|
|
|
/// <returns>The external streams located.</returns>
|
2022-02-17 08:03:08 +00:00
|
|
|
public async Task<IReadOnlyList<MediaStream>> GetExternalStreamsAsync(
|
2022-02-07 17:36:07 +00:00
|
|
|
Video video,
|
|
|
|
int startIndex,
|
|
|
|
IDirectoryService directoryService,
|
|
|
|
bool clearCache,
|
2022-02-17 08:03:08 +00:00
|
|
|
CancellationToken cancellationToken)
|
2022-02-07 17:36:07 +00:00
|
|
|
{
|
|
|
|
if (!video.IsFileProtocol)
|
|
|
|
{
|
2022-02-17 08:03:08 +00:00
|
|
|
return Array.Empty<MediaStream>();
|
2022-02-07 17:36:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var pathInfos = GetExternalFiles(video, directoryService, clearCache);
|
|
|
|
|
2022-02-17 08:03:08 +00:00
|
|
|
if (!pathInfos.Any())
|
|
|
|
{
|
|
|
|
return Array.Empty<MediaStream>();
|
|
|
|
}
|
|
|
|
|
|
|
|
var mediaStreams = new List<MediaStream>();
|
|
|
|
|
2022-02-07 17:36:07 +00:00
|
|
|
foreach (var pathInfo in pathInfos)
|
|
|
|
{
|
2022-02-17 08:03:08 +00:00
|
|
|
var mediaInfo = await GetMediaInfo(pathInfo.Path, _type, cancellationToken).ConfigureAwait(false);
|
2022-02-07 17:36:07 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2022-02-17 08:03:08 +00:00
|
|
|
mediaStreams.Add(MergeMetadata(mediaStream, pathInfo));
|
2022-02-07 17:36:07 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
foreach (MediaStream mediaStream in mediaInfo.MediaStreams)
|
|
|
|
{
|
|
|
|
mediaStream.Index = startIndex++;
|
|
|
|
|
2022-02-17 08:03:08 +00:00
|
|
|
mediaStreams.Add(MergeMetadata(mediaStream, pathInfo));
|
2022-02-07 17:36:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-02-17 08:03:08 +00:00
|
|
|
|
|
|
|
return mediaStreams.AsReadOnly();
|
2022-02-07 17:36:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// <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>
|
2022-02-17 08:03:08 +00:00
|
|
|
public IReadOnlyList<ExternalPathParserResult> GetExternalFiles(
|
2022-02-07 17:36:07 +00:00
|
|
|
Video video,
|
|
|
|
IDirectoryService directoryService,
|
|
|
|
bool clearCache)
|
|
|
|
{
|
|
|
|
if (!video.IsFileProtocol)
|
|
|
|
{
|
2022-02-17 08:03:08 +00:00
|
|
|
return Array.Empty<ExternalPathParserResult>();
|
2022-02-07 17:36:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check if video folder exists
|
|
|
|
string folder = video.ContainingFolderPath;
|
|
|
|
if (!Directory.Exists(folder))
|
|
|
|
{
|
2022-02-17 08:03:08 +00:00
|
|
|
return Array.Empty<ExternalPathParserResult>();
|
2022-02-07 17:36:07 +00:00
|
|
|
}
|
|
|
|
|
2022-02-17 08:03:08 +00:00
|
|
|
var externalPathInfos = new List<ExternalPathParserResult>();
|
|
|
|
|
2022-02-07 17:36:07 +00:00
|
|
|
var files = directoryService.GetFilePaths(folder, clearCache).ToList();
|
|
|
|
files.AddRange(directoryService.GetFilePaths(video.GetInternalMetadataPath(), clearCache));
|
|
|
|
|
2022-02-17 08:03:08 +00:00
|
|
|
if (!files.Any())
|
|
|
|
{
|
|
|
|
return Array.Empty<ExternalPathParserResult>();
|
|
|
|
}
|
|
|
|
|
2022-02-07 17:36:07 +00:00
|
|
|
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)
|
|
|
|
{
|
2022-02-17 08:03:08 +00:00
|
|
|
externalPathInfos.Add(externalPathInfo);
|
2022-02-07 17:36:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-02-17 08:03:08 +00:00
|
|
|
|
2022-02-19 22:47:11 +00:00
|
|
|
return externalPathInfos;
|
2022-02-07 17:36:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// <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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|