mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-04-10 04:12:10 +01:00
Merge branch 'dev' into project-updates
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using MediaBrowser.Model.Diagnostics;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -18,21 +19,21 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
_processFactory = processFactory;
|
||||
}
|
||||
|
||||
public Tuple<List<string>, List<string>> Validate(string encoderPath)
|
||||
public (IEnumerable<string> decoders, IEnumerable<string> encoders) Validate(string encoderPath)
|
||||
{
|
||||
_logger.LogInformation("Validating media encoder at {0}", encoderPath);
|
||||
_logger.LogInformation("Validating media encoder at {EncoderPath}", encoderPath);
|
||||
|
||||
var decoders = GetDecoders(encoderPath);
|
||||
var encoders = GetEncoders(encoderPath);
|
||||
var decoders = GetCodecs(encoderPath, Codec.Decoder);
|
||||
var encoders = GetCodecs(encoderPath, Codec.Encoder);
|
||||
|
||||
_logger.LogInformation("Encoder validation complete");
|
||||
|
||||
return new Tuple<List<string>, List<string>>(decoders, encoders);
|
||||
return (decoders, encoders);
|
||||
}
|
||||
|
||||
public bool ValidateVersion(string encoderAppPath, bool logOutput)
|
||||
{
|
||||
string output = string.Empty;
|
||||
string output = null;
|
||||
try
|
||||
{
|
||||
output = GetProcessOutput(encoderAppPath, "-version");
|
||||
@@ -71,20 +72,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<string> GetDecoders(string encoderAppPath)
|
||||
{
|
||||
string output = string.Empty;
|
||||
try
|
||||
{
|
||||
output = GetProcessOutput(encoderAppPath, "-decoders");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error detecting available decoders");
|
||||
}
|
||||
|
||||
var found = new List<string>();
|
||||
var required = new[]
|
||||
private static readonly string[] requiredDecoders = new[]
|
||||
{
|
||||
"mpeg2video",
|
||||
"h264_qsv",
|
||||
@@ -101,33 +89,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
"hevc"
|
||||
};
|
||||
|
||||
foreach (var codec in required)
|
||||
{
|
||||
var srch = " " + codec + " ";
|
||||
|
||||
if (output.IndexOf(srch, StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
_logger.LogInformation("Decoder available: " + codec);
|
||||
found.Add(codec);
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
private List<string> GetEncoders(string encoderAppPath)
|
||||
{
|
||||
string output = null;
|
||||
try
|
||||
{
|
||||
output = GetProcessOutput(encoderAppPath, "-encoders");
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
var found = new List<string>();
|
||||
var required = new[]
|
||||
private static readonly string[] requiredEncoders = new[]
|
||||
{
|
||||
"libx264",
|
||||
"libx265",
|
||||
@@ -151,32 +113,46 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
"ac3"
|
||||
};
|
||||
|
||||
output = output ?? string.Empty;
|
||||
private enum Codec
|
||||
{
|
||||
Encoder,
|
||||
Decoder
|
||||
}
|
||||
|
||||
var index = 0;
|
||||
|
||||
foreach (var codec in required)
|
||||
private IEnumerable<string> GetCodecs(string encoderAppPath, Codec codec)
|
||||
{
|
||||
string codecstr = codec == Codec.Encoder ? "encoders" : "decoders";
|
||||
string output = null;
|
||||
try
|
||||
{
|
||||
var srch = " " + codec + " ";
|
||||
|
||||
if (output.IndexOf(srch, StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
if (index < required.Length - 1)
|
||||
{
|
||||
_logger.LogInformation("Encoder available: " + codec);
|
||||
}
|
||||
|
||||
found.Add(codec);
|
||||
}
|
||||
index++;
|
||||
output = GetProcessOutput(encoderAppPath, "-" + codecstr);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error detecting available {Codec}", codecstr);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(output))
|
||||
{
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
var required = codec == Codec.Encoder ? requiredEncoders : requiredDecoders;
|
||||
|
||||
var found = Regex
|
||||
.Matches(output, @"^\s\S{6}\s(?<codec>[\w|-]+)\s+.+$", RegexOptions.Multiline)
|
||||
.Cast<Match>()
|
||||
.Select(x => x.Groups["codec"].Value)
|
||||
.Where(x => required.Contains(x));
|
||||
|
||||
_logger.LogInformation("Available {Codec}: {Codecs}", codecstr, found);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
private string GetProcessOutput(string path, string arguments)
|
||||
{
|
||||
var process = _processFactory.Create(new ProcessOptions
|
||||
IProcess process = _processFactory.Create(new ProcessOptions
|
||||
{
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
@@ -187,7 +163,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
RedirectStandardOutput = true
|
||||
});
|
||||
|
||||
_logger.LogInformation("Running {path} {arguments}", path, arguments);
|
||||
_logger.LogInformation("Running {Path} {Arguments}", path, arguments);
|
||||
|
||||
using (process)
|
||||
{
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Net;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MediaBrowser.MediaEncoding.Encoder
|
||||
{
|
||||
public class FontConfigLoader
|
||||
{
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IZipClient _zipClient;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
private readonly string[] _fontUrls =
|
||||
{
|
||||
"https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/ARIALUNI.7z"
|
||||
};
|
||||
|
||||
public FontConfigLoader(IHttpClient httpClient, IApplicationPaths appPaths, ILogger logger, IZipClient zipClient, IFileSystem fileSystem)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_appPaths = appPaths;
|
||||
_logger = logger;
|
||||
_zipClient = zipClient;
|
||||
_fileSystem = fileSystem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the fonts.
|
||||
/// </summary>
|
||||
/// <param name="targetPath">The target path.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public async Task DownloadFonts(string targetPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fontsDirectory = Path.Combine(targetPath, "fonts");
|
||||
|
||||
_fileSystem.CreateDirectory(fontsDirectory);
|
||||
|
||||
const string fontFilename = "ARIALUNI.TTF";
|
||||
|
||||
var fontFile = Path.Combine(fontsDirectory, fontFilename);
|
||||
|
||||
if (_fileSystem.FileExists(fontFile))
|
||||
{
|
||||
await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Kick this off, but no need to wait on it
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
await DownloadFontFile(fontsDirectory, fontFilename, new SimpleProgress<double>()).ConfigureAwait(false);
|
||||
|
||||
await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
// Don't let the server crash because of this
|
||||
_logger.LogError(ex, "Error downloading ffmpeg font files");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Don't let the server crash because of this
|
||||
_logger.LogError(ex, "Error writing ffmpeg font files");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads the font file.
|
||||
/// </summary>
|
||||
/// <param name="fontsDirectory">The fonts directory.</param>
|
||||
/// <param name="fontFilename">The font filename.</param>
|
||||
/// <returns>Task.</returns>
|
||||
private async Task DownloadFontFile(string fontsDirectory, string fontFilename, IProgress<double> progress)
|
||||
{
|
||||
var existingFile = _fileSystem
|
||||
.GetFilePaths(_appPaths.ProgramDataPath, true)
|
||||
.FirstOrDefault(i => string.Equals(fontFilename, Path.GetFileName(i), StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (existingFile != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_fileSystem.CopyFile(existingFile, Path.Combine(fontsDirectory, fontFilename), true);
|
||||
return;
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
// Log this, but don't let it fail the operation
|
||||
_logger.LogError(ex, "Error copying file");
|
||||
}
|
||||
}
|
||||
|
||||
string tempFile = null;
|
||||
|
||||
foreach (var url in _fontUrls)
|
||||
{
|
||||
progress.Report(0);
|
||||
|
||||
try
|
||||
{
|
||||
tempFile = await _httpClient.GetTempFile(new HttpRequestOptions
|
||||
{
|
||||
Url = url,
|
||||
Progress = progress
|
||||
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// The core can function without the font file, so handle this
|
||||
_logger.LogError(ex, "Failed to download ffmpeg font file from {url}", url);
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(tempFile))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Extract7zArchive(tempFile, fontsDirectory);
|
||||
|
||||
try
|
||||
{
|
||||
_fileSystem.DeleteFile(tempFile);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
// Log this, but don't let it fail the operation
|
||||
_logger.LogError(ex, "Error deleting temp file {path}", tempFile);
|
||||
}
|
||||
}
|
||||
private void Extract7zArchive(string archivePath, string targetPath)
|
||||
{
|
||||
_logger.LogInformation("Extracting {ArchivePath} to {TargetPath}", archivePath, targetPath);
|
||||
|
||||
_zipClient.ExtractAllFrom7z(archivePath, targetPath, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the font config file.
|
||||
/// </summary>
|
||||
/// <param name="fontsDirectory">The fonts directory.</param>
|
||||
/// <returns>Task.</returns>
|
||||
private async Task WriteFontConfigFile(string fontsDirectory)
|
||||
{
|
||||
const string fontConfigFilename = "fonts.conf";
|
||||
var fontConfigFile = Path.Combine(fontsDirectory, fontConfigFilename);
|
||||
|
||||
if (!_fileSystem.FileExists(fontConfigFile))
|
||||
{
|
||||
var contents = string.Format("<?xml version=\"1.0\"?><fontconfig><dir>{0}</dir><alias><family>Arial</family><prefer>Arial Unicode MS</prefer></alias></fontconfig>", fontsDirectory);
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(contents);
|
||||
|
||||
using (var fileStream = _fileSystem.GetFileStream(fontConfigFile, FileOpenMode.Create, FileAccessMode.Write,
|
||||
FileShareMode.Read, true))
|
||||
{
|
||||
await fileStream.WriteAsync(bytes, 0, bytes.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,13 +70,27 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
private readonly string _originalFFMpegPath;
|
||||
private readonly string _originalFFProbePath;
|
||||
private readonly int DefaultImageExtractionTimeoutMs;
|
||||
private readonly bool EnableEncoderFontFile;
|
||||
|
||||
private readonly IEnvironmentInfo _environmentInfo;
|
||||
|
||||
public MediaEncoder(ILogger logger, IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, bool hasExternalEncoder, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, Func<ISubtitleEncoder> subtitleEncoder, Func<IMediaSourceManager> mediaSourceManager, IHttpClient httpClient, IZipClient zipClient, IProcessFactory processFactory,
|
||||
public MediaEncoder(ILogger logger,
|
||||
IJsonSerializer jsonSerializer,
|
||||
string ffMpegPath,
|
||||
string ffProbePath,
|
||||
bool hasExternalEncoder,
|
||||
IServerConfigurationManager configurationManager,
|
||||
IFileSystem fileSystem,
|
||||
ILiveTvManager liveTvManager,
|
||||
IIsoManager isoManager,
|
||||
ILibraryManager libraryManager,
|
||||
IChannelManager channelManager,
|
||||
ISessionManager sessionManager,
|
||||
Func<ISubtitleEncoder> subtitleEncoder,
|
||||
Func<IMediaSourceManager> mediaSourceManager,
|
||||
IHttpClient httpClient,
|
||||
IZipClient zipClient,
|
||||
IProcessFactory processFactory,
|
||||
int defaultImageExtractionTimeoutMs,
|
||||
bool enableEncoderFontFile, IEnvironmentInfo environmentInfo)
|
||||
IEnvironmentInfo environmentInfo)
|
||||
{
|
||||
_logger = logger;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
@@ -93,7 +107,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
_zipClient = zipClient;
|
||||
_processFactory = processFactory;
|
||||
DefaultImageExtractionTimeoutMs = defaultImageExtractionTimeoutMs;
|
||||
EnableEncoderFontFile = enableEncoderFontFile;
|
||||
_environmentInfo = environmentInfo;
|
||||
FFProbePath = ffProbePath;
|
||||
FFMpegPath = ffMpegPath;
|
||||
@@ -175,18 +188,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
{
|
||||
var result = new EncoderValidator(_logger, _processFactory).Validate(FFMpegPath);
|
||||
|
||||
SetAvailableDecoders(result.Item1);
|
||||
SetAvailableEncoders(result.Item2);
|
||||
|
||||
if (EnableEncoderFontFile)
|
||||
{
|
||||
var directory = FileSystem.GetDirectoryName(FFMpegPath);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(directory) && FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.ProgramDataPath, directory))
|
||||
{
|
||||
new FontConfigLoader(_httpClient, ConfigurationManager.ApplicationPaths, _logger, _zipClient, FileSystem).DownloadFonts(directory).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
SetAvailableDecoders(result.decoders);
|
||||
SetAvailableEncoders(result.encoders);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,14 +404,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
}
|
||||
|
||||
private List<string> _encoders = new List<string>();
|
||||
public void SetAvailableEncoders(List<string> list)
|
||||
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(List<string> list)
|
||||
public void SetAvailableDecoders(IEnumerable<string> list)
|
||||
{
|
||||
_decoders = list.ToList();
|
||||
//_logger.Info("Supported decoders: {0}", string.Join(",", list.ToArray()));
|
||||
|
||||
Reference in New Issue
Block a user