mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-03-09 11:46:18 +00:00
Merge branch 'master' into feature/ffmpeg-version-check
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@@ -102,6 +104,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
// $ ffmpeg -version | perl -ne ' print "$1=$2.$3," if /^(lib\w+)\s+(\d+)\.\s*(\d+)/'
|
||||
private static readonly IReadOnlyDictionary<string, Version> _ffmpegVersionMap = new Dictionary<string, Version>
|
||||
{
|
||||
{ "libavutil=56.51,libavcodec=58.91,libavformat=58.45,libavdevice=58.10,libavfilter=7.85,libswscale=5.7,libswresample=3.7,libpostproc=55.7,", new Version(4, 3) },
|
||||
{ "libavutil=56.31,libavcodec=58.54,libavformat=58.29,libavdevice=58.8,libavfilter=7.57,libswscale=5.5,libswresample=3.5,libpostproc=55.5,", new Version(4, 2) },
|
||||
{ "libavutil=56.22,libavcodec=58.35,libavformat=58.20,libavdevice=58.5,libavfilter=7.40,libswscale=5.3,libswresample=3.3,libpostproc=55.3,", new Version(4, 1) },
|
||||
{ "libavutil=56.14,libavcodec=58.18,libavformat=58.12,libavdevice=58.3,libavfilter=7.16,libswscale=5.1,libswresample=3.1,libpostproc=55.1,", new Version(4, 0) },
|
||||
@@ -121,6 +124,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
_encoderPath = encoderPath;
|
||||
}
|
||||
|
||||
private enum Codec
|
||||
{
|
||||
Encoder,
|
||||
Decoder
|
||||
}
|
||||
|
||||
public static Version MinVersion { get; } = new Version(4, 0);
|
||||
|
||||
public static Version MaxVersion { get; } = null;
|
||||
@@ -207,12 +216,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
/// to parse. If this is not available, then we try to match known library versions to FFmpeg versions.
|
||||
/// If that fails then we test the libraries to determine if they're newer than our minimum versions.
|
||||
/// </summary>
|
||||
/// <param name="output"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="output">The output from "ffmpeg -version".</param>
|
||||
/// <returns>The FFmpeg version.</returns>
|
||||
internal Version GetFFmpegVersion(string output)
|
||||
{
|
||||
// For pre-built binaries the FFmpeg version should be mentioned at the very start of the output
|
||||
var match = Regex.Match(output, @"^ffmpeg version n?((?:\d+\.?)+)");
|
||||
var match = Regex.Match(output, @"^ffmpeg version n?((?:[0-9]+\.?)+)");
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
@@ -268,11 +277,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
|
||||
/// <summary>
|
||||
/// Grabs the library names and major.minor version numbers from the 'ffmpeg -version' output
|
||||
/// and condenses them on to one line. Output format is "name1=major.minor,name2=major.minor,etc.".
|
||||
/// </summary>
|
||||
/// <param name="output"></param>
|
||||
/// <param name="versionString"></param>
|
||||
/// <param name="versionMap"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="output">The 'ffmpeg -version' output.</param>
|
||||
/// <returns>The library names and major.minor version numbers.</returns>
|
||||
private static bool TryGetFFmpegLibraryVersions(string output, out string versionString, out IReadOnlyDictionary<string, Version> versionMap)
|
||||
{
|
||||
var sb = new StringBuilder(144);
|
||||
@@ -281,7 +289,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
|
||||
foreach (Match match in Regex.Matches(
|
||||
output,
|
||||
@"((?<name>lib\w+)\s+(?<major>\d+)\.\s*(?<minor>\d+))",
|
||||
@"((?<name>lib\w+)\s+(?<major>[0-9]+)\.\s*(?<minor>[0-9]+))",
|
||||
RegexOptions.Multiline))
|
||||
{
|
||||
sb.Append(match.Groups["name"])
|
||||
@@ -304,12 +312,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
return sb.Length > 0;
|
||||
}
|
||||
|
||||
private enum Codec
|
||||
{
|
||||
Encoder,
|
||||
Decoder
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetHwaccelTypes()
|
||||
{
|
||||
string output = null;
|
||||
@@ -327,7 +329,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
var found = output.Split(new char[] {'\r','\n'}, StringSplitOptions.RemoveEmptyEntries).Skip(1).Distinct().ToList();
|
||||
var found = output.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Skip(1).Distinct().ToList();
|
||||
_logger.LogInformation("Available hwaccel types: {Types}", found);
|
||||
|
||||
return found;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
|
||||
@@ -12,7 +15,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
{
|
||||
var url = inputFiles[0];
|
||||
|
||||
return string.Format("\"{0}\"", url);
|
||||
return string.Format(CultureInfo.InvariantCulture, "\"{0}\"", url);
|
||||
}
|
||||
|
||||
return GetConcatInputArgument(inputFiles);
|
||||
@@ -31,7 +34,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
{
|
||||
var files = string.Join("|", inputFiles.Select(NormalizePath));
|
||||
|
||||
return string.Format("concat:\"{0}\"", files);
|
||||
return string.Format(CultureInfo.InvariantCulture, "concat:\"{0}\"", files);
|
||||
}
|
||||
|
||||
// Determine the input path for video files
|
||||
@@ -47,13 +50,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
{
|
||||
if (path.IndexOf("://") != -1)
|
||||
{
|
||||
return string.Format("\"{0}\"", path);
|
||||
return string.Format(CultureInfo.InvariantCulture, "\"{0}\"", path);
|
||||
}
|
||||
|
||||
// Quotes are valid path characters in linux and they need to be escaped here with a leading \
|
||||
path = NormalizePath(path);
|
||||
|
||||
return string.Format("file:\"{0}\"", path);
|
||||
return string.Format(CultureInfo.InvariantCulture, "file:\"{0}\"", path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -19,9 +22,8 @@ using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MediaBrowser.MediaEncoding.Encoder
|
||||
{
|
||||
@@ -35,6 +37,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
/// </summary>
|
||||
internal const int DefaultImageExtractionTimeout = 5000;
|
||||
|
||||
/// <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;
|
||||
@@ -47,6 +54,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
private readonly object _runningProcessesLock = new object();
|
||||
private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
|
||||
|
||||
private List<string> _encoders = new List<string>();
|
||||
private List<string> _decoders = new List<string>();
|
||||
private List<string> _hwaccels = new List<string>();
|
||||
|
||||
private string _ffmpegPath = string.Empty;
|
||||
private string _ffprobePath;
|
||||
|
||||
@@ -77,7 +88,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
/// <summary>
|
||||
/// Run at startup or if the user removes a Custom path from transcode page.
|
||||
/// Sets global variables FFmpegPath.
|
||||
/// Precedence is: Config > CLI > $PATH
|
||||
/// Precedence is: Config > CLI > $PATH.
|
||||
/// </summary>
|
||||
public void SetFFmpegPath()
|
||||
{
|
||||
@@ -122,8 +133,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
/// Triggered from the Settings > Transcoding UI page when users submits Custom FFmpeg path to use.
|
||||
/// Only write the new path to xml if it exists. Do not perform validation checks on ffmpeg here.
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <param name="pathType"></param>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <param name="pathType">The path type.</param>
|
||||
public void UpdateEncoderPath(string path, string pathType)
|
||||
{
|
||||
string newPath;
|
||||
@@ -168,8 +179,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
/// If checks pass, global variable FFmpegPath and EncoderLocation are updated.
|
||||
/// </summary>
|
||||
/// <param name="path">FQPN to test.</param>
|
||||
/// <param name="location">Location (External, Custom, System) of tool</param>
|
||||
/// <returns></returns>
|
||||
/// <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)
|
||||
{
|
||||
bool rc = false;
|
||||
@@ -218,8 +229,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
/// <summary>
|
||||
/// Search the system $PATH environment variable looking for given filename.
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
/// <returns></returns>
|
||||
/// <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);
|
||||
@@ -243,25 +254,19 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<string> _encoders = new List<string>();
|
||||
public void SetAvailableEncoders(IEnumerable<string> list)
|
||||
{
|
||||
_encoders = list.ToList();
|
||||
// _logger.Info("Supported encoders: {0}", string.Join(",", list.ToArray()));
|
||||
}
|
||||
|
||||
private List<string> _decoders = new List<string>();
|
||||
public void SetAvailableDecoders(IEnumerable<string> list)
|
||||
{
|
||||
_decoders = list.ToList();
|
||||
// _logger.Info("Supported decoders: {0}", string.Join(",", list.ToArray()));
|
||||
}
|
||||
|
||||
private List<string> _hwaccels = new List<string>();
|
||||
public void SetAvailableHwaccels(IEnumerable<string> list)
|
||||
{
|
||||
_hwaccels = list.ToList();
|
||||
//_logger.Info("Supported hwaccels: {0}", string.Join(",", list.ToArray()));
|
||||
}
|
||||
|
||||
public bool SupportsEncoder(string encoder)
|
||||
@@ -329,8 +334,16 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
|
||||
var forceEnableLogging = request.MediaSource.Protocol != MediaProtocol.File;
|
||||
|
||||
return GetMediaInfoInternal(GetInputArgument(inputFiles, request.MediaSource.Protocol), request.MediaSource.Path, request.MediaSource.Protocol, extractChapters,
|
||||
probeSize, request.MediaType == DlnaProfileType.Audio, request.MediaSource.VideoType, forceEnableLogging, cancellationToken);
|
||||
return GetMediaInfoInternal(
|
||||
GetInputArgument(inputFiles, request.MediaSource.Protocol),
|
||||
request.MediaSource.Path,
|
||||
request.MediaSource.Protocol,
|
||||
extractChapters,
|
||||
probeSize,
|
||||
request.MediaType == DlnaProfileType.Audio,
|
||||
request.MediaSource.VideoType,
|
||||
forceEnableLogging,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -339,7 +352,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
/// <param name="inputFiles">The input files.</param>
|
||||
/// <param name="protocol">The protocol.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
/// <exception cref="ArgumentException">Unrecognized InputType</exception>
|
||||
/// <exception cref="ArgumentException">Unrecognized InputType.</exception>
|
||||
public string GetInputArgument(IReadOnlyList<string> inputFiles, MediaProtocol protocol)
|
||||
=> EncodingUtils.GetInputArgument(inputFiles, protocol);
|
||||
|
||||
@@ -347,7 +360,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
/// Gets the media info internal.
|
||||
/// </summary>
|
||||
/// <returns>Task{MediaInfoResult}.</returns>
|
||||
private async Task<MediaInfo> GetMediaInfoInternal(string inputPath,
|
||||
private async Task<MediaInfo> GetMediaInfoInternal(
|
||||
string inputPath,
|
||||
string primaryPath,
|
||||
MediaProtocol protocol,
|
||||
bool extractChapters,
|
||||
@@ -375,7 +389,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
FileName = _ffprobePath,
|
||||
Arguments = args,
|
||||
|
||||
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
ErrorDialog = false,
|
||||
},
|
||||
@@ -436,11 +449,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The us culture.
|
||||
/// </summary>
|
||||
protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||
|
||||
public Task<string> ExtractAudioImage(string path, int? imageStreamIndex, CancellationToken cancellationToken)
|
||||
{
|
||||
return ExtractImage(new[] { path }, null, null, imageStreamIndex, MediaProtocol.File, true, null, null, cancellationToken);
|
||||
@@ -456,8 +464,16 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
return ExtractImage(inputFiles, container, imageStream, imageStreamIndex, protocol, false, null, null, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<string> ExtractImage(string[] inputFiles, string container, MediaStream videoStream, int? imageStreamIndex, MediaProtocol protocol, bool isAudio,
|
||||
Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken)
|
||||
private async Task<string> ExtractImage(
|
||||
string[] inputFiles,
|
||||
string container,
|
||||
MediaStream videoStream,
|
||||
int? imageStreamIndex,
|
||||
MediaProtocol protocol,
|
||||
bool isAudio,
|
||||
Video3DFormat? threedFormat,
|
||||
TimeSpan? offset,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var inputArgument = GetInputArgument(inputFiles, protocol);
|
||||
|
||||
@@ -533,8 +549,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
// Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case.
|
||||
var thumbnail = enableThumbnail ? ",thumbnail=24" : string.Empty;
|
||||
|
||||
var args = useIFrame ? string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}{4}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, thumbnail) :
|
||||
string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg);
|
||||
var args = useIFrame ? string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}{4}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, thumbnail) :
|
||||
string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg);
|
||||
|
||||
var probeSizeArgument = EncodingHelper.GetProbeSizeArgument(1);
|
||||
var analyzeDurationArgument = EncodingHelper.GetAnalyzeDurationArgument(1);
|
||||
@@ -551,7 +567,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
|
||||
if (offset.HasValue)
|
||||
{
|
||||
args = string.Format("-ss {0} ", GetTimeParameter(offset.Value)) + args;
|
||||
args = string.Format(CultureInfo.InvariantCulture, "-ss {0} ", GetTimeParameter(offset.Value)) + args;
|
||||
}
|
||||
|
||||
if (videoStream != null)
|
||||
@@ -622,7 +638,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
|
||||
if (exitCode == -1 || !file.Exists || file.Length == 0)
|
||||
{
|
||||
var msg = string.Format("ffmpeg image extraction failed for {0}", inputPath);
|
||||
var msg = string.Format(CultureInfo.InvariantCulture, "ffmpeg image extraction failed for {0}", inputPath);
|
||||
|
||||
_logger.LogError(msg);
|
||||
|
||||
@@ -642,7 +658,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", _usCulture);
|
||||
}
|
||||
|
||||
public async Task ExtractVideoImagesOnInterval(
|
||||
@@ -659,19 +675,19 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
{
|
||||
var inputArgument = GetInputArgument(inputFiles, protocol);
|
||||
|
||||
var vf = "fps=fps=1/" + interval.TotalSeconds.ToString(UsCulture);
|
||||
var vf = "fps=fps=1/" + interval.TotalSeconds.ToString(_usCulture);
|
||||
|
||||
if (maxWidth.HasValue)
|
||||
{
|
||||
var maxWidthParam = maxWidth.Value.ToString(UsCulture);
|
||||
var maxWidthParam = maxWidth.Value.ToString(_usCulture);
|
||||
|
||||
vf += string.Format(",scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam);
|
||||
vf += string.Format(CultureInfo.InvariantCulture, ",scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(targetDirectory);
|
||||
var outputPath = Path.Combine(targetDirectory, filenamePrefix + "%05d.jpg");
|
||||
|
||||
var args = string.Format("-i {0} -threads 0 -v quiet -vf \"{2}\" -f image2 \"{1}\"", inputArgument, outputPath, vf);
|
||||
var args = string.Format(CultureInfo.InvariantCulture, "-i {0} -threads 0 -v quiet -vf \"{2}\" -f image2 \"{1}\"", inputArgument, outputPath, vf);
|
||||
|
||||
var probeSizeArgument = EncodingHelper.GetProbeSizeArgument(1);
|
||||
var analyzeDurationArgument = EncodingHelper.GetAnalyzeDurationArgument(1);
|
||||
@@ -771,7 +787,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
|
||||
if (exitCode == -1)
|
||||
{
|
||||
var msg = string.Format("ffmpeg image extraction failed for {0}", inputArgument);
|
||||
var msg = string.Format(CultureInfo.InvariantCulture, "ffmpeg image extraction failed for {0}", inputArgument);
|
||||
|
||||
_logger.LogError(msg);
|
||||
|
||||
@@ -856,6 +872,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
if (dispose)
|
||||
{
|
||||
StopProcesses();
|
||||
_thumbnailResourcePool.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user