mirror of https://github.com/jellyfin/jellyfin.git
Merge pull request #2680 from mark-monteiro/remove-common-process
Remove CommonProcess and ProcessFactory
This commit is contained in:
commit
3d611743ed
|
@ -30,7 +30,6 @@ using Emby.Server.Implementations.Configuration;
|
||||||
using Emby.Server.Implementations.Cryptography;
|
using Emby.Server.Implementations.Cryptography;
|
||||||
using Emby.Server.Implementations.Data;
|
using Emby.Server.Implementations.Data;
|
||||||
using Emby.Server.Implementations.Devices;
|
using Emby.Server.Implementations.Devices;
|
||||||
using Emby.Server.Implementations.Diagnostics;
|
|
||||||
using Emby.Server.Implementations.Dto;
|
using Emby.Server.Implementations.Dto;
|
||||||
using Emby.Server.Implementations.HttpServer;
|
using Emby.Server.Implementations.HttpServer;
|
||||||
using Emby.Server.Implementations.HttpServer.Security;
|
using Emby.Server.Implementations.HttpServer.Security;
|
||||||
|
@ -86,7 +85,6 @@ using MediaBrowser.MediaEncoding.BdInfo;
|
||||||
using MediaBrowser.Model.Activity;
|
using MediaBrowser.Model.Activity;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Cryptography;
|
using MediaBrowser.Model.Cryptography;
|
||||||
using MediaBrowser.Model.Diagnostics;
|
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
|
@ -337,8 +335,6 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
internal IImageEncoder ImageEncoder { get; private set; }
|
internal IImageEncoder ImageEncoder { get; private set; }
|
||||||
|
|
||||||
protected IProcessFactory ProcessFactory { get; private set; }
|
|
||||||
|
|
||||||
protected readonly IXmlSerializer XmlSerializer;
|
protected readonly IXmlSerializer XmlSerializer;
|
||||||
|
|
||||||
protected ISocketFactory SocketFactory { get; private set; }
|
protected ISocketFactory SocketFactory { get; private set; }
|
||||||
|
@ -680,9 +676,6 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
serviceCollection.AddSingleton(XmlSerializer);
|
serviceCollection.AddSingleton(XmlSerializer);
|
||||||
|
|
||||||
ProcessFactory = new ProcessFactory();
|
|
||||||
serviceCollection.AddSingleton(ProcessFactory);
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton(typeof(IStreamHelper), typeof(StreamHelper));
|
serviceCollection.AddSingleton(typeof(IStreamHelper), typeof(StreamHelper));
|
||||||
|
|
||||||
var cryptoProvider = new CryptographyProvider();
|
var cryptoProvider = new CryptographyProvider();
|
||||||
|
@ -743,7 +736,6 @@ namespace Emby.Server.Implementations
|
||||||
LoggerFactory.CreateLogger<MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(),
|
LoggerFactory.CreateLogger<MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(),
|
||||||
ServerConfigurationManager,
|
ServerConfigurationManager,
|
||||||
FileSystemManager,
|
FileSystemManager,
|
||||||
ProcessFactory,
|
|
||||||
LocalizationManager,
|
LocalizationManager,
|
||||||
() => SubtitleEncoder,
|
() => SubtitleEncoder,
|
||||||
startupConfig,
|
startupConfig,
|
||||||
|
@ -857,8 +849,7 @@ namespace Emby.Server.Implementations
|
||||||
FileSystemManager,
|
FileSystemManager,
|
||||||
MediaEncoder,
|
MediaEncoder,
|
||||||
HttpClient,
|
HttpClient,
|
||||||
MediaSourceManager,
|
MediaSourceManager);
|
||||||
ProcessFactory);
|
|
||||||
serviceCollection.AddSingleton(SubtitleEncoder);
|
serviceCollection.AddSingleton(SubtitleEncoder);
|
||||||
|
|
||||||
serviceCollection.AddSingleton(typeof(IResourceFileManager), typeof(ResourceFileManager));
|
serviceCollection.AddSingleton(typeof(IResourceFileManager), typeof(ResourceFileManager));
|
||||||
|
@ -1678,15 +1669,17 @@ namespace Emby.Server.Implementations
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
var process = ProcessFactory.Create(new ProcessOptions
|
var process = new Process
|
||||||
{
|
{
|
||||||
FileName = url,
|
StartInfo = new ProcessStartInfo
|
||||||
EnableRaisingEvents = true,
|
{
|
||||||
UseShellExecute = true,
|
FileName = url,
|
||||||
ErrorDialog = false
|
UseShellExecute = true,
|
||||||
});
|
ErrorDialog = false
|
||||||
|
},
|
||||||
process.Exited += ProcessExited;
|
EnableRaisingEvents = true
|
||||||
|
};
|
||||||
|
process.Exited += (sender, args) => ((Process)sender).Dispose();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -1699,11 +1692,6 @@ namespace Emby.Server.Implementations
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ProcessExited(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
((IProcess)sender).Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void EnableLoopback(string appName)
|
public virtual void EnableLoopback(string appName)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,152 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MediaBrowser.Model.Diagnostics;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Diagnostics
|
|
||||||
{
|
|
||||||
public class CommonProcess : IProcess
|
|
||||||
{
|
|
||||||
private readonly Process _process;
|
|
||||||
|
|
||||||
private bool _disposed = false;
|
|
||||||
private bool _hasExited;
|
|
||||||
|
|
||||||
public CommonProcess(ProcessOptions options)
|
|
||||||
{
|
|
||||||
StartInfo = options;
|
|
||||||
|
|
||||||
var startInfo = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
Arguments = options.Arguments,
|
|
||||||
FileName = options.FileName,
|
|
||||||
WorkingDirectory = options.WorkingDirectory,
|
|
||||||
UseShellExecute = options.UseShellExecute,
|
|
||||||
CreateNoWindow = options.CreateNoWindow,
|
|
||||||
RedirectStandardError = options.RedirectStandardError,
|
|
||||||
RedirectStandardInput = options.RedirectStandardInput,
|
|
||||||
RedirectStandardOutput = options.RedirectStandardOutput,
|
|
||||||
ErrorDialog = options.ErrorDialog
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
if (options.IsHidden)
|
|
||||||
{
|
|
||||||
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
_process = new Process
|
|
||||||
{
|
|
||||||
StartInfo = startInfo
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.EnableRaisingEvents)
|
|
||||||
{
|
|
||||||
_process.EnableRaisingEvents = true;
|
|
||||||
_process.Exited += OnProcessExited;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public event EventHandler Exited;
|
|
||||||
|
|
||||||
public ProcessOptions StartInfo { get; }
|
|
||||||
|
|
||||||
public StreamWriter StandardInput => _process.StandardInput;
|
|
||||||
|
|
||||||
public StreamReader StandardError => _process.StandardError;
|
|
||||||
|
|
||||||
public StreamReader StandardOutput => _process.StandardOutput;
|
|
||||||
|
|
||||||
public int ExitCode => _process.ExitCode;
|
|
||||||
|
|
||||||
private bool HasExited
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_hasExited)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_hasExited = _process.HasExited;
|
|
||||||
}
|
|
||||||
catch (InvalidOperationException)
|
|
||||||
{
|
|
||||||
_hasExited = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _hasExited;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Start()
|
|
||||||
{
|
|
||||||
_process.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Kill()
|
|
||||||
{
|
|
||||||
_process.Kill();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool WaitForExit(int timeMs)
|
|
||||||
{
|
|
||||||
return _process.WaitForExit(timeMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<bool> WaitForExitAsync(int timeMs)
|
|
||||||
{
|
|
||||||
// Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true.
|
|
||||||
|
|
||||||
if (HasExited)
|
|
||||||
{
|
|
||||||
return Task.FromResult(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
timeMs = Math.Max(0, timeMs);
|
|
||||||
|
|
||||||
var tcs = new TaskCompletionSource<bool>();
|
|
||||||
|
|
||||||
var cancellationToken = new CancellationTokenSource(timeMs).Token;
|
|
||||||
|
|
||||||
_process.Exited += (sender, args) => tcs.TrySetResult(true);
|
|
||||||
|
|
||||||
cancellationToken.Register(() => tcs.TrySetResult(HasExited));
|
|
||||||
|
|
||||||
return tcs.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Dispose(true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
_process?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
_disposed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnProcessExited(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
_hasExited = true;
|
|
||||||
Exited?.Invoke(this, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using MediaBrowser.Model.Diagnostics;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Diagnostics
|
|
||||||
{
|
|
||||||
public class ProcessFactory : IProcessFactory
|
|
||||||
{
|
|
||||||
public IProcess Create(ProcessOptions options)
|
|
||||||
{
|
|
||||||
return new CommonProcess(options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,6 +3,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -25,7 +26,6 @@ using MediaBrowser.Controller.LiveTv;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Diagnostics;
|
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
|
@ -61,7 +61,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IProviderManager _providerManager;
|
private readonly IProviderManager _providerManager;
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
private readonly IProcessFactory _processFactory;
|
|
||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
private readonly IStreamHelper _streamHelper;
|
private readonly IStreamHelper _streamHelper;
|
||||||
|
|
||||||
|
@ -88,8 +87,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
ILibraryMonitor libraryMonitor,
|
ILibraryMonitor libraryMonitor,
|
||||||
IProviderManager providerManager,
|
IProviderManager providerManager,
|
||||||
IMediaEncoder mediaEncoder,
|
IMediaEncoder mediaEncoder)
|
||||||
IProcessFactory processFactory)
|
|
||||||
{
|
{
|
||||||
Current = this;
|
Current = this;
|
||||||
|
|
||||||
|
@ -102,7 +100,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
_libraryMonitor = libraryMonitor;
|
_libraryMonitor = libraryMonitor;
|
||||||
_providerManager = providerManager;
|
_providerManager = providerManager;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
_processFactory = processFactory;
|
|
||||||
_liveTvManager = (LiveTvManager)liveTvManager;
|
_liveTvManager = (LiveTvManager)liveTvManager;
|
||||||
_jsonSerializer = jsonSerializer;
|
_jsonSerializer = jsonSerializer;
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
|
@ -1662,7 +1659,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
{
|
{
|
||||||
if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http))
|
if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http))
|
||||||
{
|
{
|
||||||
return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _processFactory, _config);
|
return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _config);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new DirectRecorder(_logger, _httpClient, _streamHelper);
|
return new DirectRecorder(_logger, _httpClient, _streamHelper);
|
||||||
|
@ -1683,16 +1680,19 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var process = _processFactory.Create(new ProcessOptions
|
var process = new Process
|
||||||
{
|
{
|
||||||
Arguments = GetPostProcessArguments(path, options.RecordingPostProcessorArguments),
|
StartInfo = new ProcessStartInfo
|
||||||
CreateNoWindow = true,
|
{
|
||||||
EnableRaisingEvents = true,
|
Arguments = GetPostProcessArguments(path, options.RecordingPostProcessorArguments),
|
||||||
ErrorDialog = false,
|
CreateNoWindow = true,
|
||||||
FileName = options.RecordingPostProcessor,
|
ErrorDialog = false,
|
||||||
IsHidden = true,
|
FileName = options.RecordingPostProcessor,
|
||||||
UseShellExecute = false
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
});
|
UseShellExecute = false
|
||||||
|
},
|
||||||
|
EnableRaisingEvents = true
|
||||||
|
};
|
||||||
|
|
||||||
_logger.LogInformation("Running recording post processor {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
_logger.LogInformation("Running recording post processor {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||||
|
|
||||||
|
@ -1712,11 +1712,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
|
|
||||||
private void Process_Exited(object sender, EventArgs e)
|
private void Process_Exited(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
using (var process = (IProcess)sender)
|
using (var process = (Process)sender)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Recording post-processing script completed with exit code {ExitCode}", process.ExitCode);
|
_logger.LogInformation("Recording post-processing script completed with exit code {ExitCode}", process.ExitCode);
|
||||||
|
|
||||||
process.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
@ -13,7 +14,6 @@ using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Diagnostics;
|
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
|
@ -29,8 +29,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
private bool _hasExited;
|
private bool _hasExited;
|
||||||
private Stream _logFileStream;
|
private Stream _logFileStream;
|
||||||
private string _targetPath;
|
private string _targetPath;
|
||||||
private IProcess _process;
|
private Process _process;
|
||||||
private readonly IProcessFactory _processFactory;
|
|
||||||
private readonly IJsonSerializer _json;
|
private readonly IJsonSerializer _json;
|
||||||
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
|
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
|
@ -40,14 +39,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
IMediaEncoder mediaEncoder,
|
IMediaEncoder mediaEncoder,
|
||||||
IServerApplicationPaths appPaths,
|
IServerApplicationPaths appPaths,
|
||||||
IJsonSerializer json,
|
IJsonSerializer json,
|
||||||
IProcessFactory processFactory,
|
|
||||||
IServerConfigurationManager config)
|
IServerConfigurationManager config)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
_appPaths = appPaths;
|
_appPaths = appPaths;
|
||||||
_json = json;
|
_json = json;
|
||||||
_processFactory = processFactory;
|
|
||||||
_config = config;
|
_config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +76,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
_targetPath = targetFile;
|
_targetPath = targetFile;
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
|
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
|
||||||
|
|
||||||
var process = _processFactory.Create(new ProcessOptions
|
var processStartInfo = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
CreateNoWindow = true,
|
CreateNoWindow = true,
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
|
@ -90,14 +87,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
FileName = _mediaEncoder.EncoderPath,
|
FileName = _mediaEncoder.EncoderPath,
|
||||||
Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile, duration),
|
Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile, duration),
|
||||||
|
|
||||||
IsHidden = true,
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
ErrorDialog = false,
|
ErrorDialog = false
|
||||||
EnableRaisingEvents = true
|
};
|
||||||
});
|
|
||||||
|
|
||||||
_process = process;
|
var commandLineLogMessage = processStartInfo.FileName + " " + processStartInfo.Arguments;
|
||||||
|
|
||||||
var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
|
|
||||||
_logger.LogInformation(commandLineLogMessage);
|
_logger.LogInformation(commandLineLogMessage);
|
||||||
|
|
||||||
var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid() + ".txt");
|
var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid() + ".txt");
|
||||||
|
@ -109,16 +103,21 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
|
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
|
||||||
_logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length);
|
_logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length);
|
||||||
|
|
||||||
process.Exited += (sender, args) => OnFfMpegProcessExited(process, inputFile);
|
_process = new Process
|
||||||
|
{
|
||||||
|
StartInfo = processStartInfo,
|
||||||
|
EnableRaisingEvents = true
|
||||||
|
};
|
||||||
|
_process.Exited += (sender, args) => OnFfMpegProcessExited(_process, inputFile);
|
||||||
|
|
||||||
process.Start();
|
_process.Start();
|
||||||
|
|
||||||
cancellationToken.Register(Stop);
|
cancellationToken.Register(Stop);
|
||||||
|
|
||||||
onStarted();
|
onStarted();
|
||||||
|
|
||||||
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
|
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
|
||||||
StartStreamingLog(process.StandardError.BaseStream, _logFileStream);
|
StartStreamingLog(_process.StandardError.BaseStream, _logFileStream);
|
||||||
|
|
||||||
_logger.LogInformation("ffmpeg recording process started for {0}", _targetPath);
|
_logger.LogInformation("ffmpeg recording process started for {0}", _targetPath);
|
||||||
|
|
||||||
|
@ -292,30 +291,33 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes the exited.
|
/// Processes the exited.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnFfMpegProcessExited(IProcess process, string inputFile)
|
private void OnFfMpegProcessExited(Process process, string inputFile)
|
||||||
{
|
{
|
||||||
_hasExited = true;
|
using (process)
|
||||||
|
|
||||||
_logFileStream?.Dispose();
|
|
||||||
_logFileStream = null;
|
|
||||||
|
|
||||||
var exitCode = process.ExitCode;
|
|
||||||
|
|
||||||
_logger.LogInformation("FFMpeg recording exited with code {ExitCode} for {Path}", exitCode, _targetPath);
|
|
||||||
|
|
||||||
if (exitCode == 0)
|
|
||||||
{
|
{
|
||||||
_taskCompletionSource.TrySetResult(true);
|
_hasExited = true;
|
||||||
}
|
|
||||||
else
|
_logFileStream?.Dispose();
|
||||||
{
|
_logFileStream = null;
|
||||||
_taskCompletionSource.TrySetException(
|
|
||||||
new Exception(
|
var exitCode = process.ExitCode;
|
||||||
string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
_logger.LogInformation("FFMpeg recording exited with code {ExitCode} for {Path}", exitCode, _targetPath);
|
||||||
"Recording for {0} failed. Exit code {1}",
|
|
||||||
_targetPath,
|
if (exitCode == 0)
|
||||||
exitCode)));
|
{
|
||||||
|
_taskCompletionSource.TrySetResult(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_taskCompletionSource.TrySetException(
|
||||||
|
new Exception(
|
||||||
|
string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"Recording for {0} failed. Exit code {1}",
|
||||||
|
_targetPath,
|
||||||
|
exitCode)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Common.Extensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for <see cref="Process"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class ProcessExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Asynchronously wait for the process to exit.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="process">The process to wait for.</param>
|
||||||
|
/// <param name="timeout">The duration to wait before cancelling waiting for the task.</param>
|
||||||
|
/// <returns>True if the task exited normally, false if the timeout elapsed before the process exited.</returns>
|
||||||
|
/// <exception cref="InvalidOperationException">If <see cref="Process.EnableRaisingEvents"/> is not set to true for the process.</exception>
|
||||||
|
public static async Task<bool> WaitForExitAsync(this Process process, TimeSpan timeout)
|
||||||
|
{
|
||||||
|
using (var cancelTokenSource = new CancellationTokenSource(timeout))
|
||||||
|
{
|
||||||
|
return await WaitForExitAsync(process, cancelTokenSource.Token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Asynchronously wait for the process to exit.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="process">The process to wait for.</param>
|
||||||
|
/// <param name="cancelToken">A <see cref="CancellationToken"/> to observe while waiting for the process to exit.</param>
|
||||||
|
/// <returns>True if the task exited normally, false if cancelled before the process exited.</returns>
|
||||||
|
public static async Task<bool> WaitForExitAsync(this Process process, CancellationToken cancelToken)
|
||||||
|
{
|
||||||
|
if (!process.EnableRaisingEvents)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("EnableRisingEvents must be enabled to async wait for a task to exit.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an event handler for the process exit event
|
||||||
|
var tcs = new TaskCompletionSource<bool>();
|
||||||
|
process.Exited += (sender, args) => tcs.TrySetResult(true);
|
||||||
|
|
||||||
|
// Return immediately if the process has already exited
|
||||||
|
if (process.HasExitedSafe())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register with the cancellation token then await
|
||||||
|
using (var cancelRegistration = cancelToken.Register(() => tcs.TrySetResult(process.HasExitedSafe())))
|
||||||
|
{
|
||||||
|
return await tcs.Task.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the associated process has been terminated using
|
||||||
|
/// <see cref="Process.HasExited"/>. This is safe to call even if there is no operating system process
|
||||||
|
/// associated with the <see cref="Process"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="process">The process to check the exit status for.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// True if the operating system process referenced by the <see cref="Process"/> component has
|
||||||
|
/// terminated, or if there is no associated operating system process; otherwise, false.
|
||||||
|
/// </returns>
|
||||||
|
private static bool HasExitedSafe(this Process process)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return process.HasExited;
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -155,48 +155,45 @@ namespace MediaBrowser.MediaEncoding.Attachments
|
||||||
inputPath,
|
inputPath,
|
||||||
attachmentStreamIndex,
|
attachmentStreamIndex,
|
||||||
outputPath);
|
outputPath);
|
||||||
var startInfo = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
Arguments = processArgs,
|
|
||||||
FileName = _mediaEncoder.EncoderPath,
|
|
||||||
UseShellExecute = false,
|
|
||||||
CreateNoWindow = true,
|
|
||||||
WindowStyle = ProcessWindowStyle.Hidden,
|
|
||||||
ErrorDialog = false
|
|
||||||
};
|
|
||||||
var process = new Process
|
|
||||||
{
|
|
||||||
StartInfo = startInfo
|
|
||||||
};
|
|
||||||
|
|
||||||
_logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
int exitCode;
|
||||||
|
|
||||||
process.Start();
|
using (var process = new Process
|
||||||
|
|
||||||
var processTcs = new TaskCompletionSource<bool>();
|
|
||||||
process.EnableRaisingEvents = true;
|
|
||||||
process.Exited += (sender, args) => processTcs.TrySetResult(true);
|
|
||||||
var unregister = cancellationToken.Register(() => processTcs.TrySetResult(process.HasExited));
|
|
||||||
var ranToCompletion = await processTcs.Task.ConfigureAwait(false);
|
|
||||||
unregister.Dispose();
|
|
||||||
|
|
||||||
if (!ranToCompletion)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Killing ffmpeg attachment extraction process");
|
StartInfo = new ProcessStartInfo
|
||||||
process.Kill();
|
{
|
||||||
}
|
Arguments = processArgs,
|
||||||
catch (Exception ex)
|
FileName = _mediaEncoder.EncoderPath,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
|
ErrorDialog = false
|
||||||
|
},
|
||||||
|
EnableRaisingEvents = true
|
||||||
|
})
|
||||||
|
{
|
||||||
|
_logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||||
|
|
||||||
|
process.Start();
|
||||||
|
|
||||||
|
var ranToCompletion = await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!ranToCompletion)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error killing attachment extraction process");
|
try
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Killing ffmpeg attachment extraction process");
|
||||||
|
process.Kill();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error killing attachment extraction process");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exitCode = ranToCompletion ? process.ExitCode : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var exitCode = ranToCompletion ? process.ExitCode : -1;
|
|
||||||
|
|
||||||
process.Dispose();
|
|
||||||
|
|
||||||
var failed = false;
|
var failed = false;
|
||||||
|
|
||||||
if (exitCode != 0)
|
if (exitCode != 0)
|
||||||
|
|
|
@ -13,7 +13,6 @@ using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.MediaEncoding.Probing;
|
using MediaBrowser.MediaEncoding.Probing;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Diagnostics;
|
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
|
@ -22,6 +21,7 @@ using MediaBrowser.Model.MediaInfo;
|
||||||
using MediaBrowser.Model.System;
|
using MediaBrowser.Model.System;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace MediaBrowser.MediaEncoding.Encoder
|
namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
{
|
{
|
||||||
|
@ -38,7 +38,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IServerConfigurationManager _configurationManager;
|
private readonly IServerConfigurationManager _configurationManager;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly IProcessFactory _processFactory;
|
|
||||||
private readonly ILocalizationManager _localization;
|
private readonly ILocalizationManager _localization;
|
||||||
private readonly Func<ISubtitleEncoder> _subtitleEncoder;
|
private readonly Func<ISubtitleEncoder> _subtitleEncoder;
|
||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
|
@ -58,7 +57,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
ILogger<MediaEncoder> logger,
|
ILogger<MediaEncoder> logger,
|
||||||
IServerConfigurationManager configurationManager,
|
IServerConfigurationManager configurationManager,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IProcessFactory processFactory,
|
|
||||||
ILocalizationManager localization,
|
ILocalizationManager localization,
|
||||||
Func<ISubtitleEncoder> subtitleEncoder,
|
Func<ISubtitleEncoder> subtitleEncoder,
|
||||||
IConfiguration configuration,
|
IConfiguration configuration,
|
||||||
|
@ -67,7 +65,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_configurationManager = configurationManager;
|
_configurationManager = configurationManager;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_processFactory = processFactory;
|
|
||||||
_localization = localization;
|
_localization = localization;
|
||||||
_startupOptionFFmpegPath = startupOptionsFFmpegPath;
|
_startupOptionFFmpegPath = startupOptionsFFmpegPath;
|
||||||
_subtitleEncoder = subtitleEncoder;
|
_subtitleEncoder = subtitleEncoder;
|
||||||
|
@ -362,30 +359,33 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
: "{0} -i {1} -threads 0 -v warning -print_format json -show_streams -show_format";
|
: "{0} -i {1} -threads 0 -v warning -print_format json -show_streams -show_format";
|
||||||
args = string.Format(args, probeSizeArgument, inputPath).Trim();
|
args = string.Format(args, probeSizeArgument, inputPath).Trim();
|
||||||
|
|
||||||
var process = _processFactory.Create(new ProcessOptions
|
var process = new Process
|
||||||
{
|
{
|
||||||
CreateNoWindow = true,
|
StartInfo = new ProcessStartInfo
|
||||||
UseShellExecute = false,
|
{
|
||||||
|
CreateNoWindow = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
|
||||||
// Must consume both or ffmpeg may hang due to deadlocks. See comments below.
|
// Must consume both or ffmpeg may hang due to deadlocks. See comments below.
|
||||||
RedirectStandardOutput = true,
|
RedirectStandardOutput = true,
|
||||||
|
|
||||||
FileName = _ffprobePath,
|
FileName = _ffprobePath,
|
||||||
Arguments = args,
|
Arguments = args,
|
||||||
|
|
||||||
|
|
||||||
IsHidden = true,
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
ErrorDialog = false,
|
ErrorDialog = false,
|
||||||
|
},
|
||||||
EnableRaisingEvents = true
|
EnableRaisingEvents = true
|
||||||
});
|
};
|
||||||
|
|
||||||
if (forceEnableLogging)
|
if (forceEnableLogging)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
_logger.LogInformation("{ProcessFileName} {ProcessArgs}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
_logger.LogDebug("{ProcessFileName} {ProcessArgs}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var processWrapper = new ProcessWrapper(process, this))
|
using (var processWrapper = new ProcessWrapper(process, this))
|
||||||
|
@ -571,18 +571,21 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var process = _processFactory.Create(new ProcessOptions
|
var process = new Process
|
||||||
{
|
{
|
||||||
CreateNoWindow = true,
|
StartInfo = new ProcessStartInfo
|
||||||
UseShellExecute = false,
|
{
|
||||||
FileName = _ffmpegPath,
|
CreateNoWindow = true,
|
||||||
Arguments = args,
|
UseShellExecute = false,
|
||||||
IsHidden = true,
|
FileName = _ffmpegPath,
|
||||||
ErrorDialog = false,
|
Arguments = args,
|
||||||
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
|
ErrorDialog = false,
|
||||||
|
},
|
||||||
EnableRaisingEvents = true
|
EnableRaisingEvents = true
|
||||||
});
|
};
|
||||||
|
|
||||||
_logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
_logger.LogDebug("{ProcessFileName} {ProcessArguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||||
|
|
||||||
using (var processWrapper = new ProcessWrapper(process, this))
|
using (var processWrapper = new ProcessWrapper(process, this))
|
||||||
{
|
{
|
||||||
|
@ -599,7 +602,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
timeoutMs = DefaultImageExtractionTimeout;
|
timeoutMs = DefaultImageExtractionTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
ranToCompletion = await process.WaitForExitAsync(timeoutMs).ConfigureAwait(false);
|
ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMilliseconds(timeoutMs)).ConfigureAwait(false);
|
||||||
|
|
||||||
if (!ranToCompletion)
|
if (!ranToCompletion)
|
||||||
{
|
{
|
||||||
|
@ -700,23 +703,27 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var process = _processFactory.Create(new ProcessOptions
|
var processStartInfo = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
CreateNoWindow = true,
|
CreateNoWindow = true,
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
FileName = _ffmpegPath,
|
FileName = _ffmpegPath,
|
||||||
Arguments = args,
|
Arguments = args,
|
||||||
IsHidden = true,
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
ErrorDialog = false,
|
ErrorDialog = false
|
||||||
EnableRaisingEvents = true
|
};
|
||||||
});
|
|
||||||
|
|
||||||
_logger.LogInformation(process.StartInfo.FileName + " " + process.StartInfo.Arguments);
|
_logger.LogInformation(processStartInfo.FileName + " " + processStartInfo.Arguments);
|
||||||
|
|
||||||
await _thumbnailResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
await _thumbnailResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
bool ranToCompletion = false;
|
bool ranToCompletion = false;
|
||||||
|
|
||||||
|
var process = new Process
|
||||||
|
{
|
||||||
|
StartInfo = processStartInfo,
|
||||||
|
EnableRaisingEvents = true
|
||||||
|
};
|
||||||
using (var processWrapper = new ProcessWrapper(process, this))
|
using (var processWrapper = new ProcessWrapper(process, this))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -732,7 +739,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
|
|
||||||
while (isResponsive)
|
while (isResponsive)
|
||||||
{
|
{
|
||||||
if (await process.WaitForExitAsync(30000).ConfigureAwait(false))
|
if (await process.WaitForExitAsync(TimeSpan.FromSeconds(30)).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
ranToCompletion = true;
|
ranToCompletion = true;
|
||||||
break;
|
break;
|
||||||
|
@ -949,14 +956,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
|
|
||||||
private bool _disposed = false;
|
private bool _disposed = false;
|
||||||
|
|
||||||
public ProcessWrapper(IProcess process, MediaEncoder mediaEncoder)
|
public ProcessWrapper(Process process, MediaEncoder mediaEncoder)
|
||||||
{
|
{
|
||||||
Process = process;
|
Process = process;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
Process.Exited += OnProcessExited;
|
Process.Exited += OnProcessExited;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IProcess Process { get; }
|
public Process Process { get; }
|
||||||
|
|
||||||
public bool HasExited { get; private set; }
|
public bool HasExited { get; private set; }
|
||||||
|
|
||||||
|
@ -964,7 +971,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
|
|
||||||
void OnProcessExited(object sender, EventArgs e)
|
void OnProcessExited(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var process = (IProcess)sender;
|
var process = (Process)sender;
|
||||||
|
|
||||||
HasExited = true;
|
HasExited = true;
|
||||||
|
|
||||||
|
@ -979,7 +986,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
DisposeProcess(process);
|
DisposeProcess(process);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisposeProcess(IProcess process)
|
private void DisposeProcess(Process process)
|
||||||
{
|
{
|
||||||
lock (_mediaEncoder._runningProcessesLock)
|
lock (_mediaEncoder._runningProcessesLock)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -12,7 +13,6 @@ using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Model.Diagnostics;
|
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
@ -31,7 +31,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
private readonly IProcessFactory _processFactory;
|
|
||||||
|
|
||||||
public SubtitleEncoder(
|
public SubtitleEncoder(
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
|
@ -40,8 +39,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IMediaEncoder mediaEncoder,
|
IMediaEncoder mediaEncoder,
|
||||||
IHttpClient httpClient,
|
IHttpClient httpClient,
|
||||||
IMediaSourceManager mediaSourceManager,
|
IMediaSourceManager mediaSourceManager)
|
||||||
IProcessFactory processFactory)
|
|
||||||
{
|
{
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
@ -50,7 +48,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
_processFactory = processFactory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles");
|
private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles");
|
||||||
|
@ -429,50 +426,54 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
encodingParam = " -sub_charenc " + encodingParam;
|
encodingParam = " -sub_charenc " + encodingParam;
|
||||||
}
|
}
|
||||||
|
|
||||||
var process = _processFactory.Create(new ProcessOptions
|
int exitCode;
|
||||||
|
|
||||||
|
using (var process = new Process
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
CreateNoWindow = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
FileName = _mediaEncoder.EncoderPath,
|
||||||
|
Arguments = string.Format("{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
|
||||||
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
|
ErrorDialog = false
|
||||||
|
},
|
||||||
|
EnableRaisingEvents = true
|
||||||
|
})
|
||||||
{
|
{
|
||||||
CreateNoWindow = true,
|
_logger.LogInformation("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||||
UseShellExecute = false,
|
|
||||||
FileName = _mediaEncoder.EncoderPath,
|
|
||||||
Arguments = string.Format("{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
|
|
||||||
EnableRaisingEvents = true,
|
|
||||||
IsHidden = true,
|
|
||||||
ErrorDialog = false
|
|
||||||
});
|
|
||||||
|
|
||||||
_logger.LogInformation("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
process.Start();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error starting ffmpeg");
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
var ranToCompletion = await process.WaitForExitAsync(300000).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (!ranToCompletion)
|
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Killing ffmpeg subtitle conversion process");
|
process.Start();
|
||||||
|
|
||||||
process.Kill();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error killing subtitle conversion process");
|
_logger.LogError(ex, "Error starting ffmpeg");
|
||||||
|
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(5)).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!ranToCompletion)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Killing ffmpeg subtitle conversion process");
|
||||||
|
|
||||||
|
process.Kill();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error killing subtitle conversion process");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exitCode = ranToCompletion ? process.ExitCode : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var exitCode = ranToCompletion ? process.ExitCode : -1;
|
|
||||||
|
|
||||||
process.Dispose();
|
|
||||||
|
|
||||||
var failed = false;
|
var failed = false;
|
||||||
|
|
||||||
if (exitCode == -1)
|
if (exitCode == -1)
|
||||||
|
@ -578,50 +579,54 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
outputCodec,
|
outputCodec,
|
||||||
outputPath);
|
outputPath);
|
||||||
|
|
||||||
var process = _processFactory.Create(new ProcessOptions
|
int exitCode;
|
||||||
|
|
||||||
|
using (var process = new Process
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
CreateNoWindow = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
FileName = _mediaEncoder.EncoderPath,
|
||||||
|
Arguments = processArgs,
|
||||||
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
|
ErrorDialog = false
|
||||||
|
},
|
||||||
|
EnableRaisingEvents = true
|
||||||
|
})
|
||||||
{
|
{
|
||||||
CreateNoWindow = true,
|
_logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||||
UseShellExecute = false,
|
|
||||||
EnableRaisingEvents = true,
|
|
||||||
FileName = _mediaEncoder.EncoderPath,
|
|
||||||
Arguments = processArgs,
|
|
||||||
IsHidden = true,
|
|
||||||
ErrorDialog = false
|
|
||||||
});
|
|
||||||
|
|
||||||
_logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
process.Start();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error starting ffmpeg");
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
var ranToCompletion = await process.WaitForExitAsync(300000).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (!ranToCompletion)
|
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Killing ffmpeg subtitle extraction process");
|
process.Start();
|
||||||
|
|
||||||
process.Kill();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error killing subtitle extraction process");
|
_logger.LogError(ex, "Error starting ffmpeg");
|
||||||
|
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(5)).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!ranToCompletion)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Killing ffmpeg subtitle extraction process");
|
||||||
|
|
||||||
|
process.Kill();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error killing subtitle extraction process");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exitCode = ranToCompletion ? process.ExitCode : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var exitCode = ranToCompletion ? process.ExitCode : -1;
|
|
||||||
|
|
||||||
process.Dispose();
|
|
||||||
|
|
||||||
var failed = false;
|
var failed = false;
|
||||||
|
|
||||||
if (exitCode == -1)
|
if (exitCode == -1)
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Diagnostics
|
|
||||||
{
|
|
||||||
public interface IProcess : IDisposable
|
|
||||||
{
|
|
||||||
event EventHandler Exited;
|
|
||||||
|
|
||||||
void Kill();
|
|
||||||
bool WaitForExit(int timeMs);
|
|
||||||
Task<bool> WaitForExitAsync(int timeMs);
|
|
||||||
int ExitCode { get; }
|
|
||||||
void Start();
|
|
||||||
StreamWriter StandardInput { get; }
|
|
||||||
StreamReader StandardError { get; }
|
|
||||||
StreamReader StandardOutput { get; }
|
|
||||||
ProcessOptions StartInfo { get; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Diagnostics
|
|
||||||
{
|
|
||||||
public interface IProcessFactory
|
|
||||||
{
|
|
||||||
IProcess Create(ProcessOptions options);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ProcessOptions
|
|
||||||
{
|
|
||||||
public string FileName { get; set; }
|
|
||||||
public string Arguments { get; set; }
|
|
||||||
public string WorkingDirectory { get; set; }
|
|
||||||
public bool CreateNoWindow { get; set; }
|
|
||||||
public bool UseShellExecute { get; set; }
|
|
||||||
public bool EnableRaisingEvents { get; set; }
|
|
||||||
public bool ErrorDialog { get; set; }
|
|
||||||
public bool RedirectStandardError { get; set; }
|
|
||||||
public bool RedirectStandardInput { get; set; }
|
|
||||||
public bool RedirectStandardOutput { get; set; }
|
|
||||||
public bool IsHidden { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue