aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs')
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs235
1 files changed, 120 insertions, 115 deletions
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 62c0c0bb1..fbc7ba72f 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -1,3 +1,4 @@
+#nullable disable
#pragma warning disable CS1591
using System;
@@ -10,9 +11,10 @@ using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Extensions.Json;
+using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Json;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.MediaEncoding.Probing;
@@ -22,7 +24,6 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
@@ -43,11 +44,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// </summary>
internal const int DefaultHdrImageExtractionTimeout = 20000;
- /// <summary>
- /// The us culture.
- /// </summary>
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
private readonly ILogger<MediaEncoder> _logger;
private readonly IServerConfigurationManager _configurationManager;
private readonly IFileSystem _fileSystem;
@@ -65,10 +61,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
private List<string> _encoders = new List<string>();
private List<string> _decoders = new List<string>();
private List<string> _hwaccels = new List<string>();
+ private List<string> _filters = new List<string>();
+ private IDictionary<int, bool> _filtersWithOption = new Dictionary<int, bool>();
+ private Version _ffmpegVersion = null;
private string _ffmpegPath = string.Empty;
private string _ffprobePath;
- private int threads;
+ private int _threads;
public MediaEncoder(
ILogger<MediaEncoder> logger,
@@ -88,9 +87,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <inheritdoc />
public string EncoderPath => _ffmpegPath;
- /// <inheritdoc />
- public FFmpegLocation EncoderLocation { get; private set; }
-
/// <summary>
/// Run at startup or if the user removes a Custom path from transcode page.
/// Sets global variables FFmpegPath.
@@ -99,20 +95,23 @@ namespace MediaBrowser.MediaEncoding.Encoder
public void SetFFmpegPath()
{
// 1) Custom path stored in config/encoding xml file under tag <EncoderAppPath> takes precedence
- if (!ValidatePath(_configurationManager.GetEncodingOptions().EncoderAppPath, FFmpegLocation.Custom))
+ var ffmpegPath = _configurationManager.GetEncodingOptions().EncoderAppPath;
+ if (string.IsNullOrEmpty(ffmpegPath))
{
// 2) Check if the --ffmpeg CLI switch has been given
- if (!ValidatePath(_startupOptionFFmpegPath, FFmpegLocation.SetByArgument))
+ ffmpegPath = _startupOptionFFmpegPath;
+ if (string.IsNullOrEmpty(ffmpegPath))
{
- // 3) Search system $PATH environment variable for valid FFmpeg
- if (!ValidatePath(ExistsOnSystemPath("ffmpeg"), FFmpegLocation.System))
- {
- EncoderLocation = FFmpegLocation.NotFound;
- _ffmpegPath = null;
- }
+ // 3) Check "ffmpeg"
+ ffmpegPath = "ffmpeg";
}
}
+ if (!ValidatePath(ffmpegPath))
+ {
+ _ffmpegPath = null;
+ }
+
// Write the FFmpeg path to the config/encoding.xml file as <EncoderAppPathDisplay> so it appears in UI
var config = _configurationManager.GetEncodingOptions();
config.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty;
@@ -129,11 +128,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
SetAvailableDecoders(validator.GetDecoders());
SetAvailableEncoders(validator.GetEncoders());
+ SetAvailableFilters(validator.GetFilters());
+ SetAvailableFiltersWithOption(validator.GetFiltersWithOption());
SetAvailableHwaccels(validator.GetHwaccels());
- threads = EncodingHelper.GetNumberOfThreads(null, _configurationManager.GetEncodingOptions(), null);
+ SetMediaEncoderVersion(validator);
+
+ _threads = EncodingHelper.GetNumberOfThreads(null, _configurationManager.GetEncodingOptions(), null);
}
- _logger.LogInformation("FFmpeg: {EncoderLocation}: {FfmpegPath}", EncoderLocation, _ffmpegPath ?? string.Empty);
+ _logger.LogInformation("FFmpeg: {FfmpegPath}", _ffmpegPath ?? string.Empty);
}
/// <summary>
@@ -144,6 +147,16 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <param name="pathType">The path type.</param>
public void UpdateEncoderPath(string path, string pathType)
{
+ var config = _configurationManager.GetEncodingOptions();
+
+ // Filesystem may not be case insensitive, but EncoderAppPathDisplay should always point to a valid file?
+ if (string.IsNullOrEmpty(config.EncoderAppPath)
+ && string.Equals(config.EncoderAppPathDisplay, path, StringComparison.OrdinalIgnoreCase))
+ {
+ _logger.LogDebug("Existing ffmpeg path is empty and the new path is the same as {EncoderAppPathDisplay}. Skipping", nameof(config.EncoderAppPathDisplay));
+ return;
+ }
+
string newPath;
_logger.LogInformation("Attempting to update encoder path to {Path}. pathType: {PathType}", path ?? string.Empty, pathType ?? string.Empty);
@@ -152,28 +165,32 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
throw new ArgumentException("Unexpected pathType value");
}
- else if (string.IsNullOrWhiteSpace(path))
+
+ if (string.IsNullOrWhiteSpace(path))
{
// User had cleared the custom path in UI
newPath = string.Empty;
}
- else if (File.Exists(path))
- {
- newPath = path;
- }
- else if (Directory.Exists(path))
- {
- // Given path is directory, so resolve down to filename
- newPath = GetEncoderPathFromDirectory(path, "ffmpeg");
- }
else
{
- throw new ResourceNotFoundException();
+ if (Directory.Exists(path))
+ {
+ // Given path is directory, so resolve down to filename
+ newPath = GetEncoderPathFromDirectory(path, "ffmpeg");
+ }
+ else
+ {
+ newPath = path;
+ }
+
+ if (!new EncoderValidator(_logger, newPath).ValidateVersion())
+ {
+ throw new ResourceNotFoundException();
+ }
}
// Write the new ffmpeg path to the xml as <EncoderAppPath>
// This ensures its not lost on next startup
- var config = _configurationManager.GetEncodingOptions();
config.EncoderAppPath = newPath;
_configurationManager.SaveConfiguration("encoding", config);
@@ -183,37 +200,26 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <summary>
/// Validates the supplied FQPN to ensure it is a ffmpeg utility.
- /// If checks pass, global variable FFmpegPath and EncoderLocation are updated.
+ /// If checks pass, global variable FFmpegPath is updated.
/// </summary>
/// <param name="path">FQPN to test.</param>
- /// <param name="location">Location (External, Custom, System) of tool.</param>
/// <returns><c>true</c> if the version validation succeeded; otherwise, <c>false</c>.</returns>
- private bool ValidatePath(string path, FFmpegLocation location)
+ private bool ValidatePath(string path)
{
- bool rc = false;
-
- if (!string.IsNullOrEmpty(path))
+ if (string.IsNullOrEmpty(path))
{
- if (File.Exists(path))
- {
- rc = new EncoderValidator(_logger, path).ValidateVersion();
-
- if (!rc)
- {
- _logger.LogWarning("FFmpeg: {Location}: Failed version check: {Path}", location, path);
- }
+ return false;
+ }
- _ffmpegPath = path;
- EncoderLocation = location;
- return true;
- }
- else
- {
- _logger.LogWarning("FFmpeg: {Location}: File not found: {Path}", location, path);
- }
+ bool rc = new EncoderValidator(_logger, path).ValidateVersion();
+ if (!rc)
+ {
+ _logger.LogWarning("FFmpeg: Failed version check: {Path}", path);
+ return false;
}
- return rc;
+ _ffmpegPath = path;
+ return true;
}
private string GetEncoderPathFromDirectory(string path, string filename, bool recursive = false)
@@ -234,34 +240,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
- /// <summary>
- /// Search the system $PATH environment variable looking for given filename.
- /// </summary>
- /// <param name="fileName">The filename.</param>
- /// <returns>The full path to the file.</returns>
- private string ExistsOnSystemPath(string fileName)
- {
- var inJellyfinPath = GetEncoderPathFromDirectory(AppContext.BaseDirectory, fileName, recursive: true);
- if (!string.IsNullOrEmpty(inJellyfinPath))
- {
- return inJellyfinPath;
- }
-
- var values = Environment.GetEnvironmentVariable("PATH");
-
- foreach (var path in values.Split(Path.PathSeparator))
- {
- var candidatePath = GetEncoderPathFromDirectory(path, fileName);
-
- if (!string.IsNullOrEmpty(candidatePath))
- {
- return candidatePath;
- }
- }
-
- return null;
- }
-
public void SetAvailableEncoders(IEnumerable<string> list)
{
_encoders = list.ToList();
@@ -277,6 +255,21 @@ namespace MediaBrowser.MediaEncoding.Encoder
_hwaccels = list.ToList();
}
+ public void SetAvailableFilters(IEnumerable<string> list)
+ {
+ _filters = list.ToList();
+ }
+
+ public void SetAvailableFiltersWithOption(IDictionary<int, bool> dict)
+ {
+ _filtersWithOption = dict;
+ }
+
+ public void SetMediaEncoderVersion(EncoderValidator validator)
+ {
+ _ffmpegVersion = validator.GetFFmpegVersion();
+ }
+
public bool SupportsEncoder(string encoder)
{
return _encoders.Contains(encoder, StringComparer.OrdinalIgnoreCase);
@@ -292,17 +285,26 @@ namespace MediaBrowser.MediaEncoding.Encoder
return _hwaccels.Contains(hwaccel, StringComparer.OrdinalIgnoreCase);
}
- public bool SupportsFilter(string filter, string option)
+ public bool SupportsFilter(string filter)
{
- if (_ffmpegPath != null)
+ return _filters.Contains(filter, StringComparer.OrdinalIgnoreCase);
+ }
+
+ public bool SupportsFilterWithOption(FilterOptionType option)
+ {
+ if (_filtersWithOption.TryGetValue((int)option, out var val))
{
- var validator = new EncoderValidator(_logger, _ffmpegPath);
- return validator.CheckFilter(filter, option);
+ return val;
}
return false;
}
+ public Version GetMediaEncoderVersion()
+ {
+ return _ffmpegVersion;
+ }
+
public bool CanEncodeToAudioCodec(string codec)
{
if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase))
@@ -366,7 +368,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
public string GetInputArgument(string inputFile, MediaSourceInfo mediaSource)
{
var prefix = "file";
- if (mediaSource.VideoType == VideoType.BluRay)
+ if (mediaSource.VideoType == VideoType.BluRay
+ || mediaSource.IsoType == IsoType.BluRay)
{
prefix = "bluray";
}
@@ -392,7 +395,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
var args = extractChapters
? "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_chapters -show_format"
: "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_format";
- args = string.Format(CultureInfo.InvariantCulture, args, probeSizeArgument, inputPath, threads).Trim();
+ args = string.Format(CultureInfo.InvariantCulture, args, probeSizeArgument, inputPath, _threads).Trim();
var process = new Process
{
@@ -475,17 +478,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
Protocol = MediaProtocol.File
};
- return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, cancellationToken);
+ return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, ".jpg", cancellationToken);
}
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, cancellationToken);
+ return ExtractImage(inputFile, container, videoStream, null, mediaSource, false, threedFormat, offset, ".jpg", cancellationToken);
}
- public Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, CancellationToken cancellationToken)
+ public Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, string outputExtension, CancellationToken cancellationToken)
{
- return ExtractImage(inputFile, container, imageStream, imageStreamIndex, mediaSource, false, null, null, cancellationToken);
+ return ExtractImage(inputFile, container, imageStream, imageStreamIndex, mediaSource, false, null, null, outputExtension, cancellationToken);
}
private async Task<string> ExtractImage(
@@ -497,24 +500,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
bool isAudio,
Video3DFormat? threedFormat,
TimeSpan? offset,
+ string outputExtension,
CancellationToken cancellationToken)
{
var inputArgument = GetInputArgument(inputFile, mediaSource);
- if (isAudio)
- {
- if (imageStreamIndex.HasValue && imageStreamIndex.Value > 0)
- {
- // It seems for audio files we need to subtract 1 (for the audio stream??)
- imageStreamIndex = imageStreamIndex.Value - 1;
- }
- }
- else
+ if (!isAudio)
{
// The failure of HDR extraction usually occurs when using custom ffmpeg that does not contain the zscale filter.
try
{
- return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, true, cancellationToken).ConfigureAwait(false);
+ return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, true, outputExtension, cancellationToken).ConfigureAwait(false);
}
catch (ArgumentException)
{
@@ -527,7 +523,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
try
{
- return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, true, cancellationToken).ConfigureAwait(false);
+ return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, true, outputExtension, cancellationToken).ConfigureAwait(false);
}
catch (ArgumentException)
{
@@ -540,7 +536,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
try
{
- return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, false, cancellationToken).ConfigureAwait(false);
+ return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, false, outputExtension, cancellationToken).ConfigureAwait(false);
}
catch (ArgumentException)
{
@@ -552,17 +548,26 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
- return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, false, cancellationToken).ConfigureAwait(false);
+ return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, false, outputExtension, cancellationToken).ConfigureAwait(false);
}
- private async Task<string> ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, bool allowTonemap, CancellationToken cancellationToken)
+ private async Task<string> ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, bool allowTonemap, string outputExtension, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(inputPath))
{
throw new ArgumentNullException(nameof(inputPath));
}
- var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + ".jpg");
+ if (string.IsNullOrEmpty(outputExtension))
+ {
+ outputExtension = ".jpg";
+ }
+ else if (outputExtension[0] != '.')
+ {
+ outputExtension = "." + outputExtension;
+ }
+
+ var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + outputExtension);
Directory.CreateDirectory(Path.GetDirectoryName(tempExtractPath));
// apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar.
@@ -580,7 +585,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
_ => string.Empty
};
- var mapArg = imageStreamIndex.HasValue ? (" -map 0:v:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty;
+ var mapArg = imageStreamIndex.HasValue ? (" -map 0:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty;
var enableHdrExtraction = allowTonemap && string.Equals(videoStream?.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase);
if (enableHdrExtraction)
@@ -613,7 +618,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
- var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 {2} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, threads);
+ var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 {2} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, _threads);
if (offset.HasValue)
{
@@ -697,7 +702,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
public string GetTimeParameter(TimeSpan time)
{
- return time.ToString(@"hh\:mm\:ss\.fff", _usCulture);
+ return time.ToString(@"hh\:mm\:ss\.fff", CultureInfo.InvariantCulture);
}
public async Task ExtractVideoImagesOnInterval(
@@ -714,11 +719,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
var inputArgument = GetInputArgument(inputFile, mediaSource);
- var vf = "fps=fps=1/" + interval.TotalSeconds.ToString(_usCulture);
+ var vf = "fps=fps=1/" + interval.TotalSeconds.ToString(CultureInfo.InvariantCulture);
if (maxWidth.HasValue)
{
- var maxWidthParam = maxWidth.Value.ToString(_usCulture);
+ var maxWidthParam = maxWidth.Value.ToString(CultureInfo.InvariantCulture);
vf += string.Format(CultureInfo.InvariantCulture, ",scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam);
}
@@ -726,7 +731,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
Directory.CreateDirectory(targetDirectory);
var outputPath = Path.Combine(targetDirectory, filenamePrefix + "%05d.jpg");
- var args = string.Format(CultureInfo.InvariantCulture, "-i {0} -threads {3} -v quiet {2} -f image2 \"{1}\"", inputArgument, outputPath, vf, threads);
+ var args = string.Format(CultureInfo.InvariantCulture, "-i {0} -threads {3} -v quiet {2} -f image2 \"{1}\"", inputArgument, outputPath, vf, _threads);
if (!string.IsNullOrWhiteSpace(container))
{