aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
diff options
context:
space:
mode:
authordkanada <dkanada@users.noreply.github.com>2021-09-06 13:35:58 +0900
committerdkanada <dkanada@users.noreply.github.com>2021-09-06 13:35:58 +0900
commit5e3905d41a1aad6825a1a9def66cc6c7c3c59917 (patch)
tree177ce6bd380591f35192ba5a8c3cb2dbabb2da21 /MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
parent68969c9530c42ab88da084c55cbeced8099d8ddd (diff)
parente9508616cc90c01a22ca28c13694587dd16b49d6 (diff)
merge branch 'master' into syncplay-clear-queue
Diffstat (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs')
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs268
1 files changed, 98 insertions, 170 deletions
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index fbd08a97c..a7bcaf544 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,20 +11,18 @@ using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Json;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.MediaEncoding.Probing;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
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;
@@ -53,7 +52,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
private readonly IServerConfigurationManager _configurationManager;
private readonly IFileSystem _fileSystem;
private readonly ILocalizationManager _localization;
- private readonly Lazy<EncodingHelper> _encodingHelperFactory;
private readonly string _startupOptionFFmpegPath;
private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(2, 2);
@@ -67,34 +65,32 @@ 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,
IServerConfigurationManager configurationManager,
IFileSystem fileSystem,
ILocalizationManager localization,
- Lazy<EncodingHelper> encodingHelperFactory,
IConfiguration config)
{
_logger = logger;
_configurationManager = configurationManager;
_fileSystem = fileSystem;
_localization = localization;
- _encodingHelperFactory = encodingHelperFactory;
_startupOptionFFmpegPath = config.GetValue<string>(Controller.Extensions.ConfigurationExtensions.FfmpegPathKey) ?? string.Empty;
- _jsonSerializerOptions = JsonDefaults.GetOptions();
+ _jsonSerializerOptions = JsonDefaults.Options;
}
/// <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.
@@ -103,22 +99,25 @@ namespace MediaBrowser.MediaEncoding.Encoder
public void SetFFmpegPath()
{
// 1) Custom path stored in config/encoding xml file under tag <EncoderAppPath> takes precedence
- if (!ValidatePath(_configurationManager.GetConfiguration<EncodingOptions>("encoding").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.GetConfiguration<EncodingOptions>("encoding");
+ var config = _configurationManager.GetEncodingOptions();
config.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty;
_configurationManager.SaveConfiguration("encoding", config);
@@ -133,11 +132,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>
@@ -156,15 +159,12 @@ 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
@@ -172,12 +172,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
else
{
- throw new ResourceNotFoundException();
+ newPath = path;
}
// Write the new ffmpeg path to the xml as <EncoderAppPath>
// This ensures its not lost on next startup
- var config = _configurationManager.GetConfiguration<EncodingOptions>("encoding");
+ var config = _configurationManager.GetEncodingOptions();
config.EncoderAppPath = newPath;
_configurationManager.SaveConfiguration("encoding", config);
@@ -187,36 +187,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;
- }
- 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)
@@ -237,34 +227,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();
@@ -280,6 +242,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);
@@ -295,6 +272,26 @@ namespace MediaBrowser.MediaEncoding.Encoder
return _hwaccels.Contains(hwaccel, StringComparer.OrdinalIgnoreCase);
}
+ public bool SupportsFilter(string filter)
+ {
+ return _filters.Contains(filter, StringComparer.OrdinalIgnoreCase);
+ }
+
+ public bool SupportsFilterWithOption(FilterOptionType option)
+ {
+ if (_filtersWithOption.TryGetValue((int)option, out var val))
+ {
+ return val;
+ }
+
+ return false;
+ }
+
+ public Version GetMediaEncoderVersion()
+ {
+ return _ffmpegVersion;
+ }
+
public bool CanEncodeToAudioCodec(string codec)
{
if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase))
@@ -358,7 +355,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
public string GetInputArgument(string inputFile, MediaSourceInfo mediaSource)
{
var prefix = "file";
- if (mediaSource.VideoType == VideoType.BluRay || mediaSource.VideoType == VideoType.Iso)
+ if (mediaSource.VideoType == VideoType.BluRay
+ || mediaSource.IsoType == IsoType.BluRay)
{
prefix = "bluray";
}
@@ -384,7 +382,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
{
@@ -436,7 +434,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (result == null || (result.Streams == null && result.Format == null))
{
- throw new Exception("ffprobe failed - streams and format are both null.");
+ throw new FfmpegException("ffprobe failed - streams and format are both null.");
}
if (result.Streams != null)
@@ -493,15 +491,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
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
@@ -559,40 +549,26 @@ namespace MediaBrowser.MediaEncoding.Encoder
// apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar.
// This filter chain may have adverse effects on recorded tv thumbnails if ar changes during presentation ex. commercials @ diff ar
- var vf = string.Empty;
-
- if (threedFormat.HasValue)
+ var vf = threedFormat switch
{
- switch (threedFormat.Value)
- {
- case Video3DFormat.HalfSideBySide:
- vf = "-vf crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1";
- // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars we may have made. Work out the correct height based on the display aspect it will maintain the aspect where -1 in this case (3d) may not.
- break;
- case Video3DFormat.FullSideBySide:
- vf = "-vf crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1";
- // fsbs crop width in half,set the display aspect,crop out any black bars we may have made
- break;
- case Video3DFormat.HalfTopAndBottom:
- vf = "-vf crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1";
- // htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made
- break;
- case Video3DFormat.FullTopAndBottom:
- vf = "-vf crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1";
- // ftab crop heigt in half, set the display aspect,crop out any black bars we may have made
- break;
- default:
- break;
- }
- }
+ // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars we may have made. Work out the correct height based on the display aspect it will maintain the aspect where -1 in this case (3d) may not.
+ Video3DFormat.HalfSideBySide => "-vf crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1",
+ // fsbs crop width in half,set the display aspect,crop out any black bars we may have made
+ Video3DFormat.FullSideBySide => "-vf crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1",
+ // htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made
+ Video3DFormat.HalfTopAndBottom => "-vf crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1",
+ // ftab crop heigt in half, set the display aspect,crop out any black bars we may have made
+ Video3DFormat.FullTopAndBottom => "-vf crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1",
+ _ => 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)
{
string tonemapFilters = "zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0:peak=100,zscale=t=bt709:m=bt709,format=yuv420p";
- if (string.IsNullOrEmpty(vf))
+ if (vf.Length == 0)
{
vf = "-vf " + tonemapFilters;
}
@@ -619,37 +595,13 @@ 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 probeSizeArgument = string.Empty;
- var analyzeDurationArgument = string.Empty;
-
- if (!string.IsNullOrWhiteSpace(probeSizeArgument))
- {
- args = probeSizeArgument + " " + args;
- }
-
- if (!string.IsNullOrWhiteSpace(analyzeDurationArgument))
- {
- args = analyzeDurationArgument + " " + args;
- }
+ 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)
{
args = string.Format(CultureInfo.InvariantCulture, "-ss {0} ", GetTimeParameter(offset.Value)) + args;
}
- if (videoStream != null)
- {
- /* fix
- var decoder = encodinghelper.GetHardwareAcceleratedVideoDecoder(VideoType.VideoFile, videoStream, GetEncodingOptions());
- if (!string.IsNullOrWhiteSpace(decoder))
- {
- args = decoder + " " + args;
- }
- */
- }
-
if (!string.IsNullOrWhiteSpace(container))
{
var inputFormat = EncodingHelper.GetInputFormat(container);
@@ -711,7 +663,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
_logger.LogError(msg);
- throw new Exception(msg);
+ throw new FfmpegException(msg);
}
return tempExtractPath;
@@ -756,31 +708,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 probeSizeArgument = string.Empty;
- var analyzeDurationArgument = string.Empty;
-
- if (!string.IsNullOrWhiteSpace(probeSizeArgument))
- {
- args = probeSizeArgument + " " + args;
- }
-
- if (!string.IsNullOrWhiteSpace(analyzeDurationArgument))
- {
- args = analyzeDurationArgument + " " + args;
- }
-
- if (videoStream != null)
- {
- /* fix
- var decoder = encodinghelper.GetHardwareAcceleratedVideoDecoder(VideoType.VideoFile, videoStream, GetEncodingOptions());
- if (!string.IsNullOrWhiteSpace(decoder))
- {
- args = decoder + " " + args;
- }
- */
- }
+ var args = string.Format(CultureInfo.InvariantCulture, "-i {0} -threads {3} -v quiet {2} -f image2 \"{1}\"", inputArgument, outputPath, vf, _threads);
if (!string.IsNullOrWhiteSpace(container))
{
@@ -860,7 +788,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
_logger.LogError(msg);
- throw new Exception(msg);
+ throw new FfmpegException(msg);
}
}
}