|
|
|
@ -51,6 +51,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|
|
|
|
private readonly IServerConfigurationManager _configurationManager;
|
|
|
|
|
private readonly IFileSystem _fileSystem;
|
|
|
|
|
private readonly ILocalizationManager _localization;
|
|
|
|
|
private readonly IBlurayExaminer _blurayExaminer;
|
|
|
|
|
private readonly IConfiguration _config;
|
|
|
|
|
private readonly IServerConfigurationManager _serverConfig;
|
|
|
|
|
private readonly string _startupOptionFFmpegPath;
|
|
|
|
@ -95,6 +96,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|
|
|
|
ILogger<MediaEncoder> logger,
|
|
|
|
|
IServerConfigurationManager configurationManager,
|
|
|
|
|
IFileSystem fileSystem,
|
|
|
|
|
IBlurayExaminer blurayExaminer,
|
|
|
|
|
ILocalizationManager localization,
|
|
|
|
|
IConfiguration config,
|
|
|
|
|
IServerConfigurationManager serverConfig)
|
|
|
|
@ -102,6 +104,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|
|
|
|
_logger = logger;
|
|
|
|
|
_configurationManager = configurationManager;
|
|
|
|
|
_fileSystem = fileSystem;
|
|
|
|
|
_blurayExaminer = blurayExaminer;
|
|
|
|
|
_localization = localization;
|
|
|
|
|
_config = config;
|
|
|
|
|
_serverConfig = serverConfig;
|
|
|
|
@ -117,16 +120,22 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public string ProbePath => _ffprobePath;
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public Version EncoderVersion => _ffmpegVersion;
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public bool IsPkeyPauseSupported => _isPkeyPauseSupported;
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public bool IsVaapiDeviceAmd => _isVaapiDeviceAmd;
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public bool IsVaapiDeviceInteliHD => _isVaapiDeviceInteliHD;
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public bool IsVaapiDeviceInteli965 => _isVaapiDeviceInteli965;
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public bool IsVaapiDeviceSupportVulkanFmtModifier => _isVaapiDeviceSupportVulkanFmtModifier;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -344,26 +353,31 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|
|
|
|
_ffmpegVersion = validator.GetFFmpegVersion();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public bool SupportsEncoder(string encoder)
|
|
|
|
|
{
|
|
|
|
|
return _encoders.Contains(encoder, StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public bool SupportsDecoder(string decoder)
|
|
|
|
|
{
|
|
|
|
|
return _decoders.Contains(decoder, StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public bool SupportsHwaccel(string hwaccel)
|
|
|
|
|
{
|
|
|
|
|
return _hwaccels.Contains(hwaccel, StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public bool SupportsFilter(string filter)
|
|
|
|
|
{
|
|
|
|
|
return _filters.Contains(filter, StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public bool SupportsFilterWithOption(FilterOptionType option)
|
|
|
|
|
{
|
|
|
|
|
if (_filtersWithOption.TryGetValue((int)option, out var val))
|
|
|
|
@ -394,24 +408,16 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the media info.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="request">The request.</param>
|
|
|
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
|
|
|
/// <returns>Task.</returns>
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public Task<MediaInfo> GetMediaInfo(MediaInfoRequest request, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
var extractChapters = request.MediaType == DlnaProfileType.Video && request.ExtractChapters;
|
|
|
|
|
var inputFile = request.MediaSource.Path;
|
|
|
|
|
|
|
|
|
|
string analyzeDuration = string.Empty;
|
|
|
|
|
string ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
|
|
|
|
|
|
|
|
|
|
if (request.MediaSource.AnalyzeDurationMs > 0)
|
|
|
|
|
{
|
|
|
|
|
analyzeDuration = "-analyzeduration " +
|
|
|
|
|
(request.MediaSource.AnalyzeDurationMs * 1000).ToString();
|
|
|
|
|
analyzeDuration = "-analyzeduration " + (request.MediaSource.AnalyzeDurationMs * 1000).ToString();
|
|
|
|
|
}
|
|
|
|
|
else if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
|
|
|
|
|
{
|
|
|
|
@ -419,7 +425,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return GetMediaInfoInternal(
|
|
|
|
|
GetInputArgument(inputFile, request.MediaSource),
|
|
|
|
|
GetInputArgument(request.MediaSource.Path, request.MediaSource),
|
|
|
|
|
request.MediaSource.Path,
|
|
|
|
|
request.MediaSource.Protocol,
|
|
|
|
|
extractChapters,
|
|
|
|
@ -429,36 +435,24 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|
|
|
|
cancellationToken);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the input argument.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="inputFile">The input file.</param>
|
|
|
|
|
/// <param name="mediaSource">The mediaSource.</param>
|
|
|
|
|
/// <returns>System.String.</returns>
|
|
|
|
|
/// <exception cref="ArgumentException">Unrecognized InputType.</exception>
|
|
|
|
|
public string GetInputArgument(string inputFile, MediaSourceInfo mediaSource)
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public string GetInputArgument(IReadOnlyList<string> inputFiles, MediaSourceInfo mediaSource)
|
|
|
|
|
{
|
|
|
|
|
var prefix = "file";
|
|
|
|
|
if (mediaSource.VideoType == VideoType.BluRay
|
|
|
|
|
|| mediaSource.IsoType == IsoType.BluRay)
|
|
|
|
|
{
|
|
|
|
|
prefix = "bluray";
|
|
|
|
|
}
|
|
|
|
|
return EncodingUtils.GetInputArgument("file", inputFiles, mediaSource.Protocol);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return EncodingUtils.GetInputArgument(prefix, inputFile, mediaSource.Protocol);
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public string GetInputArgument(string inputFile, MediaSourceInfo mediaSource)
|
|
|
|
|
{
|
|
|
|
|
return EncodingUtils.GetInputArgument("file", new List<string>() { inputFile }, mediaSource.Protocol);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the input argument for an external subtitle file.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="inputFile">The input file.</param>
|
|
|
|
|
/// <returns>System.String.</returns>
|
|
|
|
|
/// <exception cref="ArgumentException">Unrecognized InputType.</exception>
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public string GetExternalSubtitleInputArgument(string inputFile)
|
|
|
|
|
{
|
|
|
|
|
const string Prefix = "file";
|
|
|
|
|
|
|
|
|
|
return EncodingUtils.GetInputArgument(Prefix, inputFile, MediaProtocol.File);
|
|
|
|
|
return EncodingUtils.GetInputArgument(Prefix, new List<string> { inputFile }, MediaProtocol.File);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -549,6 +543,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public Task<string> ExtractAudioImage(string path, int? imageStreamIndex, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
var mediaSource = new MediaSourceInfo
|
|
|
|
@ -559,11 +554,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|
|
|
|
return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, ImageFormat.Jpg, cancellationToken);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream videoStream, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
return ExtractImage(inputFile, container, videoStream, null, mediaSource, false, threedFormat, offset, ImageFormat.Jpg, cancellationToken);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, ImageFormat? targetFormat, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
return ExtractImage(inputFile, container, imageStream, imageStreamIndex, mediaSource, false, null, null, targetFormat, cancellationToken);
|
|
|
|
@ -767,6 +764,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public string GetTimeParameter(long ticks)
|
|
|
|
|
{
|
|
|
|
|
var time = TimeSpan.FromTicks(ticks);
|
|
|
|
@ -868,80 +866,56 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
public IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, uint? titleNumber)
|
|
|
|
|
{
|
|
|
|
|
// min size 300 mb
|
|
|
|
|
const long MinPlayableSize = 314572800;
|
|
|
|
|
|
|
|
|
|
// Try to eliminate menus and intros by skipping all files at the front of the list that are less than the minimum size
|
|
|
|
|
// Once we reach a file that is at least the minimum, return all subsequent ones
|
|
|
|
|
// Eliminate menus and intros by omitting VIDEO_TS.VOB and all subsequent title VOBs ending with _0.VOB
|
|
|
|
|
var allVobs = _fileSystem.GetFiles(path, true)
|
|
|
|
|
.Where(file => string.Equals(file.Extension, ".vob", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
.Where(file => string.Equals(file.Extension, ".VOB", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
.Where(file => !string.Equals(file.Name, "VIDEO_TS.VOB", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
.Where(file => !file.Name.EndsWith("_0.VOB", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
.OrderBy(i => i.FullName)
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
// If we didn't find any satisfying the min length, just take them all
|
|
|
|
|
if (allVobs.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogWarning("No vobs found in dvd structure.");
|
|
|
|
|
return Enumerable.Empty<string>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (titleNumber.HasValue)
|
|
|
|
|
{
|
|
|
|
|
var prefix = string.Format(
|
|
|
|
|
CultureInfo.InvariantCulture,
|
|
|
|
|
titleNumber.Value >= 10 ? "VTS_{0}_" : "VTS_0{0}_",
|
|
|
|
|
titleNumber.Value);
|
|
|
|
|
var prefix = string.Format(CultureInfo.InvariantCulture, "VTS_{0:D2}_", titleNumber.Value);
|
|
|
|
|
var vobs = allVobs.Where(i => i.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToList();
|
|
|
|
|
|
|
|
|
|
if (vobs.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
var minSizeVobs = vobs
|
|
|
|
|
.SkipWhile(f => f.Length < MinPlayableSize)
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
return minSizeVobs.Count == 0 ? vobs.Select(i => i.FullName) : minSizeVobs.Select(i => i.FullName);
|
|
|
|
|
return vobs.Select(i => i.FullName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_logger.LogWarning("Could not determine vob file list for {Path} using DvdLib. Will scan using file sizes.", path);
|
|
|
|
|
_logger.LogWarning("Could not determine VOB file list for title {Title} of {Path}.", titleNumber, path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var files = allVobs
|
|
|
|
|
.SkipWhile(f => f.Length < MinPlayableSize)
|
|
|
|
|
// Check for multiple big titles (> 900 MB)
|
|
|
|
|
var titles = allVobs
|
|
|
|
|
.Where(vob => vob.Length >= 900 * 1024 * 1024)
|
|
|
|
|
.Select(vob => _fileSystem.GetFileNameWithoutExtension(vob).Split('_')[1])
|
|
|
|
|
.GroupBy(x => x)
|
|
|
|
|
.Select(y => y.First())
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
// If we didn't find any satisfying the min length, just take them all
|
|
|
|
|
if (files.Count == 0)
|
|
|
|
|
// Fall back to first title if no big title is found
|
|
|
|
|
if (titles.FirstOrDefault() == null)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogWarning("Vob size filter resulted in zero matches. Taking all vobs.");
|
|
|
|
|
files = allVobs;
|
|
|
|
|
titles.Add(_fileSystem.GetFileNameWithoutExtension(allVobs[0]).Split('_')[1]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Assuming they're named "vts_05_01", take all files whose second part matches that of the first file
|
|
|
|
|
if (files.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
var parts = _fileSystem.GetFileNameWithoutExtension(files[0]).Split('_');
|
|
|
|
|
|
|
|
|
|
if (parts.Length == 3)
|
|
|
|
|
{
|
|
|
|
|
var title = parts[1];
|
|
|
|
|
|
|
|
|
|
files = files.TakeWhile(f =>
|
|
|
|
|
{
|
|
|
|
|
var fileParts = _fileSystem.GetFileNameWithoutExtension(f).Split('_');
|
|
|
|
|
|
|
|
|
|
return fileParts.Length == 3 && string.Equals(title, fileParts[1], StringComparison.OrdinalIgnoreCase);
|
|
|
|
|
}).ToList();
|
|
|
|
|
// Aggregate all VOBs of the titles
|
|
|
|
|
return allVobs
|
|
|
|
|
.Where(vob => titles.Contains(_fileSystem.GetFileNameWithoutExtension(vob).Split('_')[1]))
|
|
|
|
|
.Select(i => i.FullName)
|
|
|
|
|
.ToList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If this resulted in not getting any vobs, just take them all
|
|
|
|
|
if (files.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogWarning("Vob filename filter resulted in zero matches. Taking all vobs.");
|
|
|
|
|
files = allVobs;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
public IEnumerable<string> GetPrimaryPlaylistM2TsFiles(string path, uint? titleNumber)
|
|
|
|
|
{
|
|
|
|
|
var validPlaybackFiles = _blurayExaminer.GetDiscInfo(path).Files;
|
|
|
|
|
var directoryFiles = _fileSystem.GetFiles(path + "/BDMV/STREAM/");
|
|
|
|
|
|
|
|
|
|
return files.Select(i => i.FullName);
|
|
|
|
|
return directoryFiles
|
|
|
|
|
.Where(f => validPlaybackFiles.Contains(f.Name, StringComparer.OrdinalIgnoreCase))
|
|
|
|
|
.Select(f => f.FullName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool CanExtractSubtitles(string codec)
|
|
|
|
|